manim-video-with-narration
Manim 애니메이션 영상 제작 + Qwen3-TTS 나레이션 자동 추가 — 텍스트/URL에서 대본 추출 → 장면별 나레이션 생성 → 비디오 속도 조정 → 음성 합성 최종 영상
Manim Video + Edge-TTS Narration Pipeline
Manim으로 애니메이션 영상을 제작하고, edge-tts로 한국어 나레이션을 생성하여 합성하는 전체 파이프라인입니다.
필드 규칙
- 일반 영상은 항상 5분 이상으로 제작 (숏츠는 60초 이하)
- 해상도: -qm (720p30) 기본, 프로덕션은 -qh (1080p60)
- TTS: edge-tts (ko-KR-SunHiNeural) 사용. Qwen3-TTS/ACE-Step BGM 사용 불가
- 속도 조정: 0.5x~1.2x 범위 내 유지. 벗어나면 대본 분할
- 대본: 장면당 150자 이하 권장
전체 흐름
대본 준비 → 나레이션 대본 작성 → Manim 영상 렌더링
→ Qwen3-TTS 나레이션 생성 → 오디오/비디오 동기화 → 최종 합성
전제 조건
| 도구 | 용도 | 확인 명령 |
|------|------|-----------|
| Manim CE v0.19+ | 애니메이션 렌더링 | manim --version |
| ffmpeg | 영상/음성 처리 | ffmpeg -version |
| kaggle CLI | TTS 원격 실행 | kaggle --version |
| ~/.kaggle/kaggle.json | Kaggle API 인증 | ls ~/.kaggle/kaggle.json |
| 한국어 폰트 | Manim 텍스트 렌더링 | fc-list \| grep "Apple SD Gothic" |
한글 폰트 설정 (macOS)
KOR_FONT = "Apple SD Gothic Neo" # macOS 기본 한글 폰트
MONO = "Menlo" # 영문/코드용 모노스페이스
파이프라인 상세
Step 1: 대본 준비
영상 대본 출처:
- 사용자가 직접 제공한 텍스트
- YouTube URL →
youtube-content스킬로 자막 추출 - 기사/문서 → 요약 후 대본화
Step 2: 나레이션 대본 작성
장면별로 나레이션 텍스트를 작성합니다. 중요 규칙:
- 장면 길이에 맞춰 작성 — 각 장면의 예상 길이를 고려하여 텍스트 길이 조절
- 장면당 150자 이하 권장 — TTS 생성 시간이 길어지면 비디오와 심하게 불일치
- 구어체 사용 — "합니다"보다 "해요", "입니다"보다 "이에요" 등 자연스러운 말투
- 영어 고유명사는 한글로 표기 — "Claude Code" → "클로드코드", "Office Hours" → "오피스아워즈"
- 숫자도 한글로 — "15~20분" → "십오에서 이십분" (TTS에서 더 자연스러움)
- 문장은 짧게 — 한 문장에 핵심 메시지 하나씩
narrations = {
"Scene00_Title": "클로드코드 플러그인을 전부 지웠습니다. 그리고 단 두 개만 남겼더니, 지금이 제일 잘 됩니다.",
"Scene01_Problem": "클로드코드를 쓰다 보면 자주 겪는 문제가 있습니다. 결과물을 보면, 이게 아닌데 하는 상황이죠.",
# ... 각 장면별 대본
}
Step 3: Manim 영상 렌더링
manim-video 스킬을 참조하여 애니메이션을 제작합니다.
# 1. 드래프트 렌더링 (빠른 확인용)
manim -ql --disable_caching script.py Scene00 Scene01 Scene02 ...2. 미디엄 품질 (텍스트 확인용)
manim -qm --disable_caching script.py Scene00 Scene01 Scene02 ...
한글 렌더링 시 주의사항:
""(한글 인용부호) 사용 금지 → 파이썬 문자열"와 충돌font=KOR_FONT반드시 지정font_size최소 24 이상 (한글은 영문보다 크기 필요)
Step 4: Qwen3-TTS 나레이션 생성
qwen3-tts 스킬의 Kaggle 원격 실행 방식을 사용합니다.
4-1. 노트북 준비
장면별 나레이션을 한 번에 생성하는 노트북을 작성합니다:
# Cell 1: 설치
!pip install torch==2.6.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124 --quiet
!pip install qwen-tts scipy --quiet
!apt-get install -y ffmpeg > /dev/null 2>&1Cell 2: 장면별 나레이션 생성
import os, subprocess, torch, gc, json
import scipy.io.wavfile as wavfile
import numpy as np
from qwen_tts import Qwen3TTSModeltts = Qwen3TTSModel.from_pretrained(
"Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
device_map="cuda:0",
dtype=torch.float16,
)
narrations = {
"Scene00_Title": "...",
"Scene01_Problem": "...",
# ...
}
durations = {}
for scene_name, text in narrations.items():
wavs, sr = tts.generate_custom_voice(text=text, language="Korean", speaker="Sohee")
audio_np = np.array(wavs[0], dtype=np.float32)
wav_path = f"/kaggle/working/{scene_name}.wav"
mp3_path = f"/kaggle/working/{scene_name}.mp3"
wavfile.write(wav_path, sr, audio_np)
subprocess.run(["ffmpeg", "-y", "-i", wav_path, "-codec:a", "libmp3lame", "-q:a", "2", mp3_path],
capture_output=True)
result = subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", mp3_path],
capture_output=True, text=True)
durations[scene_name] = float(result.stdout.strip())
os.remove(wav_path)
gc.collect()
torch.cuda.empty_cache()
with open("/kaggle/working/durations.json", "w") as f:
json.dump(durations, f, indent=2)
4-2. 실행 및 다운로드
# 푸시
cd /tmp/kaggle-tts && kaggle kernels push -p .상태 대기 (약 3~8분)
while true; do
status=$(kaggle kernels status icbm3112k/qwen3-tts-test)
if echo "$status" | grep -q "COMPLETE"; then break; fi
if echo "$status" | grep -q "ERROR"; then echo "FAILED"; exit 1; fi
sleep 30
done다운로드
rm -rf /tmp/kaggle-tts-output && mkdir -p /tmp/kaggle-tts-output
kaggle kernels output icbm3112k/qwen3-tts-test -p /tmp/kaggle-tts-output
Step 5: 오디오/비디오 동기화
각 장면의 비디오 길이와 오디오 길이를 비교하고, 비디오 속도를 조정합니다:
import json, subprocesswith open("/tmp/kaggle-tts-output/durations.json") as f:
audio_durs = json.load(f)
scenes = ["Scene00_Title", "Scene01_Problem", ...] # 장면 목록
base = "/path/to/media/videos/script/720p30" # 렌더링된 비디오 경로
adjusted_dir = "/path/to/adjusted"
for s in scenes:
audio_d = audio_durs[s]
video_d = float(subprocess.run(
["ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", f"{base}/{s}.mp4"],
capture_output=True, text=True
).stdout.strip())
speed = video_d / audio_d # < 1 = 느리게 (오디오가 김), > 1 = 빠르게
# 비디오 속도 조정 + 나레이션 오디오 합성
subprocess.run([
"ffmpeg", "-y",
"-i", f"{base}/{s}.mp4", # 원본 비디오
"-i", f"/tmp/kaggle-tts-output/{s}.mp3", # 나레이션
"-filter_complex", f"[0:v]setpts={1/speed}*PTS[v]",
"-map", "[v]", "-map", "1:a",
"-c:v", "libx264", "-preset", "fast", "-crf", "23",
"-c:a", "aac", "-b:a", "128k",
"-shortest", "-movflags", "+faststart",
f"{adjusted_dir}/{s}.mp4"
])
Step 6: 최종 합성
# concat 파일 생성
for s in Scene00_Title Scene01_Problem ...; do
echo "file '${adjusted_dir}/${s}.mp4'" >> concat_final.txt
done병합
ffmpeg -y -f concat -safe 0 -i concat_final.txt -c copy final_with_narration.mp4
프로젝트 디렉토리 구조
project-name/
├── script.py # Manim 애니메이션 스크립트
├── plan.md # 대본/장면 계획
├── narration/ # 나레이션 대본 텍스트
│ ├── Scene00_Title.txt
│ └── ...
├── adjusted/ # 오디오 동기화된 장면별 비디오
├── final_with_narration.mp4 # 최종 산출물
└── media/ # Manim 자동 생성
└── videos/script/
├── 480p15/ # 드래프트
└── 720p30/ # 미디엄/프로덕션
품질 가이드
| 항목 | 권장값 | 비고 |
|------|--------|------|
| 렌더링 품질 | -qm (720p30) | 균형 잡힌 품질/속도 |
| 프로덕션 | -qh (1080p60) | 최종 배포용 |
| 오디오 비트레이트 | 128kbps AAC | 나레이션용 충분 |
| 비디오 CRF | 23 | 용량/품질 균형 |
| 장면당 나레이션 | 150자 이하 | 비디오-오디오 불일치 최소화 |
| 비디오 속도 조정 한계 | 0.5x ~ 1.2x | 이 범위 밖이면 대본 수정 필요 |
오디오-비디오 불일치 해결 전략
불일치가 심할 때(속도 조정이 0.5x 미만이거나 1.5x 이상):
- 나레이션 대본 단축 — 가장 좋은 방법. 핵심만 남기기
- 비디오 애니메이션 추가 — wait() 시간 늘리거나 장면 추가
- 분할 — 하나의 장면을 두 개로 나누어 나레이션 분배
⚠️ 함정 (Pitfalls)
- 한글 인용부호
"": 파이썬 문자열 구분자와 충돌. 반드시 제거하거나\"로 이스케이프 - Manim
def construct(self:: 괄호 빠진 문법 오류 자주 발생. 항상self)확인 - Kaggle PyTorch 버전:
torch==2.6.0+cu124고정 필수 (T4 호환) - 동시 Kaggle 커널: GPU 커널 동시 1개만 실행 가능
- 한글 폰트 누락:
font파라미터 미지정시 깨진 글자 출력 torchaudio.save()금지: 반드시scipy.io.wavfile사용- ffmpeg
-shortest: 오디오가 비디오보다 길 때 비디오 끝에서 자동 잘림. 속도 조정으로 해결 - concat 시 코덱 일치:
-c copy사용하려면 모든 파일 동일 코덱/해상도 필요
Related Skills / 관련 스킬
architecture-diagram
Generate dark-themed SVG diagrams of software systems and cloud infrastructure as standalone HTML files with inline SVG graphics. Semantic component colors (cyan=frontend, emerald=backend, violet=database, amber=cloud/AWS, rose=security, orange=message bus), JetBrains Mono font, grid background. Best suited for software architecture, cloud/VPC topology, microservice maps, service-mesh diagrams, database + API layer diagrams, security groups, message buses — anything that fits a tech-infra deck with a dark aesthetic. If a more specialized diagramming skill exists for the subject (scientific, educational, hand-drawn, animated, etc.), prefer that — otherwise this skill can also serve as a general-purpose SVG diagram fallback. Based on Cocoon AI's architecture-diagram-generator (MIT).