videopython 0.55.0__tar.gz → 0.55.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.55.0 → videopython-0.55.1}/PKG-INFO +28 -25
- {videopython-0.55.0 → videopython-0.55.1}/README.md +25 -23
- {videopython-0.55.0 → videopython-0.55.1}/pyproject.toml +28 -60
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/audio/audio.py +12 -44
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/editing/effects.py +17 -26
- {videopython-0.55.0 → videopython-0.55.1}/.gitignore +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/LICENSE +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/__init__.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/__init__.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/_device.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/_ollama.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/_optional.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/_predictor.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/_revisions.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/auto_edit/__init__.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/auto_edit/backend.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/auto_edit/catalog.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/auto_edit/editor.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/auto_edit/local.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/auto_edit/models.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/auto_edit/resolve.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/dubbing/__init__.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/dubbing/_tts_backend.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/dubbing/audio_ops.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/dubbing/config.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/dubbing/dubber.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/dubbing/models.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/dubbing/pipeline.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/dubbing/quality.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/dubbing/remux.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/dubbing/separation.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/dubbing/timing.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/dubbing/translation.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/dubbing/voice_sample.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/effects.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/errors.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/generation/__init__.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/generation/audio.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/generation/image.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/generation/video.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/keyframe.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/ops.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/transforms.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/understanding/__init__.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/understanding/_detector.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/understanding/audio.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/understanding/classification.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/understanding/faces.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/understanding/image.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/understanding/objects.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/understanding/temporal.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/video_analysis/__init__.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/video_analysis/analyzer.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/video_analysis/detectors.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/video_analysis/models.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/video_analysis/sampling.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/video_analysis/source_metadata.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/audio/__init__.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/audio/analysis.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/__init__.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/_dimensions.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/_ffmpeg.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/_video_io.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/description.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/draw_detections.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/exceptions.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/fonts/Anton-OFL.txt +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/fonts/Anton-Regular.ttf +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/fonts/BebasNeue-OFL.txt +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/fonts/BebasNeue-Regular.ttf +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/fonts/DejaVuSans.ttf +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/fonts/LICENSE_DEJAVU +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/fonts/Lato-Bold.ttf +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/fonts/Lato-OFL.txt +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/fonts/Poppins-Bold.ttf +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/fonts/Poppins-OFL.txt +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/fonts/__init__.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/transcription.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/base/video.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/editing/__init__.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/editing/_ass.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/editing/_easing.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/editing/_schema.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/editing/audio_ops.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/editing/operation.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/editing/streaming.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/editing/transcription_overlay.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/editing/transforms.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/editing/video_edit.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/mcp/__init__.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/mcp/server.py +0 -0
- {videopython-0.55.0 → videopython-0.55.1}/src/videopython/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: videopython
|
|
3
|
-
Version: 0.55.
|
|
3
|
+
Version: 0.55.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/
|
|
@@ -21,6 +21,7 @@ Requires-Dist: opencv-python-headless>=4.9.0.80
|
|
|
21
21
|
Requires-Dist: pillow>=12.1.1
|
|
22
22
|
Requires-Dist: pydantic>=2.8.0
|
|
23
23
|
Requires-Dist: resvg-py>=0.3.2
|
|
24
|
+
Requires-Dist: soxr>=0.5
|
|
24
25
|
Requires-Dist: tqdm>=4.66.3
|
|
25
26
|
Provides-Extra: ai
|
|
26
27
|
Requires-Dist: accelerate>=0.29.2; extra == 'ai'
|
|
@@ -66,13 +67,11 @@ pip install "videopython[ai]" # + ALL local AI features (GPU recommended)
|
|
|
66
67
|
pip install "videopython[ai,mcp]" # + MCP server for agent-driven editing
|
|
67
68
|
```
|
|
68
69
|
|
|
69
|
-
Python `>=3.11, <3.14`. AI features run locally — no cloud API keys required, but model weights are downloaded on first use. LLM-driven editing and scene captioning use a local [Ollama](https://ollama.com) server (`ollama pull qwen3.6:27b`).
|
|
70
|
+
Python `>=3.11, <3.14`. AI features run locally — no cloud API keys required, but model weights are downloaded on first use. LLM-driven editing and scene captioning use a local [Ollama](https://ollama.com) server (`ollama pull qwen3.6:27b`). See the [install guide](https://videopython.com/install/) for FFmpeg, Ollama, and hardware details.
|
|
70
71
|
|
|
71
|
-
## Quick
|
|
72
|
+
## Quick start
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
A `VideoEdit` is a multi-segment plan, defined as a dict (or JSON), validated and executed against the source files:
|
|
74
|
+
An edit is a plan — a dict (or JSON) validated and executed against the source files:
|
|
76
75
|
|
|
77
76
|
```python
|
|
78
77
|
from videopython.editing import VideoEdit
|
|
@@ -93,7 +92,7 @@ edit.validate() # dry-run via metadata, no frames loaded
|
|
|
93
92
|
edit.run_to_file("output.mp4") # streams ffmpeg decode → effects → encode
|
|
94
93
|
```
|
|
95
94
|
|
|
96
|
-
`run_to_file()` streams
|
|
95
|
+
`run_to_file()` streams, so memory stays bounded even for hour-long sources. Walk through it in [Tutorial 1](https://videopython.com/tutorials/first-edit/).
|
|
97
96
|
|
|
98
97
|
### Automatic editing (local LLM)
|
|
99
98
|
|
|
@@ -110,7 +109,7 @@ edit = editor.edit(
|
|
|
110
109
|
edit.run_to_file("teaser.mp4")
|
|
111
110
|
```
|
|
112
111
|
|
|
113
|
-
The model picks scenes **by id** from a catalog built from scene detection + captions, so its temporal imprecision never reaches the render. See
|
|
112
|
+
The model picks scenes **by id** from a catalog built from scene detection + captions, so its temporal imprecision never reaches the render. See [Let a local LLM edit for you](https://videopython.com/how-to/auto-editing/).
|
|
114
113
|
|
|
115
114
|
### AI generation
|
|
116
115
|
|
|
@@ -123,39 +122,43 @@ audio = TextToSpeech().generate_audio("Welcome to videopython.")
|
|
|
123
122
|
video.add_audio(audio).save("ai_video.mp4")
|
|
124
123
|
```
|
|
125
124
|
|
|
126
|
-
## LLM & AI
|
|
125
|
+
## LLM & AI agent integration
|
|
127
126
|
|
|
128
|
-
Putting an LLM in the loop works three ways:
|
|
127
|
+
Putting an LLM in the loop works three ways, differing in who owns the model:
|
|
129
128
|
|
|
130
|
-
1. **Bring your own LLM** — videopython gives your model the JSON Schema and a structured refine loop; your model authors the plans (
|
|
131
|
-
2. **`AutoEditor`** — a local Ollama vision model is the planner
|
|
132
|
-
3. **MCP server** — `videopython-mcp` exposes the pipeline as [Model Context Protocol](https://modelcontextprotocol.io) tools, so an agent like Claude drives editing with its own model.
|
|
129
|
+
1. **Bring your own LLM** — videopython gives your model the JSON Schema and a structured refine loop; your model authors the plans. [Guide](https://videopython.com/how-to/llm-plans/)
|
|
130
|
+
2. **`AutoEditor`** — a local Ollama vision model is the planner. [Guide](https://videopython.com/how-to/auto-editing/)
|
|
131
|
+
3. **MCP server** — `videopython-mcp` exposes the pipeline as [Model Context Protocol](https://modelcontextprotocol.io) tools, so an agent like Claude drives editing with its own model. [Guide](https://videopython.com/how-to/mcp-server/)
|
|
133
132
|
|
|
134
|
-
|
|
133
|
+
Mode 1 in brief: every operation is a Pydantic model whose fields *are* the JSON wire format, so `VideoEdit.json_schema()` hands your model a ready-made tool schema — a discriminated union over every LLM-exposed op (pass `strict=True` for provider grammar modes). Plans parse permissively and own their numeric bounds at validation, so a refine loop converges fast:
|
|
135
134
|
|
|
136
135
|
- **`edit.check(meta)`** — collect *every* structured error in one pass, not just the first
|
|
137
136
|
- **`edit.repair(meta)`** — auto-clamp mechanical violations (overruns, negatives) with a changelog
|
|
138
137
|
- **`edit.normalize_dimensions(meta, target)`** — make heterogeneous segments concat-compatible
|
|
139
138
|
|
|
140
|
-
|
|
139
|
+
Why it is built this way: [LLM-first design](https://videopython.com/explanation/llm-first-design/).
|
|
141
140
|
|
|
142
141
|
## Features
|
|
143
142
|
|
|
144
143
|
- **`videopython.base`** — `Video`, `VideoMetadata`, `FrameIterator`, `Transcription`, and shared result types (`BoundingBox`, `FaceTrack`, `SceneBoundary`, ...). No AI dependencies.
|
|
145
144
|
- **`videopython.audio`** — `Audio` with overlay, concat, normalize, time-stretch, silence detection, segment classification.
|
|
146
145
|
- **`videopython.editing`** — `Operation`/`Effect` foundation, `VideoEdit` plan runner with JSON Schema + streaming execution. Transforms (resize, crop, fps, speed, freeze, silence removal; cutting is the segment's own start/end) and effects (blur, zoom, color grading, vignette, Ken Burns, fade, overlays, animated subtitles).
|
|
147
|
-
- **`videopython.ai`** *(install with `[ai]`)* — generation (`TextToVideo`, `ImageToVideo`, `TextToImage`, `TextToSpeech`, `TextToMusic`), understanding (`AudioToText`, `AudioClassifier`, `SceneVLM`, `FaceTracker`, `ObjectDetector`, `SemanticSceneDetector`), the `FaceTrackingCrop` transform, the `ObjectDetectionOverlay` effect
|
|
148
|
-
- **`videopython.ai.auto_edit`** — `AutoEditor` + `OllamaVisionLLM`: plan and render an edit from sources + a one-line brief
|
|
146
|
+
- **`videopython.ai`** *(install with `[ai]`)* — generation (`TextToVideo`, `ImageToVideo`, `TextToImage`, `TextToSpeech`, `TextToMusic`), understanding (`AudioToText`, `AudioClassifier`, `SceneVLM`, `FaceTracker`, `ObjectDetector`, `SemanticSceneDetector`), the `FaceTrackingCrop` transform, the `ObjectDetectionOverlay` effect, and the full-pipeline `VideoAnalyzer`. Scene captioning and dubbing translation run on a local [Ollama](https://ollama.com) model.
|
|
147
|
+
- **`videopython.ai.auto_edit`** — `AutoEditor` + `OllamaVisionLLM`: plan and render an edit from sources + a one-line brief.
|
|
149
148
|
- **`videopython.ai.dubbing`** — `VideoDubber` for voice-cloned revoicing with timing sync.
|
|
150
|
-
- **`videopython.mcp`** *(install with `[mcp]`)* — `videopython-mcp`, an MCP stdio server exposing the auto-edit pipeline
|
|
149
|
+
- **`videopython.mcp`** *(install with `[ai,mcp]`)* — `videopython-mcp`, an MCP stdio server exposing the auto-edit pipeline so an agent drives editing.
|
|
150
|
+
|
|
151
|
+
## Documentation
|
|
151
152
|
|
|
152
|
-
|
|
153
|
+
The docs follow [Diátaxis](https://diataxis.fr): four sections, each answering a different kind of question.
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
| | |
|
|
156
|
+
|---|---|
|
|
157
|
+
| [Tutorials](https://videopython.com/tutorials/) | Learn by doing — start here if you are new |
|
|
158
|
+
| [How-to guides](https://videopython.com/how-to/) | Recipes for a specific goal |
|
|
159
|
+
| [Reference](https://videopython.com/reference/) | Classes, operations, parameters, wire format |
|
|
160
|
+
| [Explanation](https://videopython.com/explanation/) | Why the library is shaped the way it is |
|
|
158
161
|
|
|
159
162
|
## Development
|
|
160
163
|
|
|
161
|
-
See [`DEVELOPMENT.md`](DEVELOPMENT.md) for local setup, testing, and
|
|
164
|
+
See [`DEVELOPMENT.md`](DEVELOPMENT.md) for local setup, testing, docs, and the release workflow.
|
|
@@ -19,13 +19,11 @@ pip install "videopython[ai]" # + ALL local AI features (GPU recommended)
|
|
|
19
19
|
pip install "videopython[ai,mcp]" # + MCP server for agent-driven editing
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Python `>=3.11, <3.14`. AI features run locally — no cloud API keys required, but model weights are downloaded on first use. LLM-driven editing and scene captioning use a local [Ollama](https://ollama.com) server (`ollama pull qwen3.6:27b`).
|
|
22
|
+
Python `>=3.11, <3.14`. AI features run locally — no cloud API keys required, but model weights are downloaded on first use. LLM-driven editing and scene captioning use a local [Ollama](https://ollama.com) server (`ollama pull qwen3.6:27b`). See the [install guide](https://videopython.com/install/) for FFmpeg, Ollama, and hardware details.
|
|
23
23
|
|
|
24
|
-
## Quick
|
|
24
|
+
## Quick start
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
A `VideoEdit` is a multi-segment plan, defined as a dict (or JSON), validated and executed against the source files:
|
|
26
|
+
An edit is a plan — a dict (or JSON) validated and executed against the source files:
|
|
29
27
|
|
|
30
28
|
```python
|
|
31
29
|
from videopython.editing import VideoEdit
|
|
@@ -46,7 +44,7 @@ edit.validate() # dry-run via metadata, no frames loaded
|
|
|
46
44
|
edit.run_to_file("output.mp4") # streams ffmpeg decode → effects → encode
|
|
47
45
|
```
|
|
48
46
|
|
|
49
|
-
`run_to_file()` streams
|
|
47
|
+
`run_to_file()` streams, so memory stays bounded even for hour-long sources. Walk through it in [Tutorial 1](https://videopython.com/tutorials/first-edit/).
|
|
50
48
|
|
|
51
49
|
### Automatic editing (local LLM)
|
|
52
50
|
|
|
@@ -63,7 +61,7 @@ edit = editor.edit(
|
|
|
63
61
|
edit.run_to_file("teaser.mp4")
|
|
64
62
|
```
|
|
65
63
|
|
|
66
|
-
The model picks scenes **by id** from a catalog built from scene detection + captions, so its temporal imprecision never reaches the render. See
|
|
64
|
+
The model picks scenes **by id** from a catalog built from scene detection + captions, so its temporal imprecision never reaches the render. See [Let a local LLM edit for you](https://videopython.com/how-to/auto-editing/).
|
|
67
65
|
|
|
68
66
|
### AI generation
|
|
69
67
|
|
|
@@ -76,39 +74,43 @@ audio = TextToSpeech().generate_audio("Welcome to videopython.")
|
|
|
76
74
|
video.add_audio(audio).save("ai_video.mp4")
|
|
77
75
|
```
|
|
78
76
|
|
|
79
|
-
## LLM & AI
|
|
77
|
+
## LLM & AI agent integration
|
|
80
78
|
|
|
81
|
-
Putting an LLM in the loop works three ways:
|
|
79
|
+
Putting an LLM in the loop works three ways, differing in who owns the model:
|
|
82
80
|
|
|
83
|
-
1. **Bring your own LLM** — videopython gives your model the JSON Schema and a structured refine loop; your model authors the plans (
|
|
84
|
-
2. **`AutoEditor`** — a local Ollama vision model is the planner
|
|
85
|
-
3. **MCP server** — `videopython-mcp` exposes the pipeline as [Model Context Protocol](https://modelcontextprotocol.io) tools, so an agent like Claude drives editing with its own model.
|
|
81
|
+
1. **Bring your own LLM** — videopython gives your model the JSON Schema and a structured refine loop; your model authors the plans. [Guide](https://videopython.com/how-to/llm-plans/)
|
|
82
|
+
2. **`AutoEditor`** — a local Ollama vision model is the planner. [Guide](https://videopython.com/how-to/auto-editing/)
|
|
83
|
+
3. **MCP server** — `videopython-mcp` exposes the pipeline as [Model Context Protocol](https://modelcontextprotocol.io) tools, so an agent like Claude drives editing with its own model. [Guide](https://videopython.com/how-to/mcp-server/)
|
|
86
84
|
|
|
87
|
-
|
|
85
|
+
Mode 1 in brief: every operation is a Pydantic model whose fields *are* the JSON wire format, so `VideoEdit.json_schema()` hands your model a ready-made tool schema — a discriminated union over every LLM-exposed op (pass `strict=True` for provider grammar modes). Plans parse permissively and own their numeric bounds at validation, so a refine loop converges fast:
|
|
88
86
|
|
|
89
87
|
- **`edit.check(meta)`** — collect *every* structured error in one pass, not just the first
|
|
90
88
|
- **`edit.repair(meta)`** — auto-clamp mechanical violations (overruns, negatives) with a changelog
|
|
91
89
|
- **`edit.normalize_dimensions(meta, target)`** — make heterogeneous segments concat-compatible
|
|
92
90
|
|
|
93
|
-
|
|
91
|
+
Why it is built this way: [LLM-first design](https://videopython.com/explanation/llm-first-design/).
|
|
94
92
|
|
|
95
93
|
## Features
|
|
96
94
|
|
|
97
95
|
- **`videopython.base`** — `Video`, `VideoMetadata`, `FrameIterator`, `Transcription`, and shared result types (`BoundingBox`, `FaceTrack`, `SceneBoundary`, ...). No AI dependencies.
|
|
98
96
|
- **`videopython.audio`** — `Audio` with overlay, concat, normalize, time-stretch, silence detection, segment classification.
|
|
99
97
|
- **`videopython.editing`** — `Operation`/`Effect` foundation, `VideoEdit` plan runner with JSON Schema + streaming execution. Transforms (resize, crop, fps, speed, freeze, silence removal; cutting is the segment's own start/end) and effects (blur, zoom, color grading, vignette, Ken Burns, fade, overlays, animated subtitles).
|
|
100
|
-
- **`videopython.ai`** *(install with `[ai]`)* — generation (`TextToVideo`, `ImageToVideo`, `TextToImage`, `TextToSpeech`, `TextToMusic`), understanding (`AudioToText`, `AudioClassifier`, `SceneVLM`, `FaceTracker`, `ObjectDetector`, `SemanticSceneDetector`), the `FaceTrackingCrop` transform, the `ObjectDetectionOverlay` effect
|
|
101
|
-
- **`videopython.ai.auto_edit`** — `AutoEditor` + `OllamaVisionLLM`: plan and render an edit from sources + a one-line brief
|
|
98
|
+
- **`videopython.ai`** *(install with `[ai]`)* — generation (`TextToVideo`, `ImageToVideo`, `TextToImage`, `TextToSpeech`, `TextToMusic`), understanding (`AudioToText`, `AudioClassifier`, `SceneVLM`, `FaceTracker`, `ObjectDetector`, `SemanticSceneDetector`), the `FaceTrackingCrop` transform, the `ObjectDetectionOverlay` effect, and the full-pipeline `VideoAnalyzer`. Scene captioning and dubbing translation run on a local [Ollama](https://ollama.com) model.
|
|
99
|
+
- **`videopython.ai.auto_edit`** — `AutoEditor` + `OllamaVisionLLM`: plan and render an edit from sources + a one-line brief.
|
|
102
100
|
- **`videopython.ai.dubbing`** — `VideoDubber` for voice-cloned revoicing with timing sync.
|
|
103
|
-
- **`videopython.mcp`** *(install with `[mcp]`)* — `videopython-mcp`, an MCP stdio server exposing the auto-edit pipeline
|
|
101
|
+
- **`videopython.mcp`** *(install with `[ai,mcp]`)* — `videopython-mcp`, an MCP stdio server exposing the auto-edit pipeline so an agent drives editing.
|
|
102
|
+
|
|
103
|
+
## Documentation
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
The docs follow [Diátaxis](https://diataxis.fr): four sections, each answering a different kind of question.
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
| | |
|
|
108
|
+
|---|---|
|
|
109
|
+
| [Tutorials](https://videopython.com/tutorials/) | Learn by doing — start here if you are new |
|
|
110
|
+
| [How-to guides](https://videopython.com/how-to/) | Recipes for a specific goal |
|
|
111
|
+
| [Reference](https://videopython.com/reference/) | Classes, operations, parameters, wire format |
|
|
112
|
+
| [Explanation](https://videopython.com/explanation/) | Why the library is shaped the way it is |
|
|
111
113
|
|
|
112
114
|
## Development
|
|
113
115
|
|
|
114
|
-
See [`DEVELOPMENT.md`](DEVELOPMENT.md) for local setup, testing, and
|
|
116
|
+
See [`DEVELOPMENT.md`](DEVELOPMENT.md) for local setup, testing, docs, and the release workflow.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "videopython"
|
|
3
|
-
version = "0.55.
|
|
3
|
+
version = "0.55.1"
|
|
4
4
|
description = "Minimal video generation and processing library."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Bartosz Wójtowicz", email = "bartoszwojtowicz@outlook.com" },
|
|
@@ -32,6 +32,7 @@ classifiers = [
|
|
|
32
32
|
|
|
33
33
|
dependencies = [
|
|
34
34
|
"numpy>=1.25.2",
|
|
35
|
+
"soxr>=0.5", # polyphase resampling for Audio.resample; the engine librosa uses
|
|
35
36
|
"opencv-python-headless>=4.9.0.80",
|
|
36
37
|
"pillow>=12.1.1",
|
|
37
38
|
"resvg-py>=0.3.2",
|
|
@@ -52,26 +53,24 @@ dev = [
|
|
|
52
53
|
"mkdocs-material>=9.7.1",
|
|
53
54
|
"mkdocstrings>=1.0.0",
|
|
54
55
|
"mkdocstrings-python>=2.0.1",
|
|
56
|
+
# Keeps the pre-Diátaxis docs URLs alive (see mkdocs.yml redirect_maps).
|
|
57
|
+
# Capped <1.2.3: that release added a hard dependency on `properdocs` (an
|
|
58
|
+
# unrelated MkDocs fork) purely to print an ad during every docs build.
|
|
59
|
+
"mkdocs-redirects>=1.2,<1.2.3",
|
|
55
60
|
]
|
|
56
61
|
# Thin dev-workflow group so `uv sync` / uv.lock keep working. References the
|
|
57
62
|
# `[ai]` extra so dev installs every AI capability (single source of truth).
|
|
58
63
|
ai = ["videopython[ai]"]
|
|
59
64
|
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# A single [ai] extra installs every AI capability. The heavy ML deps still load
|
|
63
|
-
# lazily at first use (no top-level imports under ai/), so importing videopython
|
|
64
|
-
# stays light even with [ai] installed.
|
|
65
|
+
# One [ai] extra for every AI capability. Heavy ML deps load lazily at first use
|
|
66
|
+
# (no top-level imports under ai/), so importing videopython stays light.
|
|
65
67
|
[project.optional-dependencies]
|
|
66
68
|
ai = [
|
|
67
69
|
# Speech recognition / diarization (understanding/audio.py)
|
|
68
70
|
"openai-whisper>=20240930",
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
# backtracking numba to an ancient release instead, which then fails at
|
|
73
|
-
# `import whisper`. Floor it so numba's own cap does the work; 0.62 is the
|
|
74
|
-
# oldest release covering our full Python range (3.11-3.13).
|
|
71
|
+
# Floored, not capped: numba declares the numpy cap, and without a floor a
|
|
72
|
+
# resolver satisfies a newer numpy by backtracking numba instead, breaking
|
|
73
|
+
# `import whisper`. 0.62 is the oldest release covering Python 3.11-3.13.
|
|
75
74
|
"numba>=0.62",
|
|
76
75
|
"pyannote-audio>=4.0.0",
|
|
77
76
|
"silero-vad>=5.1",
|
|
@@ -82,20 +81,14 @@ ai = [
|
|
|
82
81
|
"imagehash>=4.3",
|
|
83
82
|
# Audio source separation (understanding/separation.py)
|
|
84
83
|
"demucs>=4.0.0",
|
|
85
|
-
# Voice cloning TTS (generation/audio.py
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
# diffusers>=0.35 — it made `pip install "videopython[ai]"` impossible for
|
|
89
|
-
# consumers. videopython-chatterbox is upstream's source with corrected
|
|
90
|
-
# metadata (identical apart from its self-version lookup); the import name is
|
|
91
|
-
# still `chatterbox`. See https://github.com/BartWojtowicz/videopython-chatterbox
|
|
84
|
+
# Voice cloning TTS (generation/audio.py). Our fork of chatterbox-tts with
|
|
85
|
+
# corrected metadata; import name is still `chatterbox`. Not upstream — its
|
|
86
|
+
# `==` pins make [ai] unsatisfiable. See DEVELOPMENT.md "Dependencies".
|
|
92
87
|
"videopython-chatterbox>=0.1.7.post1",
|
|
93
|
-
# Local media generation
|
|
94
|
-
# >=0.35 is the floor that ships QwenImagePipeline + Wan2.2 support (tested on 0.37.1).
|
|
88
|
+
# Local media generation (generation/*). >=0.35 ships QwenImagePipeline + Wan2.2.
|
|
95
89
|
"diffusers>=0.35.0",
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
# does not, so it is a required dep for ImageToVideo.
|
|
90
|
+
# Required, despite looking like diffusers' problem: the Wan2.2 i2v pipeline
|
|
91
|
+
# imports ftfy unguarded (the t2v one guards it with is_ftfy_available()).
|
|
99
92
|
"ftfy>=6.1",
|
|
100
93
|
"accelerate>=0.29.2",
|
|
101
94
|
# Dubbing loudness matching (dubbing/loudness.py)
|
|
@@ -106,16 +99,13 @@ ai = [
|
|
|
106
99
|
# processor used by the D-FINE object detector (understanding/objects.py).
|
|
107
100
|
"torch>=2.8.0",
|
|
108
101
|
"torchaudio>=2.8.0",
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
# import with `OSError: libcudart.so.13`. Cap every member or none.
|
|
102
|
+
# Do NOT add an upper bound. torchvision pins an exact torch, so capping it
|
|
103
|
+
# alone pinned torch to 2.8 (cu12) while torchaudio floated to 2.11 (cu13) —
|
|
104
|
+
# resolved cleanly, then died at import with `OSError: libcudart.so.13`.
|
|
105
|
+
# Cap every member of the torch stack or none.
|
|
114
106
|
"torchvision>=0.23.0",
|
|
115
|
-
# torchaudio 2.11 routes
|
|
116
|
-
#
|
|
117
|
-
# of its own. It arrives transitively via pyannote-audio; declare it so it is
|
|
118
|
-
# visible rather than incidental.
|
|
107
|
+
# Transitive via pyannote-audio, declared because torchaudio 2.11+ routes
|
|
108
|
+
# save()/load() through it and it is ABI-coupled to torch.
|
|
119
109
|
"torchcodec>=0.7.0",
|
|
120
110
|
]
|
|
121
111
|
# MCP server (videopython/mcp/). Pin <2 — v2 is pre-release/breaking.
|
|
@@ -150,36 +140,17 @@ module = [
|
|
|
150
140
|
"silero_vad", "silero_vad.*",
|
|
151
141
|
"cv2", "cv2.*",
|
|
152
142
|
"pyloudnorm", "pyloudnorm.*",
|
|
143
|
+
"soxr", "soxr.*",
|
|
153
144
|
"imagehash", "imagehash.*",
|
|
154
145
|
"mcp", "mcp.*",
|
|
155
146
|
]
|
|
156
147
|
ignore_missing_imports = true
|
|
157
148
|
|
|
158
149
|
[tool.uv]
|
|
159
|
-
#
|
|
160
|
-
# wheel
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
# The torch/torchaudio/torchvision/diffusers overrides that used to live here
|
|
164
|
-
# existed solely to paper over chatterbox-tts's `==` pins. They masked the
|
|
165
|
-
# breakage from CI while every downstream consumer hit it, and an override
|
|
166
|
-
# replaces a requirement *everywhere* — including torchvision's own exact
|
|
167
|
-
# `torch==2.8.0` pin, which would have produced an ABI-invalid pair on the next
|
|
168
|
-
# `uv lock --upgrade`. [ai] now depends on videopython-chatterbox, whose metadata
|
|
169
|
-
# is correct, so none of them are needed.
|
|
170
|
-
# The numpy>=2.0.0 override is gone for the same reason: it existed to counter
|
|
171
|
-
# chatterbox-tts's `numpy<2.0.0` pin, and it also replaced numba's `numpy<2.5`
|
|
172
|
-
# cap, resolving numpy 2.5 into an environment where `import whisper` dies with
|
|
173
|
-
# "Numba needs NumPy 2.4 or less". numba IS version-coupled to numpy.
|
|
174
|
-
override-dependencies = [
|
|
175
|
-
# Some transitive deps pull opencv-python, which conflicts with our
|
|
176
|
-
# opencv-python-headless (both provide cv2). Exclude opencv-python so
|
|
177
|
-
# only the headless variant is installed.
|
|
178
|
-
"opencv-python ; sys_platform == '_'",
|
|
179
|
-
]
|
|
180
|
-
# Pin minimum versions for transitive deps with known vulnerabilities.
|
|
181
|
-
# Pygments 2.20.0 has a security fix but breaks mkdocs (passes None to html.escape).
|
|
182
|
-
# Keep pygments<2.20.0 until a compatible release is available.
|
|
150
|
+
# No overrides, deliberately: they are a uv workspace feature and do not ship in
|
|
151
|
+
# the wheel, so anything patched here is invisible to `pip install`. Fix the
|
|
152
|
+
# dependency's metadata instead — see DEVELOPMENT.md "Dependencies".
|
|
153
|
+
# requests: CVE floor. pygments <2.20.0: 2.20.0 breaks mkdocs (passes None to html.escape).
|
|
183
154
|
constraint-dependencies = ["requests>=2.33.0", "pygments>=2.19.2,<2.20.0"]
|
|
184
155
|
|
|
185
156
|
[build-system]
|
|
@@ -207,9 +178,6 @@ pythonpath = ["src/"]
|
|
|
207
178
|
testpaths = ["src/tests"]
|
|
208
179
|
python_files = ["test_*.py"]
|
|
209
180
|
addopts = ["-v", "--tb=short"]
|
|
210
|
-
markers = [
|
|
211
|
-
"requires_model_download: marks tests that require downloading ML models (skipped in CI)",
|
|
212
|
-
]
|
|
213
181
|
|
|
214
182
|
[tool.ruff]
|
|
215
183
|
line-length = 120
|
|
@@ -8,6 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
from typing import TYPE_CHECKING, Any
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
|
+
import soxr
|
|
11
12
|
|
|
12
13
|
from videopython.base import _ffmpeg
|
|
13
14
|
from videopython.base.exceptions import AudioLoadError, FFmpegProbeError
|
|
@@ -384,6 +385,8 @@ class Audio:
|
|
|
384
385
|
"""
|
|
385
386
|
Resample the audio to a new sample rate
|
|
386
387
|
|
|
388
|
+
Uses `soxr` (band-limited polyphase), the same engine librosa resamples with.
|
|
389
|
+
|
|
387
390
|
Args:
|
|
388
391
|
target_sample_rate: New sample rate in Hz
|
|
389
392
|
|
|
@@ -393,19 +396,16 @@ class Audio:
|
|
|
393
396
|
if target_sample_rate == self.metadata.sample_rate:
|
|
394
397
|
return self
|
|
395
398
|
|
|
396
|
-
#
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
audio_array = audio_array.reshape(-1, 1)
|
|
404
|
-
|
|
405
|
-
resampled_data = np.zeros((target_length, self.metadata.channels), dtype=np.float32)
|
|
399
|
+
# soxr takes mono (1-D) and interleaved stereo directly. "HQ" is librosa's default.
|
|
400
|
+
resampled_data = soxr.resample(
|
|
401
|
+
self.data,
|
|
402
|
+
self.metadata.sample_rate,
|
|
403
|
+
target_sample_rate,
|
|
404
|
+
quality="HQ",
|
|
405
|
+
).astype(np.float32)
|
|
406
406
|
|
|
407
|
-
|
|
408
|
-
|
|
407
|
+
# From the resampler, not a predicted round(n * ratio): metadata must match the array.
|
|
408
|
+
target_length = resampled_data.shape[0]
|
|
409
409
|
|
|
410
410
|
new_metadata = AudioMetadata(
|
|
411
411
|
sample_rate=target_sample_rate,
|
|
@@ -414,41 +414,9 @@ class Audio:
|
|
|
414
414
|
duration_seconds=target_length / target_sample_rate,
|
|
415
415
|
frame_count=target_length,
|
|
416
416
|
)
|
|
417
|
-
if self.metadata.channels == 1:
|
|
418
|
-
resampled_data = resampled_data.flatten()
|
|
419
417
|
|
|
420
418
|
return Audio(resampled_data, new_metadata)
|
|
421
419
|
|
|
422
|
-
@staticmethod
|
|
423
|
-
def _resample_channel(data: np.ndarray, new_length: int) -> np.ndarray:
|
|
424
|
-
"""Resample a single channel of audio data to a new length"""
|
|
425
|
-
|
|
426
|
-
data_fourier = np.fft.rfft(data)
|
|
427
|
-
original_length = data.shape[0]
|
|
428
|
-
|
|
429
|
-
newshape = [new_length // 2 + 1]
|
|
430
|
-
|
|
431
|
-
data_fourier_placeholder = np.zeros(newshape, data_fourier.dtype)
|
|
432
|
-
|
|
433
|
-
min_length = min(new_length, original_length)
|
|
434
|
-
nyquist = min_length // 2 + 1
|
|
435
|
-
sl = [slice(0, nyquist)]
|
|
436
|
-
data_fourier_placeholder[tuple(sl)] = data_fourier[tuple(sl)]
|
|
437
|
-
|
|
438
|
-
if min_length % 2 == 0:
|
|
439
|
-
if new_length < original_length:
|
|
440
|
-
sl = [slice(min_length // 2, min_length // 2 + 1)]
|
|
441
|
-
data_fourier_placeholder[tuple(sl)] *= 2.0
|
|
442
|
-
|
|
443
|
-
sl = [slice(min_length // 2, min_length // 2 + 1)]
|
|
444
|
-
data_fourier_placeholder[tuple(sl)] *= 0.5
|
|
445
|
-
|
|
446
|
-
resampled_data = np.fft.irfft(data_fourier_placeholder, new_length)
|
|
447
|
-
|
|
448
|
-
resampled_data *= float(new_length) / float(original_length)
|
|
449
|
-
|
|
450
|
-
return resampled_data
|
|
451
|
-
|
|
452
420
|
def concat(self, other: Audio, crossfade: float = 0.0) -> Audio:
|
|
453
421
|
"""
|
|
454
422
|
Concatenate another audio segment to this one.
|
|
@@ -311,9 +311,8 @@ class ColorGrading(Effect):
|
|
|
311
311
|
if self.brightness != 0 or self.contrast != 1.0:
|
|
312
312
|
out = cv2.LUT(out, self._lut_tone)
|
|
313
313
|
if self.saturation != 1.0:
|
|
314
|
-
#
|
|
315
|
-
#
|
|
316
|
-
# than every other stage of the grade combined (~42 of 56 ms/frame).
|
|
314
|
+
# Blend toward luma-weighted greyscale. Not an HSV round trip: that
|
|
315
|
+
# costs more than every other stage of the grade combined.
|
|
317
316
|
grey = cv2.cvtColor(out, cv2.COLOR_RGB2GRAY)
|
|
318
317
|
out = cv2.addWeighted(
|
|
319
318
|
out, self.saturation, cv2.cvtColor(grey, cv2.COLOR_GRAY2RGB), 1.0 - self.saturation, 0
|
|
@@ -364,17 +363,13 @@ class Vignette(Effect):
|
|
|
364
363
|
def streaming_init(self, total_frames: int, fps: float, width: int, height: int, **_context: Any) -> None:
|
|
365
364
|
"""Bake the gain mask into a 3-channel uint8 lookup, once per stream.
|
|
366
365
|
|
|
367
|
-
The mask is static, so
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
NEGATIVE once ``strength`` is high enough (down to -1.0 at
|
|
375
|
-
``strength=1.0``), and ``(frame * -1.0).astype(np.uint8)`` wraps around,
|
|
376
|
-
so the darkest corners rendered as mid-grey (200 -> 56) and the vignette
|
|
377
|
-
got *brighter* past the zero crossing instead of saturating to black.
|
|
366
|
+
The mask is static, so per-frame work is one ``cv2.multiply`` SIMD pass
|
|
367
|
+
with no float conversion.
|
|
368
|
+
|
|
369
|
+
Clipping to [0, 1] is load-bearing, not tidying: ``_create_mask`` goes
|
|
370
|
+
negative once ``strength`` is high enough (-1.0 at ``strength=1.0``), and
|
|
371
|
+
``(frame * -1.0).astype(np.uint8)`` wraps, which renders the darkest
|
|
372
|
+
corners mid-grey and makes the vignette brighten past the zero crossing.
|
|
378
373
|
"""
|
|
379
374
|
if self._mask is None or self._mask.shape != (height, width):
|
|
380
375
|
self._mask = self._create_mask(height, width)
|
|
@@ -1429,18 +1424,14 @@ class FilmGrain(Effect):
|
|
|
1429
1424
|
_geometry: tuple[int, int] = PrivateAttr(default=(0, 0))
|
|
1430
1425
|
|
|
1431
1426
|
def streaming_init(self, total_frames: int, fps: float, width: int, height: int, **_context: Any) -> None:
|
|
1432
|
-
"""Draw one oversized noise plane up front; each frame reads a random window
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
for one saturating integer add.
|
|
1441
|
-
|
|
1442
|
-
Reproducibility is unchanged in contract (same ``seed`` -> same grain),
|
|
1443
|
-
though the pattern itself differs from the per-frame-RNG version.
|
|
1427
|
+
"""Draw one oversized noise plane up front; each frame reads a random window.
|
|
1428
|
+
|
|
1429
|
+
Sampling Gaussian noise per frame costs ~2M draws a frame and dominates
|
|
1430
|
+
the effect; one padded plane plus a per-frame offset reduces that to a
|
|
1431
|
+
saturating integer add.
|
|
1432
|
+
|
|
1433
|
+
Offsets jump rather than advance, so the grain scintillates like film
|
|
1434
|
+
instead of sliding. Same ``seed`` still gives the same grain.
|
|
1444
1435
|
"""
|
|
1445
1436
|
amp = self.intensity * 255.0
|
|
1446
1437
|
pad = GRAIN_POOL_PAD
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/understanding/classification.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{videopython-0.55.0 → videopython-0.55.1}/src/videopython/ai/video_analysis/source_metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|