Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fluent API for effects (monkeypatch as methods) #1105

Merged
merged 26 commits into from
Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ee43464
Rework editor.py fx imports to include all fx
tburrows13 Mar 20, 2020
2fb768e
Remove debug print statements
tburrows13 Mar 20, 2020
91b8aef
use super
mgaitan Mar 24, 2020
4302303
audio loop support videoclip instance
mgaitan Mar 24, 2020
3c2f9c4
patch classes avoiding exec
mgaitan Mar 24, 2020
5629a94
explicitly set fx all
mgaitan Mar 24, 2020
f0f90f0
explicitly define __all__ on editor.py
mgaitan Mar 24, 2020
448029f
user warning to be explicit
mgaitan Mar 24, 2020
aa81368
Update moviepy/audio/fx/audio_loop.py
mgaitan Mar 24, 2020
8c77f9c
Merge remote-tracking branch 'zulko/v2' into fluent_api
mgaitan Mar 25, 2020
d46cb50
Merge branch 'fluent_api' of github.com:mgaitan/moviepy into fluent_api
mgaitan Mar 25, 2020
8eb54d6
black files
mgaitan Mar 25, 2020
19f15b2
fix regressions
mgaitan Mar 25, 2020
f1c4c3e
Merge remote-tracking branch 'zulko/master' into fluent_api
mgaitan Apr 4, 2020
f72522c
import package directly instead all modules
mgaitan Apr 4, 2020
72ffe1a
Merge branch 'master' into fluent_api
tburrows13 Apr 9, 2020
24f131e
Merge branch 'master' into fluent_api
tburrows13 Apr 26, 2020
92d8a91
Add missing import
tburrows13 Apr 29, 2020
6b594b3
Merge branch 'master' into fluent_api
tburrows13 Oct 4, 2020
e2f4d3d
Merge branch 'master' into fluent_api
tburrows13 Oct 8, 2020
3d8dd86
Fix undefined name from incorrect merging
tburrows13 Oct 8, 2020
7dc7b5f
cvsecs -> convert_to_seconds in editor.py
tburrows13 Oct 8, 2020
9cfd5fa
Minor docs cleanup
tburrows13 Oct 8, 2020
d8a0bd8
Add @audio_video_fx to audio_loop (should probably delete audio_loop …
tburrows13 Oct 8, 2020
9f174c2
Updated CHANGELOG.md
tburrows13 Oct 8, 2020
eee1e1f
Updated CHANGELOG.md
tburrows13 Oct 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion moviepy/audio/AudioClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class AudioClip(Clip):
"""

def __init__(self, make_frame=None, duration=None, fps=None):
Clip.__init__(self)
super().__init__()

if fps is not None:
self.fps = fps
Expand Down
12 changes: 8 additions & 4 deletions moviepy/audio/fx/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
"""
This module contains transformation functions (clip->clip)
One file for one fx. The file's name is the fx's name
"""
# import every video fx function

from .audio_fadein import audio_fadein
from .audio_fadeout import audio_fadeout
from .audio_left_right import audio_left_right
from .audio_loop import audio_loop
from .audio_normalize import audio_normalize
from .volumex import volumex
19 changes: 7 additions & 12 deletions moviepy/audio/fx/all/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
"""
Loads all the fx !
Usage:
import moviepy.audio.fx.all as afx
audio_clip = afx.volume_x(some_clip, .5)
"""

import pkgutil
moviepy.audio.fx.all is deprecated.

import moviepy.audio.fx as fx
Use the right fx method directly from the clip instance
or import the function from moviepy.audio.fx instead.
"""
import warnings

__all__ = [name for _, name, _ in pkgutil.iter_modules(
fx.__path__) if name != "all"]
from .. import *

for name in __all__:
exec("from ..%s import %s" % (name, name))
warnings.warn(f"MoviePy: {__doc__}", UserWarning)
13 changes: 9 additions & 4 deletions moviepy/audio/fx/audio_loop.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from ..AudioClip import concatenate_audioclips


def audio_loop(audioclip, nloops=None, duration=None):
def audio_loop(clip, nloops=None, duration=None):
""" Loops over an audio clip.

Returns an audio clip that plays the given clip either
Expand All @@ -17,12 +17,17 @@ def audio_loop(audioclip, nloops=None, duration=None):
>>> videoclip.set_audio(audio)

"""
try:
tburrows13 marked this conversation as resolved.
Show resolved Hide resolved
clip = clip.audio
except AttributeError:
# assume it's already an audioclip
pass

if duration is not None:

nloops = int( duration/ audioclip.duration)+1
return concatenate_audioclips(nloops*[audioclip]).set_duration(duration)
nloops = int( duration/ clip.duration)+1
return concatenate_audioclips(nloops*[clip]).set_duration(duration)

else:

return concatenate_audioclips(nloops*[audioclip])
return concatenate_audioclips(nloops*[clip])
68 changes: 30 additions & 38 deletions moviepy/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,31 @@

In particular it will load many effects from the video.fx and audio.fx
folders and turn them into VideoClip methods, so that instead of
>>> clip.fx( vfx.resize, 2 ) # or equivalently vfx.resize(clip, 2)
>>> clip.fx( vfx.resize, 2 ) or equivalently vfx.resize(clip, 2)
we can write
>>> clip.resize(2)

It also starts a PyGame session (if PyGame is installed) and enables
clip.preview().
"""

__all__ = [
"VideoFileClip",
"ImageSequenceClip",
"download_webfile",
"VideoClip", "ImageClip", "ColorClip", "TextClip",
"CompositeVideoClip", "concatenate_videoclips",
"AudioClip", "CompositeAudioClip", "concatenate_audioclips",
"AudioFileClip", "vfx", "afx", "transfx", "videotools",
"ffmpeg_tools", "ipython_display", "cvsecs"
]

# Note that these imports could have been performed in the __init__.py
# file, but this would make the loading of moviepy slower.

import os
import sys
import inspect
import itertools


# Hide the welcome message from pygame: https://github.com/pygame/pygame/issues/542
Expand All @@ -30,7 +42,7 @@
from .video.io.downloader import download_webfile
from .video.VideoClip import VideoClip, ImageClip, ColorClip, TextClip
from .video.compositing.CompositeVideoClip import CompositeVideoClip, clips_array
from .video.compositing.concatenate import concatenate_videoclips, concatenate # concatenate=deprecated
from .video.compositing.concatenate import concatenate_videoclips

from .audio.AudioClip import AudioClip, CompositeAudioClip, concatenate_audioclips
from .audio.io.AudioFileClip import AudioFileClip
Expand All @@ -50,45 +62,26 @@

try:
from .video.io.sliders import sliders
__all__.append("sliders")
except ImportError:
pass

# The next loop transforms many effects into VideoClip methods so that
# they can be called with myclip.resize(width=500) instead of
# myclip.fx( vfx.resize, width= 500)
for method in [
"afx.audio_fadein",
"afx.audio_fadeout",
"afx.audio_normalize",
"afx.volumex",
"transfx.crossfadein",
"transfx.crossfadeout",
"vfx.crop",
"vfx.fadein",
"vfx.fadeout",
"vfx.invert_colors",
"vfx.loop",
"vfx.margin",
"vfx.mask_and",
"vfx.mask_or",
"vfx.resize",
"vfx.rotate",
"vfx.speedx"
]:

exec("VideoClip.%s = %s" % (method.split('.')[1], method))


for method in ["afx.audio_fadein",
"afx.audio_fadeout",
"afx.audio_loop",
"afx.audio_normalize",
"afx.volumex"
]:

exec("AudioClip.%s = %s" % (method.split('.')[1], method))


audio_fxs = inspect.getmembers(afx, inspect.isfunction)
video_fxs = itertools.chain(
inspect.getmembers(vfx, inspect.isfunction),
inspect.getmembers(transfx, inspect.isfunction),
audio_fxs
)
for name, function in video_fxs:
setattr(VideoClip, name, function)

for name, function in audio_fxs:
setattr(AudioClip, name, function)


# adds easy ipython integration
VideoClip.ipython_display = ipython_display
AudioClip.ipython_display = ipython_display
Expand All @@ -109,7 +102,6 @@ def show(self, *args, **kwargs):
"""NOT AVAILABLE : clip.show requires Pygame installed."""
raise ImportError("clip.show requires Pygame installed")


VideoClip.preview = preview
VideoClip.show = show

Expand All @@ -120,4 +112,4 @@ def preview(self, *args, **kwargs):
""" NOT AVAILABLE : clip.preview requires Pygame installed."""
raise ImportError("clip.preview requires Pygame installed")

AudioClip.preview = preview
AudioClip.preview = preview
3 changes: 1 addition & 2 deletions moviepy/video/VideoClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,9 @@ class VideoClip(Clip):
See variable ``pos``.

"""

def __init__(self, make_frame=None, ismask=False, duration=None,
has_constant_size=True):
Clip.__init__(self)
super().__init__()
self.mask = None
self.audio = None
self.pos = lambda t: (0, 0)
Expand Down
25 changes: 16 additions & 9 deletions moviepy/video/compositing/transitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
from .CompositeVideoClip import CompositeVideoClip


__all__ = ["crossfadein", "crossfadeout", "slide_in", "slide_out", "make_loopable"]


@requires_duration
@add_mask_if_none
def crossfadein(clip, duration):
Expand Down Expand Up @@ -65,10 +68,12 @@ def slide_in(clip, duration, side):

"""
w, h = clip.size
pos_dict = {'left': lambda t: (min(0, w*(t/duration-1)), 'center'),
'right': lambda t: (max(0, w*(1-t/duration)), 'center'),
'top': lambda t: ('center', min(0, h*(t/duration-1))),
'bottom': lambda t: ('center', max(0, h*(1-t/duration)))}
pos_dict = {
"left": lambda t: (min(0, w * (t / duration - 1)), "center"),
"right": lambda t: (max(0, w * (1 - t / duration)), "center"),
"top": lambda t: ("center", min(0, h * (t / duration - 1))),
"bottom": lambda t: ("center", max(0, h * (1 - t / duration))),
}

return clip.set_position(pos_dict[side])

Expand Down Expand Up @@ -106,18 +111,20 @@ def slide_out(clip, duration, side):

w, h = clip.size
ts = clip.duration - duration # start time of the effect.
pos_dict = {'left': lambda t: (min(0, w*(1-(t-ts)/duration)), 'center'),
'right': lambda t: (max(0, w*((t-ts)/duration-1)), 'center'),
'top': lambda t: ('center', min(0, h*(1-(t-ts)/duration))),
'bottom': lambda t: ('center', max(0, h*((t-ts)/duration-1)))}
pos_dict = {
"left": lambda t: (min(0, w * (1 - (t - ts) / duration)), "center"),
"right": lambda t: (max(0, w * ((t - ts) / duration - 1)), "center"),
"top": lambda t: ("center", min(0, h * (1 - (t - ts) / duration))),
"bottom": lambda t: ("center", max(0, h * ((t - ts) / duration - 1))),
}

return clip.set_position(pos_dict[side])


@requires_duration
def make_loopable(clip, cross_duration):
""" Makes the clip fade in progressively at its own end, this way
it can be looped indefinitely. ``cross`` is the duration in seconds
it can be looped indefinitely. ``cross_duration`` is the duration in seconds
of the fade-in. """
d = clip.duration
clip2 = clip.fx(crossfadein, cross_duration).set_start(d - cross_duration)
Expand Down
36 changes: 32 additions & 4 deletions moviepy/video/fx/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,32 @@
"""
This module contains transformation functions (clip->clip)
One file for one fx. The file's name is the fx's name
"""
# import every video fx function

from .accel_decel import accel_decel
from .blackwhite import blackwhite
from .blink import blink
from .colorx import colorx
from .crop import crop
from .even_size import even_size
from .fadein import fadein
from .fadeout import fadeout
from .freeze import freeze
from .freeze_region import freeze_region
from .gamma_corr import gamma_corr
from .headblur import headblur
from .invert_colors import invert_colors
from .loop import loop
from .lum_contrast import lum_contrast
from .make_loopable import make_loopable
from .margin import margin
from .mask_and import mask_and
from .mask_color import mask_color
from .mask_or import mask_or
from .mirror_x import mirror_x
from .mirror_y import mirror_y
from .painting import painting
from .resize import resize
from .rotate import rotate
from .scroll import scroll
from .speedx import speedx
from .supersample import supersample
from .time_mirror import time_mirror
from .time_symmetrize import time_symmetrize
20 changes: 7 additions & 13 deletions moviepy/video/fx/all/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
"""
Loads all the fx !
Usage:
import moviepy.video.fx.all as vfx
clip = vfx.resize(some_clip, width=400)
clip = vfx.mirror_x(some_clip)
"""

import pkgutil
moviepy.video.fx.all is deprecated.

import moviepy.video.fx as fx
Use the right fx method directly from the clip instance
or import the function from moviepy.video.fx instead.
"""
import warnings

__all__ = [name for _, name, _ in pkgutil.iter_modules(
fx.__path__) if name != "all"]
from .. import *

for name in __all__:
exec("from ..%s import %s" % (name, name))
warnings.warn(f"MoviePy: {__doc__}", UserWarning)
4 changes: 2 additions & 2 deletions moviepy/video/fx/time_mirror.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
@requires_duration
@apply_to_mask
@apply_to_audio
def time_mirror(self):
def time_mirror(clip):
"""
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.
"""
return self.fl_time(lambda t: self.duration - t, keep_duration=True)
return clip.fl_time(lambda t: clip.duration - t, keep_duration=True)
1 change: 1 addition & 0 deletions moviepy/video/io/html_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def html_embed(clip, filetype=None, maxduration=60, rd_kwargs=None,
return result



def ipython_display(clip, filetype=None, maxduration=60, t=None, fps=None,
rd_kwargs=None, center=True, **html_kwargs):
"""
Expand Down