새 영상: 테이크에서 마스터까지
시작 전: harness.config.json → configured: true (그렇지 않으면 setup 스킬 실행), bash video/scripts/doctor.sh에서 MISSING 없음. brand-kit.md, editing-taste.md, exemplars.md의 '취향 프로필', anti-patterns.md, delivery-specs.md를 읽어라. 영상 스타일은 사용자 프로필에서만 가져온다: 포스터 유무, 카드, 스티커, B-롤, 사운드, 그래픽 양, 템포 등. 프로필에 없는 것은 직접 결정하지 말고 사용자에게 물어봐라. 코드의 시작점은 현재 video/src/이다.
만약 video/public/footage.mp4가 이전 영상 작업에서 남아있고 아직 edits/에 보관되지 않았다면, 먼저 보관(11단계)한 후 시작하라.
1. 테이크 검사
ffprobe -v error -show_entries format=duration:stream=width,height,r_frame_rate:stream_side_data=rotation -of default=nw=1 takes/1.MOV
방향은 rotation으로 확인하고 width로 확인하지 마라 (아이폰은 세로를 1920x1080 + 플래그로 기록함). 3-4 프레임을 확인하라 (ffmpeg -ss N -i … -frames:v 1 out/peek-N.jpg). 삼각대 촬영본은 안정화하지 마라 — vidstab이 흔들림을 추가한다. 손으로 촬영한 것이 명확한 경우에만 원본을 안정화하라 (vidstabdetect=shakiness=6:accuracy=15 → vidstabtransform=smoothing=25:optzoom=1:interpol=bicubic, -c:a copy; libvidstab이 포함된 ffmpeg 필요) 이후 안정화된 파일로 작업하라.
2. 테이크 및 펄스 스타트 — 자르기 전에
cd video && python3 scripts/chunks.py ../takes/1.MOV
전체 자막은 반복을 숨기지만 시청자는 즉시 알아챈다. 찾아야 할 것: 같은 구절이 있는 인접 조각(테이크 — 마지막 것을 남겨둠), 다음 조각이 다시 시작하는 1-2 단어 조각(펄스 스타트). 제거할 목록을 타임코드와 함께 사용자에게 보여주고 '예'를 받아라. 경계는 묵음 내부에 있어야 한다.
3. 자르기
cp public/captions.json out/captions.backup.json 2>/dev/null # cut.py가 자막 덮어씀
DROP="12.40-15.80" python3 scripts/cut.py ../takes/1.MOV
템포는 프로필의 cut에서 가져온다 (레퍼런스에 따라 조정됨). 스크립트는 DURATION, 이음새 프레임, '음성 위 이음새' 검사를 출력한다. 사용자가 원하는 템포에 비해 이음새가 너무 적으면 프로필 아래 인수를 전달하라 (예: … 0.4 0.2 -> 갭 ~0.35 / 0.2), 너무 많으면 위로 전달하라. 이음새가 전혀 없으면 — 시끄러운 방: QUIET=-46. 빠른 화자/느린 템포: SPEED=1.1. 음성 위 이음새가 있으면 DROP 또는 파라미터를 조정하고 그대로 두지 마라.
4. 마팅 — 바로 실행하라 (style.poster.enabled인 경우)
TITLE_OUT = cut.py 출력의 첫 번째 이음새 (포스터가 그 시점에 나감), 보통 4-7초. 마팅은 가장 오래 걸리는 부분 (~CPU에서 프레임당 8초, 워커 2개 ≈ 분당 6 프레임)이므로 백그라운드에서 실행하고 진행되는 동안 코드를 작성하라:
python3 scripts/matte.py <TITLE_OUT> 2 > out/matte.log 2>&1 # 백그라운드 실행 (run_in_background)
진행 중에는 스틸을 렌더링하지 마라 — 마팅을 방해한다. 완료 대기는 ps -axo command | grep "[m]atte.py"로 하고 pgrep을 사용하지 마라. 스크립트는 완료된 프레임을 건너뛰므로 재시작 후에는 그냥 다시 실행하라.
5. 자막 수정
public/captions.json 열기 (w, s, e는 초 단위, em):
- 잘못 들은 부분 수정: 이름, 브랜드, 용어, 영어 표현; 약어는 표기법대로 (
AI,ROI); whisper는 꼬리 부분 묵음에서 'Спасибо за просмотр' / 'Thank you.'를 좋아함 — 제거하라; em: 2(style.captions.highlight가size또는both인 경우) 및"accent": 1(color또는both인 경우) — 의미에 따라, 프로필에 설명된 수량과 단어에 적용하라 (brand-kit.md§2, §4). 설명되지 않았다면 어떤 단어를 강조할지 물어봐라.
6. 비트 목록
직접 또는 (긴 영상의 경우) script-beats 서브 에이전트를 통해 video/SCRIPT.md를 작성하라:
- 포스터 —
style.poster.enabled인 경우에만: 사용자의 언어와 어조로 된 줄, 강조와 키커는 프로필에 따라; - 카드 —
style.cards가none이 아닌 경우,editing-taste.md§3의 밀도로 (숫자 →count; 단계 →num+head; 열거 →head+lines또는 단어 위에 아이콘이 있는row);from= 구절의 첫 단어 + 4 프레임; FLOATS스티커 —style.stickers인 경우에만: 한 단어 캡션, 간결한 픽토그램 (ART_NAMES);pain: true카드 — 사용자가 '이렇게 하지 마세요'를 원하는 경우에만.
코드 작성 전에 비트 목록을 간략히 사용자에게 보여주라 (무엇이 어디에 나타날지) — 사용자의 영상, 사용자의 결정.
7. 코드
src/Composition.tsx:DURATION,TITLE_OUT,KICKER,TITLE,BEATS,FLOATS;Captions from= 포스터가 있으면 두 번째 구절 시작, 없으면 0.src/ui/Footage.tsx:BEATS테이블을 cut.py의 이음새를 기준으로 정확히, 최대 강도로 (클로즈업 1.16–1.21×, 일반 1.06–1.09×) —style.motion.punch가 알아서 약화시키거나 끈다. 클로즈업 위치는editing-taste.md§4에 따름; 클로즈업 후에는 다시 넓게, 그렇지 않으면 클로즈업이 '고정'됨; 포스터가 있으면 첫 번째 이음새에 적용.- 새 컴포넌트는 카드, 스티커,
Photo/Clip/Tag로 표현할 수 없는 경우에만 만든다 (그렇다면scene-builder사용). - Higgsfield의 B-롤 — 연결되어 있고 프로필이 원하는 경우 (
brand-kit.md§5):<Clip>을 통해 보여주고 (미리 자르고 음소거:ffmpeg -an -t <초>), 삽입 전에 생성된 프레임의 텍스트/얼굴/손을 확인하라. 먼저 사용자에게 물어보고 가격을 알려줘라. - 사운드 —
style.sfx인 경우에만: 부족한 롤 (card,count,end)은audio-agent서브 에이전트를 통해 프로필 설명에 따라 생성. npm run lint— 깨끗해야 함.
8. 승인용 프레임 (마팅 후)
각 카드와 스티커에 대해 완전히 조립된 순간의 스틸 한 장씩, 포스터는 수명 중간 (마지막 줄이 머리카락 위로 읽힘), 여러 자막 구절, 마지막 프레임을 렌더링하라:
npx remotion still MyComp out/key/f<N>.png --frame=<N> --scale=0.5 --timeout=120000
한 줄에 4개씩 번호 레이블과 함께 리스트로 조합하고 (PIL; 키릴 문자 글꼴 사용, 예: /System/Library/Fonts/Supplemental/Arial Bold.ttf), 사용자에게 보여주라 (SendUserFile). 마스터는 사용자의 '확인' 후에만. 리스트는 몇 분, 마스터는 10-20분.
보여주기 전에 직접 확인하라: 주요 텍스트가 하단 15% / 상단 8% 밖에 있는지; 아무것도 겹치지 않는지; 스티커가 카드와 교차하지 않는지; 마지막 프레임이 비어 있거나 잘리지 않았는지; 모든 것이 사용자 프로필과 일치하고 추가된 것이 없는지.
9. 마스터
npm run master # 백그라운드 실행; 프로세스가 완료될 때까지 대기, 파일 생성으로 대기하지 마라
스크립트가 자동으로 컷을 확인하고 프레임을 계산하여 out/preview.mp4를 만든다. 음량 확인: ffmpeg -i out/final-master.mp4 -af ebur128 -f null - 2>&1 | grep -A3 Summary ≈ audio.lufs 목표치. 사용자에게 미리보기를 보내라.
마스터 후 수정 ('이 구절을 잘라내') — 마스터 자체를 직접 자르라 (ffmpeg trim+concat to silence), 테이크를 다시 자르거나 전체를 다시 렌더링하지 마라; 미리 예상 시간을 알려줘라. 이음새 확인: 주변 6초를 잘라내고 whisper로 돌려보면 정확히 두 개의 필요한 구절이 일치해야 함.
10. 썸네일
프로필에 명시되지 않은 경우 어떤 썸네일이 필요한지 물어봐라: 제목이 있는 프레임 (Cover), 영상 포스터 (PosterCover) 또는 직접 선택. 프레임은 사용자와 함께 선택하라: 눈을 뜨고 선명한 프레임 3-4개 제안 (ffmpeg -ss <초> -i takes/1.MOV -frames:v 1 -q:v 2 out/cover-N.jpg).
- 선택된 프레임 →
public/cover-bg.jpg. rembg i -m birefnet-portrait public/cover-bg.jpg out/cover-matte.pngpython3 scripts/cover-sticker.py public/cover-bg.jpg out/cover-matte.png public/cover-cutout.png 20Root.tsx→CoverdefaultProps.lines: 사용자의 말로 된 제목,accent/em— 사용자가 원하는 대로. 크기는 자동 조정됨 — 글자 크기를 직접 수정하지 마라.npx remotion still Cover out/cover.png --timeout=120000— 보여줘라.
대안: PosterCover — 영상 포스터를 썸네일로 사용 (프레임 + poster-bg.jpg / poster-cutout.png의 컷아웃).
11. 보관
edits/<N>/ (루트에, video/ 안에 있지 않음): Composition.tsx, Footage.tsx, captions.json, SCRIPT.md, cover.png, final-master.mp4, preview.mp4. 사용자가 원하면 다음 스킬 publish 실행.