From ad5a7a8e0c9dfa306642258abd28177b5a7343ec Mon Sep 17 00:00:00 2001 From: Zulko Date: Tue, 25 Feb 2014 08:00:01 +0100 Subject: [PATCH] made 'outplace' decorator (bad idea ?). Fixed 'is_playing'. Put most of the audio reading engine in FFMPEGAudioReader --- examples/example_with_sound.py | 37 ++-- examples/star_worms.py | 6 +- moviepy/Clip.py | 89 +++++---- moviepy/audio/AudioClip.py | 42 +++-- moviepy/audio/io/AudioFileClip.py | 85 +-------- moviepy/audio/io/ffmpeg_audiowriter.py | 5 +- moviepy/audio/io/preview.py | 3 +- moviepy/audio/io/readers.py | 126 +++++++++---- moviepy/decorators.py | 23 ++- moviepy/tools.py | 10 +- moviepy/version.py | 2 +- moviepy/video/VideoClip.py | 169 +++++------------- .../video/compositing/CompositeVideoClip.py | 1 + moviepy/video/io/DirectoryClip.py | 92 +++------- moviepy/video/io/VideoFileClip.py | 31 +--- moviepy/video/io/ffmpeg_reader.py | 1 - setup.py | 2 +- 17 files changed, 297 insertions(+), 427 deletions(-) diff --git a/examples/example_with_sound.py b/examples/example_with_sound.py index 2b364fe01..87231b610 100644 --- a/examples/example_with_sound.py +++ b/examples/example_with_sound.py @@ -14,31 +14,40 @@ # LOAD THE MAIN SCENE # this small video contains the two scenes that we will put together. -main_clip = VideoFileClip("../../charadePhone.mp4") +main_clip = VideoFileClip("../../videos/charadePhone.mp4") W,H = main_clip.size # MAKE THE LEFT CLIP : cut, crop, add a mask - -clip_left = main_clip.coreader().subclip(0,duration).\ - crop( x1=60, x2=60 + 2*W/3) -mask = color_split((2*W/3,H), p1=(W/3,H), p2=(2*W/3,0), - col1=1, col2=0, grad_width=2) +mask = color_split((2*W/3,H), + p1=(W/3,H), p2=(2*W/3,0), + col1=1, col2=0, + grad_width=2) + +mask_clip = ImageClip(mask, ismask=True) -clip_left.mask = ImageClip(mask, ismask=True) +clip_left = (main_clip.coreader() + .subclip(0,duration) + .crop( x1=60, x2=60 + 2*W/3) + .set_mask(mask_clip)) # MAKE THE RIGHT CLIP : cut, crop, add a mask - -clip_right = main_clip.coreader().subclip(21,21+duration).\ - crop(x1=70, x2=70+2*W/3) - -mask = color_split((2*W/3,H), p1=(2,H), p2=(W/3+2,0), - col1=0, col2=1, grad_width=2) -clip_right.mask = ImageClip(mask, ismask=True) +mask = color_split((2*W/3,H), + p1=(2,H), p2=(W/3+2,0), + col1=0, col2=1, + grad_width=2) + +mask_clip = ImageClip(mask, ismask=True) + +clip_right = (main_clip.coreader() + .subclip(21,21+duration) + .crop(x1=70, x2=70+2*W/3) + .set_mask(mask_clip)) + diff --git a/examples/star_worms.py b/examples/star_worms.py index 3d6886382..b4bf97193 100644 --- a/examples/star_worms.py +++ b/examples/star_worms.py @@ -87,7 +87,7 @@ def trapzWarp(pic,cx,cy,ismask=False): # BACKGROUND IMAGE, DARKENED AT 60% -stars = ImageClip('../../starworms/stars.jpg') +stars = ImageClip('../../videos/stars.jpg') stars_darkened = stars.fl_image(lambda pic: (0.6*pic).astype('int16')) @@ -100,7 +100,7 @@ def trapzWarp(pic,cx,cy,ismask=False): # WRITE TO A FILE -final.set_duration(8).to_videofile("starworms.avi") +final.set_duration(8).to_videofile("starworms.avi", fps=5) # This script is heavy (30s of computations to render 8s of video) @@ -175,5 +175,5 @@ def composeCenter(clip): # Concatenate and write to a file -concatenate(annotated_clips).to_videofile('tutorial.avi') +concatenate(annotated_clips).to_videofile('tutorial.avi', fps=5) diff --git a/moviepy/Clip.py b/moviepy/Clip.py index 5a4ab208a..18995e23c 100644 --- a/moviepy/Clip.py +++ b/moviepy/Clip.py @@ -7,8 +7,10 @@ from copy import copy import numpy as np -from moviepy.decorators import ( apply_to_mask, apply_to_audio, - time_can_be_tuple) +from moviepy.decorators import ( apply_to_mask, + apply_to_audio, + time_can_be_tuple, + outplace) class Clip: @@ -48,7 +50,6 @@ def __init__(self): self.duration = None - def copy(self): """ Shallow copy of the clip. This method is intensively used @@ -111,7 +112,8 @@ def fl(self, fun, apply_to=[] , keep_duration=True): if hasattr(newclip, attr): a = getattr(newclip, attr) if a != None: - setattr(newclip, attr, a.fl(fl)) + new_a = a.fl(fun, keep_duration=keep_duration) + setattr(newclip, attr, new_a) return newclip @@ -183,6 +185,7 @@ def fx(self, func, *args, **kwargs): @apply_to_mask @apply_to_audio @time_can_be_tuple + @outplace def set_start(self, t, change_end=True): """ Returns a copy of the clip, with the ``start`` attribute set @@ -199,41 +202,38 @@ def set_start(self, t, change_end=True): These changes are also applied to the ``audio`` and ``mask`` clips of the current clip, if they exist. """ - newclip = self.copy() - newclip.start = t - if (newclip.duration != None) and change_end: - newclip.end = t + newclip.duration - elif (newclip.end !=None): - newclip.duration = newclip.end - newclip.start - - return newclip + + self.start = t + if (self.duration != None) and change_end: + self.end = t + self.duration + elif (self.end !=None): + self.duration = self.end - self.start @apply_to_mask @apply_to_audio @time_can_be_tuple + @outplace def set_end(self, t): """ Returns a copy of the clip, with the ``end`` attribute set to ``t``. Also sets the duration of the mask and audio, if any, of the returned clip. """ - newclip = self.copy() - newclip.end = t - if newclip.start is None: - if newclip.duration != None: - newclip.start = max(0, t - newclip.duration) + self.end = t + if self.start is None: + if self.duration != None: + self.start = max(0, t - newclip.duration) else: - newclip.duration = newclip.end - newclip.start - - return newclip + self.duration = self.end - self.start @apply_to_mask @apply_to_audio @time_can_be_tuple + @outplace def set_duration(self, t, change_end=True): """ Returns a copy of the clip, with the ``duration`` attribute @@ -241,41 +241,54 @@ def set_duration(self, t, change_end=True): Also sets the duration of the mask and audio, if any, of the returned clip. """ - newclip = copy(self) - newclip.duration = t + self.duration = t if change_end: - newclip.end = newclip.start + t + self.end = self.start + t else: - newclip.start = newclip.end - t - - - return newclip + self.start = self.end - t - + @outplace def set_get_frame(self, gf): """ Sets a ``get_frame`` attribute for the clip. Useful for setting arbitrary/complicated videoclips. """ - - newclip = copy(self) - newclip.get_frame = gf - return newclip + self.get_frame = gf @time_can_be_tuple def is_playing(self, t): """ - Return true if t is between the start and the end of the clip. + + If t is a number, returns true if t is between the start and the + end of the clip. + If t is a numpy array, returns False if none of the t is in the + clip, else returns a vector [b_1, b_2, b_3...] where b_i is true + iff tti is in the clip. """ + if isinstance(t, np.ndarray): - t = t.max() - return (((self.end is None) and (t >= self.start)) or - (self.start <= t <= self.end)) - - + # is the whole list of t outside the clip ? + tmin, tmax = t.min(), t.max() + + if (self.end != None) and (tmin > self.end) : + return False + + if tmax < self.start: + return False + + # If we arrive here, a part of t falls in the clip + result = 1 * (t >= self.start) + if (self.end != None): + result *= (t <= self.end) + return result + + else: + + return( (t >= self.start) and + ((self.end is None) or (t <= self.end) ) ) @time_can_be_tuple @apply_to_mask diff --git a/moviepy/audio/AudioClip.py b/moviepy/audio/AudioClip.py index 70ad95df4..90ffa7d8e 100644 --- a/moviepy/audio/AudioClip.py +++ b/moviepy/audio/AudioClip.py @@ -47,8 +47,15 @@ class AudioClip(Clip): """ - def __init__(self): + def __init__(self, get_frame = None): Clip.__init__(self) + if get_frame: + self.get_frame = get_frame + frame0 = self.get_frame(0) + if hasattr(frame0, '__iter__'): + self.nchannels = len(list(frame0)) + else: + self.nchannels = 1 @requires_duration def to_soundarray(self,tt=None,fps=None, nbytes=2): @@ -72,19 +79,24 @@ def to_soundarray(self,tt=None,fps=None, nbytes=2): tt = np.arange(0,self.duration, 1.0/fps) snd_array = self.get_frame(tt) - snd_array = np.maximum(-0.999, np.minimum(0.999,snd_array)) + snd_array = np.maximum(-0.99, + np.minimum(0.99,snd_array)) inttype = {1:'int8',2:'int16',4:'int32'}[nbytes] return (2**(8*nbytes-1)*snd_array).astype(inttype) - - @property - def nchannels(self): - return len(list(self.get_frame(0))) + @requires_duration def to_audiofile(self,filename, fps=44100, nbytes=2, - buffersize=5000, codec='libvorbis', + buffersize=2000, codec='libvorbis', bitrate=None, verbose=True): + """ + codecs = { 'libmp3lame': 'mp3', + 'libvorbis':'ogg', + 'libfdk_aac':'m4a', + 'pcm_s16le':'wav', + 'pcm_s32le': 'wav'} + """ return ffmpeg_audiowrite(self,filename, fps, nbytes, buffersize, codec, bitrate, verbose) @@ -142,6 +154,7 @@ def get_frame(t): return self.array[i] self.get_frame = get_frame + self.nchannels = len(list(self.get_frame(0))) class CompositeAudioClip(AudioClip): @@ -166,19 +179,26 @@ def __init__(self, clips): self.clips = clips ends = [c.end for c in self.clips] + self.nchannels = max([c.nchannels for c in self.clips]) if not any([(e is None) for e in ends]): self.duration = max(ends) + self.end = max(ends) def get_frame(t): + # buggy + + + played_parts = [c.is_playing(t) for c in self.clips] - sounds= [c.get_frame(t - c.start) - for c in clips if c.is_playing(t)] + sounds= [c.get_frame(t - c.start)*np.array([part]).T + for c,part in zip(self.clips, played_parts) + if (part is not False) ] if isinstance(t,np.ndarray): - zero = np.zeros((len(t),2)) + zero = np.zeros((len(t),self.nchannels)) else: - zero = np.zeros(2) + zero = np.zeros(self.nchannels) return zero + sum(sounds) diff --git a/moviepy/audio/io/AudioFileClip.py b/moviepy/audio/io/AudioFileClip.py index e838057ef..ad3718c5e 100644 --- a/moviepy/audio/io/AudioFileClip.py +++ b/moviepy/audio/io/AudioFileClip.py @@ -60,92 +60,15 @@ def __init__(self, filename, buffersize=200000, nbytes=2, fps=44100): self.filename = filename self.reader = FFMPEG_AudioReader(filename,fps=fps,nbytes=nbytes, - bufsize=buffersize+100) + buffersize=buffersize) self.fps = fps self.duration = self.reader.duration self.end = self.duration self.nframes = self.reader.nframes - self.buffersize= buffersize - self.buffer= None - self._fstart_buffer = 1 - self._buffer_around(1) - - def gf(t): - bufsize = self.buffersize - if isinstance(t,np.ndarray): - # lazy implementation, but should not cause problems in - # 99.99 % of the cases - result = np.zeros((len(t),2)) - in_time = (t>=0) & (t < self.duration) - inds = (self.fps*t+1).astype(int)[in_time] - f_tmin, f_tmax = inds.min(), inds.max() - - if not (0 <= (f_tmin - self._fstart_buffer) < len(self.buffer)): - self._buffer_around(f_tmin) - elif not (0 <= (f_tmax - self._fstart_buffer) < len(self.buffer)): - self._buffer_around(f_tmax) - - try: - tup = in_time.nonzero() - inds2 = inds - self._fstart_buffer - result[in_time] = self.buffer[inds - self._fstart_buffer] - return result - except IndexError as error: - print ("Error: wrong indices in video buffer. Maybe"+ - " buffer too small.") - raise error - - else: - ind = int(self.fps*t) - if ind<0 or ind> self.nframes: # out of time: return 0 - return np.zeros(self.nchannels) - - if not (0 <= (ind - self._fstart_buffer) =0) & (tt < self.duration) + + frames = (self.fps*tt+1).astype(int)[in_time] + fr_min, fr_max = frames.min(), frames.max() + + if not (0 <= + (fr_min - self.buffer_startframe) + < len(self.buffer)): + self.buffer_around(fr_min) + elif not (0 <= + (fr_max - self.buffer_startframe) + < len(self.buffer)): + self.buffer_around(fr_max) + + try: + result = np.zeros((len(tt),self.nchannels)) + result[in_time] = self.buffer[frames - self.buffer_startframe] + return result + except IndexError as error: + print ("Error: wrong indices in video buffer. Maybe"+ + " buffer too small.") + raise error + + else: + + ind = int(self.fps*tt) + if ind<0 or ind> self.nframes: # out of time: return 0 + return np.zeros(self.nchannels) + + if not (0 <= (ind - self.buffer_startframe) audioclip) can be also used on a video clip, at which case it returns a - videoclip with modified audio. + videoclip with unmodified video and modified audio. """ if hasattr(clip, "audio"): @@ -66,9 +74,8 @@ def audio_video_fx(f, clip, *a, **k): @decorator.decorator def time_can_be_tuple(f, clip, *a, **k): """ - This decorator tells that the function f (audioclip -> audioclip) - can be also used on a video clip, at which case it returns a - videoclip with modified audio. + All tuples in the arguments of f will be considered as time and + converted to seconds. """ fun = lambda e: e if (not isinstance(e,tuple)) else cvsecs(*e) diff --git a/moviepy/tools.py b/moviepy/tools.py index 32795e443..3f4b959fb 100644 --- a/moviepy/tools.py +++ b/moviepy/tools.py @@ -17,7 +17,8 @@ def subprocess_call(cmd, stdout=None, stdin = None, executes the subprocess command """ - verboseprint = sys_write_flush if verbose else (lambda *a : None) + def verboseprint(s): + if verbose: sys_write_flush(s) verboseprint( "\nMoviePy Running:\n>>> "+ " ".join(cmd) ) @@ -26,10 +27,11 @@ def subprocess_call(cmd, stdout=None, stdin = None, stderr = stderr ) proc.wait() - if proc.returncode and errorprint: - sys_write_flush( "\nMoviePy: WARNING !\n" + if proc.returncode: + if errorprint: + sys_write_flush( "\nMoviePy: WARNING !\n" " The following command returned an error:\n") - sys_write_flush( proc.stderr.read().decode('utf8')) + sys_write_flush( proc.stderr.read().decode('utf8')) raise IOError else: verboseprint( "\n... command successful.\n") diff --git a/moviepy/version.py b/moviepy/version.py index 0370c18e2..c9ae20bb1 100644 --- a/moviepy/version.py +++ b/moviepy/version.py @@ -1 +1 @@ -__version__ = '0.2.1.7.07' +__version__ = '0.2.1.7.08' diff --git a/moviepy/video/VideoClip.py b/moviepy/video/VideoClip.py index e97cedda2..50ad3ae35 100644 --- a/moviepy/video/VideoClip.py +++ b/moviepy/video/VideoClip.py @@ -15,7 +15,9 @@ import numpy as np from moviepy.decorators import (apply_to_mask, - requires_duration) + requires_duration, + outplace, + add_mask_if_none) from moviepy.tools import subprocess_call, sys_write_flush @@ -29,8 +31,6 @@ from ..conf import FFMPEG_BINARY - - class VideoClip(Clip): """Base class for video clips. @@ -89,8 +89,6 @@ def __init__(self, ismask=False, get_frame=None): self.size =get_frame(0).shape[:2][::-1] self.ismask = ismask - - @property def w(self): return self.size[0] @@ -116,69 +114,12 @@ def save_frame(self, filename, t=0, savemask=False): mask = 255 * self.mask.get_frame(t) im = np.dstack([im, mask]).astype('uint8') ffmpeg_write_image(filename, im) - - - - @requires_duration - def to_directory(self, foldername, fps, transparent=True, - overwrite=True, startFrame=0): - """ Write the frames of the clip into a folder. - - Writes the frames of the clip into a folder as png format, - and returns the :class:DirectoryClip associated with this - folder - - Parameters - ---------- - - foldername - Name of the folder (what did you expect ?) - - fps - Number of frames per second of movie. - - transparent - If `True`, the png images generated will possess an alpha - layer representing the mask of the clip. - - overwrite - If `True`, will overwrite the content of `folder` - if already existing. - - startFrame: - Useless for the moment, but may come in handy once - parallelization is fully implemented. - - """ - print( "writing frames in [%s]" % foldername ) - - try: - os.mkdir(foldername) - except: - if not overwrite: - print( "Error: Maybe set overwrite =true ") - - tt = np.arange(0, self.duration, 1.0 / fps) - tt_feedback = tt[::len(tt) / 10] - - for i, t in tqdm(list(enumerate(tt))[startFrame:]): - - picname = "%s/%s%06d.png" % (foldername, foldername, i) - pic = self.get_frame(t) - if transparent and (self.mask is not None): - fmask = self.mask.get_frame(t) - pic = np.dstack([pic, 255 * fmask]).astype('uint8') - imsave(picname, pic) - - print( "done." ) - return DirectoryClip(foldername, fps=fps) - - + def to_videofile(self, filename, fps=24, codec='libx264', bitrate=None, audio=True, audio_fps=44100, audio_nbytes = 2, audio_codec= 'libvorbis', - audio_bitrate = None, audio_bufsize = 40000, + audio_bitrate = None, audio_bufsize = 2000, temp_audiofile=None, rewrite_audio = True, remove_temp = True, para = False, verbose = True): @@ -322,7 +263,6 @@ def verbose_print(s): filename, ffmpeg_output=True) if remove_temp: - print("print") os.remove(videofile) if not isinstance(audio,str): os.remove(temp_audiofile) @@ -596,60 +536,51 @@ def on_color(self, size=None, color=(0, 0, 0), pos=None, return result - + @outplace def set_get_frame(self, gf): """ Change the clip's ``get_frame``. Returns a copy of the VideoClip instance, with the get_frame attribute set to `gf`. """ - newclip = Clip.set_get_frame(self, gf) - newclip.size = newclip.get_frame(0).shape[:2][::-1] - return newclip - + self.get_frame = gf + self.size = gf(0).shape[:2][::-1] + @outplace def set_audio(self, audioclip): """ Attach an AudioClip to the VideoClip. Returns a copy of the VideoClip instance, with the `audio` attribute set to ``audio``, hich must be an AudioClip instance. """ - newclip = copy(self) - newclip.audio = audioclip - return newclip - + self.audio = audioclip + @outplace def set_mask(self, mask): """ Set the clip's mask. Returns a copy of the VideoClip with the mask attribute set to ``mask``, which must be a greyscale (values in 0-1) VideoClip""" - assert mask.ismask - newclip = copy(self) - newclip.mask = mask - return newclip - - + assert ( (mask is None) or mask.ismask ) + self.mask = mask + @outplace + @add_mask_if_none def set_opacity(self, op): """ Set the opacity/transparency level of the clip. Returns a semi-transparent copy of the clip where the mask is multiplied by ``op`` (any float, normally between 0 and 1). """ - newclip = copy(self) - if not newclip.mask: - newclip = newclip.add_mask() - - newclip.mask = newclip.mask.fl_image(lambda pic: op * pic) - return newclip + self.mask = self.mask.fl_image(lambda pic: op * pic) @apply_to_mask + @outplace def set_pos(self, pos, relative=False): """ Set the clip's position in compositions. @@ -674,15 +605,13 @@ def set_pos(self, pos, relative=False): >>> clip.set_pos(lambda t: ('center', 50+t) ) """ - - newclip = copy(self) - newclip.relative_pos = relative + + self.relative_pos = relative if hasattr(pos, '__call__'): - newclip.pos = pos + self.pos = pos else: - newclip.pos = lambda t: pos - - return newclip + self.pos = lambda t: pos + #-------------------------------------------------------------- # CONVERSIONS @@ -716,9 +645,8 @@ def to_RGB(self): Return a non-mask video clip made from the mask video clip. """ if self.ismask: - newclip = self.fl_image( - lambda pic: np.dstack(3 * [255 * pic] - ).astype('uint8')) + f = lambda pic: np.dstack(3 * [255 * pic]).astype('uint8') + newclip = self.fl_image( f ) newclip.ismask = False return newclip else: @@ -729,28 +657,24 @@ def to_RGB(self): # Audio - + @outplace def without_audio(self): """ Remove the clip's audio. Return a copy of the clip with audio set to None. """ - newclip = copy(self) - newclip.audio = None - return newclip - + self.audio = None + @outplace def afx(self, fun, *a, **k): """ Transform the clip's audio. Return a new clip whose audio has been transformed by ``fun``. """ - newclip = self.copy() - newclip.audio = newclip.audio.fx(fun, *a, **k) - return newclip + self.audio = self.audio.fx(fun, *a, **k) #----------------------------------------------------------------- # Previews: @@ -850,11 +774,12 @@ def __init__(self, img, ismask=False, transparent=True, def fl(self, fl, apply_to=[], keep_duration=True): - """ General filter + """ General transformation filter. Equivalent to VideoClip.fl . The result is no more an ImageClip, it has the class VideoClip (since it may be animated) """ + # When we use fl on an image clip it may become animated. #Therefore the result is not an ImageClip, just a VideoClip. newclip = VideoClip.fl(self,fl, apply_to=apply_to, @@ -863,7 +788,7 @@ def fl(self, fl, apply_to=[], keep_duration=True): return newclip - + @outplace def fl_image(self, image_func, apply_to= []): """ Image-transformation filter. @@ -872,23 +797,21 @@ def fl_image(self, image_func, apply_to= []): and not for each 'frame'. """ - newclip = self.copy() arr = image_func(self.get_frame(0)) - newclip.size = arr.shape[:2][::-1] - newclip.get_frame = lambda t: arr - newclip.img = arr + self.size = arr.shape[:2][::-1] + self.get_frame = lambda t: arr + self.img = arr for attr in apply_to: - if hasattr(newclip, attr): - a = getattr(newclip, attr) + if hasattr(self, attr): + a = getattr(self, attr) if a != None: - setattr(newclip, attr, a.fl_image(image_func)) - - return newclip + new_a = a.fl(image_func) + setattr(newclip, attr, new_a) - - def fl_time(self, timefun, apply_to =['mask', 'audio']): + @outplace + def fl_time(self, time_func, apply_to =['mask', 'audio']): """ Time-transformation filter. Applies a transformation to the clip's timeline @@ -897,15 +820,13 @@ def fl_time(self, timefun, apply_to =['mask', 'audio']): This method does nothing for ImageClips (but it may affect their masks of their audios). The result is still an ImageClip. """ - newclip = self.copy() for attr in apply_to: - if hasattr(newclip, attr): - a = getattr(newclip, attr) + if hasattr(self, attr): + a = getattr(self, attr) if a != None: - setattr(newclip, attr, a.fl_image(image_func)) - - return newclip + new_a = a.fl_image(time_func) + setattr(self, attr, new_a) @@ -1045,7 +966,7 @@ def __init__(self, txt, size=None, color='black', if print_cmd: print( " ".join(cmd) ) - subprocess_call(cmd) + subprocess_call(cmd, verbose=False ) ImageClip.__init__(self, tempfile, transparent=transparent) self.txt = txt diff --git a/moviepy/video/compositing/CompositeVideoClip.py b/moviepy/video/compositing/CompositeVideoClip.py index 2a314fdf5..26c9c66a4 100644 --- a/moviepy/video/compositing/CompositeVideoClip.py +++ b/moviepy/video/compositing/CompositeVideoClip.py @@ -58,6 +58,7 @@ def __init__(self, clips, size=None, bg_color=None, transparent=False, ends = [c.end for c in self.clips] if not any([(e is None) for e in ends]): self.duration = max(ends) + self.end = max(ends) # compute audio audioclips = [v.audio for v in self.clips if v.audio != None] diff --git a/moviepy/video/io/DirectoryClip.py b/moviepy/video/io/DirectoryClip.py index 547af61a1..4290451e3 100644 --- a/moviepy/video/io/DirectoryClip.py +++ b/moviepy/video/io/DirectoryClip.py @@ -1,90 +1,38 @@ from moviepy.video.VideoClip import VideoClip +from .ffmpeg_reader import ffmpeg_read_image class DirectoryClip(VideoClip): """ A VideoClip read from a directory containing pictures. - Still experimental and subject to changes. - - DEPRECATED - needs update """ - def __init__(self, foldername, fps, transparent=True, ismask=False): + def __init__(self, foldername, fps, withmask=True, ismask=False): VideoClip.__init__(self, ismask=ismask) self.directory = foldername self.fps = fps - allfiles = os.listdir(foldername) - self.pics = sorted(["%s/%s" % (foldername, f) for f in allfiles - if not f.endswith(('.txt','.wav'))]) + self.imagefiles = sorted(os.listdir(foldername)) - audio = [f for f in allfiles if f.endswith('.wav')] + self.duration = 1.0* len(self.imagefiles) / self.fps + self.end = self.duration - if len(audio) > 0: - self.audio = AudioFileClip(audio[0]) - self.audiofile =audio[0] - - self.size = imread(self.pics[0]).shape[:2][::-1] - - if imread(self.pics[0]).shape[2] == 4: # transparent png - - if ismask: - def get_frame(t): - return 1.0 * imread(self.pics[int(self.fps * t)])[:, :, 3] / 255 - else: - def get_frame(t): - return imread(self.pics[int(self.fps * t)])[:, :, :2] - - if transparent: - self.mask = DirectoryClip(foldername, fps, ismask=True) - - else: - - def get_frame(t): - return imread(self.pics[int(self.fps * t)]) - - self.get_frame = get_frame - self.duration = 1.0 * len(self.pics) / self.fps - - def to_videofile(self, filename, bitrate=3000, audio=True): - """ - Transforms the directory clip into a movie using ffmpeg. - Uses the framerate specified by ``clip.fps``. + self.lastpos = None + self.lastimage = None - :param filename: name of the video file to write in, like - 'myMovie.ogv' or 'myFilm.mp4'. - :param bitrate: final bitrate of the video file (in kilobytes/s). - 3000-6000 gives generally light files and an acceptable quality. - :param audio: the name of an audiofile to be incorporated in the - the movie. - """ - - if audio != None: - # if there is audio, then ``videofile`` is a temporary file - # that will then be merged with audio and removed. - name, ext = os.path.splitext(os.path.basename(filename)) - videofile = Clip._TEMP_FILES_PREFIX + name + ext - else: - videofile = filename + + + def get_frame(t): - cmd = ["ffmpeg", "-y", "-f", "image2", - "-r","%d"%self.fps, - "-i", os.path.join(self.directory,self.directory,"%06d.png"), - "-b", "%dk"%bitrate, - "-r", "%d"%self.fps, videofile] - - print "running : "+ " ".join(cmd) - subprocess.call(cmd) - - if audio: - # Merge video and audio, and remove temporary files - subprocess.call(["ffmpeg", "-y", - "-i", self.audiofile + - "-i", videofile, - "-strict experimental", filename]) - - print "running: %s" % cmd - os.system(cmd) - os.remove(videofile) + pos = int(self.fps*t) + if pos != self.lastpos: + self.lastimage = ffmpeg_read_image(self.imagefiles[ind], + withmask=withmask) + self.lastpos = pos + + return self.lastimage + + self.get_frame = get_frame + self.size = get_frame(0).shape[:2][::-1] diff --git a/moviepy/video/io/VideoFileClip.py b/moviepy/video/io/VideoFileClip.py index 3711c4cd8..7eb93fce9 100644 --- a/moviepy/video/io/VideoFileClip.py +++ b/moviepy/video/io/VideoFileClip.py @@ -27,10 +27,10 @@ class VideoFileClip(VideoClip): has_mask: Set this to 'True' if there is a mask included in the videofile. - Video files rarely contain masks, but some video codecs enable - that. For istance if you have a MoviePy VideoClip with a mask you - can save it to a videofile with a mask. (see also - ``VideoClip.to_videofile`` for more details). + Video files rarely contain masks, but some video codecs enable + that. For istance if you have a MoviePy VideoClip with a mask you + can save it to a videofile with a mask. (see also + ``VideoClip.to_videofile`` for more details). audio: Set to `False` if the clip doesn't have any audio or if you do not @@ -53,13 +53,6 @@ def __init__(self, filename, ismask=False, has_mask=False, VideoClip.__init__(self, ismask) - # We store the construction parameters in case we need to make - # a copy (a 'co-reader'). - - self.parameters = {'filename':filename, 'ismask':ismask, - 'has_mask':has_mask, 'audio':audio, - 'audio_buffersize':audio_buffersize} - # Make a reader pix_fmt= "rgba" if has_mask else "rgb24" self.reader = FFMPEG_VideoReader(filename, pix_fmt=pix_fmt) @@ -78,19 +71,3 @@ def __init__(self, filename, ismask=False, has_mask=False, buffersize= audio_buffersize, fps = audio_fps, nbytes = audio_nbytes) - - def coreader(self, audio=True): - """ - Returns a copy of the AudioFileClip with an autonomous reader. - Maybe REMOVED in the coming versions. Wait and see. :: - - >>> clip = VideofileClip("myHolidays.mp4", ismask=True) - >>> clip2 = VideofileClip("myHolidays.mp4", ismask=True) - - is equivalent to :: - - >>> clip = VideofileClip("myHolidays.mp4", ismask=True) - >>> clip2 = clip.coreader() - """ - - return VideoFileClip(**self.parameters) diff --git a/moviepy/video/io/ffmpeg_reader.py b/moviepy/video/io/ffmpeg_reader.py index 5e715d59f..42cc97c7a 100644 --- a/moviepy/video/io/ffmpeg_reader.py +++ b/moviepy/video/io/ffmpeg_reader.py @@ -67,7 +67,6 @@ def load_infos(self, print_infos=False): # open the file in a pipe, provoke an error, read output proc = sp.Popen([FFMPEG_BINARY, "-i", self.filename, "-"], bufsize=10**6, - stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE) proc.stdout.readline() diff --git a/setup.py b/setup.py index 522eab948..e8613a00a 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup(name='moviepy', version=__version__, - author='Zulko 2013', + author='Zulko 2014', description='Module for script-based video editing', long_description=open('README.rst').read(), license='see LICENSE.txt',