generated from fahricansecer/boilerplate-be
138 lines
5.0 KiB
C#
138 lines
5.0 KiB
C#
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>
|
||
/// Suno AI API Client — Background müzik üretimi.
|
||
/// Projenin musicPrompt'unu kullanarak AI müzik üretir.
|
||
/// </summary>
|
||
public class SunoMusicService
|
||
{
|
||
private readonly HttpClient _httpClient;
|
||
private readonly ILogger<SunoMusicService> _logger;
|
||
private readonly ApiSettings _settings;
|
||
|
||
public SunoMusicService(
|
||
HttpClient httpClient,
|
||
ILogger<SunoMusicService> logger,
|
||
IOptions<ApiSettings> settings)
|
||
{
|
||
_httpClient = httpClient;
|
||
_logger = logger;
|
||
_settings = settings.Value;
|
||
|
||
_httpClient.BaseAddress = new Uri(_settings.SunoBaseUrl);
|
||
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_settings.SunoApiKey}");
|
||
_httpClient.Timeout = TimeSpan.FromMinutes(5);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Proje için background müzik üretir ve dosyaya kaydeder.
|
||
/// </summary>
|
||
public async Task<GeneratedMediaFile> GenerateMusicAsync(
|
||
string musicPrompt,
|
||
int targetDurationSeconds,
|
||
string outputDirectory,
|
||
CancellationToken ct)
|
||
{
|
||
_logger.LogInformation(
|
||
"🎵 Müzik üretimi — Prompt: \"{Prompt}\", Süre: {Duration}s",
|
||
musicPrompt[..Math.Min(80, musicPrompt.Length)],
|
||
targetDurationSeconds);
|
||
|
||
var requestBody = new
|
||
{
|
||
prompt = musicPrompt,
|
||
duration = Math.Min(120, targetDurationSeconds + 5), // Biraz fazla üret (fade-out için)
|
||
make_instrumental = true, // Vokal olmadan
|
||
model = "chirp-v3"
|
||
};
|
||
|
||
var content = new StringContent(
|
||
JsonSerializer.Serialize(requestBody),
|
||
Encoding.UTF8,
|
||
"application/json");
|
||
|
||
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
|
||
var generationId = result.GetProperty("id").GetString()
|
||
?? throw new InvalidOperationException("Suno generation ID alınamadı");
|
||
|
||
_logger.LogInformation("Suno generation başlatıldı: {Id}", generationId);
|
||
|
||
// Polling — müzik hazır olana kadar bekle
|
||
var musicUrl = await PollForCompletionAsync(generationId, ct);
|
||
|
||
// Müzik dosyasını indir
|
||
var outputPath = Path.Combine(outputDirectory, "background_music.mp3");
|
||
await DownloadFileAsync(musicUrl, outputPath, ct);
|
||
|
||
var fileInfo = new FileInfo(outputPath);
|
||
|
||
_logger.LogInformation("Müzik indirildi: {Path} ({Size} bytes)", outputPath, fileInfo.Length);
|
||
|
||
return new GeneratedMediaFile
|
||
{
|
||
SceneId = string.Empty, // Proje seviyesi — sahneye bağlı değil
|
||
SceneOrder = 0,
|
||
Type = MediaFileType.AudioMusic,
|
||
LocalPath = outputPath,
|
||
FileSizeBytes = fileInfo.Length,
|
||
DurationSeconds = targetDurationSeconds,
|
||
MimeType = "audio/mpeg",
|
||
AiProvider = "suno"
|
||
};
|
||
}
|
||
|
||
private async Task<string> PollForCompletionAsync(string generationId, CancellationToken ct)
|
||
{
|
||
var maxAttempts = 60; // 5 dakika (5s × 60)
|
||
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" || status == "complete")
|
||
{
|
||
return result.GetProperty("audio_url").GetString()
|
||
?? throw new InvalidOperationException("Müzik URL bulunamadı");
|
||
}
|
||
|
||
if (status == "failed" || status == "error")
|
||
{
|
||
var errorMsg = result.TryGetProperty("error", out var err)
|
||
? err.GetString() : "Bilinmeyen hata";
|
||
throw new InvalidOperationException($"Suno müzik üretimi başarısız: {errorMsg}");
|
||
}
|
||
}
|
||
|
||
throw new TimeoutException($"Suno müzik ü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);
|
||
}
|
||
}
|