From 60edc28a17568e1c024ada64bfdac3fa0dc1bbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sun, 15 Feb 2026 10:50:51 +0900 Subject: [PATCH] =?UTF-8?q?fix:=ED=95=9C=EA=B5=AD=EC=9D=B8=20=EC=97=AC?= =?UTF-8?q?=EC=84=B1=20=EC=BA=90=EB=A6=AD=ED=84=B0=20=EA=B3=A0=EC=A0=95=20?= =?UTF-8?q?+=20=EB=82=98=EB=A0=88=EC=9D=B4=EC=85=98-=EC=98=81=EC=83=81=20?= =?UTF-8?q?=EC=8B=B1=ED=81=AC=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Gemini 프롬프트: visual_prompt에 한국인 여성(20대) 등장인물 규칙 추가 - Veo 프롬프트: 모든 클립에 "Korean woman in her 20s" 프리픽스 자동 추가 - 싱크 버그: activeNarrationPaths 인덱스 off-by-one ($num-1→$num) 수정 - 나레이션이 영상보다 1장면 앞서 재생되던 근본 원인 - concatNarrations: atrim+apad로 나레이션을 장면 길이에 정확히 매칭 Co-Authored-By: Claude Opus 4.6 --- app/Jobs/VideoGenerationJob.php | 5 +++-- app/Services/Video/GeminiScriptService.php | 3 ++- app/Services/Video/VeoVideoService.php | 8 ++++++-- app/Services/Video/VideoAssemblyService.php | 7 ++++--- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/Jobs/VideoGenerationJob.php b/app/Jobs/VideoGenerationJob.php index 8248ccb0..ee303dd9 100644 --- a/app/Jobs/VideoGenerationJob.php +++ b/app/Jobs/VideoGenerationJob.php @@ -205,10 +205,11 @@ public function handle( $activeScenes = array_filter($scenes, fn ($s) => in_array($s['scene_number'], $activeSceneNums)); $activeScenes = array_values($activeScenes); + // 나레이션 키를 장면 번호로 유지 (concatNarrations에서 scene_number로 매칭) $activeNarrationPaths = []; foreach ($activeSceneNums as $num) { - if (isset($narrationPaths[$num - 1])) { - $activeNarrationPaths[] = $narrationPaths[$num - 1]; + if (isset($narrationPaths[$num])) { + $activeNarrationPaths[$num] = $narrationPaths[$num]; } } diff --git a/app/Services/Video/GeminiScriptService.php b/app/Services/Video/GeminiScriptService.php index bd831858..7af3a6c5 100644 --- a/app/Services/Video/GeminiScriptService.php +++ b/app/Services/Video/GeminiScriptService.php @@ -84,7 +84,8 @@ public function generateScenario(string $title, string $keyword = ''): array - 영어로 작성 - 카메라 앵글, 조명, 분위기를 구체적으로 묘사 - "cinematic", "4K", "dramatic lighting" 등 품질 키워드 포함 -- 사람이 등장할 경우 외모, 표정, 동작을 구체적으로 +- 등장인물은 반드시 "a young Korean woman in her 20s"로 설정 (모든 장면에 동일 인물 등장) +- 등장인물의 표정, 의상, 동작을 구체적으로 묘사 - 9:16 세로 영상에 적합한 구도 반드시 아래 JSON 형식으로만 응답하세요 (다른 텍스트 없이): diff --git a/app/Services/Video/VeoVideoService.php b/app/Services/Video/VeoVideoService.php index 78d17695..56fac0a9 100644 --- a/app/Services/Video/VeoVideoService.php +++ b/app/Services/Video/VeoVideoService.php @@ -38,12 +38,16 @@ public function generateClip(string $prompt, int $duration = 8): ?array try { $url = "https://{$this->location}-aiplatform.googleapis.com/v1/projects/{$this->projectId}/locations/{$this->location}/publishers/google/models/veo-3.1-generate-preview:predictLongRunning"; + // 한국인 여성 등장인물 프롬프트 프리픽스 추가 + $characterPrefix = 'Featuring a young Korean woman in her 20s with natural black hair. '; + $fullPrompt = $characterPrefix . $prompt; + $response = Http::withToken($token) ->timeout(60) ->post($url, [ 'instances' => [ [ - 'prompt' => $prompt, + 'prompt' => $fullPrompt, ], ], 'parameters' => [ @@ -76,7 +80,7 @@ public function generateClip(string $prompt, int $duration = 8): ?array Log::info('VeoVideoService: 영상 생성 요청 성공', [ 'operationName' => $operationName, - 'prompt' => substr($prompt, 0, 100), + 'prompt' => substr($fullPrompt, 0, 150), ]); return ['operationName' => $operationName]; diff --git a/app/Services/Video/VideoAssemblyService.php b/app/Services/Video/VideoAssemblyService.php index 56e527c1..d04b1200 100644 --- a/app/Services/Video/VideoAssemblyService.php +++ b/app/Services/Video/VideoAssemblyService.php @@ -114,7 +114,7 @@ public function concatNarrations(array $audioPaths, array $scenes, string $outpu mkdir($dir, 0755, true); } - // 각 나레이션에 패딩(무음)을 추가해서 장면 길이에 맞춤 + // 각 나레이션을 장면 길이에 정확히 맞춤 (atrim으로 자르고 apad로 채움) $paddedPaths = []; foreach ($scenes as $scene) { $sceneNum = $scene['scene_number']; @@ -122,11 +122,12 @@ public function concatNarrations(array $audioPaths, array $scenes, string $outpu if (isset($audioPaths[$sceneNum])) { $paddedPath = "{$dir}/narration_padded_{$sceneNum}.mp3"; - // 나레이션을 장면 길이에 맞춰 패딩 + // atrim: 장면보다 긴 나레이션은 잘라냄, apad: 짧으면 무음으로 채움 $cmd = sprintf( - 'ffmpeg -y -i %s -af "apad=whole_dur=%d" -c:a libmp3lame -q:a 2 %s 2>&1', + 'ffmpeg -y -i %s -af "atrim=0:%d,apad=whole_dur=%d" -c:a libmp3lame -q:a 2 %s 2>&1', escapeshellarg($audioPaths[$sceneNum]), $duration, + $duration, escapeshellarg($paddedPath) ); exec($cmd, $output, $returnCode);