feat:Google Lyria API로 AI 배경음악 생성 연동
Lyria API 연동: - Vertex AI 기반 Google Lyria 음악 생성 API 추가 - 분위기(mood)별 영어 프롬프트 매핑 (upbeat, energetic, calm 등 8종) - 생성된 30초 WAV → MP3 변환 + 영상 길이에 맞춰 루프/트림 - 페이드인(1초) + 페이드아웃(3초) 자동 적용 - 비용: $0.06/30초 BGM 우선순위 변경: - 1순위: Lyria AI 배경음악 (신규) - 2순위: 프리셋 BGM 파일 (storage/app/bgm/) - 3순위: FFmpeg 앰비언트 (기존 폴백) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -216,14 +216,24 @@ public function handle(
|
||||
}
|
||||
|
||||
// === Step 4: BGM 생성/선택 ===
|
||||
$video->updateProgress(VideoGeneration::STATUS_GENERATING_BGM, 75, 'BGM 준비 중...');
|
||||
$video->updateProgress(VideoGeneration::STATUS_GENERATING_BGM, 75, 'AI 배경음악 생성 중...');
|
||||
|
||||
$bgmMood = $scenario['bgm_mood'] ?? 'upbeat';
|
||||
$bgmPath = $bgm->select($bgmMood, "{$workDir}/bgm.mp3");
|
||||
$totalDuration = array_sum(array_column($activeScenes, 'duration'));
|
||||
|
||||
// BGM 파일이 없으면 앰비언트 BGM 자동 생성
|
||||
// 1순위: Google Lyria AI 배경음악 생성
|
||||
$bgmPath = $bgm->generateWithLyria($bgmMood, $totalDuration, "{$workDir}/bgm.mp3");
|
||||
if ($bgmPath) {
|
||||
$totalCost += 0.06; // Lyria 비용 ($0.06/30초)
|
||||
}
|
||||
|
||||
// 2순위: 프리셋 BGM 파일
|
||||
if (! $bgmPath) {
|
||||
$bgmPath = $bgm->select($bgmMood, "{$workDir}/bgm.mp3");
|
||||
}
|
||||
|
||||
// 3순위: FFmpeg 앰비언트 BGM
|
||||
if (! $bgmPath) {
|
||||
$totalDuration = array_sum(array_column($activeScenes, 'duration'));
|
||||
$bgmPath = $bgm->generateAmbient($bgmMood, $totalDuration, "{$workDir}/bgm.mp3");
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,147 @@
|
||||
|
||||
namespace App\Services\Video;
|
||||
|
||||
use App\Models\System\AiConfig;
|
||||
use App\Services\GoogleCloudService;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BgmService
|
||||
{
|
||||
private GoogleCloudService $googleCloud;
|
||||
|
||||
public function __construct(GoogleCloudService $googleCloud)
|
||||
{
|
||||
$this->googleCloud = $googleCloud;
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Lyria API로 AI 배경음악 생성
|
||||
*
|
||||
* @return string|null 생성된 MP3 파일 경로
|
||||
*/
|
||||
public function generateWithLyria(string $mood, int $durationSec, string $savePath): ?string
|
||||
{
|
||||
$config = AiConfig::getActiveGemini();
|
||||
|
||||
if (! $config || ! $config->isVertexAi()) {
|
||||
Log::info('BgmService: Vertex AI 설정 없음, Lyria 건너뜀');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$token = $this->googleCloud->getAccessToken();
|
||||
|
||||
if (! $token) {
|
||||
Log::warning('BgmService: Lyria 액세스 토큰 실패');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$projectId = $config->getProjectId();
|
||||
$region = $config->getRegion();
|
||||
$prompt = $this->buildMusicPrompt($mood);
|
||||
|
||||
$url = "https://{$region}-aiplatform.googleapis.com/v1/projects/{$projectId}/locations/{$region}/publishers/google/models/lyria:predict";
|
||||
|
||||
Log::info('BgmService: Lyria API 요청', ['mood' => $mood, 'prompt' => $prompt]);
|
||||
|
||||
$response = Http::withToken($token)
|
||||
->timeout(120)
|
||||
->post($url, [
|
||||
'instances' => [
|
||||
['prompt' => $prompt],
|
||||
],
|
||||
]);
|
||||
|
||||
if (! $response->successful()) {
|
||||
Log::warning('BgmService: Lyria API 실패', [
|
||||
'status' => $response->status(),
|
||||
'body' => substr($response->body(), 0, 500),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
$audioContent = $data['predictions'][0]['audioContent'] ?? null;
|
||||
|
||||
if (! $audioContent) {
|
||||
Log::warning('BgmService: Lyria 응답에 오디오 없음');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$dir = dirname($savePath);
|
||||
if (! is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
|
||||
// WAV 저장 후 MP3 변환 + 영상 길이에 맞춤
|
||||
$wavPath = "{$dir}/bgm_lyria.wav";
|
||||
file_put_contents($wavPath, base64_decode($audioContent));
|
||||
|
||||
// MP3 변환 + 영상 길이 맞춤 (30초 BGM이 짧으면 루프, 길면 트림)
|
||||
$cmd = sprintf(
|
||||
'ffmpeg -y -stream_loop -1 -i %s -t %d -af "afade=t=in:d=1,afade=t=out:st=%d:d=3" -c:a libmp3lame -q:a 2 %s 2>&1',
|
||||
escapeshellarg($wavPath),
|
||||
$durationSec,
|
||||
max(0, $durationSec - 3),
|
||||
escapeshellarg($savePath)
|
||||
);
|
||||
|
||||
exec($cmd, $output, $returnCode);
|
||||
@unlink($wavPath);
|
||||
|
||||
if ($returnCode !== 0) {
|
||||
Log::error('BgmService: Lyria WAV→MP3 변환 실패', [
|
||||
'output' => implode("\n", array_slice($output, -5)),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Log::info('BgmService: Lyria BGM 생성 완료', [
|
||||
'path' => $savePath,
|
||||
'duration' => $durationSec,
|
||||
]);
|
||||
|
||||
return $savePath;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('BgmService: Lyria 예외', ['error' => $e->getMessage()]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 분위기 → Lyria 영어 프롬프트 변환
|
||||
*/
|
||||
private function buildMusicPrompt(string $mood): string
|
||||
{
|
||||
$prompts = [
|
||||
'upbeat' => 'Upbeat cheerful background music for a short health wellness video. Light electronic beat with positive energy, moderate tempo. Instrumental only, no vocals.',
|
||||
'energetic' => 'Energetic motivating background music for a fitness video. Fast-paced electronic beat with driving rhythm. Instrumental only, no vocals.',
|
||||
'exciting' => 'Exciting dynamic background music for a surprising facts video. Building tension with electronic elements. Instrumental only, no vocals.',
|
||||
'calm' => 'Calm soothing background music for a relaxation wellness video. Gentle piano and ambient pads, slow tempo. Instrumental only, no vocals.',
|
||||
'dramatic' => 'Dramatic cinematic background music for a revealing truth video. Orchestral tension with building suspense. Instrumental only, no vocals.',
|
||||
'happy' => 'Happy bright background music for a positive health tips video. Cheerful ukulele and light percussion. Instrumental only, no vocals.',
|
||||
'mysterious' => 'Mysterious intriguing background music for a health secrets video. Dark ambient with subtle electronic textures. Instrumental only, no vocals.',
|
||||
'inspiring' => 'Inspiring uplifting background music for a motivational health video. Warm piano with gentle strings, building progression. Instrumental only, no vocals.',
|
||||
];
|
||||
|
||||
$moodLower = strtolower($mood);
|
||||
|
||||
foreach ($prompts as $key => $prompt) {
|
||||
if (str_contains($moodLower, $key)) {
|
||||
return $prompt;
|
||||
}
|
||||
}
|
||||
|
||||
return 'Light pleasant background music for a short health video. Moderate tempo, positive mood, gentle electronic beat. Instrumental only, no vocals.';
|
||||
}
|
||||
|
||||
/**
|
||||
* 분위기별 BGM 매핑 (로열티프리 BGM 파일 풀)
|
||||
* storage/app/bgm/ 디렉토리에 미리 준비
|
||||
|
||||
Reference in New Issue
Block a user