diff --git a/.env.example b/.env.example index 2031f78..fd7e632 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,10 @@ CHATGPT_APIKEY=your-openai-api-key CHATGPT_MODEL=gpt-3.5-turbo CHATGPT_IMAGEAPIURL=https://api.openai.com/v1/images/generations +# InVideo AI Configuration +INVIDEO__APIURL=https://pro-api.invideo.io/v1/generate-video +INVIDEO__APIKEY=your-invideo-api-key + # DigitalOcean Spaces (S3) Configuration S3__ACCESSKEY=your-digitalocean-spaces-access-key S3__SECRETKEY=your-digitalocean-spaces-secret-key diff --git a/docker-compose.yml b/docker-compose.yml index 5f6522f..a1e37c8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,6 +14,8 @@ services: - ChatGPT__ApiUrl=${CHATGPT_APIURL} - ChatGPT__Model=${CHATGPT_MODEL} - ChatGPT__ImageApiUrl=${CHATGPT_IMAGEAPIURL} + - InVideo__ApiUrl=${INVIDEO__APIURL} + - InVideo__ApiKey=${INVIDEO__APIKEY} - MailerSend__MailSender=${MAILERSEND__MAILSENDER} - MailerSend__ApiURL=${MAILERSEND__APIURL} - MailerSend__ApiToken=${MAILERSEND__APITOKEN} diff --git a/zTools.API/Controllers/InVideoController.cs b/zTools.API/Controllers/InVideoController.cs new file mode 100644 index 0000000..b1bc8b8 --- /dev/null +++ b/zTools.API/Controllers/InVideoController.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using zTools.Domain.Services.Interfaces; +using zTools.DTO.InVideo; +using System; +using System.Threading.Tasks; + +namespace zTools.API.Controllers +{ + [Route("[controller]")] + [ApiController] + public class InVideoController : ControllerBase + { + private readonly IInVideoService _inVideoService; + private readonly ILogger _logger; + + public InVideoController(IInVideoService inVideoService, ILogger logger) + { + _inVideoService = inVideoService; + _logger = logger; + } + + [HttpPost("generateVideo")] + public async Task> GenerateVideo([FromBody] InVideoRequest request) + { + try + { + _logger.LogInformation("Generating video with InVideo AI"); + var response = await _inVideoService.GenerateVideoAsync(request); + _logger.LogInformation("InVideo AI video generated successfully"); + return Ok(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error generating video with InVideo AI"); + return StatusCode(500, ex.Message); + } + } + } +} diff --git a/zTools.API/Startup.cs b/zTools.API/Startup.cs index 0280fd8..62da0e0 100644 --- a/zTools.API/Startup.cs +++ b/zTools.API/Startup.cs @@ -29,6 +29,7 @@ public void ConfigureServices(IServiceCollection services) services.Configure(Configuration.GetSection("MailerSend")); services.Configure(Configuration.GetSection("S3")); services.Configure(Configuration.GetSection("ChatGPT")); + services.Configure(Configuration.GetSection("InVideo")); Initializer.Configure(services, Configuration.GetConnectionString("NAuthContext")); diff --git a/zTools.API/appsettings.Template.json b/zTools.API/appsettings.Template.json index f3876f4..23bb466 100644 --- a/zTools.API/appsettings.Template.json +++ b/zTools.API/appsettings.Template.json @@ -11,6 +11,10 @@ "ApiKey": "your-openai-api-key", "Model": "gpt-3.5-turbo" }, + "InVideo": { + "ApiUrl": "https://pro-api.invideo.io/v1/generate-video", + "ApiKey": "your-invideo-api-key" + }, "S3": { "AccessKey": "your-digitalocean-spaces-access-key", "Endpoint": "https://your-space-name.nyc3.digitaloceanspaces.com", diff --git a/zTools.Application/Initializer.cs b/zTools.Application/Initializer.cs index 7b53215..9775e90 100644 --- a/zTools.Application/Initializer.cs +++ b/zTools.Application/Initializer.cs @@ -22,6 +22,7 @@ public static void Configure(IServiceCollection services, string connection, boo services.AddHttpClient(); services.AddHttpClient(); + services.AddHttpClient(); } } } diff --git a/zTools.Domain/Services/InVideoService.cs b/zTools.Domain/Services/InVideoService.cs new file mode 100644 index 0000000..ed565b2 --- /dev/null +++ b/zTools.Domain/Services/InVideoService.cs @@ -0,0 +1,69 @@ +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using zTools.Domain.Services.Interfaces; +using zTools.DTO.InVideo; +using zTools.DTO.Settings; +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; + +namespace zTools.Domain.Services +{ + public class InVideoService : IInVideoService + { + private readonly HttpClient _httpClient; + private readonly IOptions _inVideoSettings; + + public InVideoService(HttpClient httpClient, IOptions inVideoSettings) + { + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _inVideoSettings = inVideoSettings ?? throw new ArgumentNullException(nameof(inVideoSettings)); + } + + public async Task GenerateVideoAsync(string prompt) + { + var request = new InVideoRequest + { + Prompt = prompt + }; + + return await GenerateVideoAsync(request); + } + + public async Task GenerateVideoAsync(InVideoRequest request) + { + _httpClient.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", _inVideoSettings.Value.ApiKey); + + var jsonContent = new StringContent( + JsonConvert.SerializeObject(request, new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore + }), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage response = await _httpClient.PostAsync( + _inVideoSettings.Value.ApiUrl, + jsonContent); + + var responseContent = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + var errorResponse = JsonConvert.DeserializeObject(responseContent); + + if (errorResponse?.Error != null && !string.IsNullOrEmpty(errorResponse.Error.Message)) + { + throw new InvalidOperationException(errorResponse.Error.Message); + } + + throw new InvalidOperationException("Unknown error"); + } + + return JsonConvert.DeserializeObject(responseContent); + } + } +} diff --git a/zTools.Domain/Services/Interfaces/IInVideoService.cs b/zTools.Domain/Services/Interfaces/IInVideoService.cs new file mode 100644 index 0000000..42b0bd4 --- /dev/null +++ b/zTools.Domain/Services/Interfaces/IInVideoService.cs @@ -0,0 +1,11 @@ +using zTools.DTO.InVideo; +using System.Threading.Tasks; + +namespace zTools.Domain.Services.Interfaces +{ + public interface IInVideoService + { + Task GenerateVideoAsync(string prompt); + Task GenerateVideoAsync(InVideoRequest request); + } +} diff --git a/zTools.Tests/ACL/InVideoClientTests.cs b/zTools.Tests/ACL/InVideoClientTests.cs new file mode 100644 index 0000000..95c5574 --- /dev/null +++ b/zTools.Tests/ACL/InVideoClientTests.cs @@ -0,0 +1,228 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using Newtonsoft.Json; +using zTools.ACL; +using zTools.DTO.InVideo; +using zTools.DTO.Settings; +using RichardSzalay.MockHttp; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Xunit; + +namespace zTools.Tests.ACL +{ + public class InVideoClientTests + { + private readonly Mock> _mockSettings; + private readonly zToolsetting _settings; + private readonly MockHttpMessageHandler _mockHttpHandler; + private readonly HttpClient _httpClient; + private readonly Mock> _mockLogger; + + public InVideoClientTests() + { + _settings = new zToolsetting + { + ApiUrl = "https://api.example.com" + }; + + _mockSettings = new Mock>(); + _mockSettings.Setup(x => x.Value).Returns(_settings); + + _mockHttpHandler = new MockHttpMessageHandler(); + _httpClient = _mockHttpHandler.ToHttpClient(); + + _mockLogger = new Mock>(); + } + + [Fact] + public void Constructor_WithValidParameters_CreatesInstance() + { + var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object); + Assert.NotNull(client); + } + + [Fact] + public async Task GenerateVideoAsync_WithPrompt_ReturnsResponse() + { + // Arrange + var expectedResponse = new InVideoResponse + { + VideoUrl = "https://invideo.io/videos/test-123", + Title = "Test Video", + Description = "A test video" + }; + var apiUrl = $"{_settings.ApiUrl}/InVideo/generateVideo"; + + _mockHttpHandler + .When(HttpMethod.Post, apiUrl) + .Respond("application/json", JsonConvert.SerializeObject(expectedResponse)); + + var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object); + + // Act + var result = await client.GenerateVideoAsync("Create a test video"); + + // Assert + Assert.NotNull(result); + Assert.Equal(expectedResponse.VideoUrl, result.VideoUrl); + Assert.Equal(expectedResponse.Title, result.Title); + Assert.Equal(expectedResponse.Description, result.Description); + } + + [Fact] + public async Task GenerateVideoAsync_WithRequest_ReturnsResponse() + { + // Arrange + var request = new InVideoRequest + { + Prompt = "Create a video about technology", + Duration = 5.0, + Platform = "youtube_shorts", + Voice = "male", + Accent = "american" + }; + + var expectedResponse = new InVideoResponse + { + VideoUrl = "https://invideo.io/videos/tech-456", + Title = "Technology Video", + Description = "A video about technology" + }; + var apiUrl = $"{_settings.ApiUrl}/InVideo/generateVideo"; + + _mockHttpHandler + .When(HttpMethod.Post, apiUrl) + .Respond("application/json", JsonConvert.SerializeObject(expectedResponse)); + + var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object); + + // Act + var result = await client.GenerateVideoAsync(request); + + // Assert + Assert.NotNull(result); + Assert.Equal(expectedResponse.VideoUrl, result.VideoUrl); + Assert.Equal(expectedResponse.Title, result.Title); + } + + [Fact] + public async Task GenerateVideoAsync_MakesCorrectHttpPostRequest() + { + // Arrange + var expectedUrl = $"{_settings.ApiUrl}/InVideo/generateVideo"; + var response = new InVideoResponse + { + VideoUrl = "https://invideo.io/videos/test", + Title = "Test", + Description = "Test" + }; + + _mockHttpHandler + .Expect(HttpMethod.Post, expectedUrl) + .Respond("application/json", JsonConvert.SerializeObject(response)); + + var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object); + + // Act + await client.GenerateVideoAsync("test prompt"); + + // Assert + _mockHttpHandler.VerifyNoOutstandingExpectation(); + } + + [Fact] + public async Task GenerateVideoAsync_WhenApiReturns404_ThrowsHttpRequestException() + { + // Arrange + var apiUrl = $"{_settings.ApiUrl}/InVideo/generateVideo"; + + _mockHttpHandler + .When(HttpMethod.Post, apiUrl) + .Respond(HttpStatusCode.NotFound); + + var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object); + + // Act & Assert + await Assert.ThrowsAsync(() => + client.GenerateVideoAsync("test")); + } + + [Fact] + public async Task GenerateVideoAsync_WhenApiReturns500_ThrowsHttpRequestException() + { + // Arrange + var apiUrl = $"{_settings.ApiUrl}/InVideo/generateVideo"; + + _mockHttpHandler + .When(HttpMethod.Post, apiUrl) + .Respond(HttpStatusCode.InternalServerError); + + var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object); + + // Act & Assert + await Assert.ThrowsAsync(() => + client.GenerateVideoAsync(new InVideoRequest { Prompt = "test" })); + } + + [Fact] + public async Task GenerateVideoAsync_UsesCorrectApiUrl() + { + // Arrange + var customSettings = new zToolsetting { ApiUrl = "https://custom-api.com" }; + var mockCustomSettings = new Mock>(); + mockCustomSettings.Setup(x => x.Value).Returns(customSettings); + + var expectedResponse = new InVideoResponse + { + VideoUrl = "https://invideo.io/videos/custom", + Title = "Custom", + Description = "Custom" + }; + var apiUrl = $"{customSettings.ApiUrl}/InVideo/generateVideo"; + + _mockHttpHandler + .When(HttpMethod.Post, apiUrl) + .Respond("application/json", JsonConvert.SerializeObject(expectedResponse)); + + var client = new InVideoClient(_httpClient, mockCustomSettings.Object, _mockLogger.Object); + + // Act + var result = await client.GenerateVideoAsync("test"); + + // Assert + Assert.NotNull(result); + } + + [Theory] + [InlineData("Create a video about cats")] + [InlineData("Make an ad for shoes")] + [InlineData("Generate an explainer video")] + public async Task GenerateVideoAsync_WithVariousPrompts_ReturnsResponse(string prompt) + { + // Arrange + var expectedResponse = new InVideoResponse + { + VideoUrl = "https://invideo.io/videos/test", + Title = "Video", + Description = "Description" + }; + var apiUrl = $"{_settings.ApiUrl}/InVideo/generateVideo"; + + _mockHttpHandler + .When(HttpMethod.Post, apiUrl) + .Respond("application/json", JsonConvert.SerializeObject(expectedResponse)); + + var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object); + + // Act + var result = await client.GenerateVideoAsync(prompt); + + // Assert + Assert.NotNull(result); + Assert.Equal(expectedResponse.VideoUrl, result.VideoUrl); + } + } +} diff --git a/zTools.Tests/Domain/Services/InVideoServiceTests.cs b/zTools.Tests/Domain/Services/InVideoServiceTests.cs new file mode 100644 index 0000000..b90ac85 --- /dev/null +++ b/zTools.Tests/Domain/Services/InVideoServiceTests.cs @@ -0,0 +1,340 @@ +using Microsoft.Extensions.Options; +using Moq; +using Moq.Protected; +using Newtonsoft.Json; +using zTools.Domain.Services; +using zTools.DTO.InVideo; +using zTools.DTO.Settings; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace zTools.Tests.Domain.Services +{ + public class InVideoServiceTests + { + private readonly Mock> _mockInVideoSettings; + private readonly InVideoSetting _inVideoSetting; + private readonly HttpClient _httpClient; + + public InVideoServiceTests() + { + _inVideoSetting = new InVideoSetting + { + ApiUrl = "https://pro-api.invideo.io/v1/generate-video", + ApiKey = "test-api-key-12345" + }; + + _mockInVideoSettings = new Mock>(); + _mockInVideoSettings.Setup(x => x.Value).Returns(_inVideoSetting); + + _httpClient = new HttpClient(); + } + + private static InVideoResponse CreateTestResponse() + { + return new InVideoResponse + { + VideoUrl = "https://invideo.io/videos/test-video-123", + Title = "Test Video", + Description = "A test video generated by InVideo AI" + }; + } + + [Fact] + public void Constructor_WithValidSettings_InitializesSuccessfully() + { + var service = new InVideoService(_httpClient, _mockInVideoSettings.Object); + Assert.NotNull(service); + } + + [Fact] + public void Constructor_WithNullHttpClient_ThrowsArgumentNullException() + { + Assert.Throws(() => new InVideoService(null, _mockInVideoSettings.Object)); + } + + [Fact] + public void Constructor_WithNullSettings_ThrowsArgumentNullException() + { + Assert.Throws(() => new InVideoService(_httpClient, null)); + } + + [Fact] + public async Task GenerateVideoAsync_WithPrompt_ReturnsResponse() + { + // Arrange + var mockHandler = new Mock(); + var expectedResponse = CreateTestResponse(); + var responseJson = JsonConvert.SerializeObject(expectedResponse); + + mockHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(responseJson) + }); + + var httpClient = new HttpClient(mockHandler.Object); + var service = new InVideoService(httpClient, _mockInVideoSettings.Object); + + // Act + var result = await service.GenerateVideoAsync("Create a video about cats"); + + // Assert + Assert.NotNull(result); + Assert.Equal(expectedResponse.VideoUrl, result.VideoUrl); + Assert.Equal(expectedResponse.Title, result.Title); + Assert.Equal(expectedResponse.Description, result.Description); + } + + [Fact] + public async Task GenerateVideoAsync_WithRequest_ReturnsResponse() + { + // Arrange + var mockHandler = new Mock(); + var expectedResponse = CreateTestResponse(); + var responseJson = JsonConvert.SerializeObject(expectedResponse); + + mockHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(responseJson) + }); + + var httpClient = new HttpClient(mockHandler.Object); + var service = new InVideoService(httpClient, _mockInVideoSettings.Object); + + var request = new InVideoRequest + { + Prompt = "Create a video about technology", + Duration = 5.0, + Platform = "youtube_shorts", + Voice = "male", + Accent = "american" + }; + + // Act + var result = await service.GenerateVideoAsync(request); + + // Assert + Assert.NotNull(result); + Assert.Equal(expectedResponse.VideoUrl, result.VideoUrl); + } + + [Fact] + public async Task GenerateVideoAsync_WhenApiReturnsErrorWithMessage_ThrowsInvalidOperationException() + { + // Arrange + var mockHandler = new Mock(); + var errorResponse = new InVideoErrorResponse + { + Error = new InVideoError + { + Message = "Invalid API key", + Code = "invalid_api_key" + } + }; + var errorJson = JsonConvert.SerializeObject(errorResponse); + + mockHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.Unauthorized, + Content = new StringContent(errorJson) + }); + + var httpClient = new HttpClient(mockHandler.Object); + var service = new InVideoService(httpClient, _mockInVideoSettings.Object); + + // Act & Assert + var exception = await Assert.ThrowsAsync( + async () => await service.GenerateVideoAsync("test")); + + Assert.Equal("Invalid API key", exception.Message); + } + + [Fact] + public async Task GenerateVideoAsync_WhenApiReturnsErrorWithoutMessage_ThrowsUnknownError() + { + // Arrange + var mockHandler = new Mock(); + var errorResponse = new InVideoErrorResponse + { + Error = new InVideoError { Message = null } + }; + var errorJson = JsonConvert.SerializeObject(errorResponse); + + mockHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.BadRequest, + Content = new StringContent(errorJson) + }); + + var httpClient = new HttpClient(mockHandler.Object); + var service = new InVideoService(httpClient, _mockInVideoSettings.Object); + + var request = new InVideoRequest { Prompt = "test" }; + + // Act & Assert + var exception = await Assert.ThrowsAsync( + async () => await service.GenerateVideoAsync(request)); + + Assert.Equal("Unknown error", exception.Message); + } + + [Fact] + public async Task GenerateVideoAsync_WhenApiReturnsNullError_ThrowsUnknownError() + { + // Arrange + var mockHandler = new Mock(); + + mockHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.InternalServerError, + Content = new StringContent("null") + }); + + var httpClient = new HttpClient(mockHandler.Object); + var service = new InVideoService(httpClient, _mockInVideoSettings.Object); + + // Act & Assert + var exception = await Assert.ThrowsAsync( + async () => await service.GenerateVideoAsync("test")); + + Assert.Equal("Unknown error", exception.Message); + } + + [Theory] + [InlineData("Create a video about cats")] + [InlineData("Make an ad for my product")] + [InlineData("Generate an educational video about space")] + public async Task GenerateVideoAsync_WithVariousPrompts_ReturnsResponse(string prompt) + { + // Arrange + var mockHandler = new Mock(); + var expectedResponse = CreateTestResponse(); + var responseJson = JsonConvert.SerializeObject(expectedResponse); + + mockHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(responseJson) + }); + + var httpClient = new HttpClient(mockHandler.Object); + var service = new InVideoService(httpClient, _mockInVideoSettings.Object); + + // Act + var result = await service.GenerateVideoAsync(prompt); + + // Assert + Assert.NotNull(result); + Assert.Equal(expectedResponse.VideoUrl, result.VideoUrl); + } + + [Fact] + public void InVideoSetting_ConfigurationValues_AreAccessible() + { + Assert.Equal("https://pro-api.invideo.io/v1/generate-video", _inVideoSetting.ApiUrl); + Assert.Equal("test-api-key-12345", _inVideoSetting.ApiKey); + } + + [Fact] + public void InVideoRequest_Serialization_ProducesCorrectJson() + { + // Arrange + var request = new InVideoRequest + { + Prompt = "Test video", + Duration = 10.0, + Platform = "tiktok", + Voice = "female", + Accent = "british" + }; + + // Act + var json = JsonConvert.SerializeObject(request); + var deserialized = JsonConvert.DeserializeObject(json); + + // Assert + Assert.NotNull(deserialized); + Assert.Equal(request.Prompt, deserialized.Prompt); + Assert.Equal(request.Duration, deserialized.Duration); + Assert.Equal(request.Platform, deserialized.Platform); + Assert.Equal(request.Voice, deserialized.Voice); + Assert.Equal(request.Accent, deserialized.Accent); + } + + [Fact] + public void InVideoResponse_Deserialization_ParsesCorrectly() + { + // Arrange + var responseJson = @"{ + ""video_url"": ""https://invideo.io/videos/abc123"", + ""title"": ""My Video"", + ""description"": ""A great video"" + }"; + + // Act + var response = JsonConvert.DeserializeObject(responseJson); + + // Assert + Assert.NotNull(response); + Assert.Equal("https://invideo.io/videos/abc123", response.VideoUrl); + Assert.Equal("My Video", response.Title); + Assert.Equal("A great video", response.Description); + } + + [Fact] + public void InVideoErrorResponse_Deserialization_ParsesCorrectly() + { + // Arrange + var errorJson = @"{ + ""error"": { + ""message"": ""Rate limit exceeded"", + ""code"": ""rate_limit"" + } + }"; + + // Act + var errorResponse = JsonConvert.DeserializeObject(errorJson); + + // Assert + Assert.NotNull(errorResponse); + Assert.NotNull(errorResponse.Error); + Assert.Equal("Rate limit exceeded", errorResponse.Error.Message); + Assert.Equal("rate_limit", errorResponse.Error.Code); + } + } +} diff --git a/zTools/ACL/InVideoClient.cs b/zTools/ACL/InVideoClient.cs new file mode 100644 index 0000000..162b6ad --- /dev/null +++ b/zTools/ACL/InVideoClient.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using zTools.ACL.Interfaces; +using zTools.DTO.InVideo; +using zTools.DTO.Settings; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace zTools.ACL +{ + public class InVideoClient : IInVideoClient + { + private readonly HttpClient _httpClient; + private readonly IOptions _zToolsetting; + private readonly ILogger _logger; + + public InVideoClient(HttpClient httpClient, IOptions zToolsetting, ILogger logger) + { + _httpClient = httpClient; + _zToolsetting = zToolsetting; + _logger = logger; + } + + public async Task GenerateVideoAsync(string prompt) + { + var request = new InVideoRequest + { + Prompt = prompt + }; + + return await GenerateVideoAsync(request); + } + + public async Task GenerateVideoAsync(InVideoRequest request) + { + var url = $"{_zToolsetting.Value.ApiUrl}/InVideo/generateVideo"; + _logger.LogInformation("Generating video with InVideo AI via URL: {Url}", url); + + var content = new StringContent( + JsonConvert.SerializeObject(request), + Encoding.UTF8, + "application/json"); + + var response = await _httpClient.PostAsync(url, content); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + _logger.LogInformation("InVideo AI video generation response received"); + + return JsonConvert.DeserializeObject(json); + } + } +} diff --git a/zTools/ACL/Interfaces/IInVideoClient.cs b/zTools/ACL/Interfaces/IInVideoClient.cs new file mode 100644 index 0000000..a3827d8 --- /dev/null +++ b/zTools/ACL/Interfaces/IInVideoClient.cs @@ -0,0 +1,11 @@ +using zTools.DTO.InVideo; +using System.Threading.Tasks; + +namespace zTools.ACL.Interfaces +{ + public interface IInVideoClient + { + Task GenerateVideoAsync(string prompt); + Task GenerateVideoAsync(InVideoRequest request); + } +} diff --git a/zTools/DTO/InVideo/InVideoErrorResponse.cs b/zTools/DTO/InVideo/InVideoErrorResponse.cs new file mode 100644 index 0000000..01c2df3 --- /dev/null +++ b/zTools/DTO/InVideo/InVideoErrorResponse.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace zTools.DTO.InVideo +{ + public class InVideoErrorResponse + { + [JsonProperty("error")] + public InVideoError Error { get; set; } + } + + public class InVideoError + { + [JsonProperty("message")] + public string Message { get; set; } + + [JsonProperty("code")] + public string Code { get; set; } + } +} diff --git a/zTools/DTO/InVideo/InVideoRequest.cs b/zTools/DTO/InVideo/InVideoRequest.cs new file mode 100644 index 0000000..54e370e --- /dev/null +++ b/zTools/DTO/InVideo/InVideoRequest.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace zTools.DTO.InVideo +{ + public class InVideoRequest + { + [JsonProperty("prompt")] + public string Prompt { get; set; } + + [JsonProperty("duration")] + public double? Duration { get; set; } + + [JsonProperty("platform")] + public string Platform { get; set; } + + [JsonProperty("voice")] + public string Voice { get; set; } + + [JsonProperty("accent")] + public string Accent { get; set; } + } +} diff --git a/zTools/DTO/InVideo/InVideoResponse.cs b/zTools/DTO/InVideo/InVideoResponse.cs new file mode 100644 index 0000000..c994ca9 --- /dev/null +++ b/zTools/DTO/InVideo/InVideoResponse.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace zTools.DTO.InVideo +{ + public class InVideoResponse + { + [JsonProperty("video_url")] + public string VideoUrl { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + } +} diff --git a/zTools/DTO/Settings/InVideoSetting.cs b/zTools/DTO/Settings/InVideoSetting.cs new file mode 100644 index 0000000..ee36476 --- /dev/null +++ b/zTools/DTO/Settings/InVideoSetting.cs @@ -0,0 +1,8 @@ +namespace zTools.DTO.Settings +{ + public class InVideoSetting + { + public string ApiUrl { get; set; } + public string ApiKey { get; set; } + } +}