subtitles: Premiere Pro 캡션 수정하기
일회성 설정: 키트 루트의 INSTALL.md를 참조하세요. 플러그인을 사용하는 경우 설정 명령어는 bash "${CLAUDE_PLUGIN_ROOT}/install.sh" --data "${CLAUDE_PLUGIN_DATA}"이고, 수동 설치(네 개의 skills/ 폴더를 ~/.claude/skills/에 나란히 복사)의 경우 키트 루트에서 bash install.sh를 실행하세요. 스크립트는 이 스킬의 scripts/ 폴더에 있습니다. 이 스킬은 다른 스킬을 ../rough-cut/...로 참조합니다.
스킬의 기능
Premiere가 생성하는 자동 대본(.json 내보내기)은 특히 고유 명사(사진작가 이름이 두 개의 관련 없는 단어로 변환되는 등)에서 오타가 많습니다. whisper.cpp는 훨씬 더 잘 전사합니다. 이 스킬은 비디오를 로컬에서 다시 전사한 다음, Premiere의 정확한 형식(그대로 다시 가져올 수 있음)으로 .json을 재구성합니다: 참조 대본의 도움으로 이름을 수정하고, 구두점과 대문자를 복원하며, 작품 제목을 따옴표로 표시합니다. 마지막으로 강제 정렬을 통해 타이밍을 오디오에 맞게 재정렬합니다(whisper의 타임스탬프는 부정확함).
- YES: 로컬 전사, 이름 수정, 구두점 및 대소문자, 제목 주변 따옴표, 오디오 타이밍 재정렬(강제 정렬), Premiere와 동일한 형식, 재가져오기 준비 완료.
- NO: 비디오 자르기 또는 러프 컷(
rough-cut이 담당), 번역, 이미 올바른 캡션 파일 재동기화,.srt/.vtt자막(여기서 사용하는 형식은 Premiere의 네이티브 JSON).
예상 입력
- Premiere에서 내보낸 비디오(
.mp4/.mov): 전사할 오디오를 포함합니다. - Premiere에서 내보낸 캡션
.json: 복제할 대상 형식(키,speakerUUID, 언어)을 제공합니다. - 비디오의 참조 대본(일반 텍스트, Markdown, Google 문서, Notion 페이지, 붙여넣은 텍스트 등 읽을 수 있는 모든 형식): 고유 명사(사람, 작품, 장소, 브랜드) 철자의 권위 있는 출처입니다. 참조 대본이 없으면 요청하거나, 명백한 오타만 수정하고 불확실한 이름에 대해서는 whisper의 철자를 유지하세요.
언어
구두점 주변 간격, 악센트 문자, 구두점 세트, 따옴표 및 맞춤법 정규화는 languages/<code>.json(fr 및 en이 내장됨)에 있으며, 코드에는 절대 없습니다. 언어는 --lang <code>로 선택되며, 그렇지 않으면 Premiere JSON의 언어, 그렇지 않으면 whisper 대본에 기록된 언어, 그렇지 않으면 en이 사용됩니다. 파일 작동 방식 및 언어 추가 방법: ../rough-cut/references/languages.md(동일한 메커니즘, 이 스킬은 자체 languages/ 폴더를 가짐).
요구 사항 (머신당 한 번)
whisper-cli + ffmpeg + 모델 ~/Models/whisper/ggml-large-v3-turbo.bin. ../rough-cut/references/whisper-setup.md(공유 설정)을 참조하세요. 확인: which whisper-cli ffmpeg && ls ~/Models/whisper/.
강제 정렬(타이밍을 오디오에 재정렬, 6b단계):
pip3 install --break-system-packages ctc-forced-aligner torch torchaudio unidecode
약 1.2GB의 ONNX 모델이 첫 실행 시 다운로드됩니다(캐시 ~/ctc_forced_aligner/). 확인: python3 -c "import ctc_forced_aligner".
워크플로우
아래
scripts/...명령어는 스킬 폴더에 있는 것을 가정합니다. 작업 디렉토리는 두 셸 호출 사이에 재설정될 수 있습니다: 스킬 폴더로 한 번cd하거나, 모든 스크립트 앞에 절대 경로를 붙이세요. 작업 파일(오디오, 대본, 수정 사항)은 동기화된 폴더 외부의 임시 폴더에 저장됩니다.
1. 비디오 및 입력 형식 확인
먼저: 원본을 스냅샷으로 저장하세요. Premiere는 재내보낼 때 입력 .json을 다시 쓸 수 있으며, 이는 7단계에서 타이밍을 확인하는 유일한 동기화 진실입니다:
cp "<Reel.json>" /tmp/subs_original_premiere.json
ffprobe -v error -show_entries format=duration:stream=codec_type,r_frame_rate \
-of default=noprint_wrappers=1 "<video>"
지속 시간(마지막 단어가 그 안에 있어야 함)과 fps를 기록하세요. 그런 다음 입력 Premiere JSON을 검사하여 형식을 확인하세요:
python3 scripts/inspect_format.py "<Reel.json>"
이 명령어는 키(최상위/세그먼트/단어), speaker UUID, 언어, 세그먼트 수 및 현재 텍스트를 세그먼트별로(= 수정할 오타 목록) 출력합니다. 예상 형식은 references/premiere-format.md에 문서화되어 있습니다: 검사 결과 비정상적인 키가 표시되면 읽어보세요.
2. 로컬 전사 (whisper.cpp)
python3 scripts/extract_audio.py "<video>" /tmp/subs_audio.wav
python3 scripts/transcribe.py /tmp/subs_audio.wav /tmp/subs_transcript.json --model large-v3-turbo --language <code>
Apple Silicon에서 3분 기준 약 8초 소요. 대본에는 구두점과 대소문자가 있는 text와 타임스탬프가 있는 words가 포함된 세그먼트가 있습니다.
2b. whisper가 구두점을 넣지 않은 경우(연속 전달): 다시 구두점 처리
빠른 독백에서 whisper는 때때로 구두점이나 대문자가 없는 스트림을 반환합니다(세그먼트가 문장 중간에서 끊기고, ./, 없음). 명백한 증상: build_subs.py가 **eos: 0**을 보고합니다. 구두점이 없으면 캡션을 읽을 수 없습니다. 빠른 확인: python3 scripts/words_of.py /tmp/subs_transcript.json 0 1 2 --plain.
이 경우 직접 다시 구두점을 처리하세요(LLM 작업, whisper의 작업이 아님): 각 세그먼트의 text를 구두점과 문장 대문자로 다시 작성하되, whisper의 단어를 그대로 유지하세요: 구두점과 대소문자만 추가하세요. 어휘 수정(이름, 약어)은 corrections.json에 남겨둡니다. 구두점은 항상 단어에 붙습니다(so?이지 so ?가 아님): 공백이 앞에 오는 기호는 정렬에서 조용히 삭제되는 고립된 토큰이 됩니다(한 번에 114개의 ?/!가 손실된 적이 있음). build_subs.py는 언어의 간격(프랑스어의 경우 줄 바꿈 없는 공백)을 자동으로 다시 추가합니다. 결과를 JSON {index: "구두점이 있는 텍스트"}에 넣은 다음:
python3 scripts/repunctuate.py --transcript /tmp/subs_transcript.json --punct /tmp/punct.json --output /tmp/subs_transcript_punct.json
이 스크립트는 단어를 추가, 제거 또는 변경하면 거부합니다(차이점 표시): 이것이 단어-타이밍 정렬을 손상시키지 않도록 합니다. 세그먼트 간의 흐름을 고려하세요: 문장을 계속하는 세그먼트는 소문자로 시작하고, 문장을 여는 세그먼트는 대문자로 시작합니다. 나머지 단계(6단계의 --transcript)에는 이 구두점이 처리된 대본을 사용하세요.
또한 클립 끝 환각을 삭제하세요: 마지막 침묵에서 whisper는 종종 자막 크레딧이나 "시청해 주셔서 감사합니다"와 같은 줄을 만들어냅니다(각 언어의 일반적인 줄은 languages/<code>.json의 whisper_hallucinations 아래에 나열됨). seq_rule N에서 0으로 삭제하세요. 또 다른 환각: 동일한 텍스트를 가진 연속 세그먼트로, 단어의 지속 시간이 거의 0이며(start==end), 침묵 위에서 만들어진 반복입니다("here we go" x3). 탐지: 동일한 텍스트와 지속 시간이 0인 단어를 가진 세그먼트 i와 i+1. 수정: 빌드 전에 대본에서 삭제하세요(del segs[i:j]).
3. whisper 대본 및 참조 대본 읽기
대본을 세그먼트별로(text) 출력하고 입력 Premiere 텍스트(1단계)와 비교하세요. whisper는 이미 많은 것을 수정하지만 이름의 오타는 유지합니다. 참조 대본을 읽어 고유 명사와 인용된 작품 제목의 표준 철자를 확인하세요.
4. corrections.json 구축 (지능형 핵심)
여기서 품질이 결정됩니다. whisper가 들은 것과 대본의 이름을 비교하여 수정 파일을 만드세요. 영향을 받는 세그먼트의 단어별 분할을 검사하여 정확한 규칙을 작성하세요:
python3 scripts/words_of.py /tmp/subs_transcript.json 2 9 38 # 검사할 세그먼트의 인덱스 토큰
출력은 모든 단어를 인덱싱합니다(9:Smyth 10:Jones): 이것은 토큰화(이름이 몇 개의 토큰을 차지하는지, 하이픈 유지 등)를 추측하지 않고 정확한 규칙을 작성할 수 있게 합니다.
corrections.json의 형식(모든 키는 선택 사항):
{
"quote_char": "\"",
"normalize_cotes": true,
"seq_rules": [
[["jon","smyth"], ["John","Smith"]],
[["train","park"], ["Trent","Parke"]],
[["arthus","bertrand"], ["Arthus-Bertrand"]]
],
"word_rules": {"macdum": "Magnum", "scholler": "Schoeller"},
"titles": [
{"match": ["the","decisive","moment"], "canonical": ["The","Decisive","Moment"]},
{"match": ["errance"], "canonical": ["Errance"], "sep_comma": true}
]
}
seq_rules: 여러 단어로 된 이름, 또는 1대 N / N대 1 변환. 소문자로, 구두점이 제거된 단어에서 일치합니다. 2개 이상의 whisper 단어에 걸쳐 있는 이름이나 단어 수가 변경될 때 사용하세요. 대소문자: 대상 단어는 그대로 사용됩니다: 원하는 최종 대소문자를 쓰세요, 문장 내 위치 포함(단어가 문장을 열면["Today","we",...],["today",...]가 아님). 동일한 길이의 소스/대상: 내부 쉼표 구두점과 타이밍은 인덱스별로 유지됩니다. 다른 길이: 타이밍이 재분배되고 첫 번째/마지막 단어의 구두점만 유지됩니다(대체된 단어의 내부 쉼표는 손실됨: 7단계에서 해당 세그먼트를 다시 읽으세요). 삭제: 빈 대상[["uh"], []]은 단어를 제거합니다(whisper가 두 배로 만든 실제 고립된 소음에 예약하세요. 잘못 전사된 단어는 삭제보다 수정하세요. 그렇지 않으면 문장의 일부를 잃게 됨).word_rules: 한 단어 이름, 대소문자 강제(소문자 키에서 정확한 값으로).titles: 인용된 작품(책, 시리즈, 제목이 있는 사진)은 첫 번째 단어에 여는 따옴표, 마지막 단어에 닫는 따옴표와 함께 제목의 올바른 대소문자를 위한canonical을 자동으로 가져옵니다.sep_comma: true는 제목 뒤에 쉼표를 추가합니다(여러 제목이 연속될 때 유용함). 작품을 따옴표로 묶고, 사람 이름이나 브랜드는 그렇지 않습니다.normalize_cotes: 언어 파일의 맞춤법 정규화(normalize_words)를 적용하여 whisper가 일관되지 않게 쓰는 변형을 처리합니다. 설정하지 않으면 꺼짐.french_typography: 언어의 인쇄 간격에 대한 스위치(이름은 역사적이며 사용 중인 언어에 적용됨). 기본값은 언어 파일에서 가져옴: 프랑스어의 경우 이중 구두점 앞과 닫는 따옴표 앞, 여는 따옴표 뒤에 줄 바꿈 없는 공백(U+00A0); 영어의 경우 없음. 구두점을 붙이고 싶은 경우에만false로 설정하세요. 2b단계에서 다시 구두점을 처리할 때 구두점은 반드시 붙어 있어야 합니다(Flop:).build_subs.py는 간격을 자동으로 다시 추가합니다.
5. 사용자와의 불확실성 해결 (절대 추측하지 마세요)
whisper는 때때로 모호하거나 이해할 수 없는 비고유 명사 단어를 생성합니다. 조용히 추측하지 마세요. 이러한 지점을 타임코드와 함께 나열하고 실제로 말한 내용을 사용자에게 물어보고, 컨텍스트나 대본이 제안하는 경우 가설을 제시하세요. 다음을 구분하세요:
- 즉시 수정: 대본에 의해 확인된 고유 명사, 명백하고 확실한 오타.
- 먼저 질문: 모호한 비고유 명사 단어, 또는 의미를 변경하는 수정(예: whisper "who feels" 대 대본 "without": 의미 반전, 대본이 해결하더라도 표시).
말하기 스타일(은어, 채움 구문, 반복)을 다시 쓰지 마세요: 캡션은 오디오를 따라야 합니다.
답변을 기다리는 동안 파일에 쓸 내용(생성을 차단할 필요 없음): 모든 안전한 수정 사항과 일반적인 모호한 단어에 대한 최상의 가설을 적용한 첫 번째 버전을 생성하되(그리고 표시), 대본에 없는 불확실한 이름에 대해서는 whisper의 원시 철자를 유지하세요: 고유 명사를 절대 추측하지 마세요. 해당 버전을 검증할 지점 목록(타임코드)과 함께 전달한 다음, corrections.json을 업데이트하여 답변 후 재생성하세요. 이것이 정상적인 흐름입니다: 생성, 질문과 함께 제시, 개선.
6. 수정된 파일 생성
python3 scripts/build_subs.py \
--transcript /tmp/subs_transcript.json \
--original "<Reel.json>" \
--corrections /tmp/corrections.json \
--output "<folder>/<name>_corrige.json" [--lang <code>]
이 스크립트는 원본의 형식(speaker UUID, 언어, 정렬된 키, 축소된 UTF-8)을 복제하고, 구두점과 대소문자를 복원하며, 수정 사항을 적용하고, 따옴표를 추가하며, eos를 다시 계산하고 검증 보고서를 출력합니다.
<name>_corrige.json을 원본 옆에 작성하고, 원본 내보내기를 절대 덮어쓰지 마세요(사용자의 소스임). _corrige 접미사는 스크립트가 의존하는 파일 이름이므로 유지하세요.
6a. build_subs.py의 2가지 반복적인 함정 확인 (매번)
많은 비디오에서 발견된 두 가지 버그로, 매 빌드 후 확인해야 합니다:
- 소수점 숫자 중복. whisper는
6.6을 2개의 토큰(6+.6, 마찬가지로1,33또는4 AM을4+AM)으로 분할합니다. 구두점 재주입으로 출력에서 중복됩니다(1.4 .4,6.6. .6).seq_rules일치도 제대로 작동하지 않습니다(정규화가 내부./,를 제거하지 않음). 탐지: 최종 출력에서 패턴\d[.,]?\s+[.,]\d를 grep하세요. 수정: 빌드 전에 대본에서 수술: 2개의 토큰을 하나로 병합하세요({'text':'1.4','start':a.start,'end':b.end,...}; whisper 단어 스키마 =text/start/end/confidence,duration이 아님). 그런 다음 재빌드합니다. - 고립된 문장 끝 구두점 누락. whisper 세그먼트의
text에는 있지만 자체 단어 토큰이 없는?/!/:이 사라집니다(How did we do it ?는?를 잃음). 탐지: whisper 텍스트와 출력 간의count('?')(및!,:)를 비교하세요. 수정: 출력 JSON을 사후 편집: 이전 토큰에?(언어 간격 포함)를 다시 추가하세요.
함정 1이 탐지되면 대본을 수정하고 재빌드하세요(6단계). 함정 2인 경우 출력을 직접 패치하세요.
6b. 오디오에서 타이밍 재정렬: 강제 정렬 (항상)
whisper의 단어 타이밍은 부정확합니다(빠르고 여러 테이크의 독백에서 최대 7초 일찍, 비선형적으로 측정됨). whisper는 테이크 사이의 침묵을 압축하므로 음성이 캡션 후에 도착합니다. 일반적인 불만: "'아'가 나타날 때쯤이면 이미 다음 문장이 들립니다". DTW(-dtw)는 아무것도 변경하지 않으며(동일한 오프셋), 희소 앵커에 대한 시간 왜곡은 이미 올바른 영역을 왜곡합니다. whisper 텍스트는 좋지만 타이밍만 잘못되었습니다. 아래의 6b단계가 바로 그 타이밍을 수정합니다.
python3 scripts/align.py \
--audio /tmp/subs_audio.wav \
--transcript /tmp/subs_transcript.json \
--output /tmp/subs_transcript_aligned.json
이것은 강제 정렬을 실행합니다: 각 단어의 시작과 끝을 오디오 파형에 정확히 정렬합니다. 완료되면 build_subs.py에 전달하는 --transcript를 조정된 버전으로 업데이트하세요:
mv /tmp/subs_transcript_aligned.json /tmp/subs_transcript.json
그런 다음 6단계를 다시 실행하여 정렬된 타이밍으로 최종 JSON을 생성하세요.
7. 최종 JSON 검증
build_subs.py는 검증 보고서를 인쇄했어야 합니다. 다음을 확인하세요:
python3 scripts/compare_output.py /tmp/subs_original_premiere.json "<folder>/<name>_corrige.json"
이것은 다음을 보고합니다:
- 세그먼트 및 단어 수
- 평균/최대/최소 지속 시간
- 구조적 차이(누락된/추가된 키)
- 출력의 첫 번째 및 마지막 단어(비디오 지속 시간 내에 있어야 함)
동기화를 청취 테스트하세요: 원본 Premiere 프로젝트에서 _corrige JSON을 가져오고(자막 → 자막 파일 가져오기 → 수정된 파일 선택) 타임라인을 재생하여 캡션이 오디오와 일치하는지 확인하세요.