# MoneyPrinterTurbo Administrator & Developer Guide

## 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

```text
┌─────────────────────────────────────────────────────┐
│                    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

```text
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"` and `compute_type = "float16"` for significantly faster whisper transcription. The `large-v3` model 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

1. On import, `config.py` attempts to load `config.toml` from the project root.
2. If `config.toml` does not exist, it copies `config.example.toml` to `config.toml`.
3. The loader tries UTF-8 first, then falls back to UTF-8-BOM encoding.
4. Environment variables for Redis host (`MPT_APP_REDIS_HOST` or `REDIS_HOST`) override the TOML value at load time.
5. When `imagemagick_path` is set in TOML, it is propagated to the `IMAGEMAGICK_BINARY` environment variable.
6. The WebUI saves the entire config to disk on every "Generate Video" click and on Streamlit page close.

---

## 4. Deployment & Scaling

### Local Development

```bash
# 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)

```bash
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)

```bash
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-mounted `storage/` directories. Ensure the `storage/` 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:

```toml
# 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:

```yaml
# 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:

```bash
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:

```python
# 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

```text
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

```text
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 mounts `storage/tasks/` at `/tasks/{task_id}/` for direct file access.

### Monitoring

The health check endpoint provides basic liveness:

```bash
curl http://localhost:8080/ping
# Response: "pong"
```

For deeper monitoring, query the task list endpoint:

```bash
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 to `gemini-2.5-flash` to prevent API failures.
- **ModelScope streaming**: uses streaming mode with `enable_thinking=False` to avoid unnecessary reasoning token overhead.

### Video Processing Performance

- **ffmpeg binary resolution order**: `IMAGEIO_FFMPEG_EXE` env var, system PATH, `imageio_ffmpeg` bundled 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 to `0` to disable timeout for slow connections.
- **Azure TTS V2**: word boundary callbacks provide precise subtitle timing without a separate transcription pass.
- **Subtitle creation**: `SubMaker` uses 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 if `material_directory = "task"`). Subsequent requests for the same footage hit the cache.

---

## 7. Security

> [!CAUTION]
> API authentication (`verify_token()`) exists in the codebase at `app/controllers/base.py` but 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 the `Depends(verify_token)` declarations in `app/controllers/v1/video.py` and `app/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:

```python
# In router files, add dependency:
@router.post("/api/v1/videos", dependencies=[Depends(verify_token)])
```

To set the API key:

```toml
# 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:

```toml
[app]
tls_verify = true   # Default: true. Set to false only if behind a corporate proxy with MITM cert.
```

> [!WARNING]
> Setting `tls_verify = false` disables 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:

```toml
[app]
enable_g4f = true   # Not recommended for production
```

Additionally, g4f is not installed with default dependencies. It requires:

```bash
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:

```bash
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:

```bash
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:**

1. Caller invokes `generate_script(topic, language)` or `generate_terms(topic, script, amount)`.
2. The service selects the configured provider and constructs a provider-specific API call.
3. Each call is wrapped in a retry loop (max 5 attempts).
4. The response is normalized: whitespace stripped, newlines removed, validated as non-empty.
5. 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:**

```python
# 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:**

1. Clips are loaded and resized to target aspect ratio (9:16, 16:9, or 1:1).
2. If total clip duration < audio duration, clips are looped cyclically.
3. Transitions (fade, slide, or none) are applied between clips.
4. Subtitles are rendered as text overlays on the video frames.
5. Individual processed clips are written as temp files.
6. All temp clips are concatenated using ffmpeg's concat demuxer.
7. Audio track is mixed with optional background music.
8. 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:**

1. **edge_tts (V1)**: Uses `edge_tts.Communicate` with async streaming. Word boundary data from the stream is used to generate subtitle timing. Configurable timeout prevents hangs on network issues.

2. **Azure TTS V2**: Uses the `azure-cognitiveservices-speech` SDK with word boundary event callbacks. Provides the most accurate subtitle timing of all backends.

3. **SiliconFlow**: REST API calls to `siliconflow.cn`. Returns audio as a single blob (no word boundary data; subtitles require separate whisper transcription).

4. **Gemini TTS**: Uses `google.generativeai` SDK 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 the `BaseState` interface. 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 = true`
- `upload_post_api_key` set
- `upload_post_username` set
- `upload_post_platforms` configured

---

## 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

```bash
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

```bash
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.toml` settings)
- 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/`:

```text
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:**
1. Verify ffmpeg is installed and accessible: `ffmpeg -version`.
2. Check available disk space in the `storage/` directory.
3. Inspect individual temp clips in the task directory for corruption.
4. 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:**
1. Check network connectivity to Microsoft's TTS servers.
2. Increase `edge_tts_timeout` in `config.toml` (default: 30 seconds).
3. Set `edge_tts_timeout = 0` to disable the timeout entirely (not recommended for production).
4. 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:**
```bash
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:
1. Switch to GPU: set `whisper.device = "cuda"` and `whisper.compute_type = "float16"`.
2. Use a smaller model: set `whisper.model_size = "small"` or `"base"` (reduced accuracy).
3. 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:
```bash
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:

```bash
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.

---

_Generated May 2026 from harry0703/MoneyPrinterTurbo at 46ba578. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
