라펠 싱크은(는) 무엇을 하나요?
카메라 클립과 분리된 녹음기 트랙을 입력받아, 클립당 하나의 라펠 오디오 조각과 싱크된 Resolve 타임라인을 출력합니다. 라이브 강연에서 처음 테스트되었습니다: 5개의 카메라 클립을 59분 분량의 녹음기 트랙과 동기화했으며, 모든 클립이 20ms 이내(1프레임 미만)로 정확히 정렬되었습니다.
중요한 이유: 라이브 이벤트에서 카메라 마이크는 방 건너편에 있습니다. 그 오디오는 사용할 수 없으며, 라펠 마이크가 유일한 깨끗한 사운드입니다. 이 스킬을 적용하면 <work>/<clip>.wav와 <clip>.json이 라펠 오디오를 가리키므로, 페이퍼 에딧과 비디오 릴스는 아무것도 변경된 것을 모르고 깨끗한 오디오와 깨끗한 트랜스크립트를 읽을 수 있습니다.
설정: 다음을 입력하세요
| 항목 | 설명 | 예시 |
|---|---|---|
<FOOTAGE_DRIVE> | 원본 영상이 있는 드라이브, 비디오당 하나의 폴더, 녹음기 파일은 audio/ 하위 폴더에 있음 | /Volumes/MyWorkDrive |
<SYNC_OUT> | 고품질 라펠 오디오 조각이 저장될 위치 | ~/Movies/<video>-sync |
<LUT_PATH> | 선택 사항인 LUT, Resolve의 LUT 폴더 기준 상대 경로 | MyLuts/SLog3-to-709.cube |
WHISPER_MODEL (환경 변수) | whisper.cpp 모델 파일 | 기본값 ~/.cache/whisper-models/ggml-small.en.bin |
필요 사항: ffmpeg, ffprobe, whisper-cli (whisper.cpp), Python 3 (numpy와 scipy는 ~/.cache/lapel-sync-venv에 자동 설치됨), execute_resolve_code를 노출하는 DaVinci Resolve MCP 서버, 그리고 paper-edit 스킬 (ensure_resolve.sh를 위해).
실행
작업 디렉토리: 세션 스크래치패드, 예: <scratchpad>/<video>. 아래 스킬 디렉토리는 .claude/skills/lapel-sync입니다. 고품질 오디오 조각은 <SYNC_OUT>으로 이동합니다.
zsh 주의: 인라인 셸에서 $ss:end_sample는 :e를 수식어로 해석하여 조용히 텍스트를 먹어버립니다. 항상 ${ss}로 쓰세요.
1. 영상과 오디오 찾기
<FOOTAGE_DRIVE>에 비디오당 하나의 폴더가 있습니다. 녹음기 파일은 audio/ 하위 폴더에 있습니다:
find "<FOOTAGE_DRIVE>" -maxdepth 4 -iname "*<keyword>*" -not -path "*/.*"
ls "<folder>" "<folder>/audio"
예를 들어 Tascam DR-10L은 000_YYMMDD.wav, 001_...와 같이 15분마다 분할된 파일과 _D 쌍을 기록합니다. _D 파일은 이중 녹음 안전 트랙으로, 약 20dB 낮습니다. 기본 파일을 사용하고 클리핑된 부분을 패치하기 위해 _D를 보관하십시오.
2. 녹음기 파일 이어 붙이기
bash <skill>/scripts/stitch.sh "<folder>/audio" "<SYNC_OUT>/lav-master.wav"
각 파일의 BWF time_reference(자정 이후 샘플 수)를 읽고 모든 파일이 마지막 파일이 끝난 정확한 지점에서 시작하는지 확인합니다. 샘플이 연속적이면 무손실 스트림 복사가 이루어집니다. 갭이 있으면 해당 시간만큼 무음을 삽입하여 마스터가 실제 시간에 맞도록 유지하고 경고를 표시합니다.
3. 양쪽 트랜스크립션
bash <skill>/scripts/transcribe_sources.sh "<folder>" "<SYNC_OUT>/lav-master.wav" "<work>"
모든 것은 <work>/sync/로 이동하며, 페이퍼 에딧의 sentences.py가 읽지 못하도록 <work> 자체에는 보관되지 않습니다. 카메라 오디오는 먼저 강력하게 증폭됩니다(highpass=f=120,volume=40dB,alimiter, 16k 모노). 카메라 오디오는 종종 약 -65dB이기 때문입니다. 이미 완료된 작업은 건너뛰므로 자유롭게 다시 실행할 수 있습니다.
라펠 오디오는 오디오 1시간당 약 8분이 소요됩니다. 백그라운드에서 실행하세요. Whisper는 단어 타임스탬프를 <work>/sync/lav.log로 스트리밍하고, align.py는 lav.json이 아직 작성되지 않은 경우 해당 로그를 읽어 라펠 오디오가 완료되기 전에 정렬을 시작할 수 있습니다. 트랜스크립션 지점을 지난 클립은 "N개의 단어 일치만 있음"이라고 보고합니다. 완료되면 다시 실행하세요.
4. 정렬
python3 <skill>/scripts/align.py "<work>"
<work>/sync/sync.json을 작성합니다. 첫 번째 실행은 ~/.cache/lapel-sync-venv를 생성하여 numpy와 scipy를 설치합니다(시스템 python3에는 종종 둘 다 없음). 그 후에는 내부에서 자체적으로 다시 실행됩니다.
작동 방식(출력을 신뢰하거나 의문을 가질 수 있도록):
- 단어 우선. 두 트랜스크립트에 모두 나타나는 고유한 4단어 실행을 찾습니다. 오프셋 = 라펠 시간 - 카메라 시간. 클립당 수백 개의 일치 항목; 가장 밀집된 클러스터를 유지하고, 0.5초 이내, 중앙값을 사용합니다. 약 0.1초 정확도.
- 파형 미세 조정. 카메라 오디오의 각 20초 청크(40초 미만 클립의 경우 6초 청크)를 300-3000 Hz, 8kHz로 대역 통과 필터링한 후, 단어 오프셋의 +-0.6초 이내에서 라펠 오디오와 교차 상관 관계를 계산합니다. 중앙값의 6배를 초과하는 피크를 찾습니다. 20ms 이내의 청크는 서로 일치합니다. 5분 이상의 클립에서는 선형 피팅(linear fit)으로 오프셋과 드리프트를 제공합니다.
출력되는 모든 줄을 읽으십시오: 오프셋, 일치하는 청크 수, 분산, 드리프트. CHECK BY EAR로 표시된 클립(일치하는 청크가 3개 미만)은 최대 0.1초 정도 어긋날 수 있습니다. 신뢰하기 전에 Resolve에서 스크러빙하여 확인하십시오. 클립 전체에서 한 프레임 이상의 드리프트가 있는 경우에도 플래그가 표시됩니다.
5. 클립별로 라펠 오디오 자르기
python3 <skill>/scripts/slice.py "<work>" "<SYNC_OUT>/lav-master.wav" "<SYNC_OUT>"
각 클립에 대해 다음을 작성합니다:
<SYNC_OUT>/<clip>_lav.wav: 24비트, 원본 샘플레이트, 클립 길이에 샘플 정확하게 일치하여 라펠 시간이 클립 시간과 같음<work>/<clip>.wav(16k 모노) 및<work>/<clip>.json(슬라이스의 whisper): 페이퍼 에딧과 비디오 릴스가 읽는 파일"audio"필드가 슬라이스를 가리키는<work>/clips.ndjson
녹음기가 클립에 대해 늦게 시작했거나 일찍 중지된 경우, 해당 부분은 무음으로 채워지고 경고가 표시됩니다. 슬라이스에 대한 Whisper 작업은 몇 분이 소요됩니다. 이후에 페이퍼 에딧의 transcribe.sh를 실행하지 마십시오(json이 이미 존재하므로 어차피 건너뛸 것입니다).
6. 클리핑 확인
slice.py는 슬라이스당 0.99 풀 스케일 이상의 샘플 수를 계산하고 가장 긴 실행을 출력합니다. 수백 개의 짧은 실행(수십 개의 샘플)은 일반적으로 녹음기의 리미터가 피크를 잡는 것이며, 왜곡이 아닙니다. 1ms 이상의 실행이 나열된 경우나 사용자가 소리가 깨진다고 말하는 경우에만 _D 쌍에서 해당 순간을 패치하십시오(동일한 타이밍, 약 20dB 증폭).
7. Resolve에서 구축
bash .claude/skills/paper-edit/scripts/ensure_resolve.sh
<work>/sync/params.json을 작성합니다:
{"project":"<Video> - YYYY-MM-DD","work":"<work>","name":"Synced - Full Talk",
"lut":"<LUT_PATH>"}
lut은 선택 사항입니다. 사용하지 않으면 생략하십시오. 그런 다음 execute_resolve_code 호출 한 번:
PARAMS = "<work>/sync/params.json"
exec(open("<skill>/scripts/resolve_sync_timeline.py").read())
프로젝트를 생성하거나(이름이 이미 존재하면 열기), 카메라 클립과 라펠 오디오 슬라이스를 가져오고, Synced - Full Talk 타임라인을 구축합니다: 각 클립은 V1에 그림 전용(mediaType: 1), 해당 슬라이스는 A1에 사운드 전용(mediaType: 2), 동일한 in/out 프레임을 가집니다. recordFrame = 타임라인 시작 + round((오프셋 - 가장 이른 오프셋) * 24000/1001)이므로 클립 간의 실제 간격이 유지됩니다. LUT는 노드 1에 적용됩니다. 카메라 스크래치 오디오는 비활성화됩니다. 클립이 세로인 경우 타임라인은 1080x1920, 그렇지 않으면 1920x1080입니다(width/height로 재정의 가능). 타임라인 이름이 이미 사용 중이면 기존 타임라인을 건드리지 않고 v2를 추가합니다.
사용자가 각 클립에서 명확한 자음("p" 또는 "t")이 있는 몇 군데를 스크러빙하여 확인하도록 하십시오. 이것이 실제 확인입니다.
8. 인계
사용자에게 타임라인이 존재하고, 4단계의 클립별 숫자가 각각 한 줄씩, 플래그가 지정된 사항, 클리핑 판정을 알려줍니다. 그런 다음 페이퍼 에딧(트랜스크립션 단계는 건너뛰기; <work>가 이미 설정됨) 또는 비디오 릴스로 이동합니다.
알아둘 가치가 있는 사실
- 파형 상관 관계만으로는 실패했습니다. 카메라 마이크가 잔향이 있는 방에서 평균 약 -65dB일 때, 넓은 창에 걸친 전체 클립 및 20초 청크 상관 관계는 일관성 없는 쓰레기를 생성했습니다. 단어를 먼저 찾은 다음 좁은 파형 검색을 하는 것이 효과적입니다.
- 카메라 시계는 종종 잘못되었습니다. 테스트에서 카메라는 녹음기보다 약 149초 느렸습니다. 일부 카메라(예: Sony)는
creation_time을 UTC(끝에Z)로 기록하는 반면, 녹음기의 BWFtime_reference는 로컬 시간입니다.creation_time은 대략적인 힌트로만 취급하십시오. 단어 일치에는 필요하지 않습니다. - 드리프트는 테스트된 장비에서 무시할 만합니다. 약 0.5ms/분, 21분 클립에서 11ms입니다. 시간 스트레칭이 필요하지 않습니다. 짧은 클립은 몇 초에 걸친 피팅이 단순히 노이즈이기 때문에 0으로 보고합니다.
- 스크립트를 다시 실행하면 수동으로 만든 슬라이스와 6ms 이내로 재현되었습니다.
- 세로 영상: 예를 들어, 세로로 촬영하는 Sony는 rotation=90 디스플레이 매트릭스로 4K 23.976을 기록합니다. Resolve는 회전 수정 없이 2160x3840으로 읽습니다.
- 예시 녹음기, Tascam DR-10L: 48k 24비트 모노 BWF, 15분마다 새 파일,
_D안전 쌍은 약 20dB 낮음. - 페이퍼 에딧의
resolve_build.py는 여전히 카메라 파일에서 사운드를 가져옵니다. 라펠 촬영의 경우,"audio"필드를 읽도록 업데이트될 때까지 해당 타임라인은 카메라의 스크래치 오디오를 전달합니다. 그때까지는 사용자에게 알리거나 A1을_lav.wav슬라이스로 다시 연결하십시오. 슬라이스 시간이 클립 시간과 같기 때문에 컷 포인트는 동일합니다. - Whisper는 단어 타임스탬프를 위해
-oj -ml 1 -sow와 함께 실행됩니다.
관련 스킬
paper-edit: 러프 컷, 이 스킬 후에 실행video-reels:clips.ndjson을 읽습니다."audio"필드는 슬라이스에서 사운드를 가져오도록 지시합니다(설치된 경우)