generated from thinkode/modelRepository
initial commit and version 1.0
This commit is contained in:
83
moviepy/video/fx/AccelDecel.py
Normal file
83
moviepy/video/fx/AccelDecel.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccelDecel(Effect):
|
||||
"""Accelerates and decelerates a clip, useful for GIF making.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
new_duration : float
|
||||
Duration for the new transformed clip. If None, will be that of the
|
||||
current clip.
|
||||
|
||||
abruptness : float
|
||||
Slope shape in the acceleration-deceleration function. It will depend
|
||||
on the value of the parameter:
|
||||
|
||||
* ``-1 < abruptness < 0``: speed up, down, up.
|
||||
* ``abruptness == 0``: no effect.
|
||||
* ``abruptness > 0``: speed down, up, down.
|
||||
|
||||
soonness : float
|
||||
For positive abruptness, determines how soon the transformation occurs.
|
||||
Should be a positive number.
|
||||
|
||||
Raises
|
||||
------
|
||||
|
||||
ValueError
|
||||
When ``sooness`` argument is lower than 0.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
The following graphs show functions generated by different combinations
|
||||
of arguments, where the value of the slopes represents the speed of the
|
||||
videos generated, being the linear function (in red) a combination that
|
||||
does not produce any transformation.
|
||||
|
||||
.. image:: /_static/medias/accel_decel-fx-params.png
|
||||
:alt: acced_decel FX parameters combinations
|
||||
"""
|
||||
|
||||
new_duration: float = None
|
||||
abruptness: float = 1.0
|
||||
soonness: float = 1.0
|
||||
|
||||
def _f_accel_decel(
|
||||
self, t, old_duration, new_duration, abruptness=1.0, soonness=1.0
|
||||
):
|
||||
a = 1.0 + abruptness
|
||||
|
||||
def _f(t):
|
||||
def f1(t):
|
||||
return (0.5) ** (1 - a) * (t**a)
|
||||
|
||||
def f2(t):
|
||||
return 1 - f1(1 - t)
|
||||
|
||||
return (t < 0.5) * f1(t) + (t >= 0.5) * f2(t)
|
||||
|
||||
return old_duration * _f((t / new_duration) ** soonness)
|
||||
|
||||
def apply(self, clip):
|
||||
"""Apply the effect to the clip."""
|
||||
if self.new_duration is None:
|
||||
self.new_duration = clip.duration
|
||||
|
||||
if self.soonness < 0:
|
||||
raise ValueError("'sooness' should be a positive number")
|
||||
|
||||
return clip.time_transform(
|
||||
lambda t: self._f_accel_decel(
|
||||
t=t,
|
||||
old_duration=clip.duration,
|
||||
new_duration=self.new_duration,
|
||||
abruptness=self.abruptness,
|
||||
soonness=self.soonness,
|
||||
)
|
||||
).with_duration(self.new_duration)
|
||||
38
moviepy/video/fx/BlackAndWhite.py
Normal file
38
moviepy/video/fx/BlackAndWhite.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class BlackAndWhite(Effect):
|
||||
"""Desaturates the picture, makes it black and white.
|
||||
Parameter RGB allows to set weights for the different color
|
||||
channels.
|
||||
If RBG is 'CRT_phosphor' a special set of values is used.
|
||||
preserve_luminosity maintains the sum of RGB to 1.
|
||||
"""
|
||||
|
||||
RGB: str = None
|
||||
preserve_luminosity: bool = True
|
||||
|
||||
def apply(self, clip):
|
||||
"""Apply the effect to the clip."""
|
||||
if self.RGB is None:
|
||||
self.RGB = [1, 1, 1]
|
||||
|
||||
if self.RGB == "CRT_phosphor":
|
||||
self.RGB = [0.2125, 0.7154, 0.0721]
|
||||
|
||||
R, G, B = (
|
||||
1.0
|
||||
* np.array(self.RGB)
|
||||
/ (sum(self.RGB) if self.preserve_luminosity else 1)
|
||||
)
|
||||
|
||||
def filter(im):
|
||||
im = R * im[:, :, 0] + G * im[:, :, 1] + B * im[:, :, 2]
|
||||
return np.dstack(3 * [im]).astype("uint8")
|
||||
|
||||
return clip.image_transform(filter)
|
||||
27
moviepy/video/fx/Blink.py
Normal file
27
moviepy/video/fx/Blink.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class Blink(Effect):
|
||||
"""
|
||||
Makes the clip blink. At each blink it will be displayed ``duration_on``
|
||||
seconds and disappear ``duration_off`` seconds. Will only work in
|
||||
composite clips.
|
||||
"""
|
||||
|
||||
duration_on: float
|
||||
duration_off: float
|
||||
|
||||
def apply(self, clip):
|
||||
"""Apply the effect to the clip."""
|
||||
if clip.mask is None:
|
||||
clip = clip.with_mask()
|
||||
|
||||
duration = self.duration_on + self.duration_off
|
||||
clip.mask = clip.mask.transform(
|
||||
lambda get_frame, t: get_frame(t) * ((t % duration) < self.duration_on)
|
||||
)
|
||||
|
||||
return clip
|
||||
80
moviepy/video/fx/Crop.py
Normal file
80
moviepy/video/fx/Crop.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class Crop(Effect):
|
||||
"""Effect to crop a clip to get a new clip in which just a rectangular
|
||||
subregion of the original clip is conserved. `x1,y1` indicates the top left
|
||||
corner and `x2,y2` is the lower right corner of the cropped region. All
|
||||
coordinates are in pixels. Float numbers are accepted.
|
||||
|
||||
To crop an arbitrary rectangle:
|
||||
|
||||
>>> Crop(x1=50, y1=60, x2=460, y2=275)
|
||||
|
||||
Only remove the part above y=30:
|
||||
|
||||
>>> Crop(y1=30)
|
||||
|
||||
Crop a rectangle that starts 10 pixels left and is 200px wide
|
||||
|
||||
>>> Crop(x1=10, width=200)
|
||||
|
||||
Crop a rectangle centered in x,y=(300,400), width=50, height=150 :
|
||||
|
||||
>>> Crop(x_center=300, y_center=400, width=50, height=150)
|
||||
|
||||
Any combination of the above should work, like for this rectangle
|
||||
centered in x=300, with explicit y-boundaries:
|
||||
|
||||
>>> Crop(x_center=300, width=400, y1=100, y2=600)
|
||||
|
||||
"""
|
||||
|
||||
x1: int = None
|
||||
y1: int = None
|
||||
x2: int = None
|
||||
y2: int = None
|
||||
width: int = None
|
||||
height: int = None
|
||||
x_center: int = None
|
||||
y_center: int = None
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
if self.width and self.x1 is not None:
|
||||
self.x2 = self.x1 + self.width
|
||||
elif self.width and self.x2 is not None:
|
||||
self.x1 = self.x2 - self.width
|
||||
|
||||
if self.height and self.y1 is not None:
|
||||
self.y2 = self.y1 + self.height
|
||||
elif self.height and self.y2 is not None:
|
||||
self.y1 = self.y2 - self.height
|
||||
|
||||
if self.x_center:
|
||||
self.x1, self.x2 = (
|
||||
self.x_center - self.width / 2,
|
||||
self.x_center + self.width / 2,
|
||||
)
|
||||
|
||||
if self.y_center:
|
||||
self.y1, self.y2 = (
|
||||
self.y_center - self.height / 2,
|
||||
self.y_center + self.height / 2,
|
||||
)
|
||||
|
||||
self.x1 = self.x1 or 0
|
||||
self.y1 = self.y1 or 0
|
||||
self.x2 = self.x2 or clip.size[0]
|
||||
self.y2 = self.y2 or clip.size[1]
|
||||
|
||||
return clip.image_transform(
|
||||
lambda frame: frame[
|
||||
int(self.y1) : int(self.y2), int(self.x1) : int(self.x2)
|
||||
],
|
||||
apply_to=["mask"],
|
||||
)
|
||||
27
moviepy/video/fx/CrossFadeIn.py
Normal file
27
moviepy/video/fx/CrossFadeIn.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
from moviepy.video.fx.FadeIn import FadeIn
|
||||
|
||||
|
||||
@dataclass
|
||||
class CrossFadeIn(Effect):
|
||||
"""Makes the clip appear progressively, over ``duration`` seconds.
|
||||
Only works when the clip is included in a CompositeVideoClip.
|
||||
"""
|
||||
|
||||
duration: float
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
if clip.duration is None:
|
||||
raise ValueError("Attribute 'duration' not set")
|
||||
|
||||
if clip.mask is None:
|
||||
clip = clip.with_mask()
|
||||
|
||||
clip.mask.duration = clip.duration
|
||||
clip.mask = clip.mask.with_effects([FadeIn(self.duration)])
|
||||
|
||||
return clip
|
||||
27
moviepy/video/fx/CrossFadeOut.py
Normal file
27
moviepy/video/fx/CrossFadeOut.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
from moviepy.video.fx.FadeOut import FadeOut
|
||||
|
||||
|
||||
@dataclass
|
||||
class CrossFadeOut(Effect):
|
||||
"""Makes the clip disappear progressively, over ``duration`` seconds.
|
||||
Only works when the clip is included in a CompositeVideoClip.
|
||||
"""
|
||||
|
||||
duration: float
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
if clip.duration is None:
|
||||
raise ValueError("Attribute 'duration' not set")
|
||||
|
||||
if clip.mask is None:
|
||||
clip = clip.with_mask()
|
||||
|
||||
clip.mask.duration = clip.duration
|
||||
clip.mask = clip.mask.with_effects([FadeOut(self.duration)])
|
||||
|
||||
return clip
|
||||
34
moviepy/video/fx/EvenSize.py
Normal file
34
moviepy/video/fx/EvenSize.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class EvenSize(Effect):
|
||||
"""Crops the clip to make dimensions even."""
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
w, h = clip.size
|
||||
w_even = w % 2 == 0
|
||||
h_even = h % 2 == 0
|
||||
if w_even and h_even:
|
||||
return clip
|
||||
|
||||
if not w_even and not h_even:
|
||||
|
||||
def image_filter(a):
|
||||
return a[:-1, :-1, :]
|
||||
|
||||
elif h_even:
|
||||
|
||||
def image_filter(a):
|
||||
return a[:, :-1, :]
|
||||
|
||||
else:
|
||||
|
||||
def image_filter(a):
|
||||
return a[:-1, :, :]
|
||||
|
||||
return clip.image_transform(image_filter, apply_to=["mask"])
|
||||
36
moviepy/video/fx/FadeIn.py
Normal file
36
moviepy/video/fx/FadeIn.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class FadeIn(Effect):
|
||||
"""Makes the clip progressively appear from some color (black by default),
|
||||
over ``duration`` seconds at the beginning of the clip. Can be used for
|
||||
masks too, where the initial color must be a number between 0 and 1.
|
||||
|
||||
For cross-fading (progressive appearance or disappearance of a clip
|
||||
over another clip, see ``CrossFadeIn``
|
||||
"""
|
||||
|
||||
duration: float
|
||||
initial_color: list = None
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
if self.initial_color is None:
|
||||
self.initial_color = 0 if clip.is_mask else [0, 0, 0]
|
||||
|
||||
self.initial_color = np.array(self.initial_color)
|
||||
|
||||
def filter(get_frame, t):
|
||||
if t >= self.duration:
|
||||
return get_frame(t)
|
||||
else:
|
||||
fading = 1.0 * t / self.duration
|
||||
return fading * get_frame(t) + (1 - fading) * self.initial_color
|
||||
|
||||
return clip.transform(filter)
|
||||
39
moviepy/video/fx/FadeOut.py
Normal file
39
moviepy/video/fx/FadeOut.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class FadeOut(Effect):
|
||||
"""Makes the clip progressively fade to some color (black by default),
|
||||
over ``duration`` seconds at the end of the clip. Can be used for masks too,
|
||||
where the final color must be a number between 0 and 1.
|
||||
|
||||
For cross-fading (progressive appearance or disappearance of a clip over another
|
||||
clip), see ``CrossFadeOut``
|
||||
"""
|
||||
|
||||
duration: float
|
||||
final_color: list = None
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
if clip.duration is None:
|
||||
raise ValueError("Attribute 'duration' not set")
|
||||
|
||||
if self.final_color is None:
|
||||
self.final_color = 0 if clip.is_mask else [0, 0, 0]
|
||||
|
||||
self.final_color = np.array(self.final_color)
|
||||
|
||||
def filter(get_frame, t):
|
||||
if (clip.duration - t) >= self.duration:
|
||||
return get_frame(t)
|
||||
else:
|
||||
fading = 1.0 * (clip.duration - t) / self.duration
|
||||
return fading * get_frame(t) + (1 - fading) * self.final_color
|
||||
|
||||
return clip.transform(filter)
|
||||
43
moviepy/video/fx/Freeze.py
Normal file
43
moviepy/video/fx/Freeze.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
from moviepy.video.compositing.CompositeVideoClip import concatenate_videoclips
|
||||
|
||||
|
||||
@dataclass
|
||||
class Freeze(Effect):
|
||||
"""Momentarily freeze the clip at time t.
|
||||
|
||||
Set `t='end'` to freeze the clip at the end (actually it will freeze on the
|
||||
frame at time clip.duration - padding_end seconds - 1 / clip_fps).
|
||||
With ``duration`` you can specify the duration of the freeze.
|
||||
With ``total_duration`` you can specify the total duration of
|
||||
the clip and the freeze (i.e. the duration of the freeze is
|
||||
automatically computed). One of them must be provided.
|
||||
"""
|
||||
|
||||
t: float = 0
|
||||
freeze_duration: float = None
|
||||
total_duration: float = None
|
||||
padding_end: float = 0
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
if clip.duration is None:
|
||||
raise ValueError("Attribute 'duration' not set")
|
||||
|
||||
if self.t == "end":
|
||||
self.t = clip.duration - self.padding_end - 1 / clip.fps
|
||||
|
||||
if self.freeze_duration is None:
|
||||
if self.total_duration is None:
|
||||
raise ValueError(
|
||||
"You must provide either 'freeze_duration' or 'total_duration'"
|
||||
)
|
||||
self.freeze_duration = self.total_duration - clip.duration
|
||||
|
||||
before = [clip[: self.t]] if (self.t != 0) else []
|
||||
freeze = [clip.to_ImageClip(self.t).with_duration(self.freeze_duration)]
|
||||
after = [clip[self.t :]] if (self.t != clip.duration) else []
|
||||
return concatenate_videoclips(before + freeze + after)
|
||||
68
moviepy/video/fx/FreezeRegion.py
Normal file
68
moviepy/video/fx/FreezeRegion.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
|
||||
from moviepy.video.fx.Crop import Crop
|
||||
|
||||
|
||||
@dataclass
|
||||
class FreezeRegion(Effect):
|
||||
"""Freezes one region of the clip while the rest remains animated.
|
||||
|
||||
You can choose one of three methods by providing either `region`,
|
||||
`outside_region`, or `mask`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
t
|
||||
Time at which to freeze the freezed region.
|
||||
|
||||
region
|
||||
A tuple (x1, y1, x2, y2) defining the region of the screen (in pixels)
|
||||
which will be freezed. You can provide outside_region or mask instead.
|
||||
|
||||
outside_region
|
||||
A tuple (x1, y1, x2, y2) defining the region of the screen (in pixels)
|
||||
which will be the only non-freezed region.
|
||||
|
||||
mask
|
||||
If not None, will overlay a freezed version of the clip on the current clip,
|
||||
with the provided mask. In other words, the "visible" pixels in the mask
|
||||
indicate the freezed region in the final picture.
|
||||
|
||||
"""
|
||||
|
||||
t: float = 0
|
||||
region: tuple = None
|
||||
outside_region: tuple = None
|
||||
mask: Clip = None
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
if self.region is not None:
|
||||
x1, y1, _x2, _y2 = self.region
|
||||
freeze = (
|
||||
clip.with_effects([Crop(*self.region)])
|
||||
.to_ImageClip(t=self.t)
|
||||
.with_duration(clip.duration)
|
||||
.with_position((x1, y1))
|
||||
)
|
||||
return CompositeVideoClip([clip, freeze])
|
||||
|
||||
elif self.outside_region is not None:
|
||||
x1, y1, x2, y2 = self.outside_region
|
||||
animated_region = clip.with_effects(
|
||||
[Crop(*self.outside_region)]
|
||||
).with_position((x1, y1))
|
||||
freeze = clip.to_ImageClip(t=self.t).with_duration(clip.duration)
|
||||
return CompositeVideoClip([freeze, animated_region])
|
||||
|
||||
elif self.mask is not None:
|
||||
freeze = (
|
||||
clip.to_ImageClip(t=self.t)
|
||||
.with_duration(clip.duration)
|
||||
.with_mask(self.mask)
|
||||
)
|
||||
return CompositeVideoClip([clip, freeze])
|
||||
20
moviepy/video/fx/GammaCorrection.py
Normal file
20
moviepy/video/fx/GammaCorrection.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class GammaCorrection(Effect):
|
||||
"""Gamma-correction of a video clip."""
|
||||
|
||||
gamma: float
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
|
||||
def filter(im):
|
||||
corrected = 255 * (1.0 * im / 255) ** self.gamma
|
||||
return corrected.astype("uint8")
|
||||
|
||||
return clip.image_transform(filter)
|
||||
45
moviepy/video/fx/HeadBlur.py
Normal file
45
moviepy/video/fx/HeadBlur.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw, ImageFilter
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class HeadBlur(Effect):
|
||||
"""Returns a filter that will blur a moving part (a head ?) of the frames.
|
||||
|
||||
The position of the blur at time t is defined by (fx(t), fy(t)), the radius
|
||||
of the blurring by ``radius`` and the intensity of the blurring by ``intensity``.
|
||||
"""
|
||||
|
||||
fx: callable
|
||||
fy: callable
|
||||
radius: float
|
||||
intensity: float = None
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
if self.intensity is None:
|
||||
self.intensity = int(2 * self.radius / 3)
|
||||
|
||||
def filter(gf, t):
|
||||
im = gf(t).copy()
|
||||
h, w, d = im.shape
|
||||
x, y = int(self.fx(t)), int(self.fy(t))
|
||||
x1, x2 = max(0, x - self.radius), min(x + self.radius, w)
|
||||
y1, y2 = max(0, y - self.radius), min(y + self.radius, h)
|
||||
|
||||
image = Image.fromarray(im)
|
||||
mask = Image.new("RGB", image.size)
|
||||
draw = ImageDraw.Draw(mask)
|
||||
draw.ellipse([x1, y1, x2, y2], fill=(255, 255, 255))
|
||||
|
||||
blurred = image.filter(ImageFilter.GaussianBlur(radius=self.intensity))
|
||||
|
||||
res = np.where(np.array(mask) > 0, np.array(blurred), np.array(image))
|
||||
return res
|
||||
|
||||
return clip.transform(filter)
|
||||
18
moviepy/video/fx/InvertColors.py
Normal file
18
moviepy/video/fx/InvertColors.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class InvertColors(Effect):
|
||||
"""Returns the color-inversed clip.
|
||||
|
||||
The values of all pixels are replaced with (255-v) or (1-v) for masks
|
||||
Black becomes white, green becomes purple, etc.
|
||||
"""
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
maxi = 1.0 if clip.is_mask else 255
|
||||
return clip.image_transform(lambda f: maxi - f)
|
||||
43
moviepy/video/fx/Loop.py
Normal file
43
moviepy/video/fx/Loop.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class Loop(Effect):
|
||||
"""
|
||||
Returns a clip that plays the current clip in an infinite loop.
|
||||
Ideal for clips coming from GIFs.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
n
|
||||
Number of times the clip should be played. If `None` the
|
||||
the clip will loop indefinitely (i.e. with no set duration).
|
||||
|
||||
duration
|
||||
Total duration of the clip. Can be specified instead of n.
|
||||
"""
|
||||
|
||||
n: int = None
|
||||
duration: float = None
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
if clip.duration is None:
|
||||
raise ValueError("Attribute 'duration' not set")
|
||||
|
||||
previous_duration = clip.duration
|
||||
clip = clip.time_transform(
|
||||
lambda t: t % previous_duration, apply_to=["mask", "audio"]
|
||||
)
|
||||
|
||||
if self.n:
|
||||
self.duration = self.n * previous_duration
|
||||
|
||||
if self.duration:
|
||||
clip = clip.with_duration(self.duration)
|
||||
|
||||
return clip
|
||||
27
moviepy/video/fx/LumContrast.py
Normal file
27
moviepy/video/fx/LumContrast.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class LumContrast(Effect):
|
||||
"""Luminosity-contrast correction of a clip."""
|
||||
|
||||
lum: float = 0
|
||||
contrast: float = 0
|
||||
contrast_threshold: float = 127
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
|
||||
def image_filter(im):
|
||||
im = 1.0 * im # float conversion
|
||||
corrected = (
|
||||
im + self.lum + self.contrast * (im - float(self.contrast_threshold))
|
||||
)
|
||||
corrected[corrected < 0] = 0
|
||||
corrected[corrected > 255] = 255
|
||||
return corrected.astype("uint8")
|
||||
|
||||
return clip.image_transform(image_filter)
|
||||
30
moviepy/video/fx/MakeLoopable.py
Normal file
30
moviepy/video/fx/MakeLoopable.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
|
||||
from moviepy.video.fx.CrossFadeIn import CrossFadeIn
|
||||
|
||||
|
||||
@dataclass
|
||||
class MakeLoopable(Effect):
|
||||
"""Makes the clip fade in progressively at its own end, this way it can be
|
||||
looped indefinitely.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
overlap_duration : float
|
||||
Duration of the fade-in (in seconds).
|
||||
"""
|
||||
|
||||
overlap_duration: float
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
clip2 = clip.with_effects([CrossFadeIn(self.overlap_duration)]).with_start(
|
||||
clip.duration - self.overlap_duration
|
||||
)
|
||||
return CompositeVideoClip([clip, clip2]).subclipped(
|
||||
self.overlap_duration, clip.duration
|
||||
)
|
||||
90
moviepy/video/fx/Margin.py
Normal file
90
moviepy/video/fx/Margin.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
from moviepy.video.VideoClip import ImageClip
|
||||
|
||||
|
||||
@dataclass
|
||||
class Margin(Effect):
|
||||
"""Draws an external margin all around the frame.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
margin_size : int, optional
|
||||
If not ``None``, then the new clip has a margin size of
|
||||
size ``margin_size`` in pixels on the left, right, top, and bottom.
|
||||
|
||||
left : int, optional
|
||||
If ``margin_size=None``, margin size for the new clip in left direction.
|
||||
|
||||
right : int, optional
|
||||
If ``margin_size=None``, margin size for the new clip in right direction.
|
||||
|
||||
top : int, optional
|
||||
If ``margin_size=None``, margin size for the new clip in top direction.
|
||||
|
||||
bottom : int, optional
|
||||
If ``margin_size=None``, margin size for the new clip in bottom direction.
|
||||
|
||||
color : tuple, optional
|
||||
Color of the margin.
|
||||
|
||||
opacity : float, optional
|
||||
Opacity of the margin. Setting this value to 0 yields transparent margins.
|
||||
"""
|
||||
|
||||
margin_size: int = None
|
||||
left: int = 0
|
||||
right: int = 0
|
||||
top: int = 0
|
||||
bottom: int = 0
|
||||
color: tuple = (0, 0, 0)
|
||||
opacity: float = 1.0
|
||||
|
||||
def add_margin(self, clip: Clip):
|
||||
"""Add margins to the clip."""
|
||||
if (self.opacity != 1.0) and (clip.mask is None) and not (clip.is_mask):
|
||||
clip = clip.with_mask()
|
||||
|
||||
if self.margin_size is not None:
|
||||
self.left = self.right = self.top = self.bottom = self.margin_size
|
||||
|
||||
def make_bg(w, h):
|
||||
new_w, new_h = w + self.left + self.right, h + self.top + self.bottom
|
||||
if clip.is_mask:
|
||||
shape = (new_h, new_w)
|
||||
bg = np.tile(self.opacity, (new_h, new_w)).astype(float).reshape(shape)
|
||||
else:
|
||||
shape = (new_h, new_w, 3)
|
||||
bg = np.tile(self.color, (new_h, new_w)).reshape(shape)
|
||||
return bg
|
||||
|
||||
if isinstance(clip, ImageClip):
|
||||
im = make_bg(clip.w, clip.h)
|
||||
im[self.top : self.top + clip.h, self.left : self.left + clip.w] = clip.img
|
||||
return clip.image_transform(lambda pic: im)
|
||||
|
||||
else:
|
||||
|
||||
def filter(get_frame, t):
|
||||
pic = get_frame(t)
|
||||
h, w = pic.shape[:2]
|
||||
im = make_bg(w, h)
|
||||
im[self.top : self.top + h, self.left : self.left + w] = pic
|
||||
return im
|
||||
|
||||
return clip.transform(filter)
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
# We apply once on clip and once on mask if we have one
|
||||
clip = self.add_margin(clip=clip)
|
||||
|
||||
if clip.mask:
|
||||
clip.mask = self.add_margin(clip=clip.mask)
|
||||
|
||||
return clip
|
||||
45
moviepy/video/fx/MaskColor.py
Normal file
45
moviepy/video/fx/MaskColor.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class MaskColor(Effect):
|
||||
"""Returns a new clip with a mask for transparency where the original
|
||||
clip is of the given color.
|
||||
|
||||
You can also have a "progressive" mask by specifying a non-null distance
|
||||
threshold ``threshold``. In this case, if the distance between a pixel and
|
||||
the given color is d, the transparency will be
|
||||
|
||||
d**stiffness / (threshold**stiffness + d**stiffness)
|
||||
|
||||
which is 1 when d>>threshold and 0 for d<<threshold, the stiffness of the
|
||||
effect being parametrized by ``stiffness``
|
||||
"""
|
||||
|
||||
color: tuple = (0, 0, 0)
|
||||
threshold: float = 0
|
||||
stiffness: float = 1
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
color = np.array(self.color)
|
||||
|
||||
def hill(x):
|
||||
if self.threshold:
|
||||
return x**self.stiffness / (
|
||||
self.threshold**self.stiffness + x**self.stiffness
|
||||
)
|
||||
else:
|
||||
return 1.0 * (x != 0)
|
||||
|
||||
def flim(im):
|
||||
return hill(np.sqrt(((im - color) ** 2).sum(axis=2)))
|
||||
|
||||
mask = clip.image_transform(flim)
|
||||
mask.is_mask = True
|
||||
return clip.with_mask(mask)
|
||||
52
moviepy/video/fx/MasksAnd.py
Normal file
52
moviepy/video/fx/MasksAnd.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
from moviepy.video.VideoClip import ImageClip
|
||||
|
||||
|
||||
@dataclass
|
||||
class MasksAnd(Effect):
|
||||
"""Returns the logical 'and' (minimum pixel color values) between two masks.
|
||||
|
||||
The result has the duration of the clip to which has been applied, if it has any.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
other_clip ImageClip or np.ndarray
|
||||
Clip used to mask the original clip.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code:: python
|
||||
|
||||
clip = ColorClip(color=(255, 0, 0), size=(1, 1)) # red
|
||||
mask = ColorClip(color=(0, 255, 0), size=(1, 1)) # green
|
||||
masked_clip = clip.with_effects([vfx.MasksAnd(mask)]) # black
|
||||
masked_clip.get_frame(0)
|
||||
[[[0 0 0]]]
|
||||
"""
|
||||
|
||||
other_clip: Union[Clip, np.ndarray]
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
# to ensure that 'and' of two ImageClips will be an ImageClip
|
||||
if isinstance(self.other_clip, ImageClip):
|
||||
self.other_clip = self.other_clip.img
|
||||
|
||||
if isinstance(self.other_clip, np.ndarray):
|
||||
return clip.image_transform(
|
||||
lambda frame: np.minimum(frame, self.other_clip)
|
||||
)
|
||||
else:
|
||||
return clip.transform(
|
||||
lambda get_frame, t: np.minimum(
|
||||
get_frame(t), self.other_clip.get_frame(t)
|
||||
)
|
||||
)
|
||||
52
moviepy/video/fx/MasksOr.py
Normal file
52
moviepy/video/fx/MasksOr.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
from moviepy.video.VideoClip import ImageClip
|
||||
|
||||
|
||||
@dataclass
|
||||
class MasksOr(Effect):
|
||||
"""Returns the logical 'or' (maximum pixel color values) between two masks.
|
||||
|
||||
The result has the duration of the clip to which has been applied, if it has any.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
other_clip ImageClip or np.ndarray
|
||||
Clip used to mask the original clip.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code:: python
|
||||
|
||||
clip = ColorClip(color=(255, 0, 0), size=(1, 1)) # red
|
||||
mask = ColorClip(color=(0, 255, 0), size=(1, 1)) # green
|
||||
masked_clip = clip.with_effects([vfx.MasksOr(mask)]) # yellow
|
||||
masked_clip.get_frame(0)
|
||||
[[[255 255 0]]]
|
||||
"""
|
||||
|
||||
other_clip: Union[Clip, np.ndarray]
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
# to ensure that 'or' of two ImageClips will be an ImageClip
|
||||
if isinstance(self.other_clip, ImageClip):
|
||||
self.other_clip = self.other_clip.img
|
||||
|
||||
if isinstance(self.other_clip, np.ndarray):
|
||||
return clip.image_transform(
|
||||
lambda frame: np.maximum(frame, self.other_clip)
|
||||
)
|
||||
else:
|
||||
return clip.transform(
|
||||
lambda get_frame, t: np.maximum(
|
||||
get_frame(t), self.other_clip.get_frame(t)
|
||||
)
|
||||
)
|
||||
16
moviepy/video/fx/MirrorX.py
Normal file
16
moviepy/video/fx/MirrorX.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Union
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class MirrorX(Effect):
|
||||
"""Flips the clip horizontally (and its mask too, by default)."""
|
||||
|
||||
apply_to: Union[List, str] = "mask"
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
return clip.image_transform(lambda img: img[:, ::-1], apply_to=self.apply_to)
|
||||
16
moviepy/video/fx/MirrorY.py
Normal file
16
moviepy/video/fx/MirrorY.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Union
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class MirrorY(Effect):
|
||||
"""Flips the clip vertically (and its mask too, by default)."""
|
||||
|
||||
apply_to: Union[List, str] = "mask"
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
return clip.image_transform(lambda img: img[::-1], apply_to=self.apply_to)
|
||||
23
moviepy/video/fx/MultiplyColor.py
Normal file
23
moviepy/video/fx/MultiplyColor.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class MultiplyColor(Effect):
|
||||
"""
|
||||
Multiplies the clip's colors by the given factor, can be used
|
||||
to decrease or increase the clip's brightness (is that the
|
||||
right word ?)
|
||||
"""
|
||||
|
||||
factor: float
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
return clip.image_transform(
|
||||
lambda frame: np.minimum(255, (self.factor * frame)).astype("uint8")
|
||||
)
|
||||
31
moviepy/video/fx/MultiplySpeed.py
Normal file
31
moviepy/video/fx/MultiplySpeed.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class MultiplySpeed(Effect):
|
||||
"""Returns a clip playing the current clip but at a speed multiplied by ``factor``.
|
||||
|
||||
Instead of factor one can indicate the desired ``final_duration`` of the clip, and
|
||||
the factor will be automatically computed. The same effect is applied to the clip's
|
||||
audio and mask if any.
|
||||
"""
|
||||
|
||||
factor: float = None
|
||||
final_duration: float = None
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
if self.final_duration:
|
||||
self.factor = 1.0 * clip.duration / self.final_duration
|
||||
|
||||
new_clip = clip.time_transform(
|
||||
lambda t: self.factor * t, apply_to=["mask", "audio"]
|
||||
)
|
||||
|
||||
if clip.duration is not None:
|
||||
new_clip = new_clip.with_duration(1.0 * clip.duration / self.factor)
|
||||
|
||||
return new_clip
|
||||
63
moviepy/video/fx/Painting.py
Normal file
63
moviepy/video/fx/Painting.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image, ImageFilter
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class Painting(Effect):
|
||||
"""Transforms any photo into some kind of painting.
|
||||
|
||||
Transforms any photo into some kind of painting. Saturation
|
||||
tells at which point the colors of the result should be
|
||||
flashy. ``black`` gives the amount of black lines wanted.
|
||||
|
||||
np_image : a numpy image
|
||||
"""
|
||||
|
||||
saturation: float = 1.4
|
||||
black: float = 0.006
|
||||
|
||||
def to_painting(self, np_image, saturation=1.4, black=0.006):
|
||||
"""Transforms any photo into some kind of painting.
|
||||
|
||||
Transforms any photo into some kind of painting. Saturation
|
||||
tells at which point the colors of the result should be
|
||||
flashy. ``black`` gives the amount of black lines wanted.
|
||||
|
||||
np_image : a numpy image
|
||||
"""
|
||||
image = Image.fromarray(np_image)
|
||||
image = image.filter(ImageFilter.EDGE_ENHANCE_MORE)
|
||||
|
||||
# Convert the image to grayscale
|
||||
grayscale_image = image.convert("L")
|
||||
|
||||
# Find the image edges
|
||||
edges_image = grayscale_image.filter(ImageFilter.FIND_EDGES)
|
||||
|
||||
# Convert the edges image to a numpy array
|
||||
edges = np.array(edges_image)
|
||||
|
||||
# Create the darkening effect
|
||||
darkening = black * (255 * np.dstack(3 * [edges]))
|
||||
|
||||
# Apply the painting effect
|
||||
painting = saturation * np.array(image) - darkening
|
||||
|
||||
# Clip the pixel values to the valid range of 0-255
|
||||
painting = np.maximum(0, np.minimum(255, painting))
|
||||
|
||||
# Convert the pixel values to unsigned 8-bit integers
|
||||
painting = painting.astype("uint8")
|
||||
|
||||
return painting
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
return clip.image_transform(
|
||||
lambda im: self.to_painting(im, self.saturation, self.black)
|
||||
)
|
||||
158
moviepy/video/fx/Resize.py
Normal file
158
moviepy/video/fx/Resize.py
Normal file
@@ -0,0 +1,158 @@
|
||||
import numbers
|
||||
from dataclasses import dataclass
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class Resize(Effect):
|
||||
"""Effect returning a video clip that is a resized version of the clip.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
new_size : tuple or float or function, optional
|
||||
Can be either
|
||||
- ``(width, height)`` in pixels or a float representing
|
||||
- A scaling factor, like ``0.5``.
|
||||
- A function of time returning one of these.
|
||||
|
||||
height : int, optional
|
||||
Height of the new clip in pixels. The width is then computed so
|
||||
that the width/height ratio is conserved.
|
||||
|
||||
width : int, optional
|
||||
Width of the new clip in pixels. The height is then computed so
|
||||
that the width/height ratio is conserved.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code:: python
|
||||
|
||||
clip.with_effects([vfx.Resize((460,720))]) # New resolution: (460,720)
|
||||
clip.with_effects([vfx.Resize(0.6)]) # width and height multiplied by 0.6
|
||||
clip.with_effects([vfx.Resize(width=800)]) # height computed automatically.
|
||||
clip.with_effects([vfx.Resize(lambda t : 1+0.02*t)]) # slow clip swelling
|
||||
"""
|
||||
|
||||
new_size: Union[tuple, float, callable] = None
|
||||
height: int = None
|
||||
width: int = None
|
||||
apply_to_mask: bool = True
|
||||
|
||||
def resizer(self, pic, new_size):
|
||||
"""Resize the image using PIL."""
|
||||
new_size = list(map(int, new_size))
|
||||
pil_img = Image.fromarray(pic)
|
||||
resized_pil = pil_img.resize(new_size, Image.Resampling.LANCZOS)
|
||||
return np.array(resized_pil)
|
||||
|
||||
def apply(self, clip):
|
||||
"""Apply the effect to the clip."""
|
||||
w, h = clip.size
|
||||
|
||||
if self.new_size is not None:
|
||||
|
||||
def translate_new_size(new_size_):
|
||||
"""Returns a [w, h] pair from `new_size_`. If `new_size_` is a
|
||||
scalar, then work out the correct pair using the clip's size.
|
||||
Otherwise just return `new_size_`
|
||||
"""
|
||||
if isinstance(new_size_, numbers.Number):
|
||||
return [new_size_ * w, new_size_ * h]
|
||||
else:
|
||||
return new_size_
|
||||
|
||||
if hasattr(self.new_size, "__call__"):
|
||||
# The resizing is a function of time
|
||||
|
||||
def get_new_size(t):
|
||||
return translate_new_size(self.new_size(t))
|
||||
|
||||
if clip.is_mask:
|
||||
|
||||
def filter(get_frame, t):
|
||||
return (
|
||||
self.resizer(
|
||||
(255 * get_frame(t)).astype("uint8"), get_new_size(t)
|
||||
)
|
||||
/ 255.0
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
def filter(get_frame, t):
|
||||
return self.resizer(
|
||||
get_frame(t).astype("uint8"), get_new_size(t)
|
||||
)
|
||||
|
||||
newclip = clip.transform(
|
||||
filter,
|
||||
keep_duration=True,
|
||||
apply_to=(["mask"] if self.apply_to_mask else []),
|
||||
)
|
||||
if self.apply_to_mask and clip.mask is not None:
|
||||
newclip.mask = clip.mask.with_effects(
|
||||
[Resize(self.new_size, apply_to_mask=False)]
|
||||
)
|
||||
|
||||
return newclip
|
||||
|
||||
else:
|
||||
self.new_size = translate_new_size(self.new_size)
|
||||
|
||||
elif self.height is not None:
|
||||
if hasattr(self.height, "__call__"):
|
||||
|
||||
def func(t):
|
||||
return 1.0 * int(self.height(t)) / h
|
||||
|
||||
return clip.with_effects([Resize(func)])
|
||||
|
||||
else:
|
||||
self.new_size = [w * self.height / h, self.height]
|
||||
|
||||
elif self.width is not None:
|
||||
if hasattr(self.width, "__call__"):
|
||||
|
||||
def func(t):
|
||||
return 1.0 * self.width(t) / w
|
||||
|
||||
return clip.with_effects([Resize(func)])
|
||||
|
||||
else:
|
||||
self.new_size = [self.width, h * self.width / w]
|
||||
else:
|
||||
raise ValueError(
|
||||
"You must provide either 'new_size' or 'height' or 'width'"
|
||||
)
|
||||
|
||||
# From here, the resizing is constant (not a function of time), size=newsize
|
||||
|
||||
if clip.is_mask:
|
||||
|
||||
def image_filter(pic):
|
||||
return (
|
||||
1.0
|
||||
* self.resizer((255 * pic).astype("uint8"), self.new_size)
|
||||
/ 255.0
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
def image_filter(pic):
|
||||
return self.resizer(pic.astype("uint8"), self.new_size)
|
||||
|
||||
new_clip = clip.image_transform(image_filter)
|
||||
|
||||
if self.apply_to_mask and clip.mask is not None:
|
||||
new_clip.mask = clip.mask.with_effects(
|
||||
[Resize(self.new_size, apply_to_mask=False)]
|
||||
)
|
||||
|
||||
return new_clip
|
||||
128
moviepy/video/fx/Rotate.py
Normal file
128
moviepy/video/fx/Rotate.py
Normal file
@@ -0,0 +1,128 @@
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class Rotate(Effect):
|
||||
"""
|
||||
Rotates the specified clip by ``angle`` degrees (or radians) anticlockwise
|
||||
If the angle is not a multiple of 90 (degrees) or ``center``, ``translate``,
|
||||
and ``bg_color`` are not ``None``, there will be black borders.
|
||||
You can make them transparent with:
|
||||
|
||||
>>> new_clip = clip.with_mask().rotate(72)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
clip : VideoClip
|
||||
A video clip.
|
||||
|
||||
angle : float
|
||||
Either a value or a function angle(t) representing the angle of rotation.
|
||||
|
||||
unit : str, optional
|
||||
Unit of parameter `angle` (either "deg" for degrees or "rad" for radians).
|
||||
|
||||
resample : str, optional
|
||||
An optional resampling filter. One of "nearest", "bilinear", or "bicubic".
|
||||
|
||||
expand : bool, optional
|
||||
If true, expands the output image to make it large enough to hold the
|
||||
entire rotated image. If false or omitted, make the output image the same
|
||||
size as the input image.
|
||||
|
||||
translate : tuple, optional
|
||||
An optional post-rotate translation (a 2-tuple).
|
||||
|
||||
center : tuple, optional
|
||||
Optional center of rotation (a 2-tuple). Origin is the upper left corner.
|
||||
|
||||
bg_color : tuple, optional
|
||||
An optional color for area outside the rotated image. Only has effect if
|
||||
``expand`` is true.
|
||||
"""
|
||||
|
||||
angle: float
|
||||
unit: str = "deg"
|
||||
resample: str = "bicubic"
|
||||
expand: bool = True
|
||||
center: tuple = None
|
||||
translate: tuple = None
|
||||
bg_color: tuple = None
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
try:
|
||||
resample = {
|
||||
"bilinear": Image.BILINEAR,
|
||||
"nearest": Image.NEAREST,
|
||||
"bicubic": Image.BICUBIC,
|
||||
}[self.resample]
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
"'resample' argument must be either 'bilinear', 'nearest' or 'bicubic'"
|
||||
)
|
||||
|
||||
if hasattr(self.angle, "__call__"):
|
||||
get_angle = self.angle
|
||||
else:
|
||||
get_angle = lambda t: self.angle
|
||||
|
||||
def filter(get_frame, t):
|
||||
angle = get_angle(t)
|
||||
im = get_frame(t)
|
||||
|
||||
if self.unit == "rad":
|
||||
angle = math.degrees(angle)
|
||||
|
||||
angle %= 360
|
||||
if not self.center and not self.translate and not self.bg_color:
|
||||
if (angle == 0) and self.expand:
|
||||
return im
|
||||
if (angle == 90) and self.expand:
|
||||
transpose = [1, 0] if len(im.shape) == 2 else [1, 0, 2]
|
||||
return np.transpose(im, axes=transpose)[::-1]
|
||||
elif (angle == 270) and self.expand:
|
||||
transpose = [1, 0] if len(im.shape) == 2 else [1, 0, 2]
|
||||
return np.transpose(im, axes=transpose)[:, ::-1]
|
||||
elif (angle == 180) and self.expand:
|
||||
return im[::-1, ::-1]
|
||||
|
||||
pillow_kwargs = {}
|
||||
|
||||
if self.bg_color is not None:
|
||||
pillow_kwargs["fillcolor"] = self.bg_color
|
||||
|
||||
if self.center is not None:
|
||||
pillow_kwargs["center"] = self.center
|
||||
|
||||
if self.translate is not None:
|
||||
pillow_kwargs["translate"] = self.translate
|
||||
|
||||
# PIL expects uint8 type data. However a mask image has values in the
|
||||
# range [0, 1] and is of float type. To handle this we scale it up by
|
||||
# a factor 'a' for use with PIL and then back again by 'a' afterwards.
|
||||
if im.dtype == "float64":
|
||||
# this is a mask image
|
||||
a = 255.0
|
||||
else:
|
||||
a = 1
|
||||
|
||||
# call PIL.rotate
|
||||
return (
|
||||
np.array(
|
||||
Image.fromarray(np.array(a * im).astype(np.uint8)).rotate(
|
||||
angle, expand=self.expand, resample=resample, **pillow_kwargs
|
||||
)
|
||||
)
|
||||
/ a
|
||||
)
|
||||
|
||||
return clip.transform(filter, apply_to=["mask"])
|
||||
57
moviepy/video/fx/Scroll.py
Normal file
57
moviepy/video/fx/Scroll.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
class Scroll(Effect):
|
||||
"""Effect that scrolls horizontally or vertically a clip, e.g. to make end credits
|
||||
|
||||
Parameters
|
||||
----------
|
||||
w, h
|
||||
The width and height of the final clip. Default to clip.w and clip.h
|
||||
|
||||
x_speed, y_speed
|
||||
The speed of the scroll in the x and y directions.
|
||||
|
||||
x_start, y_start
|
||||
The starting position of the scroll in the x and y directions.
|
||||
|
||||
|
||||
apply_to
|
||||
Whether to apply the effect to the mask too.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
w=None,
|
||||
h=None,
|
||||
x_speed=0,
|
||||
y_speed=0,
|
||||
x_start=0,
|
||||
y_start=0,
|
||||
apply_to="mask",
|
||||
):
|
||||
self.w = w
|
||||
self.h = h
|
||||
self.x_speed = x_speed
|
||||
self.y_speed = y_speed
|
||||
self.x_start = x_start
|
||||
self.y_start = y_start
|
||||
self.apply_to = apply_to
|
||||
|
||||
def apply(self, clip):
|
||||
"""Apply the effect to the clip."""
|
||||
if self.h is None:
|
||||
self.h = clip.h
|
||||
|
||||
if self.w is None:
|
||||
self.w = clip.w
|
||||
|
||||
x_max = self.w - 1
|
||||
y_max = self.h - 1
|
||||
|
||||
def filter(get_frame, t):
|
||||
x = int(max(0, min(x_max, self.x_start + round(self.x_speed * t))))
|
||||
y = int(max(0, min(y_max, self.y_start + round(self.y_speed * t))))
|
||||
return get_frame(t)[y : y + self.h, x : x + self.w]
|
||||
|
||||
return clip.transform(filter, apply_to=self.apply_to)
|
||||
60
moviepy/video/fx/SlideIn.py
Normal file
60
moviepy/video/fx/SlideIn.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class SlideIn(Effect):
|
||||
"""Makes the clip arrive from one side of the screen.
|
||||
|
||||
Only works when the clip is included in a CompositeVideoClip,
|
||||
and if the clip has the same size as the whole composition.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
clip : moviepy.Clip.Clip
|
||||
A video clip.
|
||||
|
||||
duration : float
|
||||
Time taken for the clip to be fully visible
|
||||
|
||||
side : str
|
||||
Side of the screen where the clip comes from. One of
|
||||
'top', 'bottom', 'left' or 'right'.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code:: python
|
||||
|
||||
from moviepy import *
|
||||
|
||||
clips = [... make a list of clips]
|
||||
slided_clips = [
|
||||
CompositeVideoClip([clip.with_effects([vfx.SlideIn(1, "left")])])
|
||||
for clip in clips
|
||||
]
|
||||
final_clip = concatenate_videoclips(slided_clips, padding=-1)
|
||||
|
||||
clip = ColorClip(
|
||||
color=(255, 0, 0), duration=1, size=(300, 300)
|
||||
).with_fps(60)
|
||||
final_clip = CompositeVideoClip([clip.with_effects([vfx.SlideIn(1, "right")])])
|
||||
"""
|
||||
|
||||
duration: float
|
||||
side: str
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
w, h = clip.size
|
||||
pos_dict = {
|
||||
"left": lambda t: (min(0, w * (t / self.duration - 1)), "center"),
|
||||
"right": lambda t: (max(0, w * (1 - t / self.duration)), "center"),
|
||||
"top": lambda t: ("center", min(0, h * (t / self.duration - 1))),
|
||||
"bottom": lambda t: ("center", max(0, h * (1 - t / self.duration))),
|
||||
}
|
||||
|
||||
return clip.with_position(pos_dict[self.side])
|
||||
64
moviepy/video/fx/SlideOut.py
Normal file
64
moviepy/video/fx/SlideOut.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class SlideOut(Effect):
|
||||
"""Makes the clip goes away by one side of the screen.
|
||||
|
||||
Only works when the clip is included in a CompositeVideoClip,
|
||||
and if the clip has the same size as the whole composition.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
clip : moviepy.Clip.Clip
|
||||
A video clip.
|
||||
|
||||
duration : float
|
||||
Time taken for the clip to be fully visible
|
||||
|
||||
side : str
|
||||
Side of the screen where the clip goes. One of
|
||||
'top', 'bottom', 'left' or 'right'.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code:: python
|
||||
|
||||
from moviepy import *
|
||||
|
||||
clips = [... make a list of clips]
|
||||
slided_clips = [
|
||||
CompositeVideoClip([clip.with_effects([vfx.SlideOut(1, "left")])])
|
||||
for clip in clips
|
||||
]
|
||||
final_clip = concatenate_videoclips(slided_clips, padding=-1)
|
||||
|
||||
clip = ColorClip(
|
||||
color=(255, 0, 0), duration=1, size=(300, 300)
|
||||
).with_fps(60)
|
||||
final_clip = CompositeVideoClip([clip.with_effects([vfx.SlideOut(1, "right")])])
|
||||
"""
|
||||
|
||||
duration: float
|
||||
side: str
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
if clip.duration is None:
|
||||
raise ValueError("Attribute 'duration' not set")
|
||||
|
||||
w, h = clip.size
|
||||
ts = clip.duration - self.duration # start time of the effect.
|
||||
pos_dict = {
|
||||
"left": lambda t: (min(0, w * (-(t - ts) / self.duration)), "center"),
|
||||
"right": lambda t: (max(0, w * ((t - ts) / self.duration)), "center"),
|
||||
"top": lambda t: ("center", min(0, h * (-(t - ts) / self.duration))),
|
||||
"bottom": lambda t: ("center", max(0, h * ((t - ts) / self.duration))),
|
||||
}
|
||||
|
||||
return clip.with_position(pos_dict[self.side])
|
||||
29
moviepy/video/fx/SuperSample.py
Normal file
29
moviepy/video/fx/SuperSample.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class SuperSample(Effect):
|
||||
"""Replaces each frame at time t by the mean of `n_frames` equally spaced frames
|
||||
taken in the interval [t-d, t+d]. This results in motion blur.
|
||||
"""
|
||||
|
||||
d: float
|
||||
n_frames: int
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
|
||||
def filter(get_frame, t):
|
||||
timings = np.linspace(t - self.d, t + self.d, self.n_frames)
|
||||
frame_average = np.mean(
|
||||
1.0 * np.array([get_frame(t_) for t_ in timings], dtype="uint16"),
|
||||
axis=0,
|
||||
)
|
||||
return frame_average.astype("uint8")
|
||||
|
||||
return clip.transform(filter)
|
||||
20
moviepy/video/fx/TimeMirror.py
Normal file
20
moviepy/video/fx/TimeMirror.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class TimeMirror(Effect):
|
||||
"""
|
||||
Returns a clip that plays the current clip backwards.
|
||||
The clip must have its ``duration`` attribute set.
|
||||
The same effect is applied to the clip's audio and mask if any.
|
||||
"""
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
if clip.duration is None:
|
||||
raise ValueError("Attribute 'duration' not set")
|
||||
|
||||
return clip[::-1]
|
||||
22
moviepy/video/fx/TimeSymmetrize.py
Normal file
22
moviepy/video/fx/TimeSymmetrize.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from moviepy.Clip import Clip
|
||||
from moviepy.Effect import Effect
|
||||
|
||||
|
||||
@dataclass
|
||||
class TimeSymmetrize(Effect):
|
||||
"""
|
||||
Returns a clip that plays the current clip once forwards and
|
||||
then once backwards. This is very practival to make video that
|
||||
loop well, e.g. to create animated GIFs.
|
||||
This effect is automatically applied to the clip's mask and audio
|
||||
if they exist.
|
||||
"""
|
||||
|
||||
def apply(self, clip: Clip) -> Clip:
|
||||
"""Apply the effect to the clip."""
|
||||
if clip.duration is None:
|
||||
raise ValueError("Attribute 'duration' not set")
|
||||
|
||||
return clip + clip[::-1]
|
||||
76
moviepy/video/fx/__init__.py
Normal file
76
moviepy/video/fx/__init__.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""All the visual effects that can be applied to VideoClip."""
|
||||
|
||||
# import every video fx function
|
||||
|
||||
from moviepy.video.fx.AccelDecel import AccelDecel
|
||||
from moviepy.video.fx.BlackAndWhite import BlackAndWhite
|
||||
from moviepy.video.fx.Blink import Blink
|
||||
from moviepy.video.fx.Crop import Crop
|
||||
from moviepy.video.fx.CrossFadeIn import CrossFadeIn
|
||||
from moviepy.video.fx.CrossFadeOut import CrossFadeOut
|
||||
from moviepy.video.fx.EvenSize import EvenSize
|
||||
from moviepy.video.fx.FadeIn import FadeIn
|
||||
from moviepy.video.fx.FadeOut import FadeOut
|
||||
from moviepy.video.fx.Freeze import Freeze
|
||||
from moviepy.video.fx.FreezeRegion import FreezeRegion
|
||||
from moviepy.video.fx.GammaCorrection import GammaCorrection
|
||||
from moviepy.video.fx.HeadBlur import HeadBlur
|
||||
from moviepy.video.fx.InvertColors import InvertColors
|
||||
from moviepy.video.fx.Loop import Loop
|
||||
from moviepy.video.fx.LumContrast import LumContrast
|
||||
from moviepy.video.fx.MakeLoopable import MakeLoopable
|
||||
from moviepy.video.fx.Margin import Margin
|
||||
from moviepy.video.fx.MaskColor import MaskColor
|
||||
from moviepy.video.fx.MasksAnd import MasksAnd
|
||||
from moviepy.video.fx.MasksOr import MasksOr
|
||||
from moviepy.video.fx.MirrorX import MirrorX
|
||||
from moviepy.video.fx.MirrorY import MirrorY
|
||||
from moviepy.video.fx.MultiplyColor import MultiplyColor
|
||||
from moviepy.video.fx.MultiplySpeed import MultiplySpeed
|
||||
from moviepy.video.fx.Painting import Painting
|
||||
from moviepy.video.fx.Resize import Resize
|
||||
from moviepy.video.fx.Rotate import Rotate
|
||||
from moviepy.video.fx.Scroll import Scroll
|
||||
from moviepy.video.fx.SlideIn import SlideIn
|
||||
from moviepy.video.fx.SlideOut import SlideOut
|
||||
from moviepy.video.fx.SuperSample import SuperSample
|
||||
from moviepy.video.fx.TimeMirror import TimeMirror
|
||||
from moviepy.video.fx.TimeSymmetrize import TimeSymmetrize
|
||||
|
||||
|
||||
__all__ = (
|
||||
"AccelDecel",
|
||||
"BlackAndWhite",
|
||||
"Blink",
|
||||
"Crop",
|
||||
"CrossFadeIn",
|
||||
"CrossFadeOut",
|
||||
"EvenSize",
|
||||
"FadeIn",
|
||||
"FadeOut",
|
||||
"Freeze",
|
||||
"FreezeRegion",
|
||||
"GammaCorrection",
|
||||
"HeadBlur",
|
||||
"InvertColors",
|
||||
"Loop",
|
||||
"LumContrast",
|
||||
"MakeLoopable",
|
||||
"Margin",
|
||||
"MasksAnd",
|
||||
"MaskColor",
|
||||
"MasksOr",
|
||||
"MirrorX",
|
||||
"MirrorY",
|
||||
"MultiplyColor",
|
||||
"MultiplySpeed",
|
||||
"Painting",
|
||||
"Resize",
|
||||
"Rotate",
|
||||
"Scroll",
|
||||
"SlideIn",
|
||||
"SlideOut",
|
||||
"SuperSample",
|
||||
"TimeMirror",
|
||||
"TimeSymmetrize",
|
||||
)
|
||||
Reference in New Issue
Block a user