From a92de2861dfbefe7bc8d2ab1e7d7fbad40320db6 Mon Sep 17 00:00:00 2001 From: Zulko Date: Mon, 3 Nov 2014 14:46:28 +0100 Subject: [PATCH] Misc. changes. Added use_clip_fps_by_default. Changed ImageSequenceClip --- README.rst | 2 +- docs/examples/quick_recipes.rst | 22 ++++++ docs/getting_started/quick_presentation.rst | 8 ++- moviepy/Clip.py | 30 +++++--- moviepy/config.py | 13 ++-- moviepy/decorators.py | 36 +++++++++- moviepy/video/VideoClip.py | 15 ++-- moviepy/video/fx/accel_decel.py | 44 ++++++++++++ moviepy/video/fx/fadein.py | 29 ++++++-- moviepy/video/fx/fadeout.py | 25 +++++-- moviepy/video/fx/lum_contrast.py | 2 +- moviepy/video/fx/supersample.py | 12 ++++ moviepy/video/io/ImageSequenceClip.py | 77 ++++++++++++++------- moviepy/video/io/ffmpeg_reader.py | 16 +++-- moviepy/video/io/ffmpeg_writer.py | 2 +- moviepy/video/io/gif_writers.py | 9 +-- moviepy/video/tools/cuts.py | 13 ++-- moviepy/video/tools/interpolators.py | 14 ++-- moviepy/video/tools/tracking.py | 6 +- 19 files changed, 278 insertions(+), 97 deletions(-) create mode 100644 docs/examples/quick_recipes.rst create mode 100644 moviepy/video/fx/accel_decel.py create mode 100644 moviepy/video/fx/supersample.py diff --git a/README.rst b/README.rst index 596d8689a..dbd7650c3 100644 --- a/README.rst +++ b/README.rst @@ -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 diff --git a/docs/examples/quick_recipes.rst b/docs/examples/quick_recipes.rst new file mode 100644 index 000000000..7d612e0ce --- /dev/null +++ b/docs/examples/quick_recipes.rst @@ -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 diff --git a/docs/getting_started/quick_presentation.rst b/docs/getting_started/quick_presentation.rst index f315c5a9e..871a12195 100644 --- a/docs/getting_started/quick_presentation.rst +++ b/docs/getting_started/quick_presentation.rst @@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -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 ~~~~~~~~~~~~~~ diff --git a/moviepy/Clip.py b/moviepy/Clip.py index 4d9393cbd..13e069505 100644 --- a/moviepy/Clip.py +++ b/moviepy/Clip.py @@ -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: @@ -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. @@ -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 @@ -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 @@ -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 @@ -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. @@ -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): diff --git a/moviepy/config.py b/moviepy/config.py index edea934dd..81bd54249 100644 --- a/moviepy/config.py +++ b/moviepy/config.py @@ -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 @@ -35,9 +37,10 @@ def try_cmd(cmd): else: FFMPEG_BINARY = 'unset' else: - success, err = try_cmd(cmd) + success, err = try_cmd([FFMPEG_BINARY]) if not success: - raise err + raise IOError(err.message + + "The path specified for the ffmpeg binary might be wrong") if IMAGEMAGICK_BINARY=='auto-detect': diff --git a/moviepy/decorators.py b/moviepy/decorators.py index 0224b8bf7..c78b83e8d 100644 --- a/moviepy/decorators.py +++ b/moviepy/decorators.py @@ -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()} @@ -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) \ No newline at end of file + 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) diff --git a/moviepy/video/VideoClip.py b/moviepy/video/VideoClip.py index b5e5c0405..08a7e037d 100644 --- a/moviepy/video/VideoClip.py +++ b/moviepy/video/VideoClip.py @@ -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: @@ -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, @@ -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 @@ -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): @@ -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 = [] @@ -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: diff --git a/moviepy/video/fx/accel_decel.py b/moviepy/video/fx/accel_decel.py new file mode 100644 index 000000000..a7c413caf --- /dev/null +++ b/moviepy/video/fx/accel_decel.py @@ -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=.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=duration: + return gf(t) + else: + fading = (1.0*t/duration) + return fading*gf(t) + (1-fading)*initial_color + + return clip.fl(fl) \ No newline at end of file diff --git a/moviepy/video/fx/fadeout.py b/moviepy/video/fx/fadeout.py index 4027f62f3..68f31dda1 100644 --- a/moviepy/video/fx/fadeout.py +++ b/moviepy/video/fx/fadeout.py @@ -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) diff --git a/moviepy/video/fx/lum_contrast.py b/moviepy/video/fx/lum_contrast.py index 992b420e7..a1ae55cf2 100644 --- a/moviepy/video/fx/lum_contrast.py +++ b/moviepy/video/fx/lum_contrast.py @@ -3,7 +3,7 @@ def lum_contrast(clip, lum = 0, contrast=0, contrast_thr=127): def fl_image(im): im = 1.0*im # float conversion - corrected = im + lum + factor*(im-thr) + corrected = im + lum + contrast*(im-float(contrast_thr)) corrected[corrected < 0] = 0 corrected[corrected > 255] = 255 return corrected.astype('uint8') diff --git a/moviepy/video/fx/supersample.py b/moviepy/video/fx/supersample.py new file mode 100644 index 000000000..5c7162b50 --- /dev/null +++ b/moviepy/video/fx/supersample.py @@ -0,0 +1,12 @@ +import numpy as np + +def supersample(clip, d, nframes): + """ Replaces each frame at time t by the mean of `nframes` equally spaced frames + taken in the interval [t-d, t+d]. This results in motion blur.""" + + def fl(gf, t): + tt = np.linspace(t-d, t+d, nframes) + avg = np.mean(1.0*np.array([gf(t_) for t_ in tt], dtype='uint16'), axis=0) + return avg.astype("uint8") + + return clip.fl(fl) \ No newline at end of file diff --git a/moviepy/video/io/ImageSequenceClip.py b/moviepy/video/io/ImageSequenceClip.py index ee6101cd5..86cb17a94 100644 --- a/moviepy/video/io/ImageSequenceClip.py +++ b/moviepy/video/io/ImageSequenceClip.py @@ -1,6 +1,9 @@ +import os +import numpy as np + from ..VideoClip import VideoClip from .ffmpeg_reader import ffmpeg_read_image -import os + class ImageSequenceClip(VideoClip): """ @@ -15,13 +18,17 @@ class ImageSequenceClip(VideoClip): Can be one of these: - The name of a folder (containing only pictures). The pictures will be considered in alphanumerical order. - - A list of names of image files. + - A list of names of image files. In this case you can choose to + load the pictures in memory pictures - A list of Numpy arrays representing images. In this last case, masks are not supported currently. - fps - Number of picture frames to read per second. + Number of picture frames to read per second. Instead, you can provide + the duration of each image with durations (see below) + + durations + List of the duration of each picture. with_mask Should the alpha layer of PNG images be considered as a mask ? @@ -29,11 +36,18 @@ class ImageSequenceClip(VideoClip): ismask Will this sequence of pictures be used as an animated mask. + Notes + ------ + + If your sequence is made of image files, the only image kept in + + """ - def __init__(self, sequence, fps, with_mask=True, ismask=False): + def __init__(self, sequence, fps=None, durations=None, with_mask=True, + ismask=False, load_images=False): # CODE WRITTEN AS IT CAME, MAY BE IMPROVED IN THE FUTURE @@ -44,32 +58,48 @@ def __init__(self, sequence, fps, with_mask=True, ismask=False): fromfiles = True if isinstance(sequence, list): - if not isinstance(sequence[0], str): - # sequence is a list of numpy arrays + if isinstance(sequence[0], str): + if load_images: + sequence = [ffmpeg_read_image( f, with_mask=True) + for f in sequence] + fromfiles = False + else: + fromfiles= True + else: + # sequence is already a list of numpy arrays fromfiles = False else: - # sequence is a folder name + # sequence is a folder name, make it a list of files: + fromfiles = True sequence = sorted([os.path.join(sequence, f) for f in os.listdir(sequence)]) self.fps = fps - self.duration = 1.0* len(sequence) / self.fps + if fps is not none: + durations = [1.0/fps for image in sequence] + self.durations = durations + self.images_starts = [0]+list(np.cumsum(durations)) + self.duration = sum(durations) self.end = self.duration self.sequence = sequence + + def find_image_index(t): + return max([i for i in range(len(self.sequence)) + if self.images_starts[i]<=t]) if fromfiles: - self.lastpos = None + self.lastindex = None self.lastimage = None def get_frame(t): - pos = int(self.fps*t) - if pos != self.lastpos: - self.lastimage = ffmpeg_read_image( - self.sequence[pos], - with_mask=False) - self.lastpos = pos + index = find_image_index(t) + + if index != self.lastindex: + self.lastimage = ffmpeg_read_image( self.sequence[index], + with_mask=False) + self.lastindex = index return self.lastimage @@ -79,12 +109,11 @@ def get_frame(t): def mask_get_frame(t): - pos = int(self.fps*t) - if pos != self.lastpos: - self.mask.lastimage = ffmpeg_read_image( - self.sequence[pos], - with_mask=True)[:,:,3] - self.mask.lastpos = pos + index = find_image_index(t) + if index != self.lastindex: + self.mask.lastimage = ffmpeg_read_image( self.sequence[index], + with_mask=True)[:,:,3] + self.mask.lastindex = index return self.mask.lastimage @@ -96,8 +125,8 @@ def mask_get_frame(t): def get_frame(t): - pos = int(self.fps*t) - return self.sequence[pos] + index = find_image_index(t) + return self.sequence[index] self.get_frame = get_frame diff --git a/moviepy/video/io/ffmpeg_reader.py b/moviepy/video/io/ffmpeg_reader.py index 3f830f11d..964bde2ca 100644 --- a/moviepy/video/io/ffmpeg_reader.py +++ b/moviepy/video/io/ffmpeg_reader.py @@ -265,12 +265,18 @@ def ffmpeg_parse_infos(filename, print_infos=False, check_duration=True): if result['video_found']: - line = lines_video[0] - # get the size, of the form 460x320 (w x h) - match = re.search(" [0-9]*x[0-9]*(,| )", line) - s = list(map(int, line[match.start():match.end()-1].split('x'))) - result['video_size'] = s + try: + line = lines_video[0] + + # get the size, of the form 460x320 (w x h) + match = re.search(" [0-9]*x[0-9]*(,| )", line) + s = list(map(int, line[match.start():match.end()-1].split('x'))) + result['video_size'] = s + except: + raise (("MoviePy error: failed to read video dimensions in file %s.\n" + "Here are the file infos returned by ffmpeg:\n\n%s")%( + filename, infos)) # get the frame rate. Sometimes it's 'tbr', sometimes 'fps', sometimes diff --git a/moviepy/video/io/ffmpeg_writer.py b/moviepy/video/io/ffmpeg_writer.py index a7a1c3992..5156a3466 100644 --- a/moviepy/video/io/ffmpeg_writer.py +++ b/moviepy/video/io/ffmpeg_writer.py @@ -127,7 +127,7 @@ def __init__(self, filename, size, fps, codec="libx264", audiofile=None, # when the child process is created if os.name == "nt": popen_params["creationflags"] = 0x08000000 - + self.proc = sp.Popen(cmd, **popen_params) diff --git a/moviepy/video/io/gif_writers.py b/moviepy/video/io/gif_writers.py index f2dc0120e..4bc3d0580 100644 --- a/moviepy/video/io/gif_writers.py +++ b/moviepy/video/io/gif_writers.py @@ -2,7 +2,7 @@ import subprocess as sp from tqdm import tqdm from moviepy.config import get_setting -from moviepy.decorators import requires_duration +from moviepy.decorators import (requires_duration,use_clip_fps_by_default) from moviepy.tools import verbose_print, subprocess_call import numpy as np @@ -13,6 +13,7 @@ @requires_duration +@use_clip_fps_by_default def write_gif_with_tempfiles(clip, filename, fps=None, program= 'ImageMagick', opt="OptimizeTransparency", fuzz=1, verbose=True, loop=0, dispose=False, colors=None, tempfiles=False): @@ -26,9 +27,6 @@ def write_gif_with_tempfiles(clip, filename, fps=None, program= 'ImageMagick', """ - if fps is None: - fps = clip.fps - fileName, fileExtension = os.path.splitext(filename) tt = np.arange(0,clip.duration, 1.0/fps) @@ -93,6 +91,7 @@ def write_gif_with_tempfiles(clip, filename, fps=None, program= 'ImageMagick', @requires_duration +@use_clip_fps_by_default def write_gif(clip, filename, fps=None, program= 'ImageMagick', opt="OptimizeTransparency", fuzz=1, verbose=True, loop=0, dispose=False, colors=None): @@ -153,8 +152,6 @@ def write_gif(clip, filename, fps=None, program= 'ImageMagick', # frames -ffmpeg-> bmp frames -ImagMag-> gif -ImagMag-> better gif # - if fps is None: - fps=clip.fps delay= 100.0/fps cmd1 = [get_setting("FFMPEG_BINARY"), '-y', '-loglevel', 'error', diff --git a/moviepy/video/tools/cuts.py b/moviepy/video/tools/cuts.py index 37cd92b1a..97f3ff7f6 100644 --- a/moviepy/video/tools/cuts.py +++ b/moviepy/video/tools/cuts.py @@ -1,13 +1,15 @@ """ This module contains everything that can help automatize the cuts in MoviePy """ -import numpy as np +from moviepy.decorators import use_clip_fps_by_default +import numpy as np +@use_clip_fps_by_default def find_video_period(clip,fps=None,tmin=.3): """ Finds the period of a video based on frames correlation """ - if fps is None: - fps=clip.fps + + frame = lambda t: clip.get_frame(t).flatten() tt = np.arange(tmin,clip.duration,1.0/ fps)[1:] ref = frame(0) @@ -15,7 +17,7 @@ def find_video_period(clip,fps=None,tmin=.3): return tt[np.argmax(corrs)] - +@use_clip_fps_by_default def detect_scenes(clip=None, luminosities=None, thr=10, progress_bar=False, fps=None): @@ -61,9 +63,6 @@ def detect_scenes(clip=None, luminosities=None, thr=10, """ - - if fps is None: - fps = clip.fps if luminosities is None: luminosities = [f.sum() for f in clip.iter_frames( diff --git a/moviepy/video/tools/interpolators.py b/moviepy/video/tools/interpolators.py index 482fb628f..f5cd030eb 100644 --- a/moviepy/video/tools/interpolators.py +++ b/moviepy/video/tools/interpolators.py @@ -15,19 +15,13 @@ def __init__(self, tt=None, ss=None, ttss = None, left=None, right=None): self.tt = 1.0*np.array(tt) self.ss = 1.0*np.array(ss) - self.left = ss[0] if (left is None) else left - self.right = ss[-1] if (right is None) else right + self.left = left + self.right = right self.tmin, self.tmax = min(tt), max(tt) def __call__(self, t): - if (t <= self.tmin): - return self.left - if (t >= self.tmax): - return self.right - ind = np.argmax(np.diff(self.tt >= t)==1) - t1, t2 = self.tt[ind], self.tt[ind+1] - s1, s2 = self.ss[ind], self.ss[ind+1] - return s1 + (s2-s1)*(t-t1)/(t2-t1) + + return np.interp(t, self.tt, self.ss, self.left, self.right) class Trajectory: diff --git a/moviepy/video/tools/tracking.py b/moviepy/video/tools/tracking.py index ec9fdf1ca..2d01a38f7 100644 --- a/moviepy/video/tools/tracking.py +++ b/moviepy/video/tools/tracking.py @@ -12,7 +12,7 @@ from ..io.preview import imdisplay from .interpolators import Trajectory -from moviepy.decorators import convert_to_seconds +from moviepy.decorators import (convert_to_seconds, use_clip_fps_by_default) try: @@ -29,6 +29,7 @@ # MANUAL TRACKING @convert_to_seconds(["t1","t2"]) +@use_clip_fps_by_default def manual_tracking(clip, t1=None, t2=None, fps=None, nobjects = 1, savefile = None): """ @@ -77,9 +78,6 @@ def manual_tracking(clip, t1=None, t2=None, fps=None, nobjects = 1, """ import pygame as pg - - if fps is None: - fps = clip.fps screen = pg.display.set_mode(clip.size) step = 1.0 / fps