서로 다른 워커에서 발생한 품질 문제 두 가지를 같은 날 고쳤다.
Shorts 스크립트 구조에서 hook은 오버레이 텍스트로 따로 표시된다. 근데 Gemini가 hook의 의문형 어투를 script 본문에 그대로 이어받는 경우가 있었다.
hook: "이게 진짜 가능한 일일까요?" script 첫 문장: "과연 이게 가능한지 알아봤습니다..."
script는 hook과 무관하게 바로 팩트 서술로 시작해야 한다.
프롬프트에 규칙 두 개를 추가했다.
의문형 종결은 script 전체에서 마지막 comment_bait 딱 1회만 허용.
hook 내용을 이어받지 말고 즉각 팩트 서술로 시작.
프롬프트만으로는 부족하다는 걸 전에도 경험했다. parseOutput에 검증 로직을 추가했다.
// 마지막 문장(comment_bait) 제외하고 의문형 감지
const sentences = script.split(/[.?!]/);
const body = sentences.slice(0, -1);
if (body.some(s => /니까|십니까/.test(s))) {
throw new ScriptError("SCRIPT_QUESTION_OPENING");
}
SCRIPT_QUESTION_OPENING도 재시도 대상에 추가해서 위반 시 최대 3회 자동 재시도한다.
edge-tts의 --file 옵션으로 TTS를 생성할 때 VTT 파일에 SSML 원문이 그대로 박히는 케이스가 있었다.
// 정상: 단어별 타임스탬프
00:00:00.000 --> 00:00:00.300
결국
// 비정상: SSML 원문 오염
00:00:00.000 --> 00:00:05.000
speak>결국 이 사태는...
이 상태에서 VTT를 파싱하면 타임스탬프 기반 자막이 비어있게 된다.
두 단계로 막았다.
// 1. SSML 태그 포함 엔트리 스킵
if (/speak>|<prosody|<break/.test(raw)) continue;
// 2. 파싱 결과 비어있으면 문자 비례 방식으로 fallback
const result = buildSrtFromVtt(vtt);
if (!result.length) {
console.warn("VTT 파싱 결과 비어있음 — 문자 비례 타이밍으로 fallback");
return buildSrt(script);
}
buildSrt는 전체 스크립트 길이 대비 각 문장의 문자 수로 타이밍을 계산하는 방식이다. 정확도는 떨어지지만 자막이 아예 안 나오는 것보다 낫다.