50k 스타 · 7.1k 포크 · MIT · Python 97.8%. 영어 원조 MoneyPrinter(소규모 스크립트)를 중국 개발자 harry0703이 MVC 아키텍처 풀스택 앱으로 재설계한 프로젝트. 한 줄 주제만 던지면 LLM이 대본을 쓰고 → TTS가 더빙하고 → Pexels/Pixabay에서 무료 영상을 다운받고 → Whisper가 자막을 박고 → BGM을 깔아 → 9:16 또는 16:9 mp4로 합성해주는 7단계 파이프라인이 통째로 들어있다. Streamlit Web UI + FastAPI REST API + Docker Compose 모두 기본 제공.
입력은 video_subject = "금전의 가치" 한 줄. 출력은 final-1.mp4 같은 1080×1920(세로 9:16) 또는 1920×1080(가로 16:9) 고화질 mp4. 중간 결과물(스크립트 텍스트, 음성 mp3, srt 자막, 다운로드된 소스 클립들)도 작업 폴더에 모두 저장된다.
"숏폼을 양산해서 TikTok·YouTube Shorts·중국 더우인(抖音)에 뿌리고 광고 수익을 노린다" — 이름의 Money Printer(돈 찍어내는 기계)가 이 워크플로우를 그대로 가리킨다. 다만 실제 수익화 여부와 무관하게, "긴 미디어 파이프라인을 코드로 어떻게 조립하는가"에 대한 매우 깔끔한 학습 자료다.
video_clip_duration도 정확히 5초로 잡혀 있다.원조 FujiwaraChoki/MoneyPrinter는 Selenium으로 브라우저를 띄워 더빙 사이트를 자동 조작하는 등 "붙여서 만든 PoC"에 가까웠다. MoneyPrinterTurbo는 이 아이디어를 가져와 완전히 새로 짠 풀스택 앱으로 만든 케이스다 — FastAPI 백엔드 + Streamlit 프론트 + Docker Compose + 모듈화된 서비스 계층까지.
OpenAI 한 종류만 쓰는 게 아니라 20개 이상 프로바이더를 어댑터 패턴으로 묶어놨다 — OpenAI, Moonshot(月之暗面), Azure, One-API, 通義千問(Qwen), Gemini, Ollama, DeepSeek, 文心一言(ERNIE), Pollinations, ModelScope, AiHubMix, AIMLApi, Grok, Groq, MiniMax, MiMo, Cloudflare Workers AI 등. 중국 사용자는 moonshot/deepseek으로 VPN 없이 쓰고, 로컬 운용자는 ollama로 완전 오프라인 운용까지 가능하다. (gpt4free는 선택 설치: uv sync --extra g4f + enable_g4f=true 필요)
"전압이 다른 나라들을 위해 110V·220V·240V 어댑터를 다 들고 다니는 멀티 충전기"와 같은 구조다. 어느 나라에 떨어져도(어떤 LLM API 환경이라도) 같은 본체(MoneyPrinterTurbo)가 동작한다.
같은 비즈니스 로직(app/services/task.py)을 Streamlit Web UI(webui/Main.py, 포트 8501)와 FastAPI REST(main.py, 포트 8080)가 동시에 호출한다. 사람이 클릭으로 쓸 수도 있고, 다른 시스템이 API로 호출해서 자동화할 수도 있다. docker-compose up 한 줄에 두 컨테이너가 동시에 뜬다.
저작권 문제를 피하려고 영상은 Pexels / Pixabay 공개 API에서 다운로드(둘 다 무료 + CC 라이선스), 음성은 edge-tts(Microsoft Edge 브라우저 내장 TTS, 완전 무료), 자막은 faster-whisper(로컬), BGM은 resource/songs/에 사전 적재된 음원. 결국 유료 API는 LLM 하나만(혹은 DeepSeek 무료 크레딧·g4f·Ollama로 완전 무료화 가능).
| 구분 | 라이브러리 | 역할 |
|---|---|---|
| API 서버 | fastapi==0.136.3 + uvicorn==0.32.1 | REST 엔드포인트, ASGI 비동기 서버. 포트 8080. |
| Web UI | streamlit==1.58.0 | 설정·결과 미리보기 화면. 포트 8501. |
| LLM 통합 | openai==2.24.0 ⚠️ 2.x 메이저업, litellm==1.86.2, google.generativeai==0.8.6, dashscope==1.20.14, g4f (선택 설치) | OpenAI/Gemini/Qwen 각각 네이티브 SDK 사용. litellm이 그 위에서 통합 추상화. openai 2.x는 1.x 대비 API 일부 변경 — 예제 코드 복붙 시 주의. |
| TTS 음성 | edge_tts==7.2.7, azure-cognitiveservices-speech==1.41.1 | Edge TTS 무료 + Azure Speech 유료 옵션. 100+ 음색 지원. |
| 자막 STT | faster-whisper==1.1.0 | 음성 mp3 → srt 자막. CTranslate2 백엔드로 일반 whisper 대비 4배 빠름. |
| 영상 편집 | moviepy==2.2.1 | 클립 자르기·합치기·자막 오버레이·BGM 믹싱. 내부적으로 ffmpeg 호출. |
| 오디오 처리 | pydub==0.25.1 | 음성 길이 측정, 포맷 변환. |
| 로깅 | loguru==0.7.3 | 표준 logging 대체. 한 줄로 컬러·파일 회전 로깅. |
| 상태 관리 | redis==5.2.0 (선택) | 여러 워커 간 작업 상태 공유. 단일 인스턴스라면 메모리 dict로 대체 가능(enable_redis=false). |
별도의 React/Vue 빌드 없이 Streamlit 단일 페이지로 끝낸다. webui/Main.py 안에 입력 폼·진행도 표시·결과 미리보기가 다 들어있다. 장점은 "파이썬만 알면 UI도 만든다"지만, 디자인 자유도는 낮다 — 이 프로젝트가 "데이터/AI 도구"임을 잘 보여주는 선택.
| 외부 의존 | 용도 | API 키 필요 |
|---|---|---|
| Pexels | 고화질 영상 소스(주력) | O — 무료 발급 |
| Pixabay | 대체 영상 소스 | O — 무료 발급 |
| HuggingFace | Whisper-large-v3 모델(~3GB) 다운로드 | X (중국에선 백업 네트 드라이브 링크 제공) |
| ImageMagick | moviepy의 자막 텍스트 렌더링 백엔드 | X — OS 설치 |
| FFmpeg | 실제 영상 인코딩 | X — moviepy가 자동 다운로드 시도 |
Dockerfile은 python:3.11-slim-bullseye를 베이스로, 시스템 패키지(git, imagemagick, ffmpeg)와 pip 의존성을 설치한다. 특이점: aliyun → tsinghua → 기본 데비안 미러를 순서대로 시도하는 3단계 재시도 로직이 들어있다 — 중국발 프로젝트라 네트워크 불안정성을 그라운드부터 고려한 흔적. docker-compose.yml은 webui·api 두 컨테이너를 같은 코드베이스에서 다른 명령으로 띄운다.
app/models/), 화면(webui/), 요청 핸들러(app/controllers/)를 분리해서 짜는 전통적 웹 아키텍처. MoneyPrinterTurbo는 여기에 비즈니스 로직 전담 Service Layer(app/services/)를 추가한 4계층 구조로 갔다 — 컨트롤러는 얇게, 실제 일은 서비스에서. README가 "완전한 MVC 아키텍처"를 자랑하는 이유.task.py의 start(task_id, params, stop_at="video") 함수가 전체 흐름의 심장이다. 단계별로 진행도를 갱신하면서 한 단계라도 실패하면 즉시 TASK_STATE_FAILED로 마킹하고 종료한다. 더 흥미로운 건 stop_at 인자 — "script"·"terms"·"audio"·"subtitle"·"materials" 중 하나를 넣으면 그 단계까지만 돌고 끝낸다. "대본만 뽑아줘"·"자막까지만" 같은 부분 호출이 그냥 된다.
app/services/llm.py(26KB)는 config.toml의 llm_provider 값을 보고 20종 SDK 중 하나를 동적으로 선택한다. 호출하는 쪽(task.py)은 llm.generate_script(...)만 호출하면 끝 — 안에서 OpenAI인지 Gemini인지 Ollama인지는 신경 쓸 필요가 없다.
리모컨 하나로 LG·삼성·소니 TV를 다 조작하는 통합 리모컨과 같다. 사용자(task.py)는 "전원 켜"만 누른다. 어댑터(llm.py 안의 분기)가 "지금 TV가 LG니까 LG 코드 전송" 같은 변환을 알아서 한다.
app/services/state.py가 "진행도와 결과"를 별도 객체로 관리한다. 기본은 메모리 dict, enable_redis=true로 켜면 Redis 백엔드로 자동 전환. WebUI는 이 state를 폴링하면서 진행 바를 그린다. 로직과 상태 저장소를 분리한 덕분에 단일 노드 / 멀티 노드 배포가 같은 코드로 가능.
app/models/schema.py에 모든 요청·응답이 Pydantic BaseModel로 정의되어 있다. VideoParams 하나에 25개 가까운 필드(주제·음성·BGM·자막 색·폰트 크기·외곽선 두께…)가 모두 모여있고, 같은 모델이 WebUI 입력 폼 / REST 요청 바디 / 내부 함수 인자로 그대로 흘러간다. FastAPI가 자동 생성하는 /docs 스웨거가 그대로 사용자 매뉴얼이 되는 이유.
① config.example.toml로 "무엇을 설정할 수 있나" 감 잡기 → ② app/models/schema.py로 데이터 형태 확인 → ③ app/services/task.py의 start()로 전체 흐름 따라가기 → ④ 관심 가는 단계(예: TTS면 voice.py)만 깊게 → ⑤ webui/Main.py로 UI가 어떻게 같은 모델을 쓰는지 확인.
긴 작업(영상 합성, 1~5분)을 동기 HTTP로 처리하면 클라이언트가 타임아웃된다. MoneyPrinterTurbo는 "즉시 task_id 반환 + 별도 폴링 엔드포인트" 패턴을 쓴다:
POST /api/v1/videos — 즉시 task_id 반환, 백그라운드에서 합성 시작GET /api/v1/tasks/{task_id} — 진행도 0~100 + 상태(PROCESSING/COMPLETE/FAILED) 폴링GET /tasks/{task_id}/final-1.mp4 — 완성된 영상 정적 서빙실습: main.py → app/asgi.py → app/router.py → app/controllers/v1/ 순서로 한 번 추적해보면 FastAPI의 라우팅·의존성 주입·BackgroundTasks 패턴이 한 번에 보인다.
webui/Main.py를 보면 st.text_input · st.slider · st.video · st.progress만으로 완성된 도구가 나온다. "React 안 쓰고도 며칠이면 사내 도구가 만들어진다"는 Streamlit의 핵심을 그대로 보여주는 사례.
app/services/video.py의 30KB 코드 안에 "클립 가로세로 비율 변경·여러 클립 이어붙이기·자막 오버레이·BGM 페이드인/아웃"이 다 들어있다. 특히 VideoConcatMode(random / sequential)와 VideoTransitionMode(Shuffle / FadeIn / FadeOut / SlideIn / SlideOut) 구현은 영상 자동화 입문에 좋은 레시피.
Microsoft Edge 브라우저가 내부적으로 쓰는 신경망 TTS를 비공식 라이브러리로 뽑아 쓴다. 100개+ 음색·언어 무료. app/services/voice.py(62KB!)에 음색 목록과 SSML 처리 코드가 통째로 들어있다.
OpenAI Whisper를 CTranslate2로 다시 컴파일한 버전. compute_type="int8"이면 CPU에서도 돈다. 음성 → 자막 타이밍 자동 정렬이 무료로 가능한 시대를 연 라이브러리.
"어떤 LLM이든 붙일 수 있게 만든다"는 사례. llm.py 내부 분기 구조를 자기 프로젝트에 그대로 가져다 쓸 수 있다.
| 구분 | 최소 | 권장 | 비고 |
|---|---|---|---|
| CPU | 4코어 | 8코어+ | moviepy 인코딩이 CPU 바운드. n_threads 옵션으로 병렬 조정 |
| RAM | 4GB | 8GB+ | Whisper-large-v3 사용 시 모델만으로 ~3GB |
| 디스크 | 10GB | 30GB+ | Whisper 모델 3GB + Pexels 캐시(영상당 수십 MB) + 결과물 |
| GPU | 불필요 | NVIDIA + CUDA | Whisper만 GPU 가속됨. moviepy는 CPU 전용. device="cuda", compute_type="float16" |
| OS | — | Windows 10+ / macOS 11+ / Linux | 경로에 한글·공백·특수문자 금지 (README 명시) |
| 네트워크 | — | 안정 + (중국이면 VPN) | Pexels·HuggingFace·OpenAI 모두 해외 트래픽 |
Linux에서 자막이 안 박힐 때 99%는 ImageMagick 보안 정책(/etc/ImageMagick-6/policy.xml)이 임시파일 접근을 막아서다. rights="none"을 rights="read|write"로 바꿔야 한다. Dockerfile은 이걸 빌드 시 자동으로 해주지만, 수동 설치 시엔 직접 수정 필요.
git clone → pip install -r requirements.txt → config.toml에서 llm_provider="deepseek" + DeepSeek API 키 + Pexels API 키 설정 → sh webui.sh → 브라우저 8501 접속 → 주제 "가을 단풍의 아름다움"으로 9:16 영상 1개 생성. 어디서 오래 걸리는지 로그로 측정해본다 (대본 / 영상 다운로드 / 합성 중 어디가 병목?).
python main.py로 API만 띄우고, Python requests로 POST /api/v1/videos를 5번 호출. 반환된 task_id를 폴링해 진행도 진행 바를 콘솔에 직접 그려본다. WebUI 없이 자동화하는 감을 잡는 게 목적.
app/services/llm.py에 자신이 좋아하는 LLM(예: Claude, Mistral, SiliconFlow)을 새 분기로 추가. config.toml에 키 섹션도 만든다. Anthropic Claude를 넣는 게 의외로 빠진 부분이라 PR감.
현재는 resource/songs/에서 랜덤 선택. 이걸 "주제가 '슬픔'이면 잔잔한 곡, '운동'이면 빠른 곡"으로 매칭하는 로직 추가. 곡 메타데이터 yaml을 따로 만들어두고 LLM에게 "이 주제에 어울리는 BGM 분위기 한 단어"를 물어보고 매칭하는 식.
app/services/material.py를 어댑터 패턴으로 리팩토링 → video_source = "sora" 같은 옵션을 추가 → API 호출 → 결과 mp4 다운로드 → 기존 파이프라인에 끼우기. 완성하면 "전혀 새로운 영상을 처음부터 만드는 비디오 공장"이 된다.
app/services/task.py의 7단계를 손으로 노트에 그려보기VideoParams를 직접 확장: language="ko" 옵션 추가 등video.py에서 자막 폰트·외곽선 두께·위치를 코드 단에서 조정해보기llm.py를 LiteLLM 기반으로 리팩토링 시도uvicorn이 대표적 ASGI 서버.completion(model="claude-3-5-sonnet")만 바꾸면 다른 모델로 갈아끼울 수 있다.uv sync --extra g4f로 별도 설치하고 config.toml에서 enable_g4f=true를 명시해야 사용 가능하다.st.session_state로.llm.py가 대표 사례.task_id로 사용. 충돌 확률은 사실상 0.set_* → with_*). 이 레포는 2.2.1을 쓴다 — 인터넷 예제 베끼다 깨질 수 있음.git clone → config.toml 복사 + DeepSeek + Pexels 키만 채우기 → docker compose up → localhost:8501POST /api/v1/videos "Try it out" → "주제만 채워서" 실행 → task_id 받기 → GET /api/v1/tasks/{id}로 진행도 보기.app/services/task.py의 start()에 print("[STEP X DONE]") 한 줄씩 끼워서 어디서 시간이 가장 걸리는지 직접 확인.