videopython 0.42.0__tar.gz → 0.43.1__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.1}/.gitignore +7 -0
- {videopython-0.42.0 → videopython-0.43.1}/PKG-INFO +46 -7
- {videopython-0.42.0 → videopython-0.43.1}/README.md +3 -1
- {videopython-0.42.0 → videopython-0.43.1}/pyproject.toml +78 -64
- videopython-0.43.1/src/videopython/ai/__init__.py +82 -0
- videopython-0.43.1/src/videopython/ai/_optional.py +47 -0
- videopython-0.43.1/src/videopython/ai/_predictor.py +54 -0
- videopython-0.43.1/src/videopython/ai/_revisions.py +109 -0
- videopython-0.43.1/src/videopython/ai/dubbing/__init__.py +63 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/dubbing/dubber.py +13 -2
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/dubbing/loudness.py +3 -1
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/dubbing/pipeline.py +28 -3
- videopython-0.43.1/src/videopython/ai/generation/__init__.py +43 -0
- videopython-0.43.1/src/videopython/ai/generation/_tts_backend.py +56 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/generation/audio.py +18 -6
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/generation/image.py +8 -2
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/generation/qwen3.py +8 -3
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/generation/translation.py +12 -4
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/generation/video.py +16 -6
- videopython-0.43.1/src/videopython/ai/understanding/__init__.py +45 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/understanding/audio.py +31 -11
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/understanding/faces.py +6 -1
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/understanding/image.py +17 -5
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/understanding/objects.py +6 -1
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/understanding/separation.py +5 -2
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/understanding/temporal.py +5 -2
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/video_analysis/sampling.py +3 -1
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/audio/audio.py +32 -15
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/exceptions.py +68 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/video.py +64 -2
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/editing/__init__.py +2 -1
- videopython-0.43.1/src/videopython/editing/audio_ops.py +221 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/editing/effects.py +102 -5
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/editing/operation.py +31 -16
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/editing/streaming.py +470 -34
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/editing/transforms.py +135 -22
- {videopython-0.42.0 → videopython-0.43.1}/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.1}/LICENSE +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/__init__.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/_device.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/dubbing/config.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/dubbing/expressiveness.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/dubbing/models.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/dubbing/quality.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/dubbing/remux.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/dubbing/timing.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/dubbing/voice_sample.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/effects.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/transforms.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/video_analysis/__init__.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/video_analysis/analyzer.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/video_analysis/models.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/ai/video_analysis/stages.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/audio/__init__.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/audio/analysis.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/__init__.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/_dimensions.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/_ffmpeg.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/_video_io.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/description.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/draw_detections.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/fonts/Anton-OFL.txt +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/fonts/Anton-Regular.ttf +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/fonts/BebasNeue-OFL.txt +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/fonts/BebasNeue-Regular.ttf +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/fonts/DejaVuSans.ttf +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/fonts/LICENSE_DEJAVU +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/fonts/Lato-Bold.ttf +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/fonts/Lato-OFL.txt +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/fonts/Poppins-Bold.ttf +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/fonts/Poppins-OFL.txt +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/fonts/__init__.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/base/transcription.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/editing/_ass.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/editing/_easing.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/src/videopython/editing/transcription_overlay.py +0 -0
- {videopython-0.42.0 → videopython-0.43.1}/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.1
|
|
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.1"
|
|
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,54 @@
|
|
|
1
|
+
"""Context-manager mixin for VRAM-releasing predictors.
|
|
2
|
+
|
|
3
|
+
Every predictor under ``videopython.ai`` already exposes an ``unload()`` method
|
|
4
|
+
that drops its model reference (``self._model = None``, plus any processor /
|
|
5
|
+
pipeline fields) and calls :func:`videopython.ai._device.release_device_memory`
|
|
6
|
+
to free the allocator cache. Today that has to be called by hand -- the dubbing
|
|
7
|
+
pipeline, for example, unloads each stage manually once it is done.
|
|
8
|
+
|
|
9
|
+
:class:`ManagedPredictor` makes that bookkeeping automatic by turning any such
|
|
10
|
+
predictor into a context manager::
|
|
11
|
+
|
|
12
|
+
with SceneVLM(...) as vlm:
|
|
13
|
+
... # use vlm
|
|
14
|
+
# vlm.unload() has fired here, releasing VRAM
|
|
15
|
+
|
|
16
|
+
The mixin is deliberately tiny and dependency-free: it imports no torch /
|
|
17
|
+
transformers / ultralytics and holds no state, so it is safe to mix into any
|
|
18
|
+
predictor class regardless of how it is constructed.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from types import TracebackType
|
|
24
|
+
from typing import TYPE_CHECKING, Literal
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from typing_extensions import Self
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ManagedPredictor:
|
|
31
|
+
"""Adds ``with``-statement support that calls ``unload()`` on exit.
|
|
32
|
+
|
|
33
|
+
Subclasses MUST define their own ``unload()`` method; this mixin does not
|
|
34
|
+
provide one. The expected contract is the one shared by every predictor in
|
|
35
|
+
``videopython.ai``: ``unload()`` clears the model reference(s) and releases
|
|
36
|
+
device memory, and is safe to call more than once (including before the
|
|
37
|
+
model was ever loaded).
|
|
38
|
+
|
|
39
|
+
``__exit__`` always returns ``False`` so exceptions raised inside the ``with``
|
|
40
|
+
block are never suppressed -- ``unload()`` runs on both the success and
|
|
41
|
+
failure paths.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __enter__(self) -> Self:
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
def __exit__(
|
|
48
|
+
self,
|
|
49
|
+
exc_type: type[BaseException] | None,
|
|
50
|
+
exc: BaseException | None,
|
|
51
|
+
tb: TracebackType | None,
|
|
52
|
+
) -> Literal[False]:
|
|
53
|
+
self.unload() # type: ignore[attr-defined]
|
|
54
|
+
return False
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Centralized HuggingFace model-revision pins.
|
|
2
|
+
|
|
3
|
+
The AI predictors load weights via ``from_pretrained(...)``,
|
|
4
|
+
``hf_hub_download(...)``, and ``YOLO(...)`` with no revision pinning by
|
|
5
|
+
default, so a silent upstream weight change can alter production analysis
|
|
6
|
+
without any code change on our side. This module maps each *fixed* model
|
|
7
|
+
identifier to the commit SHA we want to pin to, so callers can pass
|
|
8
|
+
``revision=pinned(model_id)`` to lock the download to a known-good commit.
|
|
9
|
+
|
|
10
|
+
Resolution: each SHA below is the ``sha`` field returned by the HuggingFace
|
|
11
|
+
model API (``https://huggingface.co/api/models/<repo_id>``), i.e. the latest
|
|
12
|
+
commit on the repo's ``main`` branch at the time it was captured.
|
|
13
|
+
|
|
14
|
+
Refreshing SHAs:
|
|
15
|
+
For each ``model_id`` in ``MODEL_REVISIONS`` (and the TODO list, if any),
|
|
16
|
+
fetch ``https://huggingface.co/api/models/<model_id>`` and copy the JSON
|
|
17
|
+
``sha`` field into the dict. Bump deliberately -- the whole point of a pin
|
|
18
|
+
is that it does not move on its own. After editing, re-run the AI suite
|
|
19
|
+
so the new revision is exercised before release.
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
>>> from videopython.ai._revisions import pinned
|
|
23
|
+
>>> pinned("facebook/musicgen-small") # -> "<sha>" or None
|
|
24
|
+
Pass the result straight through:
|
|
25
|
+
``AutoProcessor.from_pretrained(model_id, revision=pinned(model_id))``.
|
|
26
|
+
``revision=None`` is the library default and means "current behavior"
|
|
27
|
+
(track ``main``), so an unpinned or dynamic model stays safe.
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# Unpinnable-by-design (intentionally absent from MODEL_REVISIONS)
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# These load paths cannot be pinned to a HuggingFace commit SHA, so they are
|
|
33
|
+
# documented here rather than registered above. ``pinned()`` returns None for
|
|
34
|
+
# them, which leaves the caller on its current (unpinned) behavior.
|
|
35
|
+
#
|
|
36
|
+
# * ultralytics YOLO asset -- ai/understanding/objects.py loads
|
|
37
|
+
# ``YOLO(self.model_name)`` (e.g. "yolov8n.pt"). Ultralytics resolves and
|
|
38
|
+
# downloads this from its own GitHub release assets, not from a HF repo,
|
|
39
|
+
# so there is no HF revision to pin. (The face detector in
|
|
40
|
+
# ai/understanding/faces.py is different: it pulls a YOLOv8 *checkpoint*
|
|
41
|
+
# from the HF repo ``arnabdhar/YOLOv8-Face-Detection`` via
|
|
42
|
+
# hf_hub_download, and that one IS pinned below.)
|
|
43
|
+
#
|
|
44
|
+
# * Dynamic Marian pairs -- ai/generation/translation.py builds the model
|
|
45
|
+
# name per language pair at runtime (e.g. "Helsinki-NLP/opus-mt-en-de").
|
|
46
|
+
# The set of repos is open-ended (one per direction), so pinning each pair
|
|
47
|
+
# to a SHA is impractical and out of scope; left unpinned by design.
|
|
48
|
+
#
|
|
49
|
+
# * Chatterbox internal load -- ai/generation/audio.py calls
|
|
50
|
+
# ``ChatterboxMultilingualTTS.from_pretrained(device=...)`` with no repo
|
|
51
|
+
# argument. The repo id + revision are resolved internally by the
|
|
52
|
+
# chatterbox package, so there is nothing for us to pass a ``revision`` to.
|
|
53
|
+
#
|
|
54
|
+
# * openai-whisper CDN models -- ai/understanding/audio.py loads transcription
|
|
55
|
+
# weights via ``whisper.load_model(name="turbo", ...)`` (tiny/base/small/
|
|
56
|
+
# medium/large/turbo). openai-whisper downloads these from OpenAI's own CDN
|
|
57
|
+
# by name, NOT through a HF ``from_pretrained`` repo, so there is no HF
|
|
58
|
+
# commit SHA to pin. (The faster-whisper backend, if/when used, WOULD map to
|
|
59
|
+
# a HF repo and could be pinned -- this code path does not use it.)
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
from __future__ import annotations
|
|
64
|
+
|
|
65
|
+
# Exact model-identifier string -> pinned commit SHA. The key must match the
|
|
66
|
+
# literal value passed to from_pretrained/hf_hub_download at the call site so
|
|
67
|
+
# ``pinned(model_id)`` resolves with a plain dict lookup. SHAs captured from
|
|
68
|
+
# the HuggingFace model API (see module docstring for refresh instructions).
|
|
69
|
+
MODEL_REVISIONS: dict[str, str] = {
|
|
70
|
+
# SceneVLM (ai/understanding/image.py: SCENE_VLM_MODEL_IDS)
|
|
71
|
+
"Qwen/Qwen3.5-4B": "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a",
|
|
72
|
+
"Qwen/Qwen3.5-9B": "c202236235762e1c871ad0ccb60c8ee5ba337b9a",
|
|
73
|
+
"Qwen/Qwen3.5-27B": "fc05daec18b0a78c049392ed2e771dde82bdf654",
|
|
74
|
+
# Speaker diarization (ai/understanding/audio.py: PYANNOTE_DIARIZATION_MODEL).
|
|
75
|
+
# Gated repo (auto-approved); from_pretrained needs an accepted HF token.
|
|
76
|
+
"pyannote/speaker-diarization-community-1": "3533c8cf8e369892e6b79ff1bf80f7b0286a54ee",
|
|
77
|
+
# Audio event classifier (ai/understanding/audio.py: AudioClassifier)
|
|
78
|
+
"MIT/ast-finetuned-audioset-10-10-0.4593": "f826b80d28226b62986cc218e5cec390b1096902",
|
|
79
|
+
# Face detection checkpoint (ai/understanding/faces.py: hf_hub_download)
|
|
80
|
+
"arnabdhar/YOLOv8-Face-Detection": "52fa54977207fa4f021de949b515fb19dcab4488",
|
|
81
|
+
# Qwen3 translation GGUF (ai/generation/qwen3.py: DEFAULT_REPO_ID)
|
|
82
|
+
"unsloth/Qwen3-4B-Instruct-2507-GGUF": "a06e946bb6b655725eafa393f4a9745d460374c9",
|
|
83
|
+
# MusicGen (ai/generation/audio.py: TextToMusic)
|
|
84
|
+
"facebook/musicgen-small": "4c8334b02c6ec4e8664a91979669a501ec497792",
|
|
85
|
+
# SDXL (ai/generation/image.py: TextToImage)
|
|
86
|
+
"stabilityai/stable-diffusion-xl-base-1.0": "462165984030d82259a11f4367a4eed129e94a7b",
|
|
87
|
+
# CogVideoX (ai/generation/video.py: TextToVideo / ImageToVideo)
|
|
88
|
+
"THUDM/CogVideoX1.5-5B": "fdc5267c90b5c06492985b966e43aae984e189e0",
|
|
89
|
+
"THUDM/CogVideoX1.5-5B-I2V": "46c90528707aebbe69066390b4fe7e7d24c9c2a4",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def pinned(model_id: str) -> str | None:
|
|
94
|
+
"""Return the pinned commit SHA for ``model_id``, or ``None`` if unpinned.
|
|
95
|
+
|
|
96
|
+
``None`` is a valid, safe value to forward as ``revision=`` to
|
|
97
|
+
``from_pretrained`` / ``hf_hub_download`` -- it is their default and means
|
|
98
|
+
"no pin" (track the repo's ``main`` branch, i.e. the current behavior).
|
|
99
|
+
Dynamic and unpinnable-by-design models (see the module docstring) fall
|
|
100
|
+
into this case.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
model_id: The exact repo id / model identifier string passed to the
|
|
104
|
+
loader at the call site (e.g. ``"facebook/musicgen-small"``).
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
The pinned SHA, or ``None`` when no pin is registered for ``model_id``.
|
|
108
|
+
"""
|
|
109
|
+
return MODEL_REVISIONS.get(model_id)
|
|
@@ -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__)
|