/bw-song-ad — 흑백 카툰 송 애드를 완성품까지
2026-10-18, 오히사마 팬츠 H-OHI-023a 로 만든 절차. 참고는 X 의 WhiteHat MRR (골밀도·흑백 카툰·약 4분). 본인의 반응은 "노래가 fire 미침, 신이다". 참고 동영상의 정체는 짧은 징글이 아니라, 롱폼 VSL 대본을 거의 그대로 노래한 것.
순서 (변경 금지)
가사 → 노래 (Suno) → 1글자 단위 시간 맞춤 → 장면 분할 → 캐릭터 시트 → 키프레임 → 영상화 → 배경 추출 → Remotion → 검품. 노래가 시계입니다. 그림을 먼저 만들면 노래 박자와 맞지 않아 전부 다시 하게 됩니다.
S=~/.agents/skills/bw-song-ad
AD=<케이스 폴더>/<광고 ID> # lyrics.md / captions.txt / song/ / shots.py / keys/ / clips/ / remotion/
cp -R $S/remotion $AD/remotion && cd $AD/remotion && NODE_USE_ENV_PROXY=1 npm install
0. 먼저 재고 확인
같은 브랜드에서 전에 만든 그림·영상이 있으면 먼저 사용 (브랜드별 자재 재고표 ASSETS.md 확인·새로 만들면 추가). 생성이 필요할 때는 만들기 전에 "이미지 ○장·영상 ○개·약 ○크레딧"이라고 한마디 전할 것. 이번 실적: 이미지 61장 약 15, Grok 영상 61개 약 265.
1. 가사 (lyrics.md) — reference/lyrics.md 의 형식
- VSL 흐름을 단락별로 노래함. 라임은 맞추지 않음. 참고와 1:1 전개: 전문직의 말문 열기 → 정석을 "아닙니다" ×3 → 더 중요한 것 → 원인은 생각하는 곳에 없음 → 몸속의 아군과 적군 → 예전에는 잘 되었음 → 지금은 적군이 이기고 있음 → 문제는 여기서부터 (방치의 연쇄) → 판매장은 같은 것 투성이 → 하지만 변했다 (해결) → 기사로 (등록 불필요) → 재고 → "이제 ~하지 않아도 돼" ×3 → "괜찮아요. 아래를 탭"
- 전부 히라가나. 숫자도 히라가나 (Suno는 한자와 숫자에서 망가짐).
- 상품명은 노래하지 않음 (화면의 라벨로 표시). 금액 없음. 약속은 상품 페이지 범위.
- 보여주고 나서 작곡함.
2. 노래 (Suno·Playwright)
- 로그인만 본인. 나머지는
suno.com/create?mode=CUSTOM에 가사·Style·Title 을 넣고 Create. - Style (적중):
1930s vintage upbeat jazz, old cartoon soundtrack, swing, clarinet, muted trumpet, ragtime piano, brushed snare, upright bass, warm female vocal, Japanese, clear diction, storytelling sung melody - 1회에 2곡 나옴. 둘 다 whisper 로 전체 받아쓰기해서 가사와 대조하고, 망가지지 않은 쪽을 채택.
- 다운로드는 곡 페이지 More options → Download → MP3 → "Unlock & Download" (무료枠 1회 사용).
cdn1.suno.ai는 403, 재생용 m4a는 암호화. 브라우저 내 blob 을 빼내서 횟수를 피하는 것은 하지 않음. song/song.mp3에 배치.
3. 자막문 (captions.txt)
- 가사의 1단락 = 1줄. 줄 안을
/로 구분 (1덩어리 = 화면에 1회 나오는 문자. 12자 전후까지). - 한자 가나 섞인 읽기 쉬운 일본어로 고침 (가사는 히라가나여도 자막은 보통 표기).
- 2줄로 내보내고 싶은 곳은
|(예:아래 버튼에서|기사 읽기).
4. 1글자 단위 시간 맞춤
uv run --python 3.12 --with openai-whisper python $S/scripts/align.py $AD
→ $AD/cap_events.json (덩어리별 나오는 초·사라지는 초) 와 $AD/line_starts.json (각 줄의 노래 시작).
- whisper-cli 의 줄 단위 시간은 사용하지 않음. 인트로 (노래 없는 앞부분 몇 초) 를 0초부터 노래하는 것으로 처리하고, 줄 안도 균등 분할이 되므로 자막이 "노래하지 않는데 진행됨".
- 노래가 멈춘 구간 (0.8초 이상) 은 자막을 지움.
- 일치율이 80% 미만이면 노래가 망가졌거나 captions.txt 가 가사와 어긋난 것.
5. 장면 분할 (shots.py)
STYLE(전 컷 공통 그림체 문장) 과SHOTS = [(줄 번호, 그 줄의 몇 번째 컷, 장면, 움직임), ...].- 1컷 2
5초. 긴 줄은 23컷으로 나눔. 3분 30초에 약 60컷. - 장시간은 전부 영상으로 하지 않음: 정지 화면을 천천히 확대하는 컷을 절반 섞으면 영상 생성이 절반으로 줄어듦.
- 캐릭터: 선생님 (밥·앞치마) / 엄마 (낮은 쪽머리·스트라이프) / 아이 (단발) / 몸속 전령 2명 (줄무늬 작업복·모자) / 악역 (검은 동그라미·뿔·스펀지와 빨대) / 상품 (실물 무늬).
6. 캐릭터 시트와 키프레임 (Higgsfield gpt_image_2_5·9:16·1장 0.25)
- 먼저 캐릭터 시트 1장 만들고, 이후 모든 컷에 캐릭터 시트 + 참고 1컷 2장을
image_references로 전달. - 문두에
STYLE: black and white 1930s rubber-hose cartoon, thick clean ink lines, cross-hatching, grayscale only, vintage Fleischer-era look, slightly grainy film, soft dark round vignette, no text. python3 $S/scripts/make_reqs.py $AD image→ 12건씩generate_image_batch.- 아이는 옷을 입힘 (속옷·기저귀 차림은 nsfw 로 떨어짐). 참고 1컷에 속옷이 찍혀 있으면 그것만으로 떨어짐.
- 상품은 실물 무늬를 프롬프트에 씀 (존재하지 않는 무늬가 나오면 재작성).
- 전부 콘택트 시트로 봄: 그림체·글자 없음·캐릭터 시트대로.
7. 영상화 (Grok Imagine grok_video_v15_lite·720p·start_image 만·1초 ≒ 1크레딧)
python3 $S/scripts/make_reqs.py $AD video→ 12건씩generate_video_batch. 길이는 담당 구간 + 0.5초 (3~8초).- 프롬프트는 "이 그림을 움직임: <움직임>. 같은 그림체·구도·캐릭터 그대로, 튕기는 러버 호스 움직임, 필름 깜빡임, 카메라 고정, 컷 없음, 새로운 글자 없음".
- 떨어졌을 때 preset 이 반환되면
declined_preset_id를 params 에 넣어 재시도. - 끝 컷을 전부 나열해서 봄. 상품이 옆을 보거나 돌면 무늬가 망가짐 → 망가지기 전 초까지만 사용 (
SRC_MAX) 하거나, 정면 부분을 왕복시킴.
8. 배경 추출 (글자 없음)
python3 $S/scripts/bg.py $AD # → $AD/bg.mp4 (1080x1920·30fps) 와 shot_starts.json
- 컷 전환은 각 줄의 노래 시작에 맞춤 (line_starts.json). 첫 컷은 0초부터.
- 노래 없는 앞부분 인트로는 강한 장면 (악역·대홍수·도장으로 찍기 등) 을 빠르게 잘라서 채우는 방안도 있음.
9. Remotion ($AD/remotion)
public/bg.mp4(배경) ·public/song.mp3·src/data.json을 배치:python3 $S/scripts/make_data.py $AD --hook-end 29.3 --cta 192.9 --brand 44,46,47,50 --hook-top "토이 트레이닝 중인 엄마에게" --hook-main "기저귀가 떨어지지 않아" --hook-mark "진짜 이유" --brand-name "<상품명>" --keys "<노란색으로 강조할 결정적 단어,쉼표 구분>"- 올릴 것 (
src/SongAd.tsx):- 위의 훅: 종이 카드 (검은 굵은 테두리·어긋난 그림자·약간 기울임) 로 "누구를 위한 것인지 + 무슨 이야기인지" 를 처음 1초에 알게 함. 예 "토이 트레이닝 중인 엄마에게 / 기저귀가 떨어지지 않는 진짜 이유".
- 자막: 검은 둥근 모서리에 흰 굵은 글자, 펑 튀어나오며 등장. 결정적 단어만 노란색 (흑백 중 유일한 색).
- 상품명 라벨: 상품이 나오는 컷만 위에 작게.
- CTA: 마지막 "아래를 탭"부터 끝까지, 노란색 화살표 3단이 아래로 흘러내리며 튐 + "아래 버튼에서|기사 읽기".
- 내보내기 (이 Mac은 헤드리스 Chrome이 단독으로 다운됨 →
chrome-wrap.sh, 샌드박스 밖):
cd $AD/remotion && NODE_USE_ENV_PROXY=1 npx remotion render src/index.ts SongAd out/<광고ID>.mp4 --browser-executable=./chrome-wrap.sh --concurrency=6 --crf=20
- 납품은
-crf 23 -movflags +faststart로 재인코딩 (흑백 입자로 무거움. 3분 30초에 약 200MB).
10. 검품 (open 하기 전에 반드시)
- 약 1초 간격 24컷 콘택트 시트를 읽음: 자막이 노래와 어긋나지 않음 / 노래 없는 곳에 나오지 않음 / 상품 무늬가 망가지지 않음 / CTA 화살표가 화면 안에 들어가 있음 / 불필요한 글자 없음.
- 처음 3초만 따로 봄 (훅이 한눈에 알아보이는지).
- HTML 미리보기 페이지를 만들어 open. 광고명은 AdLab 발행명 (H-XXX-000a) 을 파일명에 그대로 사용.
막힌 곳 (이번 실측)
- 자막이 처음부터 어긋남: 노래 시작이 4.9초였는데 whisper-cli 가 0초로 함 →
align.py(openai-whisper 의 word_timestamps + difflib 로 글자 맞춤) 으로 해결. - 키프레임 nsfw: 속옷 차림 아이·참고 이미지 속옷 → 옷 입힘 / 참고는 그림체 문장으로 말함.
- 상품이 돌아서 망가짐: Grok 은 자주 옆을 봄. 정면 초만 사용.
- 너무 많이 만듦: 이미지 61장 + 영상 61개를 1개에 사용. 다음은 재고와 정지 화면 확대로 절반으로 줄임.
- 긴 마지막 글자가 1자만 다음 줄로 떨어짐 →
|로 줄을 지정함.
관련: reference/lyrics.md (가사 형식과 실례), /zackd-ad (3D 애니메이션형), AdLab (만들기 전에 가설 등록).