fix:한국인 여성 캐릭터 고정 + 나레이션-영상 싱크 버그 수정
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 형식으로만 응답하세요 (다른 텍스트 없이):
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user