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.
Files changed (53) hide show
  1. {videopython-0.25.1 → videopython-0.25.3}/PKG-INFO +4 -3
  2. {videopython-0.25.1 → videopython-0.25.3}/README.md +1 -1
  3. {videopython-0.25.1 → videopython-0.25.3}/pyproject.toml +3 -2
  4. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/editing/multicam.py +44 -4
  5. {videopython-0.25.1 → videopython-0.25.3}/.gitignore +0 -0
  6. {videopython-0.25.1 → videopython-0.25.3}/LICENSE +0 -0
  7. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/__init__.py +0 -0
  8. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/__init__.py +0 -0
  9. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/_device.py +0 -0
  10. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/dubbing/__init__.py +0 -0
  11. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/dubbing/dubber.py +0 -0
  12. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/dubbing/models.py +0 -0
  13. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/dubbing/pipeline.py +0 -0
  14. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/dubbing/timing.py +0 -0
  15. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/generation/__init__.py +0 -0
  16. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/generation/audio.py +0 -0
  17. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/generation/image.py +0 -0
  18. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/generation/translation.py +0 -0
  19. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/generation/video.py +0 -0
  20. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/registry.py +0 -0
  21. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/swapping/__init__.py +0 -0
  22. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/swapping/inpainter.py +0 -0
  23. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/swapping/models.py +0 -0
  24. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/swapping/segmenter.py +0 -0
  25. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/swapping/swapper.py +0 -0
  26. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/transforms.py +0 -0
  27. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/understanding/__init__.py +0 -0
  28. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/understanding/audio.py +0 -0
  29. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/understanding/image.py +0 -0
  30. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/understanding/separation.py +0 -0
  31. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/understanding/temporal.py +0 -0
  32. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/ai/video_analysis.py +0 -0
  33. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/__init__.py +0 -0
  34. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/audio/__init__.py +0 -0
  35. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/audio/analysis.py +0 -0
  36. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/audio/audio.py +0 -0
  37. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/combine.py +0 -0
  38. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/description.py +0 -0
  39. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/effects.py +0 -0
  40. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/exceptions.py +0 -0
  41. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/progress.py +0 -0
  42. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/registry.py +0 -0
  43. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/scene.py +0 -0
  44. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/text/__init__.py +0 -0
  45. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/text/overlay.py +0 -0
  46. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/text/transcription.py +0 -0
  47. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/transforms.py +0 -0
  48. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/transitions.py +0 -0
  49. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/utils.py +0 -0
  50. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/base/video.py +0 -0
  51. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/editing/__init__.py +0 -0
  52. {videopython-0.25.1 → videopython-0.25.3}/src/videopython/editing/video_edit.py +0 -0
  53. {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.1
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
- Requires-Python: <3.13,>=3.10
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.13`. AI features run locally - no cloud API keys required, but model weights are downloaded on first use.
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.13`. AI features run locally - no cloud API keys required, but model weights are downloaded on first use.
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.1"
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.13"
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
- # Cuts must not exceed source duration
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
- if cut.time > self._source_duration:
117
- raise ValueError(f"Cut {i} time ({cut.time}) exceeds source duration ({self._source_duration}s)")
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
- segment = Video.from_path(str(source_path), start_second=start, end_second=end)
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