1"""
2This module implements VideoClip (base class for video clips) and its
3main subclasses:
4- Animated clips:     VideofileClip, ImageSequenceClip
5- Static image clips: ImageClip, ColorClip, TextClip,
6"""
7import os
8import subprocess as sp
9import tempfile
10import warnings
11
12import numpy as np
13import proglog
14from imageio import imread, imsave
15
16from ..Clip import Clip
17from ..compat import DEVNULL, string_types
18from ..config import get_setting
19from ..decorators import (add_mask_if_none, apply_to_mask,
20                          convert_masks_to_RGB, convert_to_seconds, outplace,
21                          requires_duration, use_clip_fps_by_default)
22from ..tools import (deprecated_version_of, extensions_dict, find_extension,
23                     is_string, subprocess_call)
24from .io.ffmpeg_writer import ffmpeg_write_video
25from .io.gif_writers import (write_gif, write_gif_with_image_io,
26                             write_gif_with_tempfiles)
27from .tools.drawing import blit
28
29
30class VideoClip(Clip):
31    """Base class for video clips.
32
33    See ``VideoFileClip``, ``ImageClip`` etc. for more user-friendly
34    classes.
35
36
37    Parameters
38    -----------
39
40    ismask
41      `True` if the clip is going to be used as a mask.
42
43
44    Attributes
45    ----------
46
47    size
48      The size of the clip, (width,heigth), in pixels.
49
50    w, h
51      The width and height of the clip, in pixels.
52
53    ismask
54      Boolean set to `True` if the clip is a mask.
55
56    make_frame
57      A function ``t-> frame at time t`` where ``frame`` is a
58      w*h*3 RGB array.
59
60    mask (default None)
61      VideoClip mask attached to this clip. If mask is ``None``,
62                The video clip is fully opaque.
63
64    audio (default None)
65      An AudioClip instance containing the audio of the video clip.
66
67    pos
68      A function ``t->(x,y)`` where ``x,y`` is the position
69      of the clip when it is composed with other clips.
70      See ``VideoClip.set_pos`` for more details
71
72    relative_pos
73      See variable ``pos``.
74
75    """
76
77    def __init__(self, make_frame=None, ismask=False, duration=None,
78                 has_constant_size=True):
79        Clip.__init__(self)
80        self.mask = None
81        self.audio = None
82        self.pos = lambda t: (0, 0)
83        self.relative_pos = False
84        if make_frame:
85            self.make_frame = make_frame
86            self.size = self.get_frame(0).shape[:2][::-1]
87        self.ismask = ismask
88        self.has_constant_size=has_constant_size
89        if duration is not None:
90            self.duration = duration
91            self.end = duration
92
93    @property
94    def w(self):
95        return self.size[0]
96
97    @property
98    def h(self):
99        return self.size[1]
100
101    @property
102    def aspect_ratio(self):
103        return self.w / float(self.h)
104
105    # ===============================================================
106    # EXPORT OPERATIONS
107
108    @convert_to_seconds(['t'])
109    @convert_masks_to_RGB
110    def save_frame(self, filename, t=0, withmask=True):
111        """ Save a clip's frame to an image file.
112
113        Saves the frame of clip corresponding to time ``t`` in
114        'filename'. ``t`` can be expressed in seconds (15.35), in
115        (min, sec), in (hour, min, sec), or as a string: '01:03:05.35'.
116
117        If ``withmask`` is ``True`` the mask is saved in
118        the alpha layer of the picture (only works with PNGs).
119
120        """
121
122        im = self.get_frame(t)
123        if withmask and self.mask is not None:
124            mask = 255 * self.mask.get_frame(t)
125            im = np.dstack([im, mask]).astype('uint8')
126        else:
127            im = im.astype("uint8")
128
129        imsave(filename, im)
130
131    @requires_duration
132    @use_clip_fps_by_default
133    @convert_masks_to_RGB
134    def write_videofile(self, filename, fps=None, codec=None,
135                        bitrate=None, audio=True, audio_fps=44100,
136                        preset="medium",
137                        audio_nbytes=4, audio_codec=None,
138                        audio_bitrate=None, audio_bufsize=2000,
139                        temp_audiofile=None,
140                        rewrite_audio=True, remove_temp=True,
141                        write_logfile=False, verbose=True,
142                        threads=None, ffmpeg_params=None,
143                        logger='bar'):
144        """Write the clip to a videofile.
145
146        Parameters
147        -----------
148
149        filename
150          Name of the video file to write in.
151          The extension must correspond to the "codec" used (see below),
152          or simply be '.avi' (which will work with any codec).
153
154        fps
155          Number of frames per second in the resulting video file. If None is
156          provided, and the clip has an fps attribute, this fps will be used.
157
158        codec
159          Codec to use for image encoding. Can be any codec supported
160          by ffmpeg. If the filename is has extension '.mp4', '.ogv', '.webm',
161          the codec will be set accordingly, but you can still set it if you
162          don't like the default. For other extensions, the output filename
163          must be set accordingly.
164
165          Some examples of codecs are:
166
167          ``'libx264'`` (default codec for file extension ``.mp4``)
168          makes well-compressed videos (quality tunable using 'bitrate').
169
170
171          ``'mpeg4'`` (other codec for extension ``.mp4``) can be an alternative
172          to ``'libx264'``, and produces higher quality videos by default.
173
174
175          ``'rawvideo'`` (use file extension ``.avi``) will produce
176          a video of perfect quality, of possibly very huge size.
177
178
179          ``png`` (use file extension ``.avi``) will produce a video
180          of perfect quality, of smaller size than with ``rawvideo``.
181
182
183          ``'libvorbis'`` (use file extension ``.ogv``) is a nice video
184          format, which is completely free/ open source. However not
185          everyone has the codecs installed by default on their machine.
186
187
188          ``'libvpx'`` (use file extension ``.webm``) is tiny a video
189          format well indicated for web videos (with HTML5). Open source.
190
191
192        audio
193          Either ``True``, ``False``, or a file name.
194          If ``True`` and the clip has an audio clip attached, this
195          audio clip will be incorporated as a soundtrack in the movie.
196          If ``audio`` is the name of an audio file, this audio file
197          will be incorporated as a soundtrack in the movie.
198
199        audiofps
200          frame rate to use when generating the sound.
201
202        temp_audiofile
203          the name of the temporary audiofile to be generated and
204          incorporated in the the movie, if any.
205
206        audio_codec
207          Which audio codec should be used. Examples are 'libmp3lame'
208          for '.mp3', 'libvorbis' for 'ogg', 'libfdk_aac':'m4a',
209          'pcm_s16le' for 16-bit wav and 'pcm_s32le' for 32-bit wav.
210          Default is 'libmp3lame', unless the video extension is 'ogv'
211          or 'webm', at which case the default is 'libvorbis'.
212
213        audio_bitrate
214          Audio bitrate, given as a string like '50k', '500k', '3000k'.
215          Will determine the size/quality of audio in the output file.
216          Note that it mainly an indicative goal, the bitrate won't
217          necessarily be the this in the final file.
218
219        preset
220          Sets the time that FFMPEG will spend optimizing the compression.
221          Choices are: ultrafast, superfast, veryfast, faster, fast, medium,
222          slow, slower, veryslow, placebo. Note that this does not impact
223          the quality of the video, only the size of the video file. So
224          choose ultrafast when you are in a hurry and file size does not
225          matter.
226
227        threads
228          Number of threads to use for ffmpeg. Can speed up the writing of
229          the video on multicore computers.
230
231        ffmpeg_params
232          Any additional ffmpeg parameters you would like to pass, as a list
233          of terms, like ['-option1', 'value1', '-option2', 'value2'].
234
235        write_logfile
236          If true, will write log files for the audio and the video.
237          These will be files ending with '.log' with the name of the
238          output file in them.
239
240        logger
241          Either "bar" for progress bar or None or any Proglog logger.
242
243        verbose (deprecated, kept for compatibility)
244          Formerly used for toggling messages on/off. Use logger=None now.
245
246        Examples
247        ========
248
249        >>> from moviepy.editor import VideoFileClip
250        >>> clip = VideoFileClip("myvideo.mp4").subclip(100,120)
251        >>> clip.write_videofile("my_new_video.mp4")
252        >>> clip.close()
253
254        """
255        name, ext = os.path.splitext(os.path.basename(filename))
256        ext = ext[1:].lower()
257        logger = proglog.default_bar_logger(logger)
258
259        if codec is None:
260
261            try:
262                codec = extensions_dict[ext]['codec'][0]
263            except KeyError:
264                raise ValueError("MoviePy couldn't find the codec associated "
265                                 "with the filename. Provide the 'codec' "
266                                 "parameter in write_videofile.")
267
268        if audio_codec is None:
269            if ext in ['ogv', 'webm']:
270                audio_codec = 'libvorbis'
271            else:
272                audio_codec = 'libmp3lame'
273        elif audio_codec == 'raw16':
274            audio_codec = 'pcm_s16le'
275        elif audio_codec == 'raw32':
276            audio_codec = 'pcm_s32le'
277
278        audiofile = audio if is_string(audio) else None
279        make_audio = ((audiofile is None) and (audio == True) and
280                      (self.audio is not None))
281
282        if make_audio and temp_audiofile:
283            # The audio will be the clip's audio
284            audiofile = temp_audiofile
285        elif make_audio:
286            audio_ext = find_extension(audio_codec)
287            audiofile = (name + Clip._TEMP_FILES_PREFIX + "wvf_snd.%s" % audio_ext)
288
289        # enough cpu for multiprocessing ? USELESS RIGHT NOW, WILL COME AGAIN
290        # enough_cpu = (multiprocessing.cpu_count() > 1)
291        logger(message="Moviepy - Building video %s." % filename)
292        if make_audio:
293            self.audio.write_audiofile(audiofile, audio_fps,
294                                       audio_nbytes, audio_bufsize,
295                                       audio_codec, bitrate=audio_bitrate,
296                                       write_logfile=write_logfile,
297                                       verbose=verbose,
298                                       logger=logger)
299
300        ffmpeg_write_video(self, filename, fps, codec,
301                           bitrate=bitrate,
302                           preset=preset,
303                           write_logfile=write_logfile,
304                           audiofile=audiofile,
305                           verbose=verbose, threads=threads,
306                           ffmpeg_params=ffmpeg_params,
307                           logger=logger)
308
309        if remove_temp and make_audio:
310            if os.path.exists(audiofile):
311                os.remove(audiofile)
312        logger(message="Moviepy - video ready %s" % filename)
313
314    @requires_duration
315    @use_clip_fps_by_default
316    @convert_masks_to_RGB
317    def write_images_sequence(self, nameformat, fps=None, verbose=True,
318                              withmask=True, logger='bar'):
319        """ Writes the videoclip to a sequence of image files.
320
321        Parameters
322        -----------
323
324        nameformat
325          A filename specifying the numerotation format and extension
326          of the pictures. For instance "frame%03d.png" for filenames
327          indexed with 3 digits and PNG format. Also possible:
328          "some_folder/frame%04d.jpeg", etc.
329
330        fps
331          Number of frames per second to consider when writing the
332          clip. If not specified, the clip's ``fps`` attribute will
333          be used if it has one.
334
335        withmask
336          will save the clip's mask (if any) as an alpha canal (PNGs only).
337
338        verbose
339          Boolean indicating whether to print information.
340
341        logger
342          Either 'bar' (progress bar) or None or any Proglog logger.
343
344
345        Returns
346        --------
347
348        names_list
349          A list of all the files generated.
350
351        Notes
352        ------
353
354        The resulting image sequence can be read using e.g. the class
355        ``ImageSequenceClip``.
356
357        """
358        logger = proglog.default_bar_logger(logger)
359        logger(message='Moviepy - Writing frames %s.' % nameformat)
360
361        tt = np.arange(0, self.duration, 1.0 / fps)
362
363        filenames = []
364        for i, t in logger.iter_bar(t=list(enumerate(tt))):
365            name = nameformat % i
366            filenames.append(name)
367            self.save_frame(name, t, withmask=withmask)
368        logger(message='Moviepy - Done writing frames %s.' % nameformat)
369
370        return filenames
371
372    @requires_duration
373    @convert_masks_to_RGB
374    def write_gif(self, filename, fps=None, program='imageio',
375                  opt='nq', fuzz=1, verbose=True,
376                  loop=0, dispose=False, colors=None, tempfiles=False,
377                  logger='bar'):
378        """ Write the VideoClip to a GIF file.
379
380        Converts a VideoClip into an animated GIF using ImageMagick
381        or ffmpeg.
382
383        Parameters
384        -----------
385
386        filename
387          Name of the resulting gif file.
388
389        fps
390          Number of frames per second (see note below). If it
391          isn't provided, then the function will look for the clip's
392          ``fps`` attribute (VideoFileClip, for instance, have one).
393
394        program
395          Software to use for the conversion, either 'imageio' (this will use
396          the library FreeImage through ImageIO), or 'ImageMagick', or 'ffmpeg'.
397
398        opt
399          Optimalization to apply. If program='imageio', opt must be either 'wu'
400          (Wu) or 'nq' (Neuquant). If program='ImageMagick',
401          either 'optimizeplus' or 'OptimizeTransparency'.
402
403        fuzz
404          (ImageMagick only) Compresses the GIF by considering that
405          the colors that are less than fuzz% different are in fact
406          the same.
407
408        tempfiles
409          Writes every frame to a file instead of passing them in the RAM.
410          Useful on computers with little RAM. Can only be used with
411          ImageMagick' or 'ffmpeg'.
412
413        progress_bar
414          If True, displays a progress bar
415
416
417        Notes
418        -----
419
420        The gif will be playing the clip in real time (you can
421        only change the frame rate). If you want the gif to be played
422        slower than the clip you will use ::
423
424            >>> # slow down clip 50% and make it a gif
425            >>> myClip.speedx(0.5).to_gif('myClip.gif')
426
427        """
428        # A little sketchy at the moment, maybe move all that in write_gif,
429        #  refactor a little... we will see.
430
431        if program == 'imageio':
432            write_gif_with_image_io(self, filename, fps=fps, opt=opt, loop=loop,
433                                    verbose=verbose, colors=colors,
434                                    logger=logger)
435        elif tempfiles:
436            # convert imageio opt variable to something that can be used with
437            # ImageMagick
438            opt = 'optimizeplus' if opt == 'nq' else 'OptimizeTransparency'
439            write_gif_with_tempfiles(self, filename, fps=fps,
440                                     program=program, opt=opt, fuzz=fuzz,
441                                     verbose=verbose, loop=loop,
442                                     dispose=dispose, colors=colors,
443                                     logger=logger)
444        else:
445            # convert imageio opt variable to something that can be used with
446            # ImageMagick
447            opt = 'optimizeplus' if opt == 'nq' else 'OptimizeTransparency'
448            write_gif(self, filename, fps=fps, program=program,
449                      opt=opt, fuzz=fuzz, verbose=verbose, loop=loop,
450                      dispose=dispose, colors=colors,
451                      logger=logger)
452
453    # -----------------------------------------------------------------
454    # F I L T E R I N G
455
456    def subfx(self, fx, ta=0, tb=None, **kwargs):
457        """Apply a transformation to a part of the clip.
458
459        Returns a new clip in which the function ``fun`` (clip->clip)
460        has been applied to the subclip between times `ta` and `tb`
461        (in seconds).
462
463        Examples
464        ---------
465
466        >>> # The scene between times t=3s and t=6s in ``clip`` will be
467        >>> # be played twice slower in ``newclip``
468        >>> newclip = clip.subapply(lambda c:c.speedx(0.5) , 3,6)
469
470        """
471        left = self.subclip(0, ta) if ta else None
472        center = self.subclip(ta, tb).fx(fx, **kwargs)
473        right = self.subclip(t_start=tb) if tb else None
474
475        clips = [c for c in (left, center, right) if c]
476
477        # beurk, have to find other solution
478        from moviepy.video.compositing.concatenate import concatenate_videoclips
479
480        return concatenate_videoclips(clips).set_start(self.start)
481
482    # IMAGE FILTERS
483
484    def fl_image(self, image_func, apply_to=None):
485        """
486        Modifies the images of a clip by replacing the frame
487        `get_frame(t)` by another frame,  `image_func(get_frame(t))`
488        """
489        apply_to = apply_to or []
490        return self.fl(lambda gf, t: image_func(gf(t)), apply_to)
491
492    # --------------------------------------------------------------
493    # C O M P O S I T I N G
494
495    def fill_array(self, pre_array, shape=(0, 0)):
496        pre_shape = pre_array.shape
497        dx = shape[0] - pre_shape[0]
498        dy = shape[1] - pre_shape[1]
499        post_array = pre_array
500        if dx < 0:
501            post_array = pre_array[:shape[0]]
502        elif dx > 0:
503            x_1 = [[[1, 1, 1]] * pre_shape[1]] * dx
504            post_array = np.vstack((pre_array, x_1))
505        if dy < 0:
506            post_array = post_array[:, :shape[1]]
507        elif dy > 0:
508            x_1 = [[[1, 1, 1]] * dy] * post_array.shape[0]
509            post_array = np.hstack((post_array, x_1))
510        return post_array
511
512    def blit_on(self, picture, t):
513        """
514        Returns the result of the blit of the clip's frame at time `t`
515        on the given `picture`, the position of the clip being given
516        by the clip's ``pos`` attribute. Meant for compositing.
517        """
518        hf, wf = framesize = picture.shape[:2]
519
520        if self.ismask and picture.max():
521            return np.minimum(1, picture + self.blit_on(np.zeros(framesize), t))
522
523        ct = t - self.start  # clip time
524
525        # GET IMAGE AND MASK IF ANY
526
527        img = self.get_frame(ct)
528        mask = self.mask.get_frame(ct) if self.mask else None
529
530        if mask is not None and ((img.shape[0] != mask.shape[0]) or (img.shape[1] != mask.shape[1])):
531            img = self.fill_array(img, mask.shape)
532
533        hi, wi = img.shape[:2]
534
535        # SET POSITION
536        pos = self.pos(ct)
537
538        # preprocess short writings of the position
539        if isinstance(pos, str):
540            pos = {'center': ['center', 'center'],
541                   'left': ['left', 'center'],
542                   'right': ['right', 'center'],
543                   'top': ['center', 'top'],
544                   'bottom': ['center', 'bottom']}[pos]
545        else:
546            pos = list(pos)
547
548        # is the position relative (given in % of the clip's size) ?
549        if self.relative_pos:
550            for i, dim in enumerate([wf, hf]):
551                if not isinstance(pos[i], str):
552                    pos[i] = dim * pos[i]
553
554        if isinstance(pos[0], str):
555            D = {'left': 0, 'center': (wf - wi) / 2, 'right': wf - wi}
556            pos[0] = D[pos[0]]
557
558        if isinstance(pos[1], str):
559            D = {'top': 0, 'center': (hf - hi) / 2, 'bottom': hf - hi}
560            pos[1] = D[pos[1]]
561
562        pos = map(int, pos)
563
564        return blit(img, picture, pos, mask=mask, ismask=self.ismask)
565
566    def add_mask(self):
567        """Add a mask VideoClip to the VideoClip.
568
569        Returns a copy of the clip with a completely opaque mask
570        (made of ones). This makes computations slower compared to
571        having a None mask but can be useful in many cases. Choose
572
573        Set ``constant_size`` to  `False` for clips with moving
574        image size.
575        """
576        if self.has_constant_size:
577            mask = ColorClip(self.size, 1.0, ismask=True)
578            return self.set_mask(mask.set_duration(self.duration))
579        else:
580            make_frame = lambda t: np.ones(self.get_frame(t).shape[:2], dtype=float)
581            mask = VideoClip(ismask=True, make_frame=make_frame)
582            return self.set_mask(mask.set_duration(self.duration))
583
584    def on_color(self, size=None, color=(0, 0, 0), pos=None,
585                 col_opacity=None):
586        """Place the clip on a colored background.
587
588        Returns a clip made of the current clip overlaid on a color
589        clip of a possibly bigger size. Can serve to flatten transparent
590        clips.
591
592        Parameters
593        -----------
594
595        size
596          Size (width, height) in pixels of the final clip.
597          By default it will be the size of the current clip.
598
599        color
600          Background color of the final clip ([R,G,B]).
601
602        pos
603          Position of the clip in the final clip. 'center' is the default
604
605        col_opacity
606          Parameter in 0..1 indicating the opacity of the colored
607          background.
608
609        """
610        from .compositing.CompositeVideoClip import CompositeVideoClip
611
612        if size is None:
613            size = self.size
614        if pos is None:
615            pos = 'center'
616        colorclip = ColorClip(size, color=color)
617
618        if col_opacity is not None:
619            colorclip = (ColorClip(size, color=color, duration=self.duration)
620                         .set_opacity(col_opacity))
621            result = CompositeVideoClip([colorclip, self.set_position(pos)])
622        else:
623            result = CompositeVideoClip([self.set_position(pos)],
624                                        size=size,
625                                        bg_color=color)
626
627        if (isinstance(self, ImageClip) and (not hasattr(pos, "__call__"))
628                and ((self.mask is None) or isinstance(self.mask, ImageClip))):
629            new_result = result.to_ImageClip()
630            if result.mask is not None:
631                new_result.mask = result.mask.to_ImageClip()
632            return new_result.set_duration(result.duration)
633
634        return result
635
636    @outplace
637    def set_make_frame(self, mf):
638        """Change the clip's ``get_frame``.
639
640        Returns a copy of the VideoClip instance, with the make_frame
641        attribute set to `mf`.
642        """
643        self.make_frame = mf
644        self.size = self.get_frame(0).shape[:2][::-1]
645
646    @outplace
647    def set_audio(self, audioclip):
648        """Attach an AudioClip to the VideoClip.
649
650        Returns a copy of the VideoClip instance, with the `audio`
651        attribute set to ``audio``, which must be an AudioClip instance.
652        """
653        self.audio = audioclip
654
655    @outplace
656    def set_mask(self, mask):
657        """Set the clip's mask.
658
659        Returns a copy of the VideoClip with the mask attribute set to
660        ``mask``, which must be a greyscale (values in 0-1) VideoClip"""
661        assert mask is None or mask.ismask
662        self.mask = mask
663
664    @add_mask_if_none
665    @outplace
666    def set_opacity(self, op):
667        """Set the opacity/transparency level of the clip.
668
669        Returns a semi-transparent copy of the clip where the mask is
670        multiplied by ``op`` (any float, normally between 0 and 1).
671        """
672        self.mask = self.mask.fl_image(lambda pic: op * pic)
673
674    @apply_to_mask
675    @outplace
676    def set_position(self, pos, relative=False):
677        """Set the clip's position in compositions.
678
679        Sets the position that the clip will have when included
680        in compositions. The argument ``pos`` can be either a couple
681        ``(x,y)`` or a function ``t-> (x,y)``. `x` and `y` mark the
682        location of the top left corner of the clip, and can be
683        of several types.
684
685        Examples
686        ----------
687
688        >>> clip.set_position((45,150)) # x=45, y=150
689        >>>
690        >>> # clip horizontally centered, at the top of the picture
691        >>> clip.set_position(("center","top"))
692        >>>
693        >>> # clip is at 40% of the width, 70% of the height:
694        >>> clip.set_position((0.4,0.7), relative=True)
695        >>>
696        >>> # clip's position is horizontally centered, and moving up !
697        >>> clip.set_position(lambda t: ('center', 50+t) )
698
699        """
700        self.relative_pos = relative
701        if hasattr(pos, '__call__'):
702            self.pos = pos
703        else:
704            self.pos = lambda t: pos
705
706    # --------------------------------------------------------------
707    # CONVERSIONS TO OTHER TYPES
708
709    @convert_to_seconds(['t'])
710    def to_ImageClip(self, t=0, with_mask=True, duration=None):
711        """
712        Returns an ImageClip made out of the clip's frame at time ``t``,
713        which can be expressed in seconds (15.35), in (min, sec),
714        in (hour, min, sec), or as a string: '01:03:05.35'.
715        """
716        newclip = ImageClip(self.get_frame(t), ismask=self.ismask,
717                            duration=duration)
718        if with_mask and self.mask is not None:
719            newclip.mask = self.mask.to_ImageClip(t)
720        return newclip
721
722    def to_mask(self, canal=0):
723        """Return a mask a video clip made from the clip."""
724        if self.ismask:
725            return self
726        else:
727            newclip = self.fl_image(lambda pic:
728                                    1.0 * pic[:, :, canal] / 255)
729            newclip.ismask = True
730            return newclip
731
732    def to_RGB(self):
733        """Return a non-mask video clip made from the mask video clip."""
734        if self.ismask:
735            f = lambda pic: np.dstack(3 * [255 * pic]).astype('uint8')
736            newclip = self.fl_image(f)
737            newclip.ismask = False
738            return newclip
739        else:
740            return self
741
742    # ----------------------------------------------------------------
743    # Audio
744
745    @outplace
746    def without_audio(self):
747        """Remove the clip's audio.
748
749        Return a copy of the clip with audio set to None.
750
751        """
752        self.audio = None
753
754    @outplace
755    def afx(self, fun, *a, **k):
756        """Transform the clip's audio.
757
758        Return a new clip whose audio has been transformed by ``fun``.
759
760        """
761        self.audio = self.audio.fx(fun, *a, **k)
762
763
764class DataVideoClip(VideoClip):
765    """
766    Class of video clips whose successive frames are functions
767    of successive datasets
768
769    Parameters
770    -----------
771    data
772      A liste of datasets, each dataset being used for one frame of the clip
773
774    data_to_frame
775      A function d -> video frame, where d is one element of the list `data`
776
777    fps
778      Number of frames per second in the animation
779
780    Examples
781    ---------
782    """
783
784    def __init__(self, data, data_to_frame, fps, ismask=False,
785                 has_constant_size=True):
786        self.data = data
787        self.data_to_frame = data_to_frame
788        self.fps = fps
789        make_frame = lambda t: self.data_to_frame(self.data[int(self.fps*t)])
790        VideoClip.__init__(self, make_frame, ismask=ismask,
791                           duration=1.0*len(data)/fps,
792                           has_constant_size=has_constant_size)
793
794
795class UpdatedVideoClip(VideoClip):
796    """
797    Class of clips whose make_frame requires some objects to
798    be updated. Particularly practical in science where some
799    algorithm needs to make some steps before a new frame can
800    be generated.
801
802    UpdatedVideoClips have the following make_frame:
803
804    >>> def make_frame(t):
805    >>>     while self.world.clip_t < t:
806    >>>         world.update() # updates, and increases world.clip_t
807    >>>     return world.to_frame()
808
809    Parameters
810    -----------
811
812    world
813      An object with the following attributes:
814      - world.clip_t : the clip's time corresponding to the
815          world's state
816      - world.update() : update the world's state, (including
817        increasing world.clip_t of one time step)
818      - world.to_frame() : renders a frame depending on the world's state
819
820    ismask
821      True if the clip is a WxH mask with values in 0-1
822
823    duration
824      Duration of the clip, in seconds
825
826    """
827
828    def __init__(self, world, ismask=False, duration=None):
829        self.world = world
830
831        def make_frame(t):
832            while self.world.clip_t < t:
833                world.update()
834            return world.to_frame()
835
836        VideoClip.__init__(self, make_frame=make_frame,
837                           ismask=ismask, duration=duration)
838
839
840"""---------------------------------------------------------------------
841
842    ImageClip (base class for all 'static clips') and its subclasses
843    ColorClip and TextClip.
844    I would have liked to put these in a separate file but Python is bad
845    at cyclic imports.
846
847---------------------------------------------------------------------"""
848
849
850class ImageClip(VideoClip):
851    """Class for non-moving VideoClips.
852
853    A video clip originating from a picture. This clip will simply
854    display the given picture at all times.
855
856    Examples
857    ---------
858
859    >>> clip = ImageClip("myHouse.jpeg")
860    >>> clip = ImageClip( someArray ) # a Numpy array represent
861
862    Parameters
863    -----------
864
865    img
866      Any picture file (png, tiff, jpeg, etc.) or any array representing
867      an RGB image (for instance a frame from a VideoClip).
868
869    ismask
870      Set this parameter to `True` if the clip is a mask.
871
872    transparent
873      Set this parameter to `True` (default) if you want the alpha layer
874      of the picture (if it exists) to be used as a mask.
875
876    Attributes
877    -----------
878
879    img
880      Array representing the image of the clip.
881
882    """
883
884    def __init__(self, img, ismask=False, transparent=True,
885                 fromalpha=False, duration=None):
886        VideoClip.__init__(self, ismask=ismask, duration=duration)
887
888        if isinstance(img, string_types):
889            img = imread(img)
890
891        if len(img.shape) == 3:  # img is (now) a RGB(a) numpy array
892
893            if img.shape[2] == 4:
894                if fromalpha:
895                    img = 1.0 * img[:, :, 3] / 255
896                elif ismask:
897                    img = 1.0 * img[:, :, 0] / 255
898                elif transparent:
899                    self.mask = ImageClip(
900                        1.0 * img[:, :, 3] / 255, ismask=True)
901                    img = img[:, :, :3]
902            elif ismask:
903                img = 1.0 * img[:, :, 0] / 255
904
905        # if the image was just a 2D mask, it should arrive here
906        # unchanged
907        self.make_frame = lambda t: img
908        self.size = img.shape[:2][::-1]
909        self.img = img
910
911    def fl(self, fl, apply_to=None, keep_duration=True):
912        """General transformation filter.
913
914        Equivalent to VideoClip.fl . The result is no more an
915        ImageClip, it has the class VideoClip (since it may be animated)
916        """
917        if apply_to is None:
918            apply_to = []
919        # When we use fl on an image clip it may become animated.
920        # Therefore the result is not an ImageClip, just a VideoClip.
921        newclip = VideoClip.fl(self, fl, apply_to=apply_to,
922                               keep_duration=keep_duration)
923        newclip.__class__ = VideoClip
924        return newclip
925
926    @outplace
927    def fl_image(self, image_func, apply_to=None):
928        """Image-transformation filter.
929
930        Does the same as VideoClip.fl_image, but for ImageClip the
931        tranformed clip is computed once and for all at the beginning,
932        and not for each 'frame'.
933        """
934        if apply_to is None:
935                apply_to = []
936        arr = image_func(self.get_frame(0))
937        self.size = arr.shape[:2][::-1]
938        self.make_frame = lambda t: arr
939        self.img = arr
940
941        for attr in apply_to:
942            a = getattr(self, attr, None)
943            if a is not None:
944                new_a = a.fl_image(image_func)
945                setattr(self, attr, new_a)
946
947    @outplace
948    def fl_time(self, time_func, apply_to=None,
949                keep_duration=False):
950        """Time-transformation filter.
951
952        Applies a transformation to the clip's timeline
953        (see Clip.fl_time).
954
955        This method does nothing for ImageClips (but it may affect their
956        masks or their audios). The result is still an ImageClip.
957        """
958        if apply_to is None:
959            apply_to = ['mask', 'audio']
960        for attr in apply_to:
961            a = getattr(self, attr, None)
962            if a is not None:
963                new_a = a.fl_time(time_func)
964                setattr(self, attr, new_a)
965
966
967# ##
968#
969# The old functions to_videofile, to_gif, to_images sequences have been
970# replaced by the more explicite write_videofile, write_gif, etc.
971
972VideoClip.set_pos = deprecated_version_of(VideoClip.set_position,
973                                          'set_pos')
974VideoClip.to_videofile = deprecated_version_of(VideoClip.write_videofile,
975                                               'to_videofile')
976VideoClip.to_gif = deprecated_version_of(VideoClip.write_gif, 'to_gif')
977VideoClip.to_images_sequence = deprecated_version_of(VideoClip.write_images_sequence,
978                                                     'to_images_sequence')
979
980
981class ColorClip(ImageClip):
982    """An ImageClip showing just one color.
983
984    Parameters
985    -----------
986
987    size
988      Size (width, height) in pixels of the clip.
989
990    color
991      If argument ``ismask`` is False, ``color`` indicates
992      the color in RGB of the clip (default is black). If `ismask``
993      is True, ``color`` must be  a float between 0 and 1 (default is 1)
994
995    ismask
996      Set to true if the clip will be used as a mask.
997
998    col
999      Has been deprecated. Do not use.
1000    """
1001
1002    def __init__(self, size, color=None, ismask=False, duration=None, col=None):
1003        if col is not None:
1004            warnings.warn("The `ColorClip` parameter `col` has been deprecated."
1005                          " Please use `color` instead.", DeprecationWarning)
1006            if color is not None:
1007                warnings.warn("The arguments `color` and `col` have both been "
1008                              "passed to `ColorClip` so `col` has been ignored.",
1009                              UserWarning)
1010            else:
1011                color = col
1012        w, h = size
1013        shape = (h, w) if np.isscalar(color) else (h, w, len(color))
1014        ImageClip.__init__(self, np.tile(color, w * h).reshape(shape),
1015                           ismask=ismask, duration=duration)
1016
1017
1018class TextClip(ImageClip):
1019    """Class for autogenerated text clips.
1020
1021    Creates an ImageClip originating from a script-generated text image.
1022    Requires ImageMagick.
1023
1024    Parameters
1025    -----------
1026
1027    txt
1028      A string of the text to write. Can be replaced by argument
1029      ``filename``.
1030
1031    filename
1032      The name of a file in which there is the text to write.
1033      Can be provided instead of argument ``txt``
1034
1035    size
1036      Size of the picture in pixels. Can be auto-set if
1037      method='label', but mandatory if method='caption'.
1038      the height can be None, it will then be auto-determined.
1039
1040    bg_color
1041      Color of the background. See ``TextClip.list('color')``
1042      for a list of acceptable names.
1043
1044    color
1045      Color of the text. See ``TextClip.list('color')`` for a
1046      list of acceptable names.
1047
1048    font
1049      Name of the font to use. See ``TextClip.list('font')`` for
1050      the list of fonts you can use on your computer.
1051
1052    stroke_color
1053      Color of the stroke (=contour line) of the text. If ``None``,
1054      there will be no stroke.
1055
1056    stroke_width
1057      Width of the stroke, in pixels. Can be a float, like 1.5.
1058
1059    method
1060      Either 'label' (default, the picture will be autosized so as to fit
1061      exactly the size) or 'caption' (the text will be drawn in a picture
1062      with fixed size provided with the ``size`` argument). If `caption`,
1063      the text will be wrapped automagically (sometimes it is buggy, not
1064      my fault, complain to the ImageMagick crew) and can be aligned or
1065      centered (see parameter ``align``).
1066
1067    kerning
1068      Changes the default spacing between letters. For
1069      instance ``kerning=-1`` will make the letters 1 pixel nearer from
1070      ach other compared to the default spacing.
1071
1072    align
1073      center | East | West | South | North . Will only work if ``method``
1074      is set to ``caption``
1075
1076    transparent
1077      ``True`` (default) if you want to take into account the
1078      transparency in the image.
1079
1080    """
1081
1082    def __init__(self, txt=None, filename=None, size=None, color='black',
1083                 bg_color='transparent', fontsize=None, font='Courier',
1084                 stroke_color=None, stroke_width=1, method='label',
1085                 kerning=None, align='center', interline=None,
1086                 tempfilename=None, temptxt=None,
1087                 transparent=True, remove_temp=True,
1088                 print_cmd=False):
1089
1090        if txt is not None:
1091            if temptxt is None:
1092                temptxt_fd, temptxt = tempfile.mkstemp(suffix='.txt')
1093                try:  # only in Python3 will this work
1094                    os.write(temptxt_fd, bytes(txt, 'UTF8'))
1095                except TypeError:  # oops, fall back to Python2
1096                    os.write(temptxt_fd, txt)
1097                os.close(temptxt_fd)
1098            txt = '@' + temptxt
1099        else:
1100            # use a file instead of a text.
1101            txt = "@%" + filename
1102
1103        if size is not None:
1104            size = ('' if size[0] is None else str(size[0]),
1105                    '' if size[1] is None else str(size[1]))
1106
1107        cmd = ([get_setting("IMAGEMAGICK_BINARY"),
1108               "-background", bg_color,
1109                "-fill", color,
1110                "-font", font])
1111
1112        if fontsize is not None:
1113            cmd += ["-pointsize", "%d" % fontsize]
1114        if kerning is not None:
1115            cmd += ["-kerning", "%0.1f" % kerning]
1116        if stroke_color is not None:
1117            cmd += ["-stroke", stroke_color, "-strokewidth",
1118                    "%.01f" % stroke_width]
1119        if size is not None:
1120            cmd += ["-size", "%sx%s" % (size[0], size[1])]
1121        if align is not None:
1122            cmd += ["-gravity", align]
1123        if interline is not None:
1124            cmd += ["-interline-spacing", "%d" % interline]
1125
1126        if tempfilename is None:
1127            tempfile_fd, tempfilename = tempfile.mkstemp(suffix='.png')
1128            os.close(tempfile_fd)
1129
1130        cmd += ["%s:%s" % (method, txt),
1131                "-type", "truecolormatte", "PNG32:%s" % tempfilename]
1132
1133        if print_cmd:
1134            print(" ".join(cmd))
1135
1136        try:
1137            subprocess_call(cmd, logger=None)
1138        except (IOError, OSError) as err:
1139            error = ("MoviePy Error: creation of %s failed because of the "
1140                     "following error:\n\n%s.\n\n." % (filename, str(err))
1141                     + ("This error can be due to the fact that ImageMagick "
1142                        "is not installed on your computer, or (for Windows "
1143                        "users) that you didn't specify the path to the "
1144                        "ImageMagick binary in file conf.py, or that the path "
1145                        "you specified is incorrect"))
1146            raise IOError(error)
1147
1148        ImageClip.__init__(self, tempfilename, transparent=transparent)
1149        self.txt = txt
1150        self.color = color
1151        self.stroke_color = stroke_color
1152
1153        if remove_temp:
1154            if os.path.exists(tempfilename):
1155                os.remove(tempfilename)
1156            if os.path.exists(temptxt):
1157                os.remove(temptxt)
1158
1159    @staticmethod
1160    def list(arg):
1161        """Returns the list of all valid entries for the argument of
1162        ``TextClip`` given (can be ``font``, ``color``, etc...) """
1163
1164        popen_params = {"stdout": sp.PIPE,
1165                        "stderr": DEVNULL,
1166                        "stdin": DEVNULL}
1167
1168        if os.name == "nt":
1169            popen_params["creationflags"] = 0x08000000
1170
1171        process = sp.Popen([get_setting("IMAGEMAGICK_BINARY"),
1172                            '-list', arg], **popen_params)
1173        result = process.communicate()[0]
1174        lines = result.splitlines()
1175
1176        if arg == 'font':
1177            return [l.decode('UTF-8')[8:] for l in lines if l.startswith(b"  Font:")]
1178        elif arg == 'color':
1179            return [l.split(b" ")[0] for l in lines[2:]]
1180        else:
1181            raise Exception("Moviepy:Error! Argument must equal "
1182                            "'font' or 'color'")
1183
1184    @staticmethod
1185    def search(string, arg):
1186        """Returns the of all valid entries which contain ``string`` for the
1187           argument ``arg`` of ``TextClip``, for instance
1188
1189           >>> # Find all the available fonts which contain "Courier"
1190           >>> print ( TextClip.search('Courier', 'font') )
1191
1192        """
1193        string = string.lower()
1194        names_list = TextClip.list(arg)
1195        return [name for name in names_list if string in name.lower()]
1196