📺 제작 · 자동화 가이드

유튜브 3채널, 만드는 법을 한 장에

저장소 D:\youtube 에 있는 세 프로젝트(qna · shortube · funtube)를 어떤 순서로 만들고, 어디까지 자동이고, 어디서 사람이 개입하는지 정리한 운영 문서입니다. 시간이 지난 뒤 다시 봐도 자동화를 그대로 쓸 수 있게, 저장소의 실제 규칙 문서·스크립트를 읽고 옮겨 적었습니다.

작성 2026-10-17 · 근거: 루트/프로젝트 CLAUDE.md, qna/video/README.md·VOICE.md, qna-shorts·shortube-daily 스킬, shortube/SPEC.md·COMPLIANCE_GUIDE.md·TTS_GUIDE.md, funtube 스크립트 소스. funtube 는 문서가 따로 없어서 코드를 읽고 정리한 것이라 qna·shortube 만큼 규칙이 굳어 있지 않습니다.

💸 먼저 이것만: 돈이 드는 명령

그림 생성(artgen, facegen)과 음성 생성(voice … gen, funtube tts)은 저장소 루트 .env 의 Gemini 키로 실제 과금됩니다. 사용자가 "API로 만들어 줘"라고 한 경우에만 실행하고, 점검만 할 때는 --list / jobs / studio 같은 호출 없는 모드를 씁니다. (자세히: 1-2)

세 채널 한눈에 비교

qna

여덟 살의 질문

  • 채널 모라구 (가족 채널)
  • 형식 8분 안팎 일러스트 롱폼 + 유입용 쇼츠
  • 핵심 "아이의 질문 → 아빠가 얼버무림 → 명쾌한 설명"
  • 엔진 Remotion + Gemini 그림·음성
  • 사람 일 Gemini 그림 뽑기(또는 artgen), 시나리오 확정, 업로드
  • 길잡이 qna/CLAUDE.md
shortube

숫자로 보는 요즘

  • 채널 숫자로 보는 요즘 (@숫자로보는요즘)
  • 형식 40~60초 텍스트·차트 모션그래픽 쇼츠
  • 핵심 화제를 숫자·그래프로, 사실 카드만 쓴다
  • 엔진 Python(주제 선정) + Remotion(영상) + 코드 합성 BGM
  • 사람 일 검수, 한도 확인, 공개 결정
  • 길잡이 shortube/CLAUDE.md + COMPLIANCE_GUIDE.md
funtube

웃긴 영상툰

  • 형식 세로(1080×1920) 짧은 웃긴 영상: 단톡 · 상식 · 밸런스 · 2D 영상툰 · 3D 콩트(carpool) · 노래
  • 방향 하이브리드 — 몸·무대·소리는 코드(3D·합성), 얼굴은 AI가 그린 표정 그림, 목소리는 AI 음성
  • 핵심 대본(JSON) 하나 → 음성 → 렌더
  • 엔진 Remotion + Three.js 3D + Gemini(음성·표정 그림)
  • 사람 일 대본 쓰기, 표정 시트 고르기
  • 길잡이 없음 — 이 문서 4장

무슨 일을 하려면 어디로 가나

하고 싶은 일프로젝트시작점
새 롱폼(아이 질문) 한 편 만들기qna스킬 eight-year-olds-question → 11단계
롱폼을 쇼츠로 엮어 올리기qna스킬 qna-shorts → 쇼츠
오늘 올릴 숫자 쇼츠 주제 뽑기shortube스킬 shortube-daily → 주제 선정
화제(시황·날씨·게임 등) 쇼츠 만들기shortube브리프 → 영상 → BGM → 스틸 점검 → 하루 흐름
웃긴 단톡/상식/밸런스 게임 영상 (2D)funtubecontent/episodes.json → 제작 순서
표정·연기가 있는 3D 콩트 / 3D 노래 (하이브리드)funtube하이브리드 방식 → 표정 시트(facegen) + cam·face·act 대본
음성만 다시 / 목소리 바꾸기qna · funtubeqna 음성 · funtube 음성
유튜브에 올리기공통qna 업로드 · shortube 업로드·한도

1. 공통 규칙

1-1. 저장소 · 브랜치 · 올리지 않는 것

  • GitHub: moyuchan84/moyutube. 작업은 브랜치에서 하고 PR 로 main 에 합친다. (main 에 바로 커밋하지 않는다.)
  • GitHub Pages 는 main 의 루트를 내보낸다. 루트 index.html 은 qna/ 로 넘겨 주는 페이지다. qna 의 index.html·plan/·guide/·production/·auto/ 가 사이트다.
  • 올리지 않는 것: 키 파일 .env, 렌더 결과 out/, node_modules/, .venv/, 자동 생성 그림 qna/video/public/art/, shortube 의 data/·output/videos/.
  • Windows 환경: 셸은 PowerShell/Git Bash. shortube 파이썬은 .venv/Scripts/python.exe, 한글 깨짐 방지로 PYTHONIOENCODING=utf-8.

1-2. API 키 · 과금 규칙

Gemini 키는 D:\youtube\.env 한 곳에만 둔다. GEMINI_API_KEY(기본), GEMINI_API_KEY2(첫 키가 한도에 걸리면 번갈아 쓰는 예비). 프로젝트 폴더마다 .env 를 새로 만들거나 키를 복사하지 않는다. 코드는 환경변수 → 저장소 루트 .env → 프로젝트 .env 순서로 찾는다.

프로젝트키를 읽는 곳돈이 드는 명령돈이 안 드는 점검 모드
qnaqna/video/scripts/env.mjs 의 envValue()npm run artgen -- epNN (그림)
npm run voice -- epNN gen (음성)
artgen epNN --list
voice epNN jobs · studio
shortubeshortube/src/shortube/config.py load_config()음성 · 그림 단계 (TTS_GUIDE.md)주제 선정(pick)·브리프는 무료
funtubefuntube/scripts/tts.mjs · facegen.mjsnpm run tts · node scripts/facegen.mjs키가 없으면 tts 는 글자 수로 길이만 추정 · facegen --list · --process-only · --placeholder
왜 이렇게 엄격한가

2026-10-13 에 키가 없다고 생각하고 artgen.mjs ep03 을 "확인용"으로 돌렸다가 실제 생성 요청이 나간 적이 있습니다. 생성 명령은 사용자가 요청할 때만 실행합니다. 새 스크립트에서 키가 필요하면 위와 같은 순서로 읽게 만들고, 키 값은 코드·로그·문서·커밋에 절대 쓰지 않습니다.

1-3. Remotion 영상 · 코드 합성 음악 (세 프로젝트 공통 방식)

  • 영상은 React 코드(Remotion)로 그린다. 같은 데이터면 같은 영상이 나와서 재현·수정이 쉽다. 프로젝트마다 node_modules 가 따로라 처음에 폴더에서 npm install. (버전: qna 4.0.525 · shortube 4.0.529 · funtube ^4.0.535)
  • 음악·효과음은 코드로 합성한다 (bgm*.mjs, sfx.mjs, beat.mjs). 외부 음원·유튜브 오디오 보관함 대신 쓰는 이유: Content ID 클레임 걱정이 없고, 같은 timeline.json 을 읽어 장면 전환·숫자가 박히는 순간에 소리를 정확히 맞출 수 있다. 설명란에는 [음악] 채널에서 직접 만든 배경음악.
  • 패턴: 장면 목록(timeline.json) → 영상 컴포넌트와 BGM 스크립트가 같은 파일을 읽음. 장면 길이를 바꾸면 BGM 도 다시 만들어야 맞는다 (npm run … 이 둘을 묶어 준다).
  • Claude 는 소리를 들을 수 없다. 음악·음성은 만든 뒤 "한 번 들어 봐 달라"고 말한다. 볼륨은 RMS 로 확인 (대략 −15 ~ −20 dBFS).

1-4. Claude 가 따르는 문서 · 스킬 · 메모리

무엇어디역할
루트 규칙D:\youtube\CLAUDE.md세 프로젝트 안내 · API 키 규칙 · 과금 경고 · 저장소 규칙. shortube 작업 전에 shortube/CLAUDE.md 와 COMPLIANCE_GUIDE.md 를 먼저 읽게 한다
qna 규칙qna/CLAUDE.md폴더 규칙 + 한 편 만드는 순서 (이름을 즉석에서 만들지 않는다)
shortube 규칙shortube/CLAUDE.md > COMPLIANCE_GUIDE.md > SPEC.md우선순위가 이 순서. CLAUDE.md 는 제작·업로드 체크리스트
스킬eight-year-olds-question · qna-shorts · shortube-daily"이렇게 말하면 이 절차를 자동으로" (5장 참고)
목소리 고정qna/video/src/voice.json + VOICE.md아빠 Algenib · 아이 Leda · 엄마 Kore. 바꾸지 않는다
메모리~/.claude/projects/D--youtube/memory/시나리오는 집 밖에서 · 음색 고정 · 유료 스크립트 주의 · qna 절차 — 세션이 바뀌어도 유지

qna여덟 살의 질문

2-1. 무엇을 만드는 채널인가

가족 채널 모라구의 시리즈. 아이가 무심코 던진 질문("호텔이랑 모텔이 뭐가 달라?")에 엄마·아빠도 몰라서 얼버무리다가 명쾌하게 설명해 주는 구조. 교사–학생이 아니라 부모–자녀이고, "엄마아빠도 몰랐어요"라는 공감이 핵심 톤이다. 지금까지: 1호텔·모텔·유스호스텔 / 2아파트·빌라·오피스텔 / 3마트·시장·백화점 / 4몸·머리카락 / 5의사·약사·간호사 / 6감기·독감.

기획서(.md)→script.json · art.json→콘티(storyboard.html)→그림→음성→make → mp4 · srt→쇼츠→업로드

2-2. 폴더 지도 — 이 이름들만 쓴다

qna/ ├─ CLAUDE.md ← 작업 규칙 ├─ index.html ← 사이트 홈 (편 목록 카드) ├─ plan/ guide/ production/ auto/ ← 사이트 페이지 (기획 · 튜토리얼 · 제작 노트 · 자동 제작 매뉴얼) ├─ skill/ ← eight-year-olds-question 스킬 패키지 ├─ art/ ← 모든 편이 같이 쓰는 그림 원본 │ ├─ anchor/style-cutin.png ← 화풍 앵커 (모든 Gemini 요청에 ①로 첨부) │ ├─ common/{dad,kid,mom,cutin,bg}/ ← 아빠·아이·엄마 표정, 공통 배경 │ └─ channel/ ← 채널 프로필 · 배너 ├─ episodes/NN-영문슬러그/ ← 편 하나 = 폴더 하나 └─ video/ ← Remotion 영상 코드 (README.md · VOICE.md) ├─ src/epNN/ ← 그 편의 script.json · art.json · timing.json (사람이 고치는 곳) ├─ src/shorts/epNN.json ← 편집형 쇼츠 계획 ├─ src/voice.json · art-common.json · Root.tsx ├─ scripts/ ← npm run 으로 부르는 도구들 ├─ public/ ← 자동 생성 (처리된 그림 · 음성 · 효과음) — 손으로 고치지 않는다 ├─ voice-studio/epNN/ ← AI Studio 에서 받은 컷별 음성 원본 └─ out/epNN/ ← 완성 영상 · 자막 · 컷 이미지 · 썸네일 (git 에 안 올림)

편 폴더 episodes/NN-slug/ 에 있어도 되는 파일

파일누가내용
<slug>.md스킬 · Claude기획서 원본: 구성 · 자료조사 · 콘티표 · 그림 목록 · 대본 · BGM · 썸네일/제목 · 한 문장
index.html스킬같은 기획서를 사이트용으로 (3편 index.html 의 head/CSS 그대로)
storyboard.html도구npm run storyboard -- epNN — 컷별 배경 · 인물 위치 · 움직임 · 대사 콘티 시트. 그림이 생기면 다시 돌린다
GEMINI_PROMPTS.md도구npm run prompts -- epNN — 그림 프롬프트 문서 (직접 고치지 말고 art.json 을 고친다)
ANTIGRAVITY.md도구npm run handoff -- epNN — 그림·음성을 다른 에이전트에 넘길 때만
art/<id>.png사람그 편 그림 원본 (Gemini 결과). id 는 art.json 의 id 그대로 (bg/… full/… <인물>/… cutin/… prop/…)
jobs/도구자동 생성 (images.json · voice.json)
thumb.svg(또는 .jpg)스킬사이트 홈 카드 그림
_old/사람 · Claude바뀌기 전 구성 · 버린 그림. 지우지 말고 옮긴다 (_old/<slug>-v1-<한 단어>.md)

편 번호 NN 은 영상으로 나가는 순서. episodes/NN-… 와 video/src/epNN 번호가 항상 같아야 한다. 위 표에 없는 파일을 편 폴더에 만들지 않는다 (임시 파일·생성 스크립트는 scratchpad 에).

2-3. 한 편 만드는 11단계

  1. 기획 자동(스킬)스킬 eight-year-olds-question 에 주제를 던지면 자료조사 · 7박자 콘티 · 컷별 그림 콘티 · 나레이션 대본(당황 대사 포함) · BGM/효과음 · 썸네일 문구 · 한 문장 정리까지 한 번에. → episodes/NN-slug/<slug>.md, index.html
  2. 대본 · 그림 목록 자동(Claude)기획서 대본을 영상용으로 옮긴다 (3편이 기준 예시). → video/src/epNN/script.json, art.json
  3. 콘티 확인 사람 확인npm run storyboard -- epNN → storyboard.html 을 열어 컷 구성·길이 확인. 여기서 대본을 고친다 (그림 그린 뒤에 고치면 낭비).
  4. 그림 프롬프트 npm run prompts -- epNN → GEMINI_PROMPTS.md (공통 그림은 art/common/GEMINI_PROMPTS.md 에 한 번만).
  5. 그림 사람Gemini 에서 손으로 뽑아 episodes/NN-slug/art/<id>.png 에 저장하거나, npm run artgen -- epNN (사용자에게 물어보고).
  6. 그림 처리 자동npm run art -- epNN — 초록 배경 제거 · 여백 자르기 · 크기 맞춤 → video/public/art/epNN/. 끝나면 다시 npm run storyboard -- epNN.
  7. 음성 npm run voice -- epNN studio(손으로) 또는 … gen(API) → voice-studio/epNN/, src/epNN/timing.json.
  8. 등록 · 미리보기src/Root.tsx 의 EPISODES 에 한 줄 추가 (timing.json 이 생긴 뒤) → npm run studio.
  9. 완성 자동npm run make -- epNN → video/out/epNN/epNN.mp4 + subtitles.srt. 썸네일은 Ep NN-ThumbD 스틸.
  10. 사이트 · 저장홈 index.html 카드 갱신, 브랜치에서 커밋 → PR.
  11. 쇼츠 무료스킬 qna-shorts: 편집 계획 src/shorts/epNN.json → short -- epNN --check → short -- epNN → Chrome 으로 비공개 업로드. 롱폼 음성·그림을 재사용하므로 비용 0.

2-4. 시나리오 규칙 — 재미가 여기서 갈린다

고정 규칙
  • 집 밖에서, 제삼의 인물과. "거실에서 아이가 묻고 아빠가 화들짝 놀라는" 구조를 반복하지 않는다. 질문이 나올 만한 재미있는 장소(시장 · 약국 · 이발소 …)에서, 가족이 아닌 인물(욕쟁이 시장 할머니 · 이발사 할아버지 · 약사 선생님)을 넣는다.
  • 한창 벌어지는 장면부터 시작한다 (0번 박자 "지금 상황"). 그 뒤 "3분 전으로 돌아가 보겠습니다" 식으로 되감는다.
  • 장소가 답의 일부, 제삼의 인물이 답을 몸으로 보여 준다. 그림도 정적이지 않게 움직일 수 있는 구조로.
  • 6번 박자(아이 눈높이로 번역)에는 농담을 넣지 않는다. 가장 진지하게 가는 구간.
  • 분량은 8분 안팎 (3편 음성 약 321초에 배속 1.15). storyboard.html 과 기획서 콘티표의 시간으로 확인.
  • 대본을 고치면 기획서 .md 의 대본도 같이 고친다. 영상 대본이 원본이 된 뒤에는 script.json 기준.

7박자 구성 (컷의 beat 값)

#박자하는 일
0지금 상황이미 벌어지고 있는 장면으로 시작. 마지막 줄에 "3분 전으로…" 되감기 (sfx:"rewind", pop:"3분 전")
1그 질문아이가 던진 질문. 아빠도 몰랐다는 공감의 시작
2얼버무린 순간아빠의 당황 · 어설픈 대답 (여러 컷으로 길게 가도 된다: 6편은 c02~c02f)
3왜 다들 헷갈리나헷갈리는 이유
4갈라진 지점진짜 차이 (제삼의 인물이 몸으로 보여 줌). 컷이 가장 많은 구간
5한 문장 정리결론 한 문장
6아이 눈높이로 번역농담 금지. 아이 말로 다시 설명
7남는 이야기여운 · 다음 편 · 끝 카드 ("style":"end")

소재 주의

  • 몸 · 성 관련 소재는 몸을 그리지 않는다 (4편: 닫힌 문 · 거품 모자이크 · 단면도). 민감한 단어는 대사에만, 썸네일 · 제목 · 화면 글자(hook · stamp · end 포함)에는 쓰지 않는다. 쇼츠도 그런 대사를 아예 고르지 않는다.
  • 의료 소재(5·6편)는 정보 전달 톤을 지키고, 진단·처방 권유처럼 들리지 않게.

2-5. script.json · art.json 읽는 법

사람이 고치는 두 파일. 영상의 모든 연출이 여기에 글로 적혀 있다 (video/src/epNN/).

script.json — 컷(cut) 구조

{
  "rawVoice": true,            // 전부 AI 음성이면 true (렌더 때 음높이 변조를 끈다)
  "speed": 1.15,               // 편 전체 배속 (음성을 다시 만들지 않아도 됨)
  "title": "마트 · 시장 · 백화점, 뭐가 달라요?",
  "bgm": "cheerful",           // public/bgm/cheerful.wav 를 대사 밑에 깐다. 줄을 지우면 BGM 없음
  "cuts": [{
    "id": "c00", "beat": "0. 지금 상황",
    "scene": {
      "bg": "bg/market-alley",
      "props":  [{"art":"prop/stall-front","x":0.2,"y":0.74,"w":0.4,"front":true}],
      "actors": [{"id":"granny","art":"granny/banter","x":0.2,"idle":"sway"},
                 {"id":"kid","art":"common/kid/normal","x":0.5},
                 {"id":"dad","art":"common/dad/panic","x":0.76}]
    },
    "lines": [
      {"who":"narr","text":"…","en":"…(영어 자막)"},
      {"who":"granny","text":"…","focus":"granny","cutin":"cutin/granny-banter","fx":"zoom"},
      {"who":"narr","text":"3분 전으로 돌아가 보겠습니다.","tts":"삼 분 전으로 …","pop":"3분 전","sfx":"rewind","fx":"flash"}
    ]
  }]
}
위치항목뜻
scenebg배경 그림 (한 장짜리 full/… 도 여기). tone:"flashback" 이면 빛바랜 색 + 빨간 점선 테두리
actors[].x · h발 위치(0 왼쪽~1 오른쪽) · 키. 안 쓰면 아빠 0.64, 아이 0.43
actors[].enterleft · right 미끄러져 들어옴 / drop 위에서 / pop 튀어나옴 / rise 좌판 뒤에서 솟기
actors[].idlesway · bounce · shiver (가만있을 때 움직임)
actors[].flip · at좌우 반전(아빠는 왼쪽, 아이는 오른쪽을 보게 그려져 있음) · 몇 번째 대사부터 나올지(0부터)
props[]x y w, float(둥둥), dim(흐릿), front(배우 앞에 그림), motion(sway·pulse·spin·walk), to(화면 가로질러 이동)
linewho화자: narr · dad · kid · mom · docu · granny … (목소리는 voice.json 이 정함)
pose이 대사부터 그 인물의 그림(표정)을 바꿈 → 젤리처럼 눌렸다 튐
focus · cutin그 인물로 카메라 당기기 · 큰 얼굴 컷인이 별 모양 폭발과 함께 튀어나옴
fxshake 흔들림 · lines 집중선 · flash 번쩍 · zoom
pop큰 글자가 튀어나옴 (예 "???")
sfx · sfxAfterboing · page · stinger · dudung · rewind (대사 뒤 "뚜둥"은 화면 흔들림·큰 글자 포함)
move · act그 자리로 걸어가기 · jump · shiver · nod
tts · en임시 음성이 읽을 문장(숫자·영어·괄호 대사에만) · 영어 번역 (있으면 srt … en 이 영어 자막 생성)

특별 컷: "style":"triptych" + "triptych":[{art,name,label}] (세 칸 정리), "style":"end" + "end":{series,title,next} (끝 화면). 말하는 인물은 자동으로 까딱거리고, 가만 있어도 숨 쉬며 발밑 그림자가 생기고, 컷 내내 카메라가 천천히 밀고 들어간다 (src/Scene.tsx).

art.json — 그림 목록

  • cast: 가족이 아닌 사람의 생김새를 영어로 아주 구체적으로 적는다 (헤어 · 옷 · 표정 성격까지). 같은 사람이 매번 달라 보이지 않게 하는 핵심.
  • common: 쓸 공통 그림 id (아빠·아이 표정 등). items[]: 그 편 전용 그림 — id · kind · title · cuts(쓰는 컷) + 묘사.
  • 그림 종류 kind: char 전신 · cutin 가슴 위 · prop 소품 (이 셋은 초록 배경으로 뽑아 투명 처리) · bg 사람 없는 배경 · full 완성된 한 장짜리 장면(16:9). 확장: stage(주인공 자리를 비운 무대) · layer(배우를 가리는 앞쪽 물건) · talk(말하는 그림).
  • 말하는 그림 <그림>-b: 같은 자세에 입만 다른 두 번째 그림. 있으면 말하는 동안 번갈아 나와 입이 움직인다 (없어도 됨).
  • who + ref(기준 그림, 기준 그림 자신은 null)로 같은 인물을 이어 간다. inset: 0.025 는 그림 테두리 자르기, sky 는 장면 하늘색 지정.

2-6. 그림 뽑기 — 사람이 하는 유일한 반복 작업

  1. 문서(GEMINI_PROMPTS.md)의 블록을 위에서부터 하나씩 처리한다. 프롬프트를 통째로 복사해 Gemini 에 붙여넣는다.
  2. 블록에 적힌 첨부 이미지를 같이 올린다. ① 화풍 앵커(art/anchor/style-cutin.png) ② 같은 사람을 유지하기 위한 기준 그림. 순서를 지킨다.
  3. 결과를 블록의 저장 경로·이름 그대로 저장한다. 배경 제거·크기 조절은 하지 않는다 (npm run art 가 한다).
  4. 한 사람은 한 대화창에서 끝내고, 다음 사람·배경은 새 대화창에서. 대화가 길어지면 화풍이 흐려진다.
종류저장 경로 예뽑는 모양
아빠·아이 전신art/common/dad/panic.png초록 배경, 2:3 세로
리액션 컷인art/common/cutin/dad-panic.png초록 배경, 1:1, 가슴 위
공통 배경art/common/bg/living-room.png사람 없음, 16:9
편별 배경 / 인물 / 소품 / 장면episodes/NN-…/art/bg|char|prop|full/…png배경 사람 없음 16:9 · 인물 초록 2:3 · 소품 초록 1:1 · 장면 완성 16:9

자주 생기는 문제와 다시 뽑을 때 이어 보내는 문장

문제보낼 문장 (Gemini)
배경이 단색 초록이 아님Redo it with a perfectly flat pure green #00FF00 background, no shadow, no floor.
몸이 잘림Zoom out so the whole body fits with empty margin on every side.
다른 사람처럼 나옴Keep the exact same face, hair and clothes as attached image 2. Only change the pose.
글자가 들어감Remove all text and letters; leave signs blank. (글자는 전부 코드가 얹는다)
장면 하늘이 초록This is a finished scene, not a cutout: paint a real sky, no green screen anywhere.
배경에 사람이 있음Remove every person. Background only.
실제로 생긴 실수

① 컷인을 다른 폴더에 저장 — art/common/cutin/dad-panic.png 가 맞고 art/common/dad/dad-panic.png 가 아니다 (폴더가 다르면 "없는 그림" 처리). ② 장면 그림의 하늘이 초록 — 초록 배경은 인물·컷인·소품에만 쓴다. ③ 그림 둘레 흰 테두리 → inset. ④ 그림에 일본어 의성어 등 글자가 들어감 → 다시 뽑기.

npm run art -- epNN 경고: 배경을 못 지웠다(→ 위 문장으로 다시 뽑기) · 그림이 가장자리에 닿음(몸이 잘렸을 수 있음, 컷인은 가슴에서 잘리는 게 정상) · 원본이 작아서 흐림(더 큰 해상도 또는 Upscayl 2배) · 16:9 가 아님(Gemini 에서 비율을 16:9 로). 없는 그림은 영상에서 점선 임시 그림으로 나오므로 한 장씩 넣어 가며 확인해도 된다.

2-7. 음성 — 모든 편이 같은 음색

역할목소리 (AI Studio / Gemini TTS)비고
아빠 · 나레이션 · 다큐 톤 (Speaker 1)Algenib (거친 저음) + 말투 지시 "balding Korean dad in his mid-forties: thick, deep, slightly husky ajusshi voice…"1편(2026-10-12)에서 확정. 바꾸지 않는다
아이 (Speaker 2)Leda (어린 느낌, 높은 톤)"여덟 살 아이, 밝고 높은 목소리" 문장이 voice.json 의 style 로 붙음
엄마 (Guest, mom)Kore2026-10-16 추가, 아직 들어 보고 확정 전
시장 할머니 (3편)Gacrux손님(Guest) 목소리
이발사 할아버지 (4편)Charon아빠와 너무 비슷하면 Sadaltager · Rasalgethi 시도
약사 선생님 (5편)Sulafat엄마 Kore 와 겹치지 않는지 확인
한 컷에 목소리는 둘까지

AI Studio 의 화자 자리가 둘(Speaker 1·2)이라, 아빠+아이+엄마가 한 컷에서 모두 말하면 도구가 오류를 낸다 → 컷을 나눈다. 손님이 나오면 그 컷에서 비는 자리를 쓴다 (아빠+할머니 컷은 Speaker 2=Gacrux, 아이+할머니 컷은 Speaker 1=Gacrux). npm run voice -- epNN jobs 가 이 검사를 한다.

음성을 만드는 세 가지 방법

① 한 번에 (API)

npm run voice -- ep02 gen
npm run voice -- ep02 gen --all

대사가 바뀐 컷만 생성 → 줄마다 자르기 → 타이밍 갱신. 컷 하나를 한 번에 받아 voice-studio/epNN/<컷id>.wav 에 두고 줄 사이 쉼으로 자른다(못 찾으면 줄마다 따로). 모델은 그 키로 쓸 수 있는 Flash TTS 중 최신, 고정은 voice.json 의 "model".

② 손으로 (AI Studio 화면)

사람
npm run voice -- ep02 studio
# 컷마다 복사 버튼 → AI Studio 에서
# Speaker1=Algenib, Speaker2=Leda 로 생성
# → voice-studio/ep02/<컷id>.wav 저장
npm run voice -- ep02 split

out/ep02/voice-studio.html 에서 컷마다 복사. 대사를 고친 뒤 studio 를 다시 돌리면 다시 만들 컷만 ⚠ 로 표시된다.

③ 다른 도구에 맡김

무료
npm run handoff -- ep03

episodes/03-…/ANTIGRAVITY.md + jobs/images.json + jobs/voice.json. 지시서를 읽히면 컷별 목소리·대사·저장 경로가 들어 있다. 그림·음성 모두 가능.

  • 임시 음성: npm run tts -- epNN (Windows 내장 'Heami'). 흐름·길이 확인용. 임시 음성은 한 목소리를 변조해 쓰므로 실제 목소리를 넣을 땐 "rawVoice": true 필수.
  • AI Studio WAV 는 안에 헤더가 한 번 더 있고 끝에 C2PA "AI 생성" 표시가 붙을 수 있다. voice.mjs 가 읽을 때 걸러 내니 손대지 않는다 (다른 편집기에 직접 넣을 때만 주의, 안 그러면 "칙" 잡음).
  • 직접 녹음으로 바꿔 끼우기: voice -- epNN sheet (녹음 대본 voice-script.md) → public/epNN/voice/c01-0.wav 처럼 같은 이름으로 덮기 → voice -- epNN 으로 타이밍 재계산. 녹음한 뒤에는 --tts 를 붙이지 않는다 (임시 음성이 덮어씀).

2-8. 렌더 · 미리보기 · 배속 · 자막

cd D:\youtube\qna\video
npm run studio                  # 브라우저 미리보기 (왼쪽에서 Ep03 선택, 파일을 고치면 바로 반영)
npm run make -- ep03            # 프롬프트 문서 → 그림 처리 → (음성 없으면 임시 음성) → sfx/bgm → 배속 → 렌더 → 자막
npm run make -- ep03 --voice    # 대사가 바뀐 컷을 Gemini 음성으로 다시 (GEMINI_API_KEY, 유료)
npm run make -- ep03 --tts      # 임시 음성(Windows TTS)부터 다시
npm run stills -- ep03          # 컷별 1920x1080 PNG → out/ep03/cuts
npx remotion still src/index.ts Ep03-ThumbD out/ep03/thumbnail.png    # 얼굴 썸네일
npm run srt -- ep03 [en]        # 유튜브용 자막 (en 은 영어)
  • 연출만 고쳤으면 음성을 다시 만들 필요가 없다. npm run studio 로 확인하고 make 만 다시. make 는 script.json 이 음성보다 새것이면 경고를 띄운다 (대사를 고쳤다면 --voice/--tts).
  • 배속: script.json 의 "speed": 1.2 — 목소리·쉼·컷 전환·자막 타이밍 전부 (음높이 유지). 빠른 목소리는 scripts/speed.mjs 가 public/<편>/voice-x<배속>/ 에 미리 만든다. 줄을 지우면 1배속.
  • BGM: "bgm":"cheerful" → node scripts/bgm.mjs 가 코드로 합성 (말하는 동안은 목소리보다 약 22dB 작게). 다른 곡은 public/bgm/<이름>.wav + 이름 변경. 효과음은 node scripts/sfx.mjs.
  • 렌더 시간: 4분짜리에 6~7분쯤 (8분 영상은 더 걸린다).
  • 완성본: out/epNN/epNN.mp4 (1920×1080) · subtitles.srt (+ subtitles.en.srt).

2-9. 쇼츠 — 롱폼을 유입용으로 다시 엮기 (스킬 qna-shorts)

음성·그림은 롱폼에 이미 있는 것만 쓴다 → Gemini 호출 없음, 비용 0. 새 대사가 필요하면 멈추고 사용자에게 묻는다.

대사 목록 보기→구성 고르기→src/shorts/epNN.json→Root.tsx 의 EDITED_SHORTS 등록→short -- epNN --check→콘택트 시트 눈으로 확인→short -- epNN→비공개 업로드

구성 — 궁금증을 남기는 순서

  1. 0초에 질문: 아이의 질문 대사로 바로 시작 (인사·나레이션 도입 금지). 위에 hook 두 줄이 쾅 — "아빠도 대답 못 한 / 진짜 차이"처럼 답이 있다는 약속.
  2. 틀린 답 → 땡: 아빠가 얼버무린 대사에 stamp.
  3. 반전 한 줄: "그런데 ~부터가 완전히 다릅니다"에 pop:"?!".
  4. 챕터: 항목마다 chapter (위 목록에 불이 켜지고 첫째/둘째/셋째 도장). 한 항목에 대사 1~3개.
  5. 마지막 항목은 답을 주지 않는다: 시작 대사 하나만 보이고 끝 카드로 질문을 남겨 본편으로. 목록에서도 마지막 항목은 차례가 올 때까지 ???.
  6. 길이 40~55초 (60초 넘으면 경고), 대사 9~12개. 6번 박자와 롱폼의 결론 문장은 쓰지 않는다 — 본편에서 볼 이유를 남긴다.
{ "id":"Ep01-Short", "episode":"ep01", "series":"여덟 살 아들의 질문 1편",
  "topics":["호텔","모텔","유스호스텔"],
  "hook":["아빠도 대답 못 한","진짜 차이"],
  "segments":[
    { "line":"c01-1", "hl":["호텔이랑"], "cutin":false },           // 대사 id = <컷>-<번호>
    { "line":"c03-0", "stamp":{"text":"땡!","sub":"가격 차이가 아니었다"} },
    { "line":"c04-1", "transition":"whip", "pop":"?!" },
    { "line":"c05-0", "chapter":0, "transition":"whip" },
    { "line":"c07-0", "chapter":2, "transition":"glitch", "hold":4 }
  ],
  "end":{ "title":["독일의 한 선생님은","왜 이걸 만들었을까?"], "sub":"본편에서 확인하세요" },
  "upload":{ "longTitle":"…", "title":"… #shorts", "description":"…", "tags":[], "playlist":"여덟 살 아들의 호기심" } }

조각 옵션: transition whip(가로 모션블러, 컷이 바뀔 때) · glitch(마지막 ??? 항목) / zoom · cam(기본은 말하는 배우를 따라가는 카메라 — 배우 1.6배, 나레이션 1.35, 배경만 1.15) / hl(노랗게 칠할 단어, 한 대사 1~3개) / stamp · pop · shake · cutin · hold. 자동: 훅 플래시 · 단어 단위 자막 · 위 목록+진행 막대 · 필름 입자 · 비네트 · BGM · 효과음 · 끝 카드.

cd D:\youtube\qna\video
# 대사 목록 (id · 화자 · 길이 · 대사)
node -e "const s=require('./src/ep03/script.json'),t=require('./src/ep03/timing.json');for(const c of s.cuts)c.lines.forEach((l,i)=>console.log(c.id+'-'+i,l.who,(t[c.id+'-'+i]||0).toFixed(1)+'s',l.text))"
npm run short -- ep03 --check   # 조각마다 스틸 → out/shorts/ep03-check.png (직접 열어 본다)
npm run short -- ep03           # mp4 + upload.md + 10MB 이하 업로드용 사본(.upload.mp4)

확인할 것: 글자가 오른쪽 140px · 아래 330px(쇼츠 UI 자리)에 걸리지 않는지 · 자막이 얼굴을 가리지 않는지 · 같은 그림이 3번 넘게 연달아 나오지 않는지. 고칠 게 있으면 json 만 고치고 다시 --check.

2-10. YouTube Studio 업로드 (Claude in Chrome)

원칙

쇼츠는 항상 비공개로 저장하고 공개 전환은 사람이 한다. 업로드 버튼 전에 "이 파일을 이 제목으로 비공개 업로드할까요?" 확인을 받는다. 채널은 모라구(UCCIm8f5JE4b4PmSrPIJd7zQ) — 다른 채널(숫자로 보는 요즘)이 열려 있으면 youtube.com/channel_switcher 에서 바꾼다.

  1. anthropic-skills:chrome-browser 스킬을 읽고 도구를 한 번에 불러온다 → tabs_context_mcp → 새 탭 → studio.youtube.com. 로그인 화면이면 멈추고 사용자에게 부탁 (비밀번호를 대신 넣지 않는다).
  2. 본편 URL 찾기 (관련 동영상용). 업로드 창은 …/videos/upload?d=ud 로 바로 열린다.
  3. 파일은 find 로 input type=file 을 찾아 file_upload — 10MB 한도라 epNN-short.upload.mp4 사본을 쓴다. 파일 선택 버튼은 클릭하지 않는다(보이지 않는 창이 열림).
  4. 제목 칸은 파일명이 들어 있으니 클릭 → ctrl+a → 입력. 설명 · 재생목록 · 아동용("아니요, 아동용이 아닙니다" — 묻지 않고 선택) · "자세히 보기"로 태그(쉼표로 끝맺기) · AI 사용: "예"(그림·목소리가 AI 생성).
  5. "동영상 요소"의 관련 동영상에 본편 연결. 전화 인증 창이 뜨면 취소 (채널 인증은 이미 완료 2026-10-16).
  6. 공개 상태 비공개를 확대 화면으로 확인 → 저장. 영상 링크를 out/shorts/epNN-short.upload.md 끝 ## 업로드 기록 에 적는다.
  • 롱폼(수백 MB)은 확장으로 못 올린다(10MB). 업로드 창만 열고 사람이 파일을 끌어다 놓은 뒤 세부정보·썸네일·자막(파일 업로드 → '타이밍 포함')·영어 제목/설명(영어(미국), 제목 100자 넘으면 저장 안 됨)을 채운다.
  • 같은 동작이 2~3번 실패하면 반복하지 말고 사용자에게 묻는다. 알림·확인창(삭제 등)을 띄우는 버튼은 누르지 않는다.
  • 업로드 창 클릭은 ref 보다 스크린샷 좌표로, 누른 뒤 zoom 으로 확인 (창이 다시 그려지면 ref 클릭이 안 먹을 때가 있음).
  • Chrome 이 다른 Google 계정(AI Studio 용)이면 "권한이 없습니다" → 사람에게 계정 전환 부탁.

2-11. qna 자동화 팁

🧭 순서를 지켜 낭비를 막기

  • 콘티(3단계)에서 대본을 확정하고 그림을 그린다.
  • 그림 한 장 넣을 때마다 art → storyboard 를 돌려 빠진 것만 본다 (임시 점선 그림으로 대체됨).
  • 점검은 항상 호출 없는 모드(--list, jobs, studio).

🔁 고칠 때 최소 단위로

  • 연출만 → studio 확인 → make.
  • 대사만 → voice … gen 이 바뀐 컷만 다시 생성 (해시 비교).
  • 배속은 숫자 하나. 음성 재생성 불필요.
  • 버린 구성은 _old/ 로.

🧩 재사용

  • 공통 그림(아빠·아이·엄마 표정, 거실)은 한 번만 뽑아 모든 편이 공유.
  • 같은 인물은 cast 설명 + ref 로 일관성.
  • 쇼츠는 롱폼 음성·그림 재사용 → 비용 0.
  • 다른 에이전트에게 맡길 땐 handoff.

shortube숫자로 보는 요즘

3-1. 무엇을 만드는 채널인가

채널 숫자로 보는 요즘(UCatFTcoyLuCtsiK8JTPfQkA) — 요즘 화제를 숫자·그래프로 짚는 40~60초 쇼츠. 화면은 텍스트 · 직접 그린 차트 · 도형 · 입자뿐(인물 사진·로고·캡처·음원 없음), 배경음악은 코드 합성. 재생목록: 숫자로 보는 시장 · 순위·차트 · 날씨·생활 · 건강.

문서 우선순위

COMPLIANCE_GUIDE.md > shortube/CLAUDE.md > SPEC.md. 충돌하면 앞의 것이 이긴다. COMPLIANCE_GUIDE 의 "금지"는 기능으로 만들지 않고, "조건부"는 안전장치와 함께만 만든다.

3-2. 한 편 만드는 6단계 (shortube/CLAUDE.md 0장)

  1. 브리프 먼저 Claudeoutput/briefs/YYYY-MM-DD_slotN.md — 출처 표 · 사실 카드(YAML) · 스토리라인 · 연출 카드 · 메타데이터 · 검수 체크리스트. 영상에 들어가는 숫자는 사실 카드에 있는 것만.
  2. 영상video/src/<주제>/ 에 컴포지션 + timeline.json(장면·타격 시점) + Root.tsx 등록.
  3. 배경음악video/scripts/bgm-*.mjs 로 코드 합성 (같은 timeline.json 을 읽어 장면 전환과 맞춘다).
  4. 점검 눈으로핵심 프레임 스틸 npx remotion still <Id> out.png --frame=N --scale=0.4 를 직접 보고 겹침·잘림·줄바꿈을 고친 뒤 렌더.
  5. 업로드3-9장. 기본은 비공개. 공개는 사용자가 명시적으로 요청할 때만.
  6. 보고무엇을 만들었고 무엇을 확인 못 했는지(음악은 귀로 못 들음, 미확인 사실 카드)를 그대로 알린다.

3-3. 주제 자동 선정 (Python + 스킬 shortube-daily)

"오늘 쇼츠 만들어줘" / /shortube-daily 2 → 사용자에게 주제를 묻지 않고 끝까지 진행한다. 후보가 0개처럼 규칙상 불가능할 때만 멈춘다. 업로드는 하지 않는다.

cd D:\youtube\shortube
export PYTHONIOENCODING=utf-8
.venv/Scripts/python.exe -m shortube entities       # 최근 14일 OTT 작품 (캐시되므로 재실행 무해)
.venv/Scripts/python.exe -m shortube autocomplete   # 자동완성 후보 + 리스크/조언/노이즈 필터
.venv/Scripts/python.exe -m shortube pick --count 2 --json   # 로테이션이 슬롯별 category · entity · shortlist 선정
# …브리프 작성·제작 후 이력 기록 (다음 날 로테이션·쿨다운에 반영)
.venv/Scripts/python.exe -m shortube log-topic "<대표 키워드>" --category chart --brief output/briefs/<파일>.md
로테이션 규칙 (analysis/planner.py)값
카테고리 가중치상식(trivia) 0.5 / 신작 OTT 정보(explain) 0.3 / 차트(chart) 0.2. 약 30편 성과가 쌓이면 조회수 중앙값으로 조정 (최소 0.1은 탐색용)
제한같은 카테고리 최대 2연속 · 같은 날 두 슬롯은 다른 카테고리 · 차트 주 2회 상한 · 키워드 60일/작품 3일 쿨다운 · 같은 날 재실행은 같은 결과(날짜 시드)
shortlist 걸러내기 (Claude)위에서부터 보고 → 실존 인물 이름 · 채널명/영상 제목 · 너무 포괄적("소름돋는 사실들") · 지난 시점 수치(차트는 최신으로) · 의료·투자·법률 조언형 · 공인 출처 2개 못 찾음 → 건너뛰고 첫 통과를 고른다. 카테고리는 바꾸지 않는다
사용자가 주제를 지정한 경우

이번 시황 쇼츠처럼 사용자가 주제를 말하면 pick 로테이션을 거치지 않고 지정 주제로 가며, 브리프 "선정 근거"에 그렇게 적는다. 이때도 요청 전제부터 사실 확인(3-5).

3-4. 규칙 — 쇼츠 작업 전에 반드시 (COMPLIANCE_GUIDE 요약)

핵심 5줄

  1. 사실은 가져오고 표현은 직접 만든다. 기사 문장·구성·사진은 보호된다.
  2. 사람(개인) 콘텐츠는 만들지 않는다. 한국은 사실을 말해도 명예훼손이 성립할 수 있다(형법 307조 1항).
  3. 이미지·영상·음원은 외부에서 가져오지 않는다. 텍스트 · 직접 그린 차트 · 라이선스 명확한 아이콘/지도만.
  4. 영상마다 "내가 더한 가치"(직접 계산 · 비교 · 시계열 해석 · 오해 바로잡기). 같은 틀에 단어만 바꾼 대량 생산은 수익화(YPP) "진정성 없는 콘텐츠"로 빠진다.
  5. 자동화는 초안까지, 공개는 사람이.

주제별 반드시 / 금지

주제반드시금지
주식·시황종가 기준 명시 · 방향 의견은 증권사명 인용으로 반등·지지·신중을 같은 비중 · "투자 권유 아님" 고정 · 한국 차트 색(상승 빨강·하락 파랑)"사라/팔아라/바닥이다" · 목표가 · 유료 리딩·종목 추천 뉘앙스
의료·의약품임상 논문·식약처·심평원 등 공인 출처 · 효과와 같은 비중으로 부작용·요요·규제 · "전문의약품 · 의사 처방 필요 · 의학적 조언 아님" 상시 표기복용·시술 권유 · 구매처·최저가 · 제품 사진·로고 · "맞으면 빠진다" 단정
연예·아이돌그룹 단위 공개 사실 · 성인 공인의 공개 경력 숫자미성년 멤버 개인 정보 · 외모 비교 · 논란·사생활 · 인물 사진·무대 캡처·음원
OTT·차트상위 3~10개만 + 시계열 해석 같은 자체 분석 + 출처 텍스트순위표 전체 재현 · 포스터·스틸컷·로고
실존 인물 발언원문 그대로 인용 + 출처 · 지목하지 않은 대상을 지목한 것처럼 쓰지 않기인물 사진 · 발언 왜곡·과장

출처 등급

등급예쓰는 법
A 자유 이용공공누리 1유형, 정부 보도자료, 공공데이터, 퍼블릭 도메인사실·수치 추출 자유, 출처 표기
B 표기 조건CC BY / CC BY-SA(위키백과)사실만. 문장 인용 시 라이선스 의무 → 문장은 쓰지 않는다
C 사실만 참고언론 기사, 공식 차트 사이트, 기업 홈페이지사실·수치만. 문장·구성·이미지 금지. 출처는 텍스트로
D 사용 금지나무위키(CC BY-NC-SA), 유료 기사, 커뮤니티, 타 유튜버 대본, 출처 불명 블로그열지도 않는다 (config/sources.yaml 의 blocked_domains)

금지 항목 (만들지 않음)

연예인 근황·열애·논란 콘텐츠 · 인물 사진·포스터·스틸컷·앨범아트·캡처 · 기업 로고 · 기사/블로그 문장 복사·살짝 바꿔쓰기 · 차트 전체(TOP100) 매주 그대로 옮기기 · 유명인 목소리 복제 TTS · 상업 음원/타 채널 BGM · 드라마 결말 상세 재서술 · 조회수/구독/댓글 자동화 봇·다계정 · 내용과 무관한 자극 제목/썸네일 · 태그/설명란 키워드 나열.

3-5. 브리프 · 사실 카드 — 영상의 뼈대

요청 전제부터 의심한다

사용자 요청의 전제도 확인한다. 실제 사례: "A20 LITE · SM 아이돌" → 실제는 A2O LITE, SM 아님 / "머스크의 고평가 발언" → 실제는 메모리 가격 비판, 회사 지목 없음 / "주가 반등" 보도 → 종가 기준으로는 하락 / (이번) "채권 발작"은 금요일이 아니라 10월 1일, "경기주 방어"는 목요일 이야기. 다르면 영상에 바로잡아 넣고 사용자에게 알린다 — 오히려 그게 영상의 훅·부가가치가 된다.

브리프 템플릿 (output/briefs/YYYY-MM-DD_slotN.md)

  1. 머리: 날짜 · 슬롯 · 대표 키워드 · 데이터 기준일 · 선정 근거 · ⚠ 요청 전제 바로잡기
  2. 출처 표: # · 기관/사이트 · 문서 · URL · 등급 · 확인일 (1차 자료를 못 열었으면 "미확인 — 제작 전 필수"로 표시)
  3. 사실 카드(YAML): id · claim(내 말 1문장) · value · unit · as_of · sources[#] · verified(true/partial/false) · sensitivity
  4. 직접 계산(부가가치): 계산식을 적는다 (예: 3.83 ÷ 0.34 = 11.3배)
  5. 대본 표: 시간 · 내레이션/자막 · 화면 — 사실 카드만 보고 쓴다
  6. 연출 카드 7항목 · 메타데이터(제목 후보 3 · 설명란 · 태그 ≤10) · 검수 체크리스트
- id: M1
  claim: "2026-10-16(금) 미국 종가: 나스닥 -2.15%, S&P500 -1.09%, 다우 -0.34%"
  as_of: 2026-10-16
  sources: [1, 3]
  verified: true      # 목요일 종가와 등락률로 역산해 일치 확인
- id: M3
  claim: "한 주 성과: 나스닥 -4.48% …"
  sources: [2]
  verified: partial   # 1건 → 화면에 '보도 기준'
  • 출처 2개 이상 교차 확인. 하나뿐이면 verified: partial + 화면에 "보도 기준". 출처끼리 수치가 다르면 대본에서 빼거나 "기관마다 차이"로.
  • 시세는 Yahoo 일봉이 공식 종가와 맞음(뉴스 종가와 대조 필수). 네이버 차트는 시간외 포함이라 종가가 다를 수 있음. 0거래량·평평한 봉 같은 깨진 봉을 검사하고, 보정했으면 브리프에 기록.
  • 기간이 다른 값 비교(1년 vs 반기) 금지 — 기간 명시. 다른 임상시험끼리의 수치는 "다른 시험" 표기. 숫자에는 항상 기준일과 단위.
  • 문장은 사실 카드만 보고 새로 쓴다. 출처 문장과 연속 20자 이상 겹치지 않게(렌더 전 재확인). 정식 기준: 5-gram 겹침 15% 초과 시 실패.

3-6. 영상 코드 구조 (shortube/video)

video/ ├─ src/ │ ├─ Root.tsx ← 모든 컴포지션 등록 (RankingShort · MarketShort · KpopNumbers · DietNumbers · HeatShort · GtaShort · SuneungShort · UsShort …) │ ├─ theme.ts ← 폰트(Noto Sans KR) · 색 · FPS=30 · 1080×1920 │ ├─ components/Caption.tsx ← 하단 자막 (bottom 250, 줄 단위 · 강조 단어 노랑 형광펜) │ ├─ components/Kinetic.tsx · RankChart.tsx · Background.tsx │ ├─ market/effects.tsx ← Counter · Stamp · 흔들림 · clamp (여러 영상이 가져다 씀) │ └─ <주제>/ ← 한 영상 = 폴더 하나 │ ├─ timeline.json ← 장면(id·from·dur) · hits(숫자가 박히는 프레임) · risers(전환 직전 스웰) · bpm │ ├─ <Name>Short.tsx ← Sequence 로 장면을 이어 붙임 + BGM <Audio> │ ├─ scenes.tsx · fx.tsx ← 장면 컴포넌트 · 그 영상 전용 효과 │ └─ (데이터 json) ← kospi.json 등 ├─ scripts/bgm-*.mjs ← 코드 합성 BGM → public/bgm/*.wav ├─ scripts/market-data.mjs ← ../data/prices/*.csv → 영상용 JSON (이평 · RSI · 볼린저) └─ package.json ← "heat" · "gta" · "suneung" · "usmarket" · "market" 등: BGM 만들기 + 렌더를 한 줄로

새 영상을 추가하는 패턴 (예: UsShort)

  1. src/<주제>/timeline.json — 장면 길이(프레임, 30fps) · hits · risers. 합계가 total.
  2. fx.tsx(배경·전환·상단 띠) + scenes.tsx(장면별 컴포넌트 + Caption 줄) + <Name>Short.tsx(Sequence 조립).
  3. Root.tsx 에 <Composition id=… durationInFrames={TOTAL} …/> 추가, package.json 에 "<주제>": "node scripts/bgm-<주제>.mjs && remotion render …" 한 줄.
  4. npx tsc --noEmit 으로 타입 확인 → 스틸 점검 → npm run <주제>.

화면 규칙

  • 1080×1920. 자막은 하단 박스(bottom 250) — 본문 요소가 y≈1450 아래로 내려오면 겹친다. 줄바꿈이 어색하면 whiteSpace:"pre-line" 이나 wordBreak:"keep-all", 아니면 문장 수정.
  • 출처·기준일·고지는 마지막 장면 또는 상단 고정 띠(TopBand).
  • 브랜드(남색 + 노랑 선 + 분홍 점, Brand.tsx)는 첫 1초나 마지막 띠로만. 본문 배경까지 매번 남색으로 고정하지 않는다.
  • 쓸 수 있는 재료: 코드 생성(SVG 필터·입자·그라디언트·도형 패턴·카메라 이동·화면 나누기) / 라이선스 분명한 외부 자료(Natural Earth 지도 · Material Symbols · Tabler Icons · Noto Emoji · OFL 폰트 — 받으면 video/public/<종류>/LICENSE.md 에 출처·라이선스·날짜) / 사진·영상 클립은 쓰지 않는다.

3-7. 연출 카드 — 찍어낸 영상처럼 보이지 않게

브리프마다 7항목을 채운다: 팔레트 · 주 레이아웃 · 장면 전환 · 카메라 · 상징 오브젝트 · 타이포 · 템포. 규칙: 직전 2편과 7항목 중 4개 이상 달라야 하고, 같은 날 올리는 2편은 팔레트·주 레이아웃이 반드시 달라야 한다. 주제가 연출을 정한다 (더위 → 아지랑이·색 온도 / 시황 → 흔들림·긴박한 컷 / 수능 → 칠판·OMR / GTA → 노을 네온 고속도로 / 간밤 미국장 → 수심 눈금으로 낙하).

영상팔레트주 레이아웃전환템포
폭염 (HeatShort)주황→빨강→파랑으로 식음화면 반 나누기 + 지도아지랑이 녹임멈춤→식음
GTA (GtaShort)노을 네온가로 고속도로 연표줌 스루신스 가속
수능 (SuneungShort)칠판 초록 + 분필OMR 칸 + 모눈종이 꺾은선시험지 넘기기시계 초침
간밤 미국장 (UsShort)심해 청록→검정, 얼음 파랑/빨강수심 눈금에서 아래로 늘어나는 막대위→아래 수직 와이프물방울 똑똑, 도장에서 멈춤

3-8. 배경음악 — 코드 합성

  • 외부 음원·오디오 보관함 대신 코드 합성이 기본. 설명란 [음악] 채널에서 직접 만든 배경음악.
  • 기존 스크립트(분위기별): bgm.mjs(밝고 잔잔) · bgm-tension.mjs(긴박·시황) · bgm-dance.mjs(밝은 댄스·K팝) · bgm-info.mjs(차분한 정보·의료, 리스크 구간 단조) · bgm-heat.mjs(더위→식음) · bgm-gta.mjs · bgm-suneung.mjs · bgm-usmarket.mjs(저음 드론 + 물방울). 새 주제는 비슷한 것을 복사해 진행·템포·화음만 바꾼다.
  • 구조: 마디 단위로 패드 코드 → 킥/물방울 같은 리듬 → timeline.json 의 risers(전환 직전 스웰) · hits(숫자 박히는 순간 킥/벨) → 끝 2초 페이드 → 정규화(tanh) → 16bit WAV. 마지막에 RMS dBFS 를 출력해 볼륨 점검 (대략 −15~−20, 영상 쪽 <Audio volume=0.75>).
  • 분위기 전환 아이디어: 특정 프레임(calmFrom·coolFrom) 이후 단조→장조, 킥 제거 — 이야기의 "식는 순간"을 소리로.

3-9. 시황 · 차트 주제를 만들 때 (가장 위험한 영역)

고정 문구와 금지

"투자 권유 아님 · 투자 판단과 책임은 본인" 고정. "사라/팔아라/바닥이다", 목표가, 유료 리딩 뉘앙스 금지. 한국 증시 월요일 전망 같은 예측도 쓰지 않는다. 방향 의견은 증권사·운용사 이름 + 보도 요지로 신중론/반등론을 같은 비중으로 (원문 직접 인용이 아니면 따옴표를 쓰지 않고 "보도 요지"로 표기).

  • 종가 기준·기준일을 화면에 상시 표기(TopBand). 미국장은 "미국 현지 종가".
  • 한국 차트 색: 상승 빨강 · 하락 파랑. 사용자가 "피로 물든"처럼 미국식 색으로 말해도 영상은 한국식 + 첫 장면에서 한 줄로 짚기.
  • 수치 검증 요령: 지수 종가를 (전날 종가 × 등락률)로 역산해 보도와 맞추면 1건 보도도 신뢰도가 올라간다. 일봉 기술 분석은 stockstealer 규칙(200일선 국면 · 이평 정배열 · RSI14 · 볼린저 · 지지·저항 피벗)을 같은 기준으로 세 차트에 적용 → "과매도 ✓ / 추세 ✗" 같은 판정 요약이 부가가치.
  • 못 찾은 값(예: 금요일 마감 금리)은 빈칸으로 두고 "확인하지 못해 싣지 않았습니다"라고 화면에 적는 편이 틀린 숫자보다 낫다.
  • 의료 주제: 효과와 같은 비중으로 부작용·중단 후 요요·규제, "전문의약품 · 의사 처방 필요 · 의학적 조언 아님" 상시 표기. 연예: 그룹 단위 공개 사실만, 미성년 개인 정보 금지.

3-10. 업로드 · 한도 (Claude in Chrome)

기준제한근거
하루 (최근 24시간)최대 2편 — 비공개·일부공개도 센다채널 정책 (COMPLIANCE 5장 "하루 1~2개")
간격같은 날 2편이면 최소 3시간몰아 올리지 않기
일주일 (최근 7일)최대 10편채널 정책
같은 날 2편카테고리·연출 카드가 달라야 함반복 리스크
YouTube 시스템 한도숫자 비공개. 최근 24시간 기준, 채널 이력·경고에 따라 다름. PC·모바일·API 모두 셈YouTube 고객센터
한도 확인은 이렇게 — 그리고 시계를 믿지 말고 확인

① output/uploads.csv 의 최근 24시간·7일 줄 수를 센다. ② Studio 콘텐츠 목록에서도 날짜를 직접 확인(사용자가 직접 올린 영상은 장부에 없을 수 있음). ③ 현재 시각은 date 로 확인 — 이번 작업에서 TZ=Asia/Seoul date 가 UTC 를 내놓아 장부 시각을 잘못 적었다가 고친 적이 있다 (로컬 date 가 한국 시간이었음). ④ 한도를 넘으면 올리지 않고 다음에 올릴 수 있는 시각을 알린다. 다른 계정·채널로 돌려 올리는 우회 금지. 사용자가 한도를 넘겨 올려 달라면 표를 알려 주고 확인받은 뒤에만.

  1. 준비: 원본은 output/videos/. 확장의 업로드는 파일당 10MB 제한인데 이번 영상(약 9.6MB)은 그대로 통과했다. 넘으면 스크래치패드에 2-pass 압축본(-b:v 1100~1600k, aac 128k)을 만들고 프레임 하나 뽑아 화질 확인.
  2. 새 탭에서 작업: tabs_context_mcp → 새 탭 → studio.youtube.com/channel/<채널ID>/videos/upload?d=ud (업로드 창이 바로 열림). 사용자가 쓰는 탭은 건드리지 않고, 끝나면 내가 연 탭을 닫는다.
  3. 파일: find 로 file input → file_upload (파일 선택 버튼은 클릭 금지).
  4. 세부정보: 제목 클릭 → ctrl+a → 입력 / 설명은 기본 틀 [자료 출처]…"0000년 0월 0일" 을 통째로 지우고 브리프의 설명으로 교체 / 재생목록 드롭다운에서 주제에 맞는 것(맞는 게 없으면 새로 만들지 말고 사용자에게 묻기) / 아동용 아님 / "자세히 보기" 펼쳐 AI 사용 = 아니요(텍스트·차트만, 실존 인물처럼 보이는 합성·음성 없을 때만) / 태그(쉼표로 끝맺기, 10개 이하).
  5. 검증: 입력 후 스크린샷으로 내용이 남아 있는지 확인(업로드 직후 대화상자가 다시 그려지며 입력이 날아갈 수 있음). 라디오 선택은 스크롤해서 실제 선택됐는지 다시 본다.
  6. 공개 범위: 기본 비공개. 사용자가 "공개로"라고 명시한 경우에만 공개 선택 후 "게시". 자동 모드 권한이 막으면 우회하지 말고 Studio 에서 직접 바꾸도록 안내.
  7. 사후 확인: Studio 콘텐츠 목록에서 실제 공개 상태·게시일을 다시 확인(채널 기본값 때문에 의도와 다르게 공개될 수 있음). 영상 ID 는 목록 링크(/video/<ID>/edit)에서 읽는다.
  8. 장부: output/uploads.csv 에 한 줄 — uploaded_at,video_id,brief,title,visibility,category, 시각은 한국시간.
  • 채널 업로드 기본값(2026-10-16 설정): 카테고리 교육 · 언어 한국어 · 설명란에 [자료 출처] 틀이 미리 들어감.
  • 영상 파일 교체는 불가 → 새로 올리고 옛 영상 삭제는 사용자가 한다 (영구 삭제는 Claude 가 하지 않음).
  • 설명란 고정 문구: 출처 목록 · "OO년 O월 O일 기준이며 이후 바뀔 수 있습니다" · 투자/의료 고지 · 직접 계산한 값 명시 · "대본 초안 작성에 AI 도구를 사용했으며 사람이 검수했습니다" · [음악] · 보도 1건 기준 수치 안내.

3-11. shortube 자동화 팁

⚡ 한 줄로 시키기

  • "오늘 쇼츠 만들어줘" → 주제 선정·브리프 자동.
  • "〇〇 주제로 만들어줘" → 브리프(전제 확인 포함)→영상→BGM→스틸→렌더까지.
  • "올려줘" → 한도 확인 후 업로드, "공개로"를 말해야 공개.

🧱 부품 재사용

  • Caption · Counter · Stamp · clamp 는 그대로 import.
  • 새 영상 = 가장 비슷한 폴더 복사 → timeline.json·scenes 교체.
  • BGM 도 가장 비슷한 bgm-*.mjs 복사.

🔍 눈으로 하는 검사를 자동에 가깝게

  • 핵심 프레임 8장을 --scale=0.4 로 스틸 → 겹침·잘림 확인 → 고치고 해당 프레임만 재렌더.
  • npx tsc --noEmit 로 타입 오류 먼저.
  • 렌더는 백그라운드로 돌려 두고 알림을 기다린다.

📋 반복 리스크 관리

  • 같은 틀에 숫자만 바꾼 대량 업로드 금지 → 형식을 섞는다(순위 비교·숫자 하나·전후 비교·퀴즈).
  • 브리프의 "부가가치 요소" 한 줄을 반드시.
  • 최근 업로드 5편과 구성·문장이 같지 않은지.
  • 가능하면 사람 목소리 내레이션 (TTS_GUIDE).

🎙 내레이션(선택)

  • TTS_GUIDE: "데이터 좋아하는 친한 친구" 톤, 해요체, 초당 6.5~7.5음절, 한 문장 15음절 안팎.
  • 자막과 TTS 텍스트를 분리(숫자는 한글로: 일 위·십이 주째).
  • 목소리 후보 Laomedeia·Puck·Sadachbia 등을 들어보고 1개 확정.
  • 현재 영상들은 자막+BGM 중심(내레이션 없음).

🗂 이력

  • log-topic 으로 로테이션 이력 기록.
  • uploads.csv 로 한도 계산.
  • YouTube API 데이터는 30일 후 prune.

funtube웃긴 영상툰 · 하이브리드 방향 (코드에서 정리)

이 장의 성격 (2026-10-17 갱신)

funtube 에는 CLAUDE.md·README 가 없습니다. 아래는 funtube/package.json · scripts/*.mjs · content/*.json · src/ (특히 새로 생긴 Carpool.tsx · toon3d/acts.ts · toon3d/car.tsx)를 읽고 정리한 것입니다. "하이브리드"라는 이름과 방향 설명은 코드에서 읽어 낸 것이라, 의도하신 방향과 다르면 이 장을 고쳐 주세요. 아직 굳은 운영 규칙(톤·금지 소재·업로드)은 없습니다.

4-1. 무엇인가 · 방향

1080×1920 세로 영상 모음. 대본(JSON) 한 파일 → AI 음성 → Remotion 렌더가 기본 흐름이다. 초기에는 말풍선·이모지·2D SVG 위주였지만, 지금 방향은 하이브리드다: "코드가 잘하는 것"과 "AI가 잘하는 것"을 한 장면 안에서 나눠 맡긴다.

🧱 코드가 맡는 것

  • 몸 · 무대 · 차 · 카메라 (Three.js 3D, toon3d/)
  • 몸 연기 사전 ACTS (호통·움찔·으스대기…)
  • 타이밍 · 자막 · 효과음 · 비트 (전부 코드 합성)
  • → 같은 대본이면 같은 영상(재현·수정 쉬움)

🎨 AI가 맡는 것

  • 얼굴: Gemini 가 그린 6표정 시트 → 3D 머리에 입히는 데칼 (facegen.mjs)
  • 목소리: Gemini TTS (캐릭터별 보이스 + 톤 지시)
  • → 사람이 직접 그리지 않고도 캐릭터 개성·웃음을 얻음

👤 사람이 맡는 것

  • 대본(웃음 설계)과 표정·연기 지정 (face · act · cam)
  • 표정 시트 중 마음에 드는 버전 고르기 (raw_*_v1/v2… 보관)
  • 최종 확인, 업로드
content/episodes.json (대본 + face·act·cam)→npm run tts→src/generated/timings.json + public/audio · public/sfx→facegen → public/faces/<이름>_1..6.png→npm run studio→npm run render→out/<id>.mp4

out/ 에 chat · skit · facts · balance · fight · ride · ride3d · ride3dface · carpool .mp4 가 있다 (ride3dface-v2/-v3 는 표정 시트를 바꿔 가며 비교한 버전).

4-2. 영상 형식 9가지 — 방식별로 나누면

2D 코드 방식(chat · skit · facts · balance · fight · ride)과 3D 하이브리드 방식(carpool · ride3d · ride3dface)으로 갈린다. 새 영상은 3D 하이브리드를 기본 방향으로 보고, 가벼운 형식(단톡·상식·밸런스)은 2D 로 빠르게 뽑는다.

id컴포넌트내용대본 필드
chatChat.tsx단톡방 채팅 개그 ("아빠가 갑자기 사랑한다고 했다"). 대사가 말풍선으로, 마지막에 시스템 메시지로 반전room · voices · lines[who,text,say,style,pauseAfter]
skitSkit.tsx짧은 콩트 ("7살 협상가 vs 엄마")같은 구조 + fx
factsFacts.tsx상식 한 줄씩 ("이거 알았어? 🤯") — 이모지 + 한 문장, 마지막에 반전 fx:"twist"emoji · text · style
balanceBalance.tsx밸런스 게임 — A/B 선택지 카운트다운(3초) 후 다음a · b · pauseAfter · end
fightToon.tsx + toon/2D 영상툰 — 캐릭터(Kid.tsx SVG), 카메라(cam), 포즈(pose), 피격(hit · dmg · fx · sfx · sound) ("맞짱 뜨다 드래곤볼 찍은 썰")scene · cam · pose · dmg · hit
rideSong.tsx + Lyric.tsx2D 노래 영상 ("길이 막혔어") — 가사가 박자에 맞춰 단어 단위로 노랗게 물듦. 풍경이 박자(bpm)에 비례해 지나감content/song.json
ride3dSong3D.tsx + toon3d/같은 노래의 3D 버전 (react-three-fiber + three.js, 관절 각도·머리 눌림 애니메이션). 얼굴은 기본 표정song.json
ride3dface 하이브리드Song3D.tsx (faces 속성)3D 몸 + AI 표정 그림을 머리에 입힘. 박자·가사 위치에 따라 표정 1~6을 고름(planFace · pickFace)song.json + faces.json
carpool 하이브리드Carpool.tsx + toon3d/car.tsx · acts.ts3D 차 안 콩트 "길이 막혔어" (선배 · 후배 · 내비). 대본 한 줄마다 카메라 샷 · 표정 번호 · 몸 연기를 지정. 출발 → 내비 → 정적(심장 소리) → 내려 주기까지 풍경 속도가 연출로 바뀜who · text · style · cam · face{} · act{} · sfx · fx · sound

4-3. 하이브리드 방식 — 무엇을 어떻게 섞나

한 장면의 층 구조 (Carpool.tsx 기준)

층만드는 방법어디서비용
배경 · 차 · 하늘 · 안개코드(Three.js)toon3d/car.tsx (CarBack · CarFront), ThreeCanvas무료
몸 (팔·허리·어깨·머리 움직임)코드 — 관절 각도로 연기toon3d/Char3D.tsx · acts.ts무료
얼굴 표정AI 그림 6칸 시트 → 투명 PNG 6장을 머리에 데칼로facegen.mjs → public/faces/<이름>_1..6.png → FaceChar유료(생성) · 한 번 만들면 재사용
카메라 (샷 전환 · 밀고 들어가기 · 흔들림)코드 — CAMS 표 + RigCarpool.tsx무료
자막 · 이름표 · 내비 화면 · 제목 띠코드(React) — 쇼츠 UI 안전 영역 SAFE 안components/common.tsx무료
목소리AI 음성(캐릭터별 Gemini 보이스 + 영어 톤 지시)scripts/tts.mjs유료(바뀐 줄만)
효과음 · 엔진 소리 · 노래 비트코드 합성tts.mjs 의 synth · beat.mjs무료

핵심은 "AI 산출물은 얼굴 그림과 목소리 두 가지뿐"이라는 점이다. 그래서 (1) 캐릭터 일관성이 코드로 보장되고(몸·옷·무대는 매번 같음), (2) 대사 한 줄을 고치면 그 줄 음성만 다시 만들면 되고, (3) 연기를 바꾸는 건 대본의 act 이름 하나만 바꾸면 된다.

언제 무엇을 쓰나

상황고르는 방식이유
대사가 많고 표정·몸짓으로 웃기는 콩트3D 하이브리드 (carpool 식)표정(face) + 연기(act) + 샷(cam)을 줄마다 지정해 연출 가능
음악·가사 중심ride3dface (또는 2D ride)가사 박자에 표정을 맞춤. 2D 는 더 가볍고 렌더가 빠름
채팅·상식·밸런스 같은 텍스트 위주2D (chat · facts · balance)3D 가 필요 없음. 렌더 빠름·수정 쉬움
타격·과장 액션 만화 느낌2D 영상툰 (fight)SVG 캐릭터 + 집중선·흔들림 효과

대본에서 연출을 지정하는 법 (carpool 예)

{ "who":"선배", "text":"야, 너 5분 늦었어. 시간 개념이 없어.",
  "style":"scolding loudly, annoyed older boss",      // TTS 영어 톤
  "cam":"c선배",                                       // 카메라 샷: wide · two · c선배 · c후배 · creep
  "face":{ "선배":3, "후배":1 },                        // 표정 번호 1~6 (아래 표). 지정 안 하면 직전 표정 유지, 말하는 줄은 기본 2(TALKING)
  "act": { "선배":"scold", "후배":"deadpan" } }         // 몸 연기 이름 (ACTS 사전). 다음 지정 전까지 유지

{ "who":"효과", "text":"", "dur":1, "sfx":"door" }      // voices 에 없는 who = 음성 없는 효과 줄 (dur 초)
{ "who":"내비", "text":"200미터 앞, 직진입니다.", "fx":"nav", "sound":"⬆ 직진 200m", "sfx":"chime" }   // fx:nav → 내비 화면에 sound 표시
항목값과 뜻
face 1~61 NEUTRAL 무표정 · 2 TALKING 말하는 입 · 3 ANGRY 분노(핏줄) · 4 SHOCK 충격(땀방울) · 5 CUTE 볼터치 미소 · 6 UNCANNY 섬뜩한 활짝 웃음 (content/faces.json 의 표정 순서 = 시트 읽는 순서)
act (ACTS)idle · deadpan(축 처진 무표정) · scold(삿대질 호통) · flinch(움찔) · shrugOff(억울한 으쓱) · pointFwd(직진!) · wheel(핸들 잡기) · sweepRight(우회전 휙) · smug(으스대기) · surrender(항복) · freeze(얼어붙음) · lookOver(천천히 쳐다봄). 새 연기는 toon3d/acts.ts 에 함수 하나 추가
camwide(전체) · two(둘 함께) · c선배/c후배(얼굴 클로즈업) · creep(천천히 다가가는 공포 샷). 새 샷은 CAMS 표에 d(거리), tx, ty 추가. 샷이 바뀌면 9프레임에 걸쳐 부드럽게 이동
fxdrive(출발: 풍경이 달리기 시작) · nav(내비 화면) · stinger(정적+붉은 비네트+심장 소리) · out(선배가 내려서 멀어짐). 이 줄들이 영상의 속도·장면 전환 기준점이라 대본에서 빠지면 영상이 깨진다(beats.find(...)!)
sfxdoor · blinker · chime · heart · away · engine … (public/sfx/). heart 는 음량 1, 나머지 0.7. 엔진 소리는 출발~정적, 내려 준 뒤 구간에 반복 재생
연출 설계 요령 (carpool 에서 읽히는 패턴)
  • 무표정 vs 과장: 후배는 deadpan+무표정(face 1·2), 선배는 호통·으스대기로 과장 → 대비가 웃음.
  • 소리를 비워서 웃기기: stinger 구간에서 풍경을 멈추고 엔진을 끄고 심장 소리만 → 긴장 후 반전.
  • 샷으로 말하기: 말하는 사람 클로즈업(c선배)과 반응 컷(c후배)을 번갈아, 공포 장면엔 creep 로 서서히 밀기.
  • 맞았을 때: flinch/surrender 줄에서 카메라 흔들림 자동 적용.
3D 렌더 주의

3D 장면은 remotion.config.ts 에 Config.setChromiumOpenGlRenderer("angle") 가 필요하다(이미 설정됨). 2D 보다 렌더가 느리고 파일이 크다(carpool.mp4 약 24MB, ride3dface 약 28MB — 확장 업로드 10MB 한도를 넘으니 사람이 직접 올리거나 압축). 의존성: three · @react-three/fiber · @remotion/three — 처음엔 npm install.

4-4. 제작 순서

  1. 대본 쓰기 사람·Claudecontent/episodes.json 에 새 키(id)로 title · voices · lines 추가. 노래면 content/song.json. 하이브리드 콩트라면 줄마다 cam · face · act 까지 적는다(4-3).
  2. 표정 시트 준비 (하이브리드만) 캐릭터 묘사를 content/faces.json 의 characters 에 추가 → node scripts/facegen.mjs --list 로 프롬프트 확인(무료) → 요청이 있을 때만 facegen <이름>. 이미 있는 시트면 --process-only. 이 단계가 끝나야 public/faces/<이름>_1..6.png 가 생긴다.
  3. 컴포넌트 연결2D 형식은 기존 컴포넌트(Chat·Facts …)를 쓰면 되고, 새 형식이면 src/Root.tsx 의 EPISODES 배열에 {id, component} 추가 (EpisodeId 는 episodes.json 키에서 자동). 3D 콩트는 Carpool.tsx 를 복사해 EP · WHO · X(좌석 위치) · LOOK(옷·머리색) · CAMS 를 바꾼다.
  4. 음성 + 효과음 npm run tts (특정 편만: node scripts/tts.mjs carpool). 효과음 14종은 이때 코드로 합성된다. 먼저 TTS_DRY=1 npm run tts 로 길이만 추정해 레이아웃을 확인하고(무료), 마음에 들면 키로 생성.
  5. 미리보기npm run studio → 샷·표정·연기·타이밍 확인. 대사만 고쳤다면 tts 를 다시 (바뀐 줄만 생성). 연기·카메라만 고쳤다면 음성은 그대로 두고 studio 로 확인.
  6. 렌더 무료npm run render (전부) 또는 node scripts/render.mjs carpool → out/<id>.mp4. 노래는 먼저 npm run beat. 3D 는 느리니 한 편씩.
  7. 타입 점검npm run typecheck.

scripts/render.mjs 의 기본 목록(all)은 chat skit facts balance fight ride ride3d ride3dface carpool 입니다. 새 형식을 추가하면 이 배열에도 넣어야 "전부 렌더"에 포함됩니다.

4-5. content/ 파일 — 대본 문법

"chat": {
  "title": "아빠가 갑자기 사랑한다고 했다",
  "room": "우리집 🏠",
  "voices": { "아빠":"Charon", "엄마":"Kore", "나":"Puck", "시스템":"Zephyr" },   // 화자 → Gemini 보이스
  "lines": [
    { "who":"아빠", "text":"사랑해 ❤️", "say":"사랑해~",                          // text=화면, say=읽을 문장(이모지·기호 보정)
      "style":"suspiciously sweet and sheepish" },                              // 영어 톤 지시 (TTS 프롬프트에 들어감)
    { "who":"시스템", "text":"엄마님이 아빠님을 내보냈습니다.", "pauseAfter":1.5 }   // 이 줄 뒤 추가 쉼(초)
  ] }
필드뜻
voices화자 이름 → Gemini 사전 정의 보이스 (Puck · Kore · Charon · Zephyr · Fenrir · Orus · Leda …). voices 에 없는 who(예: "효과")는 음성 없이 dur 초만큼 빈 줄
text · say화면 글자 · 실제 읽을 말 (없으면 text 에서 이모지를 지워 읽음)
style"Speak in Korean. Tone: … Say only this line" 프롬프트로 들어가는 영어 톤
pauseAfter · dur줄 뒤 추가 쉼(초) · 음성 없는 줄의 길이
fx · emoji · a · b · end형식별 연출 (twist · 이모지 · 밸런스 선택지 · 끝 표시)
scene · cam · pose · dmg · hit · sfx · sound2D 영상툰(fight) 전용: 배경 장면 · 카메라(wide/mid/closeL/closeR) · 인물 포즈(angry/punch/hit/down/smirk…) · 데미지 · 피격자 · 효과음 · 화면 의성어
cam · face · act · fx · sfx · sound3D 하이브리드(carpool) 전용: 카메라 샷 · 인물별 표정 번호(1~6) · 인물별 몸 연기 이름 · 기준 연출(drive/nav/stinger/out) · 효과음 · 내비 화면 글자. 상세는 4-3

같은 cam 이라는 필드 이름이라도 2D(fight)와 3D(carpool)에서 값 목록이 다르다(2D: wide/mid/closeL/closeR, 3D: wide/two/c선배/c후배/creep). 형식별 컴포넌트가 해석한다.

타이밍은 src/timeline.ts 가 계산: 첫 대사 앞 0.4초, 줄 사이 기본 0.35초, pauseAfter 추가, 끝 0.5초. src/generated/timings.json 이 (줄별 음성 파일 · 길이)를 담는다 — 자동 생성이니 손으로 고치지 않는다.

4-6. 음성 · 효과음 (scripts/tts.mjs)

  • 모델 기본 gemini-2.5-flash-preview-tts (환경변수 GEMINI_TTS_MODEL 로 변경). 출력 24kHz 16bit 모노 PCM → WAV. 앞뒤 무음 자동 제거(0.05초만 남김).
  • 캐시: [모델|보이스|style|say] md5 가 같고 파일이 있으면 다시 생성하지 않는다 → 대사 하나만 고치면 그 줄만 과금. 429/5xx 는 15초씩 늘려 최대 6번 재시도.
  • 키 순서: 환경변수 GEMINI_API_KEY/GOOGLE_API_KEY → 루트 .env. 키가 없으면 음성 없이 글자 수 × 0.13초로 길이만 추정해 타이밍을 만든다 (레이아웃 점검용, 무료). TTS_DRY=1 로 일부러 키를 무시할 수 있다.
  • 효과음 14종은 저작권 걱정 없이 코드로 합성해 public/sfx/ 에 만든다: 기본 pop tick ding boom crinkle whoosh punch powerup + 콩트용 사운드 디자인(차 안) door(문) · blinker(깜빡이) · chime(내비 안내음) · heart(심장 소리 4박) · away(멀어지는 소리) · engine(엔진 저음, 반복 재생용). 새 효과음은 synth("이름", 길이초, (t,p)=>…) 한 줄 추가 후 tts 를 한 번 돌리면 생성된다(키 없어도 됨).
  • 하이브리드 콩트에서는 목소리마다 역할에 맞는 보이스 + 영어 톤(예: 선배 "scolding loudly, annoyed older boss", 후배 "flat deadpan, very tired", 내비 "robotic car navigation voice, polite and flat")을 줘서 연기와 어울리게 한다. 한 편에서 보이스가 겹치지 않게 voices 를 정한다.

4-7. 노래 영상 (beat.mjs · song.json)

  • song.json = title · lead · sections[]. 섹션: name · style(intro/verse/chorus/bridge/end) · bpm · energy(0~3) · lines[{who,text,beats}]. bpm: 0 이면 음악 없는 정적 구간(lines[].seconds 사용).
  • node scripts/beat.mjs (npm run beat) 가 이 구조대로 킥·스네어 등 비트를 코드로 합성해 public/bgm/ride.wav 로 저장 (고정 시드 잡음 → 매번 같은 소리, API 없음, 무료).
  • 영상 쪽 src/songTimeline.ts 가 같은 계산으로 가사 타이밍을 만들고, 풍경 이동 속도는 bpm × 7 px/초(정적 구간은 멈춤). beat.mjs 와 songTimeline.ts 의 계산이 어긋나면 소리와 가사가 어긋난다 — 한쪽을 고치면 다른 쪽도 같이.
  • 가사는 beats 로 길이를 정하므로, 박자 수만 바꿔 가사를 늘이고 줄인다.

4-8. 얼굴 표정 시트 — 하이브리드의 AI 쪽 (facegen.mjs)

3D 노래(ride3dface)와 3D 콩트(carpool)가 같은 표정 시트(public/faces/선배_1..6.png, 후배_1..6.png)를 쓴다. 캐릭터가 늘면 faces.json 의 characters 에 묘사를 추가해 시트를 새로 만든다. 표정 번호 의미는 4-3 의 표.

node scripts/facegen.mjs --list                  # 프롬프트만 출력 (무료)
node scripts/facegen.mjs 선배 후배                # Gemini 로 6칸 표정 시트 생성 (유료)
node scripts/facegen.mjs --process-only          # 이미 받은 원본(art/faces/raw_<이름>.png)만 다시 자르기 (무료)
node scripts/facegen.mjs --placeholder           # 임시 시트로 자르기만 — 파이프라인 점검 (무료)
node scripts/facegen.mjs 선배 --force            # 원본이 있어도 다시 생성 (유료)
  • content/faces.json = 화풍(한국 웹툰 개그체 "grotesque-cute") · 작업 지시 · 캐릭터별 묘사 · 6표정 · 레이아웃. 얼굴 이목구비만 초록(#00FF00) 배경에 3×2 격자로 그리게 한다 (머리 윤곽·머리카락·몸 없음 — 3D 머리에 투영하는 데칼).
  • 처리: 초록 키잉 → 가장 넓은 빈 틈 n개를 칸 경계로 → 6칸으로 잘라 public/faces/<이름>_1..6.png (투명 PNG). Gemini 가 오른쪽 아래에 넣는 작은 별 로고는 지운다.
  • 429 면 예비 키(GEMINI_API_KEY2)로 바꿔 재시도, 이미지 모델은 키로 쓸 수 있는 것 중 최신을 자동 선택(GEMINI_IMAGE_MODEL 로 고정 가능).
  • 표정 파일이 모자라면 원본(raw_*.png · _v1 · _v2-pale 버전들)을 art/faces 에 보관해 두고 --process-only 로 다듬기만 반복.

4-9. funtube 자동화 팁

💡 무료로 먼저

  • 키 없이 TTS_DRY=1 npm run tts → 길이 추정으로 타이밍·레이아웃 점검.
  • facegen --placeholder 로 파이프라인 점검 (임시 시트로 3D 머리에 입혀 봄).
  • facegen --list 로 프롬프트만 확인.
  • 마음에 들면 그때 키로 생성.

🧬 하이브리드 재사용

  • 표정 시트·몸·차·연기 사전은 한 번 만들면 여러 편에서 재사용 — 새 편의 비용은 대본 + 음성뿐.
  • 새 콩트 = Carpool.tsx 복사 → 무대(car.tsx 대신 새 무대)·CAMS·LOOK 교체.
  • 연기가 모자라면 acts.ts 에 함수 추가 → 대본에서 이름으로 호출.
  • 시트를 바꿔 가며 비교(raw_*_v1/v2, ride3dface-v2/v3)하고 마음에 드는 것만 public/faces 에 둔다.

♻ 캐시를 믿고 대사 수정

  • 같은 모델·보이스·톤·문장이면 재생성 안 함 → 한 줄만 바꾸면 한 줄만 과금.
  • 톤(style)만 바꿔도 해시가 바뀌어 다시 생성되니 주의.

🎭 웃음 설계

  • chat: 끝에 시스템 메시지로 반전.
  • facts: 마지막 줄에서 관객을 저격하는 twist.
  • balance: 선택지 3~4개 + "댓글로 알려줘" 끝.
  • fight(2D 영상툰): 허세 → 타격 → 의외의 결과(드래곤볼 찍은 썰).
  • carpool(3D 콩트): 역할 대비(호통 vs 무표정) + 정적·심장 소리로 긴장 후 반전.
funtube 를 qna 수준으로 굳히고 싶다면

① funtube/CLAUDE.md 에 하이브리드 방향 선언(코드/AI/사람의 역할 분담), 채널 정체성·톤·금지 소재·업로드 규칙 기록 ② 캐릭터 고정표(이름 → 표정 시트 버전 → 보이스 → 말투 → LOOK) 문서화 — qna 의 voice.json 처럼 한 번 정하면 바꾸지 않는 값으로 ③ 형식별 "기준 예시 편" 지정(3D 는 carpool, 2D 는 chat) ④ render.mjs 의 all 을 episodes.json 키에서 자동으로 만들기 ⑤ 3D 영상은 10MB 를 넘으니 업로드 절차(압축·직접 업로드)를 정하기. 이렇게 하면 이 장이 shortube 장처럼 단단해집니다.

5. Claude 에게 이렇게 말하세요

버튼으로 복사해서 그대로 붙여 쓸 수 있습니다. 괄호는 바꿔 넣으세요. 키 있는 생성이 필요하면 "API 로 만들어 줘"라고 분명히 말해야 유료 명령이 실행됩니다.

qna

새 편 기획"감기와 독감" 같은 주제로 qna 7편을 기획해 줘. 집 밖 장소와 제삼의 인물로.
콘티까지(무료)qna ep07 script.json · art.json 만들고 storyboard 까지 뽑아서 보여 줘. 그림·음성 API 는 아직 돌리지 마.
그림 생성(유료)qna ep07 그림 API 로 만들어 줘 (artgen). 먼저 --list 로 몇 장인지 알려 주고 시작해.
음성(유료)qna ep07 음성 API 로 만들어 줘 (voice gen). 대사 바뀐 컷만.
완성qna ep07 make 해서 mp4 와 자막까지 뽑고, 컷 이미지로 어색한 데 있는지 봐 줘.
쇼츠/qna-shorts ep07
고치기ep03 3번 컷에서 할머니가 더 빨리 등장하게 하고 연출만 바꿔서 다시 렌더해 줘 (음성은 건드리지 마).

shortube

오늘 주제 자동/shortube-daily 2
주제 지정 + 제작(주제와 최신 상황)으로 shortube 쇼츠 만들어 줘. 요청 전제부터 사실 확인하고, 브리프 → 영상 → BGM → 스틸 점검까지.
업로드(비공개)방금 만든 쇼츠 비공개로 올려 줘. 한도 확인하고 Studio 에서 상태 재확인해서 알려 줘.
업로드(공개)공개로 올려 줘.
점검shortube 최근 5편이 구성·문장이 너무 비슷하지 않은지 점검하고 다음 편 연출 카드를 제안해 줘.

funtube

대본funtube 에 "(주제)" 단톡방 개그 chat 형식으로 대본을 episodes.json 에 추가해 줘. 반전은 마지막 시스템 메시지로.
무료 점검funtube 새 편 tts 를 키 없이(TTS_DRY=1) 돌려서 길이만 확인하고 studio 로 볼 수 있게 해 줘.
하이브리드 콩트funtube 에 "(상황)" 3D 콩트를 carpool 방식으로 추가해 줘. 인물 둘의 성격 대비로 웃기고, 줄마다 cam · face · act 를 지정해서 episodes.json 에 넣어 줘. 음성·표정 시트 API 는 아직 돌리지 마.
표정 시트(유료)funtube 후배 표정 시트를 facegen 으로 다시 만들어 줘 (API). 먼저 --list 로 프롬프트 보여 주고, 기존 시트는 art/faces 에 _v 버전으로 보관해.
연기만 수정carpool 에서 선배가 호통칠 때 더 크게 움직이게 acts.ts 의 scold 를 고치고 음성은 건드리지 말고 다시 렌더해 줘.

문서 · 정리

규칙 반영방금 정한 규칙을 qna/CLAUDE.md (또는 shortube/CLAUDE.md)에 반영하고 이 가이드(docs/workflow-guide.html)도 같이 고쳐 줘.
커밋변경을 브랜치로 커밋하고 PR 올려 줘 (out/ · public/art · .env 는 제외).

6. 막힐 때 · 체크리스트

6-1. 증상 → 원인 → 처방

증상가능한 원인처방
qna: 그림이 점선 임시 그림으로 나옴파일 이름·폴더가 art.json id 와 다름 (예: 컷인을 dad/ 에 저장)저장 경로 확인 → npm run art -- epNN 이 "빠진 그림 목록"을 출력
qna: 음성 만들 때 "목소리가 셋" 오류한 컷에 화자 3명컷을 나눈다 (voice -- epNN jobs 로 검사)
qna: 컷 시작·끝에 "칙" 잡음AI Studio WAV 의 이중 헤더·C2PA 데이터voice.mjs 가 걸러 줌. 다른 편집기에 직접 넣을 때만 문제
qna: 목소리가 일그러짐rawVoice 빠짐(임시 음성용 변조가 켜짐) 또는 녹음 후 --tts 실행"rawVoice": true, --tts 금지
qna: 소리와 자막이 어긋남대사를 고쳤는데 음성을 안 다시 만듦make -- epNN --voice (또는 --tts)
qna: 쇼츠 글자가 가려짐쇼츠 UI 영역(오른쪽 140px·아래 330px)short --check 콘택트 시트로 확인
shortube: 자막이 본문과 겹침본문 요소가 y≈1450 아래요소를 올리거나 자막 줄 수정, pre-line
shortube: 숫자가 보도마다 다름출처 간 불일치범위로 쓰거나("약 5.1%") 빼고 conflict 메모, 화면에 "보도 기준"
shortube: 업로드 후 입력이 사라짐대화상자가 다시 그려짐입력 후 스크린샷 확인, 좌표 클릭
shortube: 10MB 넘음확장 업로드 한도2-pass 압축본 (-b:v 1100~1600k)
shortube: "일일 업로드 한도 도달"YouTube 시스템 한도24시간 기다림. 다른 계정으로 우회 금지
funtube: 음성이 안 나옴키 없음 → 길이만 추정 모드루트 .env 에 GEMINI_API_KEY (그리고 과금 동의 후 npm run tts)
funtube 3D: 얼굴이 안 보이거나 텍스처 오류public/faces/<이름>_1..6.png 6장이 모두 없음 (FaceChar 는 6장을 한 번에 로드)facegen --process-only 로 원본(art/faces/raw_*.png)에서 다시 자르거나 --placeholder
funtube 3D: 렌더가 깨지거나 검은 화면OpenGL 설정 누락remotion.config.ts 에 Config.setChromiumOpenGlRenderer("angle") 확인
funtube carpool: 렌더 중 오류 Cannot read … of undefined대본에서 fx 기준 줄(drive · stinger · out)을 지움세 줄을 대본에 되살린다 (풍경 속도·장면 전환의 기준)
funtube 3D: 영상이 10MB 를 넘어 확장 업로드 불가3D 렌더 용량(약 24~28MB)압축본을 만들거나 사람이 Studio 에 직접 올리고, 이후 세부정보만 Claude 가 입력
funtube: 노래와 가사가 어긋남beat.mjs 와 songTimeline.ts 계산 불일치두 파일을 같이 고치고 npm run beat 다시
공통: 한글이 깨짐(Windows)콘솔 인코딩PYTHONIOENCODING=utf-8

6-2. 올리기 전 체크리스트

qna 롱폼

  • □ 시나리오가 집 밖 + 제삼의 인물인가
  • □ 6번 박자에 농담이 없는가
  • □ 몸·성 소재면 몸을 안 그렸고 민감 단어가 화면 글자에 없는가
  • □ 8분 안팎인가
  • □ 음색이 voice.json 과 같은가 (바꾸지 않았는가)
  • □ 영어 제목 100자 이하, 자막(srt) 올렸는가
  • □ 썸네일(ThumbD) · 본편 ID 를 SKILL.md 에 기록했는가

shortube 쇼츠

  • □ 요청 전제를 확인하고 바로잡았는가
  • □ 모든 숫자가 사실 카드에 있고 기준일·단위가 있는가
  • □ 외부 이미지·로고·캡처 없음
  • □ 투자/의료 조언으로 읽힐 문장 없음 + 고지 문구
  • □ 부가가치 요소 한 문장으로 말할 수 있는가
  • □ 제목·훅이 실제 내용과 일치
  • □ 연출 카드가 직전 2편과 4개 이상 다름
  • □ 한도(24시간 2편·7일 10편) · 공개 범위 · 사후 상태 확인
  • □ uploads.csv 기록, 음악은 사람이 들어 봄

funtube

  • □ voices 에 모든 화자가 있는가
  • □ (3D) public/faces/<이름>_1..6.png 6장 · 표정 번호가 1~6 안인가
  • □ (3D) act 이름이 ACTS 에 있고 cam 이 CAMS 에 있는가 · drive/stinger/out 줄이 있는가
  • □ (3D) 렌더 후 용량 확인(10MB 초과 시 업로드 방법)
  • □ 이모지가 음성에 읽히지 않게 say 를 줬는가
  • □ 마지막 반전/끝 표시
  • □ render.mjs 목록에 새 형식을 넣었는가
  • □ npm run typecheck
  • □ 쇼츠 UI 안전 영역(common.tsx) 안에 글자가 있는가

6-3. 이 문서를 최신으로 유지하기

  • 규칙의 원본은 각 프로젝트의 CLAUDE.md·스킬 문서다. 이 가이드는 그 요약 + 실전 팁이므로, 원본이 바뀌면 이 문서도 같이 고친다 (Claude 에게 "규칙 반영" 문구 사용).
  • 정책·법령·모델 이름은 바뀐다. COMPLIANCE_GUIDE 의 원문 링크(YouTube 수익 창출·스팸·합성 콘텐츠 정책, API 개발자 정책)는 분기마다 재확인. TTS 모델명도 구현 전에 공식 문서 재확인.
  • 이 문서에는 키 값을 쓰지 않는다.

7. 명령어 전체표

qna cd D:\youtube\qna\video

명령하는 일비용
npm run prompts [-- epNN]Gemini 프롬프트 문서 생성 (이미 있는 그림은 ✅)무료
npm run storyboard -- epNN콘티 시트 storyboard.html무료
npm run artgen -- epNN [--list]없는 그림을 Gemini API 로 한 번에 (--list 는 목록만)유료(--list 무료)
npm run art -- epNN초록 배경 제거 · 자르기 · 크기 맞춤, 빠진 그림 목록무료
npm run voice -- epNN sheet|studio|split|jobs|gen [--all]녹음 대본 / AI Studio 페이지 / 줄 자르기+타이밍 / 작업 목록 / API 생성gen 만 유료
npm run tts -- epNN임시 음성(Windows 'Heami')무료
npm run sfx · node scripts/bgm.mjs효과음 · 배경음악 코드 합성무료
npm run handoff -- epNN그림·음성을 다른 도구에 넘길 지시서무료
npm run studioRemotion 미리보기무료
npm run make -- epNN [--voice|--tts]프롬프트→그림처리→음성→sfx/bgm→배속→렌더→자막--voice 만 유료
npm run render · stills · srt렌더 · 컷 PNG · 자막(en 인자로 영어)무료
npm run short -- epNN [--check]편집형 쇼츠 렌더 / 확인용 콘택트 시트무료

shortube cd D:\youtube\shortube (Python) · cd video (영상)

명령하는 일
.venv/Scripts/python.exe -m shortube init설정 검증 · SQLite 스키마
… entities · autocomplete최근 OTT 작품 수집 · 자동완성 후보 + 필터
… pick --count N [--json] · log-topic로테이션 주제 선정 · 이력 기록
… differences [--top N]"차이" 주제 후보 (output/difference_topics.csv)
… trends · shorts · report · prune · run-all트렌드/경쟁 쇼츠/리포트/30일 지난 API 데이터 정리/전체 (일부 Phase 는 미구현 — SPEC 9장)
pytest단위 테스트 (planner · filters · ott_works 등)
npm run heat | gta | suneung | usmarket | marketBGM 합성 + 해당 영상 렌더 → ../output/videos/…mp4
node scripts/market-data.mjs일봉 CSV → src/market/kospi.json (이평 · RSI · 볼린저)
npx remotion still <Id> out.png --frame=N --scale=0.4핵심 프레임 스틸 점검
npx tsc --noEmit타입 확인

funtube cd D:\youtube\funtube

명령하는 일비용
npm run tts [-- id…]대사별 음성 + 효과음 합성 + 타이밍 (TTS_DRY=1 이면 키 무시)키 있으면 유료
npm run beat노래 비트 합성 → public/bgm/ride.wav무료
node scripts/facegen.mjs [이름] [--list|--process-only|--placeholder|--force]얼굴 6칸 표정 시트 생성·자르기 (하이브리드의 AI 쪽)생성은 유료
TTS_DRY=1 npm run tts키를 무시하고 글자 수로 길이만 추정 — 레이아웃 점검무료
npm run studio · render [-- id…] · typecheck미리보기 · 렌더(out/<id>.mp4) · 타입 확인무료