videopython 0.42.0__tar.gz → 0.43.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {videopython-0.42.0 → videopython-0.43.0}/.gitignore +7 -0
- {videopython-0.42.0 → videopython-0.43.0}/PKG-INFO +46 -7
- {videopython-0.42.0 → videopython-0.43.0}/README.md +3 -1
- {videopython-0.42.0 → videopython-0.43.0}/pyproject.toml +78 -64
- videopython-0.43.0/src/videopython/ai/__init__.py +82 -0
- videopython-0.43.0/src/videopython/ai/_optional.py +47 -0
- videopython-0.43.0/src/videopython/ai/dubbing/__init__.py +63 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/dubbing/dubber.py +13 -2
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/dubbing/loudness.py +3 -1
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/dubbing/pipeline.py +28 -3
- videopython-0.43.0/src/videopython/ai/generation/__init__.py +43 -0
- videopython-0.43.0/src/videopython/ai/generation/_tts_backend.py +56 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/generation/audio.py +10 -2
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/generation/image.py +4 -1
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/generation/qwen3.py +4 -1
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/generation/translation.py +5 -1
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/generation/video.py +8 -2
- videopython-0.43.0/src/videopython/ai/understanding/__init__.py +45 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/understanding/audio.py +19 -6
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/understanding/faces.py +4 -1
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/understanding/image.py +10 -2
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/understanding/objects.py +3 -1
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/understanding/separation.py +3 -1
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/understanding/temporal.py +3 -1
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/video_analysis/sampling.py +3 -1
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/audio/audio.py +32 -15
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/exceptions.py +3 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/video.py +54 -2
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/editing/__init__.py +2 -1
- videopython-0.43.0/src/videopython/editing/audio_ops.py +221 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/editing/effects.py +102 -5
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/editing/operation.py +31 -16
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/editing/streaming.py +470 -34
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/editing/transforms.py +135 -22
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/editing/video_edit.py +878 -102
- videopython-0.42.0/src/videopython/ai/__init__.py +0 -36
- videopython-0.42.0/src/videopython/ai/dubbing/__init__.py +0 -31
- videopython-0.42.0/src/videopython/ai/generation/__init__.py +0 -11
- videopython-0.42.0/src/videopython/ai/understanding/__init__.py +0 -14
- {videopython-0.42.0 → videopython-0.43.0}/LICENSE +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/__init__.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/_device.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/dubbing/config.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/dubbing/expressiveness.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/dubbing/models.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/dubbing/quality.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/dubbing/remux.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/dubbing/timing.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/dubbing/voice_sample.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/effects.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/transforms.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/video_analysis/__init__.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/video_analysis/analyzer.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/video_analysis/models.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/ai/video_analysis/stages.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/audio/__init__.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/audio/analysis.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/__init__.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/_dimensions.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/_ffmpeg.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/_video_io.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/description.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/draw_detections.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/fonts/Anton-OFL.txt +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/fonts/Anton-Regular.ttf +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/fonts/BebasNeue-OFL.txt +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/fonts/BebasNeue-Regular.ttf +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/fonts/DejaVuSans.ttf +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/fonts/LICENSE_DEJAVU +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/fonts/Lato-Bold.ttf +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/fonts/Lato-OFL.txt +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/fonts/Poppins-Bold.ttf +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/fonts/Poppins-OFL.txt +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/fonts/__init__.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/base/transcription.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/editing/_ass.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/editing/_easing.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/editing/transcription_overlay.py +0 -0
- {videopython-0.42.0 → videopython-0.43.0}/src/videopython/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: videopython
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.43.0
|
|
4
4
|
Summary: Minimal video generation and processing library.
|
|
5
5
|
Project-URL: Homepage, https://videopython.com
|
|
6
6
|
Project-URL: Repository, https://github.com/bartwojtowicz/videopython/
|
|
@@ -27,17 +27,12 @@ Requires-Dist: accelerate>=0.29.2; extra == 'ai'
|
|
|
27
27
|
Requires-Dist: chatterbox-tts>=0.1.7; extra == 'ai'
|
|
28
28
|
Requires-Dist: demucs>=4.0.0; extra == 'ai'
|
|
29
29
|
Requires-Dist: diffusers>=0.30.0; extra == 'ai'
|
|
30
|
-
Requires-Dist: hf-transfer>=0.1.9; extra == 'ai'
|
|
31
30
|
Requires-Dist: imagehash>=4.3; extra == 'ai'
|
|
32
31
|
Requires-Dist: llama-cpp-python>=0.3.0; extra == 'ai'
|
|
33
|
-
Requires-Dist: numba>=0.61.0; extra == 'ai'
|
|
34
|
-
Requires-Dist: ollama>=0.4.5; extra == 'ai'
|
|
35
32
|
Requires-Dist: openai-whisper>=20240930; extra == 'ai'
|
|
36
33
|
Requires-Dist: pyannote-audio>=4.0.0; extra == 'ai'
|
|
37
34
|
Requires-Dist: pyloudnorm>=0.1.1; extra == 'ai'
|
|
38
35
|
Requires-Dist: qwen-vl-utils>=0.0.10; extra == 'ai'
|
|
39
|
-
Requires-Dist: scikit-learn>=1.3.0; extra == 'ai'
|
|
40
|
-
Requires-Dist: scipy>=1.10.0; extra == 'ai'
|
|
41
36
|
Requires-Dist: sentencepiece>=0.1.99; extra == 'ai'
|
|
42
37
|
Requires-Dist: silero-vad>=5.1; extra == 'ai'
|
|
43
38
|
Requires-Dist: torch>=2.8.0; extra == 'ai'
|
|
@@ -45,6 +40,48 @@ Requires-Dist: torchaudio>=2.8.0; extra == 'ai'
|
|
|
45
40
|
Requires-Dist: transformers>=5.2.0; extra == 'ai'
|
|
46
41
|
Requires-Dist: transnetv2-pytorch>=1.0.5; extra == 'ai'
|
|
47
42
|
Requires-Dist: ultralytics>=8.0.0; extra == 'ai'
|
|
43
|
+
Provides-Extra: asr
|
|
44
|
+
Requires-Dist: openai-whisper>=20240930; extra == 'asr'
|
|
45
|
+
Requires-Dist: pyannote-audio>=4.0.0; extra == 'asr'
|
|
46
|
+
Requires-Dist: silero-vad>=5.1; extra == 'asr'
|
|
47
|
+
Requires-Dist: torch>=2.8.0; extra == 'asr'
|
|
48
|
+
Requires-Dist: torchaudio>=2.8.0; extra == 'asr'
|
|
49
|
+
Provides-Extra: dub
|
|
50
|
+
Requires-Dist: demucs>=4.0.0; extra == 'dub'
|
|
51
|
+
Requires-Dist: llama-cpp-python>=0.3.0; extra == 'dub'
|
|
52
|
+
Requires-Dist: openai-whisper>=20240930; extra == 'dub'
|
|
53
|
+
Requires-Dist: pyannote-audio>=4.0.0; extra == 'dub'
|
|
54
|
+
Requires-Dist: pyloudnorm>=0.1.1; extra == 'dub'
|
|
55
|
+
Requires-Dist: sentencepiece>=0.1.99; extra == 'dub'
|
|
56
|
+
Requires-Dist: silero-vad>=5.1; extra == 'dub'
|
|
57
|
+
Requires-Dist: torch>=2.8.0; extra == 'dub'
|
|
58
|
+
Requires-Dist: torchaudio>=2.8.0; extra == 'dub'
|
|
59
|
+
Requires-Dist: transformers>=5.2.0; extra == 'dub'
|
|
60
|
+
Provides-Extra: generation
|
|
61
|
+
Requires-Dist: accelerate>=0.29.2; extra == 'generation'
|
|
62
|
+
Requires-Dist: diffusers>=0.30.0; extra == 'generation'
|
|
63
|
+
Requires-Dist: torch>=2.8.0; extra == 'generation'
|
|
64
|
+
Requires-Dist: torchaudio>=2.8.0; extra == 'generation'
|
|
65
|
+
Requires-Dist: transformers>=5.2.0; extra == 'generation'
|
|
66
|
+
Provides-Extra: separation
|
|
67
|
+
Requires-Dist: demucs>=4.0.0; extra == 'separation'
|
|
68
|
+
Requires-Dist: torch>=2.8.0; extra == 'separation'
|
|
69
|
+
Requires-Dist: torchaudio>=2.8.0; extra == 'separation'
|
|
70
|
+
Provides-Extra: translation
|
|
71
|
+
Requires-Dist: llama-cpp-python>=0.3.0; extra == 'translation'
|
|
72
|
+
Requires-Dist: sentencepiece>=0.1.99; extra == 'translation'
|
|
73
|
+
Requires-Dist: torch>=2.8.0; extra == 'translation'
|
|
74
|
+
Requires-Dist: transformers>=5.2.0; extra == 'translation'
|
|
75
|
+
Provides-Extra: tts
|
|
76
|
+
Requires-Dist: chatterbox-tts>=0.1.7; extra == 'tts'
|
|
77
|
+
Provides-Extra: vision
|
|
78
|
+
Requires-Dist: imagehash>=4.3; extra == 'vision'
|
|
79
|
+
Requires-Dist: qwen-vl-utils>=0.0.10; extra == 'vision'
|
|
80
|
+
Requires-Dist: torch>=2.8.0; extra == 'vision'
|
|
81
|
+
Requires-Dist: torchaudio>=2.8.0; extra == 'vision'
|
|
82
|
+
Requires-Dist: transformers>=5.2.0; extra == 'vision'
|
|
83
|
+
Requires-Dist: transnetv2-pytorch>=1.0.5; extra == 'vision'
|
|
84
|
+
Requires-Dist: ultralytics>=8.0.0; extra == 'vision'
|
|
48
85
|
Description-Content-Type: text/markdown
|
|
49
86
|
|
|
50
87
|
# videopython
|
|
@@ -64,9 +101,11 @@ Full documentation: [videopython.com](https://videopython.com)
|
|
|
64
101
|
```bash
|
|
65
102
|
# Install FFmpeg first (macOS: brew install ffmpeg | Debian: apt-get install ffmpeg)
|
|
66
103
|
pip install videopython # core video/audio editing
|
|
67
|
-
pip install "videopython[ai]" # + local AI features (GPU recommended)
|
|
104
|
+
pip install "videopython[ai]" # + ALL local AI features (GPU recommended)
|
|
68
105
|
```
|
|
69
106
|
|
|
107
|
+
AI deps are also split into granular extras so you can install only what you need: `[asr]` (transcription), `[vision]` (detection/scene/VLM), `[separation]`, `[translation]`, `[tts]` (voice cloning), `[generation]` (image/video/music), and `[dub]` (the dubbing pipeline). `[dub]` excludes chatterbox — add `[tts]` for local synthesis (`pip install "videopython[dub,tts]"`) or inject a `SpeechBackend`. See the [Installation Guide](https://videopython.com/getting-started/installation/) for the full table.
|
|
108
|
+
|
|
70
109
|
Python `>=3.11, <3.14`. AI features run locally — no cloud API keys required, but model weights are downloaded on first use.
|
|
71
110
|
|
|
72
111
|
## Quick Start
|
|
@@ -15,9 +15,11 @@ Full documentation: [videopython.com](https://videopython.com)
|
|
|
15
15
|
```bash
|
|
16
16
|
# Install FFmpeg first (macOS: brew install ffmpeg | Debian: apt-get install ffmpeg)
|
|
17
17
|
pip install videopython # core video/audio editing
|
|
18
|
-
pip install "videopython[ai]" # + local AI features (GPU recommended)
|
|
18
|
+
pip install "videopython[ai]" # + ALL local AI features (GPU recommended)
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
AI deps are also split into granular extras so you can install only what you need: `[asr]` (transcription), `[vision]` (detection/scene/VLM), `[separation]`, `[translation]`, `[tts]` (voice cloning), `[generation]` (image/video/music), and `[dub]` (the dubbing pipeline). `[dub]` excludes chatterbox — add `[tts]` for local synthesis (`pip install "videopython[dub,tts]"`) or inject a `SpeechBackend`. See the [Installation Guide](https://videopython.com/getting-started/installation/) for the full table.
|
|
22
|
+
|
|
21
23
|
Python `>=3.11, <3.14`. AI features run locally — no cloud API keys required, but model weights are downloaded on first use.
|
|
22
24
|
|
|
23
25
|
## Quick Start
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "videopython"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.43.0"
|
|
4
4
|
description = "Minimal video generation and processing library."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Bartosz Wójtowicz", email = "bartoszwojtowicz@outlook.com" },
|
|
@@ -53,81 +53,90 @@ dev = [
|
|
|
53
53
|
"mkdocstrings>=1.0.0",
|
|
54
54
|
"mkdocstrings-python>=2.0.1",
|
|
55
55
|
]
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
# Thin dev-workflow group so `uv sync` / uv.lock keep working. Resolves the
|
|
57
|
+
# SAME set as the aggregate `[ai]` extra below via a PEP 508 self-reference, so
|
|
58
|
+
# there is a single source of truth (the granular extras) and no hand-sync.
|
|
59
|
+
# `test_packaging_extras.py` asserts this stays consistent with the extra.
|
|
60
|
+
ai = ["videopython[ai]"]
|
|
61
|
+
|
|
62
|
+
# pip install videopython[<extra>] - pip uses optional-dependencies, not dependency-groups.
|
|
63
|
+
#
|
|
64
|
+
# Granular extras partition the heavy ML deps by the leaf module that imports
|
|
65
|
+
# them, so consumers install only what a capability needs. `ai` is a convenience
|
|
66
|
+
# aggregate that installs EVERYTHING via PEP 685 self-references (no hand-sync).
|
|
67
|
+
# `test_packaging_extras.py` enforces union(granular) == ai as a drift guard.
|
|
68
|
+
[project.optional-dependencies]
|
|
69
|
+
# Speech recognition / diarization (understanding/audio.py).
|
|
70
|
+
asr = [
|
|
63
71
|
"openai-whisper>=20240930",
|
|
64
72
|
"pyannote-audio>=4.0.0",
|
|
65
73
|
# Voice activity detection (used by AudioToText to gate Whisper language detection)
|
|
66
74
|
"silero-vad>=5.1",
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
"torch>=2.8.0",
|
|
76
|
+
"torchaudio>=2.8.0",
|
|
77
|
+
]
|
|
78
|
+
# Visual understanding: detection, scene/temporal, VLM (understanding/{faces,
|
|
79
|
+
# objects,temporal,image}.py + video_analysis). transformers also backs the AST
|
|
80
|
+
# AudioClassifier and the SceneVLM; qwen-vl-utils is the SceneVLM preprocessor;
|
|
81
|
+
# imagehash is the SceneVLM frame-dedup; transnetv2 is scene detection.
|
|
82
|
+
vision = [
|
|
72
83
|
"ultralytics>=8.0.0",
|
|
73
|
-
# Audio classification (AST via transformers - no separate dep needed)
|
|
74
|
-
# Scene detection
|
|
75
84
|
"transnetv2-pytorch>=1.0.5",
|
|
76
|
-
|
|
77
|
-
"chatterbox-tts>=0.1.7",
|
|
78
|
-
# Translation (Marian MT tokenizer requires sentencepiece)
|
|
79
|
-
"sentencepiece>=0.1.99",
|
|
80
|
-
# Audio source separation
|
|
81
|
-
"demucs>=4.0.0",
|
|
82
|
-
# Translation backend: Qwen3 GGUF inference (M2)
|
|
83
|
-
"llama-cpp-python>=0.3.0",
|
|
84
|
-
# Loudness measurement (BS.1770) for dub-vs-source loudness matching (M3)
|
|
85
|
-
"pyloudnorm>=0.1.1",
|
|
86
|
-
# Vision-language preprocessing for Qwen3.5 (M5) - documented prerequisite
|
|
87
|
-
# for AutoModelForImageTextToText with image/video chat templates.
|
|
85
|
+
"transformers>=5.2.0",
|
|
88
86
|
"qwen-vl-utils>=0.0.10",
|
|
89
|
-
# Perceptual hashing for SceneVLM frame dedup (M5)
|
|
90
87
|
"imagehash>=4.3",
|
|
88
|
+
"torch>=2.8.0",
|
|
89
|
+
"torchaudio>=2.8.0",
|
|
91
90
|
]
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
ai = [
|
|
96
|
-
"accelerate>=0.29.2",
|
|
97
|
-
"diffusers>=0.30.0",
|
|
98
|
-
"hf-transfer>=0.1.9",
|
|
91
|
+
# Audio source separation (understanding/separation.py).
|
|
92
|
+
separation = [
|
|
93
|
+
"demucs>=4.0.0",
|
|
99
94
|
"torch>=2.8.0",
|
|
100
95
|
"torchaudio>=2.8.0",
|
|
96
|
+
]
|
|
97
|
+
# Text translation backends (generation/translation.py MarianMT +
|
|
98
|
+
# generation/qwen3.py Qwen3 GGUF). MarianTokenizer requires sentencepiece.
|
|
99
|
+
translation = [
|
|
101
100
|
"transformers>=5.2.0",
|
|
102
|
-
"openai-whisper>=20240930",
|
|
103
|
-
"pyannote-audio>=4.0.0",
|
|
104
|
-
# Voice activity detection (used by AudioToText to gate Whisper language detection)
|
|
105
|
-
"silero-vad>=5.1",
|
|
106
|
-
"numba>=0.61.0",
|
|
107
|
-
"ollama>=0.4.5",
|
|
108
|
-
"scipy>=1.10.0",
|
|
109
|
-
"scikit-learn>=1.3.0",
|
|
110
|
-
# Detection backends
|
|
111
|
-
"ultralytics>=8.0.0",
|
|
112
|
-
# Audio classification (AST via transformers - no separate dep needed)
|
|
113
|
-
# Scene detection
|
|
114
|
-
"transnetv2-pytorch>=1.0.5",
|
|
115
|
-
# Voice cloning TTS (Chatterbox Multilingual by Resemble AI)
|
|
116
|
-
"chatterbox-tts>=0.1.7",
|
|
117
|
-
# Translation (Marian MT tokenizer requires sentencepiece)
|
|
118
101
|
"sentencepiece>=0.1.99",
|
|
119
|
-
# Audio source separation
|
|
120
|
-
"demucs>=4.0.0",
|
|
121
|
-
# Translation backend: Qwen3 GGUF inference (M2)
|
|
122
102
|
"llama-cpp-python>=0.3.0",
|
|
103
|
+
"torch>=2.8.0",
|
|
104
|
+
]
|
|
105
|
+
# Voice cloning TTS (generation/audio.py — Chatterbox Multilingual by Resemble
|
|
106
|
+
# AI). ISOLATED: chatterbox-tts 0.1.7 carries the strict pins that force the
|
|
107
|
+
# [tool.uv].override-dependencies block. This is the ONLY extra that drags them.
|
|
108
|
+
# Left to chatterbox's own torch==2.6.0 / torchaudio==2.6.0 pins (no floors
|
|
109
|
+
# here) so `pip install videopython[tts]` resolves STANDALONE in its own
|
|
110
|
+
# image/Modal function -- the whole point of running TTS behind SpeechBackend.
|
|
111
|
+
# The all-in [ai] resolve forces torch>=2.8.0 over this via override-dependencies.
|
|
112
|
+
tts = [
|
|
113
|
+
"chatterbox-tts>=0.1.7",
|
|
114
|
+
]
|
|
115
|
+
# Local media generation: SDXL/CogVideoX (generation/{image,video}.py) +
|
|
116
|
+
# MusicGen (generation/audio.py TextToMusic, via transformers).
|
|
117
|
+
generation = [
|
|
118
|
+
"diffusers>=0.30.0",
|
|
119
|
+
"transformers>=5.2.0",
|
|
120
|
+
"accelerate>=0.29.2",
|
|
121
|
+
"torch>=2.8.0",
|
|
122
|
+
"torchaudio>=2.8.0",
|
|
123
|
+
]
|
|
124
|
+
# Dubbing pipeline's hard top-level deps: transcription (asr) + source
|
|
125
|
+
# separation (separation) + translation, plus BS.1770 loudness matching
|
|
126
|
+
# (dubbing/loudness.py). Deliberately EXCLUDES tts/chatterbox: local synthesis
|
|
127
|
+
# runs through the SpeechBackend protocol against a separately-installed [tts]
|
|
128
|
+
# extra or an injected remote backend, so a [dub] image co-resolves WITHOUT
|
|
129
|
+
# chatterbox's override-forcing pin. A bare [dub] install that hits local
|
|
130
|
+
# synthesis raises a clear [tts]-pointing ImportError.
|
|
131
|
+
dub = [
|
|
132
|
+
"videopython[asr,separation,translation]",
|
|
123
133
|
# Loudness measurement (BS.1770) for dub-vs-source loudness matching (M3)
|
|
124
134
|
"pyloudnorm>=0.1.1",
|
|
125
|
-
# Vision-language preprocessing for Qwen3.5 (M5) - documented prerequisite
|
|
126
|
-
# for AutoModelForImageTextToText with image/video chat templates.
|
|
127
|
-
"qwen-vl-utils>=0.0.10",
|
|
128
|
-
# Perceptual hashing for SceneVLM frame dedup (M5)
|
|
129
|
-
"imagehash>=4.3",
|
|
130
135
|
]
|
|
136
|
+
# Convenience aggregate: installs every capability. PEP 685 self-references make
|
|
137
|
+
# this the single "everything" install with no hand-duplicated dep list. This
|
|
138
|
+
# combined resolve is what the [tool.uv].override-dependencies block exists for.
|
|
139
|
+
ai = ["videopython[asr,vision,separation,translation,tts,generation,dub]"]
|
|
131
140
|
|
|
132
141
|
[project.urls]
|
|
133
142
|
Homepage = "https://videopython.com"
|
|
@@ -163,11 +172,16 @@ module = [
|
|
|
163
172
|
ignore_missing_imports = true
|
|
164
173
|
|
|
165
174
|
[tool.uv]
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
#
|
|
175
|
+
# LOAD-BEARING ONLY for the all-in `[ai]` resolve (every extra installed
|
|
176
|
+
# together, as in dev/CI and `uv sync`). chatterbox-tts 0.1.7 (the `[tts]`
|
|
177
|
+
# extra) pins strict torch/torchaudio/numpy/diffusers that conflict with
|
|
178
|
+
# pyannote-audio (torch>=2.8, `[asr]`) and CogVideoX (diffusers>=0.30,
|
|
179
|
+
# `[generation]`). This override lets the combined resolver pick compatible
|
|
180
|
+
# versions. A consumer installing a SUBSET that excludes chatterbox — notably
|
|
181
|
+
# `[dub]` (asr+separation+translation+pyloudnorm, no tts) — co-resolves cleanly
|
|
182
|
+
# WITHOUT this override; that clean `[dub]` resolve is the whole point of
|
|
183
|
+
# isolating `[tts]`. The ai dependency floors are aligned with these overrides
|
|
184
|
+
# to keep pip and uv resolving similar versions.
|
|
171
185
|
override-dependencies = [
|
|
172
186
|
"torch>=2.8.0", "torchaudio>=2.8.0", "numpy>=2.0.0", "diffusers>=0.30.0",
|
|
173
187
|
# ultralytics depends on opencv-python which conflicts with our
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""AI-powered generation, understanding, dubbing, and analysis.
|
|
2
|
+
|
|
3
|
+
Every public symbol is re-exported lazily via PEP 562 ``__getattr__`` so that
|
|
4
|
+
``import videopython.ai`` (or ``from videopython.ai import X``) loads ONLY the
|
|
5
|
+
leaf module backing the requested symbol — not every sibling. This is what
|
|
6
|
+
makes the granular extras (``asr``, ``vision``, ``tts``, ``generation``,
|
|
7
|
+
``dub``, ...) usable: with only ``[asr]`` installed, ``from videopython.ai
|
|
8
|
+
import AudioToText`` works, while touching ``TextToSpeech`` raises a clear
|
|
9
|
+
``[tts]``-pointing ``ImportError`` at attribute access instead of failing the
|
|
10
|
+
whole package import.
|
|
11
|
+
|
|
12
|
+
The ``TYPE_CHECKING`` block below keeps the eager imports visible to mypy and
|
|
13
|
+
IDEs (static autocompletion / re-export checking) without executing them at
|
|
14
|
+
runtime.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import importlib
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
# Redundant aliases mark these as intentional re-exports (mypy/IDE see the
|
|
24
|
+
# symbols; ruff doesn't flag them as unused). Runtime resolution is lazy via
|
|
25
|
+
# __getattr__ below.
|
|
26
|
+
from .effects import ObjectDetectionOverlay as ObjectDetectionOverlay
|
|
27
|
+
from .generation import ImageToVideo as ImageToVideo
|
|
28
|
+
from .generation import TextToImage as TextToImage
|
|
29
|
+
from .generation import TextToMusic as TextToMusic
|
|
30
|
+
from .generation import TextToSpeech as TextToSpeech
|
|
31
|
+
from .generation import TextToVideo as TextToVideo
|
|
32
|
+
from .transforms import FaceTrackingCrop as FaceTrackingCrop
|
|
33
|
+
from .understanding import AudioClassifier as AudioClassifier
|
|
34
|
+
from .understanding import AudioToText as AudioToText
|
|
35
|
+
from .understanding import FaceTracker as FaceTracker
|
|
36
|
+
from .understanding import ObjectDetector as ObjectDetector
|
|
37
|
+
from .understanding import SceneVLM as SceneVLM
|
|
38
|
+
from .understanding import SemanticSceneDetector as SemanticSceneDetector
|
|
39
|
+
from .video_analysis import VideoAnalysis as VideoAnalysis
|
|
40
|
+
from .video_analysis import VideoAnalysisConfig as VideoAnalysisConfig
|
|
41
|
+
from .video_analysis import VideoAnalyzer as VideoAnalyzer
|
|
42
|
+
|
|
43
|
+
# Public symbol -> submodule (relative to this package) that defines it. The
|
|
44
|
+
# submodule's own (lazy) __init__ resolves the symbol; we only import the
|
|
45
|
+
# submodule, never its siblings.
|
|
46
|
+
_SYMBOL_MODULES: dict[str, str] = {
|
|
47
|
+
# Generation
|
|
48
|
+
"TextToVideo": ".generation",
|
|
49
|
+
"ImageToVideo": ".generation",
|
|
50
|
+
"TextToImage": ".generation",
|
|
51
|
+
"TextToSpeech": ".generation",
|
|
52
|
+
"TextToMusic": ".generation",
|
|
53
|
+
# Understanding
|
|
54
|
+
"AudioToText": ".understanding",
|
|
55
|
+
"AudioClassifier": ".understanding",
|
|
56
|
+
"FaceTracker": ".understanding",
|
|
57
|
+
"ObjectDetector": ".understanding",
|
|
58
|
+
"SceneVLM": ".understanding",
|
|
59
|
+
"SemanticSceneDetector": ".understanding",
|
|
60
|
+
# Transforms (AI-powered)
|
|
61
|
+
"FaceTrackingCrop": ".transforms",
|
|
62
|
+
# Effects (AI-powered)
|
|
63
|
+
"ObjectDetectionOverlay": ".effects",
|
|
64
|
+
# Video analysis
|
|
65
|
+
"VideoAnalysis": ".video_analysis",
|
|
66
|
+
"VideoAnalysisConfig": ".video_analysis",
|
|
67
|
+
"VideoAnalyzer": ".video_analysis",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
__all__ = list(_SYMBOL_MODULES)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def __getattr__(name: str) -> object:
|
|
74
|
+
module_name = _SYMBOL_MODULES.get(name)
|
|
75
|
+
if module_name is None:
|
|
76
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
77
|
+
module = importlib.import_module(module_name, __name__)
|
|
78
|
+
return getattr(module, name)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def __dir__() -> list[str]:
|
|
82
|
+
return sorted(__all__)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Helpers for guarding optional heavy-dependency imports.
|
|
2
|
+
|
|
3
|
+
Every heavy ML dependency in ``videopython.ai`` is imported lazily inside the
|
|
4
|
+
method that needs it (there are no top-level ``import torch``/``transformers``/
|
|
5
|
+
``chatterbox`` statements anywhere under ``ai/``). That keeps leaf modules
|
|
6
|
+
importable on a slim install: importing ``ai.understanding.objects`` only needs
|
|
7
|
+
core deps until a detector is actually constructed.
|
|
8
|
+
|
|
9
|
+
The granular extras (``asr``, ``vision``, ``tts``, ...) partition those heavy
|
|
10
|
+
deps. When a user installs only one extra and reaches a code path needing a dep
|
|
11
|
+
from another, the bare ``import`` would raise a stock ``ModuleNotFoundError``
|
|
12
|
+
with no hint about which extra restores it. :func:`require` wraps the import and
|
|
13
|
+
turns that into an actionable ``ImportError`` pointing at the right
|
|
14
|
+
``pip install 'videopython[<extra>]'``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import importlib
|
|
20
|
+
from types import ModuleType
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def require(module: str, extra: str, *, feature: str | None = None) -> ModuleType:
|
|
24
|
+
"""Import ``module`` or raise a clear, extra-pointing ``ImportError``.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
module: Dotted module name to import (e.g. ``"chatterbox.mtl_tts"``,
|
|
28
|
+
``"torch"``). The fully-imported module object is returned so the
|
|
29
|
+
call site can bind the symbols it needs.
|
|
30
|
+
extra: The videopython extra that ships ``module`` (e.g. ``"tts"``,
|
|
31
|
+
``"asr"``). Surfaced verbatim in the install hint.
|
|
32
|
+
feature: Optional human-readable feature name for the message head. When
|
|
33
|
+
omitted the top-level package of ``module`` is used.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The imported module object.
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
ImportError: If ``module`` cannot be imported. The message always
|
|
40
|
+
contains the extra name and a ``pip install 'videopython[<extra>]'``
|
|
41
|
+
hint so the caller knows exactly how to fix it.
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
return importlib.import_module(module)
|
|
45
|
+
except ImportError as exc:
|
|
46
|
+
label = feature or module.split(".")[0]
|
|
47
|
+
raise ImportError(f"{label} requires the '{extra}' extra: pip install 'videopython[{extra}]'") from exc
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Local video dubbing functionality, lazily re-exported (PEP 562).
|
|
2
|
+
|
|
3
|
+
The pipeline's translation backends (``MarianTranslator``/``Qwen3Translator``)
|
|
4
|
+
top-level import transformers/llama-cpp/torch, so eagerly chaining ``pipeline``
|
|
5
|
+
here would drag the ``[dub]`` translation stack in just to read
|
|
6
|
+
``DubbingConfig`` (pydantic-only). Lazy re-exports keep the lightweight
|
|
7
|
+
config/model symbols importable without that; only ``LocalDubbingPipeline`` /
|
|
8
|
+
``VideoDubber`` access loads the heavy chain. The ``TYPE_CHECKING`` block keeps
|
|
9
|
+
symbols visible to mypy and IDEs.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import importlib
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
# Redundant aliases = intentional re-exports (visible to mypy/IDE, not
|
|
19
|
+
# flagged by ruff). Runtime resolution is lazy via __getattr__ below.
|
|
20
|
+
from videopython.ai.dubbing.config import DubbingConfig as DubbingConfig
|
|
21
|
+
from videopython.ai.dubbing.dubber import VideoDubber as VideoDubber
|
|
22
|
+
from videopython.ai.dubbing.models import DubbingResult as DubbingResult
|
|
23
|
+
from videopython.ai.dubbing.models import Expressiveness as Expressiveness
|
|
24
|
+
from videopython.ai.dubbing.models import RevoiceResult as RevoiceResult
|
|
25
|
+
from videopython.ai.dubbing.models import SeparatedAudio as SeparatedAudio
|
|
26
|
+
from videopython.ai.dubbing.models import TranslatedSegment as TranslatedSegment
|
|
27
|
+
from videopython.ai.dubbing.pipeline import LocalDubbingPipeline as LocalDubbingPipeline
|
|
28
|
+
from videopython.ai.dubbing.quality import GarbageTranscriptError as GarbageTranscriptError
|
|
29
|
+
from videopython.ai.dubbing.quality import TranscriptQuality as TranscriptQuality
|
|
30
|
+
from videopython.ai.dubbing.quality import assess_transcript as assess_transcript
|
|
31
|
+
from videopython.ai.dubbing.timing import TimingSynchronizer as TimingSynchronizer
|
|
32
|
+
from videopython.ai.generation.translation import UnsupportedLanguageError as UnsupportedLanguageError
|
|
33
|
+
|
|
34
|
+
# Public symbol -> fully-qualified module that defines it.
|
|
35
|
+
_SYMBOL_MODULES: dict[str, str] = {
|
|
36
|
+
"DubbingConfig": "videopython.ai.dubbing.config",
|
|
37
|
+
"VideoDubber": "videopython.ai.dubbing.dubber",
|
|
38
|
+
"DubbingResult": "videopython.ai.dubbing.models",
|
|
39
|
+
"RevoiceResult": "videopython.ai.dubbing.models",
|
|
40
|
+
"TranslatedSegment": "videopython.ai.dubbing.models",
|
|
41
|
+
"SeparatedAudio": "videopython.ai.dubbing.models",
|
|
42
|
+
"Expressiveness": "videopython.ai.dubbing.models",
|
|
43
|
+
"LocalDubbingPipeline": "videopython.ai.dubbing.pipeline",
|
|
44
|
+
"TimingSynchronizer": "videopython.ai.dubbing.timing",
|
|
45
|
+
"GarbageTranscriptError": "videopython.ai.dubbing.quality",
|
|
46
|
+
"TranscriptQuality": "videopython.ai.dubbing.quality",
|
|
47
|
+
"assess_transcript": "videopython.ai.dubbing.quality",
|
|
48
|
+
"UnsupportedLanguageError": "videopython.ai.generation.translation",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
__all__ = list(_SYMBOL_MODULES)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def __getattr__(name: str) -> object:
|
|
55
|
+
module_name = _SYMBOL_MODULES.get(name)
|
|
56
|
+
if module_name is None:
|
|
57
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
58
|
+
module = importlib.import_module(module_name)
|
|
59
|
+
return getattr(module, name)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def __dir__() -> list[str]:
|
|
63
|
+
return sorted(__all__)
|
|
@@ -10,6 +10,7 @@ from videopython.ai.dubbing.config import DubbingConfig
|
|
|
10
10
|
from videopython.ai.dubbing.models import DubbingResult, RevoiceResult
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
|
+
from videopython.ai.generation._tts_backend import SpeechBackend
|
|
13
14
|
from videopython.base.video import Video
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
@@ -24,10 +25,20 @@ class VideoDubber:
|
|
|
24
25
|
:class:`DubbingConfig` for the full knob list and defaults.
|
|
25
26
|
"""
|
|
26
27
|
|
|
27
|
-
def __init__(
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
config: DubbingConfig | None = None,
|
|
31
|
+
*,
|
|
32
|
+
tts_backend: SpeechBackend | None = None,
|
|
33
|
+
**kwargs: Any,
|
|
34
|
+
):
|
|
28
35
|
if config is not None and kwargs:
|
|
29
36
|
raise TypeError("Pass either `config=` or knob kwargs, not both")
|
|
30
37
|
self.config = config or DubbingConfig(**kwargs)
|
|
38
|
+
# Optional injected speech backend. None -> the pipeline lazily builds
|
|
39
|
+
# the local chatterbox-backed TextToSpeech (requires the [tts] extra).
|
|
40
|
+
# Inject a SpeechBackend to dub with only [dub] installed.
|
|
41
|
+
self._tts_backend = tts_backend
|
|
31
42
|
self._local_pipeline: Any = None
|
|
32
43
|
logger.info(
|
|
33
44
|
"VideoDubber initialized with %s",
|
|
@@ -37,7 +48,7 @@ class VideoDubber:
|
|
|
37
48
|
def _init_local_pipeline(self) -> None:
|
|
38
49
|
from videopython.ai.dubbing.pipeline import LocalDubbingPipeline
|
|
39
50
|
|
|
40
|
-
self._local_pipeline = LocalDubbingPipeline(config=self.config)
|
|
51
|
+
self._local_pipeline = LocalDubbingPipeline(config=self.config, tts_backend=self._tts_backend)
|
|
41
52
|
|
|
42
53
|
def dub(
|
|
43
54
|
self,
|
|
@@ -62,7 +62,9 @@ def loudness_match(target: Audio, reference: Audio) -> Audio:
|
|
|
62
62
|
if not target.data.size or not reference.data.size:
|
|
63
63
|
return target
|
|
64
64
|
|
|
65
|
-
import
|
|
65
|
+
from videopython.ai._optional import require
|
|
66
|
+
|
|
67
|
+
pyloudnorm = require("pyloudnorm", "dub", feature="loudness matching")
|
|
66
68
|
|
|
67
69
|
target_lufs = pyloudnorm.Meter(target.metadata.sample_rate).integrated_loudness(target.data)
|
|
68
70
|
reference_lufs = pyloudnorm.Meter(reference.metadata.sample_rate).integrated_loudness(reference.data)
|
|
@@ -22,6 +22,7 @@ from videopython.ai.generation.translation import (
|
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
24
24
|
from videopython.ai.dubbing.models import TranslatedSegment
|
|
25
|
+
from videopython.ai.generation._tts_backend import SpeechBackend
|
|
25
26
|
from videopython.audio import Audio
|
|
26
27
|
from videopython.base.transcription import Transcription
|
|
27
28
|
|
|
@@ -38,16 +39,29 @@ class LocalDubbingPipeline:
|
|
|
38
39
|
with <=12GB VRAM or hosts with <32GB RAM.
|
|
39
40
|
"""
|
|
40
41
|
|
|
41
|
-
def __init__(
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
config: DubbingConfig | None = None,
|
|
45
|
+
*,
|
|
46
|
+
tts_backend: SpeechBackend | None = None,
|
|
47
|
+
**kwargs: Any,
|
|
48
|
+
):
|
|
42
49
|
# ``DubbingConfig`` consolidates the nine knobs that used to be
|
|
43
50
|
# constructor kwargs. Either ``config=`` or the flat kwargs are
|
|
44
51
|
# accepted (not both) so existing callers don't need to change.
|
|
45
52
|
if config is not None and kwargs:
|
|
46
53
|
raise TypeError("Pass either `config=` or knob kwargs, not both")
|
|
47
54
|
self.config = config or DubbingConfig(**kwargs)
|
|
55
|
+
# Injected speech backend (a SpeechBackend, e.g. a remote/out-of-process
|
|
56
|
+
# synthesizer). When None, _init_tts lazily constructs the local
|
|
57
|
+
# chatterbox-backed TextToSpeech — which requires the [tts] extra.
|
|
58
|
+
# Supplying a backend lets dubbing run with only [dub] installed (no
|
|
59
|
+
# chatterbox in the process).
|
|
60
|
+
self._tts_backend = tts_backend
|
|
48
61
|
logger.info(
|
|
49
|
-
"LocalDubbingPipeline initialized with %s",
|
|
62
|
+
"LocalDubbingPipeline initialized with %s%s",
|
|
50
63
|
" ".join(f"{k}={v}" for k, v in self.config.init_log_fields().items()),
|
|
64
|
+
" tts_backend=injected" if tts_backend is not None else "",
|
|
51
65
|
)
|
|
52
66
|
|
|
53
67
|
self._transcriber: Any = None
|
|
@@ -250,7 +264,18 @@ class LocalDubbingPipeline:
|
|
|
250
264
|
raise UnsupportedLanguageError(source_lang, target_lang)
|
|
251
265
|
|
|
252
266
|
def _init_tts(self, language: str = "en") -> None:
|
|
253
|
-
"""
|
|
267
|
+
"""Resolve the text-to-speech backend for ``language``.
|
|
268
|
+
|
|
269
|
+
When a ``tts_backend`` was injected at construction it is used as-is
|
|
270
|
+
(it owns its own language handling). Otherwise the local
|
|
271
|
+
chatterbox-backed :class:`TextToSpeech` is constructed for ``language``
|
|
272
|
+
— importing it requires the ``[tts]`` extra; a bare ``[dub]`` install
|
|
273
|
+
raises a clear ``[tts]``-pointing ``ImportError`` at this point.
|
|
274
|
+
"""
|
|
275
|
+
if self._tts_backend is not None:
|
|
276
|
+
self._tts = self._tts_backend
|
|
277
|
+
return
|
|
278
|
+
|
|
254
279
|
from videopython.ai.generation.audio import TextToSpeech
|
|
255
280
|
|
|
256
281
|
self._tts = TextToSpeech(device=self.config.device, language=language)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Local generation models, lazily re-exported (PEP 562).
|
|
2
|
+
|
|
3
|
+
Each symbol's backing leaf module is imported only on first access, so
|
|
4
|
+
``from videopython.ai.generation import TextToImage`` does not pull in
|
|
5
|
+
``audio`` (chatterbox/musicgen) or ``video`` (diffusers/CogVideoX). The
|
|
6
|
+
``TYPE_CHECKING`` block keeps the symbols visible to mypy and IDEs.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import importlib
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
# Redundant aliases = intentional re-exports (visible to mypy/IDE, not
|
|
16
|
+
# flagged by ruff). Runtime resolution is lazy via __getattr__ below.
|
|
17
|
+
from .audio import TextToMusic as TextToMusic
|
|
18
|
+
from .audio import TextToSpeech as TextToSpeech
|
|
19
|
+
from .image import TextToImage as TextToImage
|
|
20
|
+
from .video import ImageToVideo as ImageToVideo
|
|
21
|
+
from .video import TextToVideo as TextToVideo
|
|
22
|
+
|
|
23
|
+
_SYMBOL_MODULES: dict[str, str] = {
|
|
24
|
+
"TextToSpeech": ".audio",
|
|
25
|
+
"TextToMusic": ".audio",
|
|
26
|
+
"TextToImage": ".image",
|
|
27
|
+
"ImageToVideo": ".video",
|
|
28
|
+
"TextToVideo": ".video",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
__all__ = list(_SYMBOL_MODULES)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def __getattr__(name: str) -> object:
|
|
35
|
+
module_name = _SYMBOL_MODULES.get(name)
|
|
36
|
+
if module_name is None:
|
|
37
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
38
|
+
module = importlib.import_module(module_name, __name__)
|
|
39
|
+
return getattr(module, name)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def __dir__() -> list[str]:
|
|
43
|
+
return sorted(__all__)
|