코드로 더빙 영상 만들기
개요
하나의 영상 = 최종 대본 → 문장별 TTS → 문장별 ASR 검증 → 타임라인 → HTML 애니메이션 → 프레임별 캡처 → ffmpeg 합성.
핵심 원칙: 타임라인은 오디오에서 비롯됩니다. 먼저 각 문장을 더빙하고 검증하여 각 자막 세그먼트의 실제 시작 및 종료 시간을 측정한 다음, 모든 화면 애니메이션을 이 시간 지점에 연결합니다. 화면에 고정된 초를 사용하지 않으므로 더빙을 변경한 후 타임라인을 다시 실행하기만 하면 됩니다. 렌더링은 페이지의 각 프레임에 대해 __seek(t) 캡처를 수행하며, 결과는 결정적이고 반복 가능합니다.
적용 대상: 설명/튜토리얼 (2–5분), 제품 프로모션 영상 (20–40초), 자막과 챕터 바가 있는 지식 영상.
비적용 대상: 실제 인물 출연 편집, 실사 촬영 자료가 필요한 영상, 순수 음악 MV.
프로세스
각 단계마다 관문이 있으며, 이전 단계를 통과하지 못하면 다음 단계로 진행할 수 없습니다.
| # | 단계 | 산출물 | 관문 | 참고 |
|---|---|---|---|---|
| 0 | 초기화 | 작업 디렉토리 | bash scripts/init.sh <dir> 성공 | 아래 |
| 1 | 주제 선정, 사실 확인, 대본 | script.md, audio/make_script.py | 사실 확인 완료 (실험은 먼저 실행); 사용자가 대본에 명시적으로 동의해야 더빙 시작 가능 | references/script.md |
| 2 | 더빙 | audio/raw/NN.wav → audio/clips/NN.wav (1.3x) | 각 문장 ASR 통과, 문제 문장 재녹음 | references/voice.md |
| 3 | 타임라인 | build/narration.wav, build/timeline.json | 자막 분할이 음성과 일치 | references/timeline.md |
| 4 | 자료 | 실제 인터페이스 스크린샷, 실제 터미널 출력 | 내레이션과 일치, 예시 도메인은 example.com 사용 | references/capture.md |
| 5 | 합성 | comp/index.html | 프레임별 캡처 타일 이미지를 하나씩 확인 | references/composition.md |
| 6 | 렌더링 | dist/video.mp4 | 재생 시간 = 타임라인, 음량 -14 LUFS | references/render.md |
| 7 | 썸네일, 카피 | dist/cover-bili.png, dist/cover-douyin.png, dist/copy.txt | 썸네일이 각 플랫폼의 크롭 안전 영역 내에 있어야 함 | references/publish.md |
| 8 | 전달, 정리 | 사용자에게 전송, 소스 파일 보관 | 사용자가 저장을 확인한 후에만 정리 | 아래 |
초기화
bash scripts/init.sh ~/work/<프로젝트>/video-<회차명> 명령어는 template/을 작업 디렉토리로 복사하고, scripts/를 tools/에 복사하며, playwright-core를 설치합니다. 템플릿은 바로 실행 가능한 30초 데모입니다. 작업을 시작하기 전에 다음 예시 콘텐츠를 교체하세요:
script.md,audio/make_script.py: 이번 회차 대본; 챕터는make_script.py의CHAPTERS에서 한 번만 정의하며, 챕터 간 일시 정지, 챕터 바, 챕터 타임스탬프는 모두 여기서 파생됩니다.audio/direction.txt: TTS 감독 프롬프트의 [장면].asr-prompt.txt: 이번 회차 용어집 (표준 표기법, 필요한 경우 발음 기호 포함).comp/index.html의 장면 및 애니메이션,comp/cover-*.html의 썸네일 콘텐츠.
자주 사용하는 명령어
작업 디렉토리에서 실행:
| 명령어 | 설명 |
|---|---|
python3 audio/make_script.py | audio/script.json 및 audio/lines.json 생성 |
python3 tools/tts.py audio/lines.json audio/raw | 문장별 더빙; 텍스트 기준 기존 오디오 재사용, 대본 수정 후 바로 재실행 |
bash tools/speed.sh audio/raw audio/clips 1.3 | 음정 변화 없이 속도 변경 |
python3 tools/asr.py audio/clips --en | 문장별 인식: 일반 / 용어 힌트 / 영어 모드 |
python3 tools/takes.py 3,7 3 | 3번과 7번 문장 각각 3개 버전 녹음 및 점수 측정 |
python3 tools/takes.py audio/var.json 2 | 동일 문장의 여러 변형 각각 2개 버전 녹음 |
python3 tools/tts.py audio/lines.json audio/raw --adopt 3 audio/takes/03-0-1.raw.wav | 특정 take 채택 |
python3 tools/build_audio.py audio/clips build audio/script.json | 더빙 연결, 타임라인 생성 |
python3 tools/align_chunks.py audio/clips build/timeline.json audio/script.json | 자막 세그먼트를 실제 발음에 정렬 |
node tools/render.mjs --timeline=build/timeline.json --stills=2,8.5,20 | 지정된 시간 지점의 프레임 캡처(매번 이전 내용 비움) |
bash tools/contact.sh build/stills build/sheets | 2x2 타일 이미지 생성, Read로 보기 |
bash tools/render-par.sh build/timeline.json build/narration.wav dist/video.mp4 8 | 8개 프로세스 병렬 렌더링 및 오디오 믹싱 |
node tools/render.mjs --html=comp/cover-4x3.html --width=1440 --height=1080 --png=dist/cover-bili.png | 썸네일 생성 |
python3 tools/bili_kw.py "nginx ssl 인증서 설정" "바오타 https" | 주제 선정: B站 키워드 상위 6개 동영상 조회수 |
의존성: ffmpeg, Node 18+, 로컬 Google Chrome, Python 3 + requests + openai-whisper (small 모델). TTS는 Xiaomi MiMo를 사용하며, 키는 환경 변수 MIMO_API_KEY 또는 MIMO_ENV_FILE이 가리키는 파일에서 읽습니다. 둘 다 없으면 사용자에게 요청합니다. 다른 TTS를 사용하려면 각 문장에 대해 하나의 WAV 파일을 보장하기만 하면 됩니다.
가장 실수하기 쉬운 부분
- 승인 없이 더빙하는 경우. 대본을 먼저 사용자에게 보내십시오. "계속/시작/좋아"는 동의로 간주합니다. 발음을 위해 어구를 변경한 경우, 전달 시 문장별로 사용자에게 알립니다.
- 자막과 더빙을 하나로 작성하는 경우. 자막은 사람이 보는 용도(
Nginx,443,1.3)이고, 더빙은 TTS가 읽는 용도(Engine X,사사삼,일점삼)입니다.make_script.py에서P(자막, 읽는 방식)을 사용하여 분리하여 작성하며, 각 부분의 읽는 방식을 연결하면 전체 문장의 TTS 텍스트와 동일해야 합니다. - 전체 혼합 오디오만 듣는 경우. 전체 문장 인식은 영어 용어를 잘못 인식할 수 있으므로, 반드시 문장별로 인식해야 합니다. take 선택 시 일반 점수와
--en결과만 확인합니다. 용어 힌트가 포함된 prime은 잘못 읽은 것도 "수정"할 수 있으며, 판단 규칙은 voice.md를 참조하세요. - 애니메이션에 고정 시간(초)을 하드코딩하는 경우. 첫 문장 이후의 모든 애니메이션은
T(문장, 세그먼트)를 사용해야 합니다. 속도 변경이나 재녹음 후 시간이 변경됩니다. - 페이지 시작 부분이 비어 있는 경우. 카드가 나타난 후 2-3초 동안 여전히 비어 있어 멈춘 것처럼 보입니다. 콘텐츠는 이번 챕터의 첫 번째 자막 세그먼트에 나타나야 합니다.
- 동일 요소에 여러 개의
fill: both애니메이션이 있거나, id가 중복된 경우. WAAPI는 오류를 표시하지 않지만 요소가 완전히 사라지거나 잘못된 상태로 멈출 수 있습니다. composition.md 참조. file://로 페이지를 열어 렌더링하는 경우. CSS mask 등이 작동하지 않습니다.render.mjs에는 로컬 HTTP 서비스가 내장되어 있습니다.- 플랫폼에서 썸네일을 자르는 경우. B站은 하나의 이미지를 4:3과 16:9로 동시에 자르고, Douyin 9:16은 3:4로 잘립니다. 콘텐츠는 안전 영역 내에만 배치하십시오.
- 규정을 위반하는 트래픽 유도. 화면, 썸네일, 카피에 실제 사이트 URL, QR 코드, 연락처 정보를 넣지 말고, "보세요/검색해 보세요"와 같은 문구를 사용하지 마십시오.
example.com과 같은 예약된 예시 도메인은 데모 인터페이스 및 터미널에 표시될 수 있습니다. 여러 플랫폼에 게시되는 영상은 특정 콘텐츠 플랫폼을 예시로 사용하지 마십시오. 프로모션 영상에는 가격을 언급하지 마십시오. - 결과를 확인하기 전에 성공했다고 먼저 보고하는 경우. 진행 메시지를 보내기 전에 명령어 실행이 완료되고 출력을 확인한 후에 보내십시오.
전달 및 정리
- 전달: 완성된 영상, B站 4:3 썸네일, Douyin 9:16 썸네일,
dist/copy.txt(제목, 태그, 설명, 챕터 타임스탬프). B站 영상은 기본적으로 Douyin 등 다른 플랫폼에도 게시되므로, 세로형 썸네일과 Douyin 카피도 함께 준비합니다. 사용자가 하나의 플랫폼에만 게시하기로 명시한 경우에만 해당 플랫폼 세트를 준비합니다. 발음을 위해 변경된 모든 어구와 확실하지 않은 시간 지점을 목록화하여 사용자에게 확인을 요청합니다. - 파일이 채팅 도구의 최대 용량 제한(일반적으로 30MB)을 초과하는 경우, 먼저
crf 22미리보기 버전을 보내고 고화질 버전 위치를 설명합니다. 대용량 파일 전송 시간 초과 후 재전송 시, 사용자가 중복으로 파일을 받을 수 있음을 알립니다. - 사용자가 자료 저장을 확인한 후: 재사용 가능한 소스 파일(
comp/및 사용된 자료,audio/make_script.py, 변형 명세,script.md,copy.txt,direction.txt,asr-prompt.txt, 스크린샷 스크립트,lab/)을 사용자의 템플릿 라이브러리에 보관합니다 (약속된 장소가 없으면 사용자에게 물어봅니다, 예:~/work/<프로젝트>/video-template/<회차명>/).diff -r로 하나씩 비교하여 일치하는지 확인한 후, 작업 디렉토리, 임시 브라우저 프로필, 컨테이너, 개발 서버 (PID로 종료, 먼저 명령줄 확인) 및 스크린샷만을 위해 설치된 프론트엔드 종속성을 삭제합니다. whisper 모델 캐시와 Docker 이미지는 유지합니다.