유튜브 3채널, 만드는 법을 한 장에
저장소 D:\youtube 에 있는 세 프로젝트(qna · shortube · funtube)를 어떤 순서로 만들고, 어디까지 자동이고, 어디서 사람이 개입하는지 정리한 운영 문서입니다. 시간이 지난 뒤 다시 봐도 자동화를 그대로 쓸 수 있게, 저장소의 실제 규칙 문서·스크립트를 읽고 옮겨 적었습니다.
그림 생성(artgen, facegen)과 음성 생성(voice … gen, funtube tts)은 저장소 루트 .env 의 Gemini 키로 실제 과금됩니다. 사용자가 "API로 만들어 줘"라고 한 경우에만 실행하고, 점검만 할 때는 --list / jobs / studio 같은 호출 없는 모드를 씁니다. (자세히: 1-2)
세 채널 한눈에 비교
여덟 살의 질문
- 채널 모라구 (가족 채널)
- 형식 8분 안팎 일러스트 롱폼 + 유입용 쇼츠
- 핵심 "아이의 질문 → 아빠가 얼버무림 → 명쾌한 설명"
- 엔진 Remotion + Gemini 그림·음성
- 사람 일 Gemini 그림 뽑기(또는 artgen), 시나리오 확정, 업로드
- 길잡이
qna/CLAUDE.md
숫자로 보는 요즘
- 채널 숫자로 보는 요즘 (@숫자로보는요즘)
- 형식 40~60초 텍스트·차트 모션그래픽 쇼츠
- 핵심 화제를 숫자·그래프로, 사실 카드만 쓴다
- 엔진 Python(주제 선정) + Remotion(영상) + 코드 합성 BGM
- 사람 일 검수, 한도 확인, 공개 결정
- 길잡이
shortube/CLAUDE.md+COMPLIANCE_GUIDE.md
웃긴 영상툰
- 형식 세로(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) | funtube | content/episodes.json → 제작 순서 |
| 표정·연기가 있는 3D 콩트 / 3D 노래 (하이브리드) | funtube | 하이브리드 방식 → 표정 시트(facegen) + cam·face·act 대본 |
| 음성만 다시 / 목소리 바꾸기 | qna · funtube | qna 음성 · 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 순서로 찾는다.
| 프로젝트 | 키를 읽는 곳 | 돈이 드는 명령 | 돈이 안 드는 점검 모드 |
|---|---|---|---|
| qna | qna/video/scripts/env.mjs 의 envValue() | npm run artgen -- epNN (그림)npm run voice -- epNN gen (음성) | artgen epNN --listvoice epNN jobs · studio |
| shortube | shortube/src/shortube/config.py load_config() | 음성 · 그림 단계 (TTS_GUIDE.md) | 주제 선정(pick)·브리프는 무료 |
| funtube | funtube/scripts/tts.mjs · facegen.mjs | npm 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감기·독감.
2-2. 폴더 지도 — 이 이름들만 쓴다
편 폴더 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단계
- 기획 자동(스킬)스킬
eight-year-olds-question에 주제를 던지면 자료조사 · 7박자 콘티 · 컷별 그림 콘티 · 나레이션 대본(당황 대사 포함) · BGM/효과음 · 썸네일 문구 · 한 문장 정리까지 한 번에. →episodes/NN-slug/<slug>.md,index.html - 대본 · 그림 목록 자동(Claude)기획서 대본을 영상용으로 옮긴다 (3편이 기준 예시). →
video/src/epNN/script.json,art.json - 콘티 확인 사람 확인
npm run storyboard -- epNN→storyboard.html을 열어 컷 구성·길이 확인. 여기서 대본을 고친다 (그림 그린 뒤에 고치면 낭비). - 그림 프롬프트
npm run prompts -- epNN→GEMINI_PROMPTS.md(공통 그림은art/common/GEMINI_PROMPTS.md에 한 번만). - 그림 사람artgen 은 유료Gemini 에서 손으로 뽑아
episodes/NN-slug/art/<id>.png에 저장하거나,npm run artgen -- epNN(사용자에게 물어보고). - 그림 처리 자동
npm run art -- epNN— 초록 배경 제거 · 여백 자르기 · 크기 맞춤 →video/public/art/epNN/. 끝나면 다시npm run storyboard -- epNN. - 음성 gen 은 유료
npm run voice -- epNN studio(손으로) 또는… gen(API) →voice-studio/epNN/,src/epNN/timing.json. - 등록 · 미리보기
src/Root.tsx의EPISODES에 한 줄 추가 (timing.json 이 생긴 뒤) →npm run studio. - 완성 자동
npm run make -- epNN→video/out/epNN/epNN.mp4+subtitles.srt. 썸네일은Ep NN-ThumbD스틸. - 사이트 · 저장홈
index.html카드 갱신, 브랜치에서 커밋 → PR. - 쇼츠 무료스킬
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"}
]
}]
}
| 위치 | 항목 | 뜻 |
|---|---|---|
| scene | bg | 배경 그림 (한 장짜리 full/… 도 여기). tone:"flashback" 이면 빛바랜 색 + 빨간 점선 테두리 |
actors[].x · h | 발 위치(0 왼쪽~1 오른쪽) · 키. 안 쓰면 아빠 0.64, 아이 0.43 | |
actors[].enter | left · right 미끄러져 들어옴 / drop 위에서 / pop 튀어나옴 / rise 좌판 뒤에서 솟기 | |
actors[].idle | sway · bounce · shiver (가만있을 때 움직임) | |
actors[].flip · at | 좌우 반전(아빠는 왼쪽, 아이는 오른쪽을 보게 그려져 있음) · 몇 번째 대사부터 나올지(0부터) | |
props[] | x y w, float(둥둥), dim(흐릿), front(배우 앞에 그림), motion(sway·pulse·spin·walk), to(화면 가로질러 이동) | |
| line | who | 화자: narr · dad · kid · mom · docu · granny … (목소리는 voice.json 이 정함) |
pose | 이 대사부터 그 인물의 그림(표정)을 바꿈 → 젤리처럼 눌렸다 튐 | |
focus · cutin | 그 인물로 카메라 당기기 · 큰 얼굴 컷인이 별 모양 폭발과 함께 튀어나옴 | |
fx | shake 흔들림 · lines 집중선 · flash 번쩍 · zoom | |
pop | 큰 글자가 튀어나옴 (예 "???") | |
sfx · sfxAfter | boing · 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. 그림 뽑기 — 사람이 하는 유일한 반복 작업
- 문서(
GEMINI_PROMPTS.md)의 블록을 위에서부터 하나씩 처리한다. 프롬프트를 통째로 복사해 Gemini 에 붙여넣는다. - 블록에 적힌 첨부 이미지를 같이 올린다. ① 화풍 앵커(
art/anchor/style-cutin.png) ② 같은 사람을 유지하기 위한 기준 그림. 순서를 지킨다. - 결과를 블록의 저장 경로·이름 그대로 저장한다. 배경 제거·크기 조절은 하지 않는다 (
npm run art가 한다). - 한 사람은 한 대화창에서 끝내고, 다음 사람·배경은 새 대화창에서. 대화가 길어지면 화풍이 흐려진다.
| 종류 | 저장 경로 예 | 뽑는 모양 |
|---|---|---|
| 아빠·아이 전신 | 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) | Kore | 2026-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. 새 대사가 필요하면 멈추고 사용자에게 묻는다.
구성 — 궁금증을 남기는 순서
- 0초에 질문: 아이의 질문 대사로 바로 시작 (인사·나레이션 도입 금지). 위에
hook두 줄이 쾅 — "아빠도 대답 못 한 / 진짜 차이"처럼 답이 있다는 약속. - 틀린 답 → 땡: 아빠가 얼버무린 대사에
stamp. - 반전 한 줄: "그런데 ~부터가 완전히 다릅니다"에
pop:"?!". - 챕터: 항목마다
chapter(위 목록에 불이 켜지고 첫째/둘째/셋째 도장). 한 항목에 대사 1~3개. - 마지막 항목은 답을 주지 않는다: 시작 대사 하나만 보이고 끝 카드로 질문을 남겨 본편으로. 목록에서도 마지막 항목은 차례가 올 때까지
???. - 길이 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 에서 바꾼다.
anthropic-skills:chrome-browser스킬을 읽고 도구를 한 번에 불러온다 →tabs_context_mcp→ 새 탭 →studio.youtube.com. 로그인 화면이면 멈추고 사용자에게 부탁 (비밀번호를 대신 넣지 않는다).- 본편 URL 찾기 (관련 동영상용). 업로드 창은
…/videos/upload?d=ud로 바로 열린다. - 파일은
find로input type=file을 찾아file_upload— 10MB 한도라epNN-short.upload.mp4사본을 쓴다. 파일 선택 버튼은 클릭하지 않는다(보이지 않는 창이 열림). - 제목 칸은 파일명이 들어 있으니 클릭 →
ctrl+a→ 입력. 설명 · 재생목록 · 아동용("아니요, 아동용이 아닙니다" — 묻지 않고 선택) · "자세히 보기"로 태그(쉼표로 끝맺기) · AI 사용: "예"(그림·목소리가 AI 생성). - "동영상 요소"의 관련 동영상에 본편 연결. 전화 인증 창이 뜨면 취소 (채널 인증은 이미 완료 2026-10-16).
- 공개 상태 비공개를 확대 화면으로 확인 → 저장. 영상 링크를
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장)
- 브리프 먼저 Claude
output/briefs/YYYY-MM-DD_slotN.md— 출처 표 · 사실 카드(YAML) · 스토리라인 · 연출 카드 · 메타데이터 · 검수 체크리스트. 영상에 들어가는 숫자는 사실 카드에 있는 것만. - 영상
video/src/<주제>/에 컴포지션 +timeline.json(장면·타격 시점) +Root.tsx등록. - 배경음악
video/scripts/bgm-*.mjs로 코드 합성 (같은timeline.json을 읽어 장면 전환과 맞춘다). - 점검 눈으로핵심 프레임 스틸
npx remotion still <Id> out.png --frame=N --scale=0.4를 직접 보고 겹침·잘림·줄바꿈을 고친 뒤 렌더. - 업로드3-9장. 기본은 비공개. 공개는 사용자가 명시적으로 요청할 때만.
- 보고무엇을 만들었고 무엇을 확인 못 했는지(음악은 귀로 못 들음, 미확인 사실 카드)를 그대로 알린다.
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줄
- 사실은 가져오고 표현은 직접 만든다. 기사 문장·구성·사진은 보호된다.
- 사람(개인) 콘텐츠는 만들지 않는다. 한국은 사실을 말해도 명예훼손이 성립할 수 있다(형법 307조 1항).
- 이미지·영상·음원은 외부에서 가져오지 않는다. 텍스트 · 직접 그린 차트 · 라이선스 명확한 아이콘/지도만.
- 영상마다 "내가 더한 가치"(직접 계산 · 비교 · 시계열 해석 · 오해 바로잡기). 같은 틀에 단어만 바꾼 대량 생산은 수익화(YPP) "진정성 없는 콘텐츠"로 빠진다.
- 자동화는 초안까지, 공개는 사람이.
주제별 반드시 / 금지
| 주제 | 반드시 | 금지 |
|---|---|---|
| 주식·시황 | 종가 기준 명시 · 방향 의견은 증권사명 인용으로 반등·지지·신중을 같은 비중 · "투자 권유 아님" 고정 · 한국 차트 색(상승 빨강·하락 파랑) | "사라/팔아라/바닥이다" · 목표가 · 유료 리딩·종목 추천 뉘앙스 |
| 의료·의약품 | 임상 논문·식약처·심평원 등 공인 출처 · 효과와 같은 비중으로 부작용·요요·규제 · "전문의약품 · 의사 처방 필요 · 의학적 조언 아님" 상시 표기 | 복용·시술 권유 · 구매처·최저가 · 제품 사진·로고 · "맞으면 빠진다" 단정 |
| 연예·아이돌 | 그룹 단위 공개 사실 · 성인 공인의 공개 경력 숫자 | 미성년 멤버 개인 정보 · 외모 비교 · 논란·사생활 · 인물 사진·무대 캡처·음원 |
| OTT·차트 | 상위 3~10개만 + 시계열 해석 같은 자체 분석 + 출처 텍스트 | 순위표 전체 재현 · 포스터·스틸컷·로고 |
| 실존 인물 발언 | 원문 그대로 인용 + 출처 · 지목하지 않은 대상을 지목한 것처럼 쓰지 않기 | 인물 사진 · 발언 왜곡·과장 |
출처 등급
| 등급 | 예 | 쓰는 법 |
|---|---|---|
| A 자유 이용 | 공공누리 1유형, 정부 보도자료, 공공데이터, 퍼블릭 도메인 | 사실·수치 추출 자유, 출처 표기 |
| B 표기 조건 | CC BY / CC BY-SA(위키백과) | 사실만. 문장 인용 시 라이선스 의무 → 문장은 쓰지 않는다 |
| C 사실만 참고 | 언론 기사, 공식 차트 사이트, 기업 홈페이지 | 사실·수치만. 문장·구성·이미지 금지. 출처는 텍스트로 |
| D 사용 금지 | 나무위키(CC BY-NC-SA), 유료 기사, 커뮤니티, 타 유튜버 대본, 출처 불명 블로그 | 열지도 않는다 (config/sources.yaml 의 blocked_domains) |
금지 항목 (만들지 않음)
3-5. 브리프 · 사실 카드 — 영상의 뼈대
사용자 요청의 전제도 확인한다. 실제 사례: "A20 LITE · SM 아이돌" → 실제는 A2O LITE, SM 아님 / "머스크의 고평가 발언" → 실제는 메모리 가격 비판, 회사 지목 없음 / "주가 반등" 보도 → 종가 기준으로는 하락 / (이번) "채권 발작"은 금요일이 아니라 10월 1일, "경기주 방어"는 목요일 이야기. 다르면 영상에 바로잡아 넣고 사용자에게 알린다 — 오히려 그게 영상의 훅·부가가치가 된다.
브리프 템플릿 (output/briefs/YYYY-MM-DD_slotN.md)
- 머리: 날짜 · 슬롯 · 대표 키워드 · 데이터 기준일 · 선정 근거 · ⚠ 요청 전제 바로잡기
- 출처 표: # · 기관/사이트 · 문서 · URL · 등급 · 확인일 (1차 자료를 못 열었으면 "미확인 — 제작 전 필수"로 표시)
- 사실 카드(YAML):
id · claim(내 말 1문장) · value · unit · as_of · sources[#] · verified(true/partial/false) · sensitivity - 직접 계산(부가가치): 계산식을 적는다 (예: 3.83 ÷ 0.34 = 11.3배)
- 대본 표: 시간 · 내레이션/자막 · 화면 — 사실 카드만 보고 쓴다
- 연출 카드 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)
새 영상을 추가하는 패턴 (예: UsShort)
src/<주제>/timeline.json— 장면 길이(프레임, 30fps) · hits · risers. 합계가total.fx.tsx(배경·전환·상단 띠) +scenes.tsx(장면별 컴포넌트 +Caption줄) +<Name>Short.tsx(Sequence 조립).Root.tsx에<Composition id=… durationInFrames={TOTAL} …/>추가,package.json에"<주제>": "node scripts/bgm-<주제>.mjs && remotion render …"한 줄.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 가 한국 시간이었음). ④ 한도를 넘으면 올리지 않고 다음에 올릴 수 있는 시각을 알린다. 다른 계정·채널로 돌려 올리는 우회 금지. 사용자가 한도를 넘겨 올려 달라면 표를 알려 주고 확인받은 뒤에만.
- 준비: 원본은
output/videos/. 확장의 업로드는 파일당 10MB 제한인데 이번 영상(약 9.6MB)은 그대로 통과했다. 넘으면 스크래치패드에 2-pass 압축본(-b:v 1100~1600k, aac 128k)을 만들고 프레임 하나 뽑아 화질 확인. - 새 탭에서 작업:
tabs_context_mcp→ 새 탭 →studio.youtube.com/channel/<채널ID>/videos/upload?d=ud(업로드 창이 바로 열림). 사용자가 쓰는 탭은 건드리지 않고, 끝나면 내가 연 탭을 닫는다. - 파일:
find로 file input →file_upload(파일 선택 버튼은 클릭 금지). - 세부정보: 제목 클릭 →
ctrl+a→ 입력 / 설명은 기본 틀[자료 출처]…"0000년 0월 0일"을 통째로 지우고 브리프의 설명으로 교체 / 재생목록 드롭다운에서 주제에 맞는 것(맞는 게 없으면 새로 만들지 말고 사용자에게 묻기) / 아동용 아님 / "자세히 보기" 펼쳐 AI 사용 = 아니요(텍스트·차트만, 실존 인물처럼 보이는 합성·음성 없을 때만) / 태그(쉼표로 끝맺기, 10개 이하). - 검증: 입력 후 스크린샷으로 내용이 남아 있는지 확인(업로드 직후 대화상자가 다시 그려지며 입력이 날아갈 수 있음). 라디오 선택은 스크롤해서 실제 선택됐는지 다시 본다.
- 공개 범위: 기본 비공개. 사용자가 "공개로"라고 명시한 경우에만 공개 선택 후 "게시". 자동 모드 권한이 막으면 우회하지 말고 Studio 에서 직접 바꾸도록 안내.
- 사후 확인: Studio 콘텐츠 목록에서 실제 공개 상태·게시일을 다시 확인(채널 기본값 때문에 의도와 다르게 공개될 수 있음). 영상 ID 는 목록 링크(
/video/<ID>/edit)에서 읽는다. - 장부:
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웃긴 영상툰 · 하이브리드 방향 (코드에서 정리)
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…보관) - 최종 확인, 업로드
4-2. 영상 형식 9가지 — 방식별로 나누면
2D 코드 방식(chat · skit · facts · balance · fight · ride)과 3D 하이브리드 방식(carpool · ride3d · ride3dface)으로 갈린다. 새 영상은 3D 하이브리드를 기본 방향으로 보고, 가벼운 형식(단톡·상식·밸런스)은 2D 로 빠르게 뽑는다.
| id | 컴포넌트 | 내용 | 대본 필드 |
|---|---|---|---|
chat | Chat.tsx | 단톡방 채팅 개그 ("아빠가 갑자기 사랑한다고 했다"). 대사가 말풍선으로, 마지막에 시스템 메시지로 반전 | room · voices · lines[who,text,say,style,pauseAfter] |
skit | Skit.tsx | 짧은 콩트 ("7살 협상가 vs 엄마") | 같은 구조 + fx |
facts | Facts.tsx | 상식 한 줄씩 ("이거 알았어? 🤯") — 이모지 + 한 문장, 마지막에 반전 fx:"twist" | emoji · text · style |
balance | Balance.tsx | 밸런스 게임 — A/B 선택지 카운트다운(3초) 후 다음 | a · b · pauseAfter · end |
fight | Toon.tsx + toon/ | 2D 영상툰 — 캐릭터(Kid.tsx SVG), 카메라(cam), 포즈(pose), 피격(hit · dmg · fx · sfx · sound) ("맞짱 뜨다 드래곤볼 찍은 썰") | scene · cam · pose · dmg · hit |
ride | Song.tsx + Lyric.tsx | 2D 노래 영상 ("길이 막혔어") — 가사가 박자에 맞춰 단어 단위로 노랗게 물듦. 풍경이 박자(bpm)에 비례해 지나감 | content/song.json |
ride3d | Song3D.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.ts | 3D 차 안 콩트 "길이 막혔어" (선배 · 후배 · 내비). 대본 한 줄마다 카메라 샷 · 표정 번호 · 몸 연기를 지정. 출발 → 내비 → 정적(심장 소리) → 내려 주기까지 풍경 속도가 연출로 바뀜 | 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 표 + Rig | Carpool.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~6 | 1 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 에 함수 하나 추가 |
cam | wide(전체) · two(둘 함께) · c선배/c후배(얼굴 클로즈업) · creep(천천히 다가가는 공포 샷). 새 샷은 CAMS 표에 d(거리), tx, ty 추가. 샷이 바뀌면 9프레임에 걸쳐 부드럽게 이동 |
fx | drive(출발: 풍경이 달리기 시작) · nav(내비 화면) · stinger(정적+붉은 비네트+심장 소리) · out(선배가 내려서 멀어짐). 이 줄들이 영상의 속도·장면 전환 기준점이라 대본에서 빠지면 영상이 깨진다(beats.find(...)!) |
sfx | door · blinker · chime · heart · away · engine … (public/sfx/). heart 는 음량 1, 나머지 0.7. 엔진 소리는 출발~정적, 내려 준 뒤 구간에 반복 재생 |
- 무표정 vs 과장: 후배는
deadpan+무표정(face 1·2), 선배는 호통·으스대기로 과장 → 대비가 웃음. - 소리를 비워서 웃기기:
stinger구간에서 풍경을 멈추고 엔진을 끄고 심장 소리만 → 긴장 후 반전. - 샷으로 말하기: 말하는 사람 클로즈업(
c선배)과 반응 컷(c후배)을 번갈아, 공포 장면엔creep로 서서히 밀기. - 맞았을 때:
flinch/surrender줄에서 카메라 흔들림 자동 적용.
3D 장면은 remotion.config.ts 에 Config.setChromiumOpenGlRenderer("angle") 가 필요하다(이미 설정됨). 2D 보다 렌더가 느리고 파일이 크다(carpool.mp4 약 24MB, ride3dface 약 28MB — 확장 업로드 10MB 한도를 넘으니 사람이 직접 올리거나 압축). 의존성: three · @react-three/fiber · @remotion/three — 처음엔 npm install.
4-4. 제작 순서
- 대본 쓰기 사람·Claude
content/episodes.json에 새 키(id)로title · voices · lines추가. 노래면content/song.json. 하이브리드 콩트라면 줄마다cam · face · act까지 적는다(4-3). - 표정 시트 준비 생성은 유료(하이브리드만) 캐릭터 묘사를
content/faces.json의characters에 추가 →node scripts/facegen.mjs --list로 프롬프트 확인(무료) → 요청이 있을 때만facegen <이름>. 이미 있는 시트면--process-only. 이 단계가 끝나야public/faces/<이름>_1..6.png가 생긴다. - 컴포넌트 연결2D 형식은 기존 컴포넌트(
Chat·Facts…)를 쓰면 되고, 새 형식이면src/Root.tsx의EPISODES배열에{id, component}추가 (EpisodeId는 episodes.json 키에서 자동). 3D 콩트는Carpool.tsx를 복사해EP·WHO·X(좌석 위치) ·LOOK(옷·머리색) ·CAMS를 바꾼다. - 음성 + 효과음 키가 있으면 유료
npm run tts(특정 편만:node scripts/tts.mjs carpool). 효과음 14종은 이때 코드로 합성된다. 먼저TTS_DRY=1 npm run tts로 길이만 추정해 레이아웃을 확인하고(무료), 마음에 들면 키로 생성. - 미리보기
npm run studio→ 샷·표정·연기·타이밍 확인. 대사만 고쳤다면tts를 다시 (바뀐 줄만 생성). 연기·카메라만 고쳤다면 음성은 그대로 두고 studio 로 확인. - 렌더 무료
npm run render(전부) 또는node scripts/render.mjs carpool→out/<id>.mp4. 노래는 먼저npm run beat. 3D 는 느리니 한 편씩. - 타입 점검
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 · sound | 2D 영상툰(fight) 전용: 배경 장면 · 카메라(wide/mid/closeL/closeR) · 인물 포즈(angry/punch/hit/down/smirk…) · 데미지 · 피격자 · 효과음 · 화면 의성어 |
cam · face · act · fx · sfx · sound | 3D 하이브리드(carpool) 전용: 카메라 샷 · 인물별 표정 번호(1~6) · 인물별 몸 연기 이름 · 기준 연출(drive/nav/stinger/out) · 효과음 · 내비 화면 글자. 상세는 4-3 |
타이밍은 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 × 7px/초(정적 구간은 멈춤). 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/CLAUDE.md 에 하이브리드 방향 선언(코드/AI/사람의 역할 분담), 채널 정체성·톤·금지 소재·업로드 규칙 기록 ② 캐릭터 고정표(이름 → 표정 시트 버전 → 보이스 → 말투 → LOOK) 문서화 — qna 의 voice.json 처럼 한 번 정하면 바꾸지 않는 값으로 ③ 형식별 "기준 예시 편" 지정(3D 는 carpool, 2D 는 chat) ④ render.mjs 의 all 을 episodes.json 키에서 자동으로 만들기 ⑤ 3D 영상은 10MB 를 넘으니 업로드 절차(압축·직접 업로드)를 정하기. 이렇게 하면 이 장이 shortube 장처럼 단단해집니다.
5. Claude 에게 이렇게 말하세요
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.png6장 · 표정 번호가 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 studio | Remotion 미리보기 | 무료 |
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 | market | BGM 합성 + 해당 영상 렌더 → ../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) · 타입 확인 | 무료 |