fix:여성 음성 변경 + 속도 20% 감속 + 자막 한글자 버그 수정
TTS 설정: - 음성: Neural2-C (남성) → Neural2-A (여성) - 속도: 1.5x → 1.2x (20% 감속) - 피치: 2.0 → 0.0 (자연스러운 여성 톤) 자막 한글자/한단어 버그 수정: - 최소 청크 길이 10자 보장 (짧은 조각 인접 청크에 병합) - 전체 25자 이하면 분리하지 않고 한 블록으로 표시 - 남은 짧은 버퍼는 마지막 청크에 합치기 - 최소 표시 시간 0.8초 → 1.5초로 증가 - 줄바꿈 기준 14자 → 16자 (가독성 향상) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,9 +31,9 @@ public function synthesize(string $text, string $savePath, array $options = []):
|
||||
|
||||
try {
|
||||
$languageCode = $options['language_code'] ?? 'ko-KR';
|
||||
$voiceName = $options['voice_name'] ?? 'ko-KR-Neural2-C';
|
||||
$speakingRate = $options['speaking_rate'] ?? 1.5;
|
||||
$pitch = $options['pitch'] ?? 2.0;
|
||||
$voiceName = $options['voice_name'] ?? 'ko-KR-Neural2-A';
|
||||
$speakingRate = $options['speaking_rate'] ?? 1.2;
|
||||
$pitch = $options['pitch'] ?? 0.0;
|
||||
|
||||
$response = Http::withToken($token)
|
||||
->timeout(30)
|
||||
|
||||
@@ -237,7 +237,7 @@ public function generateAssSubtitle(array $scenes, string $outputPath): string
|
||||
}
|
||||
|
||||
$ratio = mb_strlen($sentence) / max($totalChars, 1);
|
||||
$sentDuration = max($activeTime * $ratio, 0.8);
|
||||
$sentDuration = max($activeTime * $ratio, 1.5);
|
||||
|
||||
// 장면 경계 초과 방지
|
||||
if ($offset + $sentDuration > $duration) {
|
||||
@@ -250,7 +250,7 @@ public function generateAssSubtitle(array $scenes, string $outputPath): string
|
||||
$startTime = $this->formatAssTime($currentTime + $offset);
|
||||
$endTime = $this->formatAssTime($currentTime + $offset + $sentDuration);
|
||||
|
||||
$text = $this->wrapText($sentence, 14);
|
||||
$text = $this->wrapText($sentence, 16);
|
||||
$text = str_replace("\n", "\\N", $text);
|
||||
|
||||
$ass .= "Dialogue: 0,{$startTime},{$endTime},Default,,0,0,0,,{$text}\n";
|
||||
@@ -267,19 +267,65 @@ public function generateAssSubtitle(array $scenes, string $outputPath): string
|
||||
}
|
||||
|
||||
/**
|
||||
* 나레이션 텍스트를 문장 단위로 분리
|
||||
* 나레이션 텍스트를 자막 청크 단위로 분리
|
||||
*
|
||||
* 핵심 규칙:
|
||||
* - 한 청크 최소 10자 이상 (한글자/한단어 자막 방지)
|
||||
* - 장면당 2~3개 청크 목표
|
||||
* - 짧은 조각은 인접 청크에 병합
|
||||
*/
|
||||
private function splitIntoSentences(string $text): array
|
||||
{
|
||||
// 마침표, 느낌표, 물음표, 물결표 뒤에서 분리
|
||||
$sentences = preg_split('/(?<=[.!?~。!?])\s*/', $text, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$text = trim($text);
|
||||
|
||||
// 분리 안 되고 30자 이상이면 쉼표에서 분리
|
||||
if (count($sentences) <= 1 && mb_strlen($text) > 30) {
|
||||
$sentences = preg_split('/(?<=[,,])\s*/', $text, -1, PREG_SPLIT_NO_EMPTY);
|
||||
// 전체가 25자 이하면 분리하지 않음
|
||||
if (mb_strlen($text) <= 25) {
|
||||
return [$text];
|
||||
}
|
||||
|
||||
return ! empty($sentences) ? $sentences : [$text];
|
||||
// 1차: 마침표/느낌표/물음표 기준으로 분리
|
||||
$rawParts = preg_split('/(?<=[.!?。!?])\s*/', $text, -1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
// 분리가 안 되면 쉼표에서 시도
|
||||
if (count($rawParts) <= 1) {
|
||||
$rawParts = preg_split('/(?<=[,,])\s*/', $text, -1, PREG_SPLIT_NO_EMPTY);
|
||||
}
|
||||
|
||||
// 그래도 분리 안 되면 원본 반환
|
||||
if (count($rawParts) <= 1) {
|
||||
return [$text];
|
||||
}
|
||||
|
||||
// 2차: 짧은 조각을 병합하여 최소 10자 보장
|
||||
$minChunkLength = 10;
|
||||
$merged = [];
|
||||
$buffer = '';
|
||||
|
||||
foreach ($rawParts as $part) {
|
||||
$part = trim($part);
|
||||
if ($part === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$buffer .= ($buffer !== '' ? ' ' : '') . $part;
|
||||
|
||||
if (mb_strlen($buffer) >= $minChunkLength) {
|
||||
$merged[] = $buffer;
|
||||
$buffer = '';
|
||||
}
|
||||
}
|
||||
|
||||
// 남은 버퍼 처리
|
||||
if ($buffer !== '') {
|
||||
if (! empty($merged)) {
|
||||
// 마지막 청크에 붙이기
|
||||
$merged[count($merged) - 1] .= ' ' . $buffer;
|
||||
} else {
|
||||
$merged[] = $buffer;
|
||||
}
|
||||
}
|
||||
|
||||
return ! empty($merged) ? $merged : [$text];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user