using System.Net.Http.Headers; using System.Text; using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using SaasMediaWorker.Configuration; using SaasMediaWorker.Models; namespace SaasMediaWorker.Services; /// /// Higgsfield AI API Client — Video klip üretimi. /// Her sahnenin görsel prompt'unu Higgsfield'a gönderip video dosyası indirir. /// public class HiggsFieldService { private readonly HttpClient _httpClient; private readonly ILogger _logger; private readonly ApiSettings _settings; public HiggsFieldService( HttpClient httpClient, ILogger logger, IOptions settings) { _httpClient = httpClient; _logger = logger; _settings = settings.Value; _httpClient.BaseAddress = new Uri(_settings.HiggsFieldBaseUrl); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _settings.HiggsFieldApiKey); _httpClient.Timeout = TimeSpan.FromMinutes(5); } /// /// Bir sahne için video klip üretir ve dosyayı belirtilen dizine indirir. /// public async Task GenerateVideoClipAsync( ScenePayload scene, string outputDirectory, string aspectRatio, CancellationToken ct) { _logger.LogInformation( "🎥 Video üretimi — Sahne {Order}: {Prompt}", scene.Order, scene.VisualPrompt[..Math.Min(80, scene.VisualPrompt.Length)]); var requestBody = new { prompt = scene.VisualPrompt, duration = Math.Max(3, Math.Min(10, (int)scene.Duration)), aspect_ratio = MapAspectRatio(aspectRatio), style = "cinematic", quality = "high" }; var content = new StringContent( JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json"); // Video üretim isteği gönder var response = await _httpClient.PostAsync("/generations", content, ct); response.EnsureSuccessStatusCode(); var responseJson = await response.Content.ReadAsStringAsync(ct); var result = JsonSerializer.Deserialize(responseJson); // Generation ID al ve polling ile sonucu bekle var generationId = result.GetProperty("id").GetString() ?? throw new InvalidOperationException("Generation ID alınamadı"); _logger.LogInformation("Higgsfield generation başlatıldı: {Id}", generationId); // Polling — video hazır olana kadar bekle var videoUrl = await PollForCompletionAsync(generationId, ct); // Video dosyasını indir var outputPath = Path.Combine(outputDirectory, $"scene_{scene.Order:D2}_video.mp4"); await DownloadFileAsync(videoUrl, outputPath, ct); var fileInfo = new FileInfo(outputPath); return new GeneratedMediaFile { SceneId = scene.Id, SceneOrder = scene.Order, Type = MediaFileType.VideoClip, LocalPath = outputPath, FileSizeBytes = fileInfo.Length, DurationSeconds = scene.Duration, MimeType = "video/mp4", AiProvider = "higgsfield" }; } private async Task PollForCompletionAsync(string generationId, CancellationToken ct) { var maxAttempts = 120; // 10 dakika (5s interval × 120) for (var i = 0; i < maxAttempts; i++) { ct.ThrowIfCancellationRequested(); await Task.Delay(5000, ct); var response = await _httpClient.GetAsync($"/generations/{generationId}", ct); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(ct); var result = JsonSerializer.Deserialize(json); var status = result.GetProperty("status").GetString(); if (status == "completed") { return result.GetProperty("video_url").GetString() ?? throw new InvalidOperationException("Video URL bulunamadı"); } if (status == "failed") { var errorMsg = result.TryGetProperty("error", out var err) ? err.GetString() : "Bilinmeyen hata"; throw new InvalidOperationException($"Higgsfield video üretimi başarısız: {errorMsg}"); } if (i % 6 == 0) // Her 30 saniyede bir log { _logger.LogInformation("Higgsfield polling — {Id}: {Status}", generationId, status); } } throw new TimeoutException($"Higgsfield video üretimi zaman aşımına uğradı: {generationId}"); } private async Task DownloadFileAsync(string url, string outputPath, CancellationToken ct) { using var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, ct); response.EnsureSuccessStatusCode(); await using var fileStream = File.Create(outputPath); await response.Content.CopyToAsync(fileStream, ct); _logger.LogInformation("Video indirildi: {Path} ({Size} bytes)", outputPath, new FileInfo(outputPath).Length); } private static string MapAspectRatio(string aspectRatio) => aspectRatio switch { "PORTRAIT_9_16" => "9:16", "SQUARE_1_1" => "1:1", "LANDSCAPE_16_9" => "16:9", _ => "9:16" }; }