# harry0703/MoneyPrinterTurbo — codebase unpack

> Canonical page: https://codevetter.com/unpack/moneyprinterturbo

This is a deterministic Repo Unpack scan of harry0703/MoneyPrinterTurbo — a structural map, not a review, security audit, or maintainer-endorsed description.

- Scanned commit: 3d5f4e421927d61f3eac729cf4b711ac0b69d688 (2026-09-21)
- Last upstream commit: fix(webui): cache video cache stats as plain dict to stay pickle-safe
- Generated by codevetter 1.12.0 on 2026-09-22
- 177 files scanned · 3.2 MB

## Languages

- Python: 121 files
- JSON: 17 files
- YAML: 9 files
- Markdown: 6 files
- TOML: 3 files
- CSS: 1 files
- HTML: 1 files
- Shell: 1 files

## Repository structure

- test/ — 72 files
- app/ — 47 files
- webui/ — 17 files
- docs/ — 12 files
- .github/ — 6 files
- resource/ — 1 files

Probable source entrypoint: `main.py` (inferred from the tree, not upstream docs)

## Source layout — app/

- app/config/ — 2 files
- app/controllers/ — 8 files
- app/models/ — 5 files
- app/services/ — 26 files
- app/utils/ — 3 files
- app/__init__.py — 1 file
- app/asgi.py — 1 file
- app/router.py — 1 file

## Package

- pyproject.toml: moneyprinterturbo v1.3.7 — 6 declared dependencies

## Structural health signals

Average score 6.4/10 across 96 files; 45 hotspots. These are deterministic review leads, not proof of a bug.

## Recent history

- 3d5f4e421927 (2026-09-21) fix(webui): cache video cache stats as plain dict to stay pickle-safe
- 919170b05831 (2026-09-20) fix(api): parse subtitle enabled as boolean
- e0c9cb77ffd0 (2026-09-18) fix(asgi): normalize CORS origins (#1392)
- fdcf249298dc (2026-09-18) feat(i18n): add Catalan WebUI support
- 26edbbd9d9f2 (2026-09-17) fix(llm): replace the retired Groq default model with openai/gpt-oss-120b

## Overview

MoneyPrinterTurbo v1.3.7 is a Python 3.11+ application that generates finished short videos end-to-end: an LLM writes a script and material search terms, a TTS engine produces the voiceover, subtitles are built from TTS cues or Whisper, stock or AI-generated clips are downloaded per keyword, and MoviePy/FFmpeg renders final MP4s that can optionally be cross-posted to TikTok/Instagram/YouTube. It ships three frontends — a FastAPI service (port 8080), a Streamlit WebUI (port 8501), and a batch-capable argparse CLI — that all converge on one shared pipeline in app/services/task.py. Its distinctive traits are a very large provider matrix (~20 LLM providers via a registry, ~11 TTS engines, ~10 material sources including several paid text-to-video APIs with explicit charge-confirmation flags) and heavy hardening around config locking, path confinement, and task-state consistency for what is fundamentally a single-user local tool.

## System map

The system is a single-process Python app with three entrypoints that share one orchestration layer. main.py starts uvicorn serving the FastAPI app built in app/asgi.py; webui.sh/webui.bat/Dockerfile start `streamlit run webui/Main.py`; cli.py runs the pipeline synchronously and prints JSON. All routes under /api/v1 are registered in app/router.py and guarded by an optional x-api-key check. Task state lives in an in-memory dict or Redis depending on config, task scheduling in a bounded thread-backed queue, and artifacts on disk under storage/tasks/<task_id>. Deployment is a single Docker image exposing either the WebUI (8501) or API (8080).
- main.py is the API entrypoint: it calls uvicorn.run(app='app.asgi:app', host=config.listen_host, port=config.listen_port) with defaults 0.0.0.0:8080 from config.toml. — [main.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/main.py) [app/config/config.py#L572-574](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py#L572-L574)
- app/asgi.py get_application() builds the FastAPI instance, includes root_api_router, registers HttpException and RequestValidationError handlers, mounts StaticFiles at /tasks (storage/tasks) and / (resource/public), and installs middleware that requires verify_token for /tasks/* and rejects untrusted browser Origins. — [app/asgi.py#L235-288](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/asgi.py#L235-L288) [app/utils/utils.py#L110-143](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/utils/utils.py#L110-L143)
- app/router.py aggregates three routers: ping (/ping health check) plus the v1 video and llm controllers; app/controllers/v1/base.py new_router() stamps prefix '/api/v1' and attaches the verify_token dependency to every route. — [app/router.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/router.py) [app/controllers/v1/base.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/base.py) [app/controllers/ping.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/ping.py)
- webui/Main.py is an ~8,262-line Streamlit application that inserts the repo root into sys.path, loads i18n JSON locales, and submits generation through app/services/webui_task.py; webui.sh picks an interpreter (.venv → uv → PATH streamlit) and scans ports 8501-8599 for a free one. — [webui/Main.py#L23-98](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/webui/Main.py#L23-L98) [webui.sh](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/webui.sh) [app/services/webui_task.py#L128-195](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/webui_task.py#L128-L195)
- cli.py is a third entrypoint exposing every pipeline parameter as argparse flags, a --stop-at stage selector over ('script','terms','audio','subtitle','materials','video'), and a --batch-file mode accepting a JSON array or JSONL manifest capped at 100 tasks / 1 MiB. — [cli.py#L33-51](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L33-L51) [cli.py#L243-298](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L243-L298)
- app/services/task.py is the single shared pipeline: start() wraps _run_pipeline(), which all three frontends call, and converts unexpected exceptions into failed task states via _mark_task_failed. — [app/services/task.py#L1376-1740](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L1376-L1740)
- app/config/config.py loads config.toml (auto-copied from config.example.toml on first run, with a UTF-8 BOM retry path), exposes section dicts wrapped in _SynchronizedConfig, and serializes saves through a reentrant lock plus mkstemp+os.replace atomic writes with an EBUSY fallback for bind-mounted files. — [app/config/config.py#L29-84](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py#L29-L84) [app/config/config.py#L423-546](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py#L423-L546)
- Task state is pluggable: app/services/state.py picks MemoryState (dict + RLock) or RedisState (per-task Redis hashes, SCAN-paginated listing, Lua-scripted patch_task) based on config.app.enable_redis. — [app/services/state.py#L44-244](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/state.py#L44-L244)
- Task scheduling is pluggable: InMemoryTaskManager uses a bounded queue.Queue while RedisTaskManager pushes JSON jobs onto a Redis list 'task_queue' and maps function names through FUNC_MAP on dequeue; both enforce max_concurrent_tasks (default 5) and max_queued_tasks (default 100) with TaskQueueFullError → HTTP 429. — [app/controllers/manager/base_manager.py#L41-139](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/manager/base_manager.py#L41-L139) [app/controllers/manager/memory_manager.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/manager/memory_manager.py) [app/controllers/manager/redis_manager.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/manager/redis_manager.py) [app/controllers/v1/video.py#L43-69](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L43-L69)
- Docker packaging is a single python:3.11-slim-bullseye image that apt-installs git+ffmpeg (with Chinese-mirror fallback logic), pip-installs requirements.txt, and by default runs the Streamlit WebUI; docker-compose.yml runs two containers (webui on 127.0.0.1:8501, api on 127.0.0.1:8080) sharing the repo as a volume. — [Dockerfile](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/Dockerfile) [docker-compose.yml](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/docker-compose.yml)
- Filesystem layout is rooted at the repo: utils.storage_dir()/task_dir() create storage/{tasks,cache_videos,local_videos,bgm} on demand, while resource/{fonts,songs,public} hold shipped assets; task artifacts are per-task subdirectories. — [app/utils/utils.py#L89-143](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/utils/utils.py#L89-L143)
- Dependencies are declared in pyproject.toml (moviepy 2.2.1, streamlit 1.59.1, fastapi 0.136.3, edge-tts 7.2.7, faster-whisper, redis, litellm, google-genai, etc.) pinned by uv.lock; requirements.txt duplicates the list 'for legacy pip install support' and the project is configured package=false (not installable). — [pyproject.toml#L15-37](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/pyproject.toml#L15-L37) [requirements.txt](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/requirements.txt) [pyproject.toml#L77-79](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/pyproject.toml#L77-L79)
- docs/skill/SKILL.md + docs/skill/mpt_agent.py ship an agent-facing 'Skill': mpt_agent.py downloads/installs the project under ~/MoneyPrinterTurbo, prompts for missing credentials via exit code 10, and invokes cli.py — making the repo itself an installable tool for external AI agents. — [docs/skill/SKILL.md](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/docs/skill/SKILL.md) [docs/skill/mpt_agent.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/docs/skill/mpt_agent.py)

## Feature catalog

Features span a REST API for video/subtitle/audio generation and task management, a full Streamlit WebUI, and a CLI with batch mode. The provider surface is the headline: ~20 LLM providers, ~11 TTS engines, ~10 video material sources (three stock-footage search APIs, six paid text-to-video generators, an OpenAI-compatible image endpoint, and local files), two subtitle engines, three BGM modes plus two AI music services, and Upload-Post cross-posting. Supporting features include BGM/material upload endpoints, range-streaming and download endpoints, a 24h material search cache, Whisper transcription, i18n, task history, and an update checker.
- POST /api/v1/videos creates a full video task; POST /api/v1/subtitle and POST /api/v1/audio run the same pipeline truncated at intermediate stages via create_task(stop_at=...). — [app/controllers/v1/video.py#L181-238](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L181-L238)
- Task management endpoints: GET /api/v1/tasks (paginated), GET /api/v1/tasks/{id} (rewrites stored file paths into /tasks/* URIs using the configured endpoint), DELETE /api/v1/tasks/{id} which refuses busy tasks (409) and rmtree's the task directory. — [app/controllers/v1/video.py#L240-322](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L240-L322) [app/services/task.py#L106-123](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L106-L123)
- LLM utility endpoints: POST /api/v1/scripts (script generation), POST /api/v1/terms (material search terms), and POST /api/v1/social-metadata (titles/descriptions/hashtags per platform). — [app/controllers/v1/llm.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/llm.py) [app/services/llm.py#L736-832](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/llm.py#L736-L832) [app/services/llm.py#L1152-1214](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/llm.py#L1152-L1214)
- Media asset endpoints: GET/POST /api/v1/musics lists/uploads BGM (≤30 MB, UUID storage name, FFmpeg-validated) and GET/POST /api/v1/video_materials lists/uploads local clips/images into storage/local_videos with sanitized filenames. — [app/controllers/v1/video.py#L325-455](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L325-L455) [app/services/bgm.py#L270-338](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/bgm.py#L270-L338) [app/services/material_upload.py#L15-30](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/material_upload.py#L15-L30)
- GET /api/v1/stream/{file_path} returns HTTP 206 range-streamed video with single-range parsing and 416 handling; GET /api/v1/download/{file_path} returns FileResponse — both confined to the tasks directory by file_security.resolve_path_within_directory. — [app/controllers/v1/video.py#L132-178](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L132-L178) [app/controllers/v1/video.py#L457-508](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L457-L508) [app/utils/file_security.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/utils/file_security.py)
- The CLI supports per-stage execution (--stop-at), --batch-file JSON/JSONL manifests, and mandatory --confirm-{wavespeed,seedance,ofox,metaso-minimax,muapi}-charge flags before paid generation sources run. — [cli.py#L360-404](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L360-L404) [cli.py#L287-298](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L287-L298)
- LLM_PROVIDER_REGISTRY in app/models/llm_provider.py declares ~20+ providers (moonshot, openai, anthropic, gemini, deepseek, qwen, azure, volcengine, grok, minimax, mimo, cloudflare, modelscope, aihubmix, aimlapi, evolink, openrouter, api_route, ollama, claude_code, oneapi, litellm, groq, pollinations) with per-provider default models, base URLs, regional endpoints, and deprecated-value migration. — [app/models/llm_provider.py#L195-494](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/models/llm_provider.py#L195-L494) [config.example.toml#L233-417](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/config.example.toml#L233-L417)
- TTS dispatch in voice.py covers Azure v1 (edge-tts), Azure Speech v2, SiliconFlow, Gemini, MiMo, MiniMax, ElevenLabs, Chatterbox, Kokoro, Fish Audio, VoxCPM (with reference-audio cloning inputs), and a 'no-voice' silent-audio mode; provider identity is encoded in voice-name prefixes (gemini:, mimo:, elevenlabs:, chatterbox:, kokoro:, voxcpm:). — [app/services/voice.py#L371-460](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/voice.py#L371-L460) [app/services/voice.py#L597-728](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/voice.py#L597-L728) [cli.py#L477-488](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L477-L488)
- Video material sources include stock search (pexels, pixabay, coverr with a 24h result cache), paid text-to-video generation (wavespeed, volcengine_seedance, ofox, muapi, metaso_minimax, loomloom), openai_image (OpenAI-compatible /images/generations rendered into zoom clips), and local files. — [app/services/material.py#L1686-1805](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/material.py#L1686-L1805) [config.example.toml#L65-208](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/config.example.toml#L65-L208) [app/services/loomloom.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/loomloom.py)
- Subtitle generation supports 'edge' (from the TTS SubMaker timeline, sentence or word_by_word mode, pop_spring animation) and 'whisper' (faster-whisper transcription with model auto-download plus an LLM-free correct() alignment pass). — [app/services/task.py#L580-645](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L580-L645) [app/services/subtitle.py#L7-69](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/subtitle.py#L7-L69) [app/models/schema.py#L56-59](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/models/schema.py#L56-L59)
- Video rendering features: random/sequential concat, seven transition modes, clip speed 0.5-2.0, aspect ratios 9:16/16:9/1:1, cover/contain fit, video_count batch output, subtitle positioning (top/center/bottom/two_thirds_bottom/custom), and an FFmpeg encoder fallback chain (libx264, h264_nvenc/amf/qsv/mf/videotoolbox) that probes encoder availability at runtime. — [app/services/video.py#L271-540](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/video.py#L271-L540) [app/services/task.py#L883-1029](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L883-L1029) [cli.py#L176-231](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L176-L231) [config.example.toml#L436-440](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/config.example.toml#L436-L440)
- Background music modes: 'random'/'custom' from resource/songs and storage/bgm, plus AI-generated music via Sonilo (.m4a) or ElevenLabs (.mp3) registered in _VIDEO_MUSIC_PROVIDERS with per-provider prompt limits and graceful degradation to a warning when generation fails after the video is otherwise complete. — [app/services/task.py#L75-90](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L75-L90) [app/services/task.py#L970-1021](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L970-L1021) [app/services/video.py#L656-682](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/video.py#L656-L682)
- Cross-posting via upload-post.com publishes finished videos to tiktok/instagram/youtube with configurable privacy and made-for-kids flags; _CROSS_POST_SOCIAL_PLATFORMS maps upload-post platform ids to LLM social-metadata platform names so titles/descriptions are generated per platform. — [app/services/upload_post.py#L14-80](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/upload_post.py#L14-L80) [app/services/task.py#L66-71](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L66-L71) [config.example.toml#L471-494](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/config.example.toml#L471-L494)
- The WebUI offers i18n via 14 locale JSON files, a task history/manager panel with restore, per-task live log streaming via a 0.5s-refresh Streamlit fragment fed by a thread-filtered loguru sink, voice preview, an onboarding tour, and an async GitHub release update checker. — [webui/i18n/en.json](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/webui/i18n/en.json) [app/services/webui_task.py#L23-80](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/webui_task.py#L23-L80) [webui/Main.py#L1932-2043](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/webui/Main.py#L1932-L2043) [app/services/version_checker.py#L1-50](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/version_checker.py#L1-L50)
- Optional integrations: TwelveLabs Marengo term reranking (extra dependency `uv sync --extra twelvelabs`, skipped when match_materials_to_script is on) and LoomLoom Market SkillBots for multi-candidate script generation and paid video clips with quote/confirm lifecycle. — [app/services/twelvelabs.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/twelvelabs.py) [app/services/task.py#L344-352](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L344-L352) [app/services/loomloom.py#L1-33](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/loomloom.py#L1-L33) [pyproject.toml#L39-45](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/pyproject.toml#L39-L45)

## Data flow

A request becomes a pydantic VideoParams, gets a UUID task_id, a state record, and a storage/tasks/<task_id> workspace. The pipeline transforms subject→script→terms→audio→subtitle→materials→final MP4, writing intermediate files (script.json, audio.mp3, subtitle.srt, combined-N.mp4, final-N.mp4) into the task dir and progress/state into MemoryState or RedisState. Outputs leave via the /tasks static mount, /stream, /download, or cross-posting to upload-post.com. Configuration flows the other direction: config.toml → section dicts, with WebUI runtime writes funneled through a non-blocking pending-update queue and atomic file saves.
- Ingress: TaskVideoRequest (a VideoParams subclass) is validated by pydantic, dumped into a task record {task_id, request_id, params}, persisted via sm.state.update_task, then tm.start is scheduled through task_manager.add_task — with rollback of the state record if scheduling throws. — [app/controllers/v1/video.py#L202-238](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L202-L238) [app/models/schema.py#L91-161](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/models/schema.py#L91-L161)
- Script/terms stage: generate_script uses params.video_script verbatim or calls llm.generate_script; generate_terms calls llm.generate_terms (5 terms, or 8 in match-script-order mode) and optionally reranks through twelvelabs.rerank_terms_by_subject; results are persisted to <task_dir>/script.json via task_artifacts.write_script_data's atomic temp-file+os.replace write. — [app/services/task.py#L292-361](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L292-L361) [app/services/task_artifacts.py#L21-61](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task_artifacts.py#L21-L61)
- Audio stage: resolve_custom_audio_file confines submitted paths to the task directory unless allow_server_file_input=True (CLI only); otherwise voice.tts writes audio.mp3 and returns an edge-tts SubMaker timeline; audio_duration is measured from the written file, not cue end times. — [app/services/task.py#L364-577](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L364-L577) [app/services/voice.py#L995-1040](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/voice.py#L995-L1040)
- Subtitle stage: 'edge' provider serializes SubMaker cues to subtitle.srt (never auto-falling back to Whisper, to avoid surprise multi-GB model downloads); 'whisper' transcribes the audio file with faster-whisper and runs subtitle.correct() against the script. — [app/services/task.py#L580-645](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L580-L645) [app/services/subtitle.py#L23-80](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/subtitle.py#L23-L80)
- Materials stage: stock sources go through _search_videos_with_cache (24h per-term cache in material_cache, double-checked locking) then download_videos; paid generation sources take _download_videos_*_on_demand paths that generate clips one at a time and stop once audio_duration*video_count is covered; 'local' preprocesses user files; 'loomloom' executes a confirmed SkillBot run and persists run_id immediately since billing has already started. — [app/services/material.py#L1593-1805](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/material.py#L1593-L1805) [app/services/task.py#L648-728](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L648-L728) [app/services/material_cache.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/material_cache.py)
- Render stage: generate_final_videos loops video_count times calling video.combine_videos (→ combined-N.mp4), optionally calls the AI music provider's generate_bgm, then video.generate_video (muxing narration, subtitles, BGM → final-N.mp4); progress is written to state at ~5/10/20/30/40/50→100 milestones. — [app/services/task.py#L883-1029](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L883-L1029) [app/services/task.py#L1388-1630](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L1388-L1630) [app/services/video.py#L744-1209](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/video.py#L744-L1209)
- Egress: task state stores absolute file paths; GET /tasks/{id} converts them to /tasks/* URLs via _task_file_to_uri (which re-validates each path inside the task dir), and the StaticFiles mount plus /stream//download endpoints serve bytes from storage/tasks. — [app/controllers/v1/video.py#L107-129](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L107-L129) [app/controllers/v1/video.py#L258-287](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L258-L287) [app/asgi.py#L284-288](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/asgi.py#L284-L288)
- Config read path: load_config() → _cfg TOML dict → section dicts (app, azure, whisper, proxy, ui, and per-TTS sections); reads anywhere use config.<section>.get(...); app.redis_host is overridable via MPT_APP_REDIS_HOST/REDIS_HOST env vars. — [app/config/config.py#L460-586](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py#L460-L586)
- Config write path (WebUI): controls call update_config_nonblocking/delete_config_nonblocking which enqueue into _pending_config_updates keyed by id(section); a background 'mpt-config-flush' thread applies them when runtime_config_lock is free; save_config writes config.toml atomically. — [app/config/config.py#L91-271](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py#L91-L271)
- In Redis mode the task queue is a Redis list of JSON payloads; enqueue serializes VideoParams via model_dump and stores the function name, and dequeue re-validates against FUNC_MAP and current VideoParams — dropping stale entries while marking their tasks failed so they never appear stuck in 'processing'. — [app/controllers/manager/redis_manager.py#L14-130](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/manager/redis_manager.py#L14-L130)
- Cross-post flow: on success the task is marked COMPLETE with cross_post_state='pending' and cross_post_owner='hostname:pid:uuid'; _schedule_cross_post submits to a bounded ThreadPoolExecutor(max_workers=2) gated by a BoundedSemaphore, and terminal state is written via patch_task so a deleted task is never resurrected. — [app/services/task.py#L43-65](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L43-L65) [app/services/task.py#L1321-1375](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L1321-L1375) [app/services/task.py#L1644-1700](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L1644-L1700)
- Failure propagation: every stage funnels into _mark_task_failed(state=-1, failed_stage, error) which preserves remote IDs (volcengine_seedance_task_id, ofox_task_id, metaso_minimax_task_id, muapi_task_id, loomloom_run_id) so paid remote jobs remain recoverable from provider dashboards. — [app/services/task.py#L243-289](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L243-L289) [app/services/task.py#L718-806](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L718-L806)

## Behavior traces

Three flows were traced end-to-end. API startup goes main.py → asgi app construction → lifespan recovery. The primary action (video generation) is identical across frontends: a state record is created before scheduling, _run_pipeline walks preflight→script→terms→audio→subtitle→materials→video, and cross-posting is scheduled only after the task is COMPLETE. Persistence flows through two channels — task state (memory dict or Redis hashes) and task artifacts (atomic JSON writes into storage/tasks/<id>) — plus config.toml for settings.
- API startup trace: `python main.py` → uvicorn.run('app.asgi:app') → module-level get_application() builds routes/middleware/static mounts → parse_cors_allowed_origins(CORS_ALLOWED_ORIGINS) → lifespan logs startup, warns when app.api_key is empty, and calls task_service.recover_interrupted_cross_posts() to fail orphaned pending/processing cross-posts whose owner process no longer exists. — [main.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/main.py) [app/asgi.py#L21-47](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/asgi.py#L21-L47) [app/asgi.py#L255-288](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/asgi.py#L255-L288) [app/services/task.py#L1101-1147](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L1101-L1147)
- First-run config trace: importing app.config.config → load_config() → if config.toml is a directory it is deleted, if missing it is copied from config.example.toml → _load_toml_config parses with a utf-8-sig BOM fallback → section dicts are wrapped in _SynchronizedConfig → ffmpeg_path (if set) is exported as IMAGEIO_FFMPEG_EXE for imageio/moviepy. — [app/config/config.py#L423-473](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py#L423-L473) [app/config/config.py#L549-590](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py#L549-L590)
- API video-generation trace: POST /api/v1/videos → verify_token dependency → create_task → utils.get_uuid() → sm.state.update_task(task_id) → task_manager.add_task(tm.start, ...) → TaskManager.execute_task spawns a thread running run_task → tm.start → _run_pipeline → on completion update_task(COMPLETE, progress=100, videos=...) → optional _schedule_cross_post. — [app/controllers/v1/video.py#L181-238](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L181-L238) [app/controllers/manager/base_manager.py#L54-96](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/manager/base_manager.py#L54-L96) [app/services/task.py#L1703-1740](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L1703-L1740)
- Pipeline order inside _run_pipeline: (0) preflight — provider is_enabled() checks for seedance/ofox/metaso_minimax/muapi/openai_image, music-provider key+prompt-length+access validation, then utils.check_ffmpeg_ready() unless stop_at is script/terms; (1) generate_script → (2) generate_terms (skipped for local source) → save_script_data → (3) generate_audio → (4) generate_subtitle → (5) get_video_materials → (6) generate_final_videos → (7) mark COMPLETE + schedule cross-post. — [app/services/task.py#L1390-1700](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L1390-L1700) [app/utils/utils.py#L189-235](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/utils/utils.py#L189-L235)
- stop-at truncation trace: each stage boundary checks stop_at and returns early with COMPLETE state — 'script' returns {script}, 'terms' returns {script,terms}, 'audio' returns {audio_file,audio_duration}, 'subtitle' returns {subtitle_path}, 'materials' returns {materials}; the API exposes this as the /subtitle and /audio endpoints and the CLI as --stop-at. — [app/services/task.py#L1512-1611](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L1512-L1611) [app/controllers/v1/video.py#L188-199](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L188-L199) [cli.py#L360-364](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L360-L364)
- WebUI generation trace: Main.py button handler → _prepare_generation_task() → webui_task.submit_generation() writes state record before scheduling → InMemoryTaskManager(max_concurrent_tasks=1) → _run_generation attaches a thread-filtered loguru sink, acquires config.runtime_config_lock(), calls tm.start → a @st.fragment(run_every=0.5s) polls webui_task.get_task_logs() to stream logs to the page. — [app/services/webui_task.py#L19-29](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/webui_task.py#L19-L29) [app/services/webui_task.py#L53-195](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/webui_task.py#L53-L195) [webui/Main.py#L1932-2043](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/webui/Main.py#L1932-L2043) [webui/Main.py#L8181](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/webui/Main.py#L8181)
- CLI trace: `python cli.py ...` → _force_utf8_console() → run_cli() → parse_args() (custom argparse types validate colors, speeds, UUID task ids, subtitle enums) → params built by merging CLI flags over [ui] config defaults → tm.start(allow_server_file_input=True) → prints one JSON result object to stdout; batch mode merges each manifest object over CLI defaults and runs tasks sequentially. — [cli.py#L243-298](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L243-L298) [cli.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py)
- Failure trace: any unhandled exception inside _run_pipeline is caught in start(), logged, and converted by _mark_task_failed into state=-1 with failed_stage and error — visible via GET /tasks/{id}; the CLI maps a failed result to exit code 1, argument/manifest errors to exit code 2. — [app/services/task.py#L1720-1740](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L1720-L1740) [app/services/task.py#L243-289](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L243-L289) [cli.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py)
- Task-deletion trace: DELETE /api/v1/tasks/{id} → sm.state.get_task → tm.is_task_busy refuses deletion while state==PROCESSING or cross_post_state in {pending,processing} (409) → shutil.rmtree(storage/tasks/<id>) → sm.state.delete_task. — [app/controllers/v1/video.py#L290-322](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L290-L322) [app/services/task.py#L106-123](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L106-L123)
- Cross-post recovery trace: each cross-post future is registered in _cross_post_futures keyed by task_id with owner 'hostname:pid:uuid' persisted in task state; on startup or deletion checks, _is_cross_post_owner_alive inspects the owner PID (Win32 OpenProcess on Windows, os.kill(pid,0) elsewhere, conservative-alive for foreign hosts) and stranded states are marked failed. — [app/services/task.py#L126-240](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L126-L240) [app/services/task.py#L1075-1147](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L1075-L1147)

## Testing signals

Testing is unusually thorough for this genre: ~70 test files under test/ mixing pytest functions and unittest.TestCase, executed by pytest in CI on Python 3.11 and 3.13 with a real Redis service, plus a Windows smoke job. Branch coverage over app/, cli.py, main.py, webui/, and docs/skill/ is gated at ≥70%. Ruff lint runs in CI (3.11 only) and compileall checks every source tree. Live provider calls are opt-in via MPT_RUN_INTEGRATION_TESTS, so CI coverage is unit/integration-level with mocks rather than true end-to-end renders.
- Test layout: test/test_main.py plus ~70 files under test/services/ named test_<domain>.py (task, task_manager, cli, controllers, voice, video, material, providers, webui_* helpers); test/README.md documents the pytest commands and conventions. — [test/README.md](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/README.md) [test/services/test_task.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/services/test_task.py) [test/test_main.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/test_main.py)
- CI (.github/workflows/ci.yml) runs on ubuntu-latest across Python 3.11 and 3.13 with a redis:7-alpine service (MPT_TEST_REDIS_HOST/PORT/DB env), installs via `uv sync --frozen`, compiles all sources, ruff-checks on 3.11, and runs `coverage run -m pytest -q test`. — [.github/workflows/ci.yml](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/.github/workflows/ci.yml)
- Coverage gate: [tool.coverage] uses branch=true over source=['app','cli','webui','main','docs/skill'] with fail_under=70 — the comment notes measured branch coverage exceeds 70.4% on both Python versions. — [pyproject.toml#L58-72](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/pyproject.toml#L58-L72)
- A windows-latest smoke job runs a curated subset (test_config, test_state, test_task, test_task_manager, test_upload_post, test_controller_video, test_webui_task, test_webui_headless_task_actions) — implying the full suite is not guaranteed Windows-clean. — [.github/workflows/ci.yml](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/.github/workflows/ci.yml)
- test/services/test_cli.py (~106 test functions) and test/services/test_task.py (~62) are the largest suites, covering CLI arg validation, manifest handling, and pipeline stage behavior — consistent with cli.py and task.py being the most defect-sensitive surfaces. — [test/services/test_cli.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/services/test_cli.py) [test/services/test_task.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/services/test_task.py)
- Security behavior is directly tested: test_api_authentication.py, test_asgi_cors.py, and test_asgi_static_files.py exercise the x-api-key check, Origin rejection middleware, and /tasks static-mount protection. — [test/services/test_api_authentication.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/services/test_api_authentication.py) [test/services/test_asgi_cors.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/services/test_asgi_cors.py) [test/services/test_asgi_static_files.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/services/test_asgi_static_files.py)
- test_main.py verifies the uvicorn entrypoint wiring by mocking uvicorn.run and asserting host/port/reload/log_level — entrypoint correctness without binding a socket. — [test/test_main.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/test_main.py)
- Live provider tests are skipped by default and gated behind MPT_RUN_INTEGRATION_TESTS=1 with real credentials, per test/README.md — so provider adapters (LLM/TTS/material APIs) are largely exercised through mocks. — [test/README.md#L40-42](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/README.md#L40-L42)
- Redis-specific behavior (atomic patch_task Lua semantics, stale-queue-entry dropping, state recovery) is covered by test_state.py, test_task_manager.py, and friends that run against the CI Redis service. — [test/services/test_state.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/services/test_state.py) [test/services/test_task_manager.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/services/test_task_manager.py) [app/controllers/manager/redis_manager.py#L50-130](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/manager/redis_manager.py#L50-L130)
- WebUI internals are tested headlessly: ~20 test_webui_*.py files cover settings transfer, task history, log streaming, provider dropdowns, and i18n — but nothing renders an actual browser or end-to-end ffmpeg video in CI. (inferred) — [test/services/test_webui_task.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/services/test_webui_task.py) [test/services/test_webui_startup.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/services/test_webui_startup.py) [test/services/test_webui_i18n.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/services/test_webui_i18n.py)
- Lint scope: ruff checks app, cli.py, main.py, webui, test, docs/skill with a per-file ignore for E402 in webui/Main.py (the sys.path insertion must precede app imports). — [pyproject.toml#L54-56](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/pyproject.toml#L54-L56) [.github/workflows/ci.yml](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/.github/workflows/ci.yml)
- Release pipeline is separate: .github/workflows/docker-ghcr.yml builds multi-arch Docker images to ghcr.io/harry0703/moneyprinterturbo on pushes to main, v* tags, and dispatch — tests do not gate image publication beyond sharing the same push. — [.github/workflows/docker-ghcr.yml](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/.github/workflows/docker-ghcr.yml)

## Risk map

The largest risk is the default-open posture: empty api_key means no authentication while listen_host defaults to 0.0.0.0, exposing task creation, file uploads, and artifact downloads to the LAN. Redis state uses ast.literal_eval on stored values inside a documented trust boundary. Several code paths shell out (ffmpeg probing of uploaded media, the claude_code provider spawning the local claude CLI). Paid text-to-video providers create billable remote tasks — guarded by CLI confirm flags but only a config/api-key check on the API path. The 8k-line webui/Main.py and duplicated requirements.txt are maintainability hotspots.
- Authentication is opt-in: when app.api_key is empty verify_token returns immediately and every /api/v1 route plus /tasks downloads are open; combined with listen_host='0.0.0.0' default, an unconfigured instance is LAN-exposed (the code only logs a warning at startup). — [app/controllers/base.py#L47-98](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/base.py#L47-L98) [config.example.toml#L10-26](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/config.example.toml#L10-L26) [app/asgi.py#L26-37](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/asgi.py#L26-L37)
- RedisState._convert_to_original_type applies ast.literal_eval to values read from Redis; the class docstring explicitly documents Redis as a trust boundary — a Redis DB writable by other tenants could inject crafted task records. — [app/services/state.py#L98-107](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/state.py#L98-L107) [app/services/state.py#L208-228](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/state.py#L208-L228)
- Uploaded media is probed by FFmpeg/ffprobe subprocesses (MATERIAL_VALIDATION_TIMEOUT_SECONDS=120) and PIL for images — attacker-controlled files reach the media toolchain; mitigations are extension whitelist, size caps (200MB video/20MB image), UUID storage names, and filename character sanitization. — [app/services/material_upload.py#L15-80](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/material_upload.py#L15-L80) [app/controllers/v1/video.py#L419-455](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L419-L455)
- The claude_code LLM provider shells out to the locally installed `claude` CLI; build_claude_code_env scrubs conflicting ANTHROPIC_*/CLAUDE_CODE_USE_* env vars and Docker deployment requires injecting CLAUDE_CODE_OAUTH_TOKEN — a subscription credential — into the container environment. — [app/services/llm.py#L84-161](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/llm.py#L84-L161) [docker-compose.claude.yml](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/docker-compose.claude.yml) [config.example.toml#L379-393](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/config.example.toml#L379-L393)
- Paid material sources (wavespeed, volcengine_seedance, ofox, muapi, metaso_minimax, loomloom) create billable remote tasks; the CLI requires --confirm-*-charge flags and WebUI requires confirmation, but the HTTP API path only checks is_enabled() — an unauthenticated caller could spend money if api_key is unset. — [cli.py#L365-404](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L365-L404) [app/services/task.py#L1390-1447](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L1390-L1447) [app/services/material.py#L1724-1782](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/material.py#L1724-L1782)
- config.toml persists all provider API keys in plaintext at repo root and is rewritten atomically by save_config on WebUI interactions; config.example.toml warns not to commit it, and logs deliberately avoid printing keys (material._redact_secret, llm._sanitize_error_message). — [app/config/config.py#L476-546](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py#L476-L546) [config.example.toml#L1-3](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/config.example.toml#L1-L3) [app/services/material.py#L193-226](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/material.py#L193-L226)
- webui/Main.py is a single 8,262-line module mixing i18n, settings persistence, task history, upload handling, and generation orchestration — a high blast-radius file where any edit risks regressions across many features. — [webui/Main.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/webui/Main.py)
- Duplicate dependency manifests: requirements.txt mirrors pyproject.toml 'for legacy pip' while Docker builds use requirements.txt and CI uses uv.lock — version drift between the two lists would produce different runtime vs tested environments. — [requirements.txt](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/requirements.txt) [pyproject.toml#L15-37](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/pyproject.toml#L15-L37) [Dockerfile#L83-92](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/Dockerfile#L83-L92)
- Dockerfile runs `chmod 777 /MoneyPrinterTurbo` and mounts the whole repo into containers; combined with Streamlit's --server.enableCORS=True in the launch command, container posture is more permissive than the API's own Origin checks. — [Dockerfile#L8](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/Dockerfile#L8) [Dockerfile#L102](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/Dockerfile#L102) [docker-compose.yml](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/docker-compose.yml)
- VideoParams field defaults (subtitle_position, custom_position, subtitle display/animation) read config.ui at class-definition/import time — a stale or invalid config.toml value is frozen into the pydantic model before validation can run; cli.py documents this and keeps its own fallback constants. — [app/models/schema.py#L140-149](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/models/schema.py#L140-L149) [cli.py#L24-32](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L24-L32)
- Cross-post process-liveness detection uses os.kill(pid, 0) on POSIX and a Win32 OpenProcess/GetExitCodeProcess path — best-effort heuristics where a PID reuse could keep a dead task marked busy; foreign-host owners are conservatively treated as alive. — [app/services/task.py#L148-240](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L148-L240)
- save_config has an EBUSY fallback that writes config.toml in place when atomic replace fails (Docker bind-mounted single file) — a crash mid-write there could leave a truncated config. — [app/config/config.py#L476-546](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py#L476-L546)

## Extension points

The codebase is deliberately registry/dispatch oriented. New LLM providers plug into LLM_PROVIDER_REGISTRY plus (only for non-OpenAI-compatible protocols) an adapter branch in llm._generate_response. New video material sources extend the download_videos dispatch plus the CLI's _CLI_VIDEO_SOURCES and mpt_agent's SUPPORTED_SOURCES. New AI-music providers slot into _VIDEO_MUSIC_PROVIDERS. New TTS engines add is_*_voice detectors and a _single_tts branch. State and queue backends are subclassable, routes extend via new_router, and UI strings via i18n JSON files.
- LLM providers: append an LLMProviderSpec to LLM_PROVIDER_REGISTRY (provider_id, defaults, endpoints, extra_fields); OpenAI-compatible providers need no adapter code — only gemini, qwen, azure, claude_code, litellm have dedicated branches in _generate_response. — [app/models/llm_provider.py#L29-195](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/models/llm_provider.py#L29-L195) [app/services/llm.py#L257-331](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/llm.py#L257-L331)
- Video material sources: add a branch in material.download_videos plus the source id in cli.py's _CLI_VIDEO_SOURCES, docs/skill/mpt_agent.py's SUPPORTED_SOURCES (kept aligned by comment), and config.example.toml keys; WebUI-specific sources like loomloom bypass download_videos entirely via get_video_materials in task.py. — [app/services/material.py#L1686-1805](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/material.py#L1686-L1805) [cli.py#L37-51](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L37-L51) [docs/skill/mpt_agent.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/docs/skill/mpt_agent.py) [app/services/task.py#L648-728](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L648-L728)
- AI music providers: register in task.py's _VIDEO_MUSIC_PROVIDERS dict — a service module implementing is_enabled() and generate_bgm() plus error_type/suffix/warning_code/display_name metadata; orchestration, zero-volume short-circuit, and failure degradation are shared. — [app/services/task.py#L72-103](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L72-L103)
- TTS engines: add an is_<name>_voice() detector on the voice-name prefix and a dispatch branch in _single_tts; voice lists surface via get_<name>_voices() helpers consumed by the WebUI. — [app/services/voice.py#L379-460](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/voice.py#L379-L460) [app/services/voice.py#L597-728](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/voice.py#L597-L728)
- Subtitle engines: config.app.subtitle_provider selects among 'edge', 'whisper', or empty inside task.generate_subtitle — adding a provider means a new branch there plus a service module like subtitle.py. — [app/services/task.py#L580-645](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L580-L645) [config.example.toml#L426-429](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/config.example.toml#L426-L429)
- State backends: subclass BaseState (update_task/get_task/get_all_tasks/patch_task/delete_task) and switch the singleton at the bottom of state.py; queue backends subclass TaskManager's create_queue/enqueue/dequeue/is_queue_empty/queue_size. — [app/services/state.py#L24-41](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/state.py#L24-L41) [app/services/state.py#L231-244](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/state.py#L231-L244) [app/controllers/manager/base_manager.py#L41-139](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/manager/base_manager.py#L41-L139)
- API surface: create a router with controllers/v1/base.new_router(dependencies=[Depends(base.verify_token)]) and include it in app/router.py — the pattern automatically yields /api/v1 prefix, V1 tag, and auth. — [app/controllers/v1/base.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/base.py) [app/router.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/router.py) [app/controllers/v1/llm.py#L16-18](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/llm.py#L16-L18)
- Cross-post platforms: extend upload_post_platforms config values plus the _CROSS_POST_SOCIAL_PLATFORMS map in task.py that translates platform ids into LLM social-metadata platform names. — [app/services/task.py#L66-71](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L66-L71) [app/services/upload_post.py#L30-35](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/upload_post.py#L30-L35) [config.example.toml#L477-478](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/config.example.toml#L477-L478)
- UI languages: drop a new locale JSON into webui/i18n/ — utils.load_locales scans the directory at startup; locale keys like llm_provider_label.<id> are referenced by the provider registry. — [webui/i18n/en.json](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/webui/i18n/en.json) [app/utils/utils.py#L535-547](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/utils/utils.py#L535-L547) [app/models/llm_provider.py#L58-79](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/models/llm_provider.py#L58-L79)
- Config schema: add keys under config.example.toml sections and read them via config.<section>.get(); entirely new sections require adding a dict to save_config's serialization list or they will be dropped on save. — [app/config/config.py#L492-504](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py#L492-L504) [config.example.toml](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/config.example.toml)
- Pipeline parameters: new VideoParams fields automatically flow into API request models (TaskVideoRequest subclasses VideoParams) and CLI batch manifests (unknown fields rejected, known fields merged), but CLI single-task flags must be added manually to parse_args. — [app/models/schema.py#L255-256](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/models/schema.py#L255-L256) [cli.py#L287-298](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L287-L298)
- FFmpeg encoder options are config-driven: app.video_codec accepts libx264/h264_nvenc/h264_amf/h264_qsv/h264_mf/h264_videotoolbox with runtime probing and automatic fallback — new encoders plug into _get_effective_video_codec's probe path. — [app/services/video.py#L282-365](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/video.py#L282-L365) [config.example.toml#L436-440](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/config.example.toml#L436-L440)

## Contributor handoff

Start at app/services/task.py (_run_pipeline is the shared spine), app/models/schema.py (VideoParams), and app/config/config.py (global config + locking). All three frontends must stay behaviorally identical — comments throughout document past drift bugs where a value list or check existed in only one entrypoint. Tests run via uv + pytest with a 70% branch-coverage floor; ruff lints the same file set as coverage. Preserve the security invariants: path confinement, optional auth, no secrets in logs, and never resurrecting deleted task records.
- Read first: app/services/task.py (pipeline + failure semantics), app/models/schema.py (VideoParams and request models), app/config/config.py (config load/save/locks), cli.py's top constant block, and the relevant section of webui/Main.py before editing any pipeline-adjacent code. — [app/services/task.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py) [app/models/schema.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/models/schema.py) [app/config/config.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py)
- Test commands: `uv sync --frozen` then `uv run python -X utf8 -m pytest -q test`; coverage parity with CI is `coverage run -m pytest -q test` + `coverage report` (fail_under=70, branch mode); lint with `ruff check app cli.py main.py webui test docs/skill`. — [test/README.md#L14-38](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/test/README.md#L14-L38) [pyproject.toml#L58-72](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/pyproject.toml#L58-L72) [.github/workflows/ci.yml](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/.github/workflows/ci.yml)
- Keep value lists synchronized across cli.py constants (_CLI_VIDEO_SOURCES, _SUBTITLE_POSITION_VALUES, _CLI_MUSIC_BGM_TYPES), app/models/schema.py enums, app/services/video.py render branches, webui/Main.py dropdowns, and docs/skill/mpt_agent.py SUPPORTED_SOURCES — inline comments explicitly cite past bugs caused by drift. — [cli.py#L33-51](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L33-L51) [cli.py#L176-202](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L176-L202) [docs/skill/mpt_agent.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/docs/skill/mpt_agent.py)
- Any new file-serving or file-accepting endpoint must go through file_security.resolve_path_within_directory and _sanitize_upload_filename-style normalization — never join user paths directly; the /tasks StaticFiles mount is guarded by middleware, not router dependencies. — [app/utils/file_security.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/utils/file_security.py) [app/controllers/v1/video.py#L72-97](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py#L72-L97) [app/asgi.py#L258-275](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/asgi.py#L258-L275)
- Task state writes must use sm.state.update_task (create/overwrite) vs patch_task (merge-only, safe against deleted tasks) correctly — redis_manager and cross-post code rely on patch_task's Lua atomicity to avoid resurrecting deleted tasks. — [app/services/state.py#L81-90](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/state.py#L81-L90) [app/services/state.py#L185-202](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/state.py#L185-L202) [app/controllers/manager/redis_manager.py#L89-128](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/manager/redis_manager.py#L89-L128)
- WebUI config writes must use update_config_nonblocking/delete_config_nonblocking/try_save_config — direct config.toml or config.<section> writes bypass the pending-update queue and can deadlock a page against runtime_config_lock held by a running task. — [app/config/config.py#L91-308](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py#L91-L308) [webui/Main.py#L309-341](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/webui/Main.py#L309-L341)
- Never log credentials: material.py's _redact_secret/_redact_request_error, llm.py's _sanitize_error_message, and the asgi lifespan's 'log only that auth is enabled' pattern are deliberate conventions to extend, not bypass. — [app/services/material.py#L193-226](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/material.py#L193-L226) [app/services/llm.py#L187-201](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/llm.py#L187-L201) [app/asgi.py#L26-37](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/asgi.py#L26-L37)
- Custom audio file paths are intentionally asymmetric: HTTP/WebUI callers are confined to the task directory; only the CLI passes allow_server_file_input=True — do not widen this for API callers. — [app/services/task.py#L364-420](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L364-L420) [cli.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py)
- VideoParams defaults that read config.ui are frozen at import time; changing [ui] handling must account for the fact that cli.py deliberately duplicates these defaults to keep CLI behavior stable under invalid saved values. — [app/models/schema.py#L140-149](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/models/schema.py#L140-L149) [cli.py#L24-32](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/cli.py#L24-L32)
- Edge subtitle failures must NOT auto-fallback to Whisper (would trigger a multi-GB model download); Whisper loads only on explicit subtitle_provider='whisper' — preserve this when touching subtitle logic. — [app/services/task.py#L600-635](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py#L600-L635) [config.example.toml#L426-429](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/config.example.toml#L426-L429)
- Code comments are predominantly Chinese (with some English); docstrings and comments carry unusually detailed rationale for past fixes — match this style and keep the explanations when editing. (inferred) — [app/services/task.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/services/task.py) [app/config/config.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py) [app/controllers/v1/video.py](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/v1/video.py)
- Dependencies: pyproject.toml is the source of truth pinned by uv.lock, but requirements.txt must be manually kept in sync for Dockerfile/legacy pip builds — check both when bumping. — [pyproject.toml#L1-3](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/pyproject.toml#L1-L3) [requirements.txt#L1](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/requirements.txt#L1) [Dockerfile#L83-92](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/Dockerfile#L83-L92)
- Edge cases documented in code that are easy to regress: config.toml may be a directory (load_config rmtree's it), TOML may have repeated UTF-8 BOMs, queue limits may arrive as TOML strings/floats (handled by _coerce_task_limit), and dequeue must never throw inside the task_done→check_queue chain. — [app/config/config.py#L423-473](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/config/config.py#L423-L473) [app/controllers/manager/base_manager.py#L12-38](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/manager/base_manager.py#L12-L38) [app/controllers/manager/redis_manager.py#L50-104](https://github.com/harry0703/MoneyPrinterTurbo/blob/3d5f4e421927d61f3eac729cf4b711ac0b69d688/app/controllers/manager/redis_manager.py#L50-L104)

Source repository: https://github.com/harry0703/MoneyPrinterTurbo
Full evidence corpus: https://github.com/Codevetter/codevetter/tree/main/benchmarks/repo-unpacks/moneyprinterturbo.json

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
