diff --git a/moviepy/video/VideoClip.py b/moviepy/video/VideoClip.py index 5cd32dfa7..1c56bbebd 100644 --- a/moviepy/video/VideoClip.py +++ b/moviepy/video/VideoClip.py @@ -7,14 +7,11 @@ import os import subprocess as sp - import multiprocessing import tempfile from copy import copy from tqdm import tqdm - - import numpy as np import moviepy.audio.io as aio @@ -22,24 +19,19 @@ from .io.ffmpeg_reader import ffmpeg_read_image from .io.ffmpeg_tools import ffmpeg_merge_video_audio from .io.gif_writers import write_gif, write_gif_with_tempfiles - from .tools.drawing import blit - from ..Clip import Clip from ..conf import FFMPEG_BINARY, IMAGEMAGICK_BINARY - from ..tools import (subprocess_call, verbose_print, is_string, deprecated_version_of, - extensions_dict, find_extension) - -from ..decorators import (apply_to_mask, - requires_duration, - outplace, - add_mask_if_none, - convert_to_seconds) - + extensions_dict, find_extension) +from ..decorators import (apply_to_mask, + requires_duration, + outplace, + add_mask_if_none, + convert_to_seconds) class VideoClip(Clip): @@ -97,7 +89,7 @@ def __init__(self, make_frame=None, ismask=False, duration=None): self.relative_pos = False if make_frame is not None: self.make_frame = make_frame - self.size =self.get_frame(0).shape[:2][::-1] + self.size = self.get_frame(0).shape[:2][::-1] self.ismask = ismask if duration is not None: self.duration = duration @@ -108,13 +100,11 @@ def w(self): return self.size[0] - @property def h(self): return self.size[1] - # =============================================================== # EXPORT OPERATIONS @@ -126,7 +116,7 @@ def save_frame(self, filename, t=0, savemask=False): Saves the frame of clip corresponding to time ``t`` in 'filename'. ``t`` can be expressed in seconds (15.35), in (min, sec), in (hour, min, sec), or as a string: '01:03:05.35'. - + If ``savemask`` is ``True`` the mask is saved in the alpha layer of the picture. @@ -140,13 +130,14 @@ def save_frame(self, filename, t=0, savemask=False): @requires_duration def write_videofile(self, filename, fps=24, codec=None, - bitrate=None, audio=True, audio_fps=44100, - preset="medium", - audio_nbytes = 4, audio_codec= None, - audio_bitrate = None, audio_bufsize = 2000, - temp_audiofile=None, - rewrite_audio = True, remove_temp = True, - write_logfile=False, verbose = True): + bitrate=None, audio=True, audio_fps=44100, + preset="medium", + audio_nbytes=4, audio_codec=None, + audio_bitrate=None, audio_bufsize=2000, + temp_audiofile=None, + rewrite_audio=True, remove_temp=True, + write_logfile=False, verbose=True, + ffmpeg_params=None): """Write the clip to a videofile. Parameters @@ -177,7 +168,7 @@ def write_videofile(self, filename, fps=24, codec=None, to ``'libx264'``, and produces higher quality videos by default. - ``'rawvideo'`` (use file extension ``.avi``) will produce + ``'rawvideo'`` (use file extension ``.avi``) will produce a video of perfect quality, of possibly very huge size. @@ -213,7 +204,7 @@ def write_videofile(self, filename, fps=24, codec=None, for '.mp3', 'libvorbis' for 'ogg', 'libfdk_aac':'m4a', 'pcm_s16le' for 16-bit wav and 'pcm_s32le' for 32-bit wav. Default is 'libmp3lame', unless the video extension is 'ogv' - or 'webm', at which case the default is 'libvorbis'. + or 'webm', at which case the default is 'libvorbis'. audio_bitrate Audio bitrate, given as a string like '50k', '500k', '3000k'. @@ -248,13 +239,13 @@ def write_videofile(self, filename, fps=24, codec=None, ext = ext[1:].lower() if codec is None: - try: - codec = extensions_dict[ext]['codec'][0] - except KeyError: - raise ValueError("MoviePy couldn't find the codec associated " - "with the filename. Provide the 'codec' parameter in " - "write_fideofile.") - + try: + codec = extensions_dict[ext]['codec'][0] + except KeyError: + raise ValueError("MoviePy couldn't find the codec associated " + "with the filename. Provide the 'codec' parameter in " + "write_fideofile.") + if audio_codec is None: if (ext in ['ogv', 'webm']): audio_codec = 'libvorbis' @@ -267,8 +258,8 @@ def write_videofile(self, filename, fps=24, codec=None, audio_codec = 'pcm_s32le' audiofile = audio if is_string(audio) else None - make_audio = ((audiofile is None) and (audio==True) and - (self.audio is not None)) + make_audio = ((audiofile is None) and (audio == True) and + (self.audio is not None)) if make_audio: # The audio will be the clip's audio @@ -286,33 +277,34 @@ def write_videofile(self, filename, fps=24, codec=None, audio_ext = find_extension(audio_codec) except ValueError: - raise ValueError("The audio_codec you chose is unknown by MoviePy. " - "You should report this. In the meantime, you can specify a " - "temp_audiofile with the right extension in write_videofile.") + raise ValueError( + "The audio_codec you chose is unknown by MoviePy. " + "You should report this. In the meantime, you can specify a " + "temp_audiofile with the right extension in write_videofile.") - audiofile = (name+Clip._TEMP_FILES_PREFIX + - "write_videofile_SOUND.%s"%audio_ext) + audiofile = (name + Clip._TEMP_FILES_PREFIX + + "write_videofile_SOUND.%s" % audio_ext) # enough cpu for multiprocessing ? USELESS RIGHT NOW, WILL COME AGAIN - #enough_cpu = (multiprocessing.cpu_count() > 1) + # enough_cpu = (multiprocessing.cpu_count() > 1) - verbose_print(verbose, "\nMoviePy: building video file %s\n"%filename - +40*"-"+"\n") + verbose_print(verbose, "\nMoviePy: building video file %s\n" % filename + + 40 * "-" + "\n") - if make_audio: - self.audio.write_audiofile(audiofile,audio_fps, - audio_nbytes, audio_bufsize, - audio_codec, bitrate=audio_bitrate, - write_logfile=write_logfile, - verbose=verbose) - + self.audio.write_audiofile(audiofile, audio_fps, + audio_nbytes, audio_bufsize, + audio_codec, bitrate=audio_bitrate, + write_logfile=write_logfile, + verbose=verbose) + ffmpeg_write_video(self, filename, fps, codec, bitrate=bitrate, preset=preset, write_logfile=write_logfile, - audiofile = audiofile, - verbose=verbose) + audiofile=audiofile, + verbose=verbose, + ffmpeg_params=ffmpeg_params) if remove_temp and make_audio: os.remove(audiofile) @@ -320,7 +312,6 @@ def write_videofile(self, filename, fps=24, codec=None, verbose_print(verbose, "\nYour video is ready !\n") - @requires_duration def write_images_sequence(self, nameformat, fps=None, verbose=True): """ Writes the videoclip to a sequence of image files. @@ -358,30 +349,30 @@ def write_images_sequence(self, nameformat, fps=None, verbose=True): """ - verbose_print(verbose, "MoviePy: Writing frames %s."%(nameformat)) + verbose_print(verbose, "MoviePy: Writing frames %s." % (nameformat)) if fps is None: fps = self.fps - tt = np.arange(0, self.duration, 1.0/fps) + tt = np.arange(0, self.duration, 1.0 / fps) filenames = [] - total = int(self.duration/fps)+1 + total = int(self.duration / fps) + 1 for i, t in tqdm(enumerate(tt), total=total): - name = nameformat%(i+1) + name = nameformat % (i + 1) filenames.append(name) self.save_frame(name, t, savemask=True) - verbose_print(verbose, "MoviePy: Done writing frames %s."%(nameformat)) + verbose_print(verbose, + "MoviePy: Done writing frames %s." % (nameformat)) return filenames - @requires_duration - def write_gif(self, filename, fps=None, program= 'ImageMagick', - opt="OptimizeTransparency", fuzz=1, verbose=True, - loop=0, dispose=False, colors=None, tempfiles=False): + def write_gif(self, filename, fps=None, program='ImageMagick', + opt="OptimizeTransparency", fuzz=1, verbose=True, + loop=0, dispose=False, colors=None, tempfiles=False): """ Write the VideoClip to a GIF file. Converts a VideoClip into an animated GIF using ImageMagick @@ -424,21 +415,22 @@ def write_gif(self, filename, fps=None, program= 'ImageMagick', >>> myClip.speedx(0.5).to_gif('myClip.gif') """ - + if tempfiles: write_gif_with_tempfiles(self, filename, fps=fps, - program= program, opt=opt, fuzz=fuzz, verbose=verbose, - loop=loop, dispose= dispose, colors=colors) + program=program, opt=opt, fuzz=fuzz, + verbose=verbose, + loop=loop, dispose=dispose, colors=colors) else: - write_gif(self, filename, fps=fps, program= program, - opt=opt, fuzz=fuzz, verbose=verbose, loop=loop, - dispose= dispose, colors=colors) + write_gif(self, filename, fps=fps, program=program, + opt=opt, fuzz=fuzz, verbose=verbose, loop=loop, + dispose=dispose, colors=colors) - #----------------------------------------------------------------- + # ----------------------------------------------------------------- # F I L T E R I N G - + def subfx(self, fx, ta=0, tb=None, **kwargs): """ Apply a transformation to a part of the clip. @@ -455,9 +447,8 @@ def subfx(self, fx, ta=0, tb=None, **kwargs): """ - left = None if (ta == 0) else self.subclip(0, ta) - center = self.subclip(ta, tb).fx(fx,**kwargs) + center = self.subclip(ta, tb).fx(fx, **kwargs) right = None if (tb is None) else self.subclip(t_start=tb) clips = [c for c in [left, center, right] if c != None] @@ -477,10 +468,10 @@ def fl_image(self, image_func, apply_to=[]): """ return self.fl(lambda gf, t: image_func(gf(t)), apply_to) - #-------------------------------------------------------------- + # -------------------------------------------------------------- # C O M P O S I T I N G - + def blit_on(self, picture, t): """ Returns the result of the blit of the clip's frame at time `t` @@ -491,8 +482,8 @@ def blit_on(self, picture, t): hf, wf = sizef = picture.shape[:2] if self.ismask and picture.max() != 0: - return np.maximum(picture, - self.blit_on(np.zeros(sizef), t)) + return np.maximum(picture, + self.blit_on(np.zeros(sizef), t)) ct = t - self.start # clip time @@ -508,12 +499,12 @@ def blit_on(self, picture, t): pos = self.pos(ct) # preprocess short writings of the position - if isinstance(pos,str): - pos = { 'center': ['center','center'], - 'left': ['left','center'], - 'right': ['right','center'], - 'top':['center','top'], - 'bottom':['center','bottom']}[pos] + if isinstance(pos, str): + pos = {'center': ['center', 'center'], + 'left': ['left', 'center'], + 'right': ['right', 'center'], + 'top': ['center', 'top'], + 'bottom': ['center', 'bottom']}[pos] else: pos = list(pos) @@ -528,7 +519,6 @@ def blit_on(self, picture, t): pos[0] = D[pos[0]] if isinstance(pos[1], str): - D = {'top': 0, 'center': (hf - hi) / 2, 'bottom': hf - hi} pos[1] = D[pos[1]] @@ -537,7 +527,6 @@ def blit_on(self, picture, t): return blit(img, picture, pos, mask=mask, ismask=self.ismask) - def add_mask(self, constant_size=True): """ Add a mask VideoClip to the VideoClip. @@ -550,14 +539,13 @@ def add_mask(self, constant_size=True): """ if constant_size: mask = ColorClip(self.size, 1.0, ismask=True) - return self.set_mask( mask.set_duration(self.duration)) + return self.set_mask(mask.set_duration(self.duration)) else: make_frame = lambda t: np.ones(self.get_frame(t).shape, dtype=float) - mask = VideoClip(ismask=True, make_frame = make_frame) + mask = VideoClip(ismask=True, make_frame=make_frame) return self.set_mask(mask.set_duration(self.duration)) - def on_color(self, size=None, color=(0, 0, 0), pos=None, col_opacity=None): """ Place the clip on a colored background. @@ -585,6 +573,7 @@ def on_color(self, size=None, color=(0, 0, 0), pos=None, """ from .compositing.CompositeVideoClip import CompositeVideoClip + if size is None: size = self.size if pos is None: @@ -598,7 +587,7 @@ def on_color(self, size=None, color=(0, 0, 0), pos=None, colorclip = colorclip.set_duration(self.duration) result = CompositeVideoClip([colorclip, self.set_pos(pos)], - transparent=(col_opacity is not None)) + transparent=(col_opacity is not None)) if (isinstance(self, ImageClip) and (not hasattr(pos, "__call__")) and ((self.mask is None) or isinstance(self.mask, ImageClip))): @@ -610,7 +599,6 @@ def on_color(self, size=None, color=(0, 0, 0), pos=None, return result - @outplace def set_make_frame(self, mf): """ Change the clip's ``get_frame``. @@ -622,7 +610,6 @@ def set_make_frame(self, mf): self.size = self.get_frame(0).shape[:2][::-1] - @outplace def set_audio(self, audioclip): """ Attach an AudioClip to the VideoClip. @@ -633,7 +620,6 @@ def set_audio(self, audioclip): self.audio = audioclip - @outplace def set_mask(self, mask): """ Set the clip's mask. @@ -644,7 +630,6 @@ def set_mask(self, mask): self.mask = mask - @add_mask_if_none @outplace def set_opacity(self, op): @@ -657,7 +642,6 @@ def set_opacity(self, op): self.mask = self.mask.fl_image(lambda pic: op * pic) - @apply_to_mask @outplace def set_pos(self, pos, relative=False): @@ -692,14 +676,13 @@ def set_pos(self, pos, relative=False): self.pos = lambda t: pos - #-------------------------------------------------------------- # CONVERSIONS TO OTHER TYPES @convert_to_seconds(['t']) - def to_ImageClip(self,t=0, with_mask=True): + def to_ImageClip(self, t=0, with_mask=True): """ Returns an ImageClip made out of the clip's frame at time ``t``, which can be expressed in seconds (15.35), in (min, sec), @@ -707,11 +690,10 @@ def to_ImageClip(self,t=0, with_mask=True): """ newclip = ImageClip(self.get_frame(t), ismask=self.ismask) if with_mask and self.mask is not None: - newclip.mask = self.mask.to_ImageClip(t) + newclip.mask = self.mask.to_ImageClip(t) return newclip - def to_mask(self, canal=0): """ Returns a mask a video clip made from the clip. @@ -720,19 +702,18 @@ def to_mask(self, canal=0): return self else: newclip = self.fl_image(lambda pic: - 1.0 * pic[:, :, canal] / 255) + 1.0 * pic[:, :, canal] / 255) newclip.ismask = True return newclip - def to_RGB(self): """ Returns a non-mask video clip made from the mask video clip. """ if self.ismask: f = lambda pic: np.dstack(3 * [255 * pic]).astype('uint8') - newclip = self.fl_image( f ) + newclip = self.fl_image(f) newclip.ismask = False return newclip else: @@ -752,7 +733,6 @@ def without_audio(self): self.audio = None - @outplace def afx(self, fun, *a, **k): """ Transform the clip's audio. @@ -763,7 +743,6 @@ def afx(self, fun, *a, **k): self.audio = self.audio.fx(fun, *a, **k) - """--------------------------------------------------------------------- ImageClip (base class for all 'static clips') and its subclasses @@ -774,9 +753,7 @@ def afx(self, fun, *a, **k): ---------------------------------------------------------------------""" - class ImageClip(VideoClip): - """ Class for non-moving VideoClips. A video clip originating from a picture. This clip will simply @@ -817,21 +794,21 @@ def __init__(self, img, ismask=False, transparent=True, VideoClip.__init__(self, ismask=ismask) if isinstance(img, str): - img = ffmpeg_read_image(img,with_mask=transparent) - - if len(img.shape) == 3: # img is (now) a RGB(a) numpy array - - if img.shape[2] == 4: - if fromalpha: - img = 1.0 * img[:, :, 3] / 255 - elif ismask: - img = 1.0 * img[:, :, 0] / 255 - elif transparent: - self.mask = ImageClip( - 1.0 * img[:, :, 3] / 255, ismask=True) - img = img[:, :, :3] + img = ffmpeg_read_image(img, with_mask=transparent) + + if len(img.shape) == 3: # img is (now) a RGB(a) numpy array + + if img.shape[2] == 4: + if fromalpha: + img = 1.0 * img[:, :, 3] / 255 elif ismask: - img = 1.0 * img[:, :, 0] / 255 + img = 1.0 * img[:, :, 0] / 255 + elif transparent: + self.mask = ImageClip( + 1.0 * img[:, :, 3] / 255, ismask=True) + img = img[:, :, :3] + elif ismask: + img = 1.0 * img[:, :, 0] / 255 # if the image was just a 2D mask, it should arrive here # unchanged @@ -840,9 +817,7 @@ def __init__(self, img, ismask=False, transparent=True, self.img = img - - - def fl(self, fl, apply_to=[], keep_duration=True): + def fl(self, fl, apply_to=[], keep_duration=True): """ General transformation filter. Equivalent to VideoClip.fl . The result is no more an @@ -851,15 +826,14 @@ def fl(self, fl, apply_to=[], keep_duration=True): # 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, + newclip = VideoClip.fl(self, fl, apply_to=apply_to, keep_duration=keep_duration) newclip.__class__ = VideoClip return newclip - @outplace - def fl_image(self, image_func, apply_to= []): + def fl_image(self, image_func, apply_to=[]): """ Image-transformation filter. Does the same as VideoClip.fl_image, but for ImageClip the @@ -876,13 +850,13 @@ def fl_image(self, image_func, apply_to= []): if hasattr(self, attr): a = getattr(self, attr) if a != None: - new_a = a.fl_image(image_func) + new_a = a.fl_image(image_func) setattr(self, attr, new_a) - @outplace - def fl_time(self, time_func, apply_to =['mask', 'audio'], keep_duration=False): + def fl_time(self, time_func, apply_to=['mask', 'audio'], + keep_duration=False): """ Time-transformation filter. Applies a transformation to the clip's timeline @@ -900,16 +874,17 @@ def fl_time(self, time_func, apply_to =['mask', 'audio'], keep_duration=False): setattr(self, attr, new_a) -### +# ## # # The old functions to_videofile, to_gif, to_images sequences have been # replaced by the more explicite write_videofile, write_gif, etc. -VideoClip.to_videofile = deprecated_version_of(VideoClip.write_videofile, +VideoClip.to_videofile = deprecated_version_of(VideoClip.write_videofile, 'to_videofile') VideoClip.to_gif = deprecated_version_of(VideoClip.write_gif, 'to_gif') -VideoClip.to_images_sequence = deprecated_version_of(VideoClip.write_images_sequence, - 'to_images_sequence') +VideoClip.to_images_sequence = deprecated_version_of( + VideoClip.write_images_sequence, + 'to_images_sequence') ### @@ -933,16 +908,14 @@ class ColorClip(ImageClip): """ - def __init__(self,size, col=(0, 0, 0), ismask=False): + def __init__(self, size, col=(0, 0, 0), ismask=False): w, h = size shape = (h, w) if np.isscalar(col) else (h, w, len(col)) ImageClip.__init__(self, np.tile(col, w * h).reshape(shape), ismask=ismask) - class TextClip(ImageClip): - """ Class for autogenerated text clips. Creates an ImageClip originating from a script-generated text image. @@ -1007,63 +980,62 @@ class TextClip(ImageClip): """ - def __init__(self, txt=None, filename=None, size=None, color='black', - bg_color='transparent', fontsize=None, font='Courier', - stroke_color=None, stroke_width=1, method='label', - kerning=None, align='center', interline=None, - tempfilename=None, temptxt=None, - transparent=True, remove_temp=True, - print_cmd=False): - + bg_color='transparent', fontsize=None, font='Courier', + stroke_color=None, stroke_width=1, method='label', + kerning=None, align='center', interline=None, + tempfilename=None, temptxt=None, + transparent=True, remove_temp=True, + print_cmd=False): + if txt is not None: if temptxt is None: temptxt_fd, temptxt = tempfile.mkstemp(suffix='.txt') - try: # only in Python3 will this work - os.write(temptxt_fd, bytes(txt,'UTF8')) - except TypeError: # oops, fall back to Python2 + try: # only in Python3 will this work + os.write(temptxt_fd, bytes(txt, 'UTF8')) + except TypeError: # oops, fall back to Python2 os.write(temptxt_fd, txt) os.close(temptxt_fd) - txt = '@'+temptxt + txt = '@' + temptxt else: # use a file instead of a text. - txt = "@%"+filename + txt = "@%" + filename if size != None: size = ('' if size[0] is None else str(size[0]), '' if size[1] is None else str(size[1])) cmd = ( [IMAGEMAGICK_BINARY, - "-background", bg_color, - "-fill", color, - "-font", font]) + "-background", bg_color, + "-fill", color, + "-font", font]) - if fontsize !=None: - cmd += ["-pointsize", "%d"%fontsize] + if fontsize != None: + cmd += ["-pointsize", "%d" % fontsize] if kerning != None: - cmd += ["-kerning", "%0.1f"%kerning] + cmd += ["-kerning", "%0.1f" % kerning] if stroke_color != None: cmd += ["-stroke", stroke_color, "-strokewidth", - "%.01f"%stroke_width] + "%.01f" % stroke_width] if size != None: - cmd += ["-size", "%sx%s"%(size[0], size[1])] + cmd += ["-size", "%sx%s" % (size[0], size[1])] if align != None: - cmd += ["-gravity",align] + cmd += ["-gravity", align] if interline != None: - cmd += ["-interline-spacing", "%d"%interline] + cmd += ["-interline-spacing", "%d" % interline] if tempfilename is None: tempfile_fd, tempfilename = tempfile.mkstemp(suffix='.png') os.close(tempfile_fd) - cmd += ["%s:%s" %(method, txt), - "-type", "truecolormatte", "PNG32:%s"%tempfilename] + cmd += ["%s:%s" % (method, txt), + "-type", "truecolormatte", "PNG32:%s" % tempfilename] if print_cmd: print( " ".join(cmd) ) - subprocess_call(cmd, verbose=False ) + subprocess_call(cmd, verbose=False) ImageClip.__init__(self, tempfilename, transparent=transparent) self.txt = txt @@ -1077,14 +1049,13 @@ def __init__(self, txt=None, filename=None, size=None, color='black', os.remove(temptxt) - @staticmethod def list(arg): """ Returns the list of all valid entries for the argument of ``TextClip`` given (can be ``font``, ``color``, etc...) """ process = sp.Popen([IMAGEMAGICK_BINARY, '-list', arg], - stdout=sp.PIPE) + stdout=sp.PIPE) result = process.communicate()[0] lines = result.splitlines() diff --git a/moviepy/video/io/ffmpeg_writer.py b/moviepy/video/io/ffmpeg_writer.py index 01b1d496e..b53b280ed 100644 --- a/moviepy/video/io/ffmpeg_writer.py +++ b/moviepy/video/io/ffmpeg_writer.py @@ -3,25 +3,22 @@ out of VideoClips """ -import numpy as np import subprocess as sp +import numpy as np + + try: - from subprocess import DEVNULL # py3k + from subprocess import DEVNULL # py3k except ImportError: import os - DEVNULL = open(os.devnull, 'wb') - - -from tqdm import tqdm + DEVNULL = open(os.devnull, 'wb') from moviepy.conf import FFMPEG_BINARY from moviepy.tools import verbose_print - - class FFMPEG_VideoWriter: """ A class for FFMPEG-based video writing. @@ -55,12 +52,14 @@ class FFMPEG_VideoWriter: video readers. audiofile - Optional: The name of an audio file that will be incorporated to the video. + Optional: The name of an audio file that will be incorporated + to the video. preset Sets the time that FFMPEG will take to compress the video. The slower, the better the compression rate. Possibilities are: ultrafast,superfast, - veryfast, faster, fast, medium (default), slow, slower, veryslow, placebo. + veryfast, faster, fast, medium (default), slow, slower, veryslow, + placebo. bitrate Only relevant for codecs which accept a bitrate. "5000k" offers @@ -71,157 +70,164 @@ class FFMPEG_VideoWriter: encoded. """ - - - + + def __init__(self, filename, size, fps, codec="libx264", audiofile=None, preset="medium", bitrate=None, withmask=False, - logfile=None): + logfile=None, ffmpeg_params=None): if logfile is None: - logfile = sp.PIPE + logfile = sp.PIPE self.filename = filename self.codec = codec self.ext = self.filename.split(".")[-1] - - cmd = ( - [ FFMPEG_BINARY, '-y'] - +["-loglevel", "error" if logfile==sp.PIPE else "info", - "-f", 'rawvideo', - "-vcodec","rawvideo", - '-s', "%dx%d"%(size[0],size[1]), - '-pix_fmt', "rgba" if withmask else "rgb24", - '-r', "%.02f"%fps, - '-i', '-', '-an'] - + (["-i", audiofile, "-acodec", "copy"] if (audiofile is not None) else []) - +['-vcodec', codec, - '-preset', preset] - + (['-b',bitrate] if (bitrate!=None) else []) - # http://trac.ffmpeg.org/ticket/658 - + (['-pix_fmt', 'yuv420p'] - if ((codec == 'libx264') and - (size[0]%2 == 0) and - (size[1]%2 == 0)) - - else []) - + [ '-r', "%.02f"%fps, filename ] - #+ (["-acodec", "copy"] if (audiofile is not None) else []) - ) + + # order is important + cmd = [ + FFMPEG_BINARY, + '-y', + '-loglevel', 'error' if logfile == sp.PIPE else 'info', + '-f', 'rawvideo', + '-vcodec', 'rawvideo', + '-s', '%dx%d' % (size[0], size[1]), + '-pix_fmt', 'rgba' if withmask else 'rgb24', + '-r', '%.02f' % fps, + '-i', '-', '-an', + ] + if audiofile is not None: + cmd.extend([ + '-i', audiofile, + '-acodec', 'copy' + ]) + cmd.extend([ + '-vcodec', codec, + '-preset', preset, + ]) + if ffmpeg_params is not None: + cmd.extend(ffmpeg_params) + if bitrate is not None: + cmd.extend([ + '-b', bitrate + ]) + if ((codec == 'libx264') and + (size[0] % 2 == 0) and + (size[1] % 2 == 0)): + cmd.extend([ + '-pix_fmt', 'yuv420p' + ]) + cmd.extend([ + filename + ]) self.proc = sp.Popen(cmd, stdin=sp.PIPE, - stderr=logfile, - stdout=DEVNULL) + stderr=logfile, + stdout=DEVNULL) - - def write_frame(self,img_array): + + def write_frame(self, img_array): """ Writes one frame in the file.""" try: self.proc.stdin.write(img_array.tostring()) except IOError as err: ffmpeg_error = self.proc.stderr.read() - error = (str(err)+ ("\n\nMoviePy error: FFMPEG encountered " - "the following error while writing file %s:"%self.filename - + "\n\n"+ffmpeg_error)) + error = (str(err) + ("\n\nMoviePy error: FFMPEG encountered " + "the following error while writing file %s:" + "\n\n %s" % (self.filename, ffmpeg_error))) if "Unknown encoder" in ffmpeg_error: - - error = error+("\n\nThe video export " - "failed because FFMPEG didn't find the specified " - "codec for video encoding (%s). Please install " - "this codec or change the codec when calling " - "write_videofile. For instance:\n" - " >>> clip.write_videofile('myvid.webm', codec='libvpx')")%(self.codec) - + + error = error + ("\n\nThe video export " + "failed because FFMPEG didn't find the specified " + "codec for video encoding (%s). Please install " + "this codec or change the codec when calling " + "write_videofile. For instance:\n" + " >>> clip.write_videofile('myvid.webm', codec='libvpx')") \ + % (self.codec) + elif "incorrect codec parameters ?" in ffmpeg_error: - error = error+("\n\nThe video export " - "failed, possibly because the codec specified for " - "the video (%s) is not compatible with the given " - "extension (%s). Please specify a valid 'codec' " - "argument in write_videofile. This would be 'libx264' " - "or 'mpeg4' for mp4, 'libtheora' for ogv, 'libvpx for webm. " - "Another possible reason is that the audio codec was not " - "compatible with the video codec. For instance the video " - "extensions 'ogv' and 'webm' only allow 'libvorbis' (default) as a" - "video codec." - )%(self.codec, self.ext) - - elif "encoder setup failed": - - error = error+("\n\nThe video export " - "failed, possibly because the bitrate you specified " - "was too high or too low for the video codec.") - + error = error + ("\n\nThe video export " + "failed, possibly because the codec specified for " + "the video (%s) is not compatible with the given " + "extension (%s). Please specify a valid 'codec' " + "argument in write_videofile. This would be 'libx264' " + "or 'mpeg4' for mp4, 'libtheora' for ogv, 'libvpx for webm. " + "Another possible reason is that the audio codec was not " + "compatible with the video codec. For instance the video " + "extensions 'ogv' and 'webm' only allow 'libvorbis' " + "(default) as a video codec." + ) % (self.codec, self.ext) + + elif "encoder setup failed": + + error = error + ("\n\nThe video export " + "failed, possibly because the bitrate you specified " + "was too high or too low for the video codec.") + raise IOError(error) - + def close(self): self.proc.stdin.close() if self.proc.stderr is not None: self.proc.stderr.close() self.proc.wait() - + del self.proc - + + def ffmpeg_write_video(clip, filename, fps, codec="libx264", bitrate=None, - preset = "medium", withmask=False, write_logfile=False, - audiofile=None, verbose=True): - + preset="medium", withmask=False, write_logfile=False, + audiofile=None, verbose=True, ffmpeg_params=None): if write_logfile: logfile = open(filename + ".log", 'w+') else: logfile = None + verbose_print(verbose, "\nWriting video into %s\n" % filename) + writer = FFMPEG_VideoWriter(filename, clip.size, fps, codec=codec, + preset=preset, bitrate=bitrate, + logfile=logfile, audiofile=audiofile, + ffmpeg_params=ffmpeg_params) - verbose_print(verbose, "\nWriting video into %s\n"%filename) - writer = FFMPEG_VideoWriter(filename, clip.size, fps, codec = codec, - preset=preset, bitrate=bitrate, logfile=logfile, - audiofile = audiofile) - - nframes = int(clip.duration*fps) - - for t,frame in clip.iter_frames(progress_bar=True, with_times=True, - fps=fps): + for t, frame in clip.iter_frames(progress_bar=True, with_times=True, + fps=fps): if withmask: - mask = 255*clip.mask.get_frame(t) - frame = np.dstack([frame,mask]) - + mask = 255 * clip.mask.get_frame(t) + frame = np.dstack([frame, mask]) + writer.write_frame(frame.astype("uint8")) - + writer.close() if write_logfile: - logfile.close() - - verbose_print(verbose, "Done writing video in %s !"%filename) - - + logfile.close() + + verbose_print(verbose, "Done writing video in %s !" % filename) + + def ffmpeg_write_image(filename, image, logfile=False): """ Writes an image (HxWx3 or HxWx4 numpy array) to a file, using ffmpeg. """ - - cmd = [ FFMPEG_BINARY, '-y', - '-s', "%dx%d"%(image.shape[:2][::-1]), + cmd = [FFMPEG_BINARY, '-y', + '-s', "%dx%d" % (image.shape[:2][::-1]), "-f", 'rawvideo', '-pix_fmt', "rgba" if (image.shape[2] == 4) else "rgb24", - '-i','-', filename] - - if logfile: + '-i', '-', filename] + + if logfile: log_file = open(filename + ".log", 'w+') else: log_file = sp.PIPE + proc = sp.Popen(cmd, stdin=sp.PIPE, stderr=log_file) + proc.communicate(image.tostring()) # proc.wait() - proc = sp.Popen( cmd, stdin=sp.PIPE, stderr=log_file) - proc.communicate(image.tostring()) # proc.wait() - if proc.returncode: - err = "\n".join(["MoviePy running : %s"%cmd, - "WARNING: this command returned an error:", - proc.stderr.read().decode('utf8')]) + err = "\n".join(["MoviePy running : %s" % cmd, + "WARNING: this command returned an error:", + proc.stderr.read().decode('utf8')]) raise IOError(err) - - del proc