fix:자막-음성 싱크 개선 + 나레이션 밀도 증가 + BGM 볼륨 상향
1. 자막 싱크: ffprobe로 실제 TTS 오디오 길이 측정 → 자막 타이밍 반영 - 기존: 장면 길이 * 0.75 추정 → 음성과 자막 불일치 - 변경: 실제 나레이션 오디오 길이 기반 문장별 타이밍 계산 2. 나레이션 밀도: 장면당 40~70자 → 60~100자 (빈 시간 없이 채움) 3. BGM 볼륨: 0.4 → 1.2 (안 들리던 문제 해결) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -184,11 +184,31 @@ public function concatNarrations(array $audioPaths, array $scenes, string $outpu
|
||||
}
|
||||
|
||||
/**
|
||||
* ASS 자막 파일 생성 (한 문장 단위로 분리, 음성 싱크)
|
||||
* ffprobe로 오디오 파일의 실제 재생 시간 측정
|
||||
*/
|
||||
public function getAudioDuration(string $path): float
|
||||
{
|
||||
if (! file_exists($path)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$cmd = sprintf(
|
||||
'ffprobe -v quiet -show_entries format=duration -of csv=p=0 %s 2>/dev/null',
|
||||
escapeshellarg($path)
|
||||
);
|
||||
|
||||
$output = trim(shell_exec($cmd) ?? '');
|
||||
|
||||
return (float) $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* ASS 자막 파일 생성 (실제 TTS 오디오 길이 기반 싱크)
|
||||
*
|
||||
* @param array $scenes [{scene_number, narration, duration}, ...]
|
||||
* @param array $narrationDurations [scene_number => 실제 오디오 초] (ffprobe 측정값)
|
||||
*/
|
||||
public function generateAssSubtitle(array $scenes, string $outputPath): string
|
||||
public function generateAssSubtitle(array $scenes, string $outputPath, array $narrationDurations = []): string
|
||||
{
|
||||
$dir = dirname($outputPath);
|
||||
if (! is_dir($dir)) {
|
||||
@@ -203,7 +223,6 @@ public function generateAssSubtitle(array $scenes, string $outputPath): string
|
||||
|
||||
$ass .= "[V4+ Styles]\n";
|
||||
$ass .= "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n";
|
||||
// Fontsize=96(2배), 흰색 글자+검정 외곽선(5px)+검정 그림자(2px), Alignment=5(화면 중앙)
|
||||
$ass .= "Style: Default,NanumGothic,96,&H00FFFFFF,&H000000FF,&H00000000,&H80000000,-1,0,0,0,100,100,0,0,1,5,2,5,40,40,0,1\n\n";
|
||||
|
||||
$ass .= "[Events]\n";
|
||||
@@ -212,11 +231,12 @@ public function generateAssSubtitle(array $scenes, string $outputPath): string
|
||||
$currentTime = 0;
|
||||
|
||||
foreach ($scenes as $scene) {
|
||||
$duration = $scene['duration'] ?? 8;
|
||||
$sceneDuration = $scene['duration'] ?? 8;
|
||||
$sceneNum = $scene['scene_number'] ?? 0;
|
||||
$narration = $scene['narration'] ?? '';
|
||||
|
||||
if (empty($narration)) {
|
||||
$currentTime += $duration;
|
||||
$currentTime += $sceneDuration;
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -225,10 +245,12 @@ public function generateAssSubtitle(array $scenes, string $outputPath): string
|
||||
$cleanNarration = $this->stripEmoji($narration);
|
||||
$sentences = $this->splitIntoSentences($cleanNarration);
|
||||
|
||||
// 총 글자 수 기준으로 각 문장의 시간 비율 계산
|
||||
// 실제 TTS 오디오 길이 사용 (없으면 장면 길이의 90%로 추정)
|
||||
$audioDuration = $narrationDurations[$sceneNum] ?? ($sceneDuration * 0.9);
|
||||
// 장면 길이를 초과하지 않도록 제한
|
||||
$activeTime = min($audioDuration, $sceneDuration);
|
||||
|
||||
$totalChars = array_sum(array_map('mb_strlen', $sentences));
|
||||
// 나레이션이 차지하는 시간 (1.4x 속도에 맞춰 75% 구간에 압축)
|
||||
$activeTime = $duration * 0.75;
|
||||
$offset = 0.0;
|
||||
|
||||
foreach ($sentences as $sentence) {
|
||||
@@ -238,11 +260,11 @@ public function generateAssSubtitle(array $scenes, string $outputPath): string
|
||||
}
|
||||
|
||||
$ratio = mb_strlen($sentence) / max($totalChars, 1);
|
||||
$sentDuration = max($activeTime * $ratio, 1.5);
|
||||
$sentDuration = max($activeTime * $ratio, 1.0);
|
||||
|
||||
// 장면 경계 초과 방지
|
||||
if ($offset + $sentDuration > $duration) {
|
||||
$sentDuration = $duration - $offset;
|
||||
if ($offset + $sentDuration > $sceneDuration) {
|
||||
$sentDuration = $sceneDuration - $offset;
|
||||
}
|
||||
if ($sentDuration <= 0) {
|
||||
break;
|
||||
@@ -259,7 +281,7 @@ public function generateAssSubtitle(array $scenes, string $outputPath): string
|
||||
$offset += $sentDuration;
|
||||
}
|
||||
|
||||
$currentTime += $duration;
|
||||
$currentTime += $sceneDuration;
|
||||
}
|
||||
|
||||
file_put_contents($outputPath, $ass);
|
||||
@@ -364,7 +386,7 @@ public function assemble(
|
||||
// BGM 추가
|
||||
if ($bgmPath && file_exists($bgmPath)) {
|
||||
$inputs[] = '-i ' . escapeshellarg($bgmPath);
|
||||
$filterParts[] = "[{$audioIndex}:a]volume=0.4[bgm]";
|
||||
$filterParts[] = "[{$audioIndex}:a]volume=1.2[bgm]";
|
||||
$audioIndex++;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user