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