videopython 0.25.1__tar.gz → 0.25.3__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.25.1 → videopython-0.25.3}/PKG-INFO +4 -3
- {videopython-0.25.1 → videopython-0.25.3}/README.md +1 -1
- {videopython-0.25.1 → videopython-0.25.3}/pyproject.toml +3 -2
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/editing/multicam.py +44 -4
- {videopython-0.25.1 → videopython-0.25.3}/.gitignore +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/LICENSE +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/__init__.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/__init__.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/_device.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/dubbing/__init__.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/dubbing/dubber.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/dubbing/models.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/dubbing/pipeline.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/dubbing/timing.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/generation/__init__.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/generation/audio.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/generation/image.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/generation/translation.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/generation/video.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/registry.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/swapping/__init__.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/swapping/inpainter.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/swapping/models.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/swapping/segmenter.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/swapping/swapper.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/transforms.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/understanding/__init__.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/understanding/audio.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/understanding/image.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/understanding/separation.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/understanding/temporal.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/video_analysis.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/__init__.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/audio/__init__.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/audio/analysis.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/audio/audio.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/combine.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/description.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/effects.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/exceptions.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/progress.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/registry.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/scene.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/text/__init__.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/text/overlay.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/text/transcription.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/transforms.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/transitions.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/utils.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/video.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/editing/__init__.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/editing/video_edit.py +0 -0
- {videopython-0.25.1 → videopython-0.25.3}/src/videopython/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: videopython
|
|
3
|
-
Version: 0.25.
|
|
3
|
+
Version: 0.25.3
|
|
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/
|
|
@@ -15,7 +15,8 @@ Classifier: Programming Language :: Python :: 3
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
-
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Python: <3.14,>=3.10
|
|
19
20
|
Requires-Dist: numpy>=1.25.2
|
|
20
21
|
Requires-Dist: opencv-python-headless>=4.9.0.80
|
|
21
22
|
Requires-Dist: pillow>=12.1.1
|
|
@@ -81,7 +82,7 @@ pip install videopython # core video/audio editing
|
|
|
81
82
|
pip install "videopython[ai]" # + local AI features (GPU recommended)
|
|
82
83
|
```
|
|
83
84
|
|
|
84
|
-
Python `>=3.10, <3.
|
|
85
|
+
Python `>=3.10, <3.14`. AI features run locally - no cloud API keys required, but model weights are downloaded on first use.
|
|
85
86
|
|
|
86
87
|
## Quick Start
|
|
87
88
|
|
|
@@ -30,7 +30,7 @@ pip install videopython # core video/audio editing
|
|
|
30
30
|
pip install "videopython[ai]" # + local AI features (GPU recommended)
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Python `>=3.10, <3.
|
|
33
|
+
Python `>=3.10, <3.14`. AI features run locally - no cloud API keys required, but model weights are downloaded on first use.
|
|
34
34
|
|
|
35
35
|
## Quick Start
|
|
36
36
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "videopython"
|
|
3
|
-
version = "0.25.
|
|
3
|
+
version = "0.25.3"
|
|
4
4
|
description = "Minimal video generation and processing library."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Bartosz Wójtowicz", email = "bartoszwojtowicz@outlook.com" },
|
|
@@ -9,7 +9,7 @@ authors = [
|
|
|
9
9
|
]
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
11
11
|
readme = "README.md"
|
|
12
|
-
requires-python = ">=3.10, <3.
|
|
12
|
+
requires-python = ">=3.10, <3.14"
|
|
13
13
|
keywords = [
|
|
14
14
|
"python",
|
|
15
15
|
"videopython",
|
|
@@ -27,6 +27,7 @@ classifiers = [
|
|
|
27
27
|
"Programming Language :: Python :: 3.10",
|
|
28
28
|
"Programming Language :: Python :: 3.11",
|
|
29
29
|
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Programming Language :: Python :: 3.13",
|
|
30
31
|
"Operating System :: OS Independent",
|
|
31
32
|
]
|
|
32
33
|
|
|
@@ -46,6 +46,7 @@ class MultiCamEdit:
|
|
|
46
46
|
cuts: Sequence[CutPoint],
|
|
47
47
|
audio_source: str | Path | None = None,
|
|
48
48
|
default_transition: Transition | None = None,
|
|
49
|
+
source_offsets: dict[str, float] | None = None,
|
|
49
50
|
):
|
|
50
51
|
if not sources:
|
|
51
52
|
raise ValueError("MultiCamEdit requires at least one source")
|
|
@@ -56,6 +57,7 @@ class MultiCamEdit:
|
|
|
56
57
|
self.cuts: tuple[CutPoint, ...] = tuple(cuts)
|
|
57
58
|
self.audio_source: Path | None = Path(audio_source) if audio_source else None
|
|
58
59
|
self.default_transition: Transition = default_transition or InstantTransition()
|
|
60
|
+
self.source_offsets: dict[str, float] = source_offsets or {}
|
|
59
61
|
|
|
60
62
|
self._validate()
|
|
61
63
|
|
|
@@ -88,6 +90,13 @@ class MultiCamEdit:
|
|
|
88
90
|
f"Cut {i} references unknown camera '{cut.camera}'. Available: {sorted(self.sources.keys())}"
|
|
89
91
|
)
|
|
90
92
|
|
|
93
|
+
# All offset keys must reference valid sources
|
|
94
|
+
for name in self.source_offsets:
|
|
95
|
+
if name not in self.sources:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"source_offsets references unknown source '{name}'. Available: {sorted(self.sources.keys())}"
|
|
98
|
+
)
|
|
99
|
+
|
|
91
100
|
# All sources must have compatible fps and resolution
|
|
92
101
|
metas: dict[str, VideoMetadata] = {}
|
|
93
102
|
for name, path in self.sources.items():
|
|
@@ -110,11 +119,32 @@ class MultiCamEdit:
|
|
|
110
119
|
# Cache source metadata for validate() and run()
|
|
111
120
|
self._source_meta = first
|
|
112
121
|
self._source_duration = first.total_seconds
|
|
122
|
+
self._source_metas = metas
|
|
113
123
|
|
|
114
|
-
#
|
|
124
|
+
# Build per-camera time ranges (cut start, cut end) from the timeline
|
|
125
|
+
camera_ranges: dict[str, list[tuple[float, float]]] = {}
|
|
115
126
|
for i, cut in enumerate(self.cuts):
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
start = cut.time
|
|
128
|
+
end = self.cuts[i + 1].time if i + 1 < len(self.cuts) else self._source_duration
|
|
129
|
+
camera_ranges.setdefault(cut.camera, []).append((start, end))
|
|
130
|
+
|
|
131
|
+
# Validate adjusted seek positions per source
|
|
132
|
+
for camera, ranges in camera_ranges.items():
|
|
133
|
+
offset = self.source_offsets.get(camera, 0.0)
|
|
134
|
+
source_dur = metas[camera].total_seconds
|
|
135
|
+
for start, end in ranges:
|
|
136
|
+
adj_start = start - offset
|
|
137
|
+
adj_end = end - offset
|
|
138
|
+
if adj_start < 0:
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"Cut at timeline {start}s for '{camera}' (offset {offset}s) "
|
|
141
|
+
f"results in negative seek position ({adj_start}s)"
|
|
142
|
+
)
|
|
143
|
+
if adj_end > source_dur:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
f"Cut ending at timeline {end}s for '{camera}' (offset {offset}s) "
|
|
146
|
+
f"exceeds source duration ({source_dur}s)"
|
|
147
|
+
)
|
|
118
148
|
|
|
119
149
|
def run(self) -> Video:
|
|
120
150
|
"""Execute the multicam edit and return the final video."""
|
|
@@ -131,7 +161,8 @@ class MultiCamEdit:
|
|
|
131
161
|
result: Video | None = None
|
|
132
162
|
for i, (cut, start, end) in enumerate(segments):
|
|
133
163
|
source_path = self.sources[cut.camera]
|
|
134
|
-
|
|
164
|
+
offset = self.source_offsets.get(cut.camera, 0.0)
|
|
165
|
+
segment = Video.from_path(str(source_path), start_second=start - offset, end_second=end - offset)
|
|
135
166
|
|
|
136
167
|
if result is None:
|
|
137
168
|
result = segment
|
|
@@ -258,6 +289,12 @@ class MultiCamEdit:
|
|
|
258
289
|
"additionalProperties": {"type": "string"},
|
|
259
290
|
"minProperties": 1,
|
|
260
291
|
},
|
|
292
|
+
"source_offsets": {
|
|
293
|
+
"type": "object",
|
|
294
|
+
"additionalProperties": {"type": "number"},
|
|
295
|
+
"description": "Per-source time offsets in seconds. "
|
|
296
|
+
"Positive means the source starts later than the timeline origin.",
|
|
297
|
+
},
|
|
261
298
|
"audio_source": {
|
|
262
299
|
"type": "string",
|
|
263
300
|
"description": "Path to external audio track. Omit for silent output.",
|
|
@@ -284,6 +321,8 @@ class MultiCamEdit:
|
|
|
284
321
|
"cuts": [],
|
|
285
322
|
"default_transition": self.default_transition.to_dict(),
|
|
286
323
|
}
|
|
324
|
+
if self.source_offsets:
|
|
325
|
+
result["source_offsets"] = dict(self.source_offsets)
|
|
287
326
|
if self.audio_source:
|
|
288
327
|
result["audio_source"] = str(self.audio_source)
|
|
289
328
|
|
|
@@ -333,6 +372,7 @@ class MultiCamEdit:
|
|
|
333
372
|
cuts=cuts,
|
|
334
373
|
audio_source=data.get("audio_source"),
|
|
335
374
|
default_transition=default_transition,
|
|
375
|
+
source_offsets=data.get("source_offsets"),
|
|
336
376
|
)
|
|
337
377
|
|
|
338
378
|
@classmethod
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|