generated from fahricansecer/boilerplate-be
This commit is contained in:
154
media-worker/Services/HiggsFieldService.cs
Normal file
154
media-worker/Services/HiggsFieldService.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Higgsfield AI API Client — Video klip üretimi.
|
||||
/// Her sahnenin görsel prompt'unu Higgsfield'a gönderip video dosyası indirir.
|
||||
/// </summary>
|
||||
public class HiggsFieldService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<HiggsFieldService> _logger;
|
||||
private readonly ApiSettings _settings;
|
||||
|
||||
public HiggsFieldService(
|
||||
HttpClient httpClient,
|
||||
ILogger<HiggsFieldService> logger,
|
||||
IOptions<ApiSettings> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bir sahne için video klip üretir ve dosyayı belirtilen dizine indirir.
|
||||
/// </summary>
|
||||
public async Task<GeneratedMediaFile> 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<JsonElement>(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<string> 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<JsonElement>(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"
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user