videopython 0.22.1__tar.gz → 0.22.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 (51) hide show
  1. {videopython-0.22.1 → videopython-0.22.3}/PKG-INFO +1 -1
  2. {videopython-0.22.1 → videopython-0.22.3}/pyproject.toml +1 -1
  3. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/edit.py +63 -1
  4. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/effects.py +2 -2
  5. {videopython-0.22.1 → videopython-0.22.3}/.gitignore +0 -0
  6. {videopython-0.22.1 → videopython-0.22.3}/LICENSE +0 -0
  7. {videopython-0.22.1 → videopython-0.22.3}/README.md +0 -0
  8. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/__init__.py +0 -0
  9. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/__init__.py +0 -0
  10. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/_device.py +0 -0
  11. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/dubbing/__init__.py +0 -0
  12. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/dubbing/dubber.py +0 -0
  13. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/dubbing/models.py +0 -0
  14. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/dubbing/pipeline.py +0 -0
  15. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/dubbing/timing.py +0 -0
  16. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/generation/__init__.py +0 -0
  17. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/generation/audio.py +0 -0
  18. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/generation/image.py +0 -0
  19. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/generation/translation.py +0 -0
  20. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/generation/video.py +0 -0
  21. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/registry.py +0 -0
  22. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/swapping/__init__.py +0 -0
  23. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/swapping/inpainter.py +0 -0
  24. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/swapping/models.py +0 -0
  25. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/swapping/segmenter.py +0 -0
  26. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/swapping/swapper.py +0 -0
  27. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/transforms.py +0 -0
  28. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/understanding/__init__.py +0 -0
  29. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/understanding/audio.py +0 -0
  30. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/understanding/image.py +0 -0
  31. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/understanding/separation.py +0 -0
  32. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/understanding/temporal.py +0 -0
  33. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/video_analysis.py +0 -0
  34. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/__init__.py +0 -0
  35. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/audio/__init__.py +0 -0
  36. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/audio/analysis.py +0 -0
  37. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/audio/audio.py +0 -0
  38. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/combine.py +0 -0
  39. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/description.py +0 -0
  40. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/exceptions.py +0 -0
  41. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/progress.py +0 -0
  42. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/registry.py +0 -0
  43. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/scene.py +0 -0
  44. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/text/__init__.py +0 -0
  45. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/text/overlay.py +0 -0
  46. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/text/transcription.py +0 -0
  47. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/transforms.py +0 -0
  48. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/transitions.py +0 -0
  49. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/utils.py +0 -0
  50. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/video.py +0 -0
  51. {videopython-0.22.1 → videopython-0.22.3}/src/videopython/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: videopython
3
- Version: 0.22.1
3
+ Version: 0.22.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/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "videopython"
3
- version = "0.22.1"
3
+ version = "0.22.3"
4
4
  description = "Minimal video generation and processing library."
5
5
  authors = [
6
6
  { name = "Bartosz Wójtowicz", email = "bartoszwojtowicz@outlook.com" },
@@ -272,11 +272,51 @@ class VideoEdit:
272
272
  return video
273
273
 
274
274
  def validate(self) -> VideoMetadata:
275
- """Validate the editing plan without loading video data."""
275
+ """Validate the editing plan without loading video data.
276
+
277
+ Requires source video files to be present on disk (uses ``VideoMetadata.from_path``).
278
+ For validation without file access, use :meth:`validate_with_metadata`.
279
+ """
276
280
  segment_metas: list[VideoMetadata] = []
277
281
  for i, segment in enumerate(self.segments):
278
282
  segment_metas.append(self._validate_segment(i, segment))
283
+ return self._validate_assembled(segment_metas)
284
+
285
+ def validate_with_metadata(
286
+ self,
287
+ source_metadata: VideoMetadata | dict[str, VideoMetadata],
288
+ ) -> VideoMetadata:
289
+ """Validate the editing plan using pre-built metadata instead of loading from file.
290
+
291
+ Same validation as validate() but accepts a VideoMetadata directly,
292
+ avoiding the need for the source video file to be on disk.
293
+
294
+ Args:
295
+ source_metadata: VideoMetadata for the source video (duration, dimensions, fps).
296
+ For multi-source plans, pass a dict mapping source paths to their metadata.
297
+
298
+ Returns:
299
+ Predicted output VideoMetadata after all operations.
279
300
 
301
+ Raises:
302
+ ValueError: If any validation check fails.
303
+ """
304
+ if isinstance(source_metadata, VideoMetadata):
305
+ meta_map: dict[str, VideoMetadata] = {str(seg.source_video): source_metadata for seg in self.segments}
306
+ else:
307
+ meta_map = source_metadata
308
+
309
+ segment_metas: list[VideoMetadata] = []
310
+ for i, segment in enumerate(self.segments):
311
+ source_key = str(segment.source_video)
312
+ if source_key not in meta_map:
313
+ raise ValueError(
314
+ f"Segment {i}: no metadata provided for source '{source_key}'. Available keys: {sorted(meta_map)}"
315
+ )
316
+ segment_metas.append(self._validate_segment_with_metadata(i, segment, meta_map[source_key]))
317
+ return self._validate_assembled(segment_metas)
318
+
319
+ def _validate_assembled(self, segment_metas: list[VideoMetadata]) -> VideoMetadata:
280
320
  if len(segment_metas) > 1:
281
321
  first = segment_metas[0]
282
322
  for j, other in enumerate(segment_metas[1:], start=1):
@@ -343,6 +383,28 @@ class VideoEdit:
343
383
  _validate_effect_bounds(record, meta.total_seconds, context=ctx)
344
384
  return meta
345
385
 
386
+ def _validate_segment_with_metadata(
387
+ self, index: int, segment: SegmentConfig, source_meta: VideoMetadata
388
+ ) -> VideoMetadata:
389
+ ctx = f"Segment {index}"
390
+ if segment.start_second < 0:
391
+ raise ValueError(f"{ctx}: start_second ({segment.start_second}) must be >= 0")
392
+ if segment.end_second <= segment.start_second:
393
+ raise ValueError(
394
+ f"{ctx}: end_second ({segment.end_second}) must be > start_second ({segment.start_second})"
395
+ )
396
+ if segment.end_second > source_meta.total_seconds:
397
+ raise ValueError(
398
+ f"{ctx}: end_second ({segment.end_second}) exceeds source duration ({source_meta.total_seconds}s)"
399
+ )
400
+ meta = source_meta.cut(segment.start_second, segment.end_second)
401
+
402
+ for record in segment.transform_records:
403
+ meta = _predict_transform_metadata(meta, record.op_id, record.args, context=f"{ctx} ({record.op_id})")
404
+ for record in segment.effect_records:
405
+ _validate_effect_bounds(record, meta.total_seconds, context=ctx)
406
+ return meta
407
+
346
408
  def _assemble_segments(self, context: dict[str, Any] | None = None) -> Video:
347
409
  result: Video | None = None
348
410
  for segment in self.segments:
@@ -582,7 +582,7 @@ class Fade(Effect):
582
582
  if video.audio is not None and not video.audio.is_silent:
583
583
  sample_rate = video.audio.metadata.sample_rate
584
584
  audio_start = round(start_s * sample_rate)
585
- audio_end = round(stop_s * sample_rate)
585
+ audio_end = min(round(stop_s * sample_rate), len(video.audio.data))
586
586
  n_audio_samples = audio_end - audio_start
587
587
  fade_samples = min(round(self.duration * sample_rate), n_audio_samples)
588
588
 
@@ -656,7 +656,7 @@ class VolumeAdjust(AudioEffect):
656
656
 
657
657
  sample_rate = audio.metadata.sample_rate
658
658
  start_sample = round(start * sample_rate)
659
- end_sample = round(stop * sample_rate)
659
+ end_sample = min(round(stop * sample_rate), len(audio.data))
660
660
  n_samples = end_sample - start_sample
661
661
 
662
662
  # Build volume envelope
File without changes
File without changes
File without changes