videopython 0.25.7__tar.gz → 0.25.8__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.7 → videopython-0.25.8}/PKG-INFO +1 -1
- {videopython-0.25.7 → videopython-0.25.8}/pyproject.toml +1 -1
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/effects.py +64 -98
- {videopython-0.25.7 → videopython-0.25.8}/.gitignore +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/LICENSE +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/README.md +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/__init__.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/__init__.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/_device.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/dubbing/__init__.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/dubbing/dubber.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/dubbing/models.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/dubbing/pipeline.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/dubbing/timing.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/generation/__init__.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/generation/audio.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/generation/image.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/generation/translation.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/generation/video.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/registry.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/swapping/__init__.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/swapping/inpainter.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/swapping/models.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/swapping/segmenter.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/swapping/swapper.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/transforms.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/understanding/__init__.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/understanding/audio.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/understanding/image.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/understanding/separation.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/understanding/temporal.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/video_analysis.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/__init__.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/audio/__init__.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/audio/analysis.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/audio/audio.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/combine.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/description.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/exceptions.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/progress.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/registry.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/scene.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/text/__init__.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/text/overlay.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/text/transcription.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/transforms.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/transitions.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/utils.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/video.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/editing/__init__.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/editing/multicam.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/editing/premiere_xml.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/editing/video_edit.py +0 -0
- {videopython-0.25.7 → videopython-0.25.8}/src/videopython/py.typed +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from multiprocessing import Pool
|
|
5
4
|
from typing import TYPE_CHECKING, Literal
|
|
6
5
|
|
|
7
6
|
import cv2
|
|
@@ -11,9 +10,6 @@ from PIL import Image, ImageDraw, ImageFont
|
|
|
11
10
|
from videopython.base.progress import log, progress_iter
|
|
12
11
|
from videopython.base.video import Video
|
|
13
12
|
|
|
14
|
-
# Minimum frames before using multiprocessing (Pool overhead isn't worth it below this)
|
|
15
|
-
MIN_FRAMES_FOR_MULTIPROCESSING = 100
|
|
16
|
-
|
|
17
13
|
if TYPE_CHECKING:
|
|
18
14
|
from videopython.base.description import BoundingBox
|
|
19
15
|
|
|
@@ -70,24 +66,30 @@ class Effect(ABC):
|
|
|
70
66
|
Only set when the effect should end before the video does.
|
|
71
67
|
"""
|
|
72
68
|
original_shape = video.video_shape
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
69
|
+
|
|
70
|
+
if start is None and stop is None:
|
|
71
|
+
# Full-range: apply directly without slicing or np.r_ reassembly.
|
|
72
|
+
video = self._apply(video)
|
|
73
|
+
else:
|
|
74
|
+
start_s, stop_s = _resolve_time_range(start, stop, video.total_seconds)
|
|
75
|
+
# Apply effect on video slice
|
|
76
|
+
effect_start_frame = round(start_s * video.fps)
|
|
77
|
+
effect_end_frame = round(stop_s * video.fps)
|
|
78
|
+
video_with_effect = self._apply(video[effect_start_frame:effect_end_frame])
|
|
79
|
+
old_audio = video.audio
|
|
80
|
+
video = Video.from_frames(
|
|
81
|
+
np.r_[
|
|
82
|
+
"0,2",
|
|
83
|
+
video.frames[:effect_start_frame],
|
|
84
|
+
video_with_effect.frames,
|
|
85
|
+
video.frames[effect_end_frame:],
|
|
86
|
+
],
|
|
87
|
+
fps=video.fps,
|
|
88
|
+
)
|
|
89
|
+
video.audio = old_audio
|
|
90
|
+
|
|
89
91
|
# Check if dimensions didn't change
|
|
90
|
-
if
|
|
92
|
+
if video.video_shape != original_shape:
|
|
91
93
|
raise RuntimeError("The effect must not change the number of frames and the shape of the frames!")
|
|
92
94
|
|
|
93
95
|
return video
|
|
@@ -147,22 +149,15 @@ class FullImageOverlay(Effect):
|
|
|
147
149
|
|
|
148
150
|
log("Overlaying video...")
|
|
149
151
|
if self.fade_time == 0:
|
|
150
|
-
video.frames =
|
|
151
|
-
[self._overlay(
|
|
152
|
-
dtype=np.uint8,
|
|
153
|
-
)
|
|
152
|
+
for i in progress_iter(range(len(video.frames)), desc="Overlaying frames"):
|
|
153
|
+
video.frames[i] = self._overlay(video.frames[i])
|
|
154
154
|
else:
|
|
155
155
|
num_video_frames = len(video.frames)
|
|
156
156
|
num_fade_frames = round(self.fade_time * video.fps)
|
|
157
|
-
|
|
158
|
-
for i, frame in enumerate(progress_iter(video.frames, desc="Overlaying frames")):
|
|
157
|
+
for i in progress_iter(range(num_video_frames), desc="Overlaying frames"):
|
|
159
158
|
frames_dist_from_end = min(i, num_video_frames - i)
|
|
160
|
-
if frames_dist_from_end >= num_fade_frames
|
|
161
|
-
|
|
162
|
-
else:
|
|
163
|
-
fade_alpha = frames_dist_from_end / num_fade_frames
|
|
164
|
-
new_frames.append(self._overlay(frame, fade_alpha))
|
|
165
|
-
video.frames = np.array(new_frames, dtype=np.uint8)
|
|
159
|
+
fade_alpha = 1.0 if frames_dist_from_end >= num_fade_frames else frames_dist_from_end / num_fade_frames
|
|
160
|
+
video.frames[i] = self._overlay(video.frames[i], fade_alpha)
|
|
166
161
|
return video
|
|
167
162
|
|
|
168
163
|
|
|
@@ -228,17 +223,8 @@ class Blur(Effect):
|
|
|
228
223
|
raise ValueError(f"Unknown mode: `{self.mode}`.")
|
|
229
224
|
|
|
230
225
|
log(f"Applying {self.mode} blur...")
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
with Pool() as pool:
|
|
234
|
-
new_frames = pool.starmap(
|
|
235
|
-
self._blur_frame,
|
|
236
|
-
[(frame, sigma) for frame, sigma in zip(video.frames, sigmas)],
|
|
237
|
-
)
|
|
238
|
-
else:
|
|
239
|
-
new_frames = [self._blur_frame(frame, sigma) for frame, sigma in zip(video.frames, sigmas)]
|
|
240
|
-
|
|
241
|
-
video.frames = np.array(new_frames, dtype=np.uint8)
|
|
226
|
+
for i in progress_iter(range(n_frames), desc="Blurring"):
|
|
227
|
+
video.frames[i] = self._blur_frame(video.frames[i], sigmas[i])
|
|
242
228
|
return video
|
|
243
229
|
|
|
244
230
|
|
|
@@ -261,42 +247,23 @@ class Zoom(Effect):
|
|
|
261
247
|
|
|
262
248
|
def _apply(self, video: Video) -> Video:
|
|
263
249
|
n_frames = len(video.frames)
|
|
264
|
-
new_frames = []
|
|
265
|
-
|
|
266
250
|
width = video.metadata.width
|
|
267
251
|
height = video.metadata.height
|
|
268
|
-
crop_sizes_w
|
|
269
|
-
|
|
270
|
-
np.linspace(height // self.zoom_factor, height, n_frames),
|
|
271
|
-
)
|
|
252
|
+
crop_sizes_w = np.linspace(width // self.zoom_factor, width, n_frames)
|
|
253
|
+
crop_sizes_h = np.linspace(height // self.zoom_factor, height, n_frames)
|
|
272
254
|
|
|
273
255
|
if self.mode == "in":
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
total=n_frames,
|
|
278
|
-
):
|
|
279
|
-
x = width / 2 - w / 2
|
|
280
|
-
y = height / 2 - h / 2
|
|
281
|
-
|
|
282
|
-
cropped_frame = frame[round(y) : round(y + h), round(x) : round(x + w)]
|
|
283
|
-
zoomed_frame = cv2.resize(cropped_frame, (width, height))
|
|
284
|
-
new_frames.append(zoomed_frame)
|
|
285
|
-
elif self.mode == "out":
|
|
286
|
-
for frame, w, h in progress_iter(
|
|
287
|
-
zip(video.frames, crop_sizes_w, crop_sizes_h),
|
|
288
|
-
desc="Zooming",
|
|
289
|
-
total=n_frames,
|
|
290
|
-
):
|
|
291
|
-
x = width / 2 - w / 2
|
|
292
|
-
y = height / 2 - h / 2
|
|
293
|
-
|
|
294
|
-
cropped_frame = frame[round(y) : round(y + h), round(x) : round(x + w)]
|
|
295
|
-
zoomed_frame = cv2.resize(cropped_frame, (width, height))
|
|
296
|
-
new_frames.append(zoomed_frame)
|
|
297
|
-
else:
|
|
256
|
+
crop_sizes_w = crop_sizes_w[::-1]
|
|
257
|
+
crop_sizes_h = crop_sizes_h[::-1]
|
|
258
|
+
elif self.mode != "out":
|
|
298
259
|
raise ValueError(f"Unknown mode: `{self.mode}`.")
|
|
299
|
-
|
|
260
|
+
|
|
261
|
+
for i in progress_iter(range(n_frames), desc="Zooming", total=n_frames):
|
|
262
|
+
w, h = crop_sizes_w[i], crop_sizes_h[i]
|
|
263
|
+
x = width / 2 - w / 2
|
|
264
|
+
y = height / 2 - h / 2
|
|
265
|
+
cropped_frame = video.frames[i][round(y) : round(y + h), round(x) : round(x + w)]
|
|
266
|
+
video.frames[i] = cv2.resize(cropped_frame, (width, height))
|
|
300
267
|
return video
|
|
301
268
|
|
|
302
269
|
|
|
@@ -370,15 +337,8 @@ class ColorGrading(Effect):
|
|
|
370
337
|
|
|
371
338
|
def _apply(self, video: Video) -> Video:
|
|
372
339
|
log("Applying color grading...")
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if n_frames >= MIN_FRAMES_FOR_MULTIPROCESSING:
|
|
376
|
-
with Pool() as pool:
|
|
377
|
-
new_frames = pool.map(self._grade_frame, video.frames)
|
|
378
|
-
else:
|
|
379
|
-
new_frames = [self._grade_frame(frame) for frame in video.frames]
|
|
380
|
-
|
|
381
|
-
video.frames = np.array(new_frames, dtype=np.uint8)
|
|
340
|
+
for i in progress_iter(range(len(video.frames)), desc="Color grading"):
|
|
341
|
+
video.frames[i] = self._grade_frame(video.frames[i])
|
|
382
342
|
return video
|
|
383
343
|
|
|
384
344
|
|
|
@@ -426,11 +386,12 @@ class Vignette(Effect):
|
|
|
426
386
|
if self._mask is None or self._mask.shape != (height, width):
|
|
427
387
|
self._mask = self._create_mask(height, width)
|
|
428
388
|
|
|
429
|
-
# Apply mask to
|
|
430
|
-
# mask_3d shape: (height, width, 1), frames shape: (n_frames, height, width, 3)
|
|
431
|
-
# Broadcasting handles the multiplication across all frames and channels
|
|
389
|
+
# Apply mask in batches to avoid allocating a full float32 copy of all frames
|
|
432
390
|
mask_3d = self._mask[:, :, np.newaxis]
|
|
433
|
-
|
|
391
|
+
batch_size = 64
|
|
392
|
+
for start in range(0, len(video.frames), batch_size):
|
|
393
|
+
end = min(start + batch_size, len(video.frames))
|
|
394
|
+
video.frames[start:end] = (video.frames[start:end].astype(np.float32) * mask_3d).astype(np.uint8)
|
|
434
395
|
return video
|
|
435
396
|
|
|
436
397
|
|
|
@@ -533,8 +494,7 @@ class KenBurns(Effect):
|
|
|
533
494
|
end_h = int(self.end_region.height * height)
|
|
534
495
|
|
|
535
496
|
log("Applying Ken Burns effect...")
|
|
536
|
-
|
|
537
|
-
for i, frame in enumerate(progress_iter(video.frames, desc="Ken Burns")):
|
|
497
|
+
for i in progress_iter(range(n_frames), desc="Ken Burns"):
|
|
538
498
|
t = i / max(1, n_frames - 1) # Normalized time [0, 1]
|
|
539
499
|
eased_t = self._ease(t)
|
|
540
500
|
|
|
@@ -548,10 +508,7 @@ class KenBurns(Effect):
|
|
|
548
508
|
x = max(0, min(x, width - crop_w))
|
|
549
509
|
y = max(0, min(y, height - crop_h))
|
|
550
510
|
|
|
551
|
-
|
|
552
|
-
new_frames.append(new_frame)
|
|
553
|
-
|
|
554
|
-
video.frames = np.array(new_frames, dtype=np.uint8)
|
|
511
|
+
video.frames[i] = self._crop_and_scale_frame(video.frames[i], x, y, crop_w, crop_h, target_w, target_h)
|
|
555
512
|
return video
|
|
556
513
|
|
|
557
514
|
|
|
@@ -623,10 +580,19 @@ class Fade(Effect):
|
|
|
623
580
|
t = np.linspace(1, 0, fade_frames, dtype=np.float32)
|
|
624
581
|
alpha[-fade_frames:] = np.minimum(alpha[-fade_frames:], _compute_curve(t, self.curve))
|
|
625
582
|
|
|
626
|
-
# Apply to video frames
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
583
|
+
# Apply to video frames in batches to avoid a full float32 copy
|
|
584
|
+
batch_size = 64
|
|
585
|
+
for batch_start in range(0, n_effect_frames, batch_size):
|
|
586
|
+
batch_end = min(batch_start + batch_size, n_effect_frames)
|
|
587
|
+
batch_alpha = alpha[batch_start:batch_end, np.newaxis, np.newaxis, np.newaxis]
|
|
588
|
+
# Skip batch if all alphas are 1.0 (no change needed)
|
|
589
|
+
if np.all(batch_alpha == 1.0):
|
|
590
|
+
continue
|
|
591
|
+
abs_start = effect_start_frame + batch_start
|
|
592
|
+
abs_end = effect_start_frame + batch_end
|
|
593
|
+
video.frames[abs_start:abs_end] = (video.frames[abs_start:abs_end].astype(np.float32) * batch_alpha).astype(
|
|
594
|
+
np.uint8
|
|
595
|
+
)
|
|
630
596
|
|
|
631
597
|
# Verify shape invariant
|
|
632
598
|
if video.video_shape != original_shape:
|
|
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
|
|
File without changes
|
|
File without changes
|