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.
Files changed (54) hide show
  1. {videopython-0.25.7 → videopython-0.25.8}/PKG-INFO +1 -1
  2. {videopython-0.25.7 → videopython-0.25.8}/pyproject.toml +1 -1
  3. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/effects.py +64 -98
  4. {videopython-0.25.7 → videopython-0.25.8}/.gitignore +0 -0
  5. {videopython-0.25.7 → videopython-0.25.8}/LICENSE +0 -0
  6. {videopython-0.25.7 → videopython-0.25.8}/README.md +0 -0
  7. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/__init__.py +0 -0
  8. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/__init__.py +0 -0
  9. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/_device.py +0 -0
  10. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/dubbing/__init__.py +0 -0
  11. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/dubbing/dubber.py +0 -0
  12. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/dubbing/models.py +0 -0
  13. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/dubbing/pipeline.py +0 -0
  14. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/dubbing/timing.py +0 -0
  15. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/generation/__init__.py +0 -0
  16. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/generation/audio.py +0 -0
  17. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/generation/image.py +0 -0
  18. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/generation/translation.py +0 -0
  19. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/generation/video.py +0 -0
  20. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/registry.py +0 -0
  21. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/swapping/__init__.py +0 -0
  22. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/swapping/inpainter.py +0 -0
  23. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/swapping/models.py +0 -0
  24. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/swapping/segmenter.py +0 -0
  25. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/swapping/swapper.py +0 -0
  26. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/transforms.py +0 -0
  27. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/understanding/__init__.py +0 -0
  28. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/understanding/audio.py +0 -0
  29. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/understanding/image.py +0 -0
  30. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/understanding/separation.py +0 -0
  31. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/understanding/temporal.py +0 -0
  32. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/ai/video_analysis.py +0 -0
  33. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/__init__.py +0 -0
  34. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/audio/__init__.py +0 -0
  35. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/audio/analysis.py +0 -0
  36. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/audio/audio.py +0 -0
  37. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/combine.py +0 -0
  38. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/description.py +0 -0
  39. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/exceptions.py +0 -0
  40. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/progress.py +0 -0
  41. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/registry.py +0 -0
  42. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/scene.py +0 -0
  43. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/text/__init__.py +0 -0
  44. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/text/overlay.py +0 -0
  45. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/text/transcription.py +0 -0
  46. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/transforms.py +0 -0
  47. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/transitions.py +0 -0
  48. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/utils.py +0 -0
  49. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/base/video.py +0 -0
  50. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/editing/__init__.py +0 -0
  51. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/editing/multicam.py +0 -0
  52. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/editing/premiere_xml.py +0 -0
  53. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/editing/video_edit.py +0 -0
  54. {videopython-0.25.7 → videopython-0.25.8}/src/videopython/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: videopython
3
- Version: 0.25.7
3
+ Version: 0.25.8
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.25.7"
3
+ version = "0.25.8"
4
4
  description = "Minimal video generation and processing library."
5
5
  authors = [
6
6
  { name = "Bartosz Wójtowicz", email = "bartoszwojtowicz@outlook.com" },
@@ -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
- start_s, stop_s = _resolve_time_range(start, stop, video.total_seconds)
74
- # Apply effect on video slice
75
- effect_start_frame = round(start_s * video.fps)
76
- effect_end_frame = round(stop_s * video.fps)
77
- video_with_effect = self._apply(video[effect_start_frame:effect_end_frame])
78
- old_audio = video.audio
79
- video = Video.from_frames(
80
- np.r_[
81
- "0,2",
82
- video.frames[:effect_start_frame],
83
- video_with_effect.frames,
84
- video.frames[effect_end_frame:],
85
- ],
86
- fps=video.fps,
87
- )
88
- video.audio = old_audio
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 not video.video_shape == original_shape:
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 = np.array(
151
- [self._overlay(frame) for frame in progress_iter(video.frames, desc="Overlaying frames")],
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
- new_frames = []
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
- fade_alpha = 1.0
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
- if n_frames >= MIN_FRAMES_FOR_MULTIPROCESSING:
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, crop_sizes_h = (
269
- np.linspace(width // self.zoom_factor, width, n_frames),
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
- for frame, w, h in progress_iter(
275
- zip(video.frames, reversed(crop_sizes_w), reversed(crop_sizes_h)),
276
- desc="Zooming",
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
- video.frames = np.asarray(new_frames)
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
- n_frames = len(video.frames)
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 all frames using vectorized operation
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
- video.frames = (video.frames.astype(np.float32) * mask_3d).astype(np.uint8)
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
- new_frames = []
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
- new_frame = self._crop_and_scale_frame(frame, x, y, crop_w, crop_h, target_w, target_h)
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
- frames = video.frames[effect_start_frame:effect_end_frame]
628
- alpha_3d = alpha[:, np.newaxis, np.newaxis, np.newaxis]
629
- video.frames[effect_start_frame:effect_end_frame] = (frames.astype(np.float32) * alpha_3d).astype(np.uint8)
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