Generate narrated movie recap videos from a single prompt.
Project description
🎬 Movie Narrator
One Prompt → One Narrated Movie Video
Movie Narrator is an open-source toolkit that automatically generates movie recap videos with narration, subtitles, and rendered output from a simple command.
Features
- 🎬 Generate movie recap scripts with LLMs
- 🔊 Text-to-Speech narration (Edge-TTS by default)
- 💬 Automatic SRT subtitle generation
- 🌐 Multi-language subtitles (
--subtitle-lang entranslates narration cues via LLM and writessubtitle.<lang>.srt+subtitle.bilingual.srt) - 🖥️ Web UI (
mn web— local Gradio browser app with form inputs, cooperative cancel, and artifact download) - 🎞️ Video rendering with MoviePy and FFmpeg
- 📝 Script markdown export (
script.md) - 🎵 Background music integration (BGM)
- 🎬 Scene-level clip export
- 📦 Metadata export
- 🔌 Extensible pipeline architecture
- 🐍 Pure Python implementation
Installation
Requirements
- Python 3.10+
- FFmpeg
Install FFmpeg
macOS
brew install ffmpeg
Ubuntu / Debian
sudo apt install ffmpeg
Windows
# Option 1: winget
winget install Gyan.FFmpeg
# Option 2: chocolatey
choco install ffmpeg
# Option 3: Manual download from https://ffmpeg.org/
Verify installation:
ffmpeg -version
Install Movie Narrator
From PyPI
pip install movie-narrator
From Source
git clone https://github.com/zcbacxc/movie-narrator.git
cd movie-narrator
pip install -e .
Optional extras
# Scene detection (PySceneDetect)
pip install "movie-narrator[media]"
# WhisperX + semantic search (requires PyTorch)
pip install "movie-narrator[ml]"
# Web UI (Gradio)
pip install "movie-narrator[web]"
# Everything
pip install "movie-narrator[full]"
For development:
pip install -e ".[dev]"
Quick Start
Prerequisites
- LLM: Default uses local Ollama (
ollama serveto start). Or configure remote LLM via.envfile. - FFmpeg: Required for video rendering.
Basic Usage
# Generate a narrated movie video
mn create --movie "飞驰人生" --style "热血搞笑" --duration 60
# With custom voice and format
mn create --movie "飞驰人生" --voice "zh-CN-XiaoxiaoNeural" --format "9:16"
# Keep TTS cache for debugging
mn create --movie "飞驰人生" --keep-cache
CLI Options
| Option | Description | Default |
|---|---|---|
--movie, -m |
Movie name (required) | - |
--style, -s |
Narration style | 热血搞笑 |
--duration, -d |
Target duration (seconds) | 60 |
--voice, -v |
Edge-TTS voice | zh-CN-YunxiNeural |
--format, -f |
Video format (16:9 or 9:16) |
16:9 |
--video, -V |
Source movie file path | - |
--library-dir |
Movie library directory | - |
--research |
Enable plot research via LLM | false |
--no-research |
Disable plot research | - |
--bgm |
Background music file path | - |
--no-bgm |
Disable BGM even if default is set | false |
--no-clips |
Skip scene-level clip export | false |
--strict |
Abort pipeline on soft step failure | false |
--subtitle-lang |
Target language tag (en, ja, zh-TW, ...); empty = feature off |
- |
--subtitle-mode |
Overlay mode: original / translated / bilingual |
original |
--config |
Path to job YAML (movie/steps/params); CLI flags override YAML | - |
Job YAML config (v0.3)
# Drive a job from YAML (movie may live only in the file)
mn create --config examples/job.example.yaml
# CLI flags still win over YAML
mn create --config examples/job.example.yaml --movie "OtherTitle" --no-clips
See examples/job.example.yaml for the full whitelist: soft-step toggles under steps: (research, align, scene, match, bgm, export, translate), params: (scene_threshold, match_min_score, research_provider, translate_provider, translate_retries, translate_chunk_chars, translate_chunk_size), and the multi-language subtitle top-level keys subtitle_lang / subtitle_mode. Relative video / bgm / library_dir paths resolve against the YAML file's directory. LLM credentials stay in .env / MN_* only.
Multi-language subtitles
# Translate narration cues to English and overlay them on the video
mn create --movie "Inception" --subtitle-lang en --subtitle-mode bilingual
# Or just write the translated SRT files (no on-screen change)
mn create --movie "Inception" --subtitle-lang en
When --subtitle-lang is set, generate_subtitle always writes three SRT files:
subtitle.srt— original narration (always present,subtitle_pathinvariant)subtitle.<lang>.srt— translated (e.g.subtitle.en.srt)subtitle.bilingual.srt— cue bodyf"{original}\n{translation}"(LF between lines)
--subtitle-mode chooses which file render_video reads:
| Mode | Overlay text source |
|---|---|
original (default) |
subtitle.srt |
translated |
subtitle.<lang>.srt (falls back to subtitle.srt with a warn if missing) |
bilingual |
subtitle.bilingual.srt (same fallback) |
Setting subtitle_mode=translated|bilingual without subtitle_lang raises JobConfigError at merge time. Failure policy: LLM retries MN_TRANSLATE_RETRIES times, then soft-degrades to filling the translation track with the original text and surfacing a warning.
Web UI (v0.3.5)
# Install with web extra
pip install "movie-narrator[web]"
# Launch local browser app
mn web
# Or with custom host/port
mn web --host 0.0.0.0 --port 8080
# Create a public Gradio share link (temporary)
mn web --share
The Web UI provides a form-based interface to all CLI options: movie name, style, duration, voice, format, video/BGM upload, subtitle settings, and advanced params. A Cancel button allows cooperative cancellation at step boundaries. Artifacts (video, subtitles, script, metadata) are available for download at all terminal states — including after cancellation.
empty = no override: Advanced form fields left blank do NOT override Settings (.env / MN_*) defaults. Only fill a field if you want to explicitly override.
Offline Demo (No LLM Required)
# CI=1 uses silent audio fallback, bypasses LLM and Edge-TTS
CI=1 mn create --movie "Demo" --duration 10
Other Commands
mn version # Show version
mn --help # Show help
Configuration
All settings use the MN_ prefix to avoid conflicts with other tools.
Via .env file (recommended)
Create .env in your project directory (or ~/.movie-narrator/.env for global config — this file lives outside the package, so pip install/upgrade/uninstall never touches it):
MN_LLM_BASE_URL=http://localhost:11434/v1
MN_LLM_API_KEY=ollama
MN_LLM_MODEL=qwen2.5:7b
MN_DEFAULT_VOICE=zh-CN-YunxiNeural
MN_DEFAULT_FORMAT=16:9
Via environment variables
# PowerShell
$env:MN_LLM_BASE_URL="http://localhost:11434/v1"
$env:MN_LLM_MODEL="qwen2.5:7b"
mn create --movie "飞驰人生" --duration 60
# Linux / macOS
export MN_LLM_BASE_URL=http://localhost:11434/v1
export MN_LLM_MODEL=qwen2.5:7b
mn create --movie "飞驰人生" --duration 60
Config lookup order
| Priority | Location | Notes |
|---|---|---|
| 1 | Environment variables (MN_*) |
Highest |
| 2 | 当前目录/.env |
Project-level |
| 3 | ~/.movie-narrator/.env |
User-level, never lost on pip install/upgrade/uninstall |
| 4 | Built-in defaults | Local Ollama |
Full reference
| Variable | Description | Default |
|---|---|---|
MN_LLM_BASE_URL |
LLM API endpoint | http://localhost:11434/v1 |
MN_LLM_API_KEY |
LLM API key | ollama |
MN_LLM_MODEL |
LLM model name | qwen2.5:7b |
MN_DEFAULT_VOICE |
Edge-TTS voice | zh-CN-YunxiNeural |
MN_DEFAULT_FORMAT |
Video aspect ratio | 16:9 |
MN_LIBRARY_DIR |
Movie library path | - |
MN_DEFAULT_BGM |
Default BGM file | - |
MN_RESEARCH_ENABLED |
Auto-enable research | false |
MN_RESEARCH_PROVIDER |
Research backend | llm |
MN_SCENE_THRESHOLD |
PySceneDetect threshold | 27.0 |
MN_SCENE_FRAME_SKIP |
Frames to skip in scene detection | 10 |
MN_MATCH_MIN_SCORE |
Minimum match score | 0.25 |
MN_EXPORT_CLIPS_DEFAULT |
Auto-export clips | true |
MN_SUBTITLE_LANG |
Default target language tag; empty = feature off | - |
MN_SUBTITLE_MODE |
Default overlay mode (original / translated / bilingual) |
original |
MN_TRANSLATE_PROVIDER |
Translation backend (v0.3: llm only) |
llm |
MN_TRANSLATE_RETRIES |
LLM translation retries before soft-degrade | 3 |
MN_TTS_PROVIDER |
TTS backend: edge (default), openai, or mimo |
edge |
MN_OPENAI_TTS_MODEL |
OpenAI TTS model (when MN_TTS_PROVIDER=openai) |
tts-1 |
MN_OPENAI_TTS_API_KEY |
OpenAI TTS API key (falls back to MN_LLM_API_KEY) |
- |
MN_OPENAI_TTS_BASE_URL |
OpenAI TTS base URL (falls back to MN_LLM_BASE_URL) |
- |
MN_MIMO_TTS_MODEL |
MiMo TTS model (mimo-v2.5-tts, mimo-v2.5-tts-voiceclone, mimo-v2.5-tts-voicedesign) |
mimo-v2.5-tts |
MN_MIMO_API_KEY |
MiMo API key (falls back to MN_LLM_API_KEY) |
- |
MN_MIMO_BASE_URL |
MiMo base URL | https://api.xiaomimimo.com/v1 |
MN_MIMO_STYLE_PROMPT |
Style description for mimo-v2.5-tts (optional) |
- |
Output
output/
└── 飞驰人生/
├── narration.mp3 # TTS narration audio
├── mixed.mp3 # Narration + BGM mix (when BGM enabled)
├── subtitle.srt
├── subtitle.<lang>.srt # (when --subtitle-lang set; e.g. subtitle.en.srt)
├── subtitle.bilingual.srt # (when --subtitle-lang set; original + LF + translation per cue)
├── script.md
├── script.json
├── research.json # (when --research)
├── scenes.json # (when video provided)
├── matches.json # (when video provided)
├── metadata.json
├── final.mp4
└── clips/ # (when --no-clips not set)
| File | Description |
|---|---|
narration.mp3 |
AI-generated narration audio |
mixed.mp3 |
Narration + BGM overlay (when BGM enabled; otherwise narration.mp3 used directly) |
subtitle.srt |
Synchronized subtitle file (original narration) |
subtitle.<lang>.srt |
Translated subtitle (when --subtitle-lang set) |
subtitle.bilingual.srt |
Bilingual subtitle (when --subtitle-lang set; cue body f"{src}\n{dst}") |
script.md |
Human-readable script |
script.json |
Machine-readable script segments |
research.json |
Movie research data (when --research) |
scenes.json |
Detected scene boundaries (when video provided) |
metadata.json |
Segment timings, pipeline status, config |
final.mp4 |
Rendered video (16:9 or 9:16) |
matches.json |
Scene-to-segment clip matching (when video provided) |
clips/ |
Per-segment clip .mp4 files (when --no-clips not set) |
Pipeline
14-step sequential pipeline (see Architecture):
resolve_video → prepare_assets → research_plot → generate_script →
export_script_md → generate_voice → align_audio → detect_scenes →
match_clips → mix_bgm → translate_subtitles → generate_subtitle →
render_video → export_clips
Soft steps (research, align, scene detect, scene match, BGM, translate, clip export) gracefully skip or soft-degrade when optional dependencies are missing or upstream data is unavailable. Use --strict to abort instead.
Project Structure
movie-narrator/
├── src/movie_narrator/
│ ├── __init__.py # Package metadata (__version__)
│ ├── cli.py # Typer CLI entry point
│ ├── config.py # Pydantic settings
│ ├── models.py # Data models (Context, Status, etc.)
│ ├── pipeline/
│ │ ├── runner.py # 14-step pipeline orchestrator
│ │ ├── resolve.py # Source video resolution
│ │ ├── assets.py # Asset validation
│ │ ├── research.py # LLM movie research
│ │ ├── script.py # LLM script generation
│ │ ├── script_export.py # Script markdown export
│ │ ├── tts.py # TTS orchestration (uses tts/ package; caching + concurrency)
│ │ ├── align.py # WhisperX audio alignment
│ │ ├── scenes.py # PySceneDetect scene detection
│ │ ├── match.py # Heuristic clip matching
│ │ ├── bgm.py # Background music mixing
│ │ ├── translate.py # Multi-language subtitle translation (LLM)
│ │ ├── subtitle.py # SRT generation (single / translated / bilingual)
│ │ ├── render.py # MoviePy video rendering
│ │ ├── export_clips.py # Per-segment clip export
│ │ └── errors.py # PipelineStrictError
│ ├── workflow/
│ │ ├── schema.py # JobConfig / JobSteps / JobParams
│ │ ├── load.py # YAML loader + validation
│ │ ├── merge.py # CLI > YAML > Settings merge
│ │ └── errors.py # JobConfigError
│ ├── tts/ # TTS abstraction layer (v0.4)
│ │ ├── __init__.py # re-exports public API
│ │ ├── protocol.py # TTSProvider ABC
│ │ ├── base.py # BaseTTSProvider (CI silent fallback), is_ci()
│ │ ├── edge.py # EdgeTTSProvider
│ │ ├── openai_provider.py # OpenAITTSProvider (voice whitelist, lazy SDK)
│ │ ├── mimo_provider.py # MimoTTSProvider (3 models: named voice, voice clone, voice design)
│ │ ├── factory.py # get_tts_provider(settings)
│ │ └── cache.py # TTSCacheKey, cache_path_for, PROVIDER_CACHE_VERSIONS
│ ├── utils/
│ │ ├── async_utils.py # Sync/async bridge
│ │ ├── console.py # Console Protocol + PlainConsole + build_console
│ │ ├── environment.py # Environment collection
│ │ ├── errors.py # ConfigError (cross-cutting config-error class)
│ │ ├── font.py # CJK font fallback
│ │ ├── json_parser.py # LLM JSON extraction (with truncation recovery)
│ │ ├── llm.py # OpenAI client wrapper
│ │ ├── log.py # AppLogger (file logging layer)
│ │ ├── metadata_export.py # metadata.json builder
│ │ ├── optional_deps.py # Optional dependency probing
│ │ ├── prompts.py # Prompt templates
│ │ └── retention.py # Log file retention
│ └── web/ # Gradio browser UI (v0.3.5; requires [web] extra)
│ ├── __init__.py # lazy launch_web export
│ ├── __main__.py # python -m movie_narrator.web
│ ├── app.py # Gradio Blocks layout + event handlers
│ ├── bridge.py # form → background thread → yield UI updates
│ ├── form.py # FormData + validate_form + form_to_context_args
│ ├── console.py # GradioConsole (thread-safe via threading.Lock)
│ ├── controller.py # GradioController (cooperative cancel flag)
│ ├── models.py # RunStatus enum + WebRun per-session state
│ └── utils.py # upload handling + collect_artifacts + sanitize_filename
├── tests/
│ ├── test_context.py
│ ├── test_settings.py
│ ├── test_errors.py
│ ├── test_align.py
│ ├── test_assets.py
│ ├── test_bgm.py
│ ├── test_cli_config.py
│ ├── test_cli_resolve.py
│ ├── test_match.py
│ ├── test_optional_deps.py
│ ├── test_render_real.py
│ ├── test_research.py
│ ├── test_resolve.py
│ ├── test_runner_strict.py
│ ├── test_runner_workflow_metadata.py
│ ├── test_scenes.py
│ ├── test_script_export.py
│ ├── test_translate.py
│ ├── test_json_parser.py
│ ├── test_pipeline_cancel.py
│ ├── test_web_console.py
│ ├── test_web_controller.py
│ ├── test_web_form.py
│ └── test_workflow_steps.py
├── docs/
├── assets/
└── .github/workflows/
Roadmap
v0.1.x — Core Pipeline ✅
- CLI interface (
mn create,mn version) - LLM script generation with JSON output
- Edge-TTS narration with concurrent generation
- SRT subtitle generation with millisecond precision
- MoviePy video rendering (16:9 / 9:16)
- TTS result caching with content-addressable keys
- Metadata export (JSON)
- CI pipeline (unit tests + smoke test)
v0.2.x — Scene & Media ✅
- Research agent for movie plot research (
--research) - WhisperX audio-text alignment
- Scene detection from movie videos
- Automatic clip matching based on script
- Semantic scene search (embedding-based, requires
[ml]) - Background music integration (BGM mixing)
- Script markdown export (
script.md) - Scene-level clip output (
clips/)
v0.3.x — Platform & Workflow ✅
- Declarative workflow config for soft-step toggles + params
- YAML-based job configuration (
mn create --config) - Console / structured-step-state logging refactor (
ctx.services.console,StepState) - Multi-language subtitle support (
--subtitle-lang/--subtitle-mode; LLM translation with retry-then-soft-degrade;subtitle.<lang>.srt+subtitle.bilingual.srtoutputs) - Web UI (Gradio local browser app via
mn web; cooperative cancel; requires[web]extra)
v0.4.x — TTS Abstraction & Extensibility
- TTS provider abstraction (
TTSProviderprotocol, Edge + OpenAI + MiMo backends) - Provider selection via
MN_TTS_PROVIDER(edge/openai/mimo) - OpenAI TTS support (voice whitelist, credential fallback, lazy SDK import)
- MiMo TTS support (3 models: named voice, voice clone, voice design; limited-time free)
- Cache key upgrade (sha256, 7 dimensions, two-level fan-out, per-provider version map)
- CI temp-file isolation (silent audio never enters cache)
-
is_ci()single source of truth for CI detection -
ConfigErrorcross-cutting error class - Plugin system for custom pipeline steps
- Python SDK for programmatic usage
- Third-party extension support
Documentation
License
Licensed under the AGPL-3.0 License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file movie_narrator-0.4.1.tar.gz.
File metadata
- Download URL: movie_narrator-0.4.1.tar.gz
- Upload date:
- Size: 97.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2d0e9d848c3630ff1a42fda8ebc09aaeb3ebdf52c731a7ccb5e58e634f7569f
|
|
| MD5 |
060fcdd20b7990e035c6ebf5650e025c
|
|
| BLAKE2b-256 |
8532edb1bb172a84fc4f791ab13a48474331aa9dfed1f0d97755cfac5638bc76
|
Provenance
The following attestation bundles were made for movie_narrator-0.4.1.tar.gz:
Publisher:
publish.yml on zcbacxc/movie-narrator
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
movie_narrator-0.4.1.tar.gz -
Subject digest:
a2d0e9d848c3630ff1a42fda8ebc09aaeb3ebdf52c731a7ccb5e58e634f7569f - Sigstore transparency entry: 2166403555
- Sigstore integration time:
-
Permalink:
zcbacxc/movie-narrator@496692a5f894ac1271e8a79eb2b1de1a19a85466 -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/zcbacxc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@496692a5f894ac1271e8a79eb2b1de1a19a85466 -
Trigger Event:
push
-
Statement type:
File details
Details for the file movie_narrator-0.4.1-py3-none-any.whl.
File metadata
- Download URL: movie_narrator-0.4.1-py3-none-any.whl
- Upload date:
- Size: 83.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
969c692a4125ea019d6dd320bce372aba251ea1183409a61a49174026a8b1be3
|
|
| MD5 |
8f69026ef1f6b14d623ca0e269fbeab8
|
|
| BLAKE2b-256 |
db798ea674772949c400cf0c2761c87bb51848d9c37fb287968362e94e6e4b3a
|
Provenance
The following attestation bundles were made for movie_narrator-0.4.1-py3-none-any.whl:
Publisher:
publish.yml on zcbacxc/movie-narrator
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
movie_narrator-0.4.1-py3-none-any.whl -
Subject digest:
969c692a4125ea019d6dd320bce372aba251ea1183409a61a49174026a8b1be3 - Sigstore transparency entry: 2166403593
- Sigstore integration time:
-
Permalink:
zcbacxc/movie-narrator@496692a5f894ac1271e8a79eb2b1de1a19a85466 -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/zcbacxc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@496692a5f894ac1271e8a79eb2b1de1a19a85466 -
Trigger Event:
push
-
Statement type: