From 4356fcd596cda574e7e4ead9178b457cd4a3280a Mon Sep 17 00:00:00 2001 From: Tom Burrows Date: Thu, 8 Oct 2020 21:09:05 +0100 Subject: [PATCH] Renaming methods, parameters and variables for increased usability, consistency and readability (#1170) See PR description for details: https://github.com/Zulko/moviepy/pull/1170 --- README.rst | 4 +- docs/examples/quick_recipes.rst | 2 +- docs/getting_started/audioclips.rst | 2 +- docs/getting_started/compositing.rst | 30 +-- docs/getting_started/effects.rst | 26 +- docs/getting_started/efficient_moviepy.rst | 2 +- docs/getting_started/quick_presentation.rst | 2 +- docs/getting_started/videoclips.rst | 14 +- docs/ref/code_origanization.rst | 2 +- examples/compo_from_image.py | 8 +- examples/dancing_knights.py | 12 +- examples/example_with_sound.py | 17 +- examples/headblur.py | 8 +- examples/logo.py | 4 +- examples/masked_credits.py | 4 +- examples/moving_letters.py | 10 +- examples/painting_effect.py | 8 +- examples/soundtrack.py | 4 +- examples/star_worms.py | 30 +-- examples/the_end.py | 8 +- examples/ukulele_concerto.py | 2 +- moviepy/Clip.py | 159 +++++------ moviepy/audio/AudioClip.py | 36 ++- moviepy/audio/fx/audio_fadein.py | 8 +- moviepy/audio/fx/audio_fadeout.py | 8 +- moviepy/audio/fx/audio_loop.py | 12 +- moviepy/audio/fx/audio_normalize.py | 4 +- moviepy/audio/fx/volumex.py | 10 +- moviepy/audio/io/preview.py | 24 +- moviepy/audio/io/readers.py | 35 ++- moviepy/audio/tools/cuts.py | 15 +- moviepy/decorators.py | 106 ++++---- moviepy/editor.py | 2 +- moviepy/tools.py | 64 ++--- moviepy/video/VideoClip.py | 238 ++++++++-------- .../video/compositing/CompositeVideoClip.py | 56 ++-- moviepy/video/compositing/concatenate.py | 53 ++-- moviepy/video/compositing/on_color.py | 4 +- moviepy/video/compositing/transitions.py | 27 +- moviepy/video/fx/accel_decel.py | 8 +- moviepy/video/fx/blackwhite.py | 4 +- moviepy/video/fx/blink.py | 20 +- moviepy/video/fx/colorx.py | 4 +- moviepy/video/fx/crop.py | 4 +- moviepy/video/fx/even_size.py | 8 +- moviepy/video/fx/fadein.py | 10 +- moviepy/video/fx/fadeout.py | 10 +- moviepy/video/fx/freeze.py | 2 +- moviepy/video/fx/freeze_region.py | 10 +- moviepy/video/fx/gamma_corr.py | 4 +- moviepy/video/fx/headblur.py | 22 +- moviepy/video/fx/invert_colors.py | 4 +- moviepy/video/fx/loop.py | 4 +- moviepy/video/fx/lum_contrast.py | 8 +- moviepy/video/fx/make_loopable.py | 13 +- moviepy/video/fx/margin.py | 28 +- moviepy/video/fx/mask_and.py | 6 +- moviepy/video/fx/mask_color.py | 24 +- moviepy/video/fx/mask_or.py | 6 +- moviepy/video/fx/mirror_x.py | 2 +- moviepy/video/fx/mirror_y.py | 2 +- moviepy/video/fx/painting.py | 4 +- moviepy/video/fx/resize.py | 95 +++---- moviepy/video/fx/rotate.py | 43 +-- moviepy/video/fx/scroll.py | 14 +- moviepy/video/fx/speedx.py | 6 +- moviepy/video/fx/supersample.py | 16 +- moviepy/video/fx/time_mirror.py | 2 +- moviepy/video/io/ImageSequenceClip.py | 40 +-- moviepy/video/io/VideoFileClip.py | 20 +- moviepy/video/io/ffmpeg_reader.py | 68 ++--- moviepy/video/io/ffmpeg_tools.py | 105 +++----- moviepy/video/io/ffmpeg_writer.py | 36 +-- moviepy/video/io/gif_writers.py | 48 ++-- moviepy/video/io/html_tools.py | 6 +- moviepy/video/io/preview.py | 20 +- moviepy/video/io/sliders.py | 14 +- moviepy/video/tools/credits.py | 253 +++++++++--------- moviepy/video/tools/cuts.py | 180 +++++++------ moviepy/video/tools/drawing.py | 114 ++++---- moviepy/video/tools/segmenting.py | 22 +- moviepy/video/tools/subtitles.py | 61 +++-- moviepy/video/tools/tracking.py | 23 +- setup.py | 4 +- tests/test_PR.py | 14 +- tests/test_TextClip.py | 12 +- tests/test_VideoClip.py | 36 +-- tests/test_VideoFileClip.py | 6 +- tests/test_compositing.py | 6 +- tests/test_fx.py | 8 +- tests/test_issues.py | 42 +-- tests/test_misc.py | 8 +- tests/test_resourcerelease.py | 2 +- tests/test_resourcereleasedemo.py | 2 +- tests/test_tools.py | 12 +- tests/test_videotools.py | 15 +- 96 files changed, 1335 insertions(+), 1285 deletions(-) diff --git a/README.rst b/README.rst index 53dae0d7f..960b5b58f 100644 --- a/README.rst +++ b/README.rst @@ -38,8 +38,8 @@ In this example we open a video file, select the subclip between t=50s and t=60s # Make the text. Many more options are available. txt_clip = ( TextClip("My Holidays 2013",fontsize=70,color='white') - .set_position('center') - .set_duration(10) ) + .with_position('center') + .with_duration(10) ) result = CompositeVideoClip([video, txt_clip]) # Overlay text on video result.write_videofile("myHolidays_edited.webm",fps=25) # Many options... diff --git a/docs/examples/quick_recipes.rst b/docs/examples/quick_recipes.rst index 5a0c6f80e..f8cb520b9 100644 --- a/docs/examples/quick_recipes.rst +++ b/docs/examples/quick_recipes.rst @@ -19,7 +19,7 @@ Blurring all frames of a video return gaussian_filter(image.astype(float), sigma=2) clip = VideoFileClip("my_video.mp4") - clip_blurred = clip.fl_image( blur ) + clip_blurred = clip.image_transform( blur ) clip_blurred.write_videofile("blurred_video.mp4") diff --git a/docs/getting_started/audioclips.rst b/docs/getting_started/audioclips.rst index df17e148b..f80891597 100644 --- a/docs/getting_started/audioclips.rst +++ b/docs/getting_started/audioclips.rst @@ -37,4 +37,4 @@ Exporting and previewing audio clips You can also export assign an audio clip as the soundtrack of a video clip with :: - videoclip2 = videoclip.set_audio(my_audioclip) + videoclip2 = videoclip.with_audio(my_audioclip) diff --git a/docs/getting_started/compositing.rst b/docs/getting_started/compositing.rst index 6447d40fa..fa6466c98 100644 --- a/docs/getting_started/compositing.rst +++ b/docs/getting_started/compositing.rst @@ -65,19 +65,19 @@ Starting and stopping times In a CompositionClip, all the clips start to play at a time that is specified by the ``clip.start`` attribute. You can set this starting time as follows: :: - clip1 = clip1.set_start(5) # start after 5 seconds + clip1 = clip1.with_start(5) # start after 5 seconds So for instance your composition will look like :: video = CompositeVideoClip([clip1, # starts at t=0 - clip2.set_start(5), # start at t=5s - clip3.set_start(9)]) # start at t=9s + clip2.with_start(5), # start at t=5s + clip3.with_start(9)]) # start at t=9s In the example above, maybe ``clip2`` will start before ``clip1`` is over. In this case you can make ``clip2`` appear with a *fade-in* effect of one second: :: video = CompositeVideoClip([clip1, # starts at t=0 - clip2.set_start(5).crossfadein(1), - clip3.set_start(9).crossfadein(1.5)]) + clip2.with_start(5).crossfadein(1), + clip3.with_start(9).crossfadein(1.5)]) Positioning clips """""""""""""""""" @@ -85,26 +85,26 @@ Positioning clips If ``clip2`` and ``clip3`` are smaller than ``clip1``, you can decide where they will appear in the composition by setting their position. Here we indicate the coordinates of the top-left pixel of the clips: :: video = CompositeVideoClip([clip1, - clip2.set_position((45,150)), - clip3.set_position((90,100))]) + clip2.with_position((45,150)), + clip3.with_position((90,100))]) There are many ways to specify the position: :: - clip2.set_position((45,150)) # x=45, y=150 , in pixels + clip2.with_position((45,150)) # x=45, y=150 , in pixels - clip2.set_position("center") # automatically centered + clip2.with_position("center") # automatically centered # clip2 is horizontally centered, and at the top of the picture - clip2.set_position(("center","top")) + clip2.with_position(("center","top")) # clip2 is vertically centered, at the left of the picture - clip2.set_position(("left","center")) + clip2.with_position(("left","center")) # clip2 is at 40% of the width, 70% of the height of the screen: - clip2.set_position((0.4,0.7), relative=True) + clip2.with_position((0.4,0.7), relative=True) # clip2's position is horizontally centered, and moving down! - clip2.set_position(lambda t: ('center', 50+t) ) + clip2.with_position(lambda t: ('center', 50+t) ) When indicating the position keep in mind that the ``y`` coordinate has its zero at the top of the picture: @@ -129,6 +129,6 @@ If you want to make a custom audiotrack from several audio sources: audioc clips # ... make some audio clips aclip1, aclip2, aclip3 concat = concatenate_audioclips([aclip1, aclip2, aclip3]) compo = CompositeAudioClip([aclip1.volumex(1.2), - aclip2.set_start(5), # start at t=5s - aclip3.set_start(9)]) + aclip2.with_start(5), # start at t=5s + aclip3.with_start(9)]) diff --git a/docs/getting_started/effects.rst b/docs/getting_started/effects.rst index 8afb387f8..37c473406 100644 --- a/docs/getting_started/effects.rst +++ b/docs/getting_started/effects.rst @@ -5,15 +5,15 @@ Clips transformations and effects There are several categories of clip modifications in MoviePy: -- The very common methods to change the attributes of a clip: ``clip.set_duration``, ``clip.set_audio``, ``clip.set_mask``, ``clip.set_start`` etc. +- The very common methods to change the attributes of a clip: ``clip.with_duration``, ``clip.with_audio``, ``clip.with_mask``, ``clip.with_start`` etc. - The already-implemented effects. Core effects like ``clip.subclip(t1, t2)`` (keep only the cut between t1 and t2), which are very important, are implemented as class methods. More advanced and less common effects like ``loop`` (makes the clip play in a loop) or ``time_mirror`` (makes the clip play backwards) are placed in the special modules ``moviepy.video.fx`` and ``moviepy.audio.fx`` and are applied with the ``clip.fx`` method, for instance ``clip.fx(time_mirror)`` (makes the clip play backwards), ``clip.fx(black_white)`` (turns the clip black and white), etc. - The effects that you can create yourself. using All these effects have in common that they are **not inplace**: they do NOT modify the original clip, instead they create a new clip that is a version of the former with the changes applied. For instance: :: my_clip = VideoFileClip("some_file.mp4") - my_clip.set_start(t=5) # does nothing, changes are lost - my_new_clip = my_clip.set_start(t=5) # good! + my_clip.with_start(t=5) # does nothing, changes are lost + my_new_clip = my_clip.with_start(t=5) # good! Also, when you write ``clip.resize(width=640)``, it does not immediately apply the effect to all the frames of the clip, but only to the first frame: all the other frames will be resized only when required (that is, when you will write the whole clip to a file of when you will preview it). Said otherwise, creating a new clip is neither time nor memory hungry, all the computations happen during the final rendering. @@ -61,27 +61,27 @@ For convenience, when you use ``moviepy.editor``, frequently used methods such a Methods to create custom effects ---------------------------------- -clip.fl +clip.transform """""""" -You can modify a clip as you want using custom *filters* with ``clip.fl_time``, ``clip.fl_image``, and more generally with ``clip.fl``. +You can modify a clip as you want using custom *filters* with ``clip.time_transform``, ``clip.image_transform``, and more generally with ``clip.transform``. -You can change the timeline of the clip with ``clip.fl_time`` like this: :: +You can change the timeline of the clip with ``clip.time_transform`` like this: :: - modifiedClip1 = my_clip.fl_time(lambda t: 3*t) - modifiedClip2 = my_clip.fl_time(lambda t: 1+sin(t)) + modifiedClip1 = my_clip.time_transform(lambda t: 3*t) + modifiedClip2 = my_clip.time_transform(lambda t: 1+sin(t)) Now the clip ``modifiedClip1`` plays the same as ``my_clip``, only three times faster, while ``modifiedClip2`` will play ``my_clip`` by oscillating between the times t=0s and t=2s. Note that in the last case you have created a clip of infinite duration (which is not a problem for the moment). -You can also modify the display of a clip with ``clip.fl_image``. The following takes a clip and inverts the green and blue channels of the frames: :: +You can also modify the display of a clip with ``clip.image_transform``. The following takes a clip and inverts the green and blue channels of the frames: :: def invert_green_blue(image): return image[:,:,[0,2,1]] - modifiedClip = my_clip.fl_image( invert_green_blue ) + modifiedClip = my_clip.image_transform( invert_green_blue ) -Finally, you may want to process the clip by taking into account both the time and the frame picture. This is possible with the method ``clip.fl(filter)``. The filter must be a function which takes two arguments and returns a picture. the fist argument is a ``get_frame`` method (i.e. a function ``g(t)`` which given a time returns the clip's frame at that time), and the second argument is the time. :: +Finally, you may want to process the clip by taking into account both the time and the frame picture. This is possible with the method ``clip.transform(filter)``. The filter must be a function which takes two arguments and returns a picture. The first argument is a ``get_frame`` method (i.e. a function ``gf(t)`` which given a time returns the clip's frame at that time), and the second argument is the time. :: def scroll(get_frame, t): """ @@ -92,10 +92,10 @@ Finally, you may want to process the clip by taking into account both the time a frame_region = frame[int(t):int(t)+360,:] return frame_region - modifiedClip = my_clip.fl( scroll ) + modifiedClip = my_clip.transform( scroll ) This will scroll down the clip, with a constant height of 360 pixels. -When programming a new effect, whenever it is possible, prefer using ``fl_time`` and ``fl_image`` instead of ``fl`` if possible when implementing new effects. The reason is that, when these effects are applied to +When programming a new effect, whenever it is possible, prefer using ``time_transform`` and ``image_transform`` instead of ``transform`` when implementing new effects. The reason is that, when these effects are applied to ImageClips, MoviePy will recognize that these methods do not need to be applied to each frame, which will result in faster renderings. diff --git a/docs/getting_started/efficient_moviepy.rst b/docs/getting_started/efficient_moviepy.rst index 191cb3a2a..af7a70074 100644 --- a/docs/getting_started/efficient_moviepy.rst +++ b/docs/getting_started/efficient_moviepy.rst @@ -43,7 +43,7 @@ So, the rules of thumb are: * Call ``close()`` on any clip that you **construct** once you have finished using it and have also finished using any clip that was derived from it. * Also close any clips you create through ``AudioFileClip.coreader()``. * Even if you close a ``CompositeVideoClip`` instance, you still need to close the clips it was created from. - * Otherwise, if you have a clip that was created by deriving it from from another clip (e.g. by calling ``set_mask()``), then generally you shouldn't close it. Closing the original clip will also close the copy. + * Otherwise, if you have a clip that was created by deriving it from from another clip (e.g. by calling ``with_mask()``), then generally you shouldn't close it. Closing the original clip will also close the copy. Clips act as `context managers `_. This means you can use them with a ``with`` statement, and they will automatically be closed at the end of the block, even if there is diff --git a/docs/getting_started/quick_presentation.rst b/docs/getting_started/quick_presentation.rst index 3dd27f62a..fe1000a03 100644 --- a/docs/getting_started/quick_presentation.rst +++ b/docs/getting_started/quick_presentation.rst @@ -51,7 +51,7 @@ In a typical MoviePy script, you load video or audio files, modify them, put the txt_clip = TextClip("My Holidays 2013",fontsize=70,color='white') # Say that you want it to appear 10s at the center of the screen - txt_clip = txt_clip.set_position('center').set_duration(10) + txt_clip = txt_clip.with_position('center').with_duration(10) # Overlay the text clip on the first video clip video = CompositeVideoClip([clip, txt_clip]) diff --git a/docs/getting_started/videoclips.rst b/docs/getting_started/videoclips.rst index 9127604c2..274aeaa17 100644 --- a/docs/getting_started/videoclips.rst +++ b/docs/getting_started/videoclips.rst @@ -130,13 +130,13 @@ The fundamental difference between masks and standard clips is that standard cli When you create or load a clip that you will use as a mask you need to declare it: :: - maskclip = VideoClip(makeframe, duration=4, ismask=True) - maskclip = ImageClip("my_mask.jpeg", ismask=True) - maskclip = VideoFileClip("myvideo.mp4", ismask=True) + maskclip = VideoClip(makeframe, duration=4, is_mask=True) + maskclip = ImageClip("my_mask.jpeg", is_mask=True) + maskclip = VideoFileClip("myvideo.mp4", is_mask=True) In the case of video and image files, if these are not already black and white they will be converted automatically. -Then you attach this mask to a clip (which must have the same dimensions) with ``myclip.set_mask(maskclip)``. +Then you attach this mask to a clip (which must have the same dimensions) with ``myclip.with_mask(maskclip)``. Some image formats like PNG support transparency with an *alpha layer*, which MoviePy will use as a mask: :: @@ -166,12 +166,12 @@ To write a clip as a video file, use :: MoviePy has default codec names for the most common file extensions. If you want to use exotic formats or if you are not happy with the defaults you can provide the codec with ``codec='mpeg4'`` for instance. There are many many options when you are writing a video (bitrate, parameters of the audio writing, file size optimization, number of processors to use, etc.). Please refer to :py:meth:`~moviepy.video.VideoClip.VideoClip.write_videofile` for more. -Sometimes it is impossible for MoviePy to guess the ``duration`` attribute of the clip (keep in mind that some clips, like ImageClips displaying a picture, have *a priori* an infinite duration). Then, the ``duration`` must be set manually with ``clip.set_duration``: :: +Sometimes it is impossible for MoviePy to guess the ``duration`` attribute of the clip (keep in mind that some clips, like ImageClips displaying a picture, have *args priori* an infinite duration). Then, the ``duration`` must be set manually with ``clip.with_duration``: :: # Make a video showing a flower for 5 seconds my_clip = ImageClip("flower.jpeg") # has infinite duration my_clip.write_videofile("flower.mp4") # Will fail! NO DURATION! - my_clip.set_duration(5).write_videofile("flower.mp4") # works! + my_clip.with_duration(5).write_videofile("flower.mp4") # works! Animated GIFs @@ -197,4 +197,4 @@ You can write a frame to an image file with :: myclip.save_frame("frame.png") # by default the first frame is extracted myclip.save_frame("frame.jpeg", t='01:00:00') # frame at time t=1h -If the clip has a mask it will be exported as the alpha layer of the image unless you specify ``withmask=False``. +If the clip has a mask it will be exported as the alpha layer of the image unless you specify ``with_mask=False``. diff --git a/docs/ref/code_origanization.rst b/docs/ref/code_origanization.rst index ffdf189ab..83153c37a 100644 --- a/docs/ref/code_origanization.rst +++ b/docs/ref/code_origanization.rst @@ -9,7 +9,7 @@ At the root of the project you have everything required for the packaging and in The folder ``moviepy/`` the classes and modules relative to the video and the audio are clearly separated into two subfolders ``video/`` and ``audio/``. In ``moviepy/`` you will find all the classes, functions and decorations which are useful to both submodules ``audio`` and ``video``: -- ``Clip.py`` defines the base object for ``AudioClip`` and ``VideoClip`` and the simple methods that can be used by both, like ``clip.subclip``, ``clip.set_duration``, etc. +- ``Clip.py`` defines the base object for ``AudioClip`` and ``VideoClip`` and the simple methods that can be used by both, like ``clip.subclip``, ``clip.with_duration``, etc. - Files ``config.py`` and ``config_defaults.py`` store the default paths to the external programs FFMPEG and ImageMagick. - ``decorators.py`` provides very useful decorators that automatize some tasks, like the fact that some effects, when applied to a clip, should also be applied to it's mask, or to its audio track. - ``tools.py`` provides misc. functions that are useful everywhere in the library, like a standardized call to subprocess, a time converter, a standardized way to print messages in the console, etc. diff --git a/examples/compo_from_image.py b/examples/compo_from_image.py index 1bdc00e02..122f50380 100644 --- a/examples/compo_from_image.py +++ b/examples/compo_from_image.py @@ -1,11 +1,11 @@ from moviepy.editor import * -from moviepy.video.tools.segmenting import findObjects +from moviepy.video.tools.segmenting import find_objects # Load the image specifying the regions. im = ImageClip("../../ultracompositing/motif.png") # Loacate the regions, return a list of ImageClips -regions = findObjects(im) +regions = find_objects(im) # Load 7 clips from the US National Parks. Public Domain :D @@ -24,8 +24,8 @@ # fit each clip into its region comp_clips = [ - c.resize(r.size).set_mask(r.mask).set_pos(r.screenpos) - for c, r in zip(clips, regions) + clip.resize(r.size).with_mask(r.mask).set_pos(r.screenpos) + for clip, r in zip(clips, regions) ] cc = CompositeVideoClip(comp_clips, im.size) diff --git a/examples/dancing_knights.py b/examples/dancing_knights.py index 3d24d132d..c39dcc242 100644 --- a/examples/dancing_knights.py +++ b/examples/dancing_knights.py @@ -78,7 +78,7 @@ clips_array([[edited_left, edited_right]]) .fadein(1) .fadeout(1) - .set_audio(audio) + .with_audio(audio) .subclip(0.3) ) @@ -88,18 +88,18 @@ txt_title = ( TextClip( "15th century dancing\n(hypothetical)", - fontsize=70, + font_size=70, font="Century-Schoolbook-Roman", color="white", ) .margin(top=15, opacity=0) - .set_position(("center", "top")) + .with_position(("center", "top")) ) title = ( CompositeVideoClip([dancing_knights.to_ImageClip(), txt_title]) .fadein(0.5) - .set_duration(3.5) + .with_duration(3.5) ) @@ -127,13 +127,13 @@ txt_credits, color="white", font="Century-Schoolbook-Roman", - fontsize=35, + font_size=35, kerning=-2, interline=-1, bg_color="black", size=title.size, ) - .set_duration(2.5) + .with_duration(2.5) .fadein(0.5) .fadeout(0.5) ) diff --git a/examples/example_with_sound.py b/examples/example_with_sound.py index 6adb2bfdc..e0ee33411 100644 --- a/examples/example_with_sound.py +++ b/examples/example_with_sound.py @@ -20,32 +20,37 @@ # MAKE THE LEFT CLIP : cut, crop, add a mask mask = color_split( - (2 * W / 3, H), p1=(W / 3, H), p2=(2 * W / 3, 0), col1=1, col2=0, grad_width=2 + (2 * W / 3, H), + p1=(W / 3, H), + p2=(2 * W / 3, 0), + color_1=1, + color_2=0, + gradient_width=2, ) -mask_clip = ImageClip(mask, ismask=True) +mask_clip = ImageClip(mask, is_mask=True) clip_left = ( main_clip.coreader() .subclip(0, duration) .crop(x1=60, x2=60 + 2 * W / 3) - .set_mask(mask_clip) + .with_mask(mask_clip) ) # MAKE THE RIGHT CLIP : cut, crop, add a mask mask = color_split( - (2 * W / 3, H), p1=(2, H), p2=(W / 3 + 2, 0), col1=0, col2=1, grad_width=2 + (2 * W / 3, H), p1=(2, H), p2=(W / 3 + 2, 0), color_1=0, color_2=1, gradient_width=2 ) -mask_clip = ImageClip(mask, ismask=True) +mask_clip = ImageClip(mask, is_mask=True) clip_right = ( main_clip.coreader() .subclip(21, 21 + duration) .crop(x1=70, x2=70 + 2 * W / 3) - .set_mask(mask_clip) + .with_mask(mask_clip) ) diff --git a/examples/headblur.py b/examples/headblur.py index 83cca724e..93db09276 100644 --- a/examples/headblur.py +++ b/examples/headblur.py @@ -22,8 +22,8 @@ # IF THE MANUAL TRACKING HAS BEEN PREVIOUSLY DONE, # LOAD THE TRACKING DATA AND CONVERT IT TO FUNCTIONS x(t),fy(t) -with open("../../chaplin_txy.dat", "r") as f: - fx, fy = to_fxfy(pickle.load(f)) +with open("../../chaplin_txy.dat", "r") as file: + fx, fy = to_fxfy(pickle.load(file)) # BLUR CHAPLIN'S HEAD IN THE CLIP @@ -39,13 +39,13 @@ size=clip.size, bg_color="grey20", font="Century-Schoolbook-Italic", - fontsize=40, + font_size=40, ) # Concatenate the Chaplin clip with the text clip, add audio -final = concatenate_videoclips([clip_blurred, txt.set_duration(3)]).set_audio( +final = concatenate_videoclips([clip_blurred, txt.with_duration(3)]).with_audio( clip.audio ) diff --git a/examples/logo.py b/examples/logo.py index 72a47b843..829744e71 100644 --- a/examples/logo.py +++ b/examples/logo.py @@ -14,13 +14,13 @@ def f(t, size, a=np.pi / 3, thickness=20): return biGradientScreen(size, center, v, 0.6, 0.0) -logo = ImageClip("../../videos/logo_descr.png").resize(width=w / 2).set_mask(mask) +logo = ImageClip("../../videos/logo_descr.png").resize(width=w / 2).with_mask(mask) screen = logo.on_color(moviesize, color=(0, 0, 0), pos="center") shade = ColorClip(moviesize, color=(0, 0, 0)) mask_frame = lambda t: f(t, moviesize, duration) -shade.mask = VideoClip(ismask=True, get_frame=mask_frame) +shade.mask = VideoClip(is_mask=True, get_frame=mask_frame) cc = CompositeVideoClip([im.set_pos(2 * ["center"]), shade], size=moviesize) diff --git a/examples/masked_credits.py b/examples/masked_credits.py index a76657985..8d2989bfd 100644 --- a/examples/masked_credits.py +++ b/examples/masked_credits.py @@ -14,7 +14,7 @@ # Load the mountain mask made with GIMP -mountainmask = ImageClip("../../credits/mountainMask2.png", ismask=True) +mountainmask = ImageClip("../../credits/mountainMask2.png", is_mask=True) # Generate the credits from a text file credits = credits1("../../credits/credits.txt", 3 * clip.w / 4) @@ -22,6 +22,6 @@ # Make the credits scroll. Here, 10 pixels per second -final = CompositeVideoClip([clip, scrolling_credits, clip.set_mask(mountainmask)]) +final = CompositeVideoClip([clip, scrolling_credits, clip.with_mask(mountainmask)]) final.subclip(8, 10).write_videofile("../../credits_mountains.avi") diff --git a/examples/moving_letters.py b/examples/moving_letters.py index 53f417f7d..f9ce53278 100644 --- a/examples/moving_letters.py +++ b/examples/moving_letters.py @@ -1,15 +1,15 @@ import numpy as np from moviepy.editor import * -from moviepy.video.tools.segmenting import findObjects +from moviepy.video.tools.segmenting import find_objects # WE CREATE THE TEXT THAT IS GOING TO MOVE, WE CENTER IT. screensize = (720, 460) txtClip = TextClip( - "Cool effect", color="white", font="Amiri-Bold", kerning=5, fontsize=100 + "Cool effect", color="white", font="Amiri-Bold", kerning=5, font_size=100 ) -cvc = CompositeVideoClip([txtClip.set_pos("center")], size=screensize) +cvc = CompositeVideoClip([txtClip.with_position("center")], size=screensize) # THE NEXT FOUR FUNCTIONS DEFINE FOUR WAYS OF MOVING THE LETTERS @@ -50,9 +50,9 @@ def vortexout(screenpos, i, nletters): ) -# WE USE THE PLUGIN findObjects TO LOCATE AND SEPARATE EACH LETTER +# WE USE THE PLUGIN find_objects TO LOCATE AND SEPARATE EACH LETTER -letters = findObjects(cvc) # a list of ImageClips +letters = find_objects(cvc) # a list of ImageClips # WE ANIMATE THE LETTERS diff --git a/examples/painting_effect.py b/examples/painting_effect.py index ed9264553..b61d52e7d 100644 --- a/examples/painting_effect.py +++ b/examples/painting_effect.py @@ -5,7 +5,7 @@ # WE TAKE THE SUBCLIPS WHICH ARE 2 SECONDS BEFORE & AFTER THE FREEZE charade = VideoFileClip("../../videos/charade.mp4") -tfreeze = cvsecs(19.21) # Time of the freeze, 19'21 +tfreeze = convert_to_seconds(19.21) # Time of the freeze, 19'21 # when using several subclips of a same clip, it can be faster # to create 'coreaders' of the clip (=other entrance points). @@ -18,12 +18,12 @@ im_freeze = charade.to_ImageClip(tfreeze) painting = charade.fx(vfx.painting, saturation=1.6, black=0.006).to_ImageClip(tfreeze) -txt = TextClip("Audrey", font="Amiri-regular", fontsize=35) +txt = TextClip("Audrey", font="Amiri-regular", font_size=35) painting_txt = ( CompositeVideoClip([painting, txt.set_pos((10, 180))]) .add_mask() - .set_duration(3) + .with_duration(3) .crossfadein(0.5) .crossfadeout(0.5) ) @@ -35,7 +35,7 @@ # FINAL CLIP AND RENDERING final_clip = concatenate_videoclips( - [clip_before, painting_fading.set_duration(3), clip_after] + [clip_before, painting_fading.with_duration(3), clip_after] ) final_clip.write_videofile( diff --git a/examples/soundtrack.py b/examples/soundtrack.py index 1f4081ffe..057b996c0 100644 --- a/examples/soundtrack.py +++ b/examples/soundtrack.py @@ -3,7 +3,7 @@ # We load a movie and replace the sound with some music: -movie = VideoFileClip("../../videos/dam.mov").set_audio( +movie = VideoFileClip("../../videos/dam.mov").with_audio( AudioFileClip("../../sounds/startars.ogg") ) @@ -12,6 +12,6 @@ # it will freeze on the last frame and wait for the clip to finish. # If you don't want that, uncomment the next line: -# ~ movie.audio = movie.audio.set_duration(movie.duration) +# ~ movie.audio = movie.audio.with_duration(movie.duration) movie.write_videofile("../../test_soundtrack.avi", codec="mpeg4") diff --git a/examples/star_worms.py b/examples/star_worms.py index f6c940619..ae18db18e 100644 --- a/examples/star_worms.py +++ b/examples/star_worms.py @@ -48,7 +48,7 @@ clip_txt = TextClip( - txt, color="white", align="West", fontsize=25, font="Xolonium-Bold", method="label" + txt, color="white", align="West", font_size=25, font="Xolonium-Bold", method="label" ) @@ -56,23 +56,23 @@ txt_speed = 27 fl = lambda gf, t: gf(t)[int(txt_speed * t) : int(txt_speed * t) + h, :] -moving_txt = clip_txt.fl(fl, apply_to=["mask"]) +moving_txt = clip_txt.transform(fl, apply_to=["mask"]) # ADD A VANISHING EFFECT ON THE TEXT WITH A GRADIENT MASK grad = color_gradient( - moving_txt.size, p1=(0, 2 * h / 3), p2=(0, h / 4), col1=0.0, col2=1.0 + moving_txt.size, p1=(0, 2 * h / 3), p2=(0, h / 4), color_1=0.0, color_2=1.0 ) -gradmask = ImageClip(grad, ismask=True) +gradmask = ImageClip(grad, is_mask=True) fl = lambda pic: np.minimum(pic, gradmask.img) -moving_txt.mask = moving_txt.mask.fl_image(fl) +moving_txt.mask = moving_txt.mask.image_transform(fl) # WARP THE TEXT INTO A TRAPEZOID (PERSPECTIVE EFFECT) -def trapzWarp(pic, cx, cy, ismask=False): +def trapzWarp(pic, cx, cy, is_mask=False): """ Complicated function (will be latex packaged as a fx) """ Y, X = pic.shape[:2] src = np.array([[0, 0], [X, 0], [X, Y], [0, Y]]) @@ -80,19 +80,19 @@ def trapzWarp(pic, cx, cy, ismask=False): tform = tf.ProjectiveTransform() tform.estimate(src, dst) im = tf.warp(pic, tform.inverse, output_shape=(Y, X)) - return im if ismask else (im * 255).astype("uint8") + return im if is_mask else (im * 255).astype("uint8") fl_im = lambda pic: trapzWarp(pic, 0.2, 0.3) -fl_mask = lambda pic: trapzWarp(pic, 0.2, 0.3, ismask=True) -warped_txt = moving_txt.fl_image(fl_im) -warped_txt.mask = warped_txt.mask.fl_image(fl_mask) +fl_mask = lambda pic: trapzWarp(pic, 0.2, 0.3, is_mask=True) +warped_txt = moving_txt.image_transform(fl_im) +warped_txt.mask = warped_txt.mask.image_transform(fl_mask) # BACKGROUND IMAGE, DARKENED AT 60% stars = ImageClip("../../videos/stars.jpg") -stars_darkened = stars.fl_image(lambda pic: (0.6 * pic).astype("int16")) +stars_darkened = stars.image_transform(lambda pic: (0.6 * pic).astype("int16")) # COMPOSE THE MOVIE @@ -104,7 +104,7 @@ def trapzWarp(pic, cx, cy, ismask=False): # WRITE TO A FILE -final.set_duration(8).write_videofile("starworms.avi", fps=5) +final.with_duration(8).write_videofile("starworms.avi", fps=5) # This script is heavy (30s of computations to render 8s of video) @@ -125,7 +125,7 @@ def trapzWarp(pic, cx, cy, ismask=False): def annotate(clip, txt, txt_color="white", bg_color=(0, 0, 255)): """ Writes a text at the bottom of the clip. """ - txtclip = TextClip(txt, fontsize=20, font="Ubuntu-bold", color=txt_color) + txtclip = TextClip(txt, font_size=20, font="Ubuntu-bold", color=txt_color) txtclip = txtclip.on_color( (clip.w, txtclip.h + 6), color=(0, 0, 255), pos=(6, "center") @@ -133,7 +133,7 @@ def annotate(clip, txt, txt_color="white", bg_color=(0, 0, 255)): cvc = CompositeVideoClip([clip, txtclip.set_pos((0, "bottom"))]) - return cvc.set_duration(clip.duration) + return cvc.with_duration(clip.duration) def resizeCenter(clip): @@ -164,7 +164,7 @@ def composeCenter(clip): "We generate a text image.", ), ( - composeCenter(moving_txt.set_mask(None)).subclip(6, 9), + composeCenter(moving_txt.with_mask(None)).subclip(6, 9), "We scroll the text by cropping a moving region of it.", ), ( diff --git a/examples/the_end.py b/examples/the_end.py index 940463b5c..8c38ee72d 100644 --- a/examples/the_end.py +++ b/examples/the_end.py @@ -12,15 +12,15 @@ screensize=(clip.w, clip.h), center=(clip.w / 2, clip.h / 4), radius=max(0, int(800 - 200 * t)), - col1=1, - col2=0, + color=1, + bg_color=0, blur=4, ) the_end = TextClip( - "The End", font="Amiri-bold", color="white", fontsize=70 -).set_duration(clip.duration) + "The End", font="Amiri-bold", color="white", font_size=70 +).with_duration(clip.duration) final = CompositeVideoClip([the_end.set_pos("center"), clip], size=clip.size) diff --git a/examples/ukulele_concerto.py b/examples/ukulele_concerto.py index 5e2482c52..e4c6da896 100644 --- a/examples/ukulele_concerto.py +++ b/examples/ukulele_concerto.py @@ -27,7 +27,7 @@ # A CLIP WITH A TEXT AND A BLACK SEMI-OPAQUE BACKGROUND txt = TextClip( - "V. Zulkoninov - Ukulele Sonata", font="Amiri-regular", color="white", fontsize=24 + "V. Zulkoninov - Ukulele Sonata", font="Amiri-regular", color="white", font_size=24 ) txt_col = txt.on_color( diff --git a/moviepy/Clip.py b/moviepy/Clip.py index c1318ef93..670cefb88 100644 --- a/moviepy/Clip.py +++ b/moviepy/Clip.py @@ -12,7 +12,7 @@ from moviepy.decorators import ( apply_to_audio, apply_to_mask, - convert_to_seconds, + convert_parameter_to_seconds, outplace, requires_duration, use_clip_fps_by_default, @@ -57,7 +57,7 @@ def __init__(self): self.memoize = False self.memoized_t = None - self.memoize_frame = None + self.memoized_frame = None def copy(self): """Shallow copy of the clip. @@ -70,15 +70,15 @@ def copy(self): clip.subclip, etc.) """ - newclip = copy(self) + new_clip = copy(self) if hasattr(self, "audio"): - newclip.audio = copy(self.audio) + new_clip.audio = copy(self.audio) if hasattr(self, "mask"): - newclip.mask = copy(self.mask) + new_clip.mask = copy(self.mask) - return newclip + return new_clip - @convert_to_seconds(["t"]) + @convert_parameter_to_seconds(["t"]) def get_frame(self, t): """ Gets a numpy array representing the RGB picture of the clip at time t @@ -97,16 +97,16 @@ def get_frame(self, t): # print(t) return self.make_frame(t) - def fl(self, fun, apply_to=None, keep_duration=True): + def transform(self, func, apply_to=None, keep_duration=True): """General processing of a clip. Returns a new Clip whose frames are a transformation - (through function ``fun``) of the frames of the current clip. + (through function ``func``) of the frames of the current clip. Parameters ----------- - fun + func A function with signature (gf,t -> frame) where ``gf`` will represent the current clip's ``get_frame`` method, i.e. ``gf`` is a function (t->image). Parameter `t` is a time @@ -116,7 +116,7 @@ def fl(self, fun, apply_to=None, keep_duration=True): apply_to Can be either ``'mask'``, or ``'audio'``, or ``['mask','audio']``. - Specifies if the filter ``fl`` should also be applied to the + Specifies if the filter should also be applied to the audio or the mask of the clip, if any. keep_duration @@ -126,50 +126,52 @@ def fl(self, fun, apply_to=None, keep_duration=True): Examples -------- - In the following ``newclip`` a 100 pixels-high clip whose video + In the following ``new_clip`` a 100 pixels-high clip whose video content scrolls from the top to the bottom of the frames of ``clip`` at 50 pixels per second. - >>> fl = lambda gf, t : gf(t)[int(50 * t) : int(50 * t) + 100, :] - >>> newclip = clip.fl(fl, apply_to='mask') + >>> filter = lambda get_frame,t : get_frame(t)[int(t):int(t)+50, :] + >>> new_clip = clip.transform(filter, apply_to='mask') """ if apply_to is None: apply_to = [] # mf = copy(self.make_frame) - newclip = self.set_make_frame(lambda t: fun(self.get_frame, t)) + new_clip = self.with_make_frame(lambda t: func(self.get_frame, t)) if not keep_duration: - newclip.duration = None - newclip.end = None + new_clip.duration = None + new_clip.end = None if isinstance(apply_to, str): apply_to = [apply_to] - for attr in apply_to: - a = getattr(newclip, attr, None) - if a is not None: - new_a = a.fl(fun, keep_duration=keep_duration) - setattr(newclip, attr, new_a) + for attribute in apply_to: + attribute_value = getattr(new_clip, attribute, None) + if attribute_value is not None: + new_attribute_value = attribute_value.transform( + func, keep_duration=keep_duration + ) + setattr(new_clip, attribute, new_attribute_value) - return newclip + return new_clip - def fl_time(self, t_func, apply_to=None, keep_duration=False): + def time_transform(self, time_func, apply_to=None, keep_duration=False): """ Returns a Clip instance playing the content of the current clip but with a modified timeline, time ``t`` being replaced by another - time `t_func(t)`. + time `time_func(t)`. Parameters ----------- - t_func: - A function ``t-> new_t`` + time_func: + A function ``t -> new_t`` apply_to: Can be either 'mask', or 'audio', or ['mask','audio']. - Specifies if the filter ``fl`` should also be applied to the + Specifies if the filter ``transform`` should also be applied to the audio or the mask of the clip, if any. keep_duration: @@ -180,17 +182,19 @@ def fl_time(self, t_func, apply_to=None, keep_duration=False): -------- >>> # plays the clip (and its mask and sound) twice faster - >>> newclip = clip.fl_time(lambda: 2*t, apply_to=['mask', 'audio']) + >>> new_clip = clip.time_transform(lambda: 2*t, apply_to=['mask', 'audio']) >>> >>> # plays the clip starting at t=3, and backwards: - >>> newclip = clip.fl_time(lambda: 3-t) + >>> new_clip = clip.time_transform(lambda: 3-t) """ if apply_to is None: apply_to = [] - return self.fl( - lambda gf, t: gf(t_func(t)), apply_to, keep_duration=keep_duration + return self.transform( + lambda get_frame, t: get_frame(time_func(t)), + apply_to, + keep_duration=keep_duration, ) def fx(self, func, *args, **kwargs): @@ -199,11 +203,11 @@ def fx(self, func, *args, **kwargs): Returns the result of ``func(self, *args, **kwargs)``. for instance - >>> newclip = clip.fx(resize, 0.2, method="bilinear") + >>> new_clip = clip.fx(resize, 0.2, method="bilinear") is equivalent to - >>> newclip = resize(clip, 0.2, method="bilinear") + >>> new_clip = resize(clip, 0.2, method="bilinear") The motivation of fx is to keep the name of the effect near its parameters when the effects are chained: @@ -218,9 +222,9 @@ def fx(self, func, *args, **kwargs): @apply_to_mask @apply_to_audio - @convert_to_seconds(["t"]) + @convert_parameter_to_seconds(["t"]) @outplace - def set_start(self, t, change_end=True): + def with_start(self, t, change_end=True): """ Returns a copy of the clip, with the ``start`` attribute set to ``t``, which can be expressed in seconds (15.35), in (min, sec), @@ -247,9 +251,9 @@ def set_start(self, t, change_end=True): @apply_to_mask @apply_to_audio - @convert_to_seconds(["t"]) + @convert_parameter_to_seconds(["t"]) @outplace - def set_end(self, t): + def with_end(self, t): """ Returns a copy of the clip, with the ``end`` attribute set to ``t``, which can be expressed in seconds (15.35), in (min, sec), @@ -268,9 +272,9 @@ def set_end(self, t): @apply_to_mask @apply_to_audio - @convert_to_seconds(["t"]) + @convert_parameter_to_seconds(["t"]) @outplace - def set_duration(self, duration, change_end=True): + def with_duration(self, duration, change_end=True): """ Returns a copy of the clip, with the ``duration`` attribute set to ``t``, which can be expressed in seconds (15.35), in (min, sec), @@ -291,14 +295,14 @@ def set_duration(self, duration, change_end=True): self.start = self.end - duration @outplace - def set_make_frame(self, make_frame): + def with_make_frame(self, make_frame): """ Sets a ``make_frame`` attribute for the clip. Useful for setting arbitrary/complicated videoclips. """ self.make_frame = make_frame - def set_fps(self, fps, change_duration=False): + def with_fps(self, fps, change_duration=False): """Returns a copy of the clip with a new default fps for functions like write_videofile, iterframe, etc. If ``change_duration=True``, then the video speed will change to match the @@ -316,19 +320,18 @@ def set_fps(self, fps, change_duration=False): return newclip @outplace - def set_ismask(self, ismask): - """ Says wheter the clip is a mask or not (ismask is a boolean)""" - self.ismask = ismask + def with_is_mask(self, is_mask): + """ Says wheter the clip is a mask or not (is_mask is a boolean)""" + self.is_mask = is_mask @outplace - def set_memoize(self, memoize): + def with_memoize(self, memoize): """ Sets wheter the clip should keep the last frame read in memory """ self.memoize = memoize - @convert_to_seconds(["t"]) + @convert_parameter_to_seconds(["t"]) def is_playing(self, t): """ - If t is a time, returns true if t is between the start and the end of the clip. t can be expressed in seconds (15.35), in (min, sec), in (hour, min, sec), or as a string: '01:03:05.35'. @@ -357,24 +360,24 @@ def is_playing(self, t): return (t >= self.start) and ((self.end is None) or (t < self.end)) - @convert_to_seconds(["t_start", "t_end"]) + @convert_parameter_to_seconds(["start_time", "end_time"]) @apply_to_mask @apply_to_audio - def subclip(self, t_start=0, t_end=None): + def subclip(self, start_time=0, end_time=None): """ Returns a clip playing the content of the current clip - between times ``t_start`` and ``t_end``, which can be expressed + between times ``start_time`` and ``end_time``, which can be expressed in seconds (15.35), in (min, sec), in (hour, min, sec), or as a string: '01:03:05.35'. - If ``t_end`` is not provided, it is assumed to be the duration + If ``end_time`` is not provided, it is assumed to be the duration of the clip (potentially infinite). - If ``t_end`` is a negative value, it is reset to - ``clip.duration + t_end. ``. For instance: :: + If ``end_time`` is a negative value, it is reset to + ``clip.duration + end_time. ``. For instance: :: >>> # cut the last two seconds of the clip: - >>> newclip = clip.subclip(0,-2) + >>> new_clip = clip.subclip(0,-2) - If ``t_end`` is provided or if the clip has a duration attribute, + If ``end_time`` is provided or if the clip has a duration attribute, the duration of the returned clip is set automatically. The ``mask`` and ``audio`` of the resulting subclip will be @@ -382,71 +385,73 @@ def subclip(self, t_start=0, t_end=None): they exist. """ - if t_start < 0: + if start_time < 0: # Make this more Python-like, a negative value means to move # backward from the end of the clip - t_start = self.duration + t_start # Remember t_start is negative + start_time = self.duration + start_time # Remember start_time is negative - if (self.duration is not None) and (t_start > self.duration): + if (self.duration is not None) and (start_time > self.duration): raise ValueError( - "t_start (%.02f) " % t_start + "start_time (%.02f) " % start_time + "should be smaller than the clip's " + "duration (%.02f)." % self.duration ) - newclip = self.fl_time(lambda t: t + t_start, apply_to=[]) + new_clip = self.time_transform(lambda t: t + start_time, apply_to=[]) - if (t_end is None) and (self.duration is not None): + if (end_time is None) and (self.duration is not None): - t_end = self.duration + end_time = self.duration - elif (t_end is not None) and (t_end < 0): + elif (end_time is not None) and (end_time < 0): if self.duration is None: print( "Error: subclip with negative times (here %s)" - % (str((t_start, t_end))) + % (str((start_time, end_time))) + " can only be extracted from clips with a ``duration``" ) else: - t_end = self.duration + t_end + end_time = self.duration + end_time - if t_end is not None: + if end_time is not None: - newclip.duration = t_end - t_start - newclip.end = newclip.start + newclip.duration + new_clip.duration = end_time - start_time + new_clip.end = new_clip.start + new_clip.duration - return newclip + return new_clip @apply_to_mask @apply_to_audio - @convert_to_seconds(["ta", "tb"]) - def cutout(self, ta, tb): + @convert_parameter_to_seconds(["start_time", "end_time"]) + def cutout(self, start_time, end_time): """ Returns a clip playing the content of the current clip but - skips the extract between ``ta`` and ``tb``, which can be + skips the extract between ``start_time`` and ``end_time``, which can be expressed in seconds (15.35), in (min, sec), in (hour, min, sec), or as a string: '01:03:05.35'. If the original clip has a ``duration`` attribute set, the duration of the returned clip is automatically computed as - `` duration - (tb - ta)``. + `` duration - (end_time - start_time)``. The resulting clip's ``audio`` and ``mask`` will also be cutout if they exist. """ - newclip = self.fl_time(lambda t: t + (t >= ta) * (tb - ta)) + new_clip = self.time_transform( + lambda t: t + (t >= start_time) * (end_time - start_time) + ) if self.duration is not None: - return newclip.set_duration(self.duration - (tb - ta)) + return new_clip.with_duration(self.duration - (end_time - start_time)) else: - return newclip + return new_clip @requires_duration @use_clip_fps_by_default diff --git a/moviepy/audio/AudioClip.py b/moviepy/audio/AudioClip.py index f55452ed2..797e7c8b9 100644 --- a/moviepy/audio/AudioClip.py +++ b/moviepy/audio/AudioClip.py @@ -77,18 +77,18 @@ def iter_chunks( if chunk_duration is not None: chunksize = int(chunk_duration * fps) - totalsize = int(fps * self.duration) + total_size = int(fps * self.duration) - nchunks = totalsize // chunksize + 1 + nchunks = total_size // chunksize + 1 - pospos = np.linspace(0, totalsize, nchunks + 1, endpoint=True, dtype=int) + positions = np.linspace(0, total_size, nchunks + 1, endpoint=True, dtype=int) for i in logger.iter_bar(chunk=list(range(nchunks))): - size = pospos[i + 1] - pospos[i] + size = positions[i + 1] - positions[i] assert size <= chunksize - tt = (1.0 / fps) * np.arange(pospos[i], pospos[i + 1]) + timings = (1.0 / fps) * np.arange(positions[i], positions[i + 1]) yield self.to_soundarray( - tt, nbytes=nbytes, quantize=quantize, fps=fps, buffersize=chunksize + timings, nbytes=nbytes, quantize=quantize, fps=fps, buffersize=chunksize ) @requires_duration @@ -133,8 +133,6 @@ def to_soundarray( quantize=quantize, nbytes=nbytes) for ttc in tt_chunks]) """ - # print tt.max() - tt.min(), tt.min(), tt.max() - snd_array = self.get_frame(tt) if quantize: @@ -307,19 +305,19 @@ def __init__(self, clips): Clip.__init__(self) self.clips = clips - ends = [c.end for c in self.clips] - self.nchannels = max([c.nchannels for c in self.clips]) - if not any([(e is None) for e in ends]): + ends = [clip.end for clip in self.clips] + self.nchannels = max([clip.nchannels for clip in self.clips]) + if not any([(end is None) for end in ends]): self.duration = max(ends) self.end = max(ends) def make_frame(t): - played_parts = [c.is_playing(t) for c in self.clips] + played_parts = [clip.is_playing(t) for clip in self.clips] sounds = [ - c.get_frame(t - c.start) * np.array([part]).T - for c, part in zip(self.clips, played_parts) + clip.get_frame(t - clip.start) * np.array([part]).T + for clip, part in zip(self.clips, played_parts) if (part is not False) ] @@ -338,12 +336,12 @@ def concatenate_audioclips(clips): """ The clip with the highest FPS will be the FPS of the result clip. """ - durations = [c.duration for c in clips] - tt = np.cumsum([0] + durations) # start times, and end time. - newclips = [c.set_start(t) for c, t in zip(clips, tt)] + durations = [clip.duration for clip in clips] + timings = np.cumsum([0] + durations) # start times, and end time. + newclips = [clip.with_start(t) for clip, t in zip(clips, timings)] - result = CompositeAudioClip(newclips).set_duration(tt[-1]) + result = CompositeAudioClip(newclips).with_duration(timings[-1]) - fpss = [c.fps for c in clips if getattr(c, "fps", None)] + fpss = [clip.fps for clip in clips if getattr(clip, "fps", None)] result.fps = max(fpss) if fpss else None return result diff --git a/moviepy/audio/fx/audio_fadein.py b/moviepy/audio/fx/audio_fadein.py index 82dfb76a7..119ab08da 100644 --- a/moviepy/audio/fx/audio_fadein.py +++ b/moviepy/audio/fx/audio_fadein.py @@ -8,8 +8,8 @@ def audio_fadein(clip, duration): """Return an audio (or video) clip that is first mute, then the sound arrives progressively over ``duration`` seconds.""" - def fading(gf, t): - gft = gf(t) + def fading(get_frame, t): + frame = get_frame(t) if np.isscalar(t): factor = min(1.0 * t / duration, 1) @@ -17,6 +17,6 @@ def fading(gf, t): else: factor = np.minimum(1.0 * t / duration, 1) factor = np.vstack([factor, factor]).T - return factor * gft + return factor * frame - return clip.fl(fading, keep_duration=True) + return clip.transform(fading, keep_duration=True) diff --git a/moviepy/audio/fx/audio_fadeout.py b/moviepy/audio/fx/audio_fadeout.py index dc008ad7f..dc72e12f8 100644 --- a/moviepy/audio/fx/audio_fadeout.py +++ b/moviepy/audio/fx/audio_fadeout.py @@ -9,8 +9,8 @@ def audio_fadeout(clip, duration): """Return a sound clip where the sound fades out progressively over ``duration`` seconds at the end of the clip.""" - def fading(gf, t): - gft = gf(t) + def fading(get_frame, t): + frame = get_frame(t) if np.isscalar(t): factor = min(1.0 * (clip.duration - t) / duration, 1) @@ -18,6 +18,6 @@ def fading(gf, t): else: factor = np.minimum(1.0 * (clip.duration - t) / duration, 1) factor = np.vstack([factor, factor]).T - return factor * gft + return factor * frame - return clip.fl(fading, keep_duration=True) + return clip.transform(fading, keep_duration=True) diff --git a/moviepy/audio/fx/audio_loop.py b/moviepy/audio/fx/audio_loop.py index fe34c02de..9781b3978 100644 --- a/moviepy/audio/fx/audio_loop.py +++ b/moviepy/audio/fx/audio_loop.py @@ -1,11 +1,11 @@ from ..AudioClip import concatenate_audioclips -def audio_loop(audioclip, nloops=None, duration=None): +def audio_loop(audioclip, n_loops=None, duration=None): """Loops over an audio clip. Returns an audio clip that plays the given clip either - `nloops` times, or during `duration` seconds. + `n_loops` times, or during `duration` seconds. Examples ======== @@ -14,15 +14,15 @@ def audio_loop(audioclip, nloops=None, duration=None): >>> videoclip = VideoFileClip('myvideo.mp4') >>> music = AudioFileClip('music.ogg') >>> audio = afx.audio_loop( music, duration=videoclip.duration) - >>> videoclip.set_audio(audio) + >>> videoclip.with_audio(audio) """ if duration is not None: - nloops = int(duration / audioclip.duration) + 1 - return concatenate_audioclips(nloops * [audioclip]).set_duration(duration) + n_loops = int(duration / audioclip.duration) + 1 + return concatenate_audioclips(n_loops * [audioclip]).with_duration(duration) else: - return concatenate_audioclips(nloops * [audioclip]) + return concatenate_audioclips(n_loops * [audioclip]) diff --git a/moviepy/audio/fx/audio_normalize.py b/moviepy/audio/fx/audio_normalize.py index 0bdb4a863..ca63d5e01 100644 --- a/moviepy/audio/fx/audio_normalize.py +++ b/moviepy/audio/fx/audio_normalize.py @@ -18,5 +18,5 @@ def audio_normalize(clip): """ - mv = clip.max_volume() - return volumex(clip, 1 / mv) + max_volume = clip.max_volume() + return volumex(clip, 1 / max_volume) diff --git a/moviepy/audio/fx/volumex.py b/moviepy/audio/fx/volumex.py index 9633f95f7..f00c1c468 100644 --- a/moviepy/audio/fx/volumex.py +++ b/moviepy/audio/fx/volumex.py @@ -12,8 +12,10 @@ def volumex(clip, factor): Examples --------- - >>> newclip = volumex(clip, 2.0) # doubles audio volume - >>> newclip = clip.fx( volumex, 0.5) # half audio, use with fx - >>> newclip = clip.volumex(2) # only if you used "moviepy.editor" + >>> new_clip = volumex(clip, 2.0) # doubles audio volume + >>> new_clip = clip.fx( volumex, 0.5) # half audio, use with fx + >>> new_clip = clip.volumex(2) # only if you used "moviepy.editor" """ - return clip.fl(lambda gf, t: factor * gf(t), keep_duration=True) + return clip.transform( + lambda get_frame, t: factor * get_frame(t), keep_duration=True + ) diff --git a/moviepy/audio/io/preview.py b/moviepy/audio/io/preview.py index 170d21e31..39fc7f658 100644 --- a/moviepy/audio/io/preview.py +++ b/moviepy/audio/io/preview.py @@ -10,7 +10,9 @@ @requires_duration -def preview(clip, fps=22050, buffersize=4000, nbytes=2, audioFlag=None, videoFlag=None): +def preview( + clip, fps=22050, buffersize=4000, nbytes=2, audio_flag=None, video_flag=None +): """ Plays the sound clip with pygame. @@ -33,7 +35,7 @@ def preview(clip, fps=22050, buffersize=4000, nbytes=2, audioFlag=None, videoFla Number of bytes to encode the sound: 1 for 8bit sound, 2 for 16bit, 4 for 32bit sound. 2 bytes is fine. - audioFlag, videoFlag: + audio_flag, video_flag: Instances of class threading events that are used to synchronize video and audio during ``VideoClip.preview()``. @@ -44,23 +46,23 @@ def preview(clip, fps=22050, buffersize=4000, nbytes=2, audioFlag=None, videoFla pg.mixer.init(fps, -8 * nbytes, clip.nchannels, 1024) totalsize = int(fps * clip.duration) pospos = np.array(list(range(0, totalsize, buffersize)) + [totalsize]) - tt = (1.0 / fps) * np.arange(pospos[0], pospos[1]) - sndarray = clip.to_soundarray(tt, nbytes=nbytes, quantize=True) + timings = (1.0 / fps) * np.arange(pospos[0], pospos[1]) + sndarray = clip.to_soundarray(timings, nbytes=nbytes, quantize=True) chunk = pg.sndarray.make_sound(sndarray) - if (audioFlag is not None) and (videoFlag is not None): - audioFlag.set() - videoFlag.wait() + if (audio_flag is not None) and (video_flag is not None): + audio_flag.set() + video_flag.wait() channel = chunk.play() for i in range(1, len(pospos) - 1): - tt = (1.0 / fps) * np.arange(pospos[i], pospos[i + 1]) - sndarray = clip.to_soundarray(tt, nbytes=nbytes, quantize=True) + timings = (1.0 / fps) * np.arange(pospos[i], pospos[i + 1]) + sndarray = clip.to_soundarray(timings, nbytes=nbytes, quantize=True) chunk = pg.sndarray.make_sound(sndarray) while channel.get_queue(): time.sleep(0.003) - if videoFlag is not None: - if not videoFlag.is_set(): + if video_flag is not None: + if not video_flag.is_set(): channel.stop() del channel return diff --git a/moviepy/audio/io/readers.py b/moviepy/audio/io/readers.py index e50e44ff6..04db3a014 100644 --- a/moviepy/audio/io/readers.py +++ b/moviepy/audio/io/readers.py @@ -54,8 +54,8 @@ def __init__( self.filename = filename self.nbytes = nbytes self.fps = fps - self.f = "s%dle" % (8 * nbytes) - self.acodec = "pcm_s%dle" % (8 * nbytes) + self.format = "s%dle" % (8 * nbytes) + self.codec = "pcm_s%dle" % (8 * nbytes) self.nchannels = nchannels infos = ffmpeg_parse_infos(filename, decode_file=decode_file) self.duration = infos["duration"] @@ -74,16 +74,16 @@ def __init__( self.initialize() self.buffer_around(1) - def initialize(self, starttime=0): + def initialize(self, start_time=0): """ Opens the file, creates the pipe. """ self.close() # if any - if starttime != 0: - offset = min(1, starttime) + if start_time != 0: + offset = min(1, start_time) i_arg = [ "-ss", - "%.05f" % (starttime - offset), + "%.05f" % (start_time - offset), "-i", self.filename, "-vn", @@ -100,9 +100,9 @@ def initialize(self, starttime=0): "-loglevel", "error", "-f", - self.f, + self.format, "-acodec", - self.acodec, + self.codec, "-ar", "%d" % self.fps, "-ac", @@ -123,23 +123,22 @@ def initialize(self, starttime=0): self.proc = sp.Popen(cmd, **popen_params) - self.pos = np.round(self.fps * starttime) + self.pos = np.round(self.fps * start_time) def skip_chunk(self, chunksize): - s = self.proc.stdout.read(self.nchannels * chunksize * self.nbytes) + _ = self.proc.stdout.read(self.nchannels * chunksize * self.nbytes) self.proc.stdout.flush() self.pos = self.pos + chunksize def read_chunk(self, chunksize): # chunksize is not being autoconverted from float to int chunksize = int(round(chunksize)) - L = self.nchannels * chunksize * self.nbytes - s = self.proc.stdout.read(L) - dt = {1: "int8", 2: "int16", 4: "int32"}[self.nbytes] + s = self.proc.stdout.read(self.nchannels * chunksize * self.nbytes) + data_type = {1: "int8", 2: "int16", 4: "int32"}[self.nbytes] if hasattr(np, "frombuffer"): - result = np.frombuffer(s, dtype=dt) + result = np.frombuffer(s, dtype=data_type) else: - result = np.fromstring(s, dtype=dt) + result = np.fromstring(s, dtype=data_type) result = (1.0 * result / 2 ** (8 * self.nbytes - 1)).reshape( (int(len(result) / self.nchannels), self.nchannels) ) @@ -230,14 +229,14 @@ def get_frame(self, tt): # read the frame in the buffer return self.buffer[ind - self.buffer_startframe] - def buffer_around(self, framenumber): + def buffer_around(self, frame_number): """ - Fills the buffer with frames, centered on ``framenumber`` + Fills the buffer with frames, centered on ``frame_number`` if possible """ # start-frame for the buffer - new_bufferstart = max(0, framenumber - self.buffersize // 2) + new_bufferstart = max(0, frame_number - self.buffersize // 2) if self.buffer is not None: current_f_end = self.buffer_startframe + self.buffersize diff --git a/moviepy/audio/tools/cuts.py b/moviepy/audio/tools/cuts.py index c057f5cf9..d7de2bcdc 100644 --- a/moviepy/audio/tools/cuts.py +++ b/moviepy/audio/tools/cuts.py @@ -1,20 +1,19 @@ import numpy as np -def find_audio_period(aclip, t_min=0.1, t_max=2, t_res=0.01): +def find_audio_period(clip, min_time=0.1, max_time=2, time_resolution=0.01): """Finds the period, in seconds of an audioclip. - The beat is then given by bpm = 60/T - t_min and _tmax are bounds for the returned value, t_res + min_time and max_time are bounds for the returned value, time_resolution is the numerical precision """ - chunksize = int(t_res * aclip.fps) - chunk_duration = 1.0 * chunksize / aclip.fps + chunksize = int(time_resolution * clip.fps) + chunk_duration = 1.0 * chunksize / clip.fps # v denotes the list of volumes - v = np.array([(c ** 2).sum() for c in aclip.iter_chunks(chunksize)]) + v = np.array([(chunk ** 2).sum() for chunk in clip.iter_chunks(chunksize)]) v = v - v.mean() corrs = np.correlate(v, v, mode="full")[-len(v) :] - corrs[: int(t_min / chunk_duration)] = 0 - corrs[int(t_max / chunk_duration) :] = 0 + corrs[: int(min_time / chunk_duration)] = 0 + corrs[int(max_time / chunk_duration) :] = 0 return chunk_duration * np.argmax(corrs) diff --git a/moviepy/decorators.py b/moviepy/decorators.py index bf991dda9..92bea2958 100644 --- a/moviepy/decorators.py +++ b/moviepy/decorators.py @@ -5,95 +5,98 @@ import decorator -from moviepy.tools import cvsecs +from moviepy.tools import convert_to_seconds @decorator.decorator -def outplace(f, clip, *a, **k): - """ Applies f(clip.copy(), *a, **k) and returns clip.copy()""" - newclip = clip.copy() - f(newclip, *a, **k) - return newclip +def outplace(func, clip, *args, **kwargs): + """ Applies func(clip.copy(), *args, **kwargs) and returns clip.copy()""" + new_clip = clip.copy() + func(new_clip, *args, **kwargs) + return new_clip @decorator.decorator -def convert_masks_to_RGB(f, clip, *a, **k): +def convert_masks_to_RGB(func, clip, *args, **kwargs): """ If the clip is a mask, convert it to RGB before running the function """ - if clip.ismask: + if clip.is_mask: clip = clip.to_RGB() - return f(clip, *a, **k) + return func(clip, *args, **kwargs) @decorator.decorator -def apply_to_mask(f, clip, *a, **k): - """This decorator will apply the same function f to the mask of - the clip created with f""" +def apply_to_mask(func, clip, *args, **kwargs): + """This decorator will apply the same function func to the mask of + the clip created with func""" - newclip = f(clip, *a, **k) - if getattr(newclip, "mask", None): - newclip.mask = f(newclip.mask, *a, **k) - return newclip + new_clip = func(clip, *args, **kwargs) + if getattr(new_clip, "mask", None): + new_clip.mask = func(new_clip.mask, *args, **kwargs) + return new_clip @decorator.decorator -def apply_to_audio(f, clip, *a, **k): - """This decorator will apply the function f to the audio of - the clip created with f""" +def apply_to_audio(func, clip, *args, **kwargs): + """This decorator will apply the function func to the audio of + the clip created with func""" - newclip = f(clip, *a, **k) - if getattr(newclip, "audio", None): - newclip.audio = f(newclip.audio, *a, **k) - return newclip + new_clip = func(clip, *args, **kwargs) + if getattr(new_clip, "audio", None): + new_clip.audio = func(new_clip.audio, *args, **kwargs) + return new_clip @decorator.decorator -def requires_duration(f, clip, *a, **k): +def requires_duration(func, clip, *args, **kwargs): """ Raise an error if the clip has no duration.""" if clip.duration is None: raise ValueError("Attribute 'duration' not set") else: - return f(clip, *a, **k) + return func(clip, *args, **kwargs) @decorator.decorator -def audio_video_fx(f, clip, *a, **k): +def audio_video_fx(func, clip, *args, **kwargs): """Use an audio function on a video/audio clip - This decorator tells that the function f (audioclip -> audioclip) + This decorator tells that the function func (audioclip -> audioclip) can be also used on a video clip, at which case it returns a videoclip with unmodified video and modified audio. """ if hasattr(clip, "audio"): - newclip = clip.copy() + new_clip = clip.copy() if clip.audio is not None: - newclip.audio = f(clip.audio, *a, **k) - return newclip + new_clip.audio = func(clip.audio, *args, **kwargs) + return new_clip else: - return f(clip, *a, **k) + return func(clip, *args, **kwargs) def preprocess_args(fun, varnames): """ Applies fun to variables in varnames before launching the function """ - def wrapper(f, *a, **kw): - func_code = f.__code__ + def wrapper(func, *args, **kwargs): + func_code = func.__code__ names = func_code.co_varnames - new_a = [ + new_args = [ fun(arg) if (name in varnames) and (arg is not None) else arg - for (arg, name) in zip(a, names) + for (arg, name) in zip(args, names) ] - new_kw = {k: fun(v) if k in varnames else v for (k, v) in kw.items()} - return f(*new_a, **new_kw) + new_kwargs = { + kwarg: fun(value) if kwarg in varnames else value + for (kwarg, value) in kwargs.items() + } + return func(*new_args, **new_kwargs) return decorator.decorator(wrapper) -def convert_to_seconds(varnames): +def convert_parameter_to_seconds(varnames): """Converts the specified variables to seconds""" - return preprocess_args(cvsecs, varnames) + return preprocess_args(convert_to_seconds, varnames) def convert_path_to_string(varnames): @@ -102,18 +105,18 @@ def convert_path_to_string(varnames): @decorator.decorator -def add_mask_if_none(f, clip, *a, **k): +def add_mask_if_none(func, clip, *args, **kwargs): """ Add a mask to the clip if there is none. """ if clip.mask is None: clip = clip.add_mask() - return f(clip, *a, **k) + return func(clip, *args, **kwargs) @decorator.decorator -def use_clip_fps_by_default(f, clip, *a, **k): - """ Will use clip.fps if no fps=... is provided in **k """ +def use_clip_fps_by_default(func, clip, *args, **kwargs): + """ Will use clip.fps if no fps=... is provided in **kwargs """ - def fun(fps): + def find_fps(fps): if fps is not None: return fps elif getattr(clip, "fps", None): @@ -122,14 +125,19 @@ def fun(fps): "No 'fps' (frames per second) attribute specified" " for function %s and the clip has no 'fps' attribute. Either" " provide e.g. fps=24 in the arguments of the function, or define" - " the clip's fps with `clip.fps=24`" % f.__name__ + " the clip's fps with `clip.fps=24`" % func.__name__ ) - func_code = f.__code__ + func_code = func.__code__ names = func_code.co_varnames[1:] - new_a = [fun(arg) if (name == "fps") else arg for (arg, name) in zip(a, names)] - new_kw = {k: fun(v) if k == "fps" else v for (k, v) in k.items()} + new_args = [ + find_fps(arg) if (name == "fps") else arg for (arg, name) in zip(args, names) + ] + new_kwargs = { + kwarg: find_fps(value) if kwarg == "fps" else value + for (kwarg, value) in kwargs.items() + } - return f(clip, *new_a, **new_kw) + return func(clip, *new_args, **new_kwargs) diff --git a/moviepy/editor.py b/moviepy/editor.py index 8e5515c35..2be06caf7 100644 --- a/moviepy/editor.py +++ b/moviepy/editor.py @@ -46,7 +46,7 @@ import moviepy.video.tools as videotools import moviepy.video.io.ffmpeg_tools as ffmpeg_tools from .video.io.html_tools import ipython_display -from .tools import cvsecs +from .tools import convert_to_seconds try: from .video.io.sliders import sliders diff --git a/moviepy/tools.py b/moviepy/tools.py index 2e70737ad..200bb190e 100644 --- a/moviepy/tools.py +++ b/moviepy/tools.py @@ -9,17 +9,7 @@ import proglog -def sys_write_flush(s): - """ Writes and flushes without delay a text in the console """ - # Reason for not using `print` is that in some consoles "print" - # commands get delayed, while stdout.flush are instantaneous, - # so this method is better at providing feedback. - # See https://github.com/Zulko/moviepy/pull/485 - sys.stdout.write(s) - sys.stdout.flush() - - -def subprocess_call(cmd, logger="bar", errorprint=True): +def subprocess_call(cmd, logger="bar"): """Executes the given subprocess command. Set logger to None or a custom Proglog logger to avoid printings. @@ -38,8 +28,7 @@ def subprocess_call(cmd, logger="bar", errorprint=True): proc.stderr.close() if proc.returncode: - if errorprint: - logger(message="Moviepy - Command returned an error") + logger(message="Moviepy - Command returned an error") raise IOError(err.decode("utf8")) else: logger(message="Moviepy - Command successful") @@ -47,7 +36,7 @@ def subprocess_call(cmd, logger="bar", errorprint=True): del proc -def cvsecs(time): +def convert_to_seconds(time): """Will convert any time into seconds. If the type of `time` is not valid, @@ -55,25 +44,25 @@ def cvsecs(time): Here are the accepted formats:: - >>> cvsecs(15.4) # seconds + >>> convert_to_seconds(15.4) # seconds 15.4 - >>> cvsecs((1, 21.5)) # (min,sec) + >>> convert_to_seconds((1, 21.5)) # (min,sec) 81.5 - >>> cvsecs((1, 1, 2)) # (hr, min, sec) + >>> convert_to_seconds((1, 1, 2)) # (hr, min, sec) 3662 - >>> cvsecs('01:01:33.045') + >>> convert_to_seconds('01:01:33.045') 3693.045 - >>> cvsecs('01:01:33,5') # coma works too + >>> convert_to_seconds('01:01:33,5') # coma works too 3693.5 - >>> cvsecs('1:33,5') # only minutes and secs + >>> convert_to_seconds('1:33,5') # only minutes and secs 99.5 - >>> cvsecs('33.5') # only secs + >>> convert_to_seconds('33.5') # only secs 33.5 """ factors = (1, 60, 3600) if isinstance(time, str): - time = [float(f.replace(",", ".")) for f in time.split(":")] + time = [float(part.replace(",", ".")) for part in time.split(":")] if not isinstance(time, (tuple, list)): return time @@ -81,20 +70,19 @@ def cvsecs(time): return sum(mult * part for mult, part in zip(factors, reversed(time))) -def deprecated_version_of(f, oldname, newname=None): +def deprecated_version_of(func, old_name): """Indicates that a function is deprecated and has a new name. - `f` is the new function, `oldname` the name of the deprecated - function, `newname` the name of `f`, which can be automatically - found. + `func` is the new function and `old_name` is the name of the deprecated + function. Returns ======== - f_deprecated - A function that does the same thing as f, but with a docstring + deprecated_func + A function that does the same thing as `func`, but with a docstring and a printed message on call which say that the function is - deprecated and that you should use f instead. + deprecated and that you should use `func` instead. Examples ========= @@ -107,26 +95,26 @@ def deprecated_version_of(f, oldname, newname=None): >>> Clip.to_file = deprecated_version_of(Clip.write_file, 'to_file') """ - if newname is None: - newname = f.__name__ + # Detect new name of func + new_name = func.__name__ warning = ( "The function ``%s`` is deprecated and is kept temporarily " "for backwards compatibility.\nPlease use the new name, " "``%s``, instead." - ) % (oldname, newname) + ) % (old_name, new_name) - def fdepr(*a, **kw): + def deprecated_func(*args, **kwargs): warnings.warn("MoviePy: " + warning, PendingDeprecationWarning) - return f(*a, **kw) + return func(*args, **kwargs) - fdepr.__doc__ = warning + deprecated_func.__doc__ = warning - return fdepr + return deprecated_func -# non-exhaustive dictionnary to store default informations. -# any addition is most welcome. +# Non-exhaustive dictionary to store default informations. +# Any addition is most welcome. # Note that 'gif' is complicated to place. From a VideoFileClip point of view, # it is a video, but from a HTML5 point of view, it is an image. diff --git a/moviepy/video/VideoClip.py b/moviepy/video/VideoClip.py index 3fcd92f01..9a6174a5d 100644 --- a/moviepy/video/VideoClip.py +++ b/moviepy/video/VideoClip.py @@ -19,7 +19,7 @@ apply_to_mask, convert_masks_to_RGB, convert_path_to_string, - convert_to_seconds, + convert_parameter_to_seconds, outplace, requires_duration, use_clip_fps_by_default, @@ -45,7 +45,7 @@ class VideoClip(Clip): Parameters ----------- - ismask + is_mask `True` if the clip is going to be used as a mask. @@ -58,7 +58,7 @@ class VideoClip(Clip): w, h The width and height of the clip, in pixels. - ismask + is_mask Boolean set to `True` if the clip is a mask. make_frame @@ -88,7 +88,7 @@ class VideoClip(Clip): """ def __init__( - self, make_frame=None, ismask=False, duration=None, has_constant_size=True + self, make_frame=None, is_mask=False, duration=None, has_constant_size=True ): Clip.__init__(self) self.mask = None @@ -99,7 +99,7 @@ def __init__( if make_frame: self.make_frame = make_frame self.size = self.get_frame(0).shape[:2][::-1] - self.ismask = ismask + self.is_mask = is_mask self.has_constant_size = has_constant_size if duration is not None: self.duration = duration @@ -120,22 +120,22 @@ def aspect_ratio(self): # =============================================================== # EXPORT OPERATIONS - @convert_to_seconds(["t"]) + @convert_parameter_to_seconds(["t"]) @convert_masks_to_RGB - def save_frame(self, filename, t=0, withmask=True): + def save_frame(self, filename, t=0, with_mask=True): """Save a clip's frame to an image file. 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 ``withmask`` is ``True`` the mask is saved in + If ``with_mask`` is ``True`` the mask is saved in the alpha layer of the picture (only works with PNGs). """ im = self.get_frame(t) - if withmask and self.mask is not None: + if with_mask and self.mask is not None: mask = 255 * self.mask.get_frame(t) im = np.dstack([im, mask]).astype("uint8") else: @@ -168,7 +168,7 @@ def write_videofile( threads=None, ffmpeg_params=None, logger="bar", - pix_fmt=None, + pixel_format=None, ): """Write the clip to a videofile. @@ -273,7 +273,7 @@ def write_videofile( logger Either "bar" for progress bar or None or any Proglog logger. - pix_fmt + pixel_format Pixel format for the output video file. Examples @@ -352,7 +352,7 @@ def write_videofile( threads=threads, ffmpeg_params=ffmpeg_params, logger=logger, - pix_fmt=pix_fmt, + pixel_format=pixel_format, ) if remove_temp and make_audio: @@ -363,13 +363,15 @@ def write_videofile( @requires_duration @use_clip_fps_by_default @convert_masks_to_RGB - def write_images_sequence(self, nameformat, fps=None, withmask=True, logger="bar"): + def write_images_sequence( + self, name_format, fps=None, with_mask=True, logger="bar" + ): """Writes the videoclip to a sequence of image files. Parameters ----------- - nameformat + name_format A filename specifying the numerotation format and extension of the pictures. For instance "frame%03d.png" for filenames indexed with 3 digits and PNG format. Also possible: @@ -380,7 +382,7 @@ def write_images_sequence(self, nameformat, fps=None, withmask=True, logger="bar clip. If not specified, the clip's ``fps`` attribute will be used if it has one. - withmask + with_mask will save the clip's mask (if any) as an alpha canal (PNGs only). logger @@ -402,16 +404,16 @@ def write_images_sequence(self, nameformat, fps=None, withmask=True, logger="bar """ logger = proglog.default_bar_logger(logger) # Fails on GitHub macos CI - # logger(message="Moviepy - Writing frames %s." % nameformat) + # logger(message="Moviepy - Writing frames %s." % name_format) - tt = np.arange(0, self.duration, 1.0 / fps) + timings = np.arange(0, self.duration, 1.0 / fps) filenames = [] - for i, t in logger.iter_bar(t=list(enumerate(tt))): - name = nameformat % i + for i, t in logger.iter_bar(t=list(enumerate(timings))): + name = name_format % i filenames.append(name) - self.save_frame(name, t, withmask=withmask) - # logger(message="Moviepy - Done writing frames %s." % nameformat) + self.save_frame(name, t, with_mask=with_mask) + # logger(message="Moviepy - Done writing frames %s." % name_format) return filenames @@ -430,7 +432,7 @@ def write_gif( colors=None, tempfiles=False, logger="bar", - pix_fmt=None, + pixel_format=None, ): """Write the VideoClip to a GIF file. @@ -470,7 +472,7 @@ def write_gif( progress_bar If True, displays a progress bar - pix_fmt + pixel_format Pixel format for the output gif file. If is not specified 'rgb24' will be used as the default format unless ``clip.mask`` exist, then 'rgba' will be used. This option is only going to @@ -516,7 +518,7 @@ def write_gif( dispose=dispose, colors=colors, logger=logger, - pix_fmt=pix_fmt, + pixel_format=pixel_format, ) else: # convert imageio opt variable to something that can be used with @@ -533,47 +535,47 @@ def write_gif( dispose=dispose, colors=colors, logger=logger, - pix_fmt=pix_fmt, + pixel_format=pixel_format, ) # ----------------------------------------------------------------- # F I L T E R I N G - def subfx(self, fx, ta=0, tb=None, **kwargs): + def subfx(self, fx, start_time=0, end_time=None, **kwargs): """Apply a transformation to a part of the clip. Returns a new clip in which the function ``fun`` (clip->clip) - has been applied to the subclip between times `ta` and `tb` + has been applied to the subclip between times `start_time` and `end_time` (in seconds). Examples --------- >>> # The scene between times t=3s and t=6s in ``clip`` will be - >>> # be played twice slower in ``newclip`` - >>> newclip = clip.subapply(lambda c:c.speedx(0.5) , 3,6) + >>> # be played twice slower in ``new_clip`` + >>> new_clip = clip.subapply(lambda c:c.speedx(0.5) , 3,6) """ - left = self.subclip(0, ta) if ta else None - center = self.subclip(ta, tb).fx(fx, **kwargs) - right = self.subclip(t_start=tb) if tb else None + left = self.subclip(0, start_time) if start_time else None + center = self.subclip(start_time, end_time).fx(fx, **kwargs) + right = self.subclip(start_time=end_time) if end_time else None - clips = [c for c in (left, center, right) if c] + clips = [clip for clip in (left, center, right) if clip] # beurk, have to find other solution from moviepy.video.compositing.concatenate import concatenate_videoclips - return concatenate_videoclips(clips).set_start(self.start) + return concatenate_videoclips(clips).with_start(self.start) # IMAGE FILTERS - def fl_image(self, image_func, apply_to=None): + def image_transform(self, image_func, apply_to=None): """ Modifies the images of a clip by replacing the frame `get_frame(t)` by another frame, `image_func(get_frame(t))` """ apply_to = apply_to or [] - return self.fl(lambda gf, t: image_func(gf(t)), apply_to) + return self.transform(lambda get_frame, t: image_func(get_frame(t)), apply_to) # -------------------------------------------------------------- # C O M P O S I T I N G @@ -603,7 +605,7 @@ def blit_on(self, picture, t): """ hf, wf = framesize = picture.shape[:2] - if self.ismask and picture.max(): + if self.is_mask and picture.max(): return np.minimum(1, picture + self.blit_on(np.zeros(framesize), t)) ct = t - self.start # clip time @@ -651,7 +653,7 @@ def blit_on(self, picture, t): pos = map(int, pos) - return blit(img, picture, pos, mask=mask, ismask=self.ismask) + return blit(img, picture, pos, mask=mask, is_mask=self.is_mask) def add_mask(self): """Add a mask VideoClip to the VideoClip. @@ -664,15 +666,15 @@ def add_mask(self): image size. """ if self.has_constant_size: - mask = ColorClip(self.size, 1.0, ismask=True) - return self.set_mask(mask.set_duration(self.duration)) + mask = ColorClip(self.size, 1.0, is_mask=True) + return self.with_mask(mask.with_duration(self.duration)) else: def make_frame(t): return np.ones(self.get_frame(t).shape[:2], dtype=float) - mask = VideoClip(ismask=True, make_frame=make_frame) - return self.set_mask(mask.set_duration(self.duration)) + mask = VideoClip(is_mask=True, make_frame=make_frame) + return self.with_mask(mask.with_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. @@ -710,11 +712,11 @@ def on_color(self, size=None, color=(0, 0, 0), pos=None, col_opacity=None): if col_opacity is not None: colorclip = ColorClip( size, color=color, duration=self.duration - ).set_opacity(col_opacity) - result = CompositeVideoClip([colorclip, self.set_position(pos)]) + ).with_opacity(col_opacity) + result = CompositeVideoClip([colorclip, self.with_position(pos)]) else: result = CompositeVideoClip( - [self.set_position(pos)], size=size, bg_color=color + [self.with_position(pos)], size=size, bg_color=color ) if ( @@ -725,12 +727,12 @@ def on_color(self, size=None, color=(0, 0, 0), pos=None, col_opacity=None): new_result = result.to_ImageClip() if result.mask is not None: new_result.mask = result.mask.to_ImageClip() - return new_result.set_duration(result.duration) + return new_result.with_duration(result.duration) return result @outplace - def set_make_frame(self, mf): + def with_make_frame(self, mf): """Change the clip's ``get_frame``. Returns a copy of the VideoClip instance, with the make_frame @@ -740,7 +742,7 @@ def set_make_frame(self, mf): self.size = self.get_frame(0).shape[:2][::-1] @outplace - def set_audio(self, audioclip): + def with_audio(self, audioclip): """Attach an AudioClip to the VideoClip. Returns a copy of the VideoClip instance, with the `audio` @@ -749,27 +751,27 @@ def set_audio(self, audioclip): self.audio = audioclip @outplace - def set_mask(self, mask): + def with_mask(self, mask): """Set the clip's mask. Returns a copy of the VideoClip with the mask attribute set to ``mask``, which must be a greyscale (values in 0-1) VideoClip""" - assert mask is None or mask.ismask + assert mask is None or mask.is_mask self.mask = mask @add_mask_if_none @outplace - def set_opacity(self, op): + def with_opacity(self, opacity): """Set the opacity/transparency level of the clip. Returns a semi-transparent copy of the clip where the mask is multiplied by ``op`` (any float, normally between 0 and 1). """ - self.mask = self.mask.fl_image(lambda pic: op * pic) + self.mask = self.mask.image_transform(lambda pic: opacity * pic) @apply_to_mask @outplace - def set_position(self, pos, relative=False): + def with_position(self, pos, relative=False): """Set the clip's position in compositions. Sets the position that the clip will have when included @@ -781,16 +783,16 @@ def set_position(self, pos, relative=False): Examples ---------- - >>> clip.set_position((45,150)) # x=45, y=150 + >>> clip.with_position((45,150)) # x=45, y=150 >>> >>> # clip horizontally centered, at the top of the picture - >>> clip.set_position(("center","top")) + >>> clip.with_position(("center","top")) >>> >>> # clip is at 40% of the width, 70% of the height: - >>> clip.set_position((0.4,0.7), relative=True) + >>> clip.with_position((0.4,0.7), relative=True) >>> >>> # clip's position is horizontally centered, and moving up ! - >>> clip.set_position(lambda t: ('center', 50+t) ) + >>> clip.with_position(lambda t: ('center', 50+t) ) """ self.relative_pos = relative @@ -801,7 +803,7 @@ def set_position(self, pos, relative=False): @apply_to_mask @outplace - def set_layer(self, layer): + def with_layer(self, layer): """Set the clip's layer in compositions. Clips with a greater ``layer`` attribute will be displayed on top of others. @@ -811,35 +813,35 @@ def set_layer(self, layer): # -------------------------------------------------------------- # CONVERSIONS TO OTHER TYPES - @convert_to_seconds(["t"]) + @convert_parameter_to_seconds(["t"]) def to_ImageClip(self, t=0, with_mask=True, duration=None): """ Returns an ImageClip made out of the clip's frame at time ``t``, which can be expressed in seconds (15.35), in (min, sec), in (hour, min, sec), or as a string: '01:03:05.35'. """ - newclip = ImageClip(self.get_frame(t), ismask=self.ismask, duration=duration) + new_clip = ImageClip(self.get_frame(t), is_mask=self.is_mask, duration=duration) if with_mask and self.mask is not None: - newclip.mask = self.mask.to_ImageClip(t) - return newclip + new_clip.mask = self.mask.to_ImageClip(t) + return new_clip def to_mask(self, canal=0): """Return a mask a video clip made from the clip.""" - if self.ismask: + if self.is_mask: return self else: - newclip = self.fl_image(lambda pic: 1.0 * pic[:, :, canal] / 255) - newclip.ismask = True - return newclip + new_clip = self.image_transform(lambda pic: 1.0 * pic[:, :, canal] / 255) + new_clip.is_mask = True + return new_clip def to_RGB(self): """Return a non-mask video clip made from the mask video clip.""" - if self.ismask: - newclip = self.fl_image( + if self.is_mask: + new_clip = self.image_transform( lambda pic: np.dstack(3 * [255 * pic]).astype("uint8") ) - newclip.ismask = False - return newclip + new_clip.is_mask = False + return new_clip else: return self @@ -856,13 +858,13 @@ def without_audio(self): self.audio = None @outplace - def afx(self, fun, *a, **k): + def afx(self, fun, *args, **kwargs): """Transform the clip's audio. Return a new clip whose audio has been transformed by ``fun``. """ - self.audio = self.audio.fx(fun, *a, **k) + self.audio = self.audio.fx(fun, *args, **kwargs) class DataVideoClip(VideoClip): @@ -885,7 +887,7 @@ class DataVideoClip(VideoClip): --------- """ - def __init__(self, data, data_to_frame, fps, ismask=False, has_constant_size=True): + def __init__(self, data, data_to_frame, fps, is_mask=False, has_constant_size=True): self.data = data self.data_to_frame = data_to_frame self.fps = fps @@ -896,7 +898,7 @@ def make_frame(t): VideoClip.__init__( self, make_frame, - ismask=ismask, + is_mask=is_mask, duration=1.0 * len(data) / fps, has_constant_size=has_constant_size, ) @@ -927,7 +929,7 @@ class UpdatedVideoClip(VideoClip): increasing world.clip_t of one time step) - world.to_frame() : renders a frame depending on the world's state - ismask + is_mask True if the clip is a WxH mask with values in 0-1 duration @@ -935,7 +937,7 @@ class UpdatedVideoClip(VideoClip): """ - def __init__(self, world, ismask=False, duration=None): + def __init__(self, world, is_mask=False, duration=None): self.world = world def make_frame(t): @@ -944,7 +946,7 @@ def make_frame(t): return world.to_frame() VideoClip.__init__( - self, make_frame=make_frame, ismask=ismask, duration=duration + self, make_frame=make_frame, is_mask=is_mask, duration=duration ) @@ -977,7 +979,7 @@ class ImageClip(VideoClip): Any picture file (png, tiff, jpeg, etc.) as a string or a path-like object, or any array representing an RGB image (for instance a frame from a VideoClip). - ismask + is_mask Set this parameter to `True` if the clip is a mask. transparent @@ -993,9 +995,9 @@ class ImageClip(VideoClip): """ def __init__( - self, img, ismask=False, transparent=True, fromalpha=False, duration=None + self, img, is_mask=False, transparent=True, fromalpha=False, duration=None ): - VideoClip.__init__(self, ismask=ismask, duration=duration) + VideoClip.__init__(self, is_mask=is_mask, duration=duration) if not isinstance(img, np.ndarray): # img is a string or path-like object, so read it in from disk @@ -1006,12 +1008,12 @@ def __init__( if img.shape[2] == 4: if fromalpha: img = 1.0 * img[:, :, 3] / 255 - elif ismask: + elif is_mask: img = 1.0 * img[:, :, 0] / 255 elif transparent: - self.mask = ImageClip(1.0 * img[:, :, 3] / 255, ismask=True) + self.mask = ImageClip(1.0 * img[:, :, 3] / 255, is_mask=True) img = img[:, :, :3] - elif ismask: + elif is_mask: img = 1.0 * img[:, :, 0] / 255 # if the image was just a 2D mask, it should arrive here @@ -1020,25 +1022,27 @@ def __init__( self.size = img.shape[:2][::-1] self.img = img - def fl(self, fl, apply_to=None, keep_duration=True): + def transform(self, func, apply_to=None, keep_duration=True): """General transformation filter. - Equivalent to VideoClip.fl . The result is no more an + Equivalent to VideoClip.transform. The result is no more an ImageClip, it has the class VideoClip (since it may be animated) """ if apply_to is None: apply_to = [] - # When we use fl on an image clip it may become animated. + # When we use transform 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, keep_duration=keep_duration) - newclip.__class__ = VideoClip - return newclip + new_clip = VideoClip.transform( + self, func, apply_to=apply_to, keep_duration=keep_duration + ) + new_clip.__class__ = VideoClip + return new_clip @outplace - def fl_image(self, image_func, apply_to=None): + def image_transform(self, image_func, apply_to=None): """Image-transformation filter. - Does the same as VideoClip.fl_image, but for ImageClip the + Does the same as VideoClip.image_transform, but for ImageClip the tranformed clip is computed once and for all at the beginning, and not for each 'frame'. """ @@ -1052,15 +1056,15 @@ def fl_image(self, image_func, apply_to=None): for attr in apply_to: a = getattr(self, attr, None) if a is not None: - new_a = a.fl_image(image_func) + new_a = a.image_transform(image_func) setattr(self, attr, new_a) @outplace - def fl_time(self, time_func, apply_to=None, keep_duration=False): + def time_transform(self, time_func, apply_to=None, keep_duration=False): """Time-transformation filter. Applies a transformation to the clip's timeline - (see Clip.fl_time). + (see Clip.time_transform). This method does nothing for ImageClips (but it may affect their masks or their audios). The result is still an ImageClip. @@ -1070,7 +1074,7 @@ def fl_time(self, time_func, apply_to=None, keep_duration=False): for attr in apply_to: a = getattr(self, attr, None) if a is not None: - new_a = a.fl_time(time_func) + new_a = a.time_transform(time_func) setattr(self, attr, new_a) @@ -1084,19 +1088,19 @@ class ColorClip(ImageClip): Size (width, height) in pixels of the clip. color - If argument ``ismask`` is False, ``color`` indicates - the color in RGB of the clip (default is black). If `ismask`` + If argument ``is_mask`` is False, ``color`` indicates + the color in RGB of the clip (default is black). If `is_mask`` is True, ``color`` must be a float between 0 and 1 (default is 1) - ismask + is_mask Set to true if the clip will be used as a mask. """ - def __init__(self, size, color=None, ismask=False, duration=None): + def __init__(self, size, color=None, is_mask=False, duration=None): w, h = size - if ismask: + if is_mask: shape = (h, w) if color is None: color = 0 @@ -1110,7 +1114,7 @@ def __init__(self, size, color=None, ismask=False, duration=None): shape = (h, w, len(color)) super().__init__( - np.tile(color, w * h).reshape(shape), ismask=ismask, duration=duration + np.tile(color, w * h).reshape(shape), is_mask=is_mask, duration=duration ) @@ -1123,7 +1127,7 @@ class TextClip(ImageClip): Parameters ----------- - txt + text A string of the text to write. Can be replaced by argument ``filename``. @@ -1182,12 +1186,12 @@ class TextClip(ImageClip): @convert_path_to_string("filename") def __init__( self, - txt=None, + text=None, filename=None, size=None, color="black", bg_color="transparent", - fontsize=None, + font_size=None, font="Courier", stroke_color=None, stroke_width=1, @@ -1202,18 +1206,18 @@ def __init__( print_cmd=False, ): - if txt is not None: + if text 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")) + os.write(temptxt_fd, bytes(text, "UTF8")) except TypeError: # oops, fall back to Python2 - os.write(temptxt_fd, txt) + os.write(temptxt_fd, text) os.close(temptxt_fd) - txt = "@" + temptxt + text = "@" + temptxt else: # use a file instead of a text. - txt = "@%" + filename + text = "@%" + filename if size is not None: size = ( @@ -1231,8 +1235,8 @@ def __init__( font, ] - if fontsize is not None: - cmd += ["-pointsize", "%d" % fontsize] + if font_size is not None: + cmd += ["-pointsize", "%d" % font_size] if kerning is not None: cmd += ["-kerning", "%0.1f" % kerning] if stroke_color is not None: @@ -1249,7 +1253,7 @@ def __init__( os.close(tempfile_fd) cmd += [ - "%s:%s" % (method, txt), + "%s:%s" % (method, text), "-type", "truecolormatte", "PNG32:%s" % tempfilename, @@ -1272,7 +1276,7 @@ def __init__( raise IOError(error) ImageClip.__init__(self, tempfilename, transparent=transparent) - self.txt = txt + self.text = text self.color = color self.stroke_color = stroke_color @@ -1324,9 +1328,9 @@ def search(string, arg): class BitmapClip(VideoClip): - @convert_to_seconds(["duration"]) + @convert_parameter_to_seconds(["duration"]) def __init__( - self, bitmap_frames, *, fps=None, duration=None, color_dict=None, ismask=False + self, bitmap_frames, *, fps=None, duration=None, color_dict=None, is_mask=False ): """ Creates a VideoClip object from a bitmap representation. Primarily used in the test suite. @@ -1372,7 +1376,7 @@ def __init__( "E": (57, 26, 252), } - ismask + is_mask Set to ``True`` if the clip is going to be used as a mask. """ @@ -1412,7 +1416,7 @@ def __init__( VideoClip.__init__( self, make_frame=lambda t: frame_array[int(t * fps)], - ismask=ismask, + is_mask=is_mask, duration=duration, ) self.fps = fps diff --git a/moviepy/video/compositing/CompositeVideoClip.py b/moviepy/video/compositing/CompositeVideoClip.py index df31e3013..58cee0eb5 100644 --- a/moviepy/video/compositing/CompositeVideoClip.py +++ b/moviepy/video/compositing/CompositeVideoClip.py @@ -52,7 +52,9 @@ class CompositeVideoClip(VideoClip): """ - def __init__(self, clips, size=None, bg_color=None, use_bgclip=False, ismask=False): + def __init__( + self, clips, size=None, bg_color=None, use_bgclip=False, is_mask=False + ): if size is None: size = clips[0].size @@ -63,15 +65,15 @@ def __init__(self, clips, size=None, bg_color=None, use_bgclip=False, ismask=Fal transparent = bg_color is None if bg_color is None: - bg_color = 0.0 if ismask else (0, 0, 0) + bg_color = 0.0 if is_mask else (0, 0, 0) - fpss = [c.fps for c in clips if getattr(c, "fps", None)] + fpss = [clip.fps for clip in clips if getattr(clip, "fps", None)] self.fps = max(fpss) if fpss else None VideoClip.__init__(self) self.size = size - self.ismask = ismask + self.is_mask = is_mask self.clips = clips self.bg_color = bg_color @@ -81,14 +83,14 @@ def __init__(self, clips, size=None, bg_color=None, use_bgclip=False, ismask=Fal self.created_bg = False else: self.clips = clips - self.bg = ColorClip(size, color=self.bg_color, ismask=ismask) + self.bg = ColorClip(size, color=self.bg_color, is_mask=is_mask) self.created_bg = True # order self.clips by layer self.clips = sorted(self.clips, key=lambda clip: clip.layer) # compute duration - ends = [c.end for c in self.clips] + ends = [clip.end for clip in self.clips] if None not in ends: duration = max(ends) self.duration = duration @@ -102,33 +104,33 @@ def __init__(self, clips, size=None, bg_color=None, use_bgclip=False, ismask=Fal # compute mask if necessary if transparent: maskclips = [ - (c.mask if (c.mask is not None) else c.add_mask().mask) - .set_position(c.pos) - .set_end(c.end) - .set_start(c.start, change_end=False) - .set_layer(c.layer) - for c in self.clips + (clip.mask if (clip.mask is not None) else clip.add_mask().mask) + .with_position(clip.pos) + .with_end(clip.end) + .with_start(clip.start, change_end=False) + .with_layer(clip.layer) + for clip in self.clips ] self.mask = CompositeVideoClip( - maskclips, self.size, ismask=True, bg_color=0.0 + maskclips, self.size, is_mask=True, bg_color=0.0 ) def make_frame(t): """The clips playing at time `t` are blitted over one another.""" - f = self.bg.get_frame(t) - for c in self.playing_clips(t): - f = c.blit_on(f, t) - return f + frame = self.bg.get_frame(t) + for clip in self.playing_clips(t): + frame = clip.blit_on(frame, t) + return frame self.make_frame = make_frame def playing_clips(self, t=0): """Returns a list of the clips in the composite clips that are actually playing at the given time `t`.""" - return [c for c in self.clips if c.is_playing(t)] + return [clip for clip in self.clips if clip.is_playing(t)] def close(self): if self.created_bg and self.bg: @@ -160,7 +162,7 @@ def clips_array(array, rows_widths=None, cols_widths=None, bg_color=None): """ array = np.array(array) - sizes_array = np.array([[c.size for c in line] for line in array]) + sizes_array = np.array([[clip.size for clip in line] for line in array]) # find row width and col_widths automatically if not provided if rows_widths is None: @@ -168,18 +170,18 @@ def clips_array(array, rows_widths=None, cols_widths=None, bg_color=None): if cols_widths is None: cols_widths = sizes_array[:, :, 0].max(axis=0) - xx = np.cumsum([0] + list(cols_widths)) - yy = np.cumsum([0] + list(rows_widths)) + xs = np.cumsum([0] + list(cols_widths)) + ys = np.cumsum([0] + list(rows_widths)) - for j, (x, cw) in enumerate(zip(xx[:-1], cols_widths)): - for i, (y, rw) in enumerate(zip(yy[:-1], rows_widths)): + for j, (x, cw) in enumerate(zip(xs[:-1], cols_widths)): + for i, (y, rw) in enumerate(zip(ys[:-1], rows_widths)): clip = array[i, j] w, h = clip.size if (w < cw) or (h < rw): clip = CompositeVideoClip( - [clip.set_position("center")], size=(cw, rw), bg_color=bg_color - ).set_duration(clip.duration) + [clip.with_position("center")], size=(cw, rw), bg_color=bg_color + ).with_duration(clip.duration) - array[i, j] = clip.set_position((x, y)) + array[i, j] = clip.with_position((x, y)) - return CompositeVideoClip(array.flatten(), size=(xx[-1], yy[-1]), bg_color=bg_color) + return CompositeVideoClip(array.flatten(), size=(xs[-1], ys[-1]), bg_color=bg_color) diff --git a/moviepy/video/compositing/concatenate.py b/moviepy/video/compositing/concatenate.py index 34fd9f13b..1f3c4d837 100644 --- a/moviepy/video/compositing/concatenate.py +++ b/moviepy/video/compositing/concatenate.py @@ -8,7 +8,7 @@ def concatenate_videoclips( - clips, method="chain", transition=None, bg_color=None, ismask=False, padding=0 + clips, method="chain", transition=None, bg_color=None, is_mask=False, padding=0 ): """Concatenates several video clips @@ -64,39 +64,42 @@ def concatenate_videoclips( clips = reduce(lambda x, y: x + y, clip_transition_pairs) + [clips[-1]] transition = None - tt = np.cumsum([0] + [c.duration for c in clips]) + timings = np.cumsum([0] + [clip.duration for clip in clips]) - sizes = [v.size for v in clips] + sizes = [clip.size for clip in clips] - w = max(r[0] for r in sizes) - h = max(r[1] for r in sizes) + w = max(size[0] for size in sizes) + h = max(size[1] for size in sizes) - tt = np.maximum(0, tt + padding * np.arange(len(tt))) - tt[-1] -= padding # Last element is the duration of the whole + timings = np.maximum(0, timings + padding * np.arange(len(timings))) + timings[-1] -= padding # Last element is the duration of the whole if method == "chain": def make_frame(t): - i = max([i for i, e in enumerate(tt) if e <= t]) - return clips[i].get_frame(t - tt[i]) + i = max([i for i, e in enumerate(timings) if e <= t]) + return clips[i].get_frame(t - timings[i]) - def get_mask(c): - mask = c.mask or ColorClip([1, 1], color=1, ismask=True) + def get_mask(clip): + mask = clip.mask or ColorClip([1, 1], color=1, is_mask=True) if mask.duration is None: - mask.duration = c.duration + mask.duration = clip.duration return mask - result = VideoClip(ismask=ismask, make_frame=make_frame) - if any([c.mask is not None for c in clips]): - masks = [get_mask(c) for c in clips] - result.mask = concatenate_videoclips(masks, method="chain", ismask=True) + result = VideoClip(is_mask=is_mask, make_frame=make_frame) + if any([clip.mask is not None for clip in clips]): + masks = [get_mask(clip) for clip in clips] + result.mask = concatenate_videoclips(masks, method="chain", is_mask=True) result.clips = clips elif method == "compose": result = CompositeVideoClip( - [c.set_start(t).set_position("center") for (c, t) in zip(clips, tt)], + [ + clip.with_start(t).with_position("center") + for (clip, t) in zip(clips, timings) + ], size=(w, h), bg_color=bg_color, - ismask=ismask, + is_mask=is_mask, ) else: raise Exception( @@ -104,15 +107,17 @@ def get_mask(c): "concatenate_videoclips must be 'chain' or 'compose'" ) - result.tt = tt + result.timings = timings - result.start_times = tt[:-1] - result.start, result.duration, result.end = 0, tt[-1], tt[-1] + result.start_times = timings[:-1] + result.start, result.duration, result.end = 0, timings[-1], timings[-1] - audio_t = [(c.audio, t) for c, t in zip(clips, tt) if c.audio is not None] + audio_t = [ + (clip.audio, t) for clip, t in zip(clips, timings) if clip.audio is not None + ] if audio_t: - result.audio = CompositeAudioClip([a.set_start(t) for a, t in audio_t]) + result.audio = CompositeAudioClip([a.with_start(t) for a, t in audio_t]) - fpss = [c.fps for c in clips if getattr(c, "fps", None) is not None] + fpss = [clip.fps for clip in clips if getattr(clip, "fps", None) is not None] result.fps = max(fpss) if fpss else None return result diff --git a/moviepy/video/compositing/on_color.py b/moviepy/video/compositing/on_color.py index 9c4a7e012..9bb2b6ef5 100644 --- a/moviepy/video/compositing/on_color.py +++ b/moviepy/video/compositing/on_color.py @@ -21,8 +21,8 @@ def on_color(clip, size=None, color=(0, 0, 0), pos=None, col_opacity=None): pos = "center" colorclip = ColorClip(size, color=color) if col_opacity: - colorclip = colorclip.with_mask().set_opacity(col_opacity) + colorclip = colorclip.with_mask().with_opacity(col_opacity) return CompositeVideoClip( - [colorclip, clip.set_position(pos)], transparent=(col_opacity is not None) + [colorclip, clip.with_position(pos)], transparent=(col_opacity is not None) ) diff --git a/moviepy/video/compositing/transitions.py b/moviepy/video/compositing/transitions.py index 2ad1b132a..05a8d00c9 100644 --- a/moviepy/video/compositing/transitions.py +++ b/moviepy/video/compositing/transitions.py @@ -18,9 +18,9 @@ def crossfadein(clip, duration): Only works when the clip is included in a CompositeVideoClip. """ clip.mask.duration = clip.duration - newclip = clip.copy() - newclip.mask = clip.mask.fx(fadein, duration) - return newclip + new_clip = clip.copy() + new_clip.mask = clip.mask.fx(fadein, duration) + return new_clip @requires_duration @@ -30,9 +30,9 @@ def crossfadeout(clip, duration): Only works when the clip is included in a CompositeVideoClip. """ clip.mask.duration = clip.duration - newclip = clip.copy() - newclip.mask = clip.mask.fx(fadeout, duration) - return newclip + new_clip = clip.copy() + new_clip.mask = clip.mask.fx(fadeout, duration) + return new_clip def slide_in(clip, duration, side): @@ -73,7 +73,7 @@ def slide_in(clip, duration, side): "bottom": lambda t: ("center", max(0, h * (1 - t / duration))), } - return clip.set_position(pos_dict[side]) + return clip.with_position(pos_dict[side]) @requires_duration @@ -117,14 +117,15 @@ def slide_out(clip, duration, side): "bottom": lambda t: ("center", max(0, h * ((t - ts) / duration))), } - return clip.set_position(pos_dict[side]) + return clip.with_position(pos_dict[side]) @requires_duration -def make_loopable(clip, cross_duration): +def make_loopable(clip, overlap_duration): """Makes the clip fade in progressively at its own end, this way - it can be looped indefinitely. ``cross`` is the duration in seconds + it can be looped indefinitely. ``overlap_duration`` is the duration in seconds of the fade-in.""" - d = clip.duration - clip2 = clip.fx(crossfadein, cross_duration).set_start(d - cross_duration) - return CompositeVideoClip([clip, clip2]).subclip(cross_duration, d) + clip2 = clip.fx(crossfadein, overlap_duration).with_start( + clip.duration - overlap_duration + ) + return CompositeVideoClip([clip, clip2]).subclip(overlap_duration, clip.duration) diff --git a/moviepy/video/fx/accel_decel.py b/moviepy/video/fx/accel_decel.py index c5ba289c1..1b1d975d3 100644 --- a/moviepy/video/fx/accel_decel.py +++ b/moviepy/video/fx/accel_decel.py @@ -1,4 +1,4 @@ -def f_accel_decel(t, old_d, new_d, abruptness=1.0, soonness=1.0): +def f_accel_decel(t, old_duration, new_duration, abruptness=1.0, soonness=1.0): """ abruptness negative abruptness (>-1): speed up down up @@ -21,7 +21,7 @@ def f2(t): return (t < 0.5) * f1(t) + (t >= 0.5) * f2(t) - return old_d * _f((t / new_d) ** soonness) + return old_duration * _f((t / new_duration) ** soonness) def accel_decel(clip, new_duration=None, abruptness=1.0, soonness=1.0): @@ -43,6 +43,6 @@ def accel_decel(clip, new_duration=None, abruptness=1.0, soonness=1.0): if new_duration is None: new_duration = clip.duration - return clip.fl_time( + return clip.time_transform( lambda t: f_accel_decel(t, clip.duration, new_duration, abruptness, soonness) - ).set_duration(new_duration) + ).with_duration(new_duration) diff --git a/moviepy/video/fx/blackwhite.py b/moviepy/video/fx/blackwhite.py index 7c99cfa2c..52dd9c1d2 100644 --- a/moviepy/video/fx/blackwhite.py +++ b/moviepy/video/fx/blackwhite.py @@ -15,8 +15,8 @@ def blackwhite(clip, RGB=None, preserve_luminosity=True): R, G, B = 1.0 * np.array(RGB) / (sum(RGB) if preserve_luminosity else 1) - def fl(im): + def filter(im): im = R * im[:, :, 0] + G * im[:, :, 1] + B * im[:, :, 2] return np.dstack(3 * [im]).astype("uint8") - return clip.fl_image(fl) + return clip.image_transform(filter) diff --git a/moviepy/video/fx/blink.py b/moviepy/video/fx/blink.py index 3a52f44cb..2d3a36744 100644 --- a/moviepy/video/fx/blink.py +++ b/moviepy/video/fx/blink.py @@ -1,15 +1,17 @@ import copy -def blink(clip, d_on, d_off): +def blink(clip, duration_on, duration_off): """ - Makes the clip blink. At each blink it will be displayed ``d_on`` - seconds and disappear ``d_off`` seconds. Will only work in + Makes the clip blink. At each blink it will be displayed ``duration_on`` + seconds and disappear ``duration_off`` seconds. Will only work in composite clips. """ - newclip = copy.copy(clip) - if newclip.mask is None: - newclip = newclip.with_mask() - D = d_on + d_off - newclip.mask = newclip.mask.fl(lambda gf, t: gf(t) * ((t % D) < d_on)) - return newclip + new_clip = copy.copy(clip) + if new_clip.mask is None: + new_clip = new_clip.with_mask() + duration = duration_on + duration_off + new_clip.mask = new_clip.mask.transform( + lambda get_frame, t: get_frame(t) * ((t % duration) < duration_on) + ) + return new_clip diff --git a/moviepy/video/fx/colorx.py b/moviepy/video/fx/colorx.py index 04846d1fb..1ff3dffdc 100644 --- a/moviepy/video/fx/colorx.py +++ b/moviepy/video/fx/colorx.py @@ -7,4 +7,6 @@ def colorx(clip, factor): to decrease or increase the clip's brightness (is that the right word ?) """ - return clip.fl_image(lambda pic: np.minimum(255, (factor * pic)).astype("uint8")) + return clip.image_transform( + lambda frame: np.minimum(255, (factor * frame)).astype("uint8") + ) diff --git a/moviepy/video/fx/crop.py b/moviepy/video/fx/crop.py index 8d3cbff2a..786a063bf 100644 --- a/moviepy/video/fx/crop.py +++ b/moviepy/video/fx/crop.py @@ -60,6 +60,6 @@ def crop( x2 = x2 or clip.size[0] y2 = y2 or clip.size[1] - return clip.fl_image( - lambda pic: pic[int(y1) : int(y2), int(x1) : int(x2)], apply_to=["mask"] + return clip.image_transform( + lambda frame: frame[int(y1) : int(y2), int(x1) : int(x2)], apply_to=["mask"] ) diff --git a/moviepy/video/fx/even_size.py b/moviepy/video/fx/even_size.py index 7b11d42a5..2651f419a 100644 --- a/moviepy/video/fx/even_size.py +++ b/moviepy/video/fx/even_size.py @@ -14,17 +14,17 @@ def even_size(clip): if not w_even and not h_even: - def fl_image(a): + def image_filter(a): return a[:-1, :-1, :] elif h_even: - def fl_image(a): + def image_filter(a): return a[:, :-1, :] else: - def fl_image(a): + def image_filter(a): return a[:-1, :, :] - return clip.fl_image(fl_image) + return clip.image_transform(image_filter) diff --git a/moviepy/video/fx/fadein.py b/moviepy/video/fx/fadein.py index 270fd5fa2..8ab49270d 100644 --- a/moviepy/video/fx/fadein.py +++ b/moviepy/video/fx/fadein.py @@ -11,15 +11,15 @@ def fadein(clip, duration, initial_color=None): """ if initial_color is None: - initial_color = 0 if clip.ismask else [0, 0, 0] + initial_color = 0 if clip.is_mask else [0, 0, 0] initial_color = np.array(initial_color) - def fl(gf, t): + def filter(get_frame, t): if t >= duration: - return gf(t) + return get_frame(t) else: fading = 1.0 * t / duration - return fading * gf(t) + (1 - fading) * initial_color + return fading * get_frame(t) + (1 - fading) * initial_color - return clip.fl(fl) + return clip.transform(filter) diff --git a/moviepy/video/fx/fadeout.py b/moviepy/video/fx/fadeout.py index e1c7cf6f1..fc43b0cf2 100644 --- a/moviepy/video/fx/fadeout.py +++ b/moviepy/video/fx/fadeout.py @@ -14,15 +14,15 @@ def fadeout(clip, duration, final_color=None): """ if final_color is None: - final_color = 0 if clip.ismask else [0, 0, 0] + final_color = 0 if clip.is_mask else [0, 0, 0] final_color = np.array(final_color) - def fl(gf, t): + def filter(get_frame, t): if (clip.duration - t) >= duration: - return gf(t) + return get_frame(t) else: fading = 1.0 * (clip.duration - t) / duration - return fading * gf(t) + (1 - fading) * final_color + return fading * get_frame(t) + (1 - fading) * final_color - return clip.fl(fl) + return clip.transform(filter) diff --git a/moviepy/video/fx/freeze.py b/moviepy/video/fx/freeze.py index e959b662e..76f13d96d 100644 --- a/moviepy/video/fx/freeze.py +++ b/moviepy/video/fx/freeze.py @@ -22,6 +22,6 @@ def freeze(clip, t=0, freeze_duration=None, total_duration=None, padding_end=0): freeze_duration = total_duration - clip.duration before = [clip.subclip(0, t)] if (t != 0) else [] - freeze = [clip.to_ImageClip(t).set_duration(freeze_duration)] + freeze = [clip.to_ImageClip(t).with_duration(freeze_duration)] after = [clip.subclip(t)] if (t != clip.duration) else [] return concatenate_videoclips(before + freeze + after) diff --git a/moviepy/video/fx/freeze_region.py b/moviepy/video/fx/freeze_region.py index 7677ac2f4..f9b5034fc 100644 --- a/moviepy/video/fx/freeze_region.py +++ b/moviepy/video/fx/freeze_region.py @@ -38,18 +38,18 @@ def freeze_region(clip, t=0, region=None, outside_region=None, mask=None): freeze = ( clip.fx(crop, *region) .to_ImageClip(t=t) - .set_duration(clip.duration) - .set_position((x1, y1)) + .with_duration(clip.duration) + .with_position((x1, y1)) ) return CompositeVideoClip([clip, freeze]) elif outside_region is not None: x1, y1, x2, y2 = outside_region - animated_region = clip.fx(crop, *outside_region).set_position((x1, y1)) - freeze = clip.to_ImageClip(t=t).set_duration(clip.duration) + animated_region = clip.fx(crop, *outside_region).with_position((x1, y1)) + freeze = clip.to_ImageClip(t=t).with_duration(clip.duration) return CompositeVideoClip([freeze, animated_region]) elif mask is not None: - freeze = clip.to_ImageClip(t=t).set_duration(clip.duration).set_mask(mask) + freeze = clip.to_ImageClip(t=t).with_duration(clip.duration).with_mask(mask) return CompositeVideoClip([clip, freeze]) diff --git a/moviepy/video/fx/gamma_corr.py b/moviepy/video/fx/gamma_corr.py index 0a12b66e7..c6433705a 100644 --- a/moviepy/video/fx/gamma_corr.py +++ b/moviepy/video/fx/gamma_corr.py @@ -1,8 +1,8 @@ def gamma_corr(clip, gamma): """ Gamma-correction of a video clip """ - def fl(im): + def filter(im): corrected = 255 * (1.0 * im / 255) ** gamma return corrected.astype("uint8") - return clip.fl_image(fl) + return clip.image_transform(filter) diff --git a/moviepy/video/fx/headblur.py b/moviepy/video/fx/headblur.py index 3726148a2..64f7b48d7 100644 --- a/moviepy/video/fx/headblur.py +++ b/moviepy/video/fx/headblur.py @@ -12,40 +12,40 @@ # ----------------------------------------------------------------------- -def headblur(clip, fx, fy, r_zone, r_blur=None): +def headblur(clip, fx, fy, radius, intensity=None): """ - Returns a filter that will blurr a moving part (a head ?) of + Returns a filter that will blur a moving part (a head ?) of the frames. The position of the blur at time t is defined by (fx(t), fy(t)), the radius of the blurring - by ``r_zone`` and the intensity of the blurring by ``r_blur``. + by ``radius`` and the intensity of the blurring by ``intensity``. Requires OpenCV for the circling and the blurring. Automatically deals with the case where part of the image goes offscreen. """ - if r_blur is None: - r_blur = 2 * r_zone / 3 + if intensity is None: + intensity = 2 * radius / 3 - def fl(gf, t): + def filter(gf, t): im = gf(t) h, w, d = im.shape x, y = int(fx(t)), int(fy(t)) - x1, x2 = max(0, x - r_zone), min(x + r_zone, w) - y1, y2 = max(0, y - r_zone), min(y + r_zone, h) + x1, x2 = max(0, x - radius), min(x + radius, w) + y1, y2 = max(0, y - radius), min(y + radius, h) region_size = y2 - y1, x2 - x1 mask = np.zeros(region_size).astype("uint8") - cv2.circle(mask, (r_zone, r_zone), r_zone, 255, -1, lineType=cv2.CV_AA) + cv2.circle(mask, (radius, radius), radius, 255, -1, lineType=cv2.CV_AA) mask = np.dstack(3 * [(1.0 / 255) * mask]) orig = im[y1:y2, x1:x2] - blurred = cv2.blur(orig, (r_blur, r_blur)) + blurred = cv2.blur(orig, (intensity, intensity)) im[y1:y2, x1:x2] = mask * blurred + (1 - mask) * orig return im - return clip.fl(fl) + return clip.transform(filter) # ------- OVERWRITE IF REQUIREMENTS NOT MET ----------------------------- diff --git a/moviepy/video/fx/invert_colors.py b/moviepy/video/fx/invert_colors.py index 7d5aad6bc..15e675f05 100644 --- a/moviepy/video/fx/invert_colors.py +++ b/moviepy/video/fx/invert_colors.py @@ -4,5 +4,5 @@ def invert_colors(clip): The values of all pixels are replaced with (255-v) or (1-v) for masks Black becomes white, green becomes purple, etc. """ - maxi = 1.0 if clip.ismask else 255 - return clip.fl_image(lambda f: maxi - f) + maxi = 1.0 if clip.is_mask else 255 + return clip.image_transform(lambda f: maxi - f) diff --git a/moviepy/video/fx/loop.py b/moviepy/video/fx/loop.py index 2a57c5514..4488165f2 100644 --- a/moviepy/video/fx/loop.py +++ b/moviepy/video/fx/loop.py @@ -19,9 +19,9 @@ def loop(clip, n=None, duration=None): Total duration of the clip. Can be specified instead of n. """ previous_duration = clip.duration - clip = clip.fl_time(lambda t: t % previous_duration) + clip = clip.time_transform(lambda t: t % previous_duration) if n: duration = n * previous_duration if duration: - clip = clip.set_duration(duration) + clip = clip.with_duration(duration) return clip diff --git a/moviepy/video/fx/lum_contrast.py b/moviepy/video/fx/lum_contrast.py index 4893ad151..38261e512 100644 --- a/moviepy/video/fx/lum_contrast.py +++ b/moviepy/video/fx/lum_contrast.py @@ -1,11 +1,11 @@ -def lum_contrast(clip, lum=0, contrast=0, contrast_thr=127): +def lum_contrast(clip, lum=0, contrast=0, contrast_threshold=127): """ luminosity-contrast correction of a clip """ - def fl_image(im): + def image_filter(im): im = 1.0 * im # float conversion - corrected = im + lum + contrast * (im - float(contrast_thr)) + corrected = im + lum + contrast * (im - float(contrast_threshold)) corrected[corrected < 0] = 0 corrected[corrected > 255] = 255 return corrected.astype("uint8") - return clip.fl_image(fl_image) + return clip.image_transform(image_filter) diff --git a/moviepy/video/fx/make_loopable.py b/moviepy/video/fx/make_loopable.py index 81cd7bf66..2b1b3ad47 100644 --- a/moviepy/video/fx/make_loopable.py +++ b/moviepy/video/fx/make_loopable.py @@ -1,12 +1,15 @@ import moviepy.video.compositing.transitions as transfx +from moviepy.decorators import requires_duration from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip -def make_loopable(clip, cross): +@requires_duration +def make_loopable(clip, overlap_time): """ Makes the clip fade in progressively at its own end, this way - it can be looped indefinitely. ``cross`` is the duration in seconds + it can be looped indefinitely. ``overlap_time`` is the duration in seconds of the fade-in.""" - d = clip.duration - clip2 = clip.fx(transfx.crossfadein, cross).set_start(d - cross) - return CompositeVideoClip([clip, clip2]).subclip(cross, d) + clip2 = clip.fx(transfx.crossfadein, overlap_time).with_start( + clip.duration - overlap_time + ) + return CompositeVideoClip([clip, clip2]).subclip(overlap_time, clip.duration) diff --git a/moviepy/video/fx/margin.py b/moviepy/video/fx/margin.py index 9b12d557c..0f44b104b 100644 --- a/moviepy/video/fx/margin.py +++ b/moviepy/video/fx/margin.py @@ -6,14 +6,20 @@ @apply_to_mask def margin( - clip, mar=None, left=0, right=0, top=0, bottom=0, color=(0, 0, 0), opacity=1.0 + clip, + margin_size=None, + left=0, + right=0, + top=0, + bottom=0, + color=(0, 0, 0), + opacity=1.0, ): """ Draws an external margin all around the frame. - :param mar: if not ``None``, then the new clip has a margin of - size ``mar`` in pixels on the left, right, top, and bottom. - + :param margin_size: if not ``None``, then the new clip has a margin_size of + size ``margin_size`` in pixels on the left, right, top, and bottom. :param left, right, top, bottom: width of the margin in pixel in these directions. @@ -24,15 +30,15 @@ def margin( """ - if (opacity != 1.0) and (clip.mask is None) and not (clip.ismask): + if (opacity != 1.0) and (clip.mask is None) and not (clip.is_mask): clip = clip.add_mask() - if mar is not None: - left = right = top = bottom = mar + if margin_size is not None: + left = right = top = bottom = margin_size def make_bg(w, h): new_w, new_h = w + left + right, h + top + bottom - if clip.ismask: + if clip.is_mask: shape = (new_h, new_w) bg = np.tile(opacity, (new_h, new_w)).astype(float).reshape(shape) else: @@ -43,15 +49,15 @@ def make_bg(w, h): if isinstance(clip, ImageClip): im = make_bg(clip.w, clip.h) im[top : top + clip.h, left : left + clip.w] = clip.img - return clip.fl_image(lambda pic: im) + return clip.image_transform(lambda pic: im) else: - def fl(gf, t): + def filter(gf, t): pic = gf(t) h, w = pic.shape[:2] im = make_bg(w, h) im[top : top + h, left : left + w] = pic return im - return clip.fl(fl) + return clip.transform(filter) diff --git a/moviepy/video/fx/mask_and.py b/moviepy/video/fx/mask_and.py index 3ba7d31d5..5603c6d61 100644 --- a/moviepy/video/fx/mask_and.py +++ b/moviepy/video/fx/mask_and.py @@ -14,6 +14,8 @@ def mask_and(clip, other_clip): other_clip = other_clip.img if isinstance(other_clip, np.ndarray): - return clip.fl_image(lambda f: np.minimum(f, other_clip)) + return clip.image_transform(lambda frame: np.minimum(frame, other_clip)) else: - return clip.fl(lambda gf, t: np.minimum(gf(t), other_clip.get_frame(t))) + return clip.transform( + lambda get_frame, t: np.minimum(get_frame(t), other_clip.get_frame(t)) + ) diff --git a/moviepy/video/fx/mask_color.py b/moviepy/video/fx/mask_color.py index 8e791b8e7..3f745f49e 100644 --- a/moviepy/video/fx/mask_color.py +++ b/moviepy/video/fx/mask_color.py @@ -1,18 +1,18 @@ import numpy as np -def mask_color(clip, color=None, thr=0, s=1): +def mask_color(clip, color=None, threshold=0, stiffness=1): """Returns a new clip with a mask for transparency where the original clip is of the given color. - You can also have a "progressive" mask by specifying a non-nul distance - threshold thr. In this case, if the distance between a pixel and the given + You can also have a "progressive" mask by specifying a non-null distance + threshold ``threshold``. In this case, if the distance between a pixel and the given color is d, the transparency will be - d**s / (thr**s + d**s) + d**stiffness / (threshold**stiffness + d**stiffness) - which is 1 when d>>thr and 0 for d<>threshold and 0 for d< pic.shape[1] or ly > pic.shape[0]: # For upsizing use linear for good quality & decent speed interpolation = cv2.INTER_LINEAR @@ -25,16 +25,16 @@ def resizer(pic, newsize): from PIL import Image import numpy as np - def resizer(pic, newsize): - newsize = list(map(int, newsize))[::-1] + def resizer(pic, new_size): + new_size = list(map(int, new_size))[::-1] shape = pic.shape if len(shape) == 3: - newshape = (newsize[0], newsize[1], shape[2]) + newshape = (new_size[0], new_size[1], shape[2]) else: - newshape = (newsize[0], newsize[1]) + newshape = (new_size[0], new_size[1]) - pilim = Image.fromarray(pic) - resized_pil = pilim.resize(newsize[::-1], Image.ANTIALIAS) + pil_img = Image.fromarray(pic) + resized_pil = pil_img.resize(new_size[::-1], Image.ANTIALIAS) # arr = np.fromstring(resized_pil.tostring(), dtype='uint8') # arr.reshape(newshape) return np.array(resized_pil) @@ -46,8 +46,8 @@ def resizer(pic, newsize): try: from scipy.misc import imresize - def resizer(pic, newsize): - return imresize(pic, map(int, newsize[::-1])) + def resizer(pic, new_size): + return imresize(pic, map(int, new_size[::-1])) resizer.origin = "Scipy" @@ -55,14 +55,14 @@ def resizer(pic, newsize): resize_possible = False -def resize(clip, newsize=None, height=None, width=None, apply_to_mask=True): +def resize(clip, new_size=None, height=None, width=None, apply_to_mask=True): """ Returns a video clip that is a resized version of the clip. Parameters ------------ - newsize: + new_size: Can be either - ``(width,height)`` in pixels or a float representing - A scaling factor, like 0.5 @@ -88,93 +88,96 @@ def resize(clip, newsize=None, height=None, width=None, apply_to_mask=True): w, h = clip.size - if newsize is not None: + if new_size is not None: - def trans_newsize(ns): - - if isinstance(ns, (int, float)): - return [ns * w, ns * h] + def translate_new_size(new_size_): + """ + Returns a [w, h] pair from `new_size_`. If `new_size_` is a scalar, then work out + the correct pair using the clip's size. Otherwise just return `new_size_` + """ + if isinstance(new_size_, (int, float)): + return [new_size_ * w, new_size_ * h] else: - return ns + return new_size_ - if hasattr(newsize, "__call__"): + if hasattr(new_size, "__call__"): # The resizing is a function of time - def newsize2(t): - return trans_newsize(newsize(t)) + def get_new_size(t): + return translate_new_size(new_size(t)) - if clip.ismask: + if clip.is_mask: - def fun(gf, t): + def filter(get_frame, t): return ( - 1.0 * resizer((255 * gf(t)).astype("uint8"), newsize2(t)) / 255 + resizer((255 * get_frame(t)).astype("uint8"), get_new_size(t)) + / 255.0 ) else: - def fun(gf, t): - return resizer(gf(t).astype("uint8"), newsize2(t)) + def filter(get_frame, t): + return resizer(get_frame(t).astype("uint8"), get_new_size(t)) - newclip = clip.fl( - fun, keep_duration=True, apply_to=(["mask"] if apply_to_mask else []) + newclip = clip.transform( + filter, keep_duration=True, apply_to=(["mask"] if apply_to_mask else []) ) if apply_to_mask and clip.mask is not None: - newclip.mask = resize(clip.mask, newsize, apply_to_mask=False) + newclip.mask = resize(clip.mask, new_size, apply_to_mask=False) return newclip else: - newsize = trans_newsize(newsize) + new_size = translate_new_size(new_size) elif height is not None: if hasattr(height, "__call__"): - def fun(t): + def func(t): return 1.0 * int(height(t)) / h - return resize(clip, fun) + return resize(clip, func) else: - newsize = [w * height / h, height] + new_size = [w * height / h, height] elif width is not None: if hasattr(width, "__call__"): - def fun(t): + def func(t): return 1.0 * width(t) / w - return resize(clip, fun) + return resize(clip, func) else: - newsize = [width, h * width / w] + new_size = [width, h * width / w] # From here, the resizing is constant (not a function of time), size=newsize - if clip.ismask: + if clip.is_mask: - def fl(pic): - return 1.0 * resizer((255 * pic).astype("uint8"), newsize) / 255.0 + def image_filter(pic): + return 1.0 * resizer((255 * pic).astype("uint8"), new_size) / 255.0 else: - def fl(pic): - return resizer(pic.astype("uint8"), newsize) + def image_filter(pic): + return resizer(pic.astype("uint8"), new_size) - newclip = clip.fl_image(fl) + new_clip = clip.image_transform(image_filter) if apply_to_mask and clip.mask is not None: - newclip.mask = resize(clip.mask, newsize, apply_to_mask=False) + new_clip.mask = resize(clip.mask, new_size, apply_to_mask=False) - return newclip + return new_clip if not resize_possible: - doc = resize.__doc__ - def resize(clip, newsize=None, height=None, width=None): + def resize(clip, new_size=None, height=None, width=None): raise ImportError("fx resize needs OpenCV or Scipy or PIL") resize.__doc__ = doc diff --git a/moviepy/video/fx/rotate.py b/moviepy/video/fx/rotate.py index b60485bec..65e638b0e 100644 --- a/moviepy/video/fx/rotate.py +++ b/moviepy/video/fx/rotate.py @@ -26,7 +26,7 @@ def rotate(clip, angle, unit="deg", resample="bicubic", expand=True): If the angle is not a multiple of 90 (degrees), the package ``pillow`` must be installed, and there will be black borders. You can make them transparent with - >>> newclip = clip.add_mask().rotate(72) + >>> new_clip = clip.add_mask().rotate(72) Parameters =========== @@ -54,31 +54,32 @@ def rotate(clip, angle, unit="deg", resample="bicubic", expand=True): "bicubic": Image.BICUBIC, }[resample] - if not hasattr(angle, "__call__"): - # if angle is a constant, convert to a constant function - a = +angle + if hasattr(angle, "__call__"): + # angle is a function + get_angle = angle + else: + # angle is a constant so convert to a constant function + def get_angle(t): + return angle - def angle(t): - return a + transpose = [1, 0] if clip.is_mask else [1, 0, 2] - transpo = [1, 0] if clip.ismask else [1, 0, 2] + def filter(get_frame, t): - def fl(gf, t): - - a = angle(t) - im = gf(t) + angle = get_angle(t) + im = get_frame(t) if unit == "rad": - a = 360.0 * a / (2 * np.pi) + angle = 360.0 * angle / (2 * np.pi) - a %= 360 - if (a == 0) and expand: + angle %= 360 + if (angle == 0) and expand: return im - if (a == 90) and expand: - return np.transpose(im, axes=transpo)[::-1] - elif (a == 270) and expand: - return np.transpose(im, axes=transpo)[:, ::-1] - elif (a == 180) and expand: + if (angle == 90) and expand: + return np.transpose(im, axes=transpose)[::-1] + elif (angle == 270) and expand: + return np.transpose(im, axes=transpose)[:, ::-1] + elif (angle == 180) and expand: return im[::-1, ::-1] elif not PIL_FOUND: raise ValueError( @@ -86,6 +87,6 @@ def fl(gf, t): ' are supported, please install "Pillow" with `pip install pillow`' ) else: - return pil_rotater(im, a, resample=resample, expand=expand) + return pil_rotater(im, angle, resample=resample, expand=expand) - return clip.fl(fl, apply_to=["mask"]) + return clip.transform(filter, apply_to=["mask"]) diff --git a/moviepy/video/fx/scroll.py b/moviepy/video/fx/scroll.py index d2be8af29..f6a43389b 100644 --- a/moviepy/video/fx/scroll.py +++ b/moviepy/video/fx/scroll.py @@ -23,12 +23,12 @@ def scroll( if w is None: w = clip.w - xmax = clip.w - w - 1 - ymax = clip.h - h - 1 + x_max = clip.w - w - 1 + y_max = clip.h - h - 1 - def f(gf, t): - x = int(max(0, min(xmax, x_start + round(x_speed * t)))) - y = int(max(0, min(ymax, y_start + round(y_speed * t)))) - return gf(t)[y : y + h, x : x + w] + def filter(get_frame, t): + x = int(max(0, min(x_max, x_start + round(x_speed * t)))) + y = int(max(0, min(y_max, y_start + round(y_speed * t)))) + return get_frame(t)[y : y + h, x : x + w] - return clip.fl(f, apply_to=apply_to) + return clip.transform(filter, apply_to=apply_to) diff --git a/moviepy/video/fx/speedx.py b/moviepy/video/fx/speedx.py index ce4c76955..6e93fc05a 100644 --- a/moviepy/video/fx/speedx.py +++ b/moviepy/video/fx/speedx.py @@ -13,9 +13,9 @@ def speedx(clip, factor=None, final_duration=None): if final_duration: factor = 1.0 * clip.duration / final_duration - newclip = clip.fl_time(lambda t: factor * t, apply_to=["mask", "audio"]) + new_clip = clip.time_transform(lambda t: factor * t, apply_to=["mask", "audio"]) if clip.duration is not None: - newclip = newclip.set_duration(1.0 * clip.duration / factor) + new_clip = new_clip.with_duration(1.0 * clip.duration / factor) - return newclip + return new_clip diff --git a/moviepy/video/fx/supersample.py b/moviepy/video/fx/supersample.py index 63adad4a4..7a33423a4 100644 --- a/moviepy/video/fx/supersample.py +++ b/moviepy/video/fx/supersample.py @@ -1,13 +1,15 @@ import numpy as np -def supersample(clip, d, nframes): - """Replaces each frame at time t by the mean of `nframes` equally spaced frames +def supersample(clip, d, n_frames): + """Replaces each frame at time t by the mean of `n_frames` equally spaced frames taken in the interval [t-d, t+d]. This results in motion blur.""" - def fl(gf, t): - tt = np.linspace(t - d, t + d, nframes) - avg = np.mean(1.0 * np.array([gf(t_) for t_ in tt], dtype="uint16"), axis=0) - return avg.astype("uint8") + def filter(get_frame, t): + timings = np.linspace(t - d, t + d, n_frames) + frame_average = np.mean( + 1.0 * np.array([get_frame(t_) for t_ in timings], dtype="uint16"), axis=0 + ) + return frame_average.astype("uint8") - return clip.fl(fl) + return clip.transform(filter) diff --git a/moviepy/video/fx/time_mirror.py b/moviepy/video/fx/time_mirror.py index ba4c13e7f..ef5f071ce 100644 --- a/moviepy/video/fx/time_mirror.py +++ b/moviepy/video/fx/time_mirror.py @@ -10,4 +10,4 @@ def time_mirror(self): The clip must have its ``duration`` attribute set. The same effect is applied to the clip's audio and mask if any. """ - return self.fl_time(lambda t: self.duration - t - 1, keep_duration=True) + return self.time_transform(lambda t: self.duration - t - 1, keep_duration=True) diff --git a/moviepy/video/io/ImageSequenceClip.py b/moviepy/video/io/ImageSequenceClip.py index 9d548fef8..5104a6ba5 100644 --- a/moviepy/video/io/ImageSequenceClip.py +++ b/moviepy/video/io/ImageSequenceClip.py @@ -34,7 +34,7 @@ class ImageSequenceClip(VideoClip): with_mask Should the alpha layer of PNG images be considered as a mask ? - ismask + is_mask Will this sequence of pictures be used as an animated mask. Notes @@ -52,7 +52,7 @@ def __init__( fps=None, durations=None, with_mask=True, - ismask=False, + is_mask=False, load_images=False, ): @@ -60,7 +60,7 @@ def __init__( if (fps is None) and (durations is None): raise ValueError("Please provide either 'fps' or 'durations'.") - VideoClip.__init__(self, ismask=ismask) + VideoClip.__init__(self, is_mask=is_mask) # Parse the data @@ -69,7 +69,7 @@ def __init__( if isinstance(sequence, list): if isinstance(sequence[0], str): if load_images: - sequence = [imread(f) for f in sequence] + sequence = [imread(file) for file in sequence] fromfiles = False else: fromfiles = True @@ -79,7 +79,9 @@ def __init__( else: # sequence is a folder name, make it a list of files: fromfiles = True - sequence = sorted([os.path.join(sequence, f) for f in os.listdir(sequence)]) + sequence = sorted( + [os.path.join(sequence, file) for file in os.listdir(sequence)] + ) # check that all the images are of the same size if isinstance(sequence[0], str): @@ -116,34 +118,34 @@ def find_image_index(t): if fromfiles: - self.lastindex = None - self.lastimage = None + self.last_index = None + self.last_image = None def make_frame(t): index = find_image_index(t) - if index != self.lastindex: - self.lastimage = imread(self.sequence[index])[:, :, :3] - self.lastindex = index + if index != self.last_index: + self.last_image = imread(self.sequence[index])[:, :, :3] + self.last_index = index - return self.lastimage + return self.last_image if with_mask and (imread(self.sequence[0]).shape[2] == 4): - self.mask = VideoClip(ismask=True) - self.mask.lastindex = None - self.mask.lastimage = None + self.mask = VideoClip(is_mask=True) + self.mask.last_index = None + self.mask.last_image = None def mask_make_frame(t): index = find_image_index(t) - if index != self.mask.lastindex: + if index != self.mask.last_index: frame = imread(self.sequence[index])[:, :, 3] - self.mask.lastimage = frame.astype(float) / 255 - self.mask.lastindex = index + self.mask.last_image = frame.astype(float) / 255 + self.mask.last_index = index - return self.mask.lastimage + return self.mask.last_image self.mask.make_frame = mask_make_frame self.mask.size = mask_make_frame(0).shape[:2][::-1] @@ -157,7 +159,7 @@ def make_frame(t): if with_mask and (self.sequence[0].shape[2] == 4): - self.mask = VideoClip(ismask=True) + self.mask = VideoClip(is_mask=True) def mask_make_frame(t): index = find_image_index(t) diff --git a/moviepy/video/io/VideoFileClip.py b/moviepy/video/io/VideoFileClip.py index 24d625497..432889f26 100644 --- a/moviepy/video/io/VideoFileClip.py +++ b/moviepy/video/io/VideoFileClip.py @@ -36,7 +36,7 @@ class VideoFileClip(VideoClip): wish to read the audio. target_resolution: - Set to (desired_height, desired_width) to have ffmpeg resize the frames + Set to (desired_width, desired_height) to have ffmpeg resize the frames before returning them. This is much faster than streaming in high-res and then resizing. If either dimension is None, the frames are resized by keeping the existing aspect ratio. @@ -51,7 +51,7 @@ class VideoFileClip(VideoClip): can be set to 'tbr', which may be helpful if you are finding that it is reading the incorrect fps from the file. - pix_fmt + pixel_format Optional: Pixel format for the video to read. If is not specified 'rgb24' will be used as the default format unless ``has_mask`` is set as ``True``, then 'rgba' will be used. @@ -92,18 +92,18 @@ def __init__( audio_fps=44100, audio_nbytes=2, fps_source="fps", - pix_fmt=None, + pixel_format=None, ): VideoClip.__init__(self) # Make a reader - if not pix_fmt: - pix_fmt = "rgba" if has_mask else "rgb24" + if not pixel_format: + pixel_format = "rgba" if has_mask else "rgb24" self.reader = FFMPEG_VideoReader( filename, decode_file=decode_file, - pix_fmt=pix_fmt, + pixel_format=pixel_format, target_resolution=target_resolution, resize_algo=resize_algorithm, fps_source=fps_source, @@ -123,12 +123,12 @@ def __init__( self.make_frame = lambda t: self.reader.get_frame(t)[:, :, :3] - def mask_mf(t): + def mask_make_frame(t): return self.reader.get_frame(t)[:, :, 3] / 255.0 - self.mask = VideoClip(ismask=True, make_frame=mask_mf).set_duration( - self.duration - ) + self.mask = VideoClip( + is_mask=True, make_frame=mask_make_frame + ).with_duration(self.duration) self.mask.fps = self.fps else: diff --git a/moviepy/video/io/ffmpeg_reader.py b/moviepy/video/io/ffmpeg_reader.py index cbacc9bba..adc643761 100644 --- a/moviepy/video/io/ffmpeg_reader.py +++ b/moviepy/video/io/ffmpeg_reader.py @@ -13,7 +13,7 @@ import numpy as np from moviepy.config import FFMPEG_BINARY # ffmpeg, ffmpeg.exe, etc... -from moviepy.tools import cvsecs +from moviepy.tools import convert_to_seconds class FFMPEG_VideoReader: @@ -23,7 +23,7 @@ def __init__( decode_file=True, print_infos=False, bufsize=None, - pix_fmt="rgb24", + pixel_format="rgb24", check_duration=True, target_resolution=None, resize_algo="bicubic", @@ -40,9 +40,6 @@ def __init__( self.rotation = infos["video_rotation"] if target_resolution: - # revert the order, as ffmpeg used (width, height) - target_resolution = target_resolution[1], target_resolution[0] - if None in target_resolution: ratio = 1 for idx, target in enumerate(target_resolution): @@ -60,8 +57,8 @@ def __init__( self.infos = infos - self.pix_fmt = pix_fmt - self.depth = 4 if pix_fmt[-1] == "a" else 3 + self.pixel_format = pixel_format + self.depth = 4 if pixel_format[-1] == "a" else 3 # 'a' represents 'alpha' which means that each pixel has 4 values instead of 3. # See https://github.com/Zulko/moviepy/issues/1070#issuecomment-644457274 @@ -72,20 +69,20 @@ def __init__( self.bufsize = bufsize self.initialize() - def initialize(self, starttime=0): + def initialize(self, start_time=0): """ Opens the file, creates the pipe. - Sets self.pos to the appropriate value (1 if starttime == 0 because + Sets self.pos to the appropriate value (1 if start_time == 0 because it pre-reads the first frame) """ self.close(delete_lastread=False) # if any - if starttime != 0: - offset = min(1, starttime) + if start_time != 0: + offset = min(1, start_time) i_arg = [ "-ss", - "%.06f" % (starttime - offset), + "%.06f" % (start_time - offset), "-i", self.filename, "-ss", @@ -107,7 +104,7 @@ def initialize(self, starttime=0): "-sws_flags", self.resize_algo, "-pix_fmt", - self.pix_fmt, + self.pixel_format, "-vcodec", "rawvideo", "-", @@ -127,7 +124,7 @@ def initialize(self, starttime=0): # self.pos represents the (0-indexed) index of the frame that is next in line # to be read by self.read_frame(). # Eg when self.pos is 1, the 2nd frame will be read next. - self.pos = self.get_frame_number(starttime) + self.pos = self.get_frame_number(start_time) self.lastread = self.read_frame() def skip_frames(self, n=1): @@ -160,7 +157,7 @@ def read_frame(self): + "Using the last valid frame instead.", UserWarning, ) - if not hasattr(self, "lastread"): + if not hasattr(self, "last_read"): raise IOError( ( "MoviePy error: failed to read the first frame of " @@ -172,7 +169,7 @@ def read_frame(self): ) ) - result = self.lastread + result = self.last_read else: if hasattr(np, "frombuffer"): @@ -180,7 +177,7 @@ def read_frame(self): else: result = np.fromstring(s, dtype="uint8") result.shape = (h, w, len(s) // (w * h)) # reshape((h, w, len(s)//(w*h))) - self.lastread = result + self.last_read = result # We have to do this down here because `self.pos` is used in the warning above self.pos += 1 @@ -204,10 +201,10 @@ def get_frame(self, t): if not self.proc: print(f"Proc not detected") self.initialize(t) - return self.lastread + return self.last_read if pos == self.pos: - return self.lastread + return self.last_read elif (pos < self.pos) or (pos > self.pos + 100): # We can't just skip forward to `pos` or it would take too long self.initialize(t) @@ -233,14 +230,14 @@ def close(self, delete_lastread=True): self.proc.stderr.close() self.proc.wait() self.proc = None - if delete_lastread and hasattr(self, "lastread"): - del self.lastread + if delete_lastread and hasattr(self, "last_read"): + del self.last_read def __del__(self): self.close() -def ffmpeg_read_image(filename, with_mask=True, pix_fmt=None): +def ffmpeg_read_image(filename, with_mask=True, pixel_format=None): """Read an image file (PNG, BMP, JPEG...). Wraps FFMPEG_Videoreader to read just one image. @@ -259,16 +256,18 @@ def ffmpeg_read_image(filename, with_mask=True, pix_fmt=None): If the image has a transparency layer, ``with_mask=true`` will save this layer as the mask of the returned ImageClip - pix_fmt + pixel_format Optional: Pixel format for the image to read. If is not specified 'rgb24' will be used as the default format unless ``with_mask`` is set as ``True``, then 'rgba' will be used. """ - if not pix_fmt: - pix_fmt = "rgba" if with_mask else "rgb24" - reader = FFMPEG_VideoReader(filename, pix_fmt=pix_fmt, check_duration=False) - im = reader.lastread + if not pixel_format: + pixel_format = "rgba" if with_mask else "rgb24" + reader = FFMPEG_VideoReader( + filename, pixel_format=pixel_format, check_duration=False + ) + im = reader.last_read del reader return im @@ -339,14 +338,16 @@ def ffmpeg_parse_infos( else: line = [l for l in lines if "Duration:" in l][-1] match = re.findall("([0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9])", line)[0] - result["duration"] = cvsecs(match) + result["duration"] = convert_to_seconds(match) except Exception: raise IOError( f"MoviePy error: failed to read the duration of file {filename}.\nHere are the file infos returned by ffmpeg:\n\n{infos}" ) # get the output line that speaks about video - lines_video = [l for l in lines if " Video: " in l and re.search(r"\d+x\d+", l)] + lines_video = [ + line for line in lines if " Video: " in line and re.search(r"\d+x\d+", line) + ] result["video_found"] = lines_video != [] @@ -356,9 +357,8 @@ def ffmpeg_parse_infos( # get the size, of the form 460x320 (w x h) match = re.search(" [0-9]*x[0-9]*(,| )", line) - s = list(map(int, line[match.start() : match.end() - 1].split("x"))) - result["video_size"] = s - + size = list(map(int, line[match.start() : match.end() - 1].split("x"))) + result["video_size"] = size except Exception: raise IOError( ( @@ -427,7 +427,9 @@ def get_fps(): # get the video rotation info. try: rotation_lines = [ - l for l in lines if "rotate :" in l and re.search(r"\d+$", l) + line + for line in lines + if "rotate :" in line and re.search(r"\d+$", line) ] if len(rotation_lines): rotation_line = rotation_lines[0] diff --git a/moviepy/video/io/ffmpeg_tools.py b/moviepy/video/io/ffmpeg_tools.py index 5cb300135..8d41884e5 100644 --- a/moviepy/video/io/ffmpeg_tools.py +++ b/moviepy/video/io/ffmpeg_tools.py @@ -7,50 +7,26 @@ from moviepy.tools import subprocess_call -@convert_path_to_string("filename") -def ffmpeg_movie_from_frames(filename, folder, fps, digits=6, bitrate="v"): - """ - Writes a movie out of the frames (picture files) in a folder. - Almost deprecated. - """ - s = "%" + "%02d" % digits + "d.png" - cmd = [ - FFMPEG_BINARY, - "-y", - "-f", - "image2", - "-r", - "%d" % fps, - "-i", - os.path.join(folder, folder) + "/" + s, - "-b", - "%dk" % bitrate, - "-r", - "%d" % fps, - filename, - ] - - subprocess_call(cmd) - - -@convert_path_to_string(("filename", "targetname")) -def ffmpeg_extract_subclip(filename, t1, t2, targetname=None): - """Makes a new video file playing video file ``filename`` between - the times ``t1`` and ``t2``.""" - name, ext = os.path.splitext(filename) - if not targetname: - T1, T2 = [int(1000 * t) for t in [t1, t2]] - targetname = "%sSUB%d_%d%s" % (name, T1, T2, ext) +@convert_path_to_string(("inputfile", "outputfile")) +def ffmpeg_extract_subclip( + inputfile, start_time, end_time, outputfile=None, logger="bar" +): + """Makes a new video file playing video file ``inputfile`` between + the times ``start_time`` and ``end_time``.""" + name, ext = os.path.splitext(inputfile) + if not outputfile: + T1, T2 = [int(1000 * t) for t in [start_time, end_time]] + outputfile = "%sSUB%d_%d%s" % (name, T1, T2, ext) cmd = [ FFMPEG_BINARY, "-y", "-ss", - "%0.2f" % t1, + "%0.2f" % start_time, "-i", - filename, + inputfile, "-t", - "%0.2f" % (t2 - t1), + "%0.2f" % (end_time - start_time), "-map", "0", "-vcodec", @@ -58,43 +34,42 @@ def ffmpeg_extract_subclip(filename, t1, t2, targetname=None): "-acodec", "copy", "-copyts", - targetname, + outputfile, ] - subprocess_call(cmd) + subprocess_call(cmd, logger=logger) -@convert_path_to_string(("video", "audio", "output")) +@convert_path_to_string(("videofile", "audiofile", "outputfile")) def ffmpeg_merge_video_audio( - video, - audio, - output, - vcodec="copy", - acodec="copy", - ffmpeg_output=False, + videofile, + audiofile, + outputfile, + video_codec="copy", + audio_codec="copy", logger="bar", ): - """merges video file ``video`` and audio file ``audio`` into one - movie file ``output``.""" + """Merges video file ``videofile`` and audio file ``audiofile`` into one + movie file ``outputfile``.""" cmd = [ FFMPEG_BINARY, "-y", "-i", - audio, + audiofile, "-i", - video, + videofile, "-vcodec", - vcodec, + video_codec, "-acodec", - acodec, - output, + audio_codec, + outputfile, ] subprocess_call(cmd, logger=logger) -@convert_path_to_string(("inputfile", "output")) -def ffmpeg_extract_audio(inputfile, output, bitrate=3000, fps=44100): - """ extract the sound from a video file and save it in ``output`` """ +@convert_path_to_string(("inputfile", "outputfile")) +def ffmpeg_extract_audio(inputfile, outputfile, bitrate=3000, fps=44100, logger="bar"): + """ Extract the sound from a video file and save it in ``outputfile`` """ cmd = [ FFMPEG_BINARY, "-y", @@ -104,25 +79,25 @@ def ffmpeg_extract_audio(inputfile, output, bitrate=3000, fps=44100): "%dk" % bitrate, "-ar", "%d" % fps, - output, + outputfile, ] - subprocess_call(cmd) + subprocess_call(cmd, logger=logger) -@convert_path_to_string(("video", "output")) -def ffmpeg_resize(video, output, size): - """resizes ``video`` to new size ``size`` and write the result - in file ``output``.""" +@convert_path_to_string(("inputfile", "outputfile")) +def ffmpeg_resize(inputfile, outputfile, size, logger="bar"): + """resizes ``inputfile`` to new size ``size`` and write the result + in file ``outputfile``.""" cmd = [ FFMPEG_BINARY, "-i", - video, + inputfile, "-vf", "scale=%d:%d" % (size[0], size[1]), - output, + outputfile, ] - subprocess_call(cmd) + subprocess_call(cmd, logger=logger) @convert_path_to_string(("inputfile", "outputfile", "output_dir")) diff --git a/moviepy/video/io/ffmpeg_writer.py b/moviepy/video/io/ffmpeg_writer.py index d1a4a15e8..6b1e46927 100644 --- a/moviepy/video/io/ffmpeg_writer.py +++ b/moviepy/video/io/ffmpeg_writer.py @@ -58,13 +58,13 @@ class FFMPEG_VideoWriter: Only relevant for codecs which accept a bitrate. "5000k" offers nice results in general. - withmask + with_mask Boolean. Set to ``True`` if there is a mask in the video to be encoded. - pix_fmt + pixel_format Optional: Pixel format for the output video file. If is not specified - 'rgb24' will be used as the default format unless ``withmask`` is set + 'rgb24' will be used as the default format unless ``with_mask`` is set as ``True``, then 'rgba' will be used. """ @@ -78,11 +78,11 @@ def __init__( audiofile=None, preset="medium", bitrate=None, - withmask=False, + with_mask=False, logfile=None, threads=None, ffmpeg_params=None, - pix_fmt=None, + pixel_format=None, ): if logfile is None: logfile = sp.PIPE @@ -90,8 +90,8 @@ def __init__( self.filename = filename self.codec = codec self.ext = self.filename.split(".")[-1] - if not pix_fmt: - pix_fmt = "rgba" if withmask else "rgb24" + if not pixel_format: + pixel_format = "rgba" if with_mask else "rgb24" # order is important cmd = [ @@ -106,7 +106,7 @@ def __init__( "-s", "%dx%d" % (size[0], size[1]), "-pix_fmt", - pix_fmt, + pixel_format, "-r", "%.02f" % fps, "-an", @@ -220,13 +220,13 @@ def ffmpeg_write_video( codec="libx264", bitrate=None, preset="medium", - withmask=False, + with_mask=False, write_logfile=False, audiofile=None, threads=None, ffmpeg_params=None, logger="bar", - pix_fmt=None, + pixel_format=None, ): """Write the clip to a videofile. See VideoClip.write_videofile for details on the parameters. @@ -238,8 +238,8 @@ def ffmpeg_write_video( else: logfile = None logger(message="Moviepy - Writing video %s\n" % filename) - if not pix_fmt: - pix_fmt = "rgba" if withmask else "rgb24" + if not pixel_format: + pixel_format = "rgba" if with_mask else "rgb24" with FFMPEG_VideoWriter( filename, clip.size, @@ -251,12 +251,12 @@ def ffmpeg_write_video( audiofile=audiofile, threads=threads, ffmpeg_params=ffmpeg_params, - pix_fmt=pix_fmt, + pixel_format=pixel_format, ) as writer: for t, frame in clip.iter_frames( logger=logger, with_times=True, fps=fps, dtype="uint8" ): - if withmask: + if with_mask: mask = 255 * clip.mask.get_frame(t) if mask.dtype != "uint8": mask = mask.astype("uint8") @@ -269,14 +269,14 @@ def ffmpeg_write_video( logger(message="Moviepy - Done !") -def ffmpeg_write_image(filename, image, logfile=False, pix_fmt=None): +def ffmpeg_write_image(filename, image, logfile=False, pixel_format=None): """Writes an image (HxWx3 or HxWx4 numpy array) to a file, using ffmpeg.""" if image.dtype != "uint8": image = image.astype("uint8") - if not pix_fmt: - pix_fmt = "rgba" if (image.shape[2] == 4) else "rgb24" + if not pixel_format: + pixel_format = "rgba" if (image.shape[2] == 4) else "rgb24" cmd = [ FFMPEG_BINARY, @@ -286,7 +286,7 @@ def ffmpeg_write_image(filename, image, logfile=False, pix_fmt=None): "-f", "rawvideo", "-pix_fmt", - pix_fmt, + pixel_format, "-i", "-", filename, diff --git a/moviepy/video/io/gif_writers.py b/moviepy/video/io/gif_writers.py index 16865c112..b08aac443 100644 --- a/moviepy/video/io/gif_writers.py +++ b/moviepy/video/io/gif_writers.py @@ -29,7 +29,7 @@ def write_gif_with_tempfiles( dispose=True, colors=None, logger="bar", - pix_fmt=None, + pixel_format=None, ): """Write the VideoClip to a GIF file. @@ -41,7 +41,7 @@ def write_gif_with_tempfiles( """ logger = proglog.default_bar_logger(logger) - fileName, ext = os.path.splitext(filename) + file_root, ext = os.path.splitext(filename) tt = np.arange(0, clip.duration, 1.0 / fps) tempfiles = [] @@ -51,18 +51,18 @@ def write_gif_with_tempfiles( for i, t in logger.iter_bar(t=list(enumerate(tt))): - name = "%s_GIFTEMP%04d.png" % (fileName, i + 1) + name = "%s_GIFTEMP%04d.png" % (file_root, i + 1) tempfiles.append(name) - clip.save_frame(name, t, withmask=True) + clip.save_frame(name, t, with_mask=True) delay = int(100.0 / fps) if clip.mask is None: - withmask = False + with_mask = False if program == "ImageMagick": - if not pix_fmt: - pix_fmt = "RGBA" if withmask else "RGB" + if not pixel_format: + pixel_format = "RGBA" if with_mask else "RGB" logger(message="MoviePy - - Optimizing GIF with ImageMagick...") cmd = ( @@ -74,7 +74,7 @@ def write_gif_with_tempfiles( "%d" % (2 if dispose else 1), "-loop", "%d" % loop, - "%s_GIFTEMP*.png" % fileName, + "%s_GIFTEMP*.png" % file_root, "-coalesce", "-fuzz", "%02d" % fuzz + "%", @@ -82,15 +82,15 @@ def write_gif_with_tempfiles( "%s" % opt, "-set", "colorspace", - pix_fmt, + pixel_format, ] + (["-colors", "%d" % colors] if colors is not None else []) + [filename] ) elif program == "ffmpeg": - if not pix_fmt: - pix_fmt = "rgba" if withmask else "rgb24" + if not pixel_format: + pixel_format = "rgba" if with_mask else "rgb24" cmd = [ FFMPEG_BINARY, @@ -100,12 +100,12 @@ def write_gif_with_tempfiles( "-r", str(fps), "-i", - fileName + "_GIFTEMP%04d.png", + file_root + "_GIFTEMP%04d.png", "-r", str(fps), filename, "-pix_fmt", - (pix_fmt), + (pixel_format), ] try: @@ -129,8 +129,8 @@ def write_gif_with_tempfiles( raise IOError(error) - for f in tempfiles: - os.remove(f) + for file in tempfiles: + os.remove(file) @requires_duration @@ -142,12 +142,12 @@ def write_gif( program="ImageMagick", opt="OptimizeTransparency", fuzz=1, - withmask=True, + with_mask=True, loop=0, dispose=True, colors=None, logger="bar", - pix_fmt=None, + pixel_format=None, ): """Write the VideoClip to a GIF file, without temporary files. @@ -179,7 +179,7 @@ def write_gif( the colors that are less than fuzz% different are in fact the same. - pix_fmt + pixel_format Pixel format for the output gif file. If is not specified 'rgb24' will be used as the default format unless ``clip.mask`` exist, then 'rgba' will be used. This option is going to @@ -215,9 +215,9 @@ def write_gif( delay = 100.0 / fps logger = proglog.default_bar_logger(logger) if clip.mask is None: - withmask = False - if not pix_fmt: - pix_fmt = "rgba" if withmask else "rgb24" + with_mask = False + if not pixel_format: + pixel_format = "rgba" if with_mask else "rgb24" cmd1 = [ FFMPEG_BINARY, @@ -233,7 +233,7 @@ def write_gif( "-s", "%dx%d" % (clip.w, clip.h), "-pix_fmt", - (pix_fmt), + (pixel_format), "-i", "-", ] @@ -251,7 +251,7 @@ def write_gif( cmd1 + [ "-pix_fmt", - (pix_fmt), + (pixel_format), "-r", "%.02f" % fps, filename, @@ -317,7 +317,7 @@ def write_gif( for t, frame in clip.iter_frames( fps=fps, logger=logger, with_times=True, dtype="uint8" ): - if withmask: + if with_mask: mask = 255 * clip.mask.get_frame(t) frame = np.dstack([frame, mask]).astype("uint8") proc1.stdin.write(frame.tostring()) diff --git a/moviepy/video/io/html_tools.py b/moviepy/video/io/html_tools.py index c97eac7ea..dff90f598 100644 --- a/moviepy/video/io/html_tools.py +++ b/moviepy/video/io/html_tools.py @@ -97,7 +97,7 @@ def html_embed( TEMP_PREFIX = "__temp__" if isinstance(clip, ImageClip): filename = TEMP_PREFIX + ".png" - kwargs = {"filename": filename, "withmask": True} + kwargs = {"filename": filename, "with_mask": True} kwargs.update(rd_kwargs) clip.save_frame(**kwargs) elif isinstance(clip, VideoClip): @@ -164,8 +164,8 @@ def html_embed( "But note that embedding large videos may take all the memory away !" ) - with open(filename, "rb") as f: - data = b64encode(f.read()).decode("utf-8") + with open(filename, "rb") as file: + data = b64encode(file.read()).decode("utf-8") template = templates[filetype] diff --git a/moviepy/video/io/preview.py b/moviepy/video/io/preview.py index aa936b4b9..e4d6dca11 100644 --- a/moviepy/video/io/preview.py +++ b/moviepy/video/io/preview.py @@ -5,7 +5,8 @@ import pygame as pg from moviepy.decorators import convert_masks_to_RGB, requires_duration -from moviepy.tools import cvsecs +from moviepy.tools import convert_to_seconds +from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip pg.init() pg.display.set_caption("MoviePy") @@ -38,12 +39,11 @@ def show(clip, t=0, with_mask=True, interactive=False): """ if isinstance(t, tuple): - t = cvsecs(*t) + t = convert_to_seconds(*t) if with_mask and (clip.mask is not None): - import moviepy.video.compositing.CompositeVideoClip as cvc + clip = CompositeVideoClip([clip.with_position((0, 0))]) - clip = cvc.CompositeVideoClip([clip.set_position((0, 0))]) img = clip.get_frame(t) imdisplay(img) @@ -114,20 +114,20 @@ def preview( # pygame and openCV already use several cpus it seems. # two synchro-flags to tell whether audio and video are ready - videoFlag = threading.Event() - audioFlag = threading.Event() + video_flag = threading.Event() + audio_flag = threading.Event() # launch the thread audiothread = threading.Thread( target=clip.audio.preview, - args=(audio_fps, audio_buffersize, audio_nbytes, audioFlag, videoFlag), + args=(audio_fps, audio_buffersize, audio_nbytes, audio_flag, video_flag), ) audiothread.start() img = clip.get_frame(0) imdisplay(img, screen) if audio: # synchronize with audio - videoFlag.set() # say to the audio: video is ready - audioFlag.wait() # wait for the audio to be ready + video_flag.set() # say to the audio: video is ready + audio_flag.wait() # wait for the audio to be ready result = [] @@ -141,7 +141,7 @@ def preview( event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE ): if audio: - videoFlag.clear() + video_flag.clear() print("Interrupt") return result diff --git a/moviepy/video/io/sliders.py b/moviepy/video/io/sliders.py index 27473572a..a0daa2075 100644 --- a/moviepy/video/io/sliders.py +++ b/moviepy/video/io/sliders.py @@ -2,10 +2,10 @@ from matplotlib.widgets import Slider -def sliders(f, sliders_properties, wait_for_validation=False): +def sliders(func, sliders_properties, wait_for_validation=False): """A light GUI to manually explore and tune the outputs of a function. - slider_properties is a list of dicts (arguments for Slider ) + slider_properties is a list of dicts (arguments for Slider) def volume(x,y,z): return x*y*z @@ -16,18 +16,18 @@ def volume(x,y,z): inputExplorer(volume,intervals) """ - nVars = len(sliders_properties) - slider_width = 1.0 / nVars + n_vars = len(sliders_properties) + slider_width = 1.0 / n_vars # CREATE THE CANVAS figure, ax = plt.subplots(1) - figure.canvas.set_window_title("Inputs for '%s'" % (f.func_name)) + figure.canvas.set_window_title("Inputs for '%s'" % (func.func_name)) # choose an appropriate height width, height = figure.get_size_inches() - height = min(0.5 * nVars, 8) + height = min(0.5 * n_vars, 8) figure.set_size_inches(width, height, forward=True) # hide the axis @@ -50,7 +50,7 @@ def volume(x,y,z): # CREATE THE CALLBACK FUNCTIONS def on_changed(event): - res = f(*(s.val for s in sliders)) + res = func(*(s.val for s in sliders)) if res is not None: print(res) diff --git a/moviepy/video/tools/credits.py b/moviepy/video/tools/credits.py index 51e6fe376..dc2f6c4df 100644 --- a/moviepy/video/tools/credits.py +++ b/moviepy/video/tools/credits.py @@ -9,131 +9,136 @@ from moviepy.video.VideoClip import ImageClip, TextClip -@convert_path_to_string("creditfile") -def credits1( - creditfile, - width, - stretch=30, - color="white", - stroke_color="black", - stroke_width=2, - font="Impact-Normal", - fontsize=60, - gap=0, -): - """ - - Parameters - ----------- - - creditfile - A string or path like object pointing to a text file - whose content must be as follows: :: - - # This is a comment - # The next line says : leave 4 blank lines - .blank 4 - - ..Executive Story Editor - MARCEL DURAND - - ..Associate Producers - MARTIN MARCEL - DIDIER MARTIN - - ..Music Supervisor - JEAN DIDIER - - width - Total width of the credits text in pixels - - gap - Horizontal gap in pixels between the jobs and the names - - color - Color of the text. See ``TextClip.list('color')`` - for a list of acceptable names. - - font - Name of the font to use. See ``TextClip.list('font')`` for - the list of fonts you can use on your computer. - - fontsize - Size of font to use - - stroke_color - Color of the stroke (=contour line) of the text. If ``None``, - there will be no stroke. - - stroke_width - Width of the stroke, in pixels. Can be a float, like 1.5. - - - Returns - --------- - - image - An ImageClip instance that looks like this and can be scrolled - to make some credits: - - Executive Story Editor MARCEL DURAND - Associate Producers MARTIN MARCEL - DIDIER MARTIN - Music Supervisor JEAN DIDIER - - """ - - # PARSE THE TXT FILE - texts = [] - oneline = True - - with open(creditfile) as f: - for l in f: - if l.startswith(("\n", "#")): - # exclude blank lines or comments - continue - elif l.startswith(".blank"): - # ..blank n - for i in range(int(l.split(" ")[1])): - texts.append(["\n", "\n"]) - elif l.startswith(".."): - texts.append([l[2:], ""]) - oneline = True - elif oneline: - texts.append(["", l]) - oneline = False - else: - texts.append(["\n", l]) - - left, right = ("".join(l) for l in zip(*texts)) - - # MAKE TWO COLUMNS FOR THE CREDITS - left, right = [ - TextClip( - txt, - color=color, - stroke_color=stroke_color, - stroke_width=stroke_width, - font=font, - fontsize=fontsize, - align=al, - ) - for txt, al in [(left, "East"), (right, "West")] - ] - - cc = CompositeVideoClip( - [left, right.set_position((left.w + gap, 0))], - size=(left.w + right.w + gap, right.h), +class CreditsClip(TextClip): + @convert_path_to_string("creditfile") + def __init__( + self, + creditfile, + width, + stretch=30, + color="white", + stroke_color="black", + stroke_width=2, + font="Impact-Normal", + font_size=60, bg_color=None, - ) - - # SCALE TO THE REQUIRED SIZE - - scaled = resize(cc, width=width) + gap=0, + ): + """ + + Parameters + ----------- + + creditfile + A string or path like object pointing to a text file + whose content must be as follows: :: + + # This is a comment + # The next line says : leave 4 blank lines + .blank 4 + + ..Executive Story Editor + MARCEL DURAND + + ..Associate Producers + MARTIN MARCEL + DIDIER MARTIN + + ..Music Supervisor + JEAN DIDIER + + width + Total width of the credits text in pixels + + gap + Horizontal gap in pixels between the jobs and the names + + color + Color of the text. See ``TextClip.list('color')`` + for a list of acceptable names. + + font + Name of the font to use. See ``TextClip.list('font')`` for + the list of fonts you can use on your computer. + + font_size + Size of font to use + + stroke_color + Color of the stroke (=contour line) of the text. If ``None``, + there will be no stroke. + + stroke_width + Width of the stroke, in pixels. Can be a float, like 1.5. + + bg_color + Color of the background. If ``None``, the background will + be transparent + + + Returns + --------- + + image + An ImageClip instance that looks like this and can be scrolled + to make some credits: + + Executive Story Editor MARCEL DURAND + Associate Producers MARTIN MARCEL + DIDIER MARTIN + Music Supervisor JEAN DIDIER + + """ + + # Parse the .txt file + texts = [] + one_line = True + + with open(creditfile) as file: + for line in file: + if line.startswith(("\n", "#")): + # exclude blank lines or comments + continue + elif line.startswith(".blank"): + # ..blank n + for i in range(int(line.split(" ")[1])): + texts.append(["\n", "\n"]) + elif line.startswith(".."): + texts.append([line[2:], ""]) + one_line = True + elif one_line: + texts.append(["", line]) + one_line = False + else: + texts.append(["\n", line]) + + left, right = ("".join(line) for line in zip(*texts)) + + # Make two columns for the credits + left, right = [ + TextClip( + txt, + color=color, + stroke_color=stroke_color, + stroke_width=stroke_width, + font=font, + font_size=font_size, + align=align, + ) + for txt, align in [(left, "East"), (right, "West")] + ] + + both_columns = CompositeVideoClip( + [left, right.with_position((left.w + gap, 0))], + size=(left.w + right.w + gap, right.h), + bg_color=bg_color, + ) - # TRANSFORM THE WHOLE CREDIT CLIP INTO AN ImageCLip + # Scale to the required size + scaled = resize(both_columns, width=width) - imclip = ImageClip(scaled.get_frame(0)) - amask = ImageClip(scaled.mask.get_frame(0), ismask=True) + # Transform the CompositeVideoClip into an ImageClip - return imclip.set_mask(amask) + # Calls ImageClip.__init__() + super(TextClip, self).__init__(scaled.get_frame(0)) + self.mask = ImageClip(scaled.mask.get_frame(0), is_mask=True) diff --git a/moviepy/video/tools/cuts.py b/moviepy/video/tools/cuts.py index c19602ce2..d9796bd8b 100644 --- a/moviepy/video/tools/cuts.py +++ b/moviepy/video/tools/cuts.py @@ -9,16 +9,16 @@ @use_clip_fps_by_default -def find_video_period(clip, fps=None, tmin=0.3): +def find_video_period(clip, fps=None, start_time=0.3): """ Finds the period of a video based on frames correlation """ def frame(t): return clip.get_frame(t).flatten() - tt = np.arange(tmin, clip.duration, 1.0 / fps)[1:] + timings = np.arange(start_time, clip.duration, 1.0 / fps)[1:] ref = frame(0) - corrs = [np.corrcoef(ref, frame(t))[0, 1] for t in tt] - return tt[np.argmax(corrs)] + corrs = [np.corrcoef(ref, frame(t))[0, 1] for t in timings] + return timings[np.argmax(corrs)] class FramesMatch: @@ -27,62 +27,63 @@ class FramesMatch: Parameters ----------- - t1 + start_time Starting time - t2 + end_time End time - d_min + min_distance Lower bound on the distance between the first and last frames - d_max + max_distance Upper bound on the distance between the first and last frames """ - def __init__(self, t1, t2, d_min, d_max): - self.t1 = t1 - self.t2 = t2 - self.d_min = d_min - self.d_max = d_max - self.time_span = t2 - t1 + def __init__(self, start_time, end_time, min_distance, max_distance): + self.start_time = start_time + self.end_time = end_time + self.min_distance = min_distance + self.max_distance = max_distance + self.time_span = end_time - start_time def __str__(self): return "(%.04f, %.04f, %.04f, %.04f)" % ( - self.t1, - self.t2, - self.d_min, - self.d_max, + self.start_time, + self.end_time, + self.min_distance, + self.max_distance, ) def __repr__(self): return "(%.04f, %.04f, %.04f, %.04f)" % ( - self.t1, - self.t2, - self.d_min, - self.d_max, + self.start_time, + self.end_time, + self.min_distance, + self.max_distance, ) def __iter__(self): - return iter((self.t1, self.t2, self.d_min, self.d_max)) + return iter( + (self.start_time, self.end_time, self.min_distance, self.max_distance) + ) class FramesMatches(list): def __init__(self, lst): - - list.__init__(self, sorted(lst, key=lambda e: e.d_max)) + list.__init__(self, sorted(lst, key=lambda e: e.max_distance)) def best(self, n=1, percent=None): if percent is not None: n = len(self) * percent / 100 return self[0] if n == 1 else FramesMatches(self[:n]) - def filter(self, cond): + def filter(self, condition): """ Returns a FramesMatches object obtained by filtering out the FramesMatch - which do not satistify the condition ``cond``. ``cond`` is a function + which do not satistify the condition ``condition``. ``condition`` is a function (FrameMatch -> bool). Examples @@ -90,7 +91,7 @@ def filter(self, cond): >>> # Only keep the matches corresponding to (> 1 second) sequences. >>> new_matches = matches.filter( lambda match: match.time_span > 1) """ - return FramesMatches(filter(cond, self)) + return FramesMatches(filter(condition, self)) def save(self, filename): np.savetxt( @@ -110,12 +111,12 @@ def load(filename): return FramesMatches(mfs) @staticmethod - def from_clip(clip, dist_thr, max_d, fps=None): + def from_clip(clip, distance_threshold, max_duration, fps=None): """Finds all the frames tht look alike in a clip, for instance to make a looping gif. This teturns a FramesMatches object of the all pairs of frames with - (t2-t1 < max_d) and whose distance is under dist_thr. + (end_time-start_time < max_duration) and whose distance is under distance_threshold. This is well optimized routine and quite fast. @@ -126,11 +127,11 @@ def from_clip(clip, dist_thr, max_d, fps=None): a duration of 1.5s or more into a GIF: >>> from moviepy.editor import VideoFileClip - >>> from moviepy.video.tools.cuts import find_matching_frames + >>> from moviepy.video.tools.cuts import FramesMatches >>> clip = VideoFileClip("foo.mp4").resize(width=200) - >>> matches = find_matching_frames(clip, 10, 3) # will take time + >>> matches = FramesMatches.from_clip(clip, distance_threshold=10, max_duration=3) # will take time >>> best = matches.filter(lambda m: m.time_span > 1.5).best() - >>> clip.subclip(best.t1, best.t2).write_gif("foo.gif") + >>> clip.subclip(best.start_time, best.end_time).write_gif("foo.gif") Parameters ----------- @@ -138,10 +139,10 @@ def from_clip(clip, dist_thr, max_d, fps=None): clip A MoviePy video clip, possibly transformed/resized - dist_thr + distance_threshold Distance above which a match is rejected - max_d + max_duration Maximal duration (in seconds) between two matching frames fps @@ -154,11 +155,11 @@ def from_clip(clip, dist_thr, max_d, fps=None): def dot_product(F1, F2): return (F1 * F2).sum() / N_pixels - F = {} # will store the frames and their mutual distances + frame_dict = {} # will store the frames and their mutual distances def distance(t1, t2): - uv = dot_product(F[t1]["frame"], F[t2]["frame"]) - u, v = F[t1]["|F|sq"], F[t2]["|F|sq"] + uv = dot_product(frame_dict[t1]["frame"], frame_dict[t2]["frame"]) + u, v = frame_dict[t1]["|F|sq"], frame_dict[t2]["|F|sq"] return np.sqrt(u + v - 2 * uv) matching_frames = [] # the final result. @@ -169,75 +170,77 @@ def distance(t1, t2): F_norm_sq = dot_product(flat_frame, flat_frame) F_norm = np.sqrt(F_norm_sq) - for t2 in list(F.keys()): + for t2 in list(frame_dict.keys()): # forget old frames, add 't' to the others frames # check for early rejections based on differing norms - if (t - t2) > max_d: - F.pop(t2) + if (t - t2) > max_duration: + frame_dict.pop(t2) else: - F[t2][t] = { - "min": abs(F[t2]["|F|"] - F_norm), - "max": F[t2]["|F|"] + F_norm, + frame_dict[t2][t] = { + "min": abs(frame_dict[t2]["|F|"] - F_norm), + "max": frame_dict[t2]["|F|"] + F_norm, } - F[t2][t]["rejected"] = F[t2][t]["min"] > dist_thr + frame_dict[t2][t]["rejected"] = ( + frame_dict[t2][t]["min"] > distance_threshold + ) - t_F = sorted(F.keys()) + t_F = sorted(frame_dict.keys()) - F[t] = {"frame": flat_frame, "|F|sq": F_norm_sq, "|F|": F_norm} + frame_dict[t] = {"frame": flat_frame, "|F|sq": F_norm_sq, "|F|": F_norm} for i, t2 in enumerate(t_F): # Compare F(t) to all the previous frames - if F[t2][t]["rejected"]: + if frame_dict[t2][t]["rejected"]: continue dist = distance(t, t2) - F[t2][t]["min"] = F[t2][t]["max"] = dist - F[t2][t]["rejected"] = dist >= dist_thr + frame_dict[t2][t]["min"] = frame_dict[t2][t]["max"] = dist + frame_dict[t2][t]["rejected"] = dist >= distance_threshold for t3 in t_F[i + 1 :]: - # For all the next times t3, use d(F(t), F(t2)) to + # For all the next times t3, use d(F(t), F(end_time)) to # update the bounds on d(F(t), F(t3)). See if you can # conclude on wether F(t) and F(t3) match. - t3t, t2t3 = F[t3][t], F[t2][t3] + t3t, t2t3 = frame_dict[t3][t], frame_dict[t2][t3] t3t["max"] = min(t3t["max"], dist + t2t3["max"]) t3t["min"] = max(t3t["min"], dist - t2t3["max"], t2t3["min"] - dist) - if t3t["min"] > dist_thr: + if t3t["min"] > distance_threshold: t3t["rejected"] = True - # Store all the good matches (t2,t) + # Store all the good matches (end_time,t) matching_frames += [ - (t1, t, F[t1][t]["min"], F[t1][t]["max"]) - for t1 in F - if (t1 != t) and not F[t1][t]["rejected"] + (t1, t, frame_dict[t1][t]["min"], frame_dict[t1][t]["max"]) + for t1 in frame_dict + if (t1 != t) and not frame_dict[t1][t]["rejected"] ] return FramesMatches([FramesMatch(*e) for e in matching_frames]) def select_scenes( - self, match_thr, min_time_span, nomatch_thr=None, time_distance=0 + self, match_threshold, min_time_span, nomatch_threshold=None, time_distance=0 ): """ - match_thr + match_threshold The smaller, the better-looping the gifs are. min_time_span Only GIFs with a duration longer than min_time_span (in seconds) will be extracted. - nomatch_thr - If None, then it is chosen equal to match_thr + nomatch_threshold + If None, then it is chosen equal to match_threshold """ - if nomatch_thr is None: - nomatch_thr = match_thr + if nomatch_threshold is None: + nomatch_threshold = match_threshold dict_starts = defaultdict(lambda: []) - for (start, end, d_min, d_max) in self: - dict_starts[start].append([end, d_min, d_max]) + for (start, end, min_distance, max_distance) in self: + dict_starts[start].append([end, min_distance, max_distance]) starts_ends = sorted(dict_starts.items(), key=lambda k: k[0]) @@ -248,16 +251,16 @@ def select_scenes( if start < min_start: continue - ends = [end for (end, d_min, d_max) in ends_distances] + ends = [end for (end, min_distance, max_distance) in ends_distances] great_matches = [ - (end, d_min, d_max) - for (end, d_min, d_max) in ends_distances - if d_max < match_thr + (end, min_distance, max_distance) + for (end, min_distance, max_distance) in ends_distances + if max_distance < match_threshold ] great_long_matches = [ - (end, d_min, d_max) - for (end, d_min, d_max) in great_matches + (end, min_distance, max_distance) + for (end, min_distance, max_distance) in great_matches if (end - start) > min_time_span ] @@ -265,17 +268,21 @@ def select_scenes( continue # No GIF can be made starting at this time poor_matches = { - end for (end, d_min, d_max) in ends_distances if d_min > nomatch_thr + end + for (end, min_distance, max_distance) in ends_distances + if min_distance > nomatch_threshold } short_matches = {end for end in ends if (end - start) <= 0.6} if not poor_matches.intersection(short_matches): continue - end = max(end for (end, d_min, d_max) in great_long_matches) - end, d_min, d_max = next(e for e in great_long_matches if e[0] == end) + end = max(end for (end, min_distance, max_distance) in great_long_matches) + end, min_distance, max_distance = next( + e for e in great_long_matches if e[0] == end + ) - result.append(FramesMatch(start, end, d_min, d_max)) + result.append(FramesMatch(start, end, min_distance, max_distance)) min_start = start + time_distance return FramesMatches(result) @@ -287,7 +294,9 @@ def write_gifs(self, clip, gif_dir): @use_clip_fps_by_default -def detect_scenes(clip=None, luminosities=None, thr=10, logger="bar", fps=None): +def detect_scenes( + clip=None, luminosities=None, luminosity_threshold=10, logger="bar", fps=None +): """Detects scenes of a clip based on luminosity changes. Note that for large clip this may take some time @@ -312,15 +321,15 @@ def detect_scenes(clip=None, luminosities=None, thr=10, logger="bar", fps=None): A list of luminosities, e.g. returned by detect_scenes in a previous run. - thr + luminosity_threshold Determines a threshold above which the 'luminosity jumps' will be considered as scene changes. A scene change is defined as a change between 2 consecutive frames that is larger than (avg * thr) where avg is the average of the absolute changes between consecutive frames. - progress_bar - We all love progress bars ! Here is one for you, in option. + logger + Either "bar" for progress bar or None or any Proglog logger. fps Must be provided if you provide no clip or a clip without @@ -338,10 +347,11 @@ def detect_scenes(clip=None, luminosities=None, thr=10, logger="bar", fps=None): end = clip.duration else: end = len(luminosities) * (1.0 / fps) - lum_diffs = abs(np.diff(luminosities)) - avg = lum_diffs.mean() - luminosity_jumps = 1 + np.array(np.nonzero(lum_diffs > thr * avg))[0] - tt = [0] + list((1.0 / fps) * luminosity_jumps) + [end] - # print tt - cuts = [(t1, t2) for t1, t2 in zip(tt, tt[1:])] + luminosity_diffs = abs(np.diff(luminosities)) + avg = luminosity_diffs.mean() + luminosity_jumps = ( + 1 + np.array(np.nonzero(luminosity_diffs > luminosity_threshold * avg))[0] + ) + timings = [0] + list((1.0 / fps) * luminosity_jumps) + [end] + cuts = [(t1, t2) for t1, t2 in zip(timings, timings[1:])] return cuts, luminosities diff --git a/moviepy/video/tools/drawing.py b/moviepy/video/tools/drawing.py index fdf51bba5..18ef7fa4c 100644 --- a/moviepy/video/tools/drawing.py +++ b/moviepy/video/tools/drawing.py @@ -6,12 +6,11 @@ import numpy as np -def blit(im1, im2, pos=None, mask=None, ismask=False): +def blit(im1, im2, pos=None, mask=None, is_mask=False): """Blit an image over another. - Blits ``im1`` on ``im2`` as position ``pos=(x,y)``, using the ``mask`` if provided. If ``im1`` and ``im2`` are mask pictures - (2D float arrays) then ``ismask`` must be ``True``. + (2D float arrays) then ``is_mask`` must be ``True``. """ if pos is None: pos = [0, 0] @@ -46,16 +45,24 @@ def blit(im1, im2, pos=None, mask=None, ismask=False): blit_region = new_im2[yp1:yp2, xp1:xp2] new_im2[yp1:yp2, xp1:xp2] = 1.0 * mask * blitted + (1.0 - mask) * blit_region - return new_im2.astype("uint8") if (not ismask) else new_im2 + return new_im2.astype("uint8") if (not is_mask) else new_im2 def color_gradient( - size, p1, p2=None, vector=None, r=None, col1=0, col2=1.0, shape="linear", offset=0 + size, + p1, + p2=None, + vector=None, + radius=None, + color_1=0.0, + color_2=1.0, + shape="linear", + offset=0, ): """Draw a linear, bilinear, or radial gradient. The result is a picture of size ``size``, whose color varies - gradually from color `col1` in position ``p1`` to color ``col2`` + gradually from color `color_1` in position ``p1`` to color ``color_2`` in position ``p2``. If it is a RGB picture the result must be transformed into @@ -69,16 +76,16 @@ def color_gradient( Size (width, height) in pixels of the final picture/array. p1, p2 - Coordinates (x,y) in pixels of the limit point for ``col1`` - and ``col2``. The color 'before' ``p1`` is ``col1`` and it - gradually changes in the direction of ``p2`` until it is ``col2`` + Coordinates (x,y) in pixels of the limit point for ``color_1`` + and ``color_2``. The color 'before' ``p1`` is ``color_1`` and it + gradually changes in the direction of ``p2`` until it is ``color_2`` when it reaches ``p2``. vector A vector [x,y] in pixels that can be provided instead of ``p2``. ``p2`` is then defined as (p1 + vector). - col1, col2 + color_1, color_2 Either floats between 0 and 1 (for gradients used in masks) or [R,G,B] arrays (for colored gradients). @@ -86,16 +93,16 @@ def color_gradient( 'linear', 'bilinear', or 'circular'. In a linear gradient the color varies in one direction, from point ``p1`` to point ``p2``. - In a bilinear gradient it also varies symetrically form ``p1`` + In a bilinear gradient it also varies symetrically from ``p1`` in the other direction. - In a circular gradient it goes from ``col1`` to ``col2`` in all + In a circular gradient it goes from ``color_1`` to ``color_2`` in all directions. offset Real number between 0 and 1 indicating the fraction of the vector at which the gradient actually starts. For instance if ``offset`` is 0.9 in a gradient going from p1 to p2, then the gradient will - only occur near p2 (before that everything is of color ``col1``) + only occur near p2 (before that everything is of color ``color_1``) If the offset is 0.9 in a radial gradient, the gradient will occur in the region located between 90% and 100% of the radius, this creates a blurry disc of radius d(p1,p2). @@ -118,8 +125,8 @@ def color_gradient( # np-arrayize and change x,y coordinates to y,x w, h = size - col1 = np.array(col1).astype(float) - col2 = np.array(col2).astype(float) + color_1 = np.array(color_1).astype(float) + color_2 = np.array(color_2).astype(float) if shape == "bilinear": if vector is None: @@ -127,15 +134,21 @@ def color_gradient( m1, m2 = [ color_gradient( - size, p1, vector=v, col1=1.0, col2=0, shape="linear", offset=offset + size, + p1, + vector=v, + color_1=1.0, + color_2=0.0, + shape="linear", + offset=offset, ) for v in [vector, -vector] ] arr = np.maximum(m1, m2) - if col1.size > 1: + if color_1.size > 1: arr = np.dstack(3 * [arr]) - return arr * col1 + (1 - arr) * col2 + return arr * color_1 + (1 - arr) * color_2 p1 = np.array(p1[::-1]).astype(float) @@ -158,33 +171,41 @@ def color_gradient( p1 = p1 + offset * vector arr = (M - p1).dot(n_vec) / (1 - offset) arr = np.minimum(1, np.maximum(0, arr)) - if col1.size > 1: + if color_1.size > 1: arr = np.dstack(3 * [arr]) - return arr * col1 + (1 - arr) * col2 + return arr * color_1 + (1 - arr) * color_2 elif shape == "radial": - if r is None: - r = norm + if radius is None: + radius = norm - if r == 0: + if radius == 0: arr = np.ones((h, w)) else: - arr = (np.sqrt(((M - p1) ** 2).sum(axis=2))) - offset * r - arr = arr / ((1 - offset) * r) + arr = (np.sqrt(((M - p1) ** 2).sum(axis=2))) - offset * radius + arr = arr / ((1 - offset) * radius) arr = np.minimum(1.0, np.maximum(0, arr)) - if col1.size > 1: + if color_1.size > 1: arr = np.dstack(3 * [arr]) - return (1 - arr) * col1 + arr * col2 + return (1 - arr) * color_1 + arr * color_2 def color_split( - size, x=None, y=None, p1=None, p2=None, vector=None, col1=0, col2=1.0, grad_width=0 + size, + x=None, + y=None, + p1=None, + p2=None, + vector=None, + color_1=0, + color_2=1.0, + gradient_width=0, ): """Make an image splitted in 2 colored regions. Returns an array of size ``size`` divided in two regions called 1 and - 2 in wht follows, and which will have colors col& and col2 + 2 in what follows, and which will have colors color_1 and color_2 respectively. Parameters @@ -198,7 +219,7 @@ def color_split( If provided, the image is splitted vertically in y, the top region being region 1. - p1,p2: + p1, p2: Positions (x1,y1),(x2,y2) in pixels, where the numbers can be floats. Region 1 is defined as the whole region on the left when going from ``p1`` to ``p2``. @@ -219,15 +240,14 @@ def color_split( >>> size = [200,200] >>> # an image with all pixels with x<50 =0, the others =1 - >>> color_split(size, x=50, col1=0, col2=1) + >>> color_split(size, x=50, color_1=0, color_2=1) >>> # an image with all pixels with y<50 red, the others green - >>> color_split(size, x=50, col1=[255,0,0], col2=[0,255,0]) + >>> color_split(size, x=50, color_1=[255,0,0], color_2=[0,255,0]) >>> # An image splitted along an arbitrary line (see below) - >>> color_split(size, p1=[20,50], p2=[25,70] col1=0, col2=1) - + >>> color_split(size, p1=[20,50], p2=[25,70] color_1=0, color_2=1) """ - if grad_width or ((x is None) and (y is None)): + if gradient_width or ((x is None) and (y is None)): if p2 is not None: vector = np.array(p2) - np.array(p1) elif x is not None: @@ -240,20 +260,20 @@ def color_split( x, y = vector vector = np.array([y, -x]).astype("float") norm = np.linalg.norm(vector) - vector = max(0.1, grad_width) * vector / norm + vector = max(0.1, gradient_width) * vector / norm return color_gradient( - size, p1, vector=vector, col1=col1, col2=col2, shape="linear" + size, p1, vector=vector, color_1=color_1, color_2=color_2, shape="linear" ) else: w, h = size - shape = (h, w) if np.isscalar(col1) else (h, w, len(col1)) + shape = (h, w) if np.isscalar(color_1) else (h, w, len(color_1)) arr = np.zeros(shape) if x: - arr[:, :x] = col1 - arr[:, x:] = col2 + arr[:, :x] = color_1 + arr[:, x:] = color_2 elif y: - arr[:y] = col1 - arr[y:] = col2 + arr[:y] = color_1 + arr[y:] = color_2 return arr # if we are here, it means we didn't exit with a proper 'return' @@ -261,10 +281,10 @@ def color_split( raise -def circle(screensize, center, radius, col1=1.0, col2=0, blur=1): +def circle(screensize, center, radius, color=1.0, bg_color=0, blur=1): """Draw an image with a circle. - Draws a circle of color ``col1``, on a background of color ``col2``, + Draws a circle of color ``color``, on a background of color ``bg_color``, on a screen of size ``screensize`` at the position ``center=(x,y)``, with a radius ``radius`` but slightly blurred on the border by ``blur`` pixels @@ -273,9 +293,9 @@ def circle(screensize, center, radius, col1=1.0, col2=0, blur=1): return color_gradient( screensize, p1=center, - r=radius, - col1=col1, - col2=col2, + radius=radius, + color_1=color, + color_2=bg_color, shape="radial", offset=offset, ) diff --git a/moviepy/video/tools/segmenting.py b/moviepy/video/tools/segmenting.py index e25bccbef..7b8256c35 100644 --- a/moviepy/video/tools/segmenting.py +++ b/moviepy/video/tools/segmenting.py @@ -4,12 +4,12 @@ from moviepy.video.VideoClip import ImageClip -def findObjects(clip, rem_thr=500, preview=False): +def find_objects(clip, size_threshold=500, preview=False): """ Returns a list of ImageClips representing each a separate object on the screen. - rem_thr : all objects found with size < rem_Thr will be + size_threshold : all objects found with size < size_threshold will be considered false positives and will be removed """ @@ -23,33 +23,33 @@ def findObjects(clip, rem_thr=500, preview=False): # find the objects slices = [] - for e in ndi.find_objects(labelled): - if mask[e[0], e[1]].mean() <= 0.2: + for obj in ndi.find_objects(labelled): + if mask[obj[0], obj[1]].mean() <= 0.2: # remove letter holes (in o,e,a, etc.) continue - if image[e[0], e[1]].size <= rem_thr: + if image[obj[0], obj[1]].size <= size_threshold: # remove very small slices continue - slices.append(e) - islices = sorted(enumerate(slices), key=lambda s: s[1][1].start) + slices.append(obj) + indexed_slices = sorted(enumerate(slices), key=lambda slice: slice[1][1].start) letters = [] - for i, (ind, (sy, sx)) in enumerate(islices): + for i, (sy, sx) in indexed_slices: """ crop each letter separately """ sy = slice(sy.start - 1, sy.stop + 1) sx = slice(sx.start - 1, sx.stop + 1) letter = image[sy, sx] labletter = labelled[sy, sx] - maskletter = (labletter == (ind + 1)) * mask[sy, sx] + maskletter = (labletter == (i + 1)) * mask[sy, sx] letter = ImageClip(image[sy, sx]) - letter.mask = ImageClip(maskletter, ismask=True) + letter.mask = ImageClip(maskletter, is_mask=True) letter.screenpos = np.array((sx.start, sy.start)) letters.append(letter) if preview: import matplotlib.pyplot as plt - print("found %d objects" % (num_features)) + print(f"Found {num_features} objects") fig, ax = plt.subplots(2) ax[0].axis("off") ax[0].imshow(labelled) diff --git a/moviepy/video/tools/subtitles.py b/moviepy/video/tools/subtitles.py index 73913af8e..a4d54031f 100644 --- a/moviepy/video/tools/subtitles.py +++ b/moviepy/video/tools/subtitles.py @@ -5,7 +5,7 @@ import numpy as np from moviepy.decorators import convert_path_to_string -from moviepy.tools import cvsecs +from moviepy.tools import convert_to_seconds from moviepy.video.VideoClip import TextClip, VideoClip @@ -31,7 +31,7 @@ class SubtitlesClip(VideoClip): >>> from moviepy.video.tools.subtitles import SubtitlesClip >>> from moviepy.video.io.VideoFileClip import VideoFileClip - >>> generator = lambda txt: TextClip(txt, font='Georgia-Regular', fontsize=24, color='white') + >>> generator = lambda text: TextClip(text, font='Georgia-Regular', font_size=24, color='white') >>> sub = SubtitlesClip("subtitles.srt", generator) >>> sub = SubtitlesClip("subtitles.srt", generator, encoding='utf-8') >>> myvideo = VideoFileClip("myvideo.avi") @@ -48,7 +48,7 @@ def __init__(self, subtitles, make_textclip=None, encoding=None): # `subtitles` is a string or path-like object subtitles = file_to_subtitles(subtitles, encoding=encoding) - # subtitles = [(map(cvsecs, tt),txt) for tt, txt in subtitles] + # subtitles = [(map(convert_to_seconds, times),text) for times, text in subtitles] self.subtitles = subtitles self.textclips = dict() @@ -58,7 +58,7 @@ def make_textclip(txt): return TextClip( txt, font="Georgia-Bold", - fontsize=24, + font_size=24, color="white", stroke_color="black", stroke_width=0.5, @@ -74,15 +74,15 @@ def add_textclip_if_none(t): to generate it yet. If there is no subtitle to show at t, return false.""" sub = [ - ((ta, tb), txt) - for ((ta, tb), txt) in self.textclips.keys() - if (ta <= t < tb) + ((text_start, text_end), text) + for ((text_start, text_end), text) in self.textclips.keys() + if (text_start <= t < text_end) ] if not sub: sub = [ - ((ta, tb), txt) - for ((ta, tb), txt) in self.subtitles - if (ta <= t < tb) + ((text_start, text_end), text) + for ((text_start, text_end), text) in self.subtitles + if (text_start <= t < text_end) ] if not sub: return False @@ -102,24 +102,24 @@ def make_mask_frame(t): self.make_frame = make_frame hasmask = bool(self.make_textclip("T").mask) - self.mask = VideoClip(make_mask_frame, ismask=True) if hasmask else None + self.mask = VideoClip(make_mask_frame, is_mask=True) if hasmask else None - def in_subclip(self, t_start=None, t_end=None): - """Returns a sequence of [(t1,t2), txt] covering all the given subclip - from t_start to t_end. The first and last times will be cropped so as - to be exactly t_start and t_end if possible.""" + def in_subclip(self, start_time=None, end_time=None): + """Returns a sequence of [(t1,t2), text] covering all the given subclip + from start_time to end_time. The first and last times will be cropped so as + to be exactly start_time and end_time if possible.""" def is_in_subclip(t1, t2): try: - return (t_start <= t1 < t_end) or (t_start < t2 <= t_end) + return (start_time <= t1 < end_time) or (start_time < t2 <= end_time) except Exception: return False def try_cropping(t1, t2): try: - return (max(t1, t_start), min(t2, t_end)) + return max(t1, start_time), min(t2, end_time) except Exception: - return (t1, t2) + return t1, t2 return [ (try_cropping(t1, t2), txt) @@ -135,29 +135,28 @@ def __getitem__(self, k): def __str__(self): def to_srt(sub_element): - (ta, tb), txt = sub_element - fta = cvsecs(ta) - ftb = cvsecs(tb) - return "%s - %s\n%s" % (fta, ftb, txt) + (start_time, end_time), text = sub_element + formatted_start_time = convert_to_seconds(start_time) + formatted_end_time = convert_to_seconds(end_time) + return "%s - %s\n%s" % (formatted_start_time, formatted_end_time, text) - return "\n\n".join(to_srt(s) for s in self.subtitles) + return "\n\n".join(to_srt(sub) for sub in self.subtitles) def match_expr(self, expr): - return SubtitlesClip( - [e for e in self.subtitles if re.findall(expr, e[1]) != []] + [sub for sub in self.subtitles if re.findall(expr, sub[1]) != []] ) def write_srt(self, filename): - with open(filename, "w+") as f: - f.write(str(self)) + with open(filename, "w+") as file: + file.write(str(self)) @convert_path_to_string("filename") def file_to_subtitles(filename, encoding=None): """Converts a srt file into subtitles. - The returned list is of the form ``[((ta,tb),'some text'),...]`` + The returned list is of the form ``[((start_time,end_time),'some text'),...]`` and can be fed to SubtitlesClip. Only works for '.srt' format for the moment. @@ -166,11 +165,11 @@ def file_to_subtitles(filename, encoding=None): times_texts = [] current_times = None current_text = "" - with open(filename, "r", encoding=encoding) as f: - for line in f: + with open(filename, "r", encoding=encoding) as file: + for line in file: times = re.findall("([0-9]*:[0-9]*:[0-9]*,[0-9]*)", line) if times: - current_times = [cvsecs(t) for t in times] + current_times = [convert_to_seconds(t) for t in times] elif line.strip() == "": times_texts.append((current_times, current_text.strip("\n"))) current_times, current_text = None, "" diff --git a/moviepy/video/tools/tracking.py b/moviepy/video/tools/tracking.py index eda908548..da3a82856 100644 --- a/moviepy/video/tools/tracking.py +++ b/moviepy/video/tools/tracking.py @@ -10,7 +10,7 @@ import numpy as np -from moviepy.decorators import convert_to_seconds, use_clip_fps_by_default +from moviepy.decorators import convert_parameter_to_seconds, use_clip_fps_by_default from ..io.preview import imdisplay from .interpolators import Trajectory @@ -30,9 +30,9 @@ # MANUAL TRACKING -@convert_to_seconds(["t1", "t2"]) +@convert_parameter_to_seconds(["t1", "t2"]) @use_clip_fps_by_default -def manual_tracking(clip, t1=None, t2=None, fps=None, nobjects=1, savefile=None): +def manual_tracking(clip, t1=None, t2=None, fps=None, n_objects=1, savefile=None): """ Allows manual tracking of an object(s) in the video clip between times `t1` and `t2`. This displays the clip frame by frame @@ -48,13 +48,13 @@ def manual_tracking(clip, t1=None, t2=None, fps=None, nobjects=1, savefile=None) t1,t2: times during which to track (defaults are start and - end of the clip). t1 and t2 can be expressed in seconds + end of the clip). start_time and t2 can be expressed in seconds like 15.35, in (min, sec), in (hour, min, sec), or as a string: '01:03:05.35'. fps: Number of frames per second to freeze on. If None, the clip's fps attribute is used instead. - nobjects: + n_objects: Number of objects to click on each frame. savefile: If provided, the result is saved to a file, which makes @@ -67,15 +67,14 @@ def manual_tracking(clip, t1=None, t2=None, fps=None, nobjects=1, savefile=None) >>> from moviepy.video.tools.tracking import manual_tracking >>> clip = VideoFileClip("myvideo.mp4") >>> # manually indicate 3 trajectories, save them to a file - >>> trajectories = manual_tracking(clip, t1=5, t2=7, fps=5, - nobjects=3, savefile="track.txt") + >>> trajectories = manual_tracking(clip, start_time=5, t2=7, fps=5, + nobjects=3, savefile="track.text") >>> # ... >>> # LATER, IN ANOTHER SCRIPT, RECOVER THESE TRAJECTORIES >>> from moviepy.video.tools.tracking import Trajectory - >>> traj1, traj2, traj3 = Trajectory.load_list('track.txt') + >>> traj1, traj2, traj3 = Trajectory.load_list('track.text') >>> # If ever you only have one object being tracked, recover it with - >>> traj, = Trajectory.load_list('track.txt') - + >>> traj, = Trajectory.load_list('track.text') """ import pygame as pg @@ -92,7 +91,7 @@ def manual_tracking(clip, t1=None, t2=None, fps=None, nobjects=1, savefile=None) def gatherClicks(t): imdisplay(clip.get_frame(t), screen) - objects_to_click = nobjects + objects_to_click = n_objects clicks = [] while objects_to_click: @@ -123,7 +122,7 @@ def gatherClicks(t): tt, xylist = zip(*txy_list) result = [] - for i in range(nobjects): + for i in range(n_objects): xys = [e[i] for e in xylist] xx, yy = zip(*xys) result.append(Trajectory(tt, xx, yy)) diff --git a/setup.py b/setup.py index abd6a1075..349f3b3cc 100644 --- a/setup.py +++ b/setup.py @@ -102,8 +102,8 @@ def run_tests(self): extra_reqs = {"optional": optional_reqs, "doc": doc_reqs, "test": test_reqs} # Load the README. -with open("README.rst", "r", "utf-8") as f: - readme = f.read() +with open("README.rst", "r", "utf-8") as file: + readme = file.read() setup( name="moviepy", diff --git a/tests/test_PR.py b/tests/test_PR.py index 972df4b17..d7c0603f5 100644 --- a/tests/test_PR.py +++ b/tests/test_PR.py @@ -29,17 +29,17 @@ def test_PR_306(): def test_PR_339(): # In caption mode. TextClip( - txt="foo", + text="foo", color="white", font=FONT, size=(640, 480), method="caption", align="center", - fontsize=25, + font_size=25, ).close() # In label mode. - TextClip(txt="foo", font=FONT, method="label").close() + TextClip(text="foo", font=FONT, method="label").close() def test_PR_373(): @@ -79,7 +79,7 @@ def test_PR_515(): def test_PR_528(): with ImageClip("media/vacation_2017.jpg") as clip: new_clip = scroll(clip, w=1000, x_speed=50) - new_clip = new_clip.set_duration(1) + new_clip = new_clip.with_duration(1) new_clip.fps = 24 new_clip.write_videofile(os.path.join(TMP_DIR, "pano.mp4")) @@ -93,8 +93,8 @@ def test_PR_610(): """ Test that the max fps of the video clips is used for the composite video clip """ - clip1 = ColorClip((640, 480), color=(255, 0, 0)).set_duration(1) - clip2 = ColorClip((640, 480), color=(0, 255, 0)).set_duration(1) + clip1 = ColorClip((640, 480), color=(255, 0, 0)).with_duration(1) + clip2 = ColorClip((640, 480), color=(0, 255, 0)).with_duration(1) clip1.fps = 24 clip2.fps = 25 composite = CompositeVideoClip([clip1, clip2]) @@ -135,7 +135,7 @@ def make_textclip(txt): return TextClip( txt, font=FONT, - fontsize=24, + font_size=24, color="white", stroke_color="black", stroke_width=0.5, diff --git a/tests/test_TextClip.py b/tests/test_TextClip.py index d1d9ac10b..d494c6d43 100644 --- a/tests/test_TextClip.py +++ b/tests/test_TextClip.py @@ -27,12 +27,12 @@ def test_search(): def test_duration(): clip = TextClip("hello world", size=(1280, 720), color="white", font=FONT) - clip = clip.set_duration(5) + clip = clip.with_duration(5) assert clip.duration == 5 clip.close() - clip2 = clip.fx(blink, d_on=1, d_off=1) - clip2 = clip2.set_duration(5) + clip2 = clip.fx(blink, duration_on=1, duration_off=1) + clip2 = clip2.with_duration(5) assert clip2.duration == 5 close_all_clips(locals()) @@ -40,18 +40,18 @@ def test_duration(): # Moved from tests.py. Maybe we can remove these? def test_if_textclip_crashes_in_caption_mode(): TextClip( - txt="foo", + text="foo", color="white", size=(640, 480), method="caption", align="center", - fontsize=25, + font_size=25, font=FONT, ).close() def test_if_textclip_crashes_in_label_mode(): - TextClip(txt="foo", method="label", font=FONT).close() + TextClip(text="foo", method="label", font=FONT).close() if __name__ == "__main__": diff --git a/tests/test_VideoClip.py b/tests/test_VideoClip.py index ae7a5dad6..cb77df088 100644 --- a/tests/test_VideoClip.py +++ b/tests/test_VideoClip.py @@ -103,10 +103,10 @@ def test_write_gif_ffmpeg(): close_all_clips(locals()) -def test_write_gif_ffmpeg_pix_fmt(): +def test_write_gif_ffmpeg_pixel_format(): clip = VideoFileClip("media/big_buck_bunny_432_433.webm").subclip(0.2, 0.4) location = os.path.join(TMP_DIR, "ffmpeg_gif.gif") - clip.write_gif(location, program="ffmpeg", pix_fmt="bgr24") + clip.write_gif(location, program="ffmpeg", pixel_format="bgr24") assert os.path.isfile(location) close_all_clips(locals()) @@ -119,10 +119,10 @@ def test_write_gif_ffmpeg_tmpfiles(): close_all_clips(locals()) -def test_write_gif_ffmpeg_tmpfiles_pix_fmt(): +def test_write_gif_ffmpeg_tmpfiles_pixel_format(): clip = VideoFileClip("media/big_buck_bunny_432_433.webm").subclip(0.2, 0.5) location = os.path.join(TMP_DIR, "ffmpeg_tmpfiles_gif.gif") - clip.write_gif(location, program="ffmpeg", tempfiles=True, pix_fmt="bgr24") + clip.write_gif(location, program="ffmpeg", tempfiles=True, pixel_format="bgr24") assert os.path.isfile(location) close_all_clips(locals()) @@ -144,10 +144,10 @@ def test_write_gif_ImageMagick_tmpfiles(): close_all_clips(locals()) -def test_write_gif_ImageMagick_tmpfiles_pix_fmt(): +def test_write_gif_ImageMagick_tmpfiles_pixel_format(): clip = VideoFileClip("media/big_buck_bunny_432_433.webm").subclip(0.2, 0.5) location = os.path.join(TMP_DIR, "imagemagick_tmpfiles_gif.gif") - clip.write_gif(location, program="ImageMagick", tempfiles=True, pix_fmt="SGI") + clip.write_gif(location, program="ImageMagick", tempfiles=True, pixel_format="SGI") assert os.path.isfile(location) close_all_clips(locals()) @@ -171,15 +171,15 @@ def test_oncolor(): assert os.path.isfile(location) # test constructor with default arguements - clip = ColorClip(size=(100, 60), ismask=True) - clip = ColorClip(size=(100, 60), ismask=False) + clip = ColorClip(size=(100, 60), is_mask=True) + clip = ColorClip(size=(100, 60), is_mask=False) # negative test with pytest.raises(Exception): - clip = ColorClip(size=(100, 60), color=(255, 0, 0), ismask=True) + clip = ColorClip(size=(100, 60), color=(255, 0, 0), is_mask=True) with pytest.raises(Exception): - clip = ColorClip(size=(100, 60), color=0.4, ismask=False) + clip = ColorClip(size=(100, 60), color=0.4, is_mask=False) close_all_clips(locals()) @@ -189,7 +189,7 @@ def test_setaudio(): make_frame_440 = lambda t: [sin(440 * 2 * pi * t)] audio = AudioClip(make_frame_440, duration=0.5) audio.fps = 44100 - clip = clip.set_audio(audio) + clip = clip.with_audio(audio) location = os.path.join(TMP_DIR, "setaudio.mp4") clip.write_videofile(location, fps=24) assert os.path.isfile(location) @@ -199,7 +199,7 @@ def test_setaudio(): def test_setaudio_with_audiofile(): clip = ColorClip(size=(100, 60), color=(255, 0, 0), duration=0.5) audio = AudioFileClip("media/crunching.mp3").subclip(0, 0.5) - clip = clip.set_audio(audio) + clip = clip.with_audio(audio) location = os.path.join(TMP_DIR, "setaudiofile.mp4") clip.write_videofile(location, fps=24) assert os.path.isfile(location) @@ -208,7 +208,7 @@ def test_setaudio_with_audiofile(): def test_setopacity(): clip = VideoFileClip("media/big_buck_bunny_432_433.webm").subclip(0.2, 0.6) - clip = clip.set_opacity(0.5) + clip = clip.with_opacity(0.5) clip = clip.on_color(size=(1000, 1000), color=(0, 0, 255), col_opacity=0.8) location = os.path.join(TMP_DIR, "setopacity.mp4") clip.write_videofile(location) @@ -216,9 +216,9 @@ def test_setopacity(): close_all_clips(locals()) -def test_set_layer(): - bottom_clip = BitmapClip([["ABC"], ["BCA"], ["CAB"]], fps=1).set_layer(1) - top_clip = BitmapClip([["DEF"], ["EFD"]], fps=1).set_layer(2) +def test_with_layer(): + bottom_clip = BitmapClip([["ABC"], ["BCA"], ["CAB"]], fps=1).with_layer(1) + top_clip = BitmapClip([["DEF"], ["EFD"]], fps=1).with_layer(2) composite_clip = CompositeVideoClip([bottom_clip, top_clip]) reversed_composite_clip = CompositeVideoClip([top_clip, bottom_clip]) @@ -267,7 +267,7 @@ def test_setfps_withoutchangeduration(): # to check which frames are being preserved clip_sums = [f.sum() for f in clip.iter_frames()] - clip2 = clip.set_fps(48) + clip2 = clip.with_fps(48) clip2_sums = [f.sum() for f in clip2.iter_frames()] assert clip2_sums[::2] == clip_sums assert clip2.duration == clip.duration @@ -280,7 +280,7 @@ def test_setfps_withchangeduration(): # to check which frames are being preserved clip_sums = [f.sum() for f in clip.iter_frames()] - clip2 = clip.set_fps(48, change_duration=True) + clip2 = clip.with_fps(48, change_duration=True) clip2_sums = [f.sum() for f in clip2.iter_frames()] assert clip2_sums == clip_sums assert clip2.duration == clip.duration / 2 diff --git a/tests/test_VideoFileClip.py b/tests/test_VideoFileClip.py index 1fc07e6b2..b17844fc4 100644 --- a/tests/test_VideoFileClip.py +++ b/tests/test_VideoFileClip.py @@ -19,7 +19,7 @@ def test_setup(): blue = ColorClip((256, 200), color=(0, 0, 255)) red.fps = green.fps = blue.fps = 10 - with clips_array([[red, green, blue]]).set_duration(5) as video: + with clips_array([[red, green, blue]]).with_duration(5) as video: video.write_videofile(os.path.join(TMP_DIR, "test.mp4")) assert os.path.exists(os.path.join(TMP_DIR, "test.mp4")) @@ -39,7 +39,7 @@ def test_ffmpeg_resizing(): for target_resolution in target_resolutions: video = VideoFileClip(video_file, target_resolution=target_resolution) frame = video.get_frame(0) - for (target, observed) in zip(target_resolution, frame.shape): + for (target, observed) in zip(target_resolution[::-1], frame.shape): if target is not None: assert target == observed video.close() @@ -51,7 +51,7 @@ def test_shallow_copy(): does not corrupt the original clip.""" video_file = "media/big_buck_bunny_0_30.webm" video = VideoFileClip(video_file) - video_copy = video.set_start(1) + video_copy = video.with_start(1) del video_copy # The clip object buffers 200000 frames, around 5 seconds ahead. # When recentering the buffer, if the new buffer is more than 1000000 frames, diff --git a/tests/test_compositing.py b/tests/test_compositing.py index 8099560de..68ba188f2 100644 --- a/tests/test_compositing.py +++ b/tests/test_compositing.py @@ -29,13 +29,13 @@ def test_clips_array_duration(): green = ColorClip((256, 200), color=(0, 255, 0)) blue = ColorClip((256, 200), color=(0, 0, 255)) - video = clips_array([[red, green, blue]]).set_duration(5) + video = clips_array([[red, green, blue]]).with_duration(5) with pytest.raises(AttributeError): # fps not set video.write_videofile(join(TMP_DIR, "test_clips_array.mp4")) # this one should work correctly red.fps = green.fps = blue.fps = 30 - video = clips_array([[red, green, blue]]).set_duration(5) + video = clips_array([[red, green, blue]]).with_duration(5) video.write_videofile(join(TMP_DIR, "test_clips_array.mp4")) close_all_clips(locals()) @@ -58,6 +58,6 @@ def test_concatenate_floating_point(): This test uses duration=1.12 to check that it still works when the clip duration is represented as being bigger than it actually is. Fixed in #1195. """ - clip = ColorClip([100, 50], color=[255, 128, 64], duration=1.12).set_fps(25.0) + clip = ColorClip([100, 50], color=[255, 128, 64], duration=1.12).with_fps(25.0) concat = concatenate_videoclips([clip]) concat.write_videofile("concat.mp4", preset="ultrafast") diff --git a/tests/test_fx.py b/tests/test_fx.py index 3ef1cd9e7..fdd97b457 100644 --- a/tests/test_fx.py +++ b/tests/test_fx.py @@ -188,7 +188,7 @@ def test_invert_colors(): def test_loop(): clip = get_test_video() - clip1 = loop(clip).set_duration(3) # infinite looping + clip1 = loop(clip).with_duration(3) # infinite looping clip1.write_videofile(os.path.join(TMP_DIR, "loop1.webm")) return # Still buggy. TODO fix @@ -226,7 +226,7 @@ def test_margin(): assert clip == clip1 # 1 pixel black margin - clip2 = margin(clip, mar=1) + clip2 = margin(clip, margin_size=1) target = BitmapClip( [ [ @@ -247,7 +247,7 @@ def test_margin(): assert target == clip2 # 1 pixel green margin - clip3 = margin(clip, mar=1, color=(0, 255, 0)) + clip3 = margin(clip, margin_size=1, color=(0, 255, 0)) target = BitmapClip( [ [ @@ -349,7 +349,7 @@ def test_rotate(angle_offset): def test_rotate_nonstandard_angles(): # Test rotate with color clip - clip = ColorClip([600, 400], [150, 250, 100]).set_duration(1).set_fps(5) + clip = ColorClip([600, 400], [150, 250, 100]).with_duration(1).with_fps(5) clip = rotate(clip, 20) clip.write_videofile(os.path.join(TMP_DIR, "color_rotate.webm")) diff --git a/tests/test_issues.py b/tests/test_issues.py index aa4ec31f8..58fa14b08 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -11,7 +11,7 @@ def test_issue_145(): - video = ColorClip((800, 600), color=(255, 0, 0)).set_duration(5) + video = ColorClip((800, 600), color=(255, 0, 0)).with_duration(5) with pytest.raises(Exception): concatenate_videoclips([video], method="composite") @@ -220,14 +220,14 @@ def size(t): avatar = VideoFileClip("media/big_buck_bunny_432_433.webm", has_mask=True) avatar.audio = None - maskclip = ImageClip("media/afterimage.png", ismask=True, transparent=True) - avatar.set_mask(maskclip) # must set maskclip here.. + maskclip = ImageClip("media/afterimage.png", is_mask=True, transparent=True) + avatar.with_mask(maskclip) # must set maskclip here.. concatenated = concatenate_videoclips([avatar] * 3) tt = VideoFileClip("media/big_buck_bunny_0_30.webm").subclip(0, 3) # TODO: Setting mask here does not work: - # .set_mask(maskclip).resize(size)]) - final = CompositeVideoClip([tt, concatenated.set_position(posi).resize(size)]) + # .with_mask(maskclip).resize(size)]) + final = CompositeVideoClip([tt, concatenated.with_position(posi).resize(size)]) final.duration = tt.duration final.write_videofile(os.path.join(TMP_DIR, "issue_334.mp4"), fps=10) @@ -242,7 +242,7 @@ def test_issue_354(): # caption = editor.TextClip("test text", font="Liberation-Sans-Bold", # color='white', stroke_color='gray', # stroke_width=2, method='caption', - # size=(1280, 720), fontsize=60, + # size=(1280, 720), font_size=60, # align='South-East') # caption.duration = clip.duration @@ -251,7 +251,7 @@ def test_issue_354(): def test_issue_359(): - with ColorClip((800, 600), color=(255, 0, 0)).set_duration(5) as video: + with ColorClip((800, 600), color=(255, 0, 0)).with_duration(5) as video: video.fps = 30 video.write_gif(filename=os.path.join(TMP_DIR, "issue_359.gif"), tempfiles=True) @@ -277,7 +277,7 @@ def test_issue_359(): # def make_frame(t): # ax.clear() # ax.axis('off') -# ax.set_title("SVC classification", fontsize=16) +# ax.set_title("SVC classification", font_size=16) # # classifier = svm.SVC(gamma=2, C=1) # # the varying weights make the points appear one after the other @@ -296,7 +296,7 @@ def test_issue_359(): def test_issue_407(): - red = ColorClip((800, 600), color=(255, 0, 0)).set_duration(5) + red = ColorClip((800, 600), color=(255, 0, 0)).with_duration(5) red.fps = 30 assert red.fps == 30 @@ -305,8 +305,8 @@ def test_issue_407(): assert red.size == (800, 600) # ColorClip has no fps attribute. - green = ColorClip((640, 480), color=(0, 255, 0)).set_duration(2) - blue = ColorClip((640, 480), color=(0, 0, 255)).set_duration(2) + green = ColorClip((640, 480), color=(0, 255, 0)).with_duration(2) + blue = ColorClip((640, 480), color=(0, 0, 255)).with_duration(2) assert green.w == blue.w == 640 assert green.h == blue.h == 480 @@ -324,7 +324,7 @@ def test_issue_407(): def test_issue_416(): # ColorClip has no fps attribute. - green = ColorClip((640, 480), color=(0, 255, 0)).set_duration(2) + green = ColorClip((640, 480), color=(0, 255, 0)).with_duration(2) video1 = concatenate_videoclips([green]) assert video1.fps is None @@ -332,9 +332,9 @@ def test_issue_416(): def test_issue_417(): # failed in python2 cad = "media/python_logo.png" - myclip = ImageClip(cad).fx(resize, newsize=[1280, 660]) + myclip = ImageClip(cad).fx(resize, new_size=[1280, 660]) CompositeVideoClip([myclip], size=(1280, 720)) - # final.set_duration(7).write_videofile("test.mp4", fps=30) + # final.with_duration(7).write_videofile("test.mp4", fps=30) def test_issue_467(): @@ -342,14 +342,14 @@ def test_issue_467(): clip = ImageClip(cad) # caused an error, NameError: global name 'copy' is not defined - clip = clip.fx(blink, d_on=1, d_off=1) + clip = clip.fx(blink, duration_on=1, duration_off=1) def test_issue_470(): audio_clip = AudioFileClip("media/crunching.mp3") - # t_end is out of bounds - subclip = audio_clip.subclip(t_start=6, t_end=9) + # end_time is out of bounds + subclip = audio_clip.subclip(start_time=6, end_time=9) with pytest.raises(IOError): subclip.write_audiofile( @@ -357,7 +357,7 @@ def test_issue_470(): ) # but this one should work.. - subclip = audio_clip.subclip(t_start=6, t_end=8) + subclip = audio_clip.subclip(start_time=6, end_time=8) subclip.write_audiofile(os.path.join(TMP_DIR, "issue_470.wav"), write_logfile=True) @@ -371,9 +371,9 @@ def test_audio_reader(): def test_issue_547(): - red = ColorClip((640, 480), color=(255, 0, 0)).set_duration(1) - green = ColorClip((640, 480), color=(0, 255, 0)).set_duration(2) - blue = ColorClip((640, 480), color=(0, 0, 255)).set_duration(3) + red = ColorClip((640, 480), color=(255, 0, 0)).with_duration(1) + green = ColorClip((640, 480), color=(0, 255, 0)).with_duration(2) + blue = ColorClip((640, 480), color=(0, 0, 255)).with_duration(3) video = concatenate_videoclips([red, green, blue], method="compose") assert video.duration == 6 diff --git a/tests/test_misc.py b/tests/test_misc.py index 0210c1398..2b81e1cc0 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -20,9 +20,9 @@ def test_cuts1(): def test_subtitles(): - red = ColorClip((800, 600), color=(255, 0, 0)).set_duration(10) - green = ColorClip((800, 600), color=(0, 255, 0)).set_duration(10) - blue = ColorClip((800, 600), color=(0, 0, 255)).set_duration(10) + red = ColorClip((800, 600), color=(255, 0, 0)).with_duration(10) + green = ColorClip((800, 600), color=(0, 255, 0)).with_duration(10) + blue = ColorClip((800, 600), color=(0, 0, 255)).with_duration(10) myvideo = concatenate_videoclips([red, green, blue]) assert myvideo.duration == 30 @@ -30,7 +30,7 @@ def test_subtitles(): txt, font=FONT, size=(800, 600), - fontsize=24, + font_size=24, method="caption", align="South", color="white", diff --git a/tests/test_resourcerelease.py b/tests/test_resourcerelease.py index b2f78a8d1..f70c087df 100644 --- a/tests/test_resourcerelease.py +++ b/tests/test_resourcerelease.py @@ -33,7 +33,7 @@ def test_release_of_file_via_close(): TMP_DIR, "test_release_of_file_via_close_%s.mp4" % int(time.time()) ) - clip = clips_array([[red, green, blue]]).set_duration(0.5) + clip = clips_array([[red, green, blue]]).with_duration(0.5) clip.write_videofile(local_video_filename) # Open it up with VideoFileClip. diff --git a/tests/test_resourcereleasedemo.py b/tests/test_resourcereleasedemo.py index d991c36d1..c61288c55 100644 --- a/tests/test_resourcereleasedemo.py +++ b/tests/test_resourcereleasedemo.py @@ -45,7 +45,7 @@ def test_failure_to_release_file(): blue = ColorClip((256, 200), color=(0, 0, 255)) red.fps = green.fps = blue.fps = 30 - video = clips_array([[red, green, blue]]).set_duration(1) + video = clips_array([[red, green, blue]]).with_duration(1) try: video.write_videofile(local_video_filename) diff --git a/tests/test_tools.py b/tests/test_tools.py index b620d3b24..2e9b0187c 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -38,16 +38,8 @@ def test_find_extensions_not_found(): ], ) def test_cvsecs(given, expected): - """Test the cvsecs funtion outputs correct times as per the docstring.""" - assert tools.cvsecs(given) == expected - - -def test_sys_write_flush(): - """Test for sys_write-flush function. Check that stdout has no content after flushing.""" - tools.sys_write_flush("hello world") - - file = sys.stdout.read() - assert file == b"" + """Test the convert_to_seconds funtion outputs correct times as per the docstring.""" + assert tools.convert_to_seconds(given) == expected if __name__ == "__main__": diff --git a/tests/test_videotools.py b/tests/test_videotools.py index 0f55d3e34..80e8a1b27 100644 --- a/tests/test_videotools.py +++ b/tests/test_videotools.py @@ -3,7 +3,7 @@ import os from moviepy.video.compositing.concatenate import concatenate_videoclips -from moviepy.video.tools.credits import credits1 +from moviepy.video.tools.credits import CreditsClip from moviepy.video.tools.cuts import detect_scenes from moviepy.video.VideoClip import ColorClip @@ -34,11 +34,12 @@ def test_credits(): with open(file_location, "w") as file: file.write(credit_file) - image = credits1( + image = CreditsClip( file_location, 600, gap=100, stroke_color="blue", stroke_width=5, font=FONT ) - image = image.set_duration(3) + image = image.with_duration(3) image.write_videofile(vid_location, fps=24) + assert image.mask assert os.path.isfile(vid_location) @@ -46,10 +47,14 @@ def test_detect_scenes(): """ Test that a cut is detected between concatenated red and green clips """ - red = ColorClip((640, 480), color=(255, 0, 0)).set_duration(1) - green = ColorClip((640, 480), color=(0, 200, 0)).set_duration(1) + red = ColorClip((640, 480), color=(255, 0, 0)).with_duration(1) + green = ColorClip((640, 480), color=(0, 200, 0)).with_duration(1) video = concatenate_videoclips([red, green]) cuts, luminosities = detect_scenes(video, fps=10, logger=None) assert len(cuts) == 2 + + +if __name__ == "__main__": + test_credits()