Skip to content

Commit

Permalink
Misc. changes. Added use_clip_fps_by_default. Changed ImageSequenceClip
Browse files Browse the repository at this point in the history
  • Loading branch information
Zulko committed Nov 3, 2014
1 parent e66f84c commit a92de28
Show file tree
Hide file tree
Showing 19 changed files with 278 additions and 97 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Installation
(sudo) pip install moviepy

This method may fail if ``ez_setup`` is not installed on your computer. In this case install ``ez_setup`` first, with ::
This method will fail if ``ez_setup`` is not installed on your computer. In this case install ``ez_setup`` first, with ::
(sudo) pip install ez_setup

Expand Down
22 changes: 22 additions & 0 deletions docs/examples/quick_recipes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Quick recipes
===============



Add a title before a video
---------------------------



Videos



Make gifs that loop well
--------------------------

clip.fx( vfx.time_symmetrize)


# find a subclip
T = clip
8 changes: 6 additions & 2 deletions docs/getting_started/quick_presentation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ Here are a few reasons why you may want to edit videos in Python:
- You want to code your own video effects to do something no existing video editor can.
- You want to create animations from images generated by another python library (Matplotlib, Mayavi, Gizeh, scikit-images...)

And here are a few uses for which MoviePy is NOT the best solution:
- You only need to do frame-by-frame video analysis (with face detection or other fancy stuff). This could be done with MoviePy in association with other libraries, but really, just use OpenCV or SimpleCV, these are libraries that specialize in these tasks.
- You only want to convert a video, or convert a series of images to a movie. In this case it is better to directly call ``ffmpeg`` (or ``avconv`` or ``mencoder``...) it will be faster more memory-efficient than going through MoviePy.


Advantages and limitations
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand All @@ -25,8 +30,7 @@ MoviePy has been developed with the following goals in mind:
- **Flexible**. You have total control over the frames of the video and audio, and creating your own effects is easy as Py.
- **Portable**. The code uses very common software (Numpy and FFMPEG) and can run on (almost) any machine with (almost) any version of Python.

Now for the limitations: MoviePy cannot (yet) stream videos (read from a webcam, or render a video live on a distant machine), it cannot do 3D effects (by lack of a nice 3D library for Python) and is not really designed for video processing involving many successive frames of a movie (like video stabilization, you'll need another software for that). Also, if you only need to do frame-by-frame video analysis (with face detection or other fancy stuff) you may want to use the libraries OpenCV and SimpleCV instead.

Now for the main limitations: MoviePy cannot (yet) stream videos (read from a webcam, or render a video live on a distant machine), it cannot do 3D effects (by lack of a nice 3D library for Python) and is not really designed for video processing involving many successive frames of a movie (like video stabilization, you'll need another software for that).

Example code
~~~~~~~~~~~~~~
Expand Down
30 changes: 19 additions & 11 deletions moviepy/Clip.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
apply_to_audio,
requires_duration,
outplace,
convert_to_seconds)
convert_to_seconds,
use_clip_fps_by_default)
from tqdm import tqdm

class Clip:
Expand Down Expand Up @@ -68,17 +69,19 @@ def copy(self):
newclip.mask = copy(self.mask)

return newclip


@convert_to_seconds(['t'])
def get_frame(self, t):
"""
Gets a numpy array representing the RGB picture of the clip at time t.
Gets a numpy array representing the RGB picture of the clip at time t
or (mono or stereo) value for a sound clip
"""
# Coming soon: smart error handling for debugging at this point
return self.make_frame(t)

def fl(self, fun, apply_to=[] , keep_duration=True):
""" General processing of a clip.
Returns a new Clip whose frames are a transformation
(through function ``fun``) of the frames of the current clip.
Expand Down Expand Up @@ -276,12 +279,18 @@ def set_duration(self, t, change_end=True):


@outplace
def set_make_frame(self, gf):
def set_make_frame(self, make_frame):
"""
Sets a ``make_frame`` attribute for the clip. Useful for setting
arbitrary/complicated videoclips.
"""
self.make_frame = gf
self.make_frame = make_frame

@outplace
def set_fps(self, fps):
""" Returns a copy of the clip with a new default fps for functions like
write_videofile, iterframe, etc. """
self.fps = fps



Expand Down Expand Up @@ -318,6 +327,8 @@ def is_playing(self, t):
return( (t >= self.start) and
((self.end is None) or (t < self.end) ) )



@convert_to_seconds(['t_start', 't_end'])
@apply_to_mask
@apply_to_audio
Expand Down Expand Up @@ -362,8 +373,7 @@ def subclip(self, t_start=0, t_end=None):
newclip.end = newclip.start + newclip.duration

return newclip




@apply_to_mask
@apply_to_audio
Expand All @@ -390,6 +400,7 @@ def cutout(self, ta, tb):
return newclip

@requires_duration
@use_clip_fps_by_default
def iter_frames(self, fps=None, with_times = False, progress_bar=False,
dtype=None):
""" Iterates over all the frames of the clip.
Expand All @@ -416,9 +427,6 @@ def iter_frames(self, fps=None, with_times = False, progress_bar=False,
>>> print ( [frame[0,:,0].max()
for frame in myclip.iter_frames()])
"""

if fps is None:
fps = self.fps

def generator():
for t in np.arange(0, self.duration, 1.0/fps):
Expand Down
13 changes: 8 additions & 5 deletions moviepy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@

def try_cmd(cmd):
try:
popen_params = {"stdout": sp.PIPE,
"stderr": sp.PIPE,
"stdin": DEVNULL}
popen_params = { "stdout": sp.PIPE,
"stderr": sp.PIPE,
"stdin": DEVNULL
}


# This was added so that no extra unwanted window opens on windows
# when the child process is created
Expand All @@ -35,9 +37,10 @@ def try_cmd(cmd):
else:
FFMPEG_BINARY = 'unset'
else:
success, err = try_cmd(cmd)
success, err = try_cmd([FFMPEG_BINARY])

This comment has been minimized.

Copy link
@tankorsmash

tankorsmash Nov 30, 2014

I don't know much about pip, but the one on pip for python27x32 doesn't have this change in it yet. Manually fixed it and came here to commit it. It's less than a month old fix, so it's probably just an issue of pip not being updated.

This comment has been minimized.

Copy link
@Zulko

Zulko Nov 30, 2014

Author Owner

@tankorsmash I updated pip right now.

This comment has been minimized.

Copy link
@tankorsmash

tankorsmash via email Nov 30, 2014

if not success:
raise err
raise IOError(err.message +
"The path specified for the ffmpeg binary might be wrong")

if IMAGEMAGICK_BINARY=='auto-detect':

Expand Down
36 changes: 34 additions & 2 deletions moviepy/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def warper(f, *a, **kw):
func_code = f.__code__ # Python 3

names = func_code.co_varnames
new_a = [cvsecs(arg) if (name in varnames) else arg
new_a = [fun(arg) if (name in varnames) else arg
for (arg, name) in zip(a, names)]
new_kw = {k: fun(v) if k in varnames else v
for (k,v) in kw.items()}
Expand All @@ -99,4 +99,36 @@ def add_mask_if_none(f, clip, *a, **k):
""" Add a mask to the clip if there is none. """
if clip.mask is None:
clip = clip.add_mask()
return f(clip, *a, **k)
return f(clip, *a, **k)



@decorator.decorator
def use_clip_fps_by_default(f, clip, *a, **k):

def fun(fps):
if fps is not None:
return fps
else:
if hasattr(clip, 'fps') and clip.fps is not None:
return clip.fps
else:
raise AttributeError("No 'fps' (frames per second) attribute specified"
" for function %s and the clip has no 'fps' attribute. Either"
" provide e.g. fps=24 in the arguments of the function, or define"
" the clip's fps with `clip.fps=24`"%f.__name__)


if hasattr(f, "func_code"):
func_code = f.func_code # Python 2
else:
func_code = f.__code__ # Python 3

names = func_code.co_varnames[1:]

new_a = [fun(arg) if (name=='fps') else arg
for (arg, name) in zip(a, names)]
new_kw = {k: fun(v) if k=='fps' else v
for (k,v) in k.items()}

return f(clip, *new_a, **new_kw)
15 changes: 8 additions & 7 deletions moviepy/video/VideoClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
outplace,
add_mask_if_none,
convert_to_seconds,
convert_masks_to_RGB)
convert_masks_to_RGB,
use_clip_fps_by_default)

import os
try:
Expand Down Expand Up @@ -144,8 +145,9 @@ def save_frame(self, filename, t=0, withmask=True):


@requires_duration
@use_clip_fps_by_default
@convert_masks_to_RGB
def write_videofile(self, filename, fps=24, codec=None,
def write_videofile(self, filename, fps=None, codec=None,
bitrate=None, audio=True, audio_fps=44100,
preset="medium",
audio_nbytes=4, audio_codec=None,
Expand All @@ -166,7 +168,8 @@ def write_videofile(self, filename, fps=24, codec=None,
or simply be '.avi' (which will work with any codec).
fps
Number of frames per second in the resulting video file.
Number of frames per second in the resulting video file. If None is
provided, and the clip has an fps attribute, this fps will be used.
codec
Codec to use for image encoding. Can be any codec supported
Expand Down Expand Up @@ -338,6 +341,7 @@ def write_videofile(self, filename, fps=24, codec=None,


@requires_duration
@use_clip_fps_by_default
@convert_masks_to_RGB
def write_images_sequence(self, nameformat, fps=None, verbose=True,
withmask=True):
Expand Down Expand Up @@ -381,9 +385,6 @@ def write_images_sequence(self, nameformat, fps=None, verbose=True,

verbose_print(verbose, "MoviePy: Writing frames %s." % (nameformat))

if fps is None:
fps = self.fps

tt = np.arange(0, self.duration, 1.0 / fps)

filenames = []
Expand Down Expand Up @@ -894,7 +895,7 @@ def fl_time(self, time_func, apply_to=['mask', 'audio'],
(see Clip.fl_time).
This method does nothing for ImageClips (but it may affect their
masks of their audios). The result is still an ImageClip.
masks or their audios). The result is still an ImageClip.
"""

for attr in apply_to:
Expand Down
44 changes: 44 additions & 0 deletions moviepy/video/fx/accel_decel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
def f_accel_decel(t, old_d, new_d, abruptness=1, soonness=1.0):
"""
abruptness
negative abruptness (>-1): speed up down up
zero abruptness : no effect
positive abruptness: speed down up down
soonness
for positive abruptness, determines how soon the
speedup occurs (0<soonness < inf)
"""

a = 1.0+abruptness
def _f(t):
f1 = lambda t: (0.5)**(1-a)*(t**a)
f2 = lambda t: (1-f1(1-t))
return (t<.5)*f1(t) + (t>=.5)*f2(t)

return old_d*_f((t/new_d)**soonness)


def accel_decel(clip, new_duration=None, abruptness=1.0, soonness=1.0):
"""
new_duration
If None, will be that of the current clip.
abruptness
negative abruptness (>-1): speed up down up
zero abruptness : no effect
positive abruptness: speed down up down
soonness
for positive abruptness, determines how soon the
speedup occurs (0<soonness < inf)
"""

if new_duration is None:
new_duration = clip.duration

fl = lambda t : f_accel_decel(t, clip.duration, new_duration,
abruptness, soonness)

return clip.fl_time(fl).set_duration(new_duration)
29 changes: 23 additions & 6 deletions moviepy/video/fx/fadein.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@

def fadein(clip, duration):
""" Makes the clip fade to black progressively, over ``duration``
seconds. For more advanced fading, see
``moviepy.video.composition.crossfadein`` """
import numpy as np

def fadein(clip, duration, initial_color=None):
"""
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 ``composition.crossfade``
"""

if initial_color is None:
initial_color = 0 if clip.ismask else [0,0,0]

return clip.fl(lambda gf, t: min(1.0 * t / duration, 1) * gf(t))
initial_color = np.array(initial_color)

def fl(gf, t):
if t>=duration:
return gf(t)
else:
fading = (1.0*t/duration)
return fading*gf(t) + (1-fading)*initial_color

return clip.fl(fl)
25 changes: 20 additions & 5 deletions moviepy/video/fx/fadeout.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
from moviepy.decorators import requires_duration
import numpy as np

@requires_duration
def fadeout(clip, duration):
def fadeout(clip, duration, final_color=None):
"""
Makes the clip fade to black progressively, over ``duration`` seconds.
For more advanced fading, see ``composition.crossfade``
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 ``composition.crossfade``
"""
fading = lambda t: min(1.0 * (clip.duration - t) / duration, 1)
return clip.fl(lambda gf, t: fading(t) * gf(t))

if final_color is None:
final_color = 0 if clip.ismask else [0,0,0]

final_color = np.array(final_color)

def fl(gf, t):
if (clip.duration-t)>=duration:
return gf(t)
else:
fading = 1.0 * (clip.duration - t) / duration
return fading*gf(t) + (1-fading)*final_color

return clip.fl(fl)



Expand Down
Loading

0 comments on commit a92de28

Please sign in to comment.