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"
};
}