1. Architecture Overview
MoneyPrinterTurbo is an automated short-video generation platform that orchestrates LLM script writing, stock footage retrieval, text-to-speech synthesis, subtitle generation, and video compositing into a single pipeline. It exposes two user interfaces: a Streamlit-based WebUI for interactive use and a FastAPI REST API for programmatic integration.
The system is a monolithic Python application with clear internal layering: a configuration layer reads TOML settings, a service layer implements the core pipeline stages, a controller layer exposes HTTP endpoints, and a WebUI layer provides the graphical interface. State management is pluggable between in-memory storage and Redis.
Architecture Diagram
┌─────────────────────────────────────────────────────┐
│ User / Client │
│ Browser (WebUI) │ HTTP Client (API) │
└────────────┬─────────────┴──────────┬────────────────┘
│ │
┌───────▼───────┐ ┌───────▼────────┐
│ Streamlit App │ │ FastAPI (ASGI) │
│ webui/Main.py │ │ app/asgi.py │
└───────┬───────┘ │ app/router.py │
│ └───────┬────────┘
│ │
└───────────┬───────────┘
│
┌──────────▼──────────┐
│ Service Layer │
│ app/services/ │
│ ├── task.py │ ← Pipeline orchestrator
│ ├── llm.py │ ← LLM providers (14)
│ ├── material.py │ ← Stock footage search/download
│ ├── voice.py │ ← TTS engines (4)
│ ├── subtitle.py │ ← Transcription + SRT
│ ├── video.py │ ← MoviePy + ffmpeg compositing
│ └── state.py │ ← Memory or Redis state
└──────────┬──────────┘
│
┌─────────┬───────┴───────┬──────────┐
▼ ▼ ▼ ▼
External Stock APIs TTS APIs ffmpeg
LLMs (Pexels, (edge_tts, (binary)
(14+) Pixabay) Azure,
SiliconFlow,
Gemini)
Note
Both the Streamlit WebUI and the FastAPI API funnel into the same
task.py:start()pipeline. There is no divergence in video generation logic between the two interfaces -- only the entry point differs.
Component Inventory
| Component | Path | Role |
|---|---|---|
| FastAPI app | app/asgi.py |
ASGI application, CORS middleware, static file mounts |
| Router | app/router.py |
Aggregates v1 video + LLM routers |
| V1 Video API | app/controllers/v1/video.py |
Task CRUD, BGM management, material uploads |
| V1 LLM API | app/controllers/v1/llm.py |
Script and search-term generation endpoints |
| Health check | app/controllers/ping.py |
GET /ping returns "pong" |
| Auth module | app/controllers/base.py |
verify_token() checks X-API-Key header |
| Task manager | app/controllers/manager/base_manager.py |
Thread-based concurrent task executor |
| Config loader | app/config/config.py |
TOML load/save, environment variable overrides |
| LLM service | app/services/llm.py |
14 LLM providers, script/terms generation, 5-retry logic |
| Material service | app/services/material.py |
Pexels/Pixabay search + download, TLS verification, API key rotation |
| Voice service | app/services/voice.py |
4 TTS backends, subtitle creation from word boundary data |
| Subtitle service | app/services/subtitle.py |
faster-whisper transcription, SRT generation, Levenshtein correction |
| Video service | app/services/video.py |
MoviePy compositing, ffmpeg concat, transitions, subtitle overlay |
| Task orchestrator | app/services/task.py |
7-step pipeline with progress tracking |
| State backends | app/services/state.py |
MemoryState and RedisState implementations |
| Upload/Post | app/services/upload_post.py |
Cross-post to TikTok/Instagram |
| Video effects | app/services/utils/video_effects.py |
Fade/slide transitions |
| Utilities | app/utils/utils.py |
Storage directory management, SRT formatting, JSON helpers, locale, threading |
| Schema models | app/models/schema.py |
Pydantic models: VideoParams, VideoAspect, VideoConcatMode, etc. |
| Constants | app/models/const.py |
PUNCTUATIONS, TASK_STATE_*, FILE_TYPE_* constants |
| Exceptions | app/models/exception.py |
HttpException, FileNotFoundException |
| WebUI | webui/Main.py |
Streamlit frontend (1,170 lines) |
| i18n | webui/i18n/*.json |
7 language translation files |
Technology Stack
| Layer | Technology | Version |
|---|---|---|
| Backend framework | FastAPI + Uvicorn | 0.115.6 / 0.32.1 |
| Frontend | Streamlit | 1.45.0 |
| Video processing | MoviePy + ffmpeg | 2.1.2 |
| TTS (default) | edge_tts | 7.2.7 |
| TTS (Azure V2) | azure-cognitiveservices-speech | 1.41.1 |
| Speech-to-text | faster-whisper | 1.1.0 |
| LLM (primary) | OpenAI SDK | 1.56.1 |
| LLM (gateway) | LiteLLM | 1.60.0 |
| LLM (Qwen) | DashScope | 1.20.14 |
| LLM (Gemini) | google.generativeai | 0.8.6 |
| LLM (free/risky) | g4f | 0.5.2.2 (optional) |
| State (optional) | Redis | 5.2.0 |
| Config format | TOML | via toml library |
| Logging | Loguru | 0.7.3 |
| Image processing | Pillow | via MoviePy |
Class Hierarchy
BaseState(ABC)
├── MemoryState # In-process dict-based state
└── RedisState # Redis-backed state
BaseResponse(BaseModel)
├── TaskResponse
├── TaskQueryResponse
├── TaskDeletionResponse
├── VideoScriptResponse
├── VideoTermsResponse
├── BgmRetrieveResponse
├── BgmUploadResponse
├── VideoMaterialRetrieveResponse
└── VideoMaterialUploadResponse
TaskManager # Thread-based concurrent task runner
UploadPostService # Singleton for cross-posting
VideoParams(BaseModel) # Core video parameter model
Enums:
VideoConcatMode # random, sequential
VideoTransitionMode # fade, slide, none
VideoAspect # portrait (9:16), landscape (16:9), square (1:1)
2. System Requirements
Hardware
| Component | Minimum | Recommended |
|---|---|---|
| CPU | 4 cores | 8+ cores (video encoding is CPU-intensive) |
| RAM | 4 GB | 8+ GB (faster-whisper large-v3 model requires ~3 GB) |
| Disk | 10 GB free | 50+ GB (stock footage cache grows with usage) |
| GPU | Not required | NVIDIA GPU with CUDA (accelerates whisper and video encoding) |
Software
| Dependency | Required | Notes |
|---|---|---|
| Python | 3.10+ | 3.12 recommended |
| ffmpeg | Required | Must be on PATH or configured in config.toml |
| ImageMagick | Required (Windows) | Auto-detected on Linux/macOS; set imagemagick_path on Windows |
| Docker | Optional | For containerized deployment |
| NVIDIA drivers + CUDA | Optional | For GPU-accelerated whisper and video encoding |
| Redis | Optional | For multi-process state sharing |
Network
| Endpoint | Purpose | Required |
|---|---|---|
| LLM provider API | Script generation | At least one provider configured |
Pexels API (api.pexels.com) |
Stock video search/download | If video_source = "pexels" |
Pixabay API (pixabay.com/api) |
Stock video search/download | If video_source = "pixabay" |
edge_tts (speech.platform.bing.com) |
Default TTS | If using edge_tts |
| Azure Cognitive Services | Azure TTS V2 | If speech_key configured |
| SiliconFlow API | SiliconFlow TTS | If configured |
| Gemini API | Gemini TTS / LLM | If configured |
Important
At minimum you need: (1) an LLM provider API key, (2) a Pexels or Pixabay API key, and (3) ffmpeg installed. Without all three, the pipeline cannot complete a video generation task.
3. Configuration Reference
Configuration is stored in config.toml at the project root. If the file does not exist on startup, it is copied from config.example.toml. The loader handles both UTF-8 and UTF-8-BOM encodings. The WebUI writes config changes on every "Generate Video" click and on page close.
Config sections are available as module-level dicts in code: config.app, config.whisper, config.proxy, config.azure, config.siliconflow, config.ui.
Important
All configuration keys below are sourced from
config.example.toml-- the canonical ground truth. Do not invent configuration keys that are not listed here.
[app] -- Main Application Settings
| Key | Type | Default | Description |
|---|---|---|---|
video_source |
string | "pexels" |
Stock footage provider: "pexels" or "pixabay" |
hide_config |
bool | false |
Hide configuration panel in WebUI |
edge_tts_timeout |
int | 30 |
Timeout in seconds for edge_tts streaming; 0 disables timeout |
tls_verify |
bool | true |
Enable TLS certificate verification on outbound API requests |
pexels_api_keys |
array | [] |
Array of Pexels API keys (supports rotation) |
pixabay_api_keys |
array | [] |
Array of Pixabay API keys (supports rotation) |
llm_provider |
string | "openai" |
Active LLM provider (see provider list below) |
enable_g4f |
bool | false |
Enable g4f provider (disabled by default for security) |
azure_api_version |
string | "2024-02-15-preview" |
Azure OpenAI API version |
subtitle_provider |
string | "edge" |
Subtitle provider: "edge" (from TTS) or "whisper" (transcription) |
imagemagick_path |
string | "" |
Path to ImageMagick binary (Windows only; auto-detected on Linux/macOS) |
ffmpeg_path |
string | "" |
Path to ffmpeg binary (auto-detected if empty) |
endpoint |
string | "" |
Custom download URL base for API-generated videos |
material_directory |
string | "" |
Video cache directory. Default: ./storage/cache_videos. "task" = per-task directory. Or an absolute path. |
enable_redis |
bool | false |
Use Redis for state instead of in-memory |
redis_host |
string | "" |
Redis server hostname |
redis_port |
int | 6379 |
Redis server port |
redis_db |
int | 0 |
Redis database number |
redis_password |
string | "" |
Redis password |
max_concurrent_tasks |
int | 5 |
Maximum parallel video generation tasks |
api_key |
string | "" |
API key for verify_token() authentication |
upload_post_enabled |
bool | false |
Enable cross-posting to social platforms |
upload_post_api_key |
string | "" |
API key for Upload-Post service |
upload_post_username |
string | "" |
Username for Upload-Post service |
upload_post_platforms |
array | [] |
Target platforms (e.g., ["tiktok", "instagram"]) |
upload_post_auto_upload |
bool | false |
Auto-upload after video generation |
LLM Provider Configuration
Each provider uses the pattern {provider}_api_key, {provider}_base_url, {provider}_model_name:
| Provider | llm_provider value |
Required keys | Notes |
|---|---|---|---|
| OpenAI | "openai" |
openai_api_key, openai_model_name |
openai_base_url optional (defaults to OpenAI API) |
| Moonshot | "moonshot" |
moonshot_api_key, moonshot_model_name, moonshot_base_url |
|
| Azure OpenAI | "azure" |
azure_api_key, azure_model_name, azure_base_url |
Uses azure_api_version |
| Qwen (DashScope) | "qwen" |
qwen_api_key, qwen_model_name |
Uses DashScope SDK |
| DeepSeek | "deepseek" |
deepseek_api_key, deepseek_model_name, deepseek_base_url |
|
| ModelScope | "modelscope" |
modelscope_api_key, modelscope_model_name, modelscope_base_url |
Streaming with enable_thinking=False |
| Gemini | "gemini" |
gemini_api_key, gemini_model_name |
Auto-upgrades deprecated models |
| Ollama | "ollama" |
ollama_model_name, ollama_base_url |
No API key needed (local) |
| g4f | "g4f" |
enable_g4f = true |
Requires uv sync --extra g4f |
| OneAPI | "oneapi" |
oneapi_api_key, oneapi_model_name, oneapi_base_url |
|
| Cloudflare | "cloudflare" |
cloudflare_api_key, cloudflare_model_name, cloudflare_base_url |
|
| MiniMax | "minimax" |
minimax_api_key, minimax_model_name, minimax_base_url |
|
| Ernie | "ernie" |
ernie_api_key, ernie_model_name, ernie_base_url |
|
| Pollinations | "pollinations" |
pollinations_model_name |
No API key required (public access) |
| LiteLLM | "litellm" |
Varies by underlying provider | Reads provider keys from environment variables |
[whisper] -- Speech-to-Text Settings
| Key | Type | Default | Description |
|---|---|---|---|
model_size |
string | "large-v3" |
Whisper model size ("tiny", "base", "small", "medium", "large-v3") |
device |
string | "CPU" |
Compute device: "CPU" or "cuda" |
compute_type |
string | "int8" |
Quantization: "int8", "float16", "int8_float16" |
Tip
On a machine with an NVIDIA GPU, set
device = "cuda"andcompute_type = "float16"for significantly faster whisper transcription. Thelarge-v3model with CUDA can be 10-20x faster than CPU.
[proxy] -- HTTP Proxy Settings
| Key | Type | Default | Description |
|---|---|---|---|
http |
string | "" |
HTTP proxy URL (e.g., "http://user:pass@proxy:1234") |
https |
string | "" |
HTTPS proxy URL |
[azure] -- Azure Speech Services
| Key | Type | Default | Description |
|---|---|---|---|
speech_key |
string | "" |
Azure Cognitive Services speech key |
speech_region |
string | "" |
Azure region (e.g., "eastus") |
[siliconflow] -- SiliconFlow TTS
| Key | Type | Default | Description |
|---|---|---|---|
api_key |
string | "" |
SiliconFlow API key |
[ui] -- WebUI Display Settings
| Key | Type | Default | Description |
|---|---|---|---|
hide_log |
bool | false |
Hide log output in WebUI |
subtitle_position |
string | "bottom" |
Subtitle position on video |
custom_position |
float | 70.0 |
Custom subtitle Y-position (percentage from top) |
Environment Variable Overrides
| Variable | Source | Purpose |
|---|---|---|
MPT_APP_REDIS_HOST |
app/config/config.py:70 |
Override Redis host (takes priority over REDIS_HOST) |
REDIS_HOST |
app/config/config.py:72 |
Fallback Redis host override |
CORS_ALLOWED_ORIGINS |
app/asgi.py:56 |
Comma-separated allowed CORS origins (default: "*") |
IMAGEIO_FFMPEG_EXE |
app/services/video.py:63 |
Custom ffmpeg binary path |
HF_ENDPOINT |
webui.sh |
Hugging Face mirror URL for model downloads |
IMAGEMAGICK_BINARY |
app/config/config.py:137 |
Set automatically from imagemagick_path config |
Config Loading Behavior
- On import,
config.pyattempts to loadconfig.tomlfrom the project root. - If
config.tomldoes not exist, it copiesconfig.example.tomltoconfig.toml. - The loader tries UTF-8 first, then falls back to UTF-8-BOM encoding.
- Environment variables for Redis host (
MPT_APP_REDIS_HOSTorREDIS_HOST) override the TOML value at load time. - When
imagemagick_pathis set in TOML, it is propagated to theIMAGEMAGICK_BINARYenvironment variable. - The WebUI saves the entire config to disk on every "Generate Video" click and on Streamlit page close.
4. Deployment & Scaling
Local Development
# Clone and enter the project
git clone https://github.com/harry0703/MoneyPrinterTurbo.git
cd MoneyPrinterTurbo
# Install dependencies (uv recommended)
uv sync
# Optional: install g4f support
uv sync --extra g4f
# Copy config and edit
cp config.example.toml config.toml
# Edit config.toml with your API keys
# Start the API server
python main.py
# API available at http://localhost:8080
# Swagger docs at http://localhost:8080/docs
# Start the WebUI (separate terminal)
streamlit run webui/Main.py
# WebUI available at http://localhost:8501
Docker Deployment (Standard)
docker compose up -d
This starts the application in a container with the following bind mounts:
./storage/-- persistent storage for generated videos and cached materials./config.toml-- configuration file
Docker Deployment (GPU)
docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d
The GPU compose override adds NVIDIA runtime configuration, enabling CUDA-accelerated whisper transcription and video encoding.
Warning
Docker issue #885: The container runs as
appuser, which may lack write permissions to bind-mountedstorage/directories. Ensure thestorage/directory and its subdirectories are writable by the container user before starting. Fix with:chmod -R a+w storage/on the host.
Redis Setup (Multi-Process State)
For deployments running multiple worker processes or requiring state persistence across restarts:
# config.toml
[app]
enable_redis = true
redis_host = "localhost"
redis_port = 6379
redis_db = 0
redis_password = ""
In Docker, override the Redis host via environment variable:
# docker-compose.yml (excerpt)
environment:
- MPT_APP_REDIS_HOST=redis
Note
Without Redis, state is held in a Python dict (
MemoryState). This works for single-process deployments but means all task state is lost on restart. Redis provides persistence and cross-process visibility.
Scaling Considerations
| Dimension | Approach |
|---|---|
| Concurrent tasks | Adjust max_concurrent_tasks in config (default: 5). Each task spawns a thread. |
| Multiple workers | Enable Redis for shared state. Run multiple API instances behind a load balancer. |
| Disk I/O | Stock footage cache (storage/cache_videos/) grows with usage. Set material_directory = "task" for per-task cleanup, or manage a shared cache with periodic pruning. |
| CPU bottleneck | Video encoding (ffmpeg) is the primary bottleneck. Scale vertically with more cores or offload to GPU. |
| API key rotation | Provide multiple keys in pexels_api_keys / pixabay_api_keys arrays; the material service rotates through them. |
CORS Configuration
The FastAPI ASGI app configures CORS middleware. By default, all origins are allowed ("*"). To restrict:
export CORS_ALLOWED_ORIGINS="https://yourdomain.com,https://app.yourdomain.com"
5. Operations
Pipeline Lifecycle
The task.py:start() function executes a 7-step pipeline with progress tracking:
| Step | Function | Progress | Description |
|---|---|---|---|
| 1 | generate_script() |
5% - 10% | LLM generates video script from topic/prompt |
| 2 | generate_terms() |
10% - 20% | LLM generates stock footage search terms from script |
| 3 | generate_audio() |
20% - 30% | TTS engine converts script to audio file |
| 4 | generate_subtitle() |
30% - 40% | Generate SRT subtitles (from TTS word boundaries or whisper transcription) |
| 5 | get_video_materials() |
40% - 50% | Search and download stock footage clips |
| 6 | generate_final_videos() |
50% - 100% | Composite video: resize clips, overlay subtitles, mix audio, concat |
| 7 | cross_post_video() |
100% | Optional: upload to TikTok/Instagram |
The stop_at parameter allows partial pipeline execution:
# Stop after script generation only
start(task_id, params, stop_at="script")
# Stop after audio generation
start(task_id, params, stop_at="audio")
# Full pipeline (default)
start(task_id, params, stop_at="video")
Valid stop_at values: "script", "terms", "audio", "subtitle", "materials", "video".
Task State Machine
pending ──► processing ──► completed
│
└──► failed
State transitions are managed by TaskManager (thread-based). Tasks are tracked by task_id in the configured state backend (MemoryState or RedisState). The state.update_task() method updates progress percentage and status atomically.
Warning
With
MemoryState(the default), all task state is lost when the process restarts. There is no automatic retry mechanism for interrupted tasks. If you need task persistence, enable Redis.
Storage Layout
storage/
├── tasks/
│ └── {task_id}/
│ ├── audio.mp3 # Generated TTS audio
│ ├── subtitle.srt # Generated subtitles
│ ├── script.json # Generated script
│ ├── terms.json # Search terms
│ └── final-*.mp4 # Output video(s)
├── cache_videos/ # Downloaded stock footage cache
├── bgm/ # Background music files
└── fonts/ # Custom fonts
Tip
The
storage/tasks/directory serves double duty: it is both the working directory during generation and the serving directory for the API. The FastAPI app mountsstorage/tasks/at/tasks/{task_id}/for direct file access.
Monitoring
The health check endpoint provides basic liveness:
curl http://localhost:8080/ping
# Response: "pong"
For deeper monitoring, query the task list endpoint:
curl http://localhost:8080/api/v1/videos?page=1&page_size=20
Loguru handles all application logging. Log output goes to stdout by default and can be observed in the WebUI (unless ui.hide_log = true).
Backup and Recovery
Critical data paths requiring backup:
| Path | Content | Priority |
|---|---|---|
config.toml |
All configuration including API keys | High |
storage/tasks/ |
Generated videos and intermediate files | Medium |
storage/cache_videos/ |
Downloaded stock footage (re-downloadable) | Low |
storage/bgm/ |
Uploaded background music | Medium |
6. Performance
LLM Service Performance
- Retry logic: 5 retries per LLM request with exponential backoff behavior.
- Response normalization: every LLM response is stripped of whitespace, newlines removed, and validated as non-empty before proceeding.
- Gemini model auto-upgrade: deprecated model names (
gemini-pro,gemini-1.0-pro) are automatically mapped togemini-2.5-flashto prevent API failures. - ModelScope streaming: uses streaming mode with
enable_thinking=Falseto avoid unnecessary reasoning token overhead.
Video Processing Performance
- ffmpeg binary resolution order:
IMAGEIO_FFMPEG_EXEenv var, system PATH,imageio_ffmpegbundled binary, then bare"ffmpeg"fallback. - Concat strategy: individual clips are written as temp files, then joined using ffmpeg's concat demuxer with a single re-encode pass. This avoids repeated decode/encode cycles.
- Clip looping: if total video duration is shorter than the audio track, clips are looped cyclically to fill the gap.
- Resize strategy: aspect ratio is maintained; black letterboxing fills any remaining space.
- Image sanitization: corrupt EXIF/metadata is stripped from images before compositing to prevent MoviePy crashes.
- Audio output: AAC codec at 192 kbps bitrate.
- Stdout suppression: MoviePy's verbose ffmpeg probe output is redirected to avoid log pollution.
Voice Service Performance
- edge_tts: async streaming with configurable timeout (
edge_tts_timeout, default 30 seconds). Set to0to disable timeout for slow connections. - Azure TTS V2: word boundary callbacks provide precise subtitle timing without a separate transcription pass.
- Subtitle creation:
SubMakeruses word boundary data from TTS for alignment. A legacy compatibility layer (ensure_legacy_submaker_fields()) handles older edge_tts versions.
Material Download Performance
- API key rotation: when multiple Pexels/Pixabay keys are provided, the service rotates through them to distribute rate limits.
- TLS verification: configurable via
tls_verify. Disabling is not recommended but may be necessary behind corporate proxies. - Caching: downloaded footage is stored in
storage/cache_videos/(or per-task ifmaterial_directory = "task"). Subsequent requests for the same footage hit the cache.
7. Security
Caution
API authentication (
verify_token()) exists in the codebase atapp/controllers/base.pybut is currently commented out in all routers (GitHub issue #882). Any deployment exposed to the network is effectively unauthenticated. Re-enable auth in production by uncommenting theDepends(verify_token)declarations inapp/controllers/v1/video.pyandapp/controllers/v1/llm.py.
Authentication
The verify_token() function in app/controllers/base.py checks the X-API-Key header against config.app.api_key. When enabled:
# In router files, add dependency:
@router.post("/api/v1/videos", dependencies=[Depends(verify_token)])
To set the API key:
# config.toml
[app]
api_key = "your-strong-random-key-here"
TLS Verification
GitHub issue #881 identified that TLS certificate verification was previously disabled on all outbound API requests. This is now configurable:
[app]
tls_verify = true # Default: true. Set to false only if behind a corporate proxy with MITM cert.
Warning
Setting
tls_verify = falsedisables certificate validation for all outbound HTTP requests (stock footage downloads, LLM API calls). This makes the application vulnerable to man-in-the-middle attacks. Only disable in trusted network environments.
g4f Supply Chain Risk
The g4f package (GitHub issue #883) relies on reverse-engineered third-party endpoints and has been flagged as a supply chain risk. It is disabled by default and requires explicit opt-in:
[app]
enable_g4f = true # Not recommended for production
Additionally, g4f is not installed with default dependencies. It requires:
uv sync --extra g4f
Caution
The g4f package executes code from reverse-engineered third-party services. It may send data to uncontrolled endpoints, introduce vulnerabilities, or break without notice. Avoid in production environments or any deployment handling sensitive content.
CORS
Default CORS configuration allows all origins ("*"). For production:
export CORS_ALLOWED_ORIGINS="https://yourdomain.com"
API Key Storage
API keys for LLM providers and stock footage services are stored in plaintext in config.toml. Protect this file:
chmod 600 config.toml
In Docker deployments, mount config.toml as a read-only secret or use environment variable overrides where available.
Static File Serving
The FastAPI app serves generated videos directly from storage/tasks/{task_id}/. Without authentication enabled, any user with a task ID can access any generated video. Task IDs are sequential or predictable depending on the state backend implementation.
8. Developer Internals
LLM Service (app/services/llm.py)
The LLM service is the most complex service module, supporting 14 providers through a unified interface.
Request flow:
- Caller invokes
generate_script(topic, language)orgenerate_terms(topic, script, amount). - The service selects the configured provider and constructs a provider-specific API call.
- Each call is wrapped in a retry loop (max 5 attempts).
- The response is normalized: whitespace stripped, newlines removed, validated as non-empty.
- If all retries fail, the service raises an exception.
Provider-specific behaviors:
| Provider | Implementation Detail |
|---|---|
| OpenAI-compatible | Uses OpenAI SDK ChatCompletion.create() with configurable base_url |
| Gemini | Uses google.generativeai SDK; auto-upgrades deprecated model names |
| Qwen | Uses DashScope SDK with Qwen-specific parameters |
| ModelScope | Streaming mode with enable_thinking=False |
| Ollama | OpenAI-compatible SDK pointed at local Ollama server |
| g4f | Direct g4f.ChatCompletion.create(), requires explicit enable flag |
| Pollinations | No API key needed; public endpoint |
| LiteLLM | Delegates to LiteLLM gateway, which reads provider keys from environment |
Video Service (app/services/video.py)
ffmpeg resolution chain:
# Priority order:
1. os.environ["IMAGEIO_FFMPEG_EXE"]
2. shutil.which("ffmpeg") # System PATH
3. imageio_ffmpeg.get_ffmpeg_exe() # Bundled
4. "ffmpeg" # Bare fallback
Video assembly process:
- Clips are loaded and resized to target aspect ratio (9:16, 16:9, or 1:1).
- If total clip duration < audio duration, clips are looped cyclically.
- Transitions (fade, slide, or none) are applied between clips.
- Subtitles are rendered as text overlays on the video frames.
- Individual processed clips are written as temp files.
- All temp clips are concatenated using ffmpeg's concat demuxer.
- Audio track is mixed with optional background music.
- Final output is written as MP4 with AAC audio at 192 kbps.
Error handling:
If the ffmpeg concat step fails, a RuntimeError: ffmpeg concat failed is raised. This typically indicates a codec mismatch between clips or a corrupt temp file.
Voice Service (app/services/voice.py)
Four TTS backends:
-
edge_tts (V1): Uses
edge_tts.Communicatewith async streaming. Word boundary data from the stream is used to generate subtitle timing. Configurable timeout prevents hangs on network issues. -
Azure TTS V2: Uses the
azure-cognitiveservices-speechSDK with word boundary event callbacks. Provides the most accurate subtitle timing of all backends. -
SiliconFlow: REST API calls to
siliconflow.cn. Returns audio as a single blob (no word boundary data; subtitles require separate whisper transcription). -
Gemini TTS: Uses
google.generativeaiSDK for TTS. Returns audio without word boundary data.
Subtitle Service (app/services/subtitle.py)
Two modes of subtitle generation:
- edge mode (
subtitle_provider = "edge"): Subtitles are generated from TTS word boundary data during audio synthesis. Fast and accurate for edge_tts and Azure backends. - whisper mode (
subtitle_provider = "whisper"): Uses faster-whisper to transcribe the generated audio. Required when the TTS backend does not provide word boundary data (SiliconFlow, Gemini). Levenshtein distance correction aligns transcribed text with the original script to fix minor transcription errors.
State Service (app/services/state.py)
Two backends implementing the BaseState abstract class:
MemoryState: Python dict wrapped in theBaseStateinterface. Zero dependencies, instant access, but state is process-local and non-persistent.RedisState: Serializes task state to Redis. Supports multi-process deployments and survives restarts.
Selection is controlled by config.app.enable_redis. The state object is instantiated once at module level and used throughout the application.
Task Manager (app/controllers/manager/base_manager.py)
The TaskManager uses Python's threading module to run video generation tasks concurrently. Tasks are submitted as threads, up to max_concurrent_tasks (default 5). Each thread runs the task.py:start() pipeline independently.
Upload-Post Service (app/services/upload_post.py)
Optional cross-posting to social media platforms (TikTok, Instagram). Implemented as a singleton UploadPostService. Requires:
upload_post_enabled = trueupload_post_api_keysetupload_post_usernamesetupload_post_platformsconfigured
9. Integration Points
REST API Reference
The API runs at http://host:8080 with interactive Swagger documentation at /docs.
Video Task Endpoints
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/videos |
Create a video generation task |
GET |
/api/v1/videos |
List tasks (query params: page, page_size) |
GET |
/api/v1/videos/{task_id} |
Get task status and progress |
DELETE |
/api/v1/videos/{task_id} |
Delete/cancel a task |
Generation Sub-Endpoints
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/videos/script |
Generate script only (body: VideoScriptRequest) |
POST |
/api/v1/videos/terms |
Generate search terms (body: VideoTermsRequest) |
POST |
/api/v1/videos/subtitle |
Generate audio + subtitles |
POST |
/api/v1/videos/audio |
Generate audio only |
Resource Management
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/videos/bgm |
List available background music files |
POST |
/api/v1/videos/bgm/upload |
Upload a background music file |
GET |
/api/v1/videos/materials |
List available material files |
POST |
/api/v1/videos/materials/upload |
Upload material files |
Static File Access
| Pattern | Description |
|---|---|
/tasks/{task_id}/... |
Serves generated files from storage/tasks/ |
/ |
Serves resource/public/index.html |
Example: Create a Video Task
curl -X POST http://localhost:8080/api/v1/videos \
-H "Content-Type: application/json" \
-d '{
"video_subject": "The Future of Renewable Energy",
"video_language": "en-US",
"video_aspect": "portrait",
"video_concat_mode": "random",
"video_transition_mode": "fade",
"paragraph_number": 3,
"voice_name": "en-US-AriaNeural",
"bgm_type": "random",
"subtitle_enabled": true,
"font_name": "STHeitiMedium.ttc",
"text_fore_color": "#FFFFFF",
"font_size": 60,
"stroke_color": "#000000",
"stroke_width": 1.5
}'
Example: Poll Task Status
curl http://localhost:8080/api/v1/videos/{task_id}
Response includes progress (0-100) and state (pending, processing, completed, failed).
WebUI Integration
The Streamlit WebUI (webui/Main.py, 1,170 lines) provides:
- Topic/prompt input with language selection (7 languages via i18n)
- Full configuration panel (mirrors
config.tomlsettings) - Real-time progress display during generation
- Video preview and download
- Configuration auto-save on generate and page close
The WebUI communicates with the backend by directly importing and calling service-layer functions -- it does not go through the REST API.
Internationalization
Seven language files in webui/i18n/:
webui/i18n/
├── en.json
├── zh.json
├── ja.json
├── ko.json
├── fr.json
├── de.json
└── es.json
External Service Dependencies
| Service | Required | Configuration |
|---|---|---|
| LLM Provider | Yes (one minimum) | [app] section, provider-specific keys |
| Pexels | If video_source = "pexels" |
pexels_api_keys array |
| Pixabay | If video_source = "pixabay" |
pixabay_api_keys array |
| Azure Speech | Optional TTS | [azure] section |
| SiliconFlow | Optional TTS | [siliconflow] section |
| Gemini | Optional TTS/LLM | gemini_api_key in [app] |
| Redis | Optional state backend | [app] Redis settings |
| Hugging Face | Whisper model download | HF_ENDPOINT env var for mirror |
| Upload-Post API | Optional cross-posting | [app] upload_post settings |
Troubleshooting
Error: "{provider}: api_key is not set, please set it in the config.toml file."
Cause: The selected LLM provider does not have an API key configured.
Solution: Edit config.toml and set the {provider}_api_key value for your chosen provider. Restart the application.
Prevention: Verify all required provider keys are set before starting.
Error: "{provider}: model_name is not set, please set it in the config.toml file."
Cause: The LLM provider is configured but no model name is specified.
Solution: Set {provider}_model_name in config.toml. For example: openai_model_name = "gpt-4o".
Error: "{provider}: base_url is not set, please set it in the config.toml file."
Cause: Providers that require a custom endpoint (Moonshot, Azure, DeepSeek, ModelScope, OneAPI, Cloudflare, MiniMax, Ernie) need {provider}_base_url set.
Solution: Set the appropriate {provider}_base_url in config.toml.
Error: "[{provider}] returned an empty response"
Cause: The LLM returned a blank response after all 5 retry attempts. This may indicate rate limiting, an overloaded model, or a prompt issue.
Solution: Check the LLM provider's status page. Try a different model. Verify your API key has sufficient quota. Check network connectivity and proxy settings.
Error: "[{provider}] returned an invalid response"
Cause: The LLM response could not be parsed as expected text. May indicate a model returning structured data when plain text was expected.
Solution: Verify the model name is correct. Some models return JSON by default; ensure you are using a chat-completion-compatible model.
Error: "g4f provider is disabled by default because it relies on reverse-engineered third-party endpoints"
Cause: Attempting to use the g4f provider without explicitly enabling it.
Solution: Set enable_g4f = true in config.toml if you accept the security risks. This is not recommended for production.
Error: "g4f package is not installed by default. Install the optional dependency with uv sync --extra g4f"
Cause: g4f is enabled in config but the Python package is not installed.
Solution: Run uv sync --extra g4f to install the g4f dependency.
Error: "RuntimeError: ffmpeg concat failed"
Cause: The ffmpeg concat demuxer could not join the processed video clips. Common causes: codec mismatch between clips, corrupt temp file, insufficient disk space, or ffmpeg not found.
Solution:
- Verify ffmpeg is installed and accessible:
ffmpeg -version. - Check available disk space in the
storage/directory. - Inspect individual temp clips in the task directory for corruption.
- If using Docker, ensure the ffmpeg binary is available inside the container.
Prevention: Set ffmpeg_path explicitly in config.toml if auto-detection is unreliable.
Error: "invalid voice name: {voice_name}"
Cause: The specified TTS voice name does not match any available voice in the selected TTS engine.
Solution: Use a valid voice name. For edge_tts, run edge-tts --list-voices to see available voices. Common voices: en-US-AriaNeural, en-US-GuyNeural, zh-CN-XiaoxiaoNeural.
Error: "edge_tts stream timed out after {N}s"
Cause: The edge_tts service did not respond within the configured timeout (edge_tts_timeout).
Solution:
- Check network connectivity to Microsoft's TTS servers.
- Increase
edge_tts_timeoutinconfig.toml(default: 30 seconds). - Set
edge_tts_timeout = 0to disable the timeout entirely (not recommended for production). - If behind a proxy, configure
[proxy]settings.
Error: "edge_tts communicate object has no stream method"
Cause: Incompatible edge_tts version. The stream() method interface changed between versions.
Solution: Update edge_tts to version 7.2.7 or later: uv sync. The voice service includes a legacy compatibility layer (ensure_legacy_submaker_fields()), but severely outdated versions may not be supported.
Error: "video_terms must be a string or a list of strings"
Cause: The video terms parameter was passed in an unexpected format (e.g., a dict or nested structure).
Solution: Ensure video_terms is either a single string or a flat list of strings when calling the API.
Problem: Docker container cannot write to storage directory
Cause: GitHub issue #885. The Docker container runs as appuser (non-root), which may lack write permissions to bind-mounted host directories.
Solution:
chmod -R a+w storage/
# Or set ownership to match container user:
chown -R 1000:1000 storage/
Problem: Videos generate but are not accessible via API
Cause: The FastAPI static file mount expects files at storage/tasks/{task_id}/. If material_directory or storage paths are misconfigured, files may be written elsewhere.
Solution: Verify that generated files exist in storage/tasks/{task_id}/ and that the directory is mounted correctly in Docker. Check the endpoint config if using a custom download URL.
Problem: Whisper transcription is extremely slow
Cause: Whisper is running on CPU with the large-v3 model (default), which is very slow without GPU acceleration.
Solution: Either:
- Switch to GPU: set
whisper.device = "cuda"andwhisper.compute_type = "float16". - Use a smaller model: set
whisper.model_size = "small"or"base"(reduced accuracy). - Use edge subtitle provider instead: set
subtitle_provider = "edge"to skip whisper entirely.
Problem: API requests return CORS errors in browser
Cause: Default CORS allows all origins ("*"), but if CORS_ALLOWED_ORIGINS is set incorrectly, requests from your frontend domain will be blocked.
Solution: Set CORS_ALLOWED_ORIGINS to include your frontend's origin, comma-separated:
export CORS_ALLOWED_ORIGINS="http://localhost:3000,https://yourdomain.com"
Problem: Generated videos have black bars
Cause: Source footage aspect ratio does not match the target video_aspect setting. The resize strategy maintains aspect ratio and fills remaining space with black letterboxing.
Solution: This is expected behavior. Choose footage that matches your target aspect ratio, or accept the letterboxing. The system does not crop or stretch video to fill.
Testing
Tests use Python's unittest framework:
python -m unittest discover -s test
Test files in test/services/:
| File | Coverage |
|---|---|
test_llm.py |
LiteLLM provider integration |
test_material.py |
TLS verification behavior |
test_task.py |
Task service pipeline |
test_video.py |
Video preprocessing and assembly |
test_voice.py |
Voice synthesis |
See the User Guide for basic usage and video generation workflows.