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.
- {videopython-0.22.1 → videopython-0.22.3}/PKG-INFO +1 -1
- {videopython-0.22.1 → videopython-0.22.3}/pyproject.toml +1 -1
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/edit.py +63 -1
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/effects.py +2 -2
- {videopython-0.22.1 → videopython-0.22.3}/.gitignore +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/LICENSE +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/README.md +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/__init__.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/__init__.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/_device.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/dubbing/__init__.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/dubbing/dubber.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/dubbing/models.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/dubbing/pipeline.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/dubbing/timing.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/generation/__init__.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/generation/audio.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/generation/image.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/generation/translation.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/generation/video.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/registry.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/swapping/__init__.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/swapping/inpainter.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/swapping/models.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/swapping/segmenter.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/swapping/swapper.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/transforms.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/understanding/__init__.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/understanding/audio.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/understanding/image.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/understanding/separation.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/understanding/temporal.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/ai/video_analysis.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/__init__.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/audio/__init__.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/audio/analysis.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/audio/audio.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/combine.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/description.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/exceptions.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/progress.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/registry.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/scene.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/text/__init__.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/text/overlay.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/text/transcription.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/transforms.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/transitions.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/utils.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/base/video.py +0 -0
- {videopython-0.22.1 → videopython-0.22.3}/src/videopython/py.typed +0 -0
|
@@ -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
|
|
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
|