Skip to content

Commit

Permalink
Fluent API for effects (monkeypatch as methods) (#1105)
Browse files Browse the repository at this point in the history
  • Loading branch information
mgaitan committed Oct 8, 2020
1 parent 4356fcd commit 4423b16
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 83 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed <!-- for changes in existing functionality -->

### Deprecated <!-- for soon-to-be removed features -->
### Deprecated <!-- for soon-to-be removed features -->
- `moviepy.video.fx.all` and `moviepy.audio.fx.all`. Use the fx method directly from the clip instance or import the fx function from `moviepy.video.fx` and `moviepy.audio.fx`. [#1105]

### Removed <!-- for now removed features -->

Expand Down
2 changes: 1 addition & 1 deletion moviepy/audio/AudioClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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
18 changes: 7 additions & 11 deletions moviepy/audio/fx/all/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +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 fx method directly from the clip instance (e.g. ``clip.audio_loop(...)``)
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"\nMoviePy: {__doc__}", UserWarning)
14 changes: 6 additions & 8 deletions moviepy/audio/fx/audio_loop.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from ..AudioClip import concatenate_audioclips
from moviepy.decorators import audio_video_fx


def audio_loop(audioclip, n_loops=None, duration=None):
@audio_video_fx
def audio_loop(clip, n_loops=None, duration=None):
"""Loops over an audio clip.
Returns an audio clip that plays the given clip either
Expand All @@ -17,12 +19,8 @@ def audio_loop(audioclip, n_loops=None, duration=None):
>>> videoclip.with_audio(audio)
"""

if duration is not None:
n_loops = int(duration / clip.duration) + 1
return concatenate_audioclips(n_loops * [clip]).with_duration(duration)

n_loops = int(duration / audioclip.duration) + 1
return concatenate_audioclips(n_loops * [audioclip]).with_duration(duration)

else:

return concatenate_audioclips(n_loops * [audioclip])
return concatenate_audioclips(n_loops * [clip])
85 changes: 46 additions & 39 deletions moviepy/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,44 @@
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__ = [
"afx",
"AudioClip",
"AudioFileClip",
"BitmapClip",
"clips_array",
"ColorClip",
"CompositeAudioClip",
"CompositeVideoClip",
"concatenate_audioclips",
"concatenate_videoclips",
"convert_to_seconds",
"download_webfile",
"ffmpeg_tools",
"ImageClip",
"ImageSequenceClip",
"ipython_display",
"TextClip",
"transfx",
"vfx",
"VideoClip",
"VideoFileClip",
"videotools",
]

# 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


# Hide the welcome message from pygame: https://github.com/pygame/pygame/issues/542
Expand All @@ -37,8 +62,8 @@

# FX

import moviepy.video.fx.all as vfx
import moviepy.audio.fx.all as afx
import moviepy.video.fx as vfx
import moviepy.audio.fx as afx
import moviepy.video.compositing.transitions as transfx

# Tools
Expand All @@ -50,44 +75,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))
# Transforms the effects into Clip methods so that
# they can be called with clip.resize(width=500) instead of
# clip.fx(vfx.resize, width=500)
audio_fxs = inspect.getmembers(afx, inspect.isfunction)
video_fxs = (
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
Expand Down
2 changes: 1 addition & 1 deletion moviepy/video/VideoClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class VideoClip(Clip):
def __init__(
self, make_frame=None, is_mask=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
3 changes: 3 additions & 0 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
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
19 changes: 7 additions & 12 deletions moviepy/video/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.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 fx method directly from the clip instance (e.g. ``clip.resize(...)``)
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"\nMoviePy: {__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.time_transform(lambda t: self.duration - t - 1, keep_duration=True)
return clip.time_transform(lambda t: clip.duration - t - 1, keep_duration=True)
1 change: 1 addition & 0 deletions tests/test_issues.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""Issue tests meant to be run with pytest."""
import pytest
import os

from moviepy.editor import *
from moviepy.utils import close_all_clips
Expand Down

0 comments on commit 4423b16

Please sign in to comment.