main
Some checks failed
Backend Deploy 🚀 / build-and-deploy (push) Has been cancelled

This commit is contained in:
Harun CAN
2026-03-29 12:43:49 +03:00
parent 829413f05d
commit 85c35c73e8
41 changed files with 6127 additions and 36 deletions

View File

@@ -0,0 +1,137 @@
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);
}
}