videopython 0.22.0__tar.gz → 0.22.2__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.0 → videopython-0.22.2}/PKG-INFO +1 -1
- {videopython-0.22.0 → videopython-0.22.2}/pyproject.toml +1 -1
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/understanding/audio.py +2 -2
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/edit.py +63 -1
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/text/transcription.py +13 -5
- {videopython-0.22.0 → videopython-0.22.2}/.gitignore +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/LICENSE +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/README.md +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/__init__.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/__init__.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/_device.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/dubbing/__init__.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/dubbing/dubber.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/dubbing/models.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/dubbing/pipeline.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/dubbing/timing.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/generation/__init__.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/generation/audio.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/generation/image.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/generation/translation.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/generation/video.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/registry.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/swapping/__init__.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/swapping/inpainter.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/swapping/models.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/swapping/segmenter.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/swapping/swapper.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/transforms.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/understanding/__init__.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/understanding/image.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/understanding/separation.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/understanding/temporal.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/ai/video_analysis.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/__init__.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/audio/__init__.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/audio/analysis.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/audio/audio.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/combine.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/description.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/effects.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/exceptions.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/progress.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/registry.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/scene.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/text/__init__.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/text/overlay.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/transforms.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/transitions.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/utils.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/base/video.py +0 -0
- {videopython-0.22.0 → videopython-0.22.2}/src/videopython/py.typed +0 -0
|
@@ -59,7 +59,7 @@ class AudioToText:
|
|
|
59
59
|
)
|
|
60
60
|
transcription_segments.append(transcription_segment)
|
|
61
61
|
|
|
62
|
-
return Transcription(segments=transcription_segments)
|
|
62
|
+
return Transcription(segments=transcription_segments, language=transcription_result.get("language"))
|
|
63
63
|
|
|
64
64
|
def _process_whisperx_result(self, whisperx_result: dict, audio_data) -> Transcription:
|
|
65
65
|
"""Process whisperx result with diarization."""
|
|
@@ -94,7 +94,7 @@ class AudioToText:
|
|
|
94
94
|
)
|
|
95
95
|
)
|
|
96
96
|
|
|
97
|
-
return Transcription(words=words)
|
|
97
|
+
return Transcription(words=words, language=whisperx_result.get("language"))
|
|
98
98
|
|
|
99
99
|
def _transcribe_local(self, audio: Audio) -> Transcription:
|
|
100
100
|
"""Transcribe using local Whisper model."""
|
|
@@ -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:
|
|
@@ -67,12 +67,14 @@ class Transcription:
|
|
|
67
67
|
self,
|
|
68
68
|
segments: list[TranscriptionSegment] | None = None,
|
|
69
69
|
words: list[TranscriptionWord] | None = None,
|
|
70
|
+
language: str | None = None,
|
|
70
71
|
):
|
|
71
72
|
"""Initialize Transcription from either segments or words.
|
|
72
73
|
|
|
73
74
|
Args:
|
|
74
75
|
segments: Pre-constructed segments (backward compatible)
|
|
75
76
|
words: Words to group into segments by speaker (for diarization)
|
|
77
|
+
language: ISO 639-1 language code detected during transcription (e.g. "en", "pl")
|
|
76
78
|
|
|
77
79
|
Raises:
|
|
78
80
|
ValueError: If both or neither arguments are provided
|
|
@@ -80,6 +82,8 @@ class Transcription:
|
|
|
80
82
|
if (segments is None) == (words is None):
|
|
81
83
|
raise ValueError("Exactly one of 'segments' or 'words' must be provided")
|
|
82
84
|
|
|
85
|
+
self.language = language
|
|
86
|
+
|
|
83
87
|
if segments is not None:
|
|
84
88
|
self.segments = segments
|
|
85
89
|
self.speakers = {s.speaker for s in segments if s.speaker is not None}
|
|
@@ -185,7 +189,7 @@ class Transcription:
|
|
|
185
189
|
)
|
|
186
190
|
)
|
|
187
191
|
|
|
188
|
-
return Transcription(segments=offset_segments)
|
|
192
|
+
return Transcription(segments=offset_segments, language=self.language)
|
|
189
193
|
|
|
190
194
|
def standardize_segments(self, *, time: float | None = None, num_words: int | None = None) -> Transcription:
|
|
191
195
|
"""Return a new Transcription with standardized segments.
|
|
@@ -212,7 +216,7 @@ class Transcription:
|
|
|
212
216
|
all_words.extend(segment.words)
|
|
213
217
|
|
|
214
218
|
if not all_words:
|
|
215
|
-
return Transcription(segments=[])
|
|
219
|
+
return Transcription(segments=[], language=self.language)
|
|
216
220
|
|
|
217
221
|
standardized_segments = []
|
|
218
222
|
|
|
@@ -266,7 +270,7 @@ class Transcription:
|
|
|
266
270
|
)
|
|
267
271
|
)
|
|
268
272
|
|
|
269
|
-
return Transcription(segments=standardized_segments)
|
|
273
|
+
return Transcription(segments=standardized_segments, language=self.language)
|
|
270
274
|
|
|
271
275
|
def slice(self, start: float, end: float) -> Transcription | None:
|
|
272
276
|
"""Return a new Transcription containing only words within the time range.
|
|
@@ -334,15 +338,19 @@ class Transcription:
|
|
|
334
338
|
)
|
|
335
339
|
)
|
|
336
340
|
|
|
337
|
-
return Transcription(segments=sliced_segments)
|
|
341
|
+
return Transcription(segments=sliced_segments, language=self.language)
|
|
338
342
|
|
|
339
343
|
def to_dict(self) -> dict:
|
|
340
344
|
"""Convert to dictionary for JSON serialization."""
|
|
341
345
|
return {
|
|
342
346
|
"segments": [s.to_dict() for s in self.segments],
|
|
347
|
+
"language": self.language,
|
|
343
348
|
}
|
|
344
349
|
|
|
345
350
|
@classmethod
|
|
346
351
|
def from_dict(cls, data: dict) -> Transcription:
|
|
347
352
|
"""Create Transcription from dictionary."""
|
|
348
|
-
return cls(
|
|
353
|
+
return cls(
|
|
354
|
+
segments=[TranscriptionSegment.from_dict(s) for s in data["segments"]],
|
|
355
|
+
language=data.get("language"),
|
|
356
|
+
)
|
|
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
|