비트 싱크 (Beat sync)
scripts/ 디렉토리의 스크립트:
beats.py MUSIC --out beats.json [--start S --end E]— 템포, 비트, 다운비트 추정, 강한 온셋(히트), 초당 RMS 에너지.timemap.py— 가변 속도 타임맵(스피드 램프)을 만들고 ffmpeg로 프레임별로 렌더링.
1. 음악 분석
python beats.py song.mp3 --out edit/beats.json
출력: tempo, beats (초 단위), downbeats (가장 강한 위상에서 4번째 비트마다), hits (강한 온셋: 스네어/킥/드롭), energy (0.5초 단위).
결과 읽기:
- 비트 주기
P = 60 / BPM. 30fps에서 120BPM의 한 비트는 15프레임입니다. - 히트가 항상 비트는 아닙니다. 보컬 악센트, 라이저 종료, 드롭 등이 포함됩니다. "편집 오디오" 트렌드에서 컷 포인트는 메트로놈이 아닌 히트입니다.
- librosa는 반박자 또는 두 배 박자로 잠길 수 있습니다. 기본 점검: 일반 팝 90-130 BPM, 트랩 130-160(종종 반박자로 느껴짐), EDM 120-128, 펑크 130-150. 프레임 그래브 테스트에서 감지된 그리드가 잘못 느껴지면 반으로 나누거나 두 배로 하세요.
- 에너지 곡선은 드롭/코러스를 찾습니다: 조용한 구간 후 가장 큰 RMS 점프.
2. 노래 구간 선택
- 짧은 형식: 7-20초. 다운비트에서 시작하고 다운비트나 구절 끝(4 또는 8마디)에서 끝나도록 구간을 설정합니다.
- 가장 강력한 비주얼을 드롭에 배치합니다. 곡에서 드롭이 12초에 있고 릴에서 3초 지점에 넣고 싶다면 구간은 9초에서 시작합니다.
- 인스타그램/틱톡의 트렌드 사운드: 인앱 사운드는 고정된 지점에서 시작합니다. 최종 게시물이 인앱 사운드를 사용한다면, 해당 시작점에 맞춰 편집하고 가이드로 참조 오디오가 포함된 버전을 내보냅니다.
- 구간이 트랜지언트에서 시작하지 않으면 10-50ms로 페이드 인(클릭 방지), 0.3-1.5초 동안 페이드 아웃하거나 강한 히트에서 끝냅니다.
3. 그리드 구축 및 이벤트 배치
fps = 30
fr = lambda t: round(t * fps) # 모든 이벤트를 가장 가까운 프레임으로 스냅
U = beat_period / 2 # 노래가 바쁠 경우 서브비트 단위 (8분음표)
- 컷: 흐름을 위해 비트 위에, 임팩트를 위해 히트 위에. 모든 비트에서 컷하면 지루함; 2 또는 4비트마다 컷하고, 드롭 전에만 버스트(2-4초)로 모든 비트 컷 사용.
- 예측: 비트보다 1-2프레임 앞서 배치된 컷이 휴대폰에서 더 tight하게 느껴짐 (오디오 지연 + 인식). 둘 다 테스트하고 일관되게 선택.
- 플래시 / 쉐이크 / 줌 펀치: 히트 위에, 2-6프레임, 감쇠.
- 텍스트: 단어가 보컬 음절 또는 비트에 나타남; 단일 단어의 경우 단어당 최소 0.4초 유지, 리듬에 맞춰 읽히는 줄의 경우 단어당 약 0.25초.
- 속도 / 효과 변경: 다운비트 또는 드롭에서.
- 모든 이벤트를 음악적 이유와 함께 테이블(
timing.json)로 유지; 빌드 스크립트가 이를 읽습니다.
4. 스피드 램프 (속도)
스피드 램프는 타임맵 src_t = f(out_t)입니다. 이징된 속도로 세그먼트를 구성합니다:
# 속도 곡선: 1x ... 히트 사이에서 4x로 램프 ... 히트에서 다시 0.3x로
segments = [(out_start, out_end, speed_start, speed_end, ease)]
- smoothstep/cubic으로 속도를 인/아웃 이징; 즉시 1x→4x 점프는 글리치처럼 보입니다.
- 히트 위에서 느리게 (0.2-0.5x), 히트 사이에서 빠르게 (2-5x). 히트 프레임 자체가 주요 동작(임팩트, 턴, 룩)을 보여줍니다.
- 느린 구간은 고프레임 소스 또는 프레임 보간(
minterpolate, Resolve Speed Warp, RIFE)이 필요합니다. - 빠른 부분에 모션 블러 추가(
tmix=frames=3또는 타임맵 렌더러에서 프레임 블렌드)로 스트로빙 방지. - 실제 사례: 오버헤드 책상 샷, 카메라 이동 부분은 1x 유지, 정적 부분은 5.6x, 모든 속도 변경은 강한 히트에서; 광학 흐름 패스(OpenCV LK + RANSAC 유사성)가 카메라 이동을 자동으로 찾음 (
motion-tracking참조).
timemap.py는 모든 출력 프레임에 대해 올바른 소스 프레임을 가져와 타임맵을 렌더링합니다 (선택적 프레임 블렌드 포함) — setpts 필터 체인보다 정확하고 간단합니다.
5. 참조 릴의 타이밍 매칭
- 참조 영상을 다운로드/입수하고 오디오 추출.
- 컷 감지:
ffmpeg -i ref.mp4 -vf "select='gt(scene,0.3)',showinfo" -f null - 2>&1 | grep pts_time. - 해당 오디오에서 비트 감지; 각 컷을 비트 인덱스 또는 오프셋으로 표현 → 이것이 리듬 템플릿입니다.
- 텍스트 온/오프 시간 측정: 변화 주변에서 프레임 단계별(1/30초 정밀도)로 확인.
- 동일한 그리드에서 자신의 푸티지로 재구축 (
reference-matching참조).
6. 검증
- 렌더를 장면 감지; 모든 컷 시간 × fps가 계획된 프레임과 일치해야 함 (±0).
- 디버그 렌더에서 비트에 클릭 트랙을 오버레이(
aevalsrc사인 버스트)하고 히트에서 프레임 캡처. - 청취는 불가능: 숫자 보고(계획된 컷 프레임 대 측정된 컷 프레임), 사용자에게 귀로 확인 요청.