videopython 0.41.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.
Files changed (80) hide show
  1. {videopython-0.41.0 → videopython-0.43.0}/.gitignore +7 -0
  2. {videopython-0.41.0 → videopython-0.43.0}/PKG-INFO +48 -9
  3. {videopython-0.41.0 → videopython-0.43.0}/README.md +5 -3
  4. {videopython-0.41.0 → videopython-0.43.0}/pyproject.toml +78 -64
  5. videopython-0.43.0/src/videopython/ai/__init__.py +82 -0
  6. videopython-0.43.0/src/videopython/ai/_optional.py +47 -0
  7. videopython-0.43.0/src/videopython/ai/dubbing/__init__.py +63 -0
  8. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/dubbing/dubber.py +13 -2
  9. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/dubbing/loudness.py +3 -1
  10. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/dubbing/pipeline.py +28 -3
  11. videopython-0.43.0/src/videopython/ai/generation/__init__.py +43 -0
  12. videopython-0.43.0/src/videopython/ai/generation/_tts_backend.py +56 -0
  13. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/generation/audio.py +10 -2
  14. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/generation/image.py +4 -1
  15. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/generation/qwen3.py +4 -1
  16. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/generation/translation.py +5 -1
  17. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/generation/video.py +8 -2
  18. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/transforms.py +107 -81
  19. videopython-0.43.0/src/videopython/ai/understanding/__init__.py +45 -0
  20. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/understanding/audio.py +19 -6
  21. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/understanding/faces.py +4 -1
  22. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/understanding/image.py +10 -2
  23. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/understanding/objects.py +3 -1
  24. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/understanding/separation.py +3 -1
  25. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/understanding/temporal.py +3 -1
  26. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/video_analysis/sampling.py +3 -1
  27. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/audio/audio.py +32 -15
  28. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/exceptions.py +5 -2
  29. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/video.py +60 -4
  30. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/editing/__init__.py +2 -3
  31. videopython-0.43.0/src/videopython/editing/audio_ops.py +221 -0
  32. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/editing/effects.py +104 -5
  33. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/editing/operation.py +77 -2
  34. videopython-0.43.0/src/videopython/editing/streaming.py +1171 -0
  35. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/editing/transcription_overlay.py +5 -4
  36. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/editing/transforms.py +346 -174
  37. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/editing/video_edit.py +1090 -261
  38. videopython-0.41.0/src/videopython/ai/__init__.py +0 -36
  39. videopython-0.41.0/src/videopython/ai/dubbing/__init__.py +0 -31
  40. videopython-0.41.0/src/videopython/ai/generation/__init__.py +0 -11
  41. videopython-0.41.0/src/videopython/ai/understanding/__init__.py +0 -14
  42. videopython-0.41.0/src/videopython/editing/streaming.py +0 -547
  43. {videopython-0.41.0 → videopython-0.43.0}/LICENSE +0 -0
  44. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/__init__.py +0 -0
  45. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/_device.py +0 -0
  46. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/dubbing/config.py +0 -0
  47. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/dubbing/expressiveness.py +0 -0
  48. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/dubbing/models.py +0 -0
  49. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/dubbing/quality.py +0 -0
  50. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/dubbing/remux.py +0 -0
  51. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/dubbing/timing.py +0 -0
  52. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/dubbing/voice_sample.py +0 -0
  53. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/effects.py +0 -0
  54. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/video_analysis/__init__.py +0 -0
  55. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/video_analysis/analyzer.py +0 -0
  56. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/video_analysis/models.py +0 -0
  57. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/ai/video_analysis/stages.py +0 -0
  58. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/audio/__init__.py +0 -0
  59. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/audio/analysis.py +0 -0
  60. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/__init__.py +0 -0
  61. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/_dimensions.py +0 -0
  62. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/_ffmpeg.py +0 -0
  63. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/_video_io.py +0 -0
  64. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/description.py +0 -0
  65. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/draw_detections.py +0 -0
  66. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/fonts/Anton-OFL.txt +0 -0
  67. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/fonts/Anton-Regular.ttf +0 -0
  68. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/fonts/BebasNeue-OFL.txt +0 -0
  69. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/fonts/BebasNeue-Regular.ttf +0 -0
  70. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/fonts/DejaVuSans.ttf +0 -0
  71. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/fonts/LICENSE_DEJAVU +0 -0
  72. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/fonts/Lato-Bold.ttf +0 -0
  73. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/fonts/Lato-OFL.txt +0 -0
  74. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/fonts/Poppins-Bold.ttf +0 -0
  75. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/fonts/Poppins-OFL.txt +0 -0
  76. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/fonts/__init__.py +0 -0
  77. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/base/transcription.py +0 -0
  78. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/editing/_ass.py +0 -0
  79. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/editing/_easing.py +0 -0
  80. {videopython-0.41.0 → videopython-0.43.0}/src/videopython/py.typed +0 -0
@@ -148,3 +148,10 @@ data/exported/*.mp4
148
148
  # Model weights
149
149
  *.pt
150
150
 
151
+ # Sample / working videos (keep them out of the repo root)
152
+ *.mp4
153
+ !data/exported/example.mp4
154
+
155
+ # Local scratch / sign-off artifacts
156
+ libass_signoff/
157
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: videopython
3
- Version: 0.41.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
@@ -117,9 +156,9 @@ See the [LLM Integration Guide](https://videopython.com/guides/llm-integration/)
117
156
 
118
157
  ## Features
119
158
 
120
- - **`videopython.base`** — `Video`, `VideoMetadata`, `FrameIterator`, `ImageText`, `Transcription`, and shared result types (`BoundingBox`, `FaceTrack`, `SceneBoundary`, ...). No AI dependencies.
159
+ - **`videopython.base`** — `Video`, `VideoMetadata`, `FrameIterator`, `Transcription`, and shared result types (`BoundingBox`, `FaceTrack`, `SceneBoundary`, ...). No AI dependencies.
121
160
  - **`videopython.audio`** — `Audio` with overlay, concat, normalize, time-stretch, silence detection, segment classification.
122
- - **`videopython.editing`** — `Operation`/`Effect` foundation, `VideoEdit` plan runner with JSON Schema + streaming execution. Transforms (cut, resize, crop, fps, speed, reverse, freeze, silence removal) and effects (blur, zoom, color grading, vignette, Ken Burns, fade, overlays, animated subtitles).
161
+ - **`videopython.editing`** — `Operation`/`Effect` foundation, `VideoEdit` plan runner with JSON Schema + streaming execution. Transforms (cut, resize, crop, fps, speed, freeze, silence removal) and effects (blur, zoom, color grading, vignette, Ken Burns, fade, overlays, animated subtitles).
123
162
  - **`videopython.ai`** *(install with `[ai]`)* — generation (`TextToVideo`, `ImageToVideo`, `TextToImage`, `TextToSpeech`, `TextToMusic`), understanding (`AudioToText`, `AudioClassifier`, `SceneVLM`, `FaceTracker`, `ObjectDetector`, `SemanticSceneDetector`), the `FaceTrackingCrop` transform, the `ObjectDetectionOverlay` effect (per-frame bounding boxes + labels), and the full-pipeline `VideoAnalyzer`.
124
163
  - **`videopython.ai.dubbing`** — `VideoDubber` for voice-cloned revoicing with timing sync.
125
164
 
@@ -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
@@ -68,9 +70,9 @@ See the [LLM Integration Guide](https://videopython.com/guides/llm-integration/)
68
70
 
69
71
  ## Features
70
72
 
71
- - **`videopython.base`** — `Video`, `VideoMetadata`, `FrameIterator`, `ImageText`, `Transcription`, and shared result types (`BoundingBox`, `FaceTrack`, `SceneBoundary`, ...). No AI dependencies.
73
+ - **`videopython.base`** — `Video`, `VideoMetadata`, `FrameIterator`, `Transcription`, and shared result types (`BoundingBox`, `FaceTrack`, `SceneBoundary`, ...). No AI dependencies.
72
74
  - **`videopython.audio`** — `Audio` with overlay, concat, normalize, time-stretch, silence detection, segment classification.
73
- - **`videopython.editing`** — `Operation`/`Effect` foundation, `VideoEdit` plan runner with JSON Schema + streaming execution. Transforms (cut, resize, crop, fps, speed, reverse, freeze, silence removal) and effects (blur, zoom, color grading, vignette, Ken Burns, fade, overlays, animated subtitles).
75
+ - **`videopython.editing`** — `Operation`/`Effect` foundation, `VideoEdit` plan runner with JSON Schema + streaming execution. Transforms (cut, resize, crop, fps, speed, freeze, silence removal) and effects (blur, zoom, color grading, vignette, Ken Burns, fade, overlays, animated subtitles).
74
76
  - **`videopython.ai`** *(install with `[ai]`)* — generation (`TextToVideo`, `ImageToVideo`, `TextToImage`, `TextToSpeech`, `TextToMusic`), understanding (`AudioToText`, `AudioClassifier`, `SceneVLM`, `FaceTracker`, `ObjectDetector`, `SemanticSceneDetector`), the `FaceTrackingCrop` transform, the `ObjectDetectionOverlay` effect (per-frame bounding boxes + labels), and the full-pipeline `VideoAnalyzer`.
75
77
  - **`videopython.ai.dubbing`** — `VideoDubber` for voice-cloned revoicing with timing sync.
76
78
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "videopython"
3
- version = "0.41.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
- ai = [
57
- "accelerate>=0.29.2",
58
- "diffusers>=0.30.0",
59
- "hf-transfer>=0.1.9",
60
- "torch>=2.8.0",
61
- "torchaudio>=2.8.0",
62
- "transformers>=5.2.0",
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
- "numba>=0.61.0",
68
- "ollama>=0.4.5",
69
- "scipy>=1.10.0",
70
- "scikit-learn>=1.3.0",
71
- # Detection backends
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
- # Voice cloning TTS (Chatterbox Multilingual by Resemble AI)
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
- # Required for pip install videopython[ai] - pip uses optional-dependencies, not dependency-groups
94
- [project.optional-dependencies]
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
- # chatterbox-tts 0.1.7 pins strict versions of torch, torchaudio, numpy, and
167
- # diffusers that conflict with pyannote-audio (torch>=2.8) and CogVideoX
168
- # (diffusers>=0.30). Override to let the resolver pick compatible versions.
169
- # The ai dependency floors are aligned with these overrides to keep pip and uv
170
- # resolving similar versions.
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__(self, config: DubbingConfig | None = None, **kwargs: Any):
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 pyloudnorm
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__(self, config: DubbingConfig | None = None, **kwargs: Any):
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
- """Initialize the text-to-speech model."""
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)