diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ac476bcce..9dca452bd 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,3 +7,4 @@ Delete them if they are not appropriate for this pull request. - [ ] I have properly documented new or changed features in the documention or in the docstrings - [ ] I have properly documented unusual changes to the code in the comments around it - [ ] I have made note of any breaking/backwards incompatible changes +- [ ] I formatted my code using `black -t py36` \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..7dfb31cd1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,14 @@ +name: Code Format Check + +on: [pull_request] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Black Code Formatter + uses: lgeiger/black-action@v1.0.1 + with: + args: "--check --target-version py36 $GITHUB_WORKSPACE" \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index dc3d574aa..045243ee9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ cache: pip os: linux python: - - 3.5 - 3.6 - 3.7 - 3.8 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 960ecc1c0..fac767ac4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,13 @@ ## Keeping/Improving code quality -- Respect PEP8 standards! +- Respect PEP8 conventions! - Just the right amount of comments. Try to write auto-documented code (with very explicit variable names). - If you introduce a new functionality or fix a subtle bug, document it in the docstring/code. +- Moviepy's team adopted [black](https://github.com/psf/black) to autoformat the code. This is enforced for any pull request. + +> Tip: use `black -t py36 .` to autoformat your code, or set black as a plugin of your editor. + ## Using Github @@ -26,10 +30,11 @@ - To make a change - Create a new local branch: `git checkout -b branchname` - Make any changes in it + - Make sure that your code still conforms to the formatting standard: `black -t py36 .` - Run the test suite over it to expose any problems: `python3 setup.py test` - Push the local branch to your fork on github: `git push -u origin branchname` - Go to github.com/yourname/moviepy and it will display 'Recently pushed branches' giving you the option to make a Pull Request to the main repo - Fill in any details for your PR and submit - The test suite will automatically be ran over your submission - A moviepy collaborator will review your code, and help you to get it merge-ready -- You don't have to have finished your feature/bugfix before submitting a PR; just mention that it is a work-in-progress \ No newline at end of file +- You don't have to have finished your feature/bugfix before submitting a PR; just mention that it is a work-in-progress diff --git a/appveyor.yml b/appveyor.yml index b9d18ae95..3b81a5360 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,10 +4,6 @@ environment: global: IMAGE_MAGICK_INSTALL_DIR: c://ImageMagick matrix: - - PYTHON: "C:\\Python35-x64" - PYTHON_ARCH: "64" - ARCH: x64 - - PYTHON: "C:\\Python36-x64" PYTHON_ARCH: "64" ARCH: x64 diff --git a/docs/conf.py b/docs/conf.py index d80d5a9ab..d04c0e2f8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,37 +21,42 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath(".")) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', - 'sphinx.ext.viewcode', 'sphinx.ext.autosummary', 'numpydoc'] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "sphinx.ext.autosummary", + "numpydoc", +] numpydoc_class_members_toctree = False numpydoc_show_class_members = False autosummary_generate = True # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'MoviePy' -copyright = u'2017, Zulko' +project = "MoviePy" +copyright = "2017, Zulko" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -64,174 +69,168 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # -- Options for HTML output --------------------------------------------------- -sys.path.append(os.path.abspath('_themes')) -#html_theme_path = ['_themes'] -html_theme = 'sphinx_rtd_theme' # formerly 'kr' +sys.path.append(os.path.abspath("_themes")) +# html_theme_path = ['_themes'] +html_theme = "sphinx_rtd_theme" # formerly 'kr' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = '_static/logo_small.jpeg' +html_logo = "_static/logo_small.jpeg" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'MoviePydoc' +htmlhelp_basename = "MoviePydoc" # -- Options for LaTeX output -------------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'MoviePy.tex', u'MoviePy Documentation', - u'Zulko', 'manual'), + ("index", "MoviePy.tex", "MoviePy Documentation", "Zulko", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'moviepy', u'MoviePy Documentation', - [u'Zulko'], 1) -] +man_pages = [("index", "moviepy", "MoviePy Documentation", ["Zulko"], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ @@ -240,79 +239,85 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'MoviePy', u'MoviePy Documentation', - u'Zulko', 'MoviePy', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "MoviePy", + "MoviePy Documentation", + "Zulko", + "MoviePy", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. -epub_title = u'MoviePy' -epub_author = u'Zulko' -epub_publisher = u'Zulko' -epub_copyright = u'2017, Zulko' +epub_title = "MoviePy" +epub_author = "Zulko" +epub_publisher = "Zulko" +epub_copyright = "2017, Zulko" # The language of the text. It defaults to the language option # or en if the language is not set. -#epub_language = '' +# epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' +# epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. -#epub_identifier = '' +# epub_identifier = '' # A unique identification for the text. -#epub_uid = '' +# epub_uid = '' # A tuple containing the cover image and cover page html template filenames. -#epub_cover = () +# epub_cover = () # A sequence of (type, uri, title) tuples for the guide element of content.opf. -#epub_guide = () +# epub_guide = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. -#epub_pre_files = [] +# epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. -#epub_post_files = [] +# epub_post_files = [] # A list of files that should not be packed into the epub file. -#epub_exclude_files = [] +# epub_exclude_files = [] # The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 +# epub_tocdepth = 3 # Allow duplicate toc entries. -#epub_tocdup = True +# epub_tocdup = True # Fix unsupported image types using the PIL. -#epub_fix_images = False +# epub_fix_images = False # Scale large images. -#epub_max_image_width = 0 +# epub_max_image_width = 0 # If 'no', URL addresses will not be shown. -#epub_show_urls = 'inline' +# epub_show_urls = 'inline' # If false, no index is generated. -#epub_use_index = True +# epub_use_index = True -#autodoc_member_order = 'bysource' +# autodoc_member_order = 'bysource' diff --git a/examples/compo_from_image.py b/examples/compo_from_image.py index 47ad19aa3..1bdc00e02 100644 --- a/examples/compo_from_image.py +++ b/examples/compo_from_image.py @@ -9,22 +9,26 @@ # Load 7 clips from the US National Parks. Public Domain :D -clips = [VideoFileClip(n, audio=False).subclip(18,22) for n in - [ "../../videos/romo_0004.mov", - "../../videos/apis-0001.mov", - "../../videos/romo_0001.mov", - "../../videos/elma_s0003.mov", - "../../videos/elma_s0002.mov", - "../../videos/calo-0007.mov", - "../../videos/grsm_0005.mov"]] +clips = [ + VideoFileClip(n, audio=False).subclip(18, 22) + for n in [ + "../../videos/romo_0004.mov", + "../../videos/apis-0001.mov", + "../../videos/romo_0001.mov", + "../../videos/elma_s0003.mov", + "../../videos/elma_s0002.mov", + "../../videos/calo-0007.mov", + "../../videos/grsm_0005.mov", + ] +] # 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)] +comp_clips = [ + c.resize(r.size).set_mask(r.mask).set_pos(r.screenpos) + for c, r in zip(clips, regions) +] -cc = CompositeVideoClip(comp_clips,im.size) +cc = CompositeVideoClip(comp_clips, im.size) cc.resize(0.6).write_videofile("../../composition.mp4") # Note that this particular composition takes quite a long time of diff --git a/examples/dancing_knights.py b/examples/dancing_knights.py index 913057b47..c136b7c7c 100644 --- a/examples/dancing_knights.py +++ b/examples/dancing_knights.py @@ -36,52 +36,71 @@ if not os.path.exists("knights.mp4"): os.system("youtube-dl zvCvOC2VwDc -o knights.mp4") os.system("youtube-dl lkY3Ek9VPtg -o frontier.mp4") -#========== +# ========== # LOAD, EDIT, ANALYZE THE AUDIO -audio = (AudioFileClip("frontier.mp4") - .subclip((4,7), (4,18)) - .audio_fadein(1) - .audio_fadeout(1)) +audio = ( + AudioFileClip("frontier.mp4") + .subclip((4, 7), (4, 18)) + .audio_fadein(1) + .audio_fadeout(1) +) audio_period = find_audio_period(audio) -print ('Analyzed the audio, found a period of %.02f seconds'%audio_period) +print("Analyzed the audio, found a period of %.02f seconds" % audio_period) # LOAD, EDIT, ANALYZE THE VIDEO -clip = (VideoFileClip("./knights.mp4", audio=False) - .subclip((1,24.15),(1,26)) - .crop(x1=332, x2=910, y2=686)) +clip = ( + VideoFileClip("./knights.mp4", audio=False) + .subclip((1, 24.15), (1, 26)) + .crop(x1=332, x2=910, y2=686) +) -video_period = find_video_period(clip, tmin=.3) -print ('Analyzed the video, found a period of %.02f seconds'%video_period) +video_period = find_video_period(clip, tmin=0.3) +print("Analyzed the video, found a period of %.02f seconds" % video_period) -edited_right = (clip.subclip(0,video_period) - .speedx(final_duration=2*audio_period) - .fx(vfx.loop, duration=audio.duration) - .subclip(.25)) +edited_right = ( + clip.subclip(0, video_period) + .speedx(final_duration=2 * audio_period) + .fx(vfx.loop, duration=audio.duration) + .subclip(0.25) +) edited_left = edited_right.fx(vfx.mirror_x) -dancing_knights = (clips_array([[edited_left, edited_right]]) - .fadein(1).fadeout(1).set_audio(audio).subclip(.3)) +dancing_knights = ( + clips_array([[edited_left, edited_right]]) + .fadein(1) + .fadeout(1) + .set_audio(audio) + .subclip(0.3) +) # MAKE THE TITLE SCREEN -txt_title = (TextClip("15th century dancing\n(hypothetical)", fontsize=70, - font="Century-Schoolbook-Roman", color="white") - .margin(top=15, opacity=0) - .set_position(("center","top"))) +txt_title = ( + TextClip( + "15th century dancing\n(hypothetical)", + fontsize=70, + font="Century-Schoolbook-Roman", + color="white", + ) + .margin(top=15, opacity=0) + .set_position(("center", "top")) +) -title = (CompositeVideoClip([dancing_knights.to_ImageClip(), txt_title]) - .fadein(.5) - .set_duration(3.5)) +title = ( + CompositeVideoClip([dancing_knights.to_ImageClip(), txt_title]) + .fadein(0.5) + .set_duration(3.5) +) # MAKE THE CREDITS SCREEN @@ -103,17 +122,27 @@ Edited with MoviePy: http://zulko.github.io/moviepy/ """ -credits = (TextClip(txt_credits, color='white', - font="Century-Schoolbook-Roman", fontsize=35, kerning=-2, - interline=-1, bg_color='black', size=title.size) - .set_duration(2.5) - .fadein(.5) - .fadeout(.5)) +credits = ( + TextClip( + txt_credits, + color="white", + font="Century-Schoolbook-Roman", + fontsize=35, + kerning=-2, + interline=-1, + bg_color="black", + size=title.size, + ) + .set_duration(2.5) + .fadein(0.5) + .fadeout(0.5) +) # ASSEMBLE EVERYTHING, WRITE TO FILE final = concatenate_videoclips([title, dancing_knights, credits]) -final.write_videofile("dancing_knights.mp4", fps=clip.fps, - audio_bitrate="1000k", bitrate="4000k") +final.write_videofile( + "dancing_knights.mp4", fps=clip.fps, audio_bitrate="1000k", bitrate="4000k" +) diff --git a/examples/example_with_sound.py b/examples/example_with_sound.py index 85b4f8f10..6adb2bfdc 100644 --- a/examples/example_with_sound.py +++ b/examples/example_with_sound.py @@ -8,52 +8,52 @@ from moviepy.editor import * from moviepy.video.tools.drawing import color_split -duration = 6 # duration of the final clip +duration = 6 # duration of the final clip # LOAD THE MAIN SCENE # this small video contains the two scenes that we will put together. main_clip = VideoFileClip("../../videos/charadePhone.mp4") -W,H = main_clip.size +W, H = main_clip.size +# 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 +) -# 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) - mask_clip = ImageClip(mask, ismask=True) - -clip_left = (main_clip.coreader() - .subclip(0,duration) - .crop( x1=60, x2=60 + 2*W/3) - .set_mask(mask_clip)) +clip_left = ( + main_clip.coreader() + .subclip(0, duration) + .crop(x1=60, x2=60 + 2 * W / 3) + .set_mask(mask_clip) +) -# MAKE THE RIGHT CLIP : cut, crop, add a mask - -mask = color_split((2*W/3,H), - p1=(2,H), p2=(W/3+2,0), - col1=0, col2=1, - grad_width=2) -mask_clip = ImageClip(mask, ismask=True) +# MAKE THE RIGHT CLIP : cut, crop, add a mask -clip_right = (main_clip.coreader() - .subclip(21,21+duration) - .crop(x1=70, x2=70+2*W/3) - .set_mask(mask_clip)) +mask = color_split( + (2 * W / 3, H), p1=(2, H), p2=(W / 3 + 2, 0), col1=0, col2=1, grad_width=2 +) +mask_clip = ImageClip(mask, ismask=True) +clip_right = ( + main_clip.coreader() + .subclip(21, 21 + duration) + .crop(x1=70, x2=70 + 2 * W / 3) + .set_mask(mask_clip) +) # ASSEMBLE AND WRITE THE MOVIE TO A FILE -cc = CompositeVideoClip([clip_right.set_pos('right').volumex(0.4), - clip_left.set_pos('left').volumex(0.4)], - size = (W,H)) -#cc.preview() -cc.write_videofile("../../biphone3.avi",fps=24, codec='mpeg4') +cc = CompositeVideoClip( + [clip_right.set_pos("right").volumex(0.4), clip_left.set_pos("left").volumex(0.4)], + size=(W, H), +) +# cc.preview() +cc.write_videofile("../../biphone3.avi", fps=24, codec="mpeg4") diff --git a/examples/headblur.py b/examples/headblur.py index b7f993204..4d3193ef2 100644 --- a/examples/headblur.py +++ b/examples/headblur.py @@ -4,7 +4,7 @@ from moviepy.video.tools.tracking import manual_tracking, to_fxfy # LOAD THE CLIP (subclip 6'51 - 7'01 of a chaplin movie) -clip = VideoFileClip("../../videos/chaplin.mp4").subclip((6,51.7),(7,01.3)) +clip = VideoFileClip("../../videos/chaplin.mp4").subclip((6, 51.7), (7, 01.3)) # MANUAL TRACKING OF THE HEAD @@ -14,37 +14,42 @@ # Note that we save the list (ti,xi,yi), not the functions fx and fy # (that we will need) because they have dependencies. -#txy, (fx,fy) = manual_tracking(clip, fps=6) -#with open("../../chaplin_txy.dat",'w+') as f: +# txy, (fx,fy) = manual_tracking(clip, fps=6) +# with open("../../chaplin_txy.dat",'w+') as f: # pickle.dump(txy) - # 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 f: + fx, fy = to_fxfy(pickle.load(f)) # BLUR CHAPLIN'S HEAD IN THE CLIP -clip_blurred = clip.fx( vfx.headblur, fx, fy, 25) +clip_blurred = clip.fx(vfx.headblur, fx, fy, 25) # Generate the text, put in on a grey background -txt = TextClip("Hey you ! \n You're blurry!", color='grey70', - size = clip.size, bg_color='grey20', - font = "Century-Schoolbook-Italic", fontsize=40) - - +txt = TextClip( + "Hey you ! \n You're blurry!", + color="grey70", + size=clip.size, + bg_color="grey20", + font="Century-Schoolbook-Italic", + fontsize=40, +) + + # Concatenate the Chaplin clip with the text clip, add audio -final = concatenate_videoclips([clip_blurred,txt.set_duration(3)]).\ - set_audio(clip.audio) +final = concatenate_videoclips([clip_blurred, txt.set_duration(3)]).set_audio( + clip.audio +) # We write the result to a file. Here we raise the bitrate so that # the final video is not too ugly. -final.write_videofile('../../blurredChaplin.avi', bitrate="3000k") +final.write_videofile("../../blurredChaplin.avi", bitrate="3000k") diff --git a/examples/logo.py b/examples/logo.py index 439939001..72a47b843 100644 --- a/examples/logo.py +++ b/examples/logo.py @@ -2,27 +2,26 @@ from moviepy.editor import * -w,h = moviesize = (720,380) +w, h = moviesize = (720, 380) duration = 1 -def f(t,size, a = np.pi/3, thickness = 20): - w,h = size - v = thickness* np.array([np.cos(a),np.sin(a)])[::-1] - center = [int(t*w/duration),h/2] - return biGradientScreen(size,center,v,0.6,0.0) - -logo = ImageClip("../../videos/logo_descr.png").\ - resize(width=w/2).\ - set_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) - -cc = CompositeVideoClip([im.set_pos(2*["center"]),shade], - size = moviesize) - -cc.subclip(0,duration).write_videofile("moviepy_logo.avi",fps=24) + +def f(t, size, a=np.pi / 3, thickness=20): + w, h = size + v = thickness * np.array([np.cos(a), np.sin(a)])[::-1] + center = [int(t * w / duration), h / 2] + return biGradientScreen(size, center, v, 0.6, 0.0) + + +logo = ImageClip("../../videos/logo_descr.png").resize(width=w / 2).set_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) + +cc = CompositeVideoClip([im.set_pos(2 * ["center"]), shade], size=moviesize) + +cc.subclip(0, duration).write_videofile("moviepy_logo.avi", fps=24) diff --git a/examples/masked_credits.py b/examples/masked_credits.py index ff8ab71ff..a76657985 100644 --- a/examples/masked_credits.py +++ b/examples/masked_credits.py @@ -2,26 +2,26 @@ from moviepy.video.tools.credits import credits1 # Load the mountains clip, cut it, slow it down, make it look darker -clip = (VideoFileClip('../../videos/badl-0001.mov', audio=False) - .subclip(37,46) - .speedx( 0.4) - .fx( vfx.colorx, 0.7)) +clip = ( + VideoFileClip("../../videos/badl-0001.mov", audio=False) + .subclip(37, 46) + .speedx(0.4) + .fx(vfx.colorx, 0.7) +) # Save the first frame to later make a mask with GIMP (only once) -#~ clip.save_frame('../../credits/mountainMask2.png') +# ~ clip.save_frame('../../credits/mountainMask2.png') # Load the mountain mask made with GIMP -mountainmask = ImageClip('../../credits/mountainMask2.png',ismask=True) +mountainmask = ImageClip("../../credits/mountainMask2.png", ismask=True) # Generate the credits from a text file -credits = credits1('../../credits/credits.txt',3*clip.w/4) -scrolling_credits = credits.set_pos(lambda t:('center',-10*t)) +credits = credits1("../../credits/credits.txt", 3 * clip.w / 4) +scrolling_credits = credits.set_pos(lambda t: ("center", -10 * t)) # Make the credits scroll. Here, 10 pixels per second -final = CompositeVideoClip([clip, - scrolling_credits, - clip.set_mask(mountainmask)]) - -final.subclip(8,10).write_videofile("../../credits_mountains.avi") +final = CompositeVideoClip([clip, scrolling_credits, clip.set_mask(mountainmask)]) + +final.subclip(8, 10).write_videofile("../../credits_mountains.avi") diff --git a/examples/moving_letters.py b/examples/moving_letters.py index 6b1dae27d..53f417f7d 100644 --- a/examples/moving_letters.py +++ b/examples/moving_letters.py @@ -5,61 +5,72 @@ # 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) -cvc = CompositeVideoClip( [txtClip.set_pos('center')], - size=screensize) +screensize = (720, 460) +txtClip = TextClip( + "Cool effect", color="white", font="Amiri-Bold", kerning=5, fontsize=100 +) +cvc = CompositeVideoClip([txtClip.set_pos("center")], size=screensize) # THE NEXT FOUR FUNCTIONS DEFINE FOUR WAYS OF MOVING THE LETTERS # helper function -rotMatrix = lambda a: np.array( [[np.cos(a),np.sin(a)], - [-np.sin(a),np.cos(a)]] ) - -def vortex(screenpos,i,nletters): - d = lambda t : 1.0/(0.3+t**8) #damping - a = i*np.pi/ nletters # angle of the movement - v = rotMatrix(a).dot([-1,0]) - if i%2 : v[1] = -v[1] - return lambda t: screenpos+400*d(t)*rotMatrix(0.5*d(t)*a).dot(v) - -def cascade(screenpos,i,nletters): - v = np.array([0,-1]) - d = lambda t : 1 if t<0 else abs(np.sinc(t)/(1+t**4)) - return lambda t: screenpos+v*400*d(t-0.15*i) - -def arrive(screenpos,i,nletters): - v = np.array([-1,0]) - d = lambda t : max(0, 3-3*t) - return lambda t: screenpos-400*v*d(t-0.2*i) - -def vortexout(screenpos,i,nletters): - d = lambda t : max(0,t) #damping - a = i*np.pi/ nletters # angle of the movement - v = rotMatrix(a).dot([-1,0]) - if i%2 : v[1] = -v[1] - return lambda t: screenpos+400*d(t-0.1*i)*rotMatrix(-0.2*d(t)*a).dot(v) +rotMatrix = lambda a: np.array([[np.cos(a), np.sin(a)], [-np.sin(a), np.cos(a)]]) +def vortex(screenpos, i, nletters): + d = lambda t: 1.0 / (0.3 + t ** 8) # damping + a = i * np.pi / nletters # angle of the movement + v = rotMatrix(a).dot([-1, 0]) + if i % 2: + v[1] = -v[1] + return lambda t: screenpos + 400 * d(t) * rotMatrix(0.5 * d(t) * a).dot(v) + + +def cascade(screenpos, i, nletters): + v = np.array([0, -1]) + d = lambda t: 1 if t < 0 else abs(np.sinc(t) / (1 + t ** 4)) + return lambda t: screenpos + v * 400 * d(t - 0.15 * i) + + +def arrive(screenpos, i, nletters): + v = np.array([-1, 0]) + d = lambda t: max(0, 3 - 3 * t) + return lambda t: screenpos - 400 * v * d(t - 0.2 * i) + + +def vortexout(screenpos, i, nletters): + d = lambda t: max(0, t) # damping + a = i * np.pi / nletters # angle of the movement + v = rotMatrix(a).dot([-1, 0]) + if i % 2: + v[1] = -v[1] + return lambda t: screenpos + 400 * d(t - 0.1 * i) * rotMatrix(-0.2 * d(t) * a).dot( + v + ) + # WE USE THE PLUGIN findObjects TO LOCATE AND SEPARATE EACH LETTER -letters = findObjects(cvc) # a list of ImageClips +letters = findObjects(cvc) # a list of ImageClips # WE ANIMATE THE LETTERS + def moveLetters(letters, funcpos): - return [ letter.set_pos(funcpos(letter.screenpos,i,len(letters))) - for i,letter in enumerate(letters)] + return [ + letter.set_pos(funcpos(letter.screenpos, i, len(letters))) + for i, letter in enumerate(letters) + ] + -clips = [ CompositeVideoClip( moveLetters(letters,funcpos), - size = screensize).subclip(0,5) - for funcpos in [vortex, cascade, arrive, vortexout] ] +clips = [ + CompositeVideoClip(moveLetters(letters, funcpos), size=screensize).subclip(0, 5) + for funcpos in [vortex, cascade, arrive, vortexout] +] # WE CONCATENATE EVERYTHING AND WRITE TO A FILE final_clip = concatenate_videoclips(clips) -final_clip.write_videofile('../../coolTextEffects.avi',fps=25,codec='mpeg4') +final_clip.write_videofile("../../coolTextEffects.avi", fps=25, codec="mpeg4") diff --git a/examples/painting_effect.py b/examples/painting_effect.py index 8dda77201..ed9264553 100644 --- a/examples/painting_effect.py +++ b/examples/painting_effect.py @@ -1,41 +1,43 @@ """ requires scikit-image installed (for vfx.painting) """ - + from moviepy.editor import * # 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 = cvsecs(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). -clip_before = charade.coreader().subclip(tfreeze -2,tfreeze) -clip_after = charade.coreader().subclip(tfreeze ,tfreeze +2) +clip_before = charade.coreader().subclip(tfreeze - 2, tfreeze) +clip_after = charade.coreader().subclip(tfreeze, tfreeze + 2) # THE FRAME TO FREEZE 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) +painting = charade.fx(vfx.painting, saturation=1.6, black=0.006).to_ImageClip(tfreeze) + +txt = TextClip("Audrey", font="Amiri-regular", fontsize=35) -painting_txt = (CompositeVideoClip([painting,txt.set_pos((10,180))]) - .add_mask() - .set_duration(3) - .crossfadein( 0.5) - .crossfadeout( 0.5)) +painting_txt = ( + CompositeVideoClip([painting, txt.set_pos((10, 180))]) + .add_mask() + .set_duration(3) + .crossfadein(0.5) + .crossfadeout(0.5) +) # FADEIN/FADEOUT EFFECT ON THE PAINTED IMAGE -painting_fading = CompositeVideoClip([im_freeze,painting_txt]) +painting_fading = CompositeVideoClip([im_freeze, painting_txt]) # FINAL CLIP AND RENDERING -final_clip = concatenate_videoclips([ clip_before, - painting_fading.set_duration(3), - clip_after]) +final_clip = concatenate_videoclips( + [clip_before, painting_fading.set_duration(3), clip_after] +) -final_clip.write_videofile('../../audrey.avi',fps=charade.fps, - codec = "mpeg4", audio_bitrate="3000k") +final_clip.write_videofile( + "../../audrey.avi", fps=charade.fps, codec="mpeg4", audio_bitrate="3000k" +) diff --git a/examples/soundtrack.py b/examples/soundtrack.py index 38c20b209..1f4081ffe 100644 --- a/examples/soundtrack.py +++ b/examples/soundtrack.py @@ -3,14 +3,15 @@ # We load a movie and replace the sound with some music: -movie = VideoFileClip("../../videos/dam.mov").\ - set_audio( AudioFileClip("../../sounds/startars.ogg") ) +movie = VideoFileClip("../../videos/dam.mov").set_audio( + AudioFileClip("../../sounds/startars.ogg") +) # If the soundtrack is longer than the movie, then at the end of the clip # 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.set_duration(movie.duration) movie.write_videofile("../../test_soundtrack.avi", codec="mpeg4") diff --git a/examples/star_worms.py b/examples/star_worms.py index 97ccf0d84..0117fffb6 100644 --- a/examples/star_worms.py +++ b/examples/star_worms.py @@ -15,87 +15,91 @@ # RESOLUTION w = 720 -h = w*9/16 # 16/9 screen -moviesize = w,h - +h = w * 9 / 16 # 16/9 screen +moviesize = w, h # THE RAW TEXT -txt = "\n".join([ -"A long time ago, in a faraway galaxy,", -"there lived a prince and a princess", -"who had never seen the stars, for they", -"lived deep underground.", -"", -"Many years before, the prince's", -"grandfather had ventured out to the", -"surface and had been burnt to ashes by", -"solar winds.", -"", -"One day, as the princess was coding", -"and the prince was shopping online, a", -"meteor landed just a few megameters", -"from the couple's flat." -]) +txt = "\n".join( + [ + "A long time ago, in a faraway galaxy,", + "there lived a prince and a princess", + "who had never seen the stars, for they", + "lived deep underground.", + "", + "Many years before, the prince's", + "grandfather had ventured out to the", + "surface and had been burnt to ashes by", + "solar winds.", + "", + "One day, as the princess was coding", + "and the prince was shopping online, a", + "meteor landed just a few megameters", + "from the couple's flat.", + ] +) # Add blanks -txt = 10*"\n" +txt + 10*"\n" +txt = 10 * "\n" + txt + 10 * "\n" # CREATE THE TEXT IMAGE -clip_txt = TextClip(txt,color='white', align='West',fontsize=25, - font='Xolonium-Bold', method='label') +clip_txt = TextClip( + txt, color="white", align="West", fontsize=25, font="Xolonium-Bold", method="label" +) # SCROLL THE TEXT IMAGE BY CROPPING A MOVING AREA 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']) +fl = lambda gf, t: gf(t)[int(txt_speed * t) : int(txt_speed * t) + h, :] +moving_txt = clip_txt.fl(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) -gradmask = ImageClip(grad,ismask=True) -fl = lambda pic : np.minimum(pic,gradmask.img) +grad = color_gradient( + moving_txt.size, p1=(0, 2 * h / 3), p2=(0, h / 4), col1=0.0, col2=1.0 +) +gradmask = ImageClip(grad, ismask=True) +fl = lambda pic: np.minimum(pic, gradmask.img) moving_txt.mask = moving_txt.mask.fl_image(fl) # WARP THE TEXT INTO A TRAPEZOID (PERSPECTIVE EFFECT) -def trapzWarp(pic,cx,cy,ismask=False): + +def trapzWarp(pic, cx, cy, ismask=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]]) - dst = np.array([[cx*X,cy*Y],[(1-cx)*X,cy*Y],[X,Y],[0,Y]]) + Y, X = pic.shape[:2] + src = np.array([[0, 0], [X, 0], [X, Y], [0, Y]]) + dst = np.array([[cx * X, cy * Y], [(1 - cx) * X, cy * Y], [X, Y], [0, Y]]) 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') + tform.estimate(src, dst) + im = tf.warp(pic, tform.inverse, output_shape=(Y, X)) + return im if ismask 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) +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) # BACKGROUND IMAGE, DARKENED AT 60% -stars = ImageClip('../../videos/stars.jpg') -stars_darkened = stars.fl_image(lambda pic: (0.6*pic).astype('int16')) +stars = ImageClip("../../videos/stars.jpg") +stars_darkened = stars.fl_image(lambda pic: (0.6 * pic).astype("int16")) # COMPOSE THE MOVIE -final = CompositeVideoClip([ - stars_darkened, - warped_txt.set_pos(('center','bottom'))], - size = moviesize) +final = CompositeVideoClip( + [stars_darkened, warped_txt.set_pos(("center", "bottom"))], size=moviesize +) # WRITE TO A FILE @@ -105,7 +109,6 @@ def trapzWarp(pic,cx,cy,ismask=False): # This script is heavy (30s of computations to render 8s of video) - """===================================================================== CODE FOR THE VIDEO TUTORIAL @@ -119,60 +122,64 @@ def trapzWarp(pic,cx,cy,ismask=False): =====================================================================""" - -def annotate(clip,txt,txt_color='white',bg_color=(0,0,255)): +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 = txtclip.on_color((clip.w,txtclip.h+6), color=(0,0,255), - pos=(6,'center')) - - cvc = CompositeVideoClip([clip , txtclip.set_pos((0,'bottom'))]) - - return cvc.set_duration(clip.duration) - -def resizeCenter(clip): - return clip.resize( height=h).set_pos('center') + txtclip = TextClip(txt, fontsize=20, font="Ubuntu-bold", color=txt_color) - -def composeCenter(clip): - return CompositeVideoClip([clip.set_pos('center')],size=moviesize) + txtclip = txtclip.on_color( + (clip.w, txtclip.h + 6), color=(0, 0, 255), pos=(6, "center") + ) + cvc = CompositeVideoClip([clip, txtclip.set_pos((0, "bottom"))]) + return cvc.set_duration(clip.duration) -annotated_clips = [ annotate(clip,text) for clip,text in [ - -(composeCenter(resizeCenter(stars)).subclip(0,3), - "This is a public domain picture of stars"), - -(CompositeVideoClip([stars],moviesize).subclip(0,3), - "We only keep one part."), - -(CompositeVideoClip([stars_darkened],moviesize).subclip(0,3), - "We darken it a little."), - -(composeCenter(resizeCenter(clip_txt)).subclip(0,3), - "We generate a text image."), - -(composeCenter(moving_txt.set_mask(None)).subclip(6,9), - "We scroll the text by cropping a moving region of it."), - -(composeCenter(gradmask.to_RGB()).subclip(0,2), - "We add this mask to the clip."), -(composeCenter(moving_txt).subclip(6,9), - "Here is the result"), +def resizeCenter(clip): + return clip.resize(height=h).set_pos("center") -(composeCenter(warped_txt).subclip(6,9), - "We now warp this clip in a trapezoid."), -(final.subclip(6,9), - "We finally superimpose with the stars.") -]] +def composeCenter(clip): + return CompositeVideoClip([clip.set_pos("center")], size=moviesize) + + +annotated_clips = [ + annotate(clip, text) + for clip, text in [ + ( + composeCenter(resizeCenter(stars)).subclip(0, 3), + "This is a public domain picture of stars", + ), + ( + CompositeVideoClip([stars], moviesize).subclip(0, 3), + "We only keep one part.", + ), + ( + CompositeVideoClip([stars_darkened], moviesize).subclip(0, 3), + "We darken it a little.", + ), + ( + composeCenter(resizeCenter(clip_txt)).subclip(0, 3), + "We generate a text image.", + ), + ( + composeCenter(moving_txt.set_mask(None)).subclip(6, 9), + "We scroll the text by cropping a moving region of it.", + ), + ( + composeCenter(gradmask.to_RGB()).subclip(0, 2), + "We add this mask to the clip.", + ), + (composeCenter(moving_txt).subclip(6, 9), "Here is the result"), + ( + composeCenter(warped_txt).subclip(6, 9), + "We now warp this clip in a trapezoid.", + ), + (final.subclip(6, 9), "We finally superimpose with the stars."), + ] +] # Concatenate and write to a file -concatenate_videoclips(annotated_clips).write_videofile('tutorial.avi', fps=5) +concatenate_videoclips(annotated_clips).write_videofile("tutorial.avi", fps=5) diff --git a/examples/the_end.py b/examples/the_end.py index baa11eadd..940463b5c 100644 --- a/examples/the_end.py +++ b/examples/the_end.py @@ -1,23 +1,27 @@ from moviepy.editor import * from moviepy.video.tools.drawing import circle -clip = VideoFileClip("../../videos/badl-0006.mov", audio=False).\ - subclip(26,31).\ - add_mask() - -w,h = clip.size +clip = ( + VideoFileClip("../../videos/badl-0006.mov", audio=False).subclip(26, 31).add_mask() +) -# The mask is a circle with vanishing radius r(t) = 800-200*t -clip.mask.get_frame = lambda t: circle(screensize=(clip.w,clip.h), - center=(clip.w/2,clip.h/4), - radius=max(0,int(800-200*t)), - col1=1, col2=0, blur=4) +w, h = clip.size +# The mask is a circle with vanishing radius r(t) = 800-200*t +clip.mask.get_frame = lambda t: circle( + screensize=(clip.w, clip.h), + center=(clip.w / 2, clip.h / 4), + radius=max(0, int(800 - 200 * t)), + col1=1, + col2=0, + blur=4, +) -the_end = TextClip("The End", font="Amiri-bold", color="white", - fontsize=70).set_duration(clip.duration) -final = CompositeVideoClip([the_end.set_pos('center'),clip], - size =clip.size) - +the_end = TextClip( + "The End", font="Amiri-bold", color="white", fontsize=70 +).set_duration(clip.duration) + +final = CompositeVideoClip([the_end.set_pos("center"), clip], size=clip.size) + final.write_videofile("../../theEnd.avi") diff --git a/examples/ukulele_concerto.py b/examples/ukulele_concerto.py index 345d1ec7c..5e2482c52 100644 --- a/examples/ukulele_concerto.py +++ b/examples/ukulele_concerto.py @@ -3,40 +3,48 @@ # UKULELE CLIP, OBTAINED BY CUTTING AND CROPPING # RAW FOOTAGE -ukulele = VideoFileClip("../../videos/moi_ukulele.MOV", audio=False).\ - subclip(60+33, 60+50).\ - crop(486, 180, 1196, 570) +ukulele = ( + VideoFileClip("../../videos/moi_ukulele.MOV", audio=False) + .subclip(60 + 33, 60 + 50) + .crop(486, 180, 1196, 570) +) -w,h = moviesize = ukulele.size +w, h = moviesize = ukulele.size # THE PIANO FOOTAGE IS DOWNSIZED, HAS A WHITE MARGIN, IS -# IN THE BOTTOM RIGHT CORNER - -piano = (VideoFileClip("../../videos/douceamb.mp4",audio=False). - subclip(30,50). - resize((w/3,h/3)). # one third of the total screen - margin( 6,color=(255,255,255)). #white margin - margin( bottom=20, right=20, opacity=0). # transparent - set_pos(('right','bottom')) ) +# IN THE BOTTOM RIGHT CORNER +piano = ( + VideoFileClip("../../videos/douceamb.mp4", audio=False) + .subclip(30, 50) + .resize((w / 3, h / 3)) + .margin(6, color=(255, 255, 255)) # one third of the total screen + .margin(bottom=20, right=20, opacity=0) # white margin + .set_pos(("right", "bottom")) # transparent +) # A CLIP WITH A TEXT AND A BLACK SEMI-OPAQUE BACKGROUND -txt = TextClip("V. Zulkoninov - Ukulele Sonata", font='Amiri-regular', - color='white',fontsize=24) +txt = TextClip( + "V. Zulkoninov - Ukulele Sonata", font="Amiri-regular", color="white", fontsize=24 +) -txt_col = txt.on_color(size=(ukulele.w + txt.w,txt.h-10), - color=(0,0,0), pos=(6,'center'), col_opacity=0.6) +txt_col = txt.on_color( + size=(ukulele.w + txt.w, txt.h - 10), + color=(0, 0, 0), + pos=(6, "center"), + col_opacity=0.6, +) # THE TEXT CLIP IS ANIMATED. # I am *NOT* explaining the formula, understands who can/want. -txt_mov = txt_col.set_pos( lambda t: (max(w/30,int(w-0.5*w*t)), - max(5*h/6,int(100*t))) ) - +txt_mov = txt_col.set_pos( + lambda t: (max(w / 30, int(w - 0.5 * w * t)), max(5 * h / 6, int(100 * t))) +) # FINAL ASSEMBLY -final = CompositeVideoClip([ukulele,txt_mov,piano]) -final.subclip(0,5).write_videofile("../../ukulele.avi",fps=24,codec='libx264') +final = CompositeVideoClip([ukulele, txt_mov, piano]) +final.subclip(0, 5).write_videofile("../../ukulele.avi", fps=24, codec="libx264") diff --git a/ez_setup.py b/ez_setup.py index 253547219..b0d6f2aab 100644 --- a/ez_setup.py +++ b/ez_setup.py @@ -31,14 +31,16 @@ DEFAULT_VERSION = "0.9.6" DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" + def _python_cmd(*args): args = (sys.executable,) + args return subprocess.call(args) == 0 + def _install(tarball, install_args=()): # extracting the tarball tmpdir = tempfile.mkdtemp() - log.warn('Extracting in %s', tmpdir) + log.warn("Extracting in %s", tmpdir) old_wd = os.getcwd() try: os.chdir(tmpdir) @@ -49,13 +51,13 @@ def _install(tarball, install_args=()): # going in the directory subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) os.chdir(subdir) - log.warn('Now working in %s', subdir) + log.warn("Now working in %s", subdir) # installing - log.warn('Installing Setuptools') - if not _python_cmd('setup.py', 'install', *install_args): - log.warn('Something went wrong during the installation.') - log.warn('See the error message above.') + log.warn("Installing Setuptools") + if not _python_cmd("setup.py", "install", *install_args): + log.warn("Something went wrong during the installation.") + log.warn("See the error message above.") # exitcode will be 2 return 2 finally: @@ -66,7 +68,7 @@ def _install(tarball, install_args=()): def _build_egg(egg, tarball, to_dir): # extracting the tarball tmpdir = tempfile.mkdtemp() - log.warn('Extracting in %s', tmpdir) + log.warn("Extracting in %s", tmpdir) old_wd = os.getcwd() try: os.chdir(tmpdir) @@ -77,11 +79,11 @@ def _build_egg(egg, tarball, to_dir): # going in the directory subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) os.chdir(subdir) - log.warn('Now working in %s', subdir) + log.warn("Now working in %s", subdir) # building an egg - log.warn('Building a Setuptools egg in %s', to_dir) - _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + log.warn("Building a Setuptools egg in %s", to_dir) + _python_cmd("setup.py", "-q", "bdist_egg", "--dist-dir", to_dir) finally: os.chdir(old_wd) @@ -89,27 +91,33 @@ def _build_egg(egg, tarball, to_dir): # returning the result log.warn(egg) if not os.path.exists(egg): - raise IOError('Could not build the egg.') + raise IOError("Could not build the egg.") def _do_download(version, download_base, to_dir, download_delay): - egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' - % (version, sys.version_info[0], sys.version_info[1])) + egg = os.path.join( + to_dir, + "setuptools-%s-py%d.%d.egg" + % (version, sys.version_info[0], sys.version_info[1]), + ) if not os.path.exists(egg): - tarball = download_setuptools(version, download_base, - to_dir, download_delay) + tarball = download_setuptools(version, download_base, to_dir, download_delay) _build_egg(egg, tarball, to_dir) sys.path.insert(0, egg) import setuptools + setuptools.bootstrap_install_from = egg -def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, download_delay=15): +def use_setuptools( + version=DEFAULT_VERSION, + download_base=DEFAULT_URL, + to_dir=os.curdir, + download_delay=15, +): # making sure we use the absolute path to_dir = os.path.abspath(to_dir) - was_imported = 'pkg_resources' in sys.modules or \ - 'setuptools' in sys.modules + was_imported = "pkg_resources" in sys.modules or "setuptools" in sys.modules try: import pkg_resources except ImportError: @@ -121,23 +129,23 @@ def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, e = sys.exc_info()[1] if was_imported: sys.stderr.write( - "The required version of setuptools (>=%s) is not available,\n" - "and can't be installed while this script is running. Please\n" - "install a more recent version first, using\n" - "'easy_install -U setuptools'." - "\n\n(Currently using %r)\n" % (version, e.args[0])) + "The required version of setuptools (>=%s) is not available,\n" + "and can't be installed while this script is running. Please\n" + "install a more recent version first, using\n" + "'easy_install -U setuptools'." + "\n\n(Currently using %r)\n" % (version, e.args[0]) + ) sys.exit(2) else: - del pkg_resources, sys.modules['pkg_resources'] # reload ok - return _do_download(version, download_base, to_dir, - download_delay) + del pkg_resources, sys.modules["pkg_resources"] # reload ok + return _do_download(version, download_base, to_dir, download_delay) except pkg_resources.DistributionNotFound: - return _do_download(version, download_base, to_dir, - download_delay) + return _do_download(version, download_base, to_dir, download_delay) -def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, delay=15): +def download_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay=15 +): """Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available @@ -183,6 +191,7 @@ def _extractall(self, path=".", members=None): import copy import operator from tarfile import ExtractError + directories = [] if members is None: @@ -198,12 +207,14 @@ def _extractall(self, path=".", members=None): # Reverse sort directories. if sys.version_info < (2, 4): + def sorter(dir1, dir2): return cmp(dir1.name, dir2.name) + directories.sort(sorter) directories.reverse() else: - directories.sort(key=operator.attrgetter('name'), reverse=True) + directories.sort(key=operator.attrgetter("name"), reverse=True) # Set correct owner, mtime and filemode on directories. for tarinfo in directories: @@ -229,30 +240,40 @@ def _build_install_args(options): if sys.version_info < (2, 6): log.warn("--user requires Python 2.6 or later") raise SystemExit(1) - install_args.append('--user') + install_args.append("--user") return install_args + def _parse_args(): """ Parse the command line for options """ parser = optparse.OptionParser() parser.add_option( - '--user', dest='user_install', action='store_true', default=False, - help='install in user site package (requires Python 2.6 or later)') + "--user", + dest="user_install", + action="store_true", + default=False, + help="install in user site package (requires Python 2.6 or later)", + ) parser.add_option( - '--download-base', dest='download_base', metavar="URL", + "--download-base", + dest="download_base", + metavar="URL", default=DEFAULT_URL, - help='alternative URL from where to download the setuptools package') + help="alternative URL from where to download the setuptools package", + ) options, args = parser.parse_args() # positional arguments are ignored return options + def main(version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" options = _parse_args() tarball = download_setuptools(download_base=options.download_base) return _install(tarball, _build_install_args(options)) -if __name__ == '__main__': + +if __name__ == "__main__": sys.exit(main()) diff --git a/find_latest_imagemagick_version.py b/find_latest_imagemagick_version.py index 00cde14ef..0d0f8904d 100644 --- a/find_latest_imagemagick_version.py +++ b/find_latest_imagemagick_version.py @@ -3,19 +3,21 @@ url = "https://legacy.imagemagick.org/script/index.php" -'''This little script parses url above to extract latest image magick version +"""This little script parses url above to extract latest image magick version (major version 6.9), to feed it into CI system. Not the best way for reproducible builds, but it's preferred for now over storing imagemagick installer into the git repository -''' +""" response = request.urlopen(url) -html = response.read().decode('utf-8') +html = response.read().decode("utf-8") r = re.compile("6\.9\.[0-9]+\-[0-9]+") version = r.findall(html) if len(version) == 0: - raise ValueError("Could not find latest legacy 6.9.X-Y ImageMagick version from {}".format(url)) + raise ValueError( + "Could not find latest legacy 6.9.X-Y ImageMagick version from {}".format(url) + ) version = version[0] # Append Q16 build -version += '-Q16' +version += "-Q16" print(version) diff --git a/moviepy/Clip.py b/moviepy/Clip.py index 24bc3b2c5..dfd213738 100644 --- a/moviepy/Clip.py +++ b/moviepy/Clip.py @@ -10,9 +10,14 @@ import proglog from tqdm import tqdm -from moviepy.decorators import (apply_to_audio, apply_to_mask, - convert_to_seconds, outplace, - requires_duration, use_clip_fps_by_default) +from moviepy.decorators import ( + apply_to_audio, + apply_to_mask, + convert_to_seconds, + outplace, + requires_duration, + use_clip_fps_by_default, +) class Clip: @@ -38,12 +43,12 @@ class Clip: this case their duration will be ``None``. """ - + # prefix for all temporary video and audio files. - # You can overwrite it with + # You can overwrite it with # >>> Clip._TEMP_FILES_PREFIX = "temp_" - _TEMP_FILES_PREFIX = 'TEMP_MPY_' + _TEMP_FILES_PREFIX = "TEMP_MPY_" def __init__(self): @@ -67,14 +72,14 @@ def copy(self): """ newclip = copy(self) - if hasattr(self, 'audio'): + if hasattr(self, "audio"): newclip.audio = copy(self.audio) - if hasattr(self, 'mask'): + if hasattr(self, "mask"): newclip.mask = copy(self.mask) - + return newclip - @convert_to_seconds(['t']) + @convert_to_seconds(["t"]) def get_frame(self, t): """ Gets a numpy array representing the RGB picture of the clip at time t @@ -132,7 +137,7 @@ def fl(self, fun, apply_to=None, keep_duration=True): if apply_to is None: apply_to = [] - #mf = copy(self.make_frame) + # mf = copy(self.make_frame) newclip = self.set_make_frame(lambda t: fun(self.get_frame, t)) if not keep_duration: @@ -143,8 +148,8 @@ def fl(self, fun, apply_to=None, keep_duration=True): apply_to = [apply_to] for attr in apply_to: - a = getattr(newclip, attr, None) - if a is not None: + a = getattr(newclip, attr, None) + if a is not None: new_a = a.fl(fun, keep_duration=keep_duration) setattr(newclip, attr, new_a) @@ -184,8 +189,9 @@ def fl_time(self, t_func, apply_to=None, keep_duration=False): 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.fl( + lambda gf, t: gf(t_func(t)), apply_to, keep_duration=keep_duration + ) def fx(self, func, *args, **kwargs): """ @@ -211,11 +217,9 @@ def fx(self, func, *args, **kwargs): return func(self, *args, **kwargs) - - @apply_to_mask @apply_to_audio - @convert_to_seconds(['t']) + @convert_to_seconds(["t"]) @outplace def set_start(self, t, change_end=True): """ @@ -242,11 +246,9 @@ def set_start(self, t, change_end=True): elif self.end is not None: self.duration = self.end - self.start - - @apply_to_mask @apply_to_audio - @convert_to_seconds(['t']) + @convert_to_seconds(["t"]) @outplace def set_end(self, t): """ @@ -257,18 +259,17 @@ def set_end(self, t): of the returned clip. """ self.end = t - if self.end is None: return + if self.end is None: + return if self.start is None: if self.duration is not None: self.start = max(0, t - newclip.duration) else: self.duration = self.end - self.start - - @apply_to_mask @apply_to_audio - @convert_to_seconds(['t']) + @convert_to_seconds(["t"]) @outplace def set_duration(self, t, change_end=True): """ @@ -287,11 +288,9 @@ def set_duration(self, t, change_end=True): self.end = None if (t is None) else (self.start + t) else: if self.duration is None: - raise Exception("Cannot change clip start when new" - "duration is None") + raise Exception("Cannot change clip start when new" "duration is None") self.start = self.end - t - @outplace def set_make_frame(self, make_frame): """ @@ -306,7 +305,6 @@ def set_fps(self, fps): write_videofile, iterframe, etc. """ self.fps = fps - @outplace def set_ismask(self, ismask): """ Says wheter the clip is a mask or not (ismask is a boolean)""" @@ -317,7 +315,7 @@ def set_memoize(self, memoize): """ Sets wheter the clip should keep the last frame read in memory """ self.memoize = memoize - @convert_to_seconds(['t']) + @convert_to_seconds(["t"]) def is_playing(self, t): """ @@ -342,16 +340,14 @@ def is_playing(self, t): # If we arrive here, a part of t falls in the clip result = 1 * (t >= self.start) if self.end is not None: - result *= (t <= self.end) + result *= t <= self.end return result else: - return((t >= self.start) and - ((self.end is None) or (t < self.end))) - + return (t >= self.start) and ((self.end is None) or (t < self.end)) - @convert_to_seconds(['t_start', 't_end']) + @convert_to_seconds(["t_start", "t_end"]) @apply_to_mask @apply_to_audio def subclip(self, t_start=0, t_end=None): @@ -379,12 +375,14 @@ def subclip(self, t_start=0, t_end=None): if t_start < 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 + t_start = self.duration + t_start # Remember t_start is negative if (self.duration is not None) and (t_start > self.duration): - raise ValueError("t_start (%.02f) " % t_start + - "should be smaller than the clip's " + - "duration (%.02f)." % self.duration) + raise ValueError( + "t_start (%.02f) " % t_start + + "should be smaller than the clip's " + + "duration (%.02f)." % self.duration + ) newclip = self.fl_time(lambda t: t + t_start, apply_to=[]) @@ -396,8 +394,11 @@ def subclip(self, t_start=0, t_end=None): if self.duration is None: - print("Error: subclip with negative times (here %s)" % (str((t_start, t_end))) - + " can only be extracted from clips with a ``duration``") + print( + "Error: subclip with negative times (here %s)" + % (str((t_start, t_end))) + + " can only be extracted from clips with a ``duration``" + ) else: @@ -410,10 +411,9 @@ def subclip(self, t_start=0, t_end=None): return newclip - @apply_to_mask @apply_to_audio - @convert_to_seconds(['ta', 'tb']) + @convert_to_seconds(["ta", "tb"]) def cutout(self, ta, tb): """ Returns a clip playing the content of the current clip but @@ -428,7 +428,7 @@ def cutout(self, ta, tb): if they exist. """ - fl = lambda t: t + (t >= ta)*(tb - ta) + fl = lambda t: t + (t >= ta) * (tb - ta) newclip = self.fl_time(fl) if self.duration is not None: @@ -441,8 +441,7 @@ def cutout(self, ta, tb): @requires_duration @use_clip_fps_by_default - def iter_frames(self, fps=None, with_times = False, logger=None, - dtype=None): + def iter_frames(self, fps=None, with_times=False, logger=None, dtype=None): """ Iterates over all the frames of the clip. Returns each frame of the clip as a HxWxN np.array, @@ -468,7 +467,7 @@ def iter_frames(self, fps=None, with_times = False, logger=None, for frame in myclip.iter_frames()]) """ logger = proglog.default_bar_logger(logger) - for t in logger.iter_bar(t=np.arange(0, self.duration, 1.0/fps)): + for t in logger.iter_bar(t=np.arange(0, self.duration, 1.0 / fps)): frame = self.get_frame(t) if (dtype is not None) and (frame.dtype != dtype): frame = frame.astype(dtype) diff --git a/moviepy/audio/AudioClip.py b/moviepy/audio/AudioClip.py index 122d9b2ec..95b183068 100644 --- a/moviepy/audio/AudioClip.py +++ b/moviepy/audio/AudioClip.py @@ -43,7 +43,7 @@ class AudioClip(Clip): >>> clip.preview() """ - + def __init__(self, make_frame=None, duration=None, fps=None): Clip.__init__(self) @@ -53,40 +53,50 @@ def __init__(self, make_frame=None, duration=None, fps=None): if make_frame is not None: self.make_frame = make_frame frame0 = self.get_frame(0) - if hasattr(frame0, '__iter__'): + if hasattr(frame0, "__iter__"): self.nchannels = len(list(frame0)) else: self.nchannels = 1 if duration is not None: self.duration = duration self.end = duration - + @requires_duration - def iter_chunks(self, chunksize=None, chunk_duration=None, fps=None, - quantize=False, nbytes=2, logger=None): + def iter_chunks( + self, + chunksize=None, + chunk_duration=None, + fps=None, + quantize=False, + nbytes=2, + logger=None, + ): """ Iterator that returns the whole sound array of the clip by chunks """ if fps is None: fps = self.fps logger = proglog.default_bar_logger(logger) if chunk_duration is not None: - chunksize = int(chunk_duration*fps) - - totalsize = int(fps*self.duration) + chunksize = int(chunk_duration * fps) + + totalsize = int(fps * self.duration) nchunks = totalsize // chunksize + 1 pospos = np.linspace(0, totalsize, nchunks + 1, endpoint=True, dtype=int) - + for i in logger.iter_bar(chunk=list(range(nchunks))): - size = pospos[i+1] - pospos[i] - assert(size <= chunksize) - tt = (1.0/fps)*np.arange(pospos[i], pospos[i+1]) - yield self.to_soundarray(tt, nbytes=nbytes, quantize=quantize, - fps=fps, buffersize=chunksize) + size = pospos[i + 1] - pospos[i] + assert size <= chunksize + tt = (1.0 / fps) * np.arange(pospos[i], pospos[i + 1]) + yield self.to_soundarray( + tt, nbytes=nbytes, quantize=quantize, fps=fps, buffersize=chunksize + ) @requires_duration - def to_soundarray(self, tt=None, fps=None, quantize=False, nbytes=2, buffersize=50000): + def to_soundarray( + self, tt=None, fps=None, quantize=False, nbytes=2, buffersize=50000 + ): """ Transforms the sound into an array that can be played by pygame or written in a wav file. See ``AudioClip.preview``. @@ -105,15 +115,18 @@ def to_soundarray(self, tt=None, fps=None, quantize=False, nbytes=2, buffersize= """ if fps is None: fps = self.fps - + stacker = np.vstack if self.nchannels == 2 else np.hstack max_duration = 1.0 * buffersize / fps if tt is None: if self.duration > max_duration: - return stacker(self.iter_chunks(fps=fps, quantize=quantize, - nbytes=2, chunksize=buffersize)) + return stacker( + self.iter_chunks( + fps=fps, quantize=quantize, nbytes=2, chunksize=buffersize + ) + ) else: - tt = np.arange(0, self.duration, 1.0/fps) + tt = np.arange(0, self.duration, 1.0 / fps) """ elif len(tt)> 1.5*buffersize: nchunks = int(len(tt)/buffersize+1) @@ -122,30 +135,44 @@ def to_soundarray(self, tt=None, fps=None, quantize=False, nbytes=2, buffersize= quantize=quantize, nbytes=nbytes) for ttc in tt_chunks]) """ - #print tt.max() - tt.min(), tt.min(), tt.max() - + # print tt.max() - tt.min(), tt.min(), tt.max() + snd_array = self.get_frame(tt) if quantize: snd_array = np.maximum(-0.99, np.minimum(0.99, snd_array)) - inttype = {1: 'int8', 2: 'int16', 4: 'int32'}[nbytes] - snd_array = (2**(8*nbytes-1)*snd_array).astype(inttype) - + inttype = {1: "int8", 2: "int16", 4: "int32"}[nbytes] + snd_array = (2 ** (8 * nbytes - 1) * snd_array).astype(inttype) + return snd_array def max_volume(self, stereo=False, chunksize=50000, logger=None): - + stereo = stereo and (self.nchannels == 2) maxi = np.array([0, 0]) if stereo else 0 - for chunk in self.iter_chunks(chunksize=chunksize,logger=logger): - maxi = np.maximum(maxi, abs(chunk).max(axis=0)) if stereo else max(maxi, abs(chunk).max()) + for chunk in self.iter_chunks(chunksize=chunksize, logger=logger): + maxi = ( + np.maximum(maxi, abs(chunk).max(axis=0)) + if stereo + else max(maxi, abs(chunk).max()) + ) return maxi @requires_duration - def write_audiofile(self, filename, fps=None, nbytes=2, buffersize=2000, - codec=None, bitrate=None, ffmpeg_params=None, - write_logfile=False, verbose=True, logger='bar'): + def write_audiofile( + self, + filename, + fps=None, + nbytes=2, + buffersize=2000, + codec=None, + bitrate=None, + ffmpeg_params=None, + write_logfile=False, + verbose=True, + logger="bar", + ): """ Writes an audio file from the AudioClip. @@ -197,22 +224,33 @@ def write_audiofile(self, filename, fps=None, nbytes=2, buffersize=2000, if codec is None: name, ext = os.path.splitext(os.path.basename(filename)) try: - codec = extensions_dict[ext[1:]]['codec'][0] + codec = extensions_dict[ext[1:]]["codec"][0] except KeyError: - raise ValueError("MoviePy couldn't find the codec associated " - "with the filename. Provide the 'codec' " - "parameter in write_audiofile.") - - return ffmpeg_audiowrite(self, filename, fps, nbytes, buffersize, - codec=codec, bitrate=bitrate, - write_logfile=write_logfile, verbose=verbose, - ffmpeg_params=ffmpeg_params, - logger=logger) + raise ValueError( + "MoviePy couldn't find the codec associated " + "with the filename. Provide the 'codec' " + "parameter in write_audiofile." + ) + + return ffmpeg_audiowrite( + self, + filename, + fps, + nbytes, + buffersize, + codec=codec, + bitrate=bitrate, + write_logfile=write_logfile, + verbose=verbose, + ffmpeg_params=ffmpeg_params, + logger=logger, + ) # The to_audiofile method is replaced by the more explicit write_audiofile. -AudioClip.to_audiofile = deprecated_version_of(AudioClip.write_audiofile, - 'to_audiofile') +AudioClip.to_audiofile = deprecated_version_of( + AudioClip.write_audiofile, "to_audiofile" +) ### @@ -233,9 +271,9 @@ class AudioArrayClip(AudioClip): played. """ - + def __init__(self, array, fps): - + Clip.__init__(self) self.array = array self.fps = fps @@ -244,9 +282,9 @@ def __init__(self, array, fps): def make_frame(t): """ complicated, but must be able to handle the case where t is a list of the form sin(t) """ - + if isinstance(t, np.ndarray): - array_inds = (self.fps*t).astype(int) + array_inds = (self.fps * t).astype(int) in_array = (array_inds > 0) & (array_inds < len(self.array)) result = np.zeros((len(t), 2)) result[in_array] = self.array[array_inds[in_array]] @@ -254,14 +292,14 @@ def make_frame(t): else: i = int(self.fps * t) if i < 0 or i >= len(self.array): - return 0*self.array[0] + return 0 * self.array[0] else: return self.array[i] self.make_frame = make_frame self.nchannels = len(list(self.get_frame(0))) - - + + class CompositeAudioClip(AudioClip): """ Clip made by composing several AudioClips. @@ -282,7 +320,7 @@ 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]): @@ -290,19 +328,21 @@ def __init__(self, clips): self.end = max(ends) def make_frame(t): - + played_parts = [c.is_playing(t) for c in self.clips] - - sounds = [c.get_frame(t - c.start)*np.array([part]).T - for c, part in zip(self.clips, played_parts) - if (part is not False)] - + + sounds = [ + c.get_frame(t - c.start) * np.array([part]).T + for c, part in zip(self.clips, played_parts) + if (part is not False) + ] + if isinstance(t, np.ndarray): zero = np.zeros((len(t), self.nchannels)) - + else: zero = np.zeros(self.nchannels) - + return zero + sum(sounds) self.make_frame = make_frame @@ -313,11 +353,11 @@ 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. + tt = np.cumsum([0] + durations) # start times, and end time. newclips = [c.set_start(t) for c, t in zip(clips, tt)] result = CompositeAudioClip(newclips).set_duration(tt[-1]) - fpss = [c.fps for c in clips if getattr(c, 'fps', None)] + fpss = [c.fps for c in clips if getattr(c, "fps", None)] result.fps = max(fpss) if fpss else None return result diff --git a/moviepy/audio/fx/all/__init__.py b/moviepy/audio/fx/all/__init__.py index 11258d7f0..fabfc41e0 100644 --- a/moviepy/audio/fx/all/__init__.py +++ b/moviepy/audio/fx/all/__init__.py @@ -9,8 +9,7 @@ import moviepy.audio.fx as fx -__all__ = [name for _, name, _ in pkgutil.iter_modules( - fx.__path__) if name != "all"] +__all__ = [name for _, name, _ in pkgutil.iter_modules(fx.__path__) if name != "all"] for name in __all__: exec("from ..%s import %s" % (name, name)) diff --git a/moviepy/audio/fx/audio_fadein.py b/moviepy/audio/fx/audio_fadein.py index 20b1eed00..631a21abd 100644 --- a/moviepy/audio/fx/audio_fadein.py +++ b/moviepy/audio/fx/audio_fadein.py @@ -7,15 +7,16 @@ 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): + + def fading(gf, t): gft = gf(t) - + if np.isscalar(t): factor = min(1.0 * t / duration, 1) - factor = np.array([factor,factor]) + factor = np.array([factor, factor]) else: factor = np.minimum(1.0 * t / duration, 1) - factor = np.vstack([factor,factor]).T + factor = np.vstack([factor, factor]).T return factor * gft - return clip.fl(fading, keep_duration = True) + + return clip.fl(fading, keep_duration=True) diff --git a/moviepy/audio/fx/audio_fadeout.py b/moviepy/audio/fx/audio_fadeout.py index ab6f40ee5..617008724 100644 --- a/moviepy/audio/fx/audio_fadeout.py +++ b/moviepy/audio/fx/audio_fadeout.py @@ -8,16 +8,16 @@ 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): + + def fading(gf, t): gft = gf(t) - + if np.isscalar(t): factor = min(1.0 * (clip.duration - t) / duration, 1) - factor = np.array([factor,factor]) + factor = np.array([factor, factor]) else: - factor = np.minimum( 1.0 * (clip.duration - t) / duration, 1) - factor = np.vstack([factor,factor]).T + factor = np.minimum(1.0 * (clip.duration - t) / duration, 1) + factor = np.vstack([factor, factor]).T return factor * gft - - return clip.fl(fading, keep_duration = True) + + return clip.fl(fading, keep_duration=True) diff --git a/moviepy/audio/fx/audio_loop.py b/moviepy/audio/fx/audio_loop.py index 7e309b7ff..ec4add871 100644 --- a/moviepy/audio/fx/audio_loop.py +++ b/moviepy/audio/fx/audio_loop.py @@ -20,9 +20,9 @@ def audio_loop(audioclip, nloops=None, duration=None): if duration is not None: - nloops = int( duration/ audioclip.duration)+1 - return concatenate_audioclips(nloops*[audioclip]).set_duration(duration) - + nloops = int(duration / audioclip.duration) + 1 + return concatenate_audioclips(nloops * [audioclip]).set_duration(duration) + else: - return concatenate_audioclips(nloops*[audioclip]) + return concatenate_audioclips(nloops * [audioclip]) diff --git a/moviepy/audio/fx/audio_normalize.py b/moviepy/audio/fx/audio_normalize.py index 0c4daff78..5d0485bfc 100644 --- a/moviepy/audio/fx/audio_normalize.py +++ b/moviepy/audio/fx/audio_normalize.py @@ -18,6 +18,5 @@ def audio_normalize(clip): """ - mv = clip.max_volume() return volumex(clip, 1 / mv) diff --git a/moviepy/audio/fx/volumex.py b/moviepy/audio/fx/volumex.py index 6ec84ad5c..dffbd834c 100644 --- a/moviepy/audio/fx/volumex.py +++ b/moviepy/audio/fx/volumex.py @@ -16,5 +16,4 @@ def volumex(clip, factor): >>> newclip = clip.fx( volumex, 0.5) # half audio, use with fx >>> newclip = clip.volumex(2) # only if you used "moviepy.editor" """ - return clip.fl(lambda gf, t: factor * gf(t), - keep_duration=True) + return clip.fl(lambda gf, t: factor * gf(t), keep_duration=True) diff --git a/moviepy/audio/io/AudioFileClip.py b/moviepy/audio/io/AudioFileClip.py index e93dbb04c..41919c991 100644 --- a/moviepy/audio/io/AudioFileClip.py +++ b/moviepy/audio/io/AudioFileClip.py @@ -67,8 +67,9 @@ def __init__(self, filename, buffersize=200000, nbytes=2, fps=44100): AudioClip.__init__(self) self.filename = filename - self.reader = FFMPEG_AudioReader(filename, fps=fps, nbytes=nbytes, - buffersize=buffersize) + self.reader = FFMPEG_AudioReader( + filename, fps=fps, nbytes=nbytes, buffersize=buffersize + ) self.fps = fps self.duration = self.reader.duration self.end = self.reader.duration diff --git a/moviepy/audio/io/ffmpeg_audiowriter.py b/moviepy/audio/io/ffmpeg_audiowriter.py index 174244f9a..017955210 100644 --- a/moviepy/audio/io/ffmpeg_audiowriter.py +++ b/moviepy/audio/io/ffmpeg_audiowriter.py @@ -33,9 +33,18 @@ class FFMPEG_AudioWriter: """ - def __init__(self, filename, fps_input, nbytes=2, - nchannels=2, codec='libfdk_aac', bitrate=None, - input_video=None, logfile=None, ffmpeg_params=None): + def __init__( + self, + filename, + fps_input, + nbytes=2, + nchannels=2, + codec="libfdk_aac", + bitrate=None, + input_video=None, + logfile=None, + ffmpeg_params=None, + ): self.filename = filename self.codec = codec @@ -43,24 +52,37 @@ def __init__(self, filename, fps_input, nbytes=2, if logfile is None: logfile = sp.PIPE - cmd = ([get_setting("FFMPEG_BINARY"), '-y', - "-loglevel", "error" if logfile == sp.PIPE else "info", - "-f", 's%dle' % (8*nbytes), - "-acodec",'pcm_s%dle' % (8*nbytes), - '-ar', "%d" % fps_input, - '-ac', "%d" % nchannels, - '-i', '-'] - + (['-vn'] if input_video is None else ["-i", input_video, '-vcodec', 'copy']) - + ['-acodec', codec] - + ['-ar', "%d" % fps_input] - + ['-strict', '-2'] # needed to support codec 'aac' - + (['-ab', bitrate] if (bitrate is not None) else []) - + (ffmpeg_params if ffmpeg_params else []) - + [filename]) - - popen_params = {"stdout": sp.DEVNULL, - "stderr": logfile, - "stdin": sp.PIPE} + cmd = ( + [ + get_setting("FFMPEG_BINARY"), + "-y", + "-loglevel", + "error" if logfile == sp.PIPE else "info", + "-f", + "s%dle" % (8 * nbytes), + "-acodec", + "pcm_s%dle" % (8 * nbytes), + "-ar", + "%d" % fps_input, + "-ac", + "%d" % nchannels, + "-i", + "-", + ] + + ( + ["-vn"] + if input_video is None + else ["-i", input_video, "-vcodec", "copy"] + ) + + ["-acodec", codec] + + ["-ar", "%d" % fps_input] + + ["-strict", "-2"] # needed to support codec 'aac' + + (["-ab", bitrate] if (bitrate is not None) else []) + + (ffmpeg_params if ffmpeg_params else []) + + [filename] + ) + + popen_params = {"stdout": sp.DEVNULL, "stderr": logfile, "stdin": sp.PIPE} if os.name == "nt": popen_params["creationflags"] = 0x08000000 @@ -75,48 +97,53 @@ def write_frames(self, frames_array): self.proc.stdin.write(frames_array.tostring()) except IOError as err: ffmpeg_error = self.proc.stderr.read() - error = (str(err) + ("\n\nMoviePy error: FFMPEG encountered " - "the following error while writing file %s:" % self.filename - + "\n\n" + str(ffmpeg_error))) + error = str(err) + ( + "\n\nMoviePy error: FFMPEG encountered " + "the following error while writing file %s:" % self.filename + + "\n\n" + + str(ffmpeg_error) + ) if b"Unknown encoder" in ffmpeg_error: - error = (error + - ("\n\nThe audio export failed because FFMPEG didn't " - "find the specified codec for audio encoding (%s). " - "Please install this codec or change the codec when " - "calling to_videofile or to_audiofile. For instance " - "for mp3:\n" - " >>> to_videofile('myvid.mp4', audio_codec='libmp3lame')" - ) % (self.codec)) + error = error + ( + "\n\nThe audio export failed because FFMPEG didn't " + "find the specified codec for audio encoding (%s). " + "Please install this codec or change the codec when " + "calling to_videofile or to_audiofile. For instance " + "for mp3:\n" + " >>> to_videofile('myvid.mp4', audio_codec='libmp3lame')" + ) % (self.codec) elif b"incorrect codec parameters ?" in ffmpeg_error: - error = (error + - ("\n\nThe audio export failed, possibly because the " - "codec specified for the video (%s) is not compatible" - " with the given extension (%s). Please specify a " - "valid 'codec' argument in to_videofile. This would " - "be 'libmp3lame' for mp3, 'libvorbis' for ogg...") - % (self.codec, self.ext)) + error = error + ( + "\n\nThe audio export failed, possibly because the " + "codec specified for the video (%s) is not compatible" + " with the given extension (%s). Please specify a " + "valid 'codec' argument in to_videofile. This would " + "be 'libmp3lame' for mp3, 'libvorbis' for ogg..." + ) % (self.codec, self.ext) elif b"encoder setup failed" in ffmpeg_error: - error = (error + - ("\n\nThe audio export failed, possily because the " - "bitrate you specified was two high or too low for " - "the video codec.")) + error = error + ( + "\n\nThe audio export failed, possily because the " + "bitrate you specified was two high or too low for " + "the video codec." + ) else: - error = (error + - ("\n\nIn case it helps, make sure you are using a " - "recent version of FFMPEG (the versions in the " - "Ubuntu/Debian repos are deprecated).")) + error = error + ( + "\n\nIn case it helps, make sure you are using a " + "recent version of FFMPEG (the versions in the " + "Ubuntu/Debian repos are deprecated)." + ) raise IOError(error) def close(self): - if hasattr(self, 'proc') and self.proc: + if hasattr(self, "proc") and self.proc: self.proc.stdin.close() self.proc.stdin = None if self.proc.stderr is not None: @@ -140,10 +167,19 @@ def __exit__(self, exc_type, exc_value, traceback): @requires_duration -def ffmpeg_audiowrite(clip, filename, fps, nbytes, buffersize, - codec='libvorbis', bitrate=None, - write_logfile=False, verbose=True, - ffmpeg_params=None, logger='bar'): +def ffmpeg_audiowrite( + clip, + filename, + fps, + nbytes, + buffersize, + codec="libvorbis", + bitrate=None, + write_logfile=False, + verbose=True, + ffmpeg_params=None, + logger="bar", +): """ A function that wraps the FFMPEG_AudioWriter to write an AudioClip to a file. @@ -152,20 +188,25 @@ def ffmpeg_audiowrite(clip, filename, fps, nbytes, buffersize, """ if write_logfile: - logfile = open(filename + ".log", 'w+') + logfile = open(filename + ".log", "w+") else: logfile = None logger = proglog.default_bar_logger(logger) logger(message="MoviePy - Writing audio in %s" % filename) - writer = FFMPEG_AudioWriter(filename, fps, nbytes, clip.nchannels, - codec=codec, bitrate=bitrate, - logfile=logfile, - ffmpeg_params=ffmpeg_params) - - for chunk in clip.iter_chunks(chunksize=buffersize, - quantize=True, - nbytes=nbytes, fps=fps, - logger=logger): + writer = FFMPEG_AudioWriter( + filename, + fps, + nbytes, + clip.nchannels, + codec=codec, + bitrate=bitrate, + logfile=logfile, + ffmpeg_params=ffmpeg_params, + ) + + for chunk in clip.iter_chunks( + chunksize=buffersize, quantize=True, nbytes=nbytes, fps=fps, logger=logger + ): writer.write_frames(chunk) writer.close() diff --git a/moviepy/audio/io/preview.py b/moviepy/audio/io/preview.py index e321b2978..5376e0edb 100644 --- a/moviepy/audio/io/preview.py +++ b/moviepy/audio/io/preview.py @@ -6,12 +6,11 @@ from moviepy.decorators import requires_duration pg.init() -pg.display.set_caption('MoviePy') +pg.display.set_caption("MoviePy") @requires_duration -def preview(clip, fps=22050, buffersize=4000, nbytes=2, audioFlag=None, - videoFlag=None): +def preview(clip, fps=22050, buffersize=4000, nbytes=2, audioFlag=None, videoFlag=None): """ Plays the sound clip with pygame. @@ -39,23 +38,23 @@ def preview(clip, fps=22050, buffersize=4000, nbytes=2, audioFlag=None, video and audio during ``VideoClip.preview()``. """ - + pg.mixer.quit() - + 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]) + 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) chunk = pg.sndarray.make_sound(sndarray) - + if (audioFlag is not None) and (videoFlag is not None): audioFlag.set() videoFlag.wait() - + channel = chunk.play() - for i in range(1, len(pospos)-1): - tt = (1.0/fps)*np.arange(pospos[i], pospos[i+1]) + 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) chunk = pg.sndarray.make_sound(sndarray) while channel.get_queue(): diff --git a/moviepy/audio/io/readers.py b/moviepy/audio/io/readers.py index 4f0f0c107..304af72bc 100644 --- a/moviepy/audio/io/readers.py +++ b/moviepy/audio/io/readers.py @@ -38,93 +38,106 @@ class FFMPEG_AudioReader: """ - def __init__(self, filename, buffersize, print_infos=False, - fps=44100, nbytes=2, nchannels=2): + def __init__( + self, filename, buffersize, print_infos=False, fps=44100, nbytes=2, nchannels=2 + ): self.filename = filename self.nbytes = nbytes self.fps = fps - self.f = 's%dle'%(8*nbytes) - self.acodec = 'pcm_s%dle'%(8*nbytes) + self.f = "s%dle" % (8 * nbytes) + self.acodec = "pcm_s%dle" % (8 * nbytes) self.nchannels = nchannels infos = ffmpeg_parse_infos(filename) - self.duration = infos['duration'] - if 'video_duration' in infos: - self.duration = infos['video_duration'] + self.duration = infos["duration"] + if "video_duration" in infos: + self.duration = infos["video_duration"] else: - self.duration = infos['duration'] + self.duration = infos["duration"] self.infos = infos self.proc = None self.nframes = int(self.fps * self.duration) - self.buffersize= min( self.nframes+1, buffersize ) - self.buffer= None + self.buffersize = min(self.nframes + 1, buffersize) + self.buffer = None self.buffer_startframe = 1 self.initialize() self.buffer_around(1) - def initialize(self, starttime = 0): + def initialize(self, starttime=0): """ Opens the file, creates the pipe. """ - self.close_proc() # if any - - if starttime !=0 : - offset = min(1,starttime) - i_arg = ["-ss", "%.05f"%(starttime-offset), - '-i', self.filename, '-vn', - "-ss", "%.05f"%offset] + self.close_proc() # if any + + if starttime != 0: + offset = min(1, starttime) + i_arg = [ + "-ss", + "%.05f" % (starttime - offset), + "-i", + self.filename, + "-vn", + "-ss", + "%.05f" % offset, + ] else: - i_arg = [ '-i', self.filename, '-vn'] - - - cmd = ([get_setting("FFMPEG_BINARY")] + i_arg + - [ '-loglevel', 'error', - '-f', self.f, - '-acodec', self.acodec, - '-ar', "%d"%self.fps, - '-ac', '%d'%self.nchannels, '-']) - - popen_params = {"bufsize": self.buffersize, - "stdout": sp.PIPE, - "stderr": sp.PIPE, - "stdin": sp.DEVNULL} + i_arg = ["-i", self.filename, "-vn"] + + cmd = ( + [get_setting("FFMPEG_BINARY")] + + i_arg + + [ + "-loglevel", + "error", + "-f", + self.f, + "-acodec", + self.acodec, + "-ar", + "%d" % self.fps, + "-ac", + "%d" % self.nchannels, + "-", + ] + ) + + popen_params = { + "bufsize": self.buffersize, + "stdout": sp.PIPE, + "stderr": sp.PIPE, + "stdin": sp.DEVNULL, + } if os.name == "nt": popen_params["creationflags"] = 0x08000000 - self.proc = sp.Popen( cmd, **popen_params) + self.proc = sp.Popen(cmd, **popen_params) - self.pos = np.round(self.fps*starttime) + self.pos = np.round(self.fps * starttime) - - - def skip_chunk(self,chunksize): - s = self.proc.stdout.read(self.nchannels*chunksize*self.nbytes) + def skip_chunk(self, chunksize): + s = self.proc.stdout.read(self.nchannels * chunksize * self.nbytes) self.proc.stdout.flush() - self.pos = self.pos+chunksize - - + self.pos = self.pos + chunksize - def read_chunk(self,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 + L = self.nchannels * chunksize * self.nbytes s = self.proc.stdout.read(L) - dt = {1: 'int8',2:'int16',4:'int32'}[self.nbytes] - if hasattr(np, 'frombuffer'): + dt = {1: "int8", 2: "int16", 4: "int32"}[self.nbytes] + if hasattr(np, "frombuffer"): result = np.frombuffer(s, dtype=dt) else: result = np.fromstring(s, dtype=dt) - result = (1.0*result / 2**(8*self.nbytes-1)).\ - reshape((int(len(result)/self.nchannels), - self.nchannels)) - #self.proc.stdout.flush() - self.pos = self.pos+chunksize + result = (1.0 * result / 2 ** (8 * self.nbytes - 1)).reshape( + (int(len(result) / self.nchannels), self.nchannels) + ) + # self.proc.stdout.flush() + self.pos = self.pos + chunksize return result - - - def seek(self,pos): + def seek(self, pos): """ Reads a frame at time t. Note for coders: getting an arbitrary frame in the video with ffmpeg can be painfully slow if some @@ -132,59 +145,53 @@ def seek(self,pos): arbitrary frames whenever possible, by moving between adjacent frames. """ - if (pos < self.pos) or (pos> (self.pos+1000000)): - t = 1.0*pos/self.fps + if (pos < self.pos) or (pos > (self.pos + 1000000)): + t = 1.0 * pos / self.fps self.initialize(t) elif pos > self.pos: - #print pos - self.skip_chunk(pos-self.pos) + # print pos + self.skip_chunk(pos - self.pos) # last case standing: pos = current pos self.pos = pos - - def close_proc(self): - if hasattr(self, 'proc') and self.proc is not None: + if hasattr(self, "proc") and self.proc is not None: self.proc.terminate() - for std in [ self.proc.stdout, - self.proc.stderr]: + for std in [self.proc.stdout, self.proc.stderr]: std.close() self.proc = None def get_frame(self, tt): buffersize = self.buffersize - if isinstance(tt,np.ndarray): + if isinstance(tt, np.ndarray): # lazy implementation, but should not cause problems in # 99.99 % of the cases - # elements of t that are actually in the range of the # audio file. - in_time = (tt>=0) & (tt < self.duration) + in_time = (tt >= 0) & (tt < self.duration) - # Check that the requested time is in the valid range + # Check that the requested time is in the valid range if not in_time.any(): - raise IOError("Error in file %s, "%(self.filename)+ - "Accessing time t=%.02f-%.02f seconds, "%(tt[0], tt[-1])+ - "with clip duration=%d seconds, "%self.duration) + raise IOError( + "Error in file %s, " % (self.filename) + + "Accessing time t=%.02f-%.02f seconds, " % (tt[0], tt[-1]) + + "with clip duration=%d seconds, " % self.duration + ) # The np.round in the next line is super-important. # Removing it results in artifacts in the noise. - frames = np.round((self.fps*tt)).astype(int)[in_time] + frames = np.round((self.fps * tt)).astype(int)[in_time] fr_min, fr_max = frames.min(), frames.max() - if not (0 <= - (fr_min - self.buffer_startframe) - < len(self.buffer)): + if not (0 <= (fr_min - self.buffer_startframe) < len(self.buffer)): self.buffer_around(fr_min) - elif not (0 <= - (fr_max - self.buffer_startframe) - < len(self.buffer)): + elif not (0 <= (fr_max - self.buffer_startframe) < len(self.buffer)): self.buffer_around(fr_max) try: - result = np.zeros((len(tt),self.nchannels)) + result = np.zeros((len(tt), self.nchannels)) indices = frames - self.buffer_startframe if len(self.buffer) < self.buffersize // 2: indices = indices - (self.buffersize // 2 - len(self.buffer) + 1) @@ -192,61 +199,59 @@ def get_frame(self, tt): return result except IndexError as error: - warnings.warn("Error in file %s, "%(self.filename)+ - "At time t=%.02f-%.02f seconds, "%(tt[0], tt[-1])+ - "indices wanted: %d-%d, "%(indices.min(), indices.max())+ - "but len(buffer)=%d\n"%(len(self.buffer))+ str(error), - UserWarning) + warnings.warn( + "Error in file %s, " % (self.filename) + + "At time t=%.02f-%.02f seconds, " % (tt[0], tt[-1]) + + "indices wanted: %d-%d, " % (indices.min(), indices.max()) + + "but len(buffer)=%d\n" % (len(self.buffer)) + + str(error), + UserWarning, + ) # repeat the last frame instead - indices[indices>=len(self.buffer)] = len(self.buffer) -1 + indices[indices >= len(self.buffer)] = len(self.buffer) - 1 result[in_time] = self.buffer[indices] return result else: - ind = int(self.fps*tt) - if ind<0 or ind> self.nframes: # out of time: return 0 + ind = int(self.fps * tt) + if ind < 0 or ind > self.nframes: # out of time: return 0 return np.zeros(self.nchannels) - if not (0 <= (ind - self.buffer_startframe) >> Clip.to_file = deprecated_version_of(Clip.write_file, 'to_file') """ - if newname is None: newname = f.__name__ + if newname is None: + newname = f.__name__ - warning= ("The function ``%s`` is deprecated and is kept temporarily " - "for backwards compatibility.\nPlease use the new name, " - "``%s``, instead.")%(oldname, newname) + warning = ( + "The function ``%s`` is deprecated and is kept temporarily " + "for backwards compatibility.\nPlease use the new name, " + "``%s``, instead." + ) % (oldname, newname) def fdepr(*a, **kw): warnings.warn("MoviePy: " + warning, PendingDeprecationWarning) return f(*a, **kw) + fdepr.__doc__ = warning return fdepr @@ -135,20 +136,20 @@ def fdepr(*a, **kw): # 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. -extensions_dict = { "mp4": {'type':'video', 'codec':['libx264','libmpeg4', 'aac']}, - 'ogv': {'type':'video', 'codec':['libtheora']}, - 'webm': {'type':'video', 'codec':['libvpx']}, - 'avi': {'type':'video'}, - 'mov': {'type':'video'}, - - 'ogg': {'type':'audio', 'codec':['libvorbis']}, - 'mp3': {'type':'audio', 'codec':['libmp3lame']}, - 'wav': {'type':'audio', 'codec':['pcm_s16le', 'pcm_s24le', 'pcm_s32le']}, - 'm4a': {'type':'audio', 'codec':['libfdk_aac']} - } +extensions_dict = { + "mp4": {"type": "video", "codec": ["libx264", "libmpeg4", "aac"]}, + "ogv": {"type": "video", "codec": ["libtheora"]}, + "webm": {"type": "video", "codec": ["libvpx"]}, + "avi": {"type": "video"}, + "mov": {"type": "video"}, + "ogg": {"type": "audio", "codec": ["libvorbis"]}, + "mp3": {"type": "audio", "codec": ["libmp3lame"]}, + "wav": {"type": "audio", "codec": ["pcm_s16le", "pcm_s24le", "pcm_s32le"]}, + "m4a": {"type": "audio", "codec": ["libfdk_aac"]}, +} for ext in ["jpg", "jpeg", "png", "bmp", "tiff"]: - extensions_dict[ext] = {'type':'image'} + extensions_dict[ext] = {"type": "image"} def find_extension(codec): @@ -156,8 +157,8 @@ def find_extension(codec): # codec is already the extension return codec - for ext,infos in extensions_dict.items(): - if codec in infos.get('codec', []): + for ext, infos in extensions_dict.items(): + if codec in infos.get("codec", []): return ext raise ValueError( "The audio_codec you chose is unknown by MoviePy. " diff --git a/moviepy/utils.py b/moviepy/utils.py index 705cb495e..0a5962726 100644 --- a/moviepy/utils.py +++ b/moviepy/utils.py @@ -4,15 +4,16 @@ CLIP_TYPES = { - 'audio': AudioFileClip, - 'video': VideoFileClip, - 'image': ImageClip, + "audio": AudioFileClip, + "video": VideoFileClip, + "image": ImageClip, } -def close_all_clips(objects='globals', types=('audio', 'video', 'image')): - if objects == 'globals': + +def close_all_clips(objects="globals", types=("audio", "video", "image")): + if objects == "globals": objects = globals() - if hasattr(objects, 'values'): + if hasattr(objects, "values"): objects = objects.values() types_tuple = tuple(CLIP_TYPES[key] for key in types) for obj in objects: diff --git a/moviepy/video/VideoClip.py b/moviepy/video/VideoClip.py index 92a2a7452..6021d791a 100644 --- a/moviepy/video/VideoClip.py +++ b/moviepy/video/VideoClip.py @@ -15,14 +15,23 @@ from ..Clip import Clip from ..config import get_setting -from ..decorators import (add_mask_if_none, apply_to_mask, - convert_masks_to_RGB, convert_to_seconds, outplace, - requires_duration, use_clip_fps_by_default) -from ..tools import (deprecated_version_of, extensions_dict, find_extension, - subprocess_call) +from ..decorators import ( + add_mask_if_none, + apply_to_mask, + convert_masks_to_RGB, + convert_to_seconds, + outplace, + requires_duration, + use_clip_fps_by_default, +) +from ..tools import ( + deprecated_version_of, + extensions_dict, + find_extension, + subprocess_call, +) from .io.ffmpeg_writer import ffmpeg_write_video -from .io.gif_writers import (write_gif, write_gif_with_image_io, - write_gif_with_tempfiles) +from .io.gif_writers import write_gif, write_gif_with_image_io, write_gif_with_tempfiles from .tools.drawing import blit @@ -73,8 +82,9 @@ class VideoClip(Clip): """ - def __init__(self, make_frame=None, ismask=False, duration=None, - has_constant_size=True): + def __init__( + self, make_frame=None, ismask=False, duration=None, has_constant_size=True + ): Clip.__init__(self) self.mask = None self.audio = None @@ -84,7 +94,7 @@ def __init__(self, make_frame=None, ismask=False, duration=None, self.make_frame = make_frame self.size = self.get_frame(0).shape[:2][::-1] self.ismask = ismask - self.has_constant_size=has_constant_size + self.has_constant_size = has_constant_size if duration is not None: self.duration = duration self.end = duration @@ -104,7 +114,7 @@ def aspect_ratio(self): # =============================================================== # EXPORT OPERATIONS - @convert_to_seconds(['t']) + @convert_to_seconds(["t"]) @convert_masks_to_RGB def save_frame(self, filename, t=0, withmask=True): """ Save a clip's frame to an image file. @@ -121,7 +131,7 @@ def save_frame(self, filename, t=0, withmask=True): im = self.get_frame(t) if withmask and self.mask is not None: mask = 255 * self.mask.get_frame(t) - im = np.dstack([im, mask]).astype('uint8') + im = np.dstack([im, mask]).astype("uint8") else: im = im.astype("uint8") @@ -130,16 +140,28 @@ def save_frame(self, filename, t=0, withmask=True): @requires_duration @use_clip_fps_by_default @convert_masks_to_RGB - def write_videofile(self, filename, fps=None, codec=None, - bitrate=None, audio=True, audio_fps=44100, - preset="medium", - audio_nbytes=4, audio_codec=None, - audio_bitrate=None, audio_bufsize=2000, - temp_audiofile=None, - rewrite_audio=True, remove_temp=True, - write_logfile=False, verbose=True, - threads=None, ffmpeg_params=None, - logger='bar'): + def write_videofile( + self, + filename, + fps=None, + codec=None, + bitrate=None, + audio=True, + audio_fps=44100, + preset="medium", + audio_nbytes=4, + audio_codec=None, + audio_bitrate=None, + audio_bufsize=2000, + temp_audiofile=None, + rewrite_audio=True, + remove_temp=True, + write_logfile=False, + verbose=True, + threads=None, + ffmpeg_params=None, + logger="bar", + ): """Write the clip to a videofile. Parameters @@ -258,52 +280,66 @@ def write_videofile(self, filename, fps=None, codec=None, if codec is None: try: - codec = extensions_dict[ext]['codec'][0] + codec = extensions_dict[ext]["codec"][0] except KeyError: - raise ValueError("MoviePy couldn't find the codec associated " - "with the filename. Provide the 'codec' " - "parameter in write_videofile.") + raise ValueError( + "MoviePy couldn't find the codec associated " + "with the filename. Provide the 'codec' " + "parameter in write_videofile." + ) if audio_codec is None: - if ext in ['ogv', 'webm']: - audio_codec = 'libvorbis' + if ext in ["ogv", "webm"]: + audio_codec = "libvorbis" else: - audio_codec = 'libmp3lame' - elif audio_codec == 'raw16': - audio_codec = 'pcm_s16le' - elif audio_codec == 'raw32': - audio_codec = 'pcm_s32le' + audio_codec = "libmp3lame" + elif audio_codec == "raw16": + audio_codec = "pcm_s16le" + elif audio_codec == "raw32": + audio_codec = "pcm_s32le" audiofile = audio if isinstance(audio, str) else None - make_audio = ((audiofile is None) and (audio == True) and - (self.audio is not None)) + make_audio = ( + (audiofile is None) and (audio == True) and (self.audio is not None) + ) if make_audio and temp_audiofile: # The audio will be the clip's audio audiofile = temp_audiofile elif make_audio: audio_ext = find_extension(audio_codec) - audiofile = (name + Clip._TEMP_FILES_PREFIX + "wvf_snd.%s" % audio_ext) + audiofile = name + Clip._TEMP_FILES_PREFIX + "wvf_snd.%s" % audio_ext # enough cpu for multiprocessing ? USELESS RIGHT NOW, WILL COME AGAIN # enough_cpu = (multiprocessing.cpu_count() > 1) logger(message="Moviepy - Building video %s." % filename) if make_audio: - self.audio.write_audiofile(audiofile, audio_fps, - audio_nbytes, audio_bufsize, - audio_codec, bitrate=audio_bitrate, - write_logfile=write_logfile, - verbose=verbose, - logger=logger) - - ffmpeg_write_video(self, filename, fps, codec, - bitrate=bitrate, - preset=preset, - write_logfile=write_logfile, - audiofile=audiofile, - verbose=verbose, threads=threads, - ffmpeg_params=ffmpeg_params, - logger=logger) + self.audio.write_audiofile( + audiofile, + audio_fps, + audio_nbytes, + audio_bufsize, + audio_codec, + bitrate=audio_bitrate, + write_logfile=write_logfile, + verbose=verbose, + logger=logger, + ) + + ffmpeg_write_video( + self, + filename, + fps, + codec, + bitrate=bitrate, + preset=preset, + write_logfile=write_logfile, + audiofile=audiofile, + verbose=verbose, + threads=threads, + ffmpeg_params=ffmpeg_params, + logger=logger, + ) if remove_temp and make_audio: if os.path.exists(audiofile): @@ -313,8 +349,9 @@ def write_videofile(self, filename, fps=None, codec=None, @requires_duration @use_clip_fps_by_default @convert_masks_to_RGB - def write_images_sequence(self, nameformat, fps=None, verbose=True, - withmask=True, logger='bar'): + def write_images_sequence( + self, nameformat, fps=None, verbose=True, withmask=True, logger="bar" + ): """ Writes the videoclip to a sequence of image files. Parameters @@ -355,7 +392,7 @@ def write_images_sequence(self, nameformat, fps=None, verbose=True, """ logger = proglog.default_bar_logger(logger) - logger(message='Moviepy - Writing frames %s.' % nameformat) + logger(message="Moviepy - Writing frames %s." % nameformat) tt = np.arange(0, self.duration, 1.0 / fps) @@ -364,16 +401,26 @@ def write_images_sequence(self, nameformat, fps=None, verbose=True, name = nameformat % i filenames.append(name) self.save_frame(name, t, withmask=withmask) - logger(message='Moviepy - Done writing frames %s.' % nameformat) + logger(message="Moviepy - Done writing frames %s." % nameformat) return filenames @requires_duration @convert_masks_to_RGB - def write_gif(self, filename, fps=None, program='imageio', - opt='nq', fuzz=1, verbose=True, - loop=0, dispose=False, colors=None, tempfiles=False, - logger='bar'): + def write_gif( + self, + filename, + fps=None, + program="imageio", + opt="nq", + fuzz=1, + verbose=True, + loop=0, + dispose=False, + colors=None, + tempfiles=False, + logger="bar", + ): """ Write the VideoClip to a GIF file. Converts a VideoClip into an animated GIF using ImageMagick @@ -427,27 +474,51 @@ def write_gif(self, filename, fps=None, program='imageio', # A little sketchy at the moment, maybe move all that in write_gif, # refactor a little... we will see. - if program == 'imageio': - write_gif_with_image_io(self, filename, fps=fps, opt=opt, loop=loop, - verbose=verbose, colors=colors, - logger=logger) + if program == "imageio": + write_gif_with_image_io( + self, + filename, + fps=fps, + opt=opt, + loop=loop, + verbose=verbose, + colors=colors, + logger=logger, + ) elif tempfiles: # convert imageio opt variable to something that can be used with # ImageMagick - opt = 'optimizeplus' if opt == 'nq' else 'OptimizeTransparency' - write_gif_with_tempfiles(self, filename, fps=fps, - program=program, opt=opt, fuzz=fuzz, - verbose=verbose, loop=loop, - dispose=dispose, colors=colors, - logger=logger) + opt = "optimizeplus" if opt == "nq" else "OptimizeTransparency" + write_gif_with_tempfiles( + self, + filename, + fps=fps, + program=program, + opt=opt, + fuzz=fuzz, + verbose=verbose, + loop=loop, + dispose=dispose, + colors=colors, + logger=logger, + ) else: # convert imageio opt variable to something that can be used with # ImageMagick - opt = 'optimizeplus' if opt == 'nq' else 'OptimizeTransparency' - write_gif(self, filename, fps=fps, program=program, - opt=opt, fuzz=fuzz, verbose=verbose, loop=loop, - dispose=dispose, colors=colors, - logger=logger) + opt = "optimizeplus" if opt == "nq" else "OptimizeTransparency" + write_gif( + self, + filename, + fps=fps, + program=program, + opt=opt, + fuzz=fuzz, + verbose=verbose, + loop=loop, + dispose=dispose, + colors=colors, + logger=logger, + ) # ----------------------------------------------------------------- # F I L T E R I N G @@ -497,12 +568,12 @@ def fill_array(self, pre_array, shape=(0, 0)): dy = shape[1] - pre_shape[1] post_array = pre_array if dx < 0: - post_array = pre_array[:shape[0]] + post_array = pre_array[: shape[0]] elif dx > 0: x_1 = [[[1, 1, 1]] * pre_shape[1]] * dx post_array = np.vstack((pre_array, x_1)) if dy < 0: - post_array = post_array[:, :shape[1]] + post_array = post_array[:, : shape[1]] elif dy > 0: x_1 = [[[1, 1, 1]] * dy] * post_array.shape[0] post_array = np.hstack((post_array, x_1)) @@ -524,9 +595,11 @@ def blit_on(self, picture, t): # GET IMAGE AND MASK IF ANY img = self.get_frame(ct) - mask = self.mask.get_frame(ct) if self.mask else None - - if mask is not None and ((img.shape[0] != mask.shape[0]) or (img.shape[1] != mask.shape[1])): + mask = self.mask.get_frame(ct) if self.mask else None + + if mask is not None and ( + (img.shape[0] != mask.shape[0]) or (img.shape[1] != mask.shape[1]) + ): img = self.fill_array(img, mask.shape) hi, wi = img.shape[:2] @@ -536,11 +609,13 @@ def blit_on(self, picture, t): # preprocess short writings of the position if isinstance(pos, str): - pos = {'center': ['center', 'center'], - 'left': ['left', 'center'], - 'right': ['right', 'center'], - 'top': ['center', 'top'], - 'bottom': ['center', 'bottom']}[pos] + pos = { + "center": ["center", "center"], + "left": ["left", "center"], + "right": ["right", "center"], + "top": ["center", "top"], + "bottom": ["center", "bottom"], + }[pos] else: pos = list(pos) @@ -551,11 +626,11 @@ def blit_on(self, picture, t): pos[i] = dim * pos[i] if isinstance(pos[0], str): - D = {'left': 0, 'center': (wf - wi) / 2, 'right': wf - wi} + D = {"left": 0, "center": (wf - wi) / 2, "right": wf - wi} pos[0] = D[pos[0]] if isinstance(pos[1], str): - D = {'top': 0, 'center': (hf - hi) / 2, 'bottom': hf - hi} + D = {"top": 0, "center": (hf - hi) / 2, "bottom": hf - hi} pos[1] = D[pos[1]] pos = map(int, pos) @@ -580,8 +655,7 @@ def add_mask(self): mask = VideoClip(ismask=True, make_frame=make_frame) return self.set_mask(mask.set_duration(self.duration)) - def on_color(self, size=None, color=(0, 0, 0), pos=None, - col_opacity=None): + def on_color(self, size=None, color=(0, 0, 0), pos=None, col_opacity=None): """Place the clip on a colored background. Returns a clip made of the current clip overlaid on a color @@ -611,20 +685,24 @@ def on_color(self, size=None, color=(0, 0, 0), pos=None, if size is None: size = self.size if pos is None: - pos = 'center' + pos = "center" colorclip = ColorClip(size, color=color) if col_opacity is not None: - colorclip = (ColorClip(size, color=color, duration=self.duration) - .set_opacity(col_opacity)) + colorclip = ColorClip( + size, color=color, duration=self.duration + ).set_opacity(col_opacity) result = CompositeVideoClip([colorclip, self.set_position(pos)]) else: - result = CompositeVideoClip([self.set_position(pos)], - size=size, - bg_color=color) - - if (isinstance(self, ImageClip) and (not hasattr(pos, "__call__")) - and ((self.mask is None) or isinstance(self.mask, ImageClip))): + result = CompositeVideoClip( + [self.set_position(pos)], size=size, bg_color=color + ) + + if ( + isinstance(self, ImageClip) + and (not hasattr(pos, "__call__")) + and ((self.mask is None) or isinstance(self.mask, ImageClip)) + ): new_result = result.to_ImageClip() if result.mask is not None: new_result.mask = result.mask.to_ImageClip() @@ -697,7 +775,7 @@ def set_position(self, pos, relative=False): """ self.relative_pos = relative - if hasattr(pos, '__call__'): + if hasattr(pos, "__call__"): self.pos = pos else: self.pos = lambda t: pos @@ -705,15 +783,14 @@ def set_position(self, pos, relative=False): # -------------------------------------------------------------- # CONVERSIONS TO OTHER TYPES - @convert_to_seconds(['t']) + @convert_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) + newclip = ImageClip(self.get_frame(t), ismask=self.ismask, duration=duration) if with_mask and self.mask is not None: newclip.mask = self.mask.to_ImageClip(t) return newclip @@ -723,15 +800,14 @@ def to_mask(self, canal=0): if self.ismask: return self else: - newclip = self.fl_image(lambda pic: - 1.0 * pic[:, :, canal] / 255) + newclip = self.fl_image(lambda pic: 1.0 * pic[:, :, canal] / 255) newclip.ismask = True return newclip def to_RGB(self): """Return a non-mask video clip made from the mask video clip.""" if self.ismask: - f = lambda pic: np.dstack(3 * [255 * pic]).astype('uint8') + f = lambda pic: np.dstack(3 * [255 * pic]).astype("uint8") newclip = self.fl_image(f) newclip.ismask = False return newclip @@ -780,15 +856,18 @@ 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, ismask=False, has_constant_size=True): self.data = data self.data_to_frame = data_to_frame self.fps = fps - make_frame = lambda t: self.data_to_frame(self.data[int(self.fps*t)]) - VideoClip.__init__(self, make_frame, ismask=ismask, - duration=1.0*len(data)/fps, - has_constant_size=has_constant_size) + make_frame = lambda t: self.data_to_frame(self.data[int(self.fps * t)]) + VideoClip.__init__( + self, + make_frame, + ismask=ismask, + duration=1.0 * len(data) / fps, + has_constant_size=has_constant_size, + ) class UpdatedVideoClip(VideoClip): @@ -832,8 +911,9 @@ def make_frame(t): world.update() return world.to_frame() - VideoClip.__init__(self, make_frame=make_frame, - ismask=ismask, duration=duration) + VideoClip.__init__( + self, make_frame=make_frame, ismask=ismask, duration=duration + ) """--------------------------------------------------------------------- @@ -880,8 +960,9 @@ class ImageClip(VideoClip): """ - def __init__(self, img, ismask=False, transparent=True, - fromalpha=False, duration=None): + def __init__( + self, img, ismask=False, transparent=True, fromalpha=False, duration=None + ): VideoClip.__init__(self, ismask=ismask, duration=duration) if isinstance(img, str): @@ -895,8 +976,7 @@ def __init__(self, img, ismask=False, transparent=True, elif ismask: 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, ismask=True) img = img[:, :, :3] elif ismask: img = 1.0 * img[:, :, 0] / 255 @@ -917,8 +997,7 @@ def fl(self, fl, apply_to=None, keep_duration=True): apply_to = [] # When we use fl on an image clip it may become animated. # Therefore the result is not an ImageClip, just a VideoClip. - newclip = VideoClip.fl(self, fl, apply_to=apply_to, - keep_duration=keep_duration) + newclip = VideoClip.fl(self, fl, apply_to=apply_to, keep_duration=keep_duration) newclip.__class__ = VideoClip return newclip @@ -931,7 +1010,7 @@ def fl_image(self, image_func, apply_to=None): and not for each 'frame'. """ if apply_to is None: - apply_to = [] + apply_to = [] arr = image_func(self.get_frame(0)) self.size = arr.shape[:2][::-1] self.make_frame = lambda t: arr @@ -944,8 +1023,7 @@ def fl_image(self, image_func, apply_to=None): setattr(self, attr, new_a) @outplace - def fl_time(self, time_func, apply_to=None, - keep_duration=False): + def fl_time(self, time_func, apply_to=None, keep_duration=False): """Time-transformation filter. Applies a transformation to the clip's timeline @@ -955,7 +1033,7 @@ def fl_time(self, time_func, apply_to=None, masks or their audios). The result is still an ImageClip. """ if apply_to is None: - apply_to = ['mask', 'audio'] + apply_to = ["mask", "audio"] for attr in apply_to: a = getattr(self, attr, None) if a is not None: @@ -968,13 +1046,14 @@ def fl_time(self, time_func, apply_to=None, # The old functions to_videofile, to_gif, to_images sequences have been # replaced by the more explicite write_videofile, write_gif, etc. -VideoClip.set_pos = deprecated_version_of(VideoClip.set_position, - 'set_pos') -VideoClip.to_videofile = deprecated_version_of(VideoClip.write_videofile, - 'to_videofile') -VideoClip.to_gif = deprecated_version_of(VideoClip.write_gif, 'to_gif') -VideoClip.to_images_sequence = deprecated_version_of(VideoClip.write_images_sequence, - 'to_images_sequence') +VideoClip.set_pos = deprecated_version_of(VideoClip.set_position, "set_pos") +VideoClip.to_videofile = deprecated_version_of( + VideoClip.write_videofile, "to_videofile" +) +VideoClip.to_gif = deprecated_version_of(VideoClip.write_gif, "to_gif") +VideoClip.to_images_sequence = deprecated_version_of( + VideoClip.write_images_sequence, "to_images_sequence" +) class ColorClip(ImageClip): @@ -1000,18 +1079,24 @@ class ColorClip(ImageClip): def __init__(self, size, color=None, ismask=False, duration=None, col=None): if col is not None: - warnings.warn("The `ColorClip` parameter `col` has been deprecated." - " Please use `color` instead.", DeprecationWarning) + warnings.warn( + "The `ColorClip` parameter `col` has been deprecated." + " Please use `color` instead.", + DeprecationWarning, + ) if color is not None: - warnings.warn("The arguments `color` and `col` have both been " - "passed to `ColorClip` so `col` has been ignored.", - UserWarning) + warnings.warn( + "The arguments `color` and `col` have both been " + "passed to `ColorClip` so `col` has been ignored.", + UserWarning, + ) else: color = col w, h = size shape = (h, w) if np.isscalar(color) else (h, w, len(color)) - ImageClip.__init__(self, np.tile(color, w * h).reshape(shape), - ismask=ismask, duration=duration) + ImageClip.__init__( + self, np.tile(color, w * h).reshape(shape), ismask=ismask, duration=duration + ) class TextClip(ImageClip): @@ -1078,43 +1163,63 @@ class TextClip(ImageClip): """ - def __init__(self, txt=None, filename=None, size=None, color='black', - bg_color='transparent', fontsize=None, font='Courier', - stroke_color=None, stroke_width=1, method='label', - kerning=None, align='center', interline=None, - tempfilename=None, temptxt=None, - transparent=True, remove_temp=True, - print_cmd=False): + def __init__( + self, + txt=None, + filename=None, + size=None, + color="black", + bg_color="transparent", + fontsize=None, + font="Courier", + stroke_color=None, + stroke_width=1, + method="label", + kerning=None, + align="center", + interline=None, + tempfilename=None, + temptxt=None, + transparent=True, + remove_temp=True, + print_cmd=False, + ): if txt is not None: if temptxt is None: - temptxt_fd, temptxt = tempfile.mkstemp(suffix='.txt') + 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(txt, "UTF8")) except TypeError: # oops, fall back to Python2 os.write(temptxt_fd, txt) os.close(temptxt_fd) - txt = '@' + temptxt + txt = "@" + temptxt else: # use a file instead of a text. txt = "@%" + filename if size is not None: - size = ('' if size[0] is None else str(size[0]), - '' if size[1] is None else str(size[1])) - - cmd = ([get_setting("IMAGEMAGICK_BINARY"), - "-background", bg_color, - "-fill", color, - "-font", font]) + size = ( + "" if size[0] is None else str(size[0]), + "" if size[1] is None else str(size[1]), + ) + + cmd = [ + get_setting("IMAGEMAGICK_BINARY"), + "-background", + bg_color, + "-fill", + color, + "-font", + font, + ] if fontsize is not None: cmd += ["-pointsize", "%d" % fontsize] if kerning is not None: cmd += ["-kerning", "%0.1f" % kerning] if stroke_color is not None: - cmd += ["-stroke", stroke_color, "-strokewidth", - "%.01f" % stroke_width] + cmd += ["-stroke", stroke_color, "-strokewidth", "%.01f" % stroke_width] if size is not None: cmd += ["-size", "%sx%s" % (size[0], size[1])] if align is not None: @@ -1123,11 +1228,15 @@ def __init__(self, txt=None, filename=None, size=None, color='black', cmd += ["-interline-spacing", "%d" % interline] if tempfilename is None: - tempfile_fd, tempfilename = tempfile.mkstemp(suffix='.png') + tempfile_fd, tempfilename = tempfile.mkstemp(suffix=".png") os.close(tempfile_fd) - cmd += ["%s:%s" % (method, txt), - "-type", "truecolormatte", "PNG32:%s" % tempfilename] + cmd += [ + "%s:%s" % (method, txt), + "-type", + "truecolormatte", + "PNG32:%s" % tempfilename, + ] if print_cmd: print(" ".join(cmd)) @@ -1135,13 +1244,16 @@ def __init__(self, txt=None, filename=None, size=None, color='black', try: subprocess_call(cmd, logger=None) except (IOError, OSError) as err: - error = ("MoviePy Error: creation of %s failed because of the " - "following error:\n\n%s.\n\n." % (filename, str(err)) - + ("This error can be due to the fact that ImageMagick " - "is not installed on your computer, or (for Windows " - "users) that you didn't specify the path to the " - "ImageMagick binary in file conf.py, or that the path " - "you specified is incorrect")) + error = "MoviePy Error: creation of %s failed because of the " "following error:\n\n%s.\n\n." % ( + filename, + str(err), + ) + ( + "This error can be due to the fact that ImageMagick " + "is not installed on your computer, or (for Windows " + "users) that you didn't specify the path to the " + "ImageMagick binary in file conf.py, or that the path " + "you specified is incorrect" + ) raise IOError(error) ImageClip.__init__(self, tempfilename, transparent=transparent) @@ -1160,25 +1272,23 @@ def list(arg): """Returns the list of all valid entries for the argument of ``TextClip`` given (can be ``font``, ``color``, etc...) """ - popen_params = {"stdout": sp.PIPE, - "stderr": sp.DEVNULL, - "stdin": sp.DEVNULL} + popen_params = {"stdout": sp.PIPE, "stderr": sp.DEVNULL, "stdin": sp.DEVNULL} if os.name == "nt": popen_params["creationflags"] = 0x08000000 - process = sp.Popen([get_setting("IMAGEMAGICK_BINARY"), - '-list', arg], **popen_params) + process = sp.Popen( + [get_setting("IMAGEMAGICK_BINARY"), "-list", arg], **popen_params + ) result = process.communicate()[0] lines = result.splitlines() - if arg == 'font': - return [l.decode('UTF-8')[8:] for l in lines if l.startswith(b" Font:")] - elif arg == 'color': + if arg == "font": + return [l.decode("UTF-8")[8:] for l in lines if l.startswith(b" Font:")] + elif arg == "color": return [l.split(b" ")[0] for l in lines[2:]] else: - raise Exception("Moviepy:Error! Argument must equal " - "'font' or 'color'") + raise Exception("Moviepy:Error! Argument must equal " "'font' or 'color'") @staticmethod def search(string, arg): diff --git a/moviepy/video/compositing/CompositeVideoClip.py b/moviepy/video/compositing/CompositeVideoClip.py index 080cd3779..0cef7ce09 100644 --- a/moviepy/video/compositing/CompositeVideoClip.py +++ b/moviepy/video/compositing/CompositeVideoClip.py @@ -5,6 +5,7 @@ # CompositeVideoClip + class CompositeVideoClip(VideoClip): """ @@ -45,26 +46,24 @@ 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, ismask=False): if size is None: size = clips[0].size - if use_bgclip and (clips[0].mask is None): transparent = False else: - transparent = (bg_color is None) - + transparent = bg_color is None + if bg_color is None: bg_color = 0.0 if ismask else (0, 0, 0) - fpss = [c.fps for c in clips if getattr(c, 'fps', None)] + fpss = [c.fps for c in clips if getattr(c, "fps", None)] self.fps = max(fpss) if fpss else None VideoClip.__init__(self) - + self.size = size self.ismask = ismask self.clips = clips @@ -79,7 +78,6 @@ def __init__(self, clips, size=None, bg_color=None, use_bgclip=False, self.bg = ColorClip(size, color=self.bg_color) self.created_bg = True - # compute duration ends = [c.end for c in self.clips] if None not in ends: @@ -94,13 +92,17 @@ def __init__(self, clips, size=None, bg_color=None, use_bgclip=False, # 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) - for c in self.clips] - - self.mask = CompositeVideoClip(maskclips,self.size, ismask=True, - bg_color=0.0) + 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) + for c in self.clips + ] + + self.mask = CompositeVideoClip( + maskclips, self.size, ismask=True, bg_color=0.0 + ) def make_frame(t): """ The clips playing at time `t` are blitted over one @@ -108,7 +110,7 @@ def make_frame(t): f = self.bg.get_frame(t) for c in self.playing_clips(t): - f = c.blit_on(f, t) + f = c.blit_on(f, t) return f self.make_frame = make_frame @@ -129,9 +131,7 @@ def close(self): self.audio = None - -def clips_array(array, rows_widths=None, cols_widths=None, - bg_color = None): +def clips_array(array, rows_widths=None, cols_widths=None, bg_color=None): """ @@ -148,29 +148,28 @@ def clips_array(array, rows_widths=None, cols_widths=None, regions to be transparent (will be slower). """ - + array = np.array(array) sizes_array = np.array([[c.size for c in line] for line in array]) - + # find row width and col_widths automatically if not provided if rows_widths is None: - rows_widths = sizes_array[:,:,1].max(axis=1) + rows_widths = sizes_array[:, :, 1].max(axis=1) 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)) - + cols_widths = sizes_array[:, :, 0].max(axis=0) + + xx = np.cumsum([0] + list(cols_widths)) + yy = 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)): 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 = CompositeVideoClip( + [clip.set_position("center")], size=(cw, rw), bg_color=bg_color + ).set_duration(clip.duration) + array[i, j] = clip.set_position((x, y)) - + return CompositeVideoClip(array.flatten(), size=(xx[-1], yy[-1]), bg_color=bg_color) diff --git a/moviepy/video/compositing/concatenate.py b/moviepy/video/compositing/concatenate.py index a59e974bb..0bc99b88e 100644 --- a/moviepy/video/compositing/concatenate.py +++ b/moviepy/video/compositing/concatenate.py @@ -8,8 +8,9 @@ from moviepy.video.VideoClip import ColorClip, VideoClip -def concatenate_videoclips(clips, method="chain", transition=None, - bg_color=None, ismask=False, padding = 0): +def concatenate_videoclips( + clips, method="chain", transition=None, bg_color=None, ismask=False, padding=0 +): """ Concatenates several video clips Returns a video clip made by clip by concatenating several video clips. @@ -74,6 +75,7 @@ def concatenate_videoclips(clips, method="chain", transition=None, tt = np.maximum(0, tt + padding * np.arange(len(tt))) 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]) @@ -81,37 +83,39 @@ def make_frame(t): def get_mask(c): mask = c.mask or ColorClip([1, 1], color=1, ismask=True) if mask.duration is None: - mask.duration = c.duration + mask.duration = c.duration return mask - result = VideoClip(ismask = ismask, make_frame = make_frame) + 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.mask = concatenate_videoclips(masks, method="chain", ismask=True) result.clips = clips elif method == "compose": - result = CompositeVideoClip( [c.set_start(t).set_position('center') - for (c, t) in zip(clips, tt)], - size = (w, h), bg_color=bg_color, ismask=ismask) + result = CompositeVideoClip( + [c.set_start(t).set_position("center") for (c, t) in zip(clips, tt)], + size=(w, h), + bg_color=bg_color, + ismask=ismask, + ) else: - raise Exception("Moviepy Error: The 'method' argument of " - "concatenate_videoclips must be 'chain' or 'compose'") + raise Exception( + "Moviepy Error: The 'method' argument of " + "concatenate_videoclips must be 'chain' or 'compose'" + ) result.tt = tt result.start_times = tt[:-1] - result.start, result.duration, result.end = 0, tt[-1] , tt[-1] + result.start, result.duration, result.end = 0, tt[-1], tt[-1] - audio_t = [(c.audio, t) for c, t in zip(clips,tt) if c.audio is not None] + audio_t = [(c.audio, t) for c, t in zip(clips, tt) if c.audio is not None] if audio_t: - result.audio = CompositeAudioClip([a.set_start(t) - for a,t in audio_t]) + result.audio = CompositeAudioClip([a.set_start(t) for a, t in audio_t]) - fpss = [c.fps for c in clips if getattr(c, 'fps', None) is not None] + fpss = [c.fps for c in clips if getattr(c, "fps", None) is not None] result.fps = max(fpss) if fpss else None return result -concatenate = deprecated_version_of(concatenate_videoclips, - oldname="concatenate") +concatenate = deprecated_version_of(concatenate_videoclips, oldname="concatenate") diff --git a/moviepy/video/compositing/on_color.py b/moviepy/video/compositing/on_color.py index c5fcb147b..86ceb3989 100644 --- a/moviepy/video/compositing/on_color.py +++ b/moviepy/video/compositing/on_color.py @@ -14,14 +14,15 @@ def on_color(clip, size=None, color=(0, 0, 0), pos=None, col_opacity=None): :param pos: the position of the clip in the final clip. :param col_opacity: should the added zones be transparent ? """ - + if size is None: size = clip.size if pos is None: - pos = 'center' + pos = "center" colorclip = ColorClip(size, color=color) if col_opacity: colorclip = colorclip.with_mask().set_opacity(col_opacity) - return CompositeVideoClip([colorclip, clip.set_position(pos)], - transparent=(col_opacity is not None)) + return CompositeVideoClip( + [colorclip, clip.set_position(pos)], transparent=(col_opacity is not None) + ) diff --git a/moviepy/video/compositing/transitions.py b/moviepy/video/compositing/transitions.py index ab2920b23..649565bf4 100644 --- a/moviepy/video/compositing/transitions.py +++ b/moviepy/video/compositing/transitions.py @@ -65,10 +65,12 @@ def slide_in(clip, duration, side): """ w, h = clip.size - pos_dict = {'left': lambda t: (min(0, w*(t/duration-1)), 'center'), - 'right': lambda t: (max(0, w*(1-t/duration)), 'center'), - 'top': lambda t: ('center', min(0, h*(t/duration-1))), - 'bottom': lambda t: ('center', max(0, h*(1-t/duration)))} + pos_dict = { + "left": lambda t: (min(0, w * (t / duration - 1)), "center"), + "right": lambda t: (max(0, w * (1 - t / duration)), "center"), + "top": lambda t: ("center", min(0, h * (t / duration - 1))), + "bottom": lambda t: ("center", max(0, h * (1 - t / duration))), + } return clip.set_position(pos_dict[side]) @@ -106,10 +108,12 @@ def slide_out(clip, duration, side): w, h = clip.size ts = clip.duration - duration # start time of the effect. - pos_dict = {'left': lambda t: (min(0, w*(1-(t-ts)/duration)), 'center'), - 'right': lambda t: (max(0, w*((t-ts)/duration-1)), 'center'), - 'top': lambda t: ('center', min(0, h*(1-(t-ts)/duration))), - 'bottom': lambda t: ('center', max(0, h*((t-ts)/duration-1)))} + pos_dict = { + "left": lambda t: (min(0, w * (1 - (t - ts) / duration)), "center"), + "right": lambda t: (max(0, w * ((t - ts) / duration - 1)), "center"), + "top": lambda t: ("center", min(0, h * (1 - (t - ts) / duration))), + "bottom": lambda t: ("center", max(0, h * ((t - ts) / duration - 1))), + } return clip.set_position(pos_dict[side]) diff --git a/moviepy/video/fx/accel_decel.py b/moviepy/video/fx/accel_decel.py index 6fbb4cca5..955b46b5e 100644 --- a/moviepy/video/fx/accel_decel.py +++ b/moviepy/video/fx/accel_decel.py @@ -9,14 +9,15 @@ def f_accel_decel(t, old_d, new_d, abruptness=1, soonness=1.0): for positive abruptness, determines how soon the speedup occurs (0=.5)*f2(t) - - return old_d*_f((t/new_d)**soonness) + f1 = lambda t: (0.5) ** (1 - a) * (t ** a) + f2 = lambda t: (1 - f1(1 - t)) + return (t < 0.5) * f1(t) + (t >= 0.5) * f2(t) + + return old_d * _f((t / new_d) ** soonness) def accel_decel(clip, new_duration=None, abruptness=1.0, soonness=1.0): @@ -34,11 +35,10 @@ def accel_decel(clip, new_duration=None, abruptness=1.0, soonness=1.0): for positive abruptness, determines how soon the speedup occurs (0=duration: + if t >= duration: return gf(t) else: - fading = (1.0*t/duration) - return fading*gf(t) + (1-fading)*initial_color + fading = 1.0 * t / duration + return fading * gf(t) + (1 - fading) * initial_color return clip.fl(fl) diff --git a/moviepy/video/fx/fadeout.py b/moviepy/video/fx/fadeout.py index 66c7230a8..4b7e5723e 100644 --- a/moviepy/video/fx/fadeout.py +++ b/moviepy/video/fx/fadeout.py @@ -12,17 +12,17 @@ def fadeout(clip, duration, final_color=None): For cross-fading (progressive appearance or disappearance of a clip over another clip, see ``composition.crossfade`` """ - + if final_color is None: - final_color = 0 if clip.ismask else [0,0,0] - + final_color = 0 if clip.ismask else [0, 0, 0] + final_color = np.array(final_color) def fl(gf, t): - if (clip.duration-t)>=duration: + if (clip.duration - t) >= duration: return gf(t) else: fading = 1.0 * (clip.duration - t) / duration - return fading*gf(t) + (1-fading)*final_color + return fading * gf(t) + (1 - fading) * final_color return clip.fl(fl) diff --git a/moviepy/video/fx/freeze.py b/moviepy/video/fx/freeze.py index ff4d4aa8d..7d831d77d 100644 --- a/moviepy/video/fx/freeze.py +++ b/moviepy/video/fx/freeze.py @@ -4,8 +4,7 @@ @requires_duration -def freeze(clip, t=0, freeze_duration=None, total_duration=None, - padding_end=0): +def freeze(clip, t=0, freeze_duration=None, total_duration=None, padding_end=0): """ Momentarily freeze the clip at time t. Set `t='end'` to freeze the clip at the end (actually it will freeze on the @@ -16,14 +15,13 @@ def freeze(clip, t=0, freeze_duration=None, total_duration=None, automatically calculated). One of them must be provided. """ - if t=='end': + if t == "end": t = clip.duration - padding_end if freeze_duration is None: freeze_duration = total_duration - clip.duration - before = [clip.subclip(0,t)] if (t!=0) else [] + before = [clip.subclip(0, t)] if (t != 0) else [] freeze = [clip.to_ImageClip(t).set_duration(freeze_duration)] - after = [clip.subclip(t)] if (t !=clip.duration) else [] + after = [clip.subclip(t)] if (t != clip.duration) else [] return concatenate_videoclips(before + freeze + after) - \ No newline at end of file diff --git a/moviepy/video/fx/freeze_region.py b/moviepy/video/fx/freeze_region.py index a8ec7cf09..b5f03bae0 100644 --- a/moviepy/video/fx/freeze_region.py +++ b/moviepy/video/fx/freeze_region.py @@ -4,7 +4,7 @@ from .crop import crop -#@apply_to_mask +# @apply_to_mask def freeze_region(clip, t=0, region=None, outside_region=None, mask=None): """ Freezes one region of the clip while the rest remains animated. @@ -31,27 +31,25 @@ def freeze_region(clip, t=0, region=None, outside_region=None, mask=None): indicate the freezed region in the final picture. """ - + if region is not None: x1, y1, x2, y2 = region - freeze = (clip.fx(crop, *region) - .to_ImageClip(t=t) - .set_duration(clip.duration) - .set_position((x1,y1))) + freeze = ( + clip.fx(crop, *region) + .to_ImageClip(t=t) + .set_duration(clip.duration) + .set_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).set_position((x1, y1)) + freeze = clip.to_ImageClip(t=t).set_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).set_duration(clip.duration).set_mask(mask) return CompositeVideoClip([clip, freeze]) diff --git a/moviepy/video/fx/gamma_corr.py b/moviepy/video/fx/gamma_corr.py index 5668698ad..0a12b66e7 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): - corrected = (255*(1.0*im/255)**gamma) - return corrected.astype('uint8') - + corrected = 255 * (1.0 * im / 255) ** gamma + return corrected.astype("uint8") + return clip.fl_image(fl) diff --git a/moviepy/video/fx/headblur.py b/moviepy/video/fx/headblur.py index 2d46d5a77..e1e314122 100644 --- a/moviepy/video/fx/headblur.py +++ b/moviepy/video/fx/headblur.py @@ -1,17 +1,18 @@ import numpy as np -#------- CHECKING DEPENDENCIES ----------------------------------------- +# ------- CHECKING DEPENDENCIES ----------------------------------------- try: import cv2 + headblur_possible = True - if cv2.__version__ >= '3.0.0': - cv2.CV_AA=cv2.LINE_AA + if cv2.__version__ >= "3.0.0": + cv2.CV_AA = cv2.LINE_AA except: headblur_possible = False -#----------------------------------------------------------------------- +# ----------------------------------------------------------------------- -def headblur(clip,fx,fy,r_zone,r_blur=None): +def headblur(clip, fx, fy, r_zone, r_blur=None): """ Returns a filter that will blurr a moving part (a head ?) of the frames. The position of the blur at time t is @@ -21,38 +22,38 @@ def headblur(clip,fx,fy,r_zone,r_blur=None): Automatically deals with the case where part of the image goes offscreen. """ - - if r_blur is None: r_blur = 2*r_zone/3 - - def fl(gf,t): - + + if r_blur is None: + r_blur = 2 * r_zone / 3 + + def fl(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) - 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) - - mask = np.dstack(3*[(1.0/255)*mask]) - + 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) + 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) + + mask = np.dstack(3 * [(1.0 / 255) * mask]) + orig = im[y1:y2, x1:x2] - blurred = cv2.blur(orig,(r_blur, r_blur)) - im[y1:y2, x1:x2] = mask*blurred + (1-mask)*orig + blurred = cv2.blur(orig, (r_blur, r_blur)) + im[y1:y2, x1:x2] = mask * blurred + (1 - mask) * orig return im - - return clip.fl(fl) + return clip.fl(fl) -#------- OVERWRITE IF REQUIREMENTS NOT MET ----------------------------- +# ------- OVERWRITE IF REQUIREMENTS NOT MET ----------------------------- if not headblur_possible: doc = headblur.__doc__ - def headblur(clip,fx,fy,r_zone,r_blur=None): + + def headblur(clip, fx, fy, r_zone, r_blur=None): raise IOError("fx painting needs opencv") - + headblur.__doc__ = doc -#----------------------------------------------------------------------- +# ----------------------------------------------------------------------- diff --git a/moviepy/video/fx/invert_colors.py b/moviepy/video/fx/invert_colors.py index 0fb948eb3..db91d0636 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.ismask else 255 + return clip.fl_image(lambda f: maxi - f) diff --git a/moviepy/video/fx/loop.py b/moviepy/video/fx/loop.py index b0c04beb3..ca2b7bcdd 100644 --- a/moviepy/video/fx/loop.py +++ b/moviepy/video/fx/loop.py @@ -20,7 +20,7 @@ def loop(self, n=None, duration=None): """ result = self.fl_time(lambda t: t % self.duration) if n: - duration = n*self.duration + duration = n * self.duration if duration: result = result.set_duration(duration) return result diff --git a/moviepy/video/fx/lum_contrast.py b/moviepy/video/fx/lum_contrast.py index a1ae55cf2..4893ad151 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_thr=127): """ luminosity-contrast correction of a clip """ - + def fl_image(im): - im = 1.0*im # float conversion - corrected = im + lum + contrast*(im-float(contrast_thr)) + im = 1.0 * im # float conversion + corrected = im + lum + contrast * (im - float(contrast_thr)) corrected[corrected < 0] = 0 corrected[corrected > 255] = 255 - return corrected.astype('uint8') - + return corrected.astype("uint8") + return clip.fl_image(fl_image) diff --git a/moviepy/video/fx/make_loopable.py b/moviepy/video/fx/make_loopable.py index 485887c7f..01d3a08b6 100644 --- a/moviepy/video/fx/make_loopable.py +++ b/moviepy/video/fx/make_loopable.py @@ -6,9 +6,7 @@ def make_loopable(clip, cross): """ Makes the clip fade in progressively at its own end, this way it can be looped indefinitely. ``cross`` is the duration in seconds - of the fade-in. """ + 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, cross).set_start(d - cross) + return CompositeVideoClip([clip, clip2]).subclip(cross, d) diff --git a/moviepy/video/fx/margin.py b/moviepy/video/fx/margin.py index 20dd1930e..464bac5f8 100644 --- a/moviepy/video/fx/margin.py +++ b/moviepy/video/fx/margin.py @@ -5,8 +5,9 @@ @apply_to_mask -def margin(clip, mar=None, left=0, right=0, top=0, - bottom=0, color=(0, 0, 0), opacity = 1.0): +def margin( + clip, mar=None, left=0, right=0, top=0, bottom=0, color=(0, 0, 0), opacity=1.0 +): """ Draws an external margin all around the frame. @@ -28,31 +29,30 @@ def margin(clip, mar=None, left=0, right=0, top=0, if mar is not None: left = right = top = bottom = mar - - def make_bg(w,h): + + def make_bg(w, h): new_w, new_h = w + left + right, h + top + bottom if clip.ismask: shape = (new_h, new_w) - bg = ( np.tile(opacity, (new_h, new_w)) - .astype(float) - .reshape(shape)) + bg = np.tile(opacity, (new_h, new_w)).astype(float).reshape(shape) else: shape = (new_h, new_w, 3) bg = np.tile(color, (new_h, new_w)).reshape(shape) return bg - + 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) - + + 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) + else: - + def fl(gf, t): pic = gf(t) - h,w = pic.shape[:2] - im = make_bg(w,h) - im[top:top + h, left:left + w] = pic + h, w = pic.shape[:2] + im = make_bg(w, h) + im[top : top + h, left : left + w] = pic return im + return clip.fl(fl) diff --git a/moviepy/video/fx/mask_and.py b/moviepy/video/fx/mask_and.py index ccec602ab..b2daebb15 100644 --- a/moviepy/video/fx/mask_and.py +++ b/moviepy/video/fx/mask_and.py @@ -11,10 +11,9 @@ def mask_and(clip, other_clip): # To ensure that 'or' of two ImageClips will be an ImageClip. if isinstance(other_clip, ImageClip): - other_clip = other_clip.img + other_clip = other_clip.img if isinstance(other_clip, np.ndarray): - return clip.fl_image(lambda f : np.minimum(f, other_clip)) + return clip.fl_image(lambda f: np.minimum(f, other_clip)) else: - return clip.fl(lambda gf, t : np.minimum(gf(t), - other_clip.get_frame(t))) + return clip.fl(lambda gf, t: np.minimum(gf(t), other_clip.get_frame(t))) diff --git a/moviepy/video/fx/mask_color.py b/moviepy/video/fx/mask_color.py index fba87dda4..89b64dcc7 100644 --- a/moviepy/video/fx/mask_color.py +++ b/moviepy/video/fx/mask_color.py @@ -15,20 +15,20 @@ def mask_color(clip, color=None, thr=0, s=1): parametrized by s """ if color is None: - color = [0,0,0] + color = [0, 0, 0] color = np.array(color) def hill(x): if thr: - return x**s / (thr**s + x**s) + return x ** s / (thr ** s + x ** s) else: - return 1.0 * (x != 0) - - def flim(im): - return hill(np.sqrt(((im-color)**2).sum(axis=2))) - + return 1.0 * (x != 0) + + def flim(im): + return hill(np.sqrt(((im - color) ** 2).sum(axis=2))) + mask = clip.fl_image(flim) - mask.ismask= True + mask.ismask = True newclip = clip.set_mask(mask) return newclip diff --git a/moviepy/video/fx/mask_or.py b/moviepy/video/fx/mask_or.py index abe5e74f4..b93e9144f 100644 --- a/moviepy/video/fx/mask_or.py +++ b/moviepy/video/fx/mask_or.py @@ -11,10 +11,9 @@ def mask_or(clip, other_clip): # To ensure that 'or' of two ImageClips will be an ImageClip. if isinstance(other_clip, ImageClip): - other_clip = other_clip.img + other_clip = other_clip.img if isinstance(other_clip, np.ndarray): - return clip.fl_image(lambda f : np.maximum(f, other_clip)) + return clip.fl_image(lambda f: np.maximum(f, other_clip)) else: - return clip.fl(lambda gf, t : np.maximum(gf(t), - other_clip.get_frame(t))) + return clip.fl(lambda gf, t: np.maximum(gf(t), other_clip.get_frame(t))) diff --git a/moviepy/video/fx/mirror_x.py b/moviepy/video/fx/mirror_x.py index 3173dac5c..ceb850b2a 100644 --- a/moviepy/video/fx/mirror_x.py +++ b/moviepy/video/fx/mirror_x.py @@ -1,4 +1,3 @@ - -def mirror_x(clip, apply_to= "mask"): +def mirror_x(clip, apply_to="mask"): """ flips the clip horizontally (and its mask too, by default) """ - return clip.fl_image(lambda f: f[:,::-1], apply_to = apply_to) + return clip.fl_image(lambda f: f[:, ::-1], apply_to=apply_to) diff --git a/moviepy/video/fx/mirror_y.py b/moviepy/video/fx/mirror_y.py index 0a602e9fc..2d5b0502d 100644 --- a/moviepy/video/fx/mirror_y.py +++ b/moviepy/video/fx/mirror_y.py @@ -1,3 +1,3 @@ -def mirror_y(clip, apply_to= "mask"): +def mirror_y(clip, apply_to="mask"): """ flips the clip vertically (and its mask too, by default) """ - return clip.fl_image(lambda f : f[::-1], apply_to = apply_to) + return clip.fl_image(lambda f: f[::-1], apply_to=apply_to) diff --git a/moviepy/video/fx/painting.py b/moviepy/video/fx/painting.py index b41bac30d..538618f1f 100644 --- a/moviepy/video/fx/painting.py +++ b/moviepy/video/fx/painting.py @@ -1,4 +1,4 @@ -#------- CHECKING DEPENDENCIES ----------------------------------------- +# ------- CHECKING DEPENDENCIES ----------------------------------------- painting_possible = True try: from skimage.filter import sobel @@ -7,37 +7,37 @@ from scipy.ndimage.filters import sobel except: painting_possible = False -#----------------------------------------------------------------------- - +# ----------------------------------------------------------------------- import numpy as np -def to_painting(image,saturation = 1.4,black = 0.006): +def to_painting(image, saturation=1.4, black=0.006): """ transforms any photo into some kind of painting """ edges = sobel(image.mean(axis=2)) - darkening = black*(255*np.dstack(3*[edges])) - painting = saturation*image-darkening - return np.maximum(0,np.minimum(255,painting)).astype('uint8') - -def painting(clip, saturation = 1.4,black = 0.006): + darkening = black * (255 * np.dstack(3 * [edges])) + painting = saturation * image - darkening + return np.maximum(0, np.minimum(255, painting)).astype("uint8") + + +def painting(clip, saturation=1.4, black=0.006): """ Transforms any photo into some kind of painting. Saturation tells at which point the colors of the result should be flashy. ``black`` gives the anount of black lines wanted. Requires Scikit-image or Scipy installed. """ - return clip.fl_image(lambda im : to_painting(im,saturation,black)) - + return clip.fl_image(lambda im: to_painting(im, saturation, black)) -#------- OVERWRITE IF REQUIREMENTS NOT MET ----------------------------- +# ------- OVERWRITE IF REQUIREMENTS NOT MET ----------------------------- if not painting_possible: doc = painting.__doc__ + def painting(clip, newsize=None, height=None, width=None): raise IOError("fx painting needs scikit-image or scipy") - + painting.__doc__ = doc -#----------------------------------------------------------------------- +# ----------------------------------------------------------------------- diff --git a/moviepy/video/fx/resize.py b/moviepy/video/fx/resize.py index 8636e749a..bb7b178fd 100644 --- a/moviepy/video/fx/resize.py +++ b/moviepy/video/fx/resize.py @@ -2,10 +2,11 @@ try: # TRY USING OpenCV AS RESIZER - #raise ImportError #debugging + # raise ImportError #debugging import cv2 import numpy as np - def resizer (pic, newsize): + + def resizer(pic, newsize): lx, ly = int(newsize[0]), int(newsize[1]) if lx > pic.shape[1] or ly > pic.shape[0]: # For upsizing use linear for good quality & decent speed @@ -13,48 +14,45 @@ def resizer (pic, newsize): else: # For dowsizing use area to prevent aliasing interpolation = cv2.INTER_AREA - return cv2.resize(+pic.astype('uint8'), (lx, ly), - interpolation=interpolation) + return cv2.resize(+pic.astype("uint8"), (lx, ly), interpolation=interpolation) resizer.origin = "cv2" - + except ImportError: - - + try: # TRY USING PIL/PILLOW AS RESIZER from PIL import Image import numpy as np + def resizer(pic, newsize): newsize = list(map(int, newsize))[::-1] shape = pic.shape - if len(shape)==3: - newshape = (newsize[0],newsize[1], shape[2] ) + if len(shape) == 3: + newshape = (newsize[0], newsize[1], shape[2]) else: - newshape = (newsize[0],newsize[1]) - + newshape = (newsize[0], newsize[1]) + pilim = Image.fromarray(pic) resized_pil = pilim.resize(newsize[::-1], Image.ANTIALIAS) - #arr = np.fromstring(resized_pil.tostring(), dtype='uint8') - #arr.reshape(newshape) + # arr = np.fromstring(resized_pil.tostring(), dtype='uint8') + # arr.reshape(newshape) return np.array(resized_pil) - + resizer.origin = "PIL" - + except ImportError: # TRY USING SCIPY AS RESIZER try: from scipy.misc import imresize - resizer = lambda pic, newsize : imresize(pic, - map(int, newsize[::-1])) + + resizer = lambda pic, newsize: imresize(pic, map(int, newsize[::-1])) resizer.origin = "Scipy" - + except ImportError: resize_possible = False - - - - + + from moviepy.decorators import apply_to_mask @@ -90,64 +88,62 @@ def resize(clip, newsize=None, height=None, width=None, apply_to_mask=True): """ w, h = clip.size - + if newsize is not None: - + def trans_newsize(ns): - + if isinstance(ns, (int, float)): return [ns * w, ns * h] else: return ns - + if hasattr(newsize, "__call__"): - - newsize2 = lambda t : trans_newsize(newsize(t)) - + + newsize2 = lambda t: trans_newsize(newsize(t)) + if clip.ismask: - - fun = lambda gf,t: (1.0*resizer((255 * gf(t)).astype('uint8'), - newsize2(t))/255) + + fun = lambda gf, t: ( + 1.0 * resizer((255 * gf(t)).astype("uint8"), newsize2(t)) / 255 + ) else: - - fun = lambda gf,t: resizer(gf(t).astype('uint8'), - newsize2(t)) - - return clip.fl(fun, keep_duration=True, - apply_to= (["mask"] if apply_to_mask else [])) - + + fun = lambda gf, t: resizer(gf(t).astype("uint8"), newsize2(t)) + + return clip.fl( + fun, keep_duration=True, apply_to=(["mask"] if apply_to_mask else []) + ) + else: - + newsize = trans_newsize(newsize) - elif height is not None: - + if hasattr(height, "__call__"): - fun = lambda t : 1.0*int(height(t))/h + fun = lambda t: 1.0 * int(height(t)) / h return resize(clip, fun) - else: newsize = [w * height / h, height] - + elif width is not None: if hasattr(width, "__call__"): - fun = lambda t : 1.0*width(t)/w + fun = lambda t: 1.0 * width(t) / w return resize(clip, fun) - + newsize = [width, h * width / w] - - + # From here, the resizing is constant (not a function of time), size=newsize if clip.ismask: - fl = lambda pic: 1.0*resizer((255 * pic).astype('uint8'), newsize)/255.0 - + fl = lambda pic: 1.0 * resizer((255 * pic).astype("uint8"), newsize) / 255.0 + else: - fl = lambda pic: resizer(pic.astype('uint8'), newsize) + fl = lambda pic: resizer(pic.astype("uint8"), newsize) newclip = clip.fl_image(fl) @@ -158,8 +154,10 @@ def trans_newsize(ns): if not resize_possible: - + doc = resize.__doc__ + def resize(clip, newsize=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 da22268d0..e20623e70 100644 --- a/moviepy/video/fx/rotate.py +++ b/moviepy/video/fx/rotate.py @@ -4,14 +4,20 @@ try: from PIL import Image + PIL_FOUND = True + def pil_rotater(pic, angle, resample, expand): - return np.array( Image.fromarray(pic).rotate(angle, expand=expand, - resample=resample)) + return np.array( + Image.fromarray(pic).rotate(angle, expand=expand, resample=resample) + ) + + except ImportError: PIL_FOUND = False -def rotate(clip, angle, unit='deg', resample="bicubic", expand=True): + +def rotate(clip, angle, unit="deg", resample="bicubic", expand=True): """ Change unit to 'rad' to define angles as radians. If the angle is not one of 90, 180, -90, -180 (degrees) there will be @@ -37,36 +43,40 @@ def rotate(clip, angle, unit='deg', resample="bicubic", expand=True): expand Only applIf False, the clip will maintain the same True, the clip will be resized so that the whole """ - - resample = {"bilinear": Image.BILINEAR, - "nearest": Image.NEAREST, - "bicubic": Image.BICUBIC}[resample] - if not hasattr(angle, '__call__'): + resample = { + "bilinear": Image.BILINEAR, + "nearest": Image.NEAREST, + "bicubic": Image.BICUBIC, + }[resample] + + if not hasattr(angle, "__call__"): # if angle is a constant, convert to a constant function a = +angle angle = lambda t: a - transpo = [1,0] if clip.ismask else [1,0,2] + transpo = [1, 0] if clip.ismask else [1, 0, 2] def fl(gf, t): a = angle(t) im = gf(t) - if unit == 'rad': - a = 360.0*a/(2*np.pi) - - if (a==90) and expand: + if unit == "rad": + a = 360.0 * a / (2 * np.pi) + + if (a == 90) and expand: return np.transpose(im, axes=transpo)[::-1] - elif (a==-90) and expand: - return np.transpose(im, axes=transpo)[:,::-1] + elif (a == -90) and expand: + return np.transpose(im, axes=transpo)[:, ::-1] elif (a in [180, -180]) and expand: - return im[::-1,::-1] + return im[::-1, ::-1] elif not PIL_FOUND: - raise ValueError('Without "Pillow" installed, only angles 90, -90,' - '180 are supported, please install "Pillow" with' - "pip install pillow") + raise ValueError( + 'Without "Pillow" installed, only angles 90, -90,' + '180 are supported, please install "Pillow" with' + "pip install pillow" + ) else: return pil_rotater(im, a, resample=resample, expand=expand) diff --git a/moviepy/video/fx/scroll.py b/moviepy/video/fx/scroll.py index 34f4f4012..e37ad2f98 100644 --- a/moviepy/video/fx/scroll.py +++ b/moviepy/video/fx/scroll.py @@ -1,16 +1,19 @@ -def scroll(clip, h=None, w=None, x_speed=0, y_speed=0, - x_start=0, y_start=0, apply_to="mask"): +def scroll( + clip, h=None, w=None, x_speed=0, y_speed=0, x_start=0, y_start=0, apply_to="mask" +): """ Scrolls horizontally or vertically a clip, e.g. to make end credits """ - if h is None: h = clip.h - if w is None: w = clip.w - - xmax = clip.w-w-1 - ymax = clip.h-h-1 + if h is None: + h = clip.h + if w is None: + w = clip.w - 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] - - return clip.fl(f, apply_to = apply_to) + xmax = clip.w - w - 1 + ymax = 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] + + return clip.fl(f, apply_to=apply_to) diff --git a/moviepy/video/fx/speedx.py b/moviepy/video/fx/speedx.py index 1c6b82cff..ce4c76955 100644 --- a/moviepy/video/fx/speedx.py +++ b/moviepy/video/fx/speedx.py @@ -1,7 +1,7 @@ from moviepy.decorators import apply_to_audio, apply_to_mask -def speedx(clip, factor = None, final_duration=None): +def speedx(clip, factor=None, final_duration=None): """ Returns a clip playing the current clip but at a speed multiplied by ``factor``. Instead of factor one can indicate the desired @@ -9,13 +9,13 @@ def speedx(clip, factor = None, final_duration=None): computed. The same effect is applied to the clip's audio and mask if any. """ - + if final_duration: - factor = 1.0* clip.duration / final_duration - - newclip = clip.fl_time(lambda t: factor * t, apply_to=['mask', 'audio']) - + factor = 1.0 * clip.duration / final_duration + + newclip = clip.fl_time(lambda t: factor * t, apply_to=["mask", "audio"]) + if clip.duration is not None: newclip = newclip.set_duration(1.0 * clip.duration / factor) - + return newclip diff --git a/moviepy/video/fx/supersample.py b/moviepy/video/fx/supersample.py index 93b9d7a43..329f17f09 100644 --- a/moviepy/video/fx/supersample.py +++ b/moviepy/video/fx/supersample.py @@ -4,10 +4,10 @@ def supersample(clip, d, nframes): """ Replaces each frame at time t by the mean of `nframes` 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) + 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") return clip.fl(fl) diff --git a/moviepy/video/fx/time_symmetrize.py b/moviepy/video/fx/time_symmetrize.py index de2f3181d..e4ebb8b58 100644 --- a/moviepy/video/fx/time_symmetrize.py +++ b/moviepy/video/fx/time_symmetrize.py @@ -14,4 +14,4 @@ def time_symmetrize(clip): This effect is automatically applied to the clip's mask and audio if they exist. """ - return concatenate_videoclips([clip, clip.fx( time_mirror )]) + return concatenate_videoclips([clip, clip.fx(time_mirror)]) diff --git a/moviepy/video/io/ImageSequenceClip.py b/moviepy/video/io/ImageSequenceClip.py index 662138c00..f6d5c0ced 100644 --- a/moviepy/video/io/ImageSequenceClip.py +++ b/moviepy/video/io/ImageSequenceClip.py @@ -46,9 +46,15 @@ class ImageSequenceClip(VideoClip): """ - - def __init__(self, sequence, fps=None, durations=None, with_mask=True, - ismask=False, load_images=False): + def __init__( + self, + sequence, + fps=None, + durations=None, + with_mask=True, + ismask=False, + load_images=False, + ): # CODE WRITTEN AS IT CAME, MAY BE IMPROVED IN THE FUTURE @@ -66,45 +72,47 @@ def __init__(self, sequence, fps=None, durations=None, with_mask=True, sequence = [imread(f) for f in sequence] fromfiles = False else: - fromfiles= True + fromfiles = True else: # sequence is already a list of numpy arrays fromfiles = False 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, f) for f in os.listdir(sequence)]) - #check that all the images are of the same size + # check that all the images are of the same size if isinstance(sequence[0], str): - size = imread(sequence[0]).shape + size = imread(sequence[0]).shape else: - size = sequence[0].shape + size = sequence[0].shape for image in sequence: - image1=image + image1 = image if isinstance(image, str): - image1=imread(image) + image1 = imread(image) if size != image1.shape: - raise Exception("Moviepy: ImageSequenceClip requires all images to be the same size") - + raise Exception( + "Moviepy: ImageSequenceClip requires all images to be the same size" + ) self.fps = fps if fps is not None: - durations = [1.0/fps for image in sequence] - self.images_starts = [1.0*i/fps-np.finfo(np.float32).eps for i in range(len(sequence))] + durations = [1.0 / fps for image in sequence] + self.images_starts = [ + 1.0 * i / fps - np.finfo(np.float32).eps for i in range(len(sequence)) + ] else: - self.images_starts = [0]+list(np.cumsum(durations)) + self.images_starts = [0] + list(np.cumsum(durations)) self.durations = durations self.duration = sum(durations) self.end = self.duration self.sequence = sequence - + def find_image_index(t): - return max([i for i in range(len(self.sequence)) - if self.images_starts[i]<=t]) + return max( + [i for i in range(len(self.sequence)) if self.images_starts[i] <= t] + ) if fromfiles: @@ -112,27 +120,27 @@ def find_image_index(t): self.lastimage = None def make_frame(t): - + index = find_image_index(t) if index != self.lastindex: - self.lastimage = imread(self.sequence[index])[:,:,:3] + self.lastimage = imread(self.sequence[index])[:, :, :3] self.lastindex = index - + return self.lastimage - if with_mask and (imread(self.sequence[0]).shape[2]==4): + if with_mask and (imread(self.sequence[0]).shape[2] == 4): self.mask = VideoClip(ismask=True) self.mask.lastindex = None self.mask.lastimage = None def mask_make_frame(t): - + index = find_image_index(t) if index != self.mask.lastindex: - frame = imread(self.sequence[index])[:,:,3] - self.mask.lastimage = frame.astype(float)/255 + frame = imread(self.sequence[index])[:, :, 3] + self.mask.lastimage = frame.astype(float) / 255 self.mask.lastindex = index return self.mask.lastimage @@ -140,25 +148,23 @@ def mask_make_frame(t): self.mask.make_frame = mask_make_frame self.mask.size = mask_make_frame(0).shape[:2][::-1] - else: def make_frame(t): - + index = find_image_index(t) - return self.sequence[index][:,:,:3] + return self.sequence[index][:, :, :3] - if with_mask and (self.sequence[0].shape[2]==4): + if with_mask and (self.sequence[0].shape[2] == 4): self.mask = VideoClip(ismask=True) def mask_make_frame(t): index = find_image_index(t) - return 1.0*self.sequence[index][:,:,3]/255 + return 1.0 * self.sequence[index][:, :, 3] / 255 self.mask.make_frame = mask_make_frame self.mask.size = mask_make_frame(0).shape[:2][::-1] - - + self.make_frame = make_frame self.size = make_frame(0).shape[:2][::-1] diff --git a/moviepy/video/io/VideoFileClip.py b/moviepy/video/io/VideoFileClip.py index 1d5c8b63e..9a6938da1 100644 --- a/moviepy/video/io/VideoFileClip.py +++ b/moviepy/video/io/VideoFileClip.py @@ -75,20 +75,31 @@ class VideoFileClip(VideoClip): """ - def __init__(self, filename, has_mask=False, - audio=True, audio_buffersize=200000, - target_resolution=None, resize_algorithm='bicubic', - audio_fps=44100, audio_nbytes=2, verbose=False, - fps_source='tbr'): + def __init__( + self, + filename, + has_mask=False, + audio=True, + audio_buffersize=200000, + target_resolution=None, + resize_algorithm="bicubic", + audio_fps=44100, + audio_nbytes=2, + verbose=False, + fps_source="tbr", + ): VideoClip.__init__(self) # Make a reader pix_fmt = "rgba" if has_mask else "rgb24" - self.reader = FFMPEG_VideoReader(filename, pix_fmt=pix_fmt, - target_resolution=target_resolution, - resize_algo=resize_algorithm, - fps_source=fps_source) + self.reader = FFMPEG_VideoReader( + filename, + pix_fmt=pix_fmt, + target_resolution=target_resolution, + resize_algo=resize_algorithm, + fps_source=fps_source, + ) # Make some of the reader's attributes accessible from the clip self.duration = self.reader.duration @@ -102,10 +113,11 @@ def __init__(self, filename, has_mask=False, if has_mask: - self.make_frame = lambda t: self.reader.get_frame(t)[:,:,:3] - mask_mf = lambda t: self.reader.get_frame(t)[:,:,3]/255.0 - self.mask = (VideoClip(ismask=True, make_frame=mask_mf) - .set_duration(self.duration)) + self.make_frame = lambda t: self.reader.get_frame(t)[:, :, :3] + mask_mf = lambda t: self.reader.get_frame(t)[:, :, 3] / 255.0 + self.mask = VideoClip(ismask=True, make_frame=mask_mf).set_duration( + self.duration + ) self.mask.fps = self.fps else: @@ -113,12 +125,14 @@ def __init__(self, filename, has_mask=False, self.make_frame = lambda t: self.reader.get_frame(t) # Make a reader for the audio, if any. - if audio and self.reader.infos['audio_found']: - - self.audio = AudioFileClip(filename, - buffersize=audio_buffersize, - fps=audio_fps, - nbytes=audio_nbytes) + if audio and self.reader.infos["audio_found"]: + + self.audio = AudioFileClip( + filename, + buffersize=audio_buffersize, + fps=audio_fps, + nbytes=audio_nbytes, + ) def close(self): """ Close the internal reader. """ diff --git a/moviepy/video/io/bindings.py b/moviepy/video/io/bindings.py index 4b206f75d..8509fe0e8 100644 --- a/moviepy/video/io/bindings.py +++ b/moviepy/video/io/bindings.py @@ -10,23 +10,24 @@ def PIL_to_npimage(im): """ Transforms a PIL/Pillow image into a numpy RGB(A) image. Actually all this do is returning numpy.array(im).""" return np.array(im) - #w,h = im.size - #d = (4 if im.mode=="RGBA" else 3) - #return +np.frombuffer(im.tobytes(), dtype='uint8').reshape((h,w,d)) + # w,h = im.size + # d = (4 if im.mode=="RGBA" else 3) + # return +np.frombuffer(im.tobytes(), dtype='uint8').reshape((h,w,d)) def mplfig_to_npimage(fig): """ Converts a matplotlib figure to a RGB frame after updating the canvas""" # only the Agg backend now supports the tostring_rgb function from matplotlib.backends.backend_agg import FigureCanvasAgg + canvas = FigureCanvasAgg(fig) - canvas.draw() # update/draw the elements + canvas.draw() # update/draw the elements # get the width and the height to resize the matrix - l,b,w,h = canvas.figure.bbox.bounds + l, b, w, h = canvas.figure.bbox.bounds w, h = int(w), int(h) # exports the canvas to a string buffer and then to a numpy nd.array buf = canvas.tostring_rgb() - image= np.frombuffer(buf, dtype=np.uint8) - return image.reshape(h,w,3) + image = np.frombuffer(buf, dtype=np.uint8) + return image.reshape(h, w, 3) diff --git a/moviepy/video/io/downloader.py b/moviepy/video/io/downloader.py index 5b579de02..5016bc448 100644 --- a/moviepy/video/io/downloader.py +++ b/moviepy/video/io/downloader.py @@ -18,17 +18,18 @@ def download_webfile(url, filename, overwrite=False): if os.path.exists(filename) and not overwrite: return - if '.' in url: + if "." in url: r = requests.get(url, stream=True) - with open(filename, 'wb') as fd: + with open(filename, "wb") as fd: for chunk in r.iter_content(chunk_size=128): fd.write(chunk) else: try: - subprocess_call(['youtube-dl', url, '-o', filename]) + subprocess_call(["youtube-dl", url, "-o", filename]) except OSError as e: raise OSError( - e.message + '\n A possible reason is that youtube-dl' - ' is not installed on your computer. Install it with ' - ' "pip install youtube_dl"') + e.message + "\n A possible reason is that youtube-dl" + " is not installed on your computer. Install it with " + ' "pip install youtube_dl"' + ) diff --git a/moviepy/video/io/ffmpeg_reader.py b/moviepy/video/io/ffmpeg_reader.py index 9f05d0936..0fe7f34bf 100644 --- a/moviepy/video/io/ffmpeg_reader.py +++ b/moviepy/video/io/ffmpeg_reader.py @@ -19,23 +19,25 @@ logging.captureWarnings(True) - - - class FFMPEG_VideoReader: - - def __init__(self, filename, print_infos=False, bufsize = None, - pix_fmt="rgb24", check_duration=True, - target_resolution=None, resize_algo='bicubic', - fps_source='tbr'): + def __init__( + self, + filename, + print_infos=False, + bufsize=None, + pix_fmt="rgb24", + check_duration=True, + target_resolution=None, + resize_algo="bicubic", + fps_source="tbr", + ): self.filename = filename self.proc = None - infos = ffmpeg_parse_infos(filename, print_infos, check_duration, - fps_source) - self.fps = infos['video_fps'] - self.size = infos['video_size'] - self.rotation = infos['video_rotation'] + infos = ffmpeg_parse_infos(filename, print_infos, check_duration, fps_source) + self.fps = infos["video_fps"] + self.size = infos["video_size"] + self.rotation = infos["video_rotation"] if target_resolution: # revert the order, as ffmpeg used (width, height) @@ -51,100 +53,119 @@ def __init__(self, filename, print_infos=False, bufsize = None, self.size = target_resolution self.resize_algo = resize_algo - self.duration = infos['video_duration'] - self.ffmpeg_duration = infos['duration'] - self.nframes = infos['video_nframes'] + self.duration = infos["video_duration"] + self.ffmpeg_duration = infos["duration"] + self.nframes = infos["video_nframes"] self.infos = infos self.pix_fmt = pix_fmt - self.depth = 4 if pix_fmt == 'rgba' else 3 + self.depth = 4 if pix_fmt == "rgba" else 3 if bufsize is None: w, h = self.size bufsize = self.depth * w * h + 100 - self.bufsize= bufsize + self.bufsize = bufsize self.initialize() - self.pos = 1 self.lastread = self.read_frame() - def initialize(self, starttime=0): """Opens the file, creates the pipe. """ - self.close() # if any + self.close() # if any - if starttime != 0 : + if starttime != 0: offset = min(1, starttime) - i_arg = ['-ss', "%.06f" % (starttime - offset), - '-i', self.filename, - '-ss', "%.06f" % offset] + i_arg = [ + "-ss", + "%.06f" % (starttime - offset), + "-i", + self.filename, + "-ss", + "%.06f" % offset, + ] else: - i_arg = [ '-i', self.filename] - - cmd = ([get_setting("FFMPEG_BINARY")] + i_arg + - ['-loglevel', 'error', - '-f', 'image2pipe', - '-vf', 'scale=%d:%d' % tuple(self.size), - '-sws_flags', self.resize_algo, - "-pix_fmt", self.pix_fmt, - '-vcodec', 'rawvideo', '-']) - popen_params = {"bufsize": self.bufsize, - "stdout": sp.PIPE, - "stderr": sp.PIPE, - "stdin": sp.DEVNULL} + i_arg = ["-i", self.filename] + + cmd = ( + [get_setting("FFMPEG_BINARY")] + + i_arg + + [ + "-loglevel", + "error", + "-f", + "image2pipe", + "-vf", + "scale=%d:%d" % tuple(self.size), + "-sws_flags", + self.resize_algo, + "-pix_fmt", + self.pix_fmt, + "-vcodec", + "rawvideo", + "-", + ] + ) + popen_params = { + "bufsize": self.bufsize, + "stdout": sp.PIPE, + "stderr": sp.PIPE, + "stdin": sp.DEVNULL, + } if os.name == "nt": popen_params["creationflags"] = 0x08000000 self.proc = sp.Popen(cmd, **popen_params) - def skip_frames(self, n=1): """Reads and throws away n frames """ w, h = self.size for i in range(n): - self.proc.stdout.read(self.depth*w*h) - #self.proc.stdout.flush() + self.proc.stdout.read(self.depth * w * h) + # self.proc.stdout.flush() self.pos += n - def read_frame(self): w, h = self.size - nbytes= self.depth*w*h + nbytes = self.depth * w * h s = self.proc.stdout.read(nbytes) if len(s) != nbytes: - warnings.warn("Warning: in file %s, "%(self.filename)+ - "%d bytes wanted but %d bytes read,"%(nbytes, len(s))+ - "at frame %d/%d, at time %.02f/%.02f sec. "%( - self.pos,self.nframes, - 1.0*self.pos/self.fps, - self.duration)+ - "Using the last valid frame instead.", - UserWarning) - - if not hasattr(self, 'lastread'): - raise IOError(("MoviePy error: failed to read the first frame of " - "video file %s. That might mean that the file is " - "corrupted. That may also mean that you are using " - "a deprecated version of FFMPEG. On Ubuntu/Debian " - "for instance the version in the repos is deprecated. " - "Please update to a recent version from the website.")%( - self.filename)) + warnings.warn( + "Warning: in file %s, " % (self.filename) + + "%d bytes wanted but %d bytes read," % (nbytes, len(s)) + + "at frame %d/%d, at time %.02f/%.02f sec. " + % (self.pos, self.nframes, 1.0 * self.pos / self.fps, self.duration) + + "Using the last valid frame instead.", + UserWarning, + ) + + if not hasattr(self, "lastread"): + raise IOError( + ( + "MoviePy error: failed to read the first frame of " + "video file %s. That might mean that the file is " + "corrupted. That may also mean that you are using " + "a deprecated version of FFMPEG. On Ubuntu/Debian " + "for instance the version in the repos is deprecated. " + "Please update to a recent version from the website." + ) + % (self.filename) + ) result = self.lastread else: - if hasattr(np, 'frombuffer'): - result = np.frombuffer(s, dtype='uint8') + if hasattr(np, "frombuffer"): + result = np.frombuffer(s, dtype="uint8") else: - result = np.fromstring(s, dtype='uint8') - result.shape =(h, w, len(s)//(w*h)) # reshape((h, w, len(s)//(w*h))) + result = np.fromstring(s, dtype="uint8") + result.shape = (h, w, len(s) // (w * h)) # reshape((h, w, len(s)//(w*h))) self.lastread = result return result @@ -165,7 +186,7 @@ def get_frame(self, t): # go to the previous integer. This makes the fetching more robust in the # case where you get the nth frame by writing get_frame(n/fps). - pos = int(self.fps*t + 0.00001)+1 + pos = int(self.fps * t + 0.00001) + 1 # Initialize proc if it is not open if not self.proc: @@ -179,7 +200,7 @@ def get_frame(self, t): self.initialize(t) self.pos = pos else: - self.skip_frames(pos-self.pos-1) + self.skip_frames(pos - self.pos - 1) result = self.read_frame() self.pos = pos return result @@ -191,7 +212,7 @@ def close(self): self.proc.stderr.close() self.proc.wait() self.proc = None - if hasattr(self, 'lastread'): + if hasattr(self, "lastread"): del self.lastread def __del__(self): @@ -218,15 +239,16 @@ def ffmpeg_read_image(filename, with_mask=True): this layer as the mask of the returned ImageClip """ - pix_fmt = 'rgba' if with_mask else "rgb24" + pix_fmt = "rgba" if with_mask else "rgb24" reader = FFMPEG_VideoReader(filename, pix_fmt=pix_fmt, check_duration=False) im = reader.lastread del reader return im -def ffmpeg_parse_infos(filename, print_infos=False, check_duration=True, - fps_source='tbr'): +def ffmpeg_parse_infos( + filename, print_infos=False, check_duration=True, fps_source="tbr" +): """Get file infos using ffmpeg. Returns a dictionnary with the fields: @@ -238,24 +260,25 @@ def ffmpeg_parse_infos(filename, print_infos=False, check_duration=True, """ - # open the file in a pipe, provoke an error, read output - is_GIF = filename.endswith('.gif') + is_GIF = filename.endswith(".gif") cmd = [get_setting("FFMPEG_BINARY"), "-i", filename] if is_GIF: cmd += ["-f", "null", "/dev/null"] - popen_params = {"bufsize": 10**5, - "stdout": sp.PIPE, - "stderr": sp.PIPE, - "stdin": sp.DEVNULL} + popen_params = { + "bufsize": 10 ** 5, + "stdout": sp.PIPE, + "stderr": sp.PIPE, + "stdin": sp.DEVNULL, + } if os.name == "nt": popen_params["creationflags"] = 0x08000000 proc = sp.Popen(cmd, **popen_params) (output, error) = proc.communicate() - infos = error.decode('utf8') + infos = error.decode("utf8") del proc @@ -263,49 +286,60 @@ def ffmpeg_parse_infos(filename, print_infos=False, check_duration=True, # print the whole info text returned by FFMPEG print(infos) - lines = infos.splitlines() if "No such file or directory" in lines[-1]: - raise IOError(("MoviePy error: the file %s could not be found!\n" - "Please check that you entered the correct " - "path.")%filename) + raise IOError( + ( + "MoviePy error: the file %s could not be found!\n" + "Please check that you entered the correct " + "path." + ) + % filename + ) result = dict() - # get duration (in seconds) - result['duration'] = None + result["duration"] = None if check_duration: try: - keyword = ('frame=' if is_GIF else 'Duration: ') + keyword = "frame=" if is_GIF else "Duration: " # for large GIFS the "full" duration is presented as the last element in the list. index = -1 if is_GIF else 0 line = [l for l in lines if keyword in l][index] 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"] = cvsecs(match) except: - raise IOError(("MoviePy error: failed to read the duration of file %s.\n" - "Here are the file infos returned by ffmpeg:\n\n%s")%( - filename, infos)) + raise IOError( + ( + "MoviePy error: failed to read the duration of file %s.\n" + "Here are the file infos returned by ffmpeg:\n\n%s" + ) + % (filename, infos) + ) # get the output line that speaks about video - lines_video = [l for l in lines if ' Video: ' in l and re.search('\d+x\d+', l)] + lines_video = [l for l in lines if " Video: " in l and re.search("\d+x\d+", l)] - result['video_found'] = ( lines_video != [] ) + result["video_found"] = lines_video != [] - if result['video_found']: + if result["video_found"]: try: line = lines_video[0] # 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 + s = list(map(int, line[match.start() : match.end() - 1].split("x"))) + result["video_size"] = s except: - raise IOError(("MoviePy error: failed to read video dimensions in file %s.\n" - "Here are the file infos returned by ffmpeg:\n\n%s")%( - filename, infos)) + raise IOError( + ( + "MoviePy error: failed to read video dimensions in file %s.\n" + "Here are the file infos returned by ffmpeg:\n\n%s" + ) + % (filename, infos) + ) # Get the frame rate. Sometimes it's 'tbr', sometimes 'fps', sometimes # tbc, and sometimes tbc/2... @@ -319,7 +353,7 @@ def get_tbr(): match = re.search("( [0-9]*.| )[0-9]* tbr", line) # Sometimes comes as e.g. 12k. We need to replace that with 12000. - s_tbr = line[match.start():match.end()].split(' ')[1] + s_tbr = line[match.start() : match.end()].split(" ")[1] if "k" in s_tbr: tbr = float(s_tbr.replace("k", "")) * 1000 else: @@ -328,65 +362,74 @@ def get_tbr(): def get_fps(): match = re.search("( [0-9]*.| )[0-9]* fps", line) - fps = float(line[match.start():match.end()].split(' ')[1]) + fps = float(line[match.start() : match.end()].split(" ")[1]) return fps - if fps_source == 'tbr': + if fps_source == "tbr": try: - result['video_fps'] = get_tbr() + result["video_fps"] = get_tbr() except: - result['video_fps'] = get_fps() + result["video_fps"] = get_fps() - elif fps_source == 'fps': + elif fps_source == "fps": try: - result['video_fps'] = get_fps() + result["video_fps"] = get_fps() except: - result['video_fps'] = get_tbr() + result["video_fps"] = get_tbr() # It is known that a fps of 24 is often written as 24000/1001 # but then ffmpeg nicely rounds it to 23.98, which we hate. - coef = 1000.0/1001.0 - fps = result['video_fps'] - for x in [23,24,25,30,50]: - if (fps!=x) and abs(fps - x*coef) < .01: - result['video_fps'] = x*coef + coef = 1000.0 / 1001.0 + fps = result["video_fps"] + for x in [23, 24, 25, 30, 50]: + if (fps != x) and abs(fps - x * coef) < 0.01: + result["video_fps"] = x * coef if check_duration: - result['video_nframes'] = int(result['duration']*result['video_fps'])+1 - result['video_duration'] = result['duration'] + result["video_nframes"] = int(result["duration"] * result["video_fps"]) + 1 + result["video_duration"] = result["duration"] else: - result['video_nframes'] = 1 - result['video_duration'] = None + result["video_nframes"] = 1 + result["video_duration"] = None # We could have also recomputed the duration from the number # of frames, as follows: # >>> result['video_duration'] = result['video_nframes'] / result['video_fps'] # get the video rotation info. try: - rotation_lines = [l for l in lines if 'rotate :' in l and re.search('\d+$', l)] + rotation_lines = [ + l for l in lines if "rotate :" in l and re.search("\d+$", l) + ] if len(rotation_lines): rotation_line = rotation_lines[0] - match = re.search('\d+$', rotation_line) - result['video_rotation'] = int(rotation_line[match.start() : match.end()]) + match = re.search("\d+$", rotation_line) + result["video_rotation"] = int( + rotation_line[match.start() : match.end()] + ) else: - result['video_rotation'] = 0 + result["video_rotation"] = 0 except: - raise IOError(("MoviePy error: failed to read video rotation in file %s.\n" - "Here are the file infos returned by ffmpeg:\n\n%s")%( - filename, infos)) - + raise IOError( + ( + "MoviePy error: failed to read video rotation in file %s.\n" + "Here are the file infos returned by ffmpeg:\n\n%s" + ) + % (filename, infos) + ) - lines_audio = [l for l in lines if ' Audio: ' in l] + lines_audio = [l for l in lines if " Audio: " in l] - result['audio_found'] = lines_audio != [] + result["audio_found"] = lines_audio != [] - if result['audio_found']: + if result["audio_found"]: line = lines_audio[0] try: match = re.search(" [0-9]* Hz", line) - hz_string = line[match.start()+1:match.end()-3] # Removes the 'hz' from the end - result['audio_fps'] = int(hz_string) + hz_string = line[ + match.start() + 1 : match.end() - 3 + ] # Removes the 'hz' from the end + result["audio_fps"] = int(hz_string) except: - result['audio_fps'] = 'unknown' + result["audio_fps"] = "unknown" return result diff --git a/moviepy/video/io/ffmpeg_tools.py b/moviepy/video/io/ffmpeg_tools.py index ef6642115..8128de903 100644 --- a/moviepy/video/io/ffmpeg_tools.py +++ b/moviepy/video/io/ffmpeg_tools.py @@ -8,19 +8,28 @@ from moviepy.tools import subprocess_call -def ffmpeg_movie_from_frames(filename, folder, fps, digits=6, bitrate='v'): +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 = [get_setting("FFMPEG_BINARY"), "-y", "-f","image2", - "-r", "%d"%fps, - "-i", os.path.join(folder,folder) + '/' + s, - "-b", "%dk"%bitrate, - "-r", "%d"%fps, - filename] - + cmd = [ + get_setting("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) @@ -29,40 +38,84 @@ def ffmpeg_extract_subclip(filename, t1, t2, targetname=None): the times ``t1`` and ``t2``. """ name, ext = os.path.splitext(filename) if not targetname: - T1, T2 = [int(1000*t) for t in [t1, t2]] + T1, T2 = [int(1000 * t) for t in [t1, t2]] targetname = "%sSUB%d_%d.%s" % (name, T1, T2, ext) - - cmd = [get_setting("FFMPEG_BINARY"),"-y", - "-ss", "%0.2f"%t1, - "-i", filename, - "-t", "%0.2f"%(t2-t1), - "-map", "0", "-vcodec", "copy", "-acodec", "copy", targetname] - + + cmd = [ + get_setting("FFMPEG_BINARY"), + "-y", + "-ss", + "%0.2f" % t1, + "-i", + filename, + "-t", + "%0.2f" % (t2 - t1), + "-map", + "0", + "-vcodec", + "copy", + "-acodec", + "copy", + targetname, + ] + subprocess_call(cmd) -def ffmpeg_merge_video_audio(video,audio,output, vcodec='copy', - acodec='copy', ffmpeg_output=False, - logger = 'bar'): +def ffmpeg_merge_video_audio( + video, + audio, + output, + vcodec="copy", + acodec="copy", + ffmpeg_output=False, + logger="bar", +): """ merges video file ``video`` and audio file ``audio`` into one movie file ``output``. """ - cmd = [get_setting("FFMPEG_BINARY"), "-y", "-i", audio,"-i", video, - "-vcodec", vcodec, "-acodec", acodec, output] - - subprocess_call(cmd, logger = logger) - + cmd = [ + get_setting("FFMPEG_BINARY"), + "-y", + "-i", + audio, + "-i", + video, + "-vcodec", + vcodec, + "-acodec", + acodec, + output, + ] + + subprocess_call(cmd, logger=logger) -def ffmpeg_extract_audio(inputfile,output,bitrate=3000,fps=44100): + +def ffmpeg_extract_audio(inputfile, output, bitrate=3000, fps=44100): """ extract the sound from a video file and save it in ``output`` """ - cmd = [get_setting("FFMPEG_BINARY"), "-y", "-i", inputfile, "-ab", "%dk"%bitrate, - "-ar", "%d"%fps, output] + cmd = [ + get_setting("FFMPEG_BINARY"), + "-y", + "-i", + inputfile, + "-ab", + "%dk" % bitrate, + "-ar", + "%d" % fps, + output, + ] subprocess_call(cmd) - -def ffmpeg_resize(video,output,size): + +def ffmpeg_resize(video, output, size): """ resizes ``video`` to new size ``size`` and write the result in file ``output``. """ - cmd= [get_setting("FFMPEG_BINARY"), "-i", video, "-vf", "scale=%d:%d"%(size[0], size[1]), - output] - + cmd = [ + get_setting("FFMPEG_BINARY"), + "-i", + video, + "-vf", + "scale=%d:%d" % (size[0], size[1]), + output, + ] + subprocess_call(cmd) diff --git a/moviepy/video/io/ffmpeg_writer.py b/moviepy/video/io/ffmpeg_writer.py index f56ca5077..e82f53f8b 100644 --- a/moviepy/video/io/ffmpeg_writer.py +++ b/moviepy/video/io/ffmpeg_writer.py @@ -64,9 +64,20 @@ class FFMPEG_VideoWriter: """ - def __init__(self, filename, size, fps, codec="libx264", audiofile=None, - preset="medium", bitrate=None, withmask=False, - logfile=None, threads=None, ffmpeg_params=None): + def __init__( + self, + filename, + size, + fps, + codec="libx264", + audiofile=None, + preset="medium", + bitrate=None, + withmask=False, + logfile=None, + threads=None, + ffmpeg_params=None, + ): if logfile is None: logfile = sp.PIPE @@ -78,47 +89,41 @@ def __init__(self, filename, size, fps, codec="libx264", audiofile=None, # order is important cmd = [ get_setting("FFMPEG_BINARY"), - '-y', - '-loglevel', 'error' if logfile == sp.PIPE else 'info', - '-f', 'rawvideo', - '-vcodec', 'rawvideo', - '-s', '%dx%d' % (size[0], size[1]), - '-pix_fmt', 'rgba' if withmask else 'rgb24', - '-r', '%.02f' % fps, - '-an', '-i', '-' + "-y", + "-loglevel", + "error" if logfile == sp.PIPE else "info", + "-f", + "rawvideo", + "-vcodec", + "rawvideo", + "-s", + "%dx%d" % (size[0], size[1]), + "-pix_fmt", + "rgba" if withmask else "rgb24", + "-r", + "%.02f" % fps, + "-an", + "-i", + "-", ] if audiofile is not None: - cmd.extend([ - '-i', audiofile, - '-acodec', 'copy' - ]) - cmd.extend([ - '-vcodec', codec, - '-preset', preset, - ]) + cmd.extend(["-i", audiofile, "-acodec", "copy"]) + cmd.extend( + ["-vcodec", codec, "-preset", preset,] + ) if ffmpeg_params is not None: cmd.extend(ffmpeg_params) if bitrate is not None: - cmd.extend([ - '-b', bitrate - ]) + cmd.extend(["-b", bitrate]) if threads is not None: cmd.extend(["-threads", str(threads)]) - if ((codec == 'libx264') and - (size[0] % 2 == 0) and - (size[1] % 2 == 0)): - cmd.extend([ - '-pix_fmt', 'yuv420p' - ]) - cmd.extend([ - filename - ]) + if (codec == "libx264") and (size[0] % 2 == 0) and (size[1] % 2 == 0): + cmd.extend(["-pix_fmt", "yuv420p"]) + cmd.extend([filename]) - popen_params = {"stdout": sp.DEVNULL, - "stderr": logfile, - "stdin": sp.PIPE} + popen_params = {"stdout": sp.DEVNULL, "stderr": logfile, "stdin": sp.PIPE} # This was added so that no extra unwanted window opens on windows # when the child process is created @@ -127,51 +132,58 @@ def __init__(self, filename, size, fps, codec="libx264", audiofile=None, self.proc = sp.Popen(cmd, **popen_params) - def write_frame(self, img_array): """ Writes one frame in the file.""" try: self.proc.stdin.write(img_array.tobytes()) except IOError as err: _, ffmpeg_error = self.proc.communicate() - error = (str(err) + ("\n\nMoviePy error: FFMPEG encountered " - "the following error while writing file %s:" - "\n\n %s" % (self.filename, str(ffmpeg_error)))) + error = str(err) + ( + "\n\nMoviePy error: FFMPEG encountered " + "the following error while writing file %s:" + "\n\n %s" % (self.filename, str(ffmpeg_error)) + ) if b"Unknown encoder" in ffmpeg_error: - error = error+("\n\nThe video export " - "failed because FFMPEG didn't find the specified " - "codec for video encoding (%s). Please install " - "this codec or change the codec when calling " - "write_videofile. For instance:\n" - " >>> clip.write_videofile('myvid.webm', codec='libvpx')")%(self.codec) + error = error + ( + "\n\nThe video export " + "failed because FFMPEG didn't find the specified " + "codec for video encoding (%s). Please install " + "this codec or change the codec when calling " + "write_videofile. For instance:\n" + " >>> clip.write_videofile('myvid.webm', codec='libvpx')" + ) % (self.codec) elif b"incorrect codec parameters ?" in ffmpeg_error: - error = error+("\n\nThe video export " - "failed, possibly because the codec specified for " - "the video (%s) is not compatible with the given " - "extension (%s). Please specify a valid 'codec' " - "argument in write_videofile. This would be 'libx264' " - "or 'mpeg4' for mp4, 'libtheora' for ogv, 'libvpx for webm. " - "Another possible reason is that the audio codec was not " - "compatible with the video codec. For instance the video " - "extensions 'ogv' and 'webm' only allow 'libvorbis' (default) as a" - "video codec." - )%(self.codec, self.ext) - - elif b"encoder setup failed" in ffmpeg_error: - - error = error+("\n\nThe video export " - "failed, possibly because the bitrate you specified " - "was too high or too low for the video codec.") + error = error + ( + "\n\nThe video export " + "failed, possibly because the codec specified for " + "the video (%s) is not compatible with the given " + "extension (%s). Please specify a valid 'codec' " + "argument in write_videofile. This would be 'libx264' " + "or 'mpeg4' for mp4, 'libtheora' for ogv, 'libvpx for webm. " + "Another possible reason is that the audio codec was not " + "compatible with the video codec. For instance the video " + "extensions 'ogv' and 'webm' only allow 'libvorbis' (default) as a" + "video codec." + ) % (self.codec, self.ext) + + elif b"encoder setup failed" in ffmpeg_error: + + error = error + ( + "\n\nThe video export " + "failed, possibly because the bitrate you specified " + "was too high or too low for the video codec." + ) elif b"Invalid encoder type" in ffmpeg_error: - error = error + ("\n\nThe video export failed because the codec " - "or file extension you provided is not a video") - + error = error + ( + "\n\nThe video export failed because the codec " + "or file extension you provided is not a video" + ) raise IOError(error) @@ -192,63 +204,90 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, traceback): self.close() -def ffmpeg_write_video(clip, filename, fps, codec="libx264", bitrate=None, - preset="medium", withmask=False, write_logfile=False, - audiofile=None, verbose=True, threads=None, ffmpeg_params=None, - logger='bar'): + +def ffmpeg_write_video( + clip, + filename, + fps, + codec="libx264", + bitrate=None, + preset="medium", + withmask=False, + write_logfile=False, + audiofile=None, + verbose=True, + threads=None, + ffmpeg_params=None, + logger="bar", +): """ Write the clip to a videofile. See VideoClip.write_videofile for details on the parameters. """ logger = proglog.default_bar_logger(logger) if write_logfile: - logfile = open(filename + ".log", 'w+') + logfile = open(filename + ".log", "w+") else: logfile = None - logger(message='Moviepy - Writing video %s\n' % filename) - with FFMPEG_VideoWriter(filename, clip.size, fps, codec = codec, - preset=preset, bitrate=bitrate, logfile=logfile, - audiofile=audiofile, threads=threads, - ffmpeg_params=ffmpeg_params) as writer: - - nframes = int(clip.duration*fps) - - for t,frame in clip.iter_frames(logger=logger, with_times=True, - fps=fps, dtype="uint8"): + logger(message="Moviepy - Writing video %s\n" % filename) + with FFMPEG_VideoWriter( + filename, + clip.size, + fps, + codec=codec, + preset=preset, + bitrate=bitrate, + logfile=logfile, + audiofile=audiofile, + threads=threads, + ffmpeg_params=ffmpeg_params, + ) as writer: + + nframes = int(clip.duration * fps) + + for t, frame in clip.iter_frames( + logger=logger, with_times=True, fps=fps, dtype="uint8" + ): if withmask: - mask = (255*clip.mask.get_frame(t)) + mask = 255 * clip.mask.get_frame(t) if mask.dtype != "uint8": mask = mask.astype("uint8") - frame = np.dstack([frame,mask]) + frame = np.dstack([frame, mask]) writer.write_frame(frame) if write_logfile: logfile.close() - logger(message='Moviepy - Done !') + logger(message="Moviepy - Done !") def ffmpeg_write_image(filename, image, logfile=False): """ Writes an image (HxWx3 or HxWx4 numpy array) to a file, using ffmpeg. """ - if image.dtype != 'uint8': - image = image.astype("uint8") - - cmd = [ get_setting("FFMPEG_BINARY"), '-y', - '-s', "%dx%d"%(image.shape[:2][::-1]), - "-f", 'rawvideo', - '-pix_fmt', "rgba" if (image.shape[2] == 4) else "rgb24", - '-i','-', filename] + if image.dtype != "uint8": + image = image.astype("uint8") + + cmd = [ + get_setting("FFMPEG_BINARY"), + "-y", + "-s", + "%dx%d" % (image.shape[:2][::-1]), + "-f", + "rawvideo", + "-pix_fmt", + "rgba" if (image.shape[2] == 4) else "rgb24", + "-i", + "-", + filename, + ] if logfile: - log_file = open(filename + ".log", 'w+') + log_file = open(filename + ".log", "w+") else: log_file = sp.PIPE - popen_params = {"stdout": sp.DEVNULL, - "stderr": log_file, - "stdin": sp.PIPE} + popen_params = {"stdout": sp.DEVNULL, "stderr": log_file, "stdin": sp.PIPE} if os.name == "nt": popen_params["creationflags"] = 0x08000000 @@ -257,9 +296,13 @@ def ffmpeg_write_image(filename, image, logfile=False): out, err = proc.communicate(image.tostring()) if proc.returncode: - err = "\n".join(["[MoviePy] Running : %s\n" % cmd, - "WARNING: this command returned an error:", - err.decode('utf8')]) + err = "\n".join( + [ + "[MoviePy] Running : %s\n" % cmd, + "WARNING: this command returned an error:", + err.decode("utf8"), + ] + ) raise IOError(err) del proc diff --git a/moviepy/video/io/gif_writers.py b/moviepy/video/io/gif_writers.py index 7719140d3..baa005e18 100644 --- a/moviepy/video/io/gif_writers.py +++ b/moviepy/video/io/gif_writers.py @@ -10,6 +10,7 @@ try: import imageio + IMAGEIO_FOUND = True except ImportError: IMAGEIO_FOUND = False @@ -17,9 +18,19 @@ @requires_duration @use_clip_fps_by_default -def write_gif_with_tempfiles(clip, filename, fps=None, program= 'ImageMagick', - opt="OptimizeTransparency", fuzz=1, verbose=True, - loop=0, dispose=True, colors=None, logger='bar'): +def write_gif_with_tempfiles( + clip, + filename, + fps=None, + program="ImageMagick", + opt="OptimizeTransparency", + fuzz=1, + verbose=True, + loop=0, + dispose=True, + colors=None, + logger="bar", +): """ Write the VideoClip to a GIF file. @@ -31,57 +42,77 @@ def write_gif_with_tempfiles(clip, filename, fps=None, program= 'ImageMagick', """ logger = proglog.default_bar_logger(logger) fileName, ext = os.path.splitext(filename) - tt = np.arange(0,clip.duration, 1.0/fps) + tt = np.arange(0, clip.duration, 1.0 / fps) tempfiles = [] - - logger(message='MoviePy - Building file %s\n' % filename) - logger(message='MoviePy - - Generating GIF frames') - + logger(message="MoviePy - Building file %s\n" % filename) + logger(message="MoviePy - - Generating GIF frames") + for i, t in logger.iter_bar(t=list(enumerate(tt))): - name = "%s_GIFTEMP%04d.png"%(fileName, i+1) + name = "%s_GIFTEMP%04d.png" % (fileName, i + 1) tempfiles.append(name) clip.save_frame(name, t, withmask=True) - delay = int(100.0/fps) + delay = int(100.0 / fps) if program == "ImageMagick": - logger(message='MoviePy - - Optimizing GIF with ImageMagick...') - cmd = [get_setting("IMAGEMAGICK_BINARY"), - '-delay' , '%d'%delay, - "-dispose" ,"%d"%(2 if dispose else 1), - "-loop" , "%d"%loop, - "%s_GIFTEMP*.png"%fileName, - "-coalesce", - "-fuzz", "%02d"%fuzz + "%", - "-layers", "%s"%opt, - ]+(["-colors", "%d"%colors] if colors is not None else [])+[ - filename] + logger(message="MoviePy - - Optimizing GIF with ImageMagick...") + cmd = ( + [ + get_setting("IMAGEMAGICK_BINARY"), + "-delay", + "%d" % delay, + "-dispose", + "%d" % (2 if dispose else 1), + "-loop", + "%d" % loop, + "%s_GIFTEMP*.png" % fileName, + "-coalesce", + "-fuzz", + "%02d" % fuzz + "%", + "-layers", + "%s" % opt, + ] + + (["-colors", "%d" % colors] if colors is not None else []) + + [filename] + ) elif program == "ffmpeg": - cmd = [get_setting("FFMPEG_BINARY"), '-y', - '-f', 'image2', '-r',str(fps), - '-i', fileName+'_GIFTEMP%04d.png', - '-r',str(fps), - filename] + cmd = [ + get_setting("FFMPEG_BINARY"), + "-y", + "-f", + "image2", + "-r", + str(fps), + "-i", + fileName + "_GIFTEMP%04d.png", + "-r", + str(fps), + filename, + ] try: subprocess_call(cmd, logger=logger) - logger(message='MoviePy - GIF ready: %s.' % filename) + logger(message="MoviePy - GIF ready: %s." % filename) - except (IOError,OSError) as err: + except (IOError, OSError) as err: - error = ("MoviePy Error: creation of %s failed because " - "of the following error:\n\n%s.\n\n."%(filename, str(err))) + error = ( + "MoviePy Error: creation of %s failed because " + "of the following error:\n\n%s.\n\n." % (filename, str(err)) + ) if program == "ImageMagick": - error = error + ("This error can be due to the fact that " + error = error + ( + "This error can be due to the fact that " "ImageMagick is not installed on your computer, or " "(for Windows users) that you didn't specify the " - "path to the ImageMagick binary in file config_defaults.py." ) + "path to the ImageMagick binary in file config_defaults.py." + ) raise IOError(error) @@ -89,12 +120,22 @@ def write_gif_with_tempfiles(clip, filename, fps=None, program= 'ImageMagick', os.remove(f) - @requires_duration @use_clip_fps_by_default -def write_gif(clip, filename, fps=None, program= 'ImageMagick', - opt="OptimizeTransparency", fuzz=1, verbose=True, withmask=True, - loop=0, dispose=True, colors=None, logger='bar'): +def write_gif( + clip, + filename, + fps=None, + program="ImageMagick", + opt="OptimizeTransparency", + fuzz=1, + verbose=True, + withmask=True, + loop=0, + dispose=True, + colors=None, + logger="bar", +): """ Write the VideoClip to a GIF file, without temporary files. Converts a VideoClip into an animated GIF using ImageMagick @@ -152,21 +193,31 @@ def write_gif(clip, filename, fps=None, program= 'ImageMagick', # frames -ffmpeg-> bmp frames -ImagMag-> gif -ImagMag-> better gif # - delay= 100.0/fps + delay = 100.0 / fps logger = proglog.default_bar_logger(logger) if clip.mask is None: withmask = False - cmd1 = [get_setting("FFMPEG_BINARY"), '-y', '-loglevel', 'error', - '-f', 'rawvideo', - '-vcodec','rawvideo', '-r', "%.02f"%fps, - '-s', "%dx%d"%(clip.w, clip.h), - '-pix_fmt', ('rgba' if withmask else 'rgb24'), - '-i', '-'] - - popen_params = {"stdout": sp.DEVNULL, - "stderr": sp.DEVNULL, - "stdin": sp.DEVNULL} + cmd1 = [ + get_setting("FFMPEG_BINARY"), + "-y", + "-loglevel", + "error", + "-f", + "rawvideo", + "-vcodec", + "rawvideo", + "-r", + "%.02f" % fps, + "-s", + "%dx%d" % (clip.w, clip.h), + "-pix_fmt", + ("rgba" if withmask else "rgb24"), + "-i", + "-", + ] + + popen_params = {"stdout": sp.DEVNULL, "stderr": sp.DEVNULL, "stdin": sp.DEVNULL} if os.name == "nt": popen_params["creationflags"] = 0x08000000 @@ -175,79 +226,111 @@ def write_gif(clip, filename, fps=None, program= 'ImageMagick', popen_params["stdin"] = sp.PIPE popen_params["stdout"] = sp.DEVNULL - proc1 = sp.Popen(cmd1+[ '-pix_fmt', ('rgba' if withmask else 'rgb24'), - '-r', "%.02f"%fps, filename], **popen_params) + proc1 = sp.Popen( + cmd1 + + [ + "-pix_fmt", + ("rgba" if withmask else "rgb24"), + "-r", + "%.02f" % fps, + filename, + ], + **popen_params, + ) else: popen_params["stdin"] = sp.PIPE popen_params["stdout"] = sp.PIPE - proc1 = sp.Popen(cmd1+ ['-f', 'image2pipe', '-vcodec', 'bmp', '-'], - **popen_params) - - if program == 'ImageMagick': + proc1 = sp.Popen( + cmd1 + ["-f", "image2pipe", "-vcodec", "bmp", "-"], **popen_params + ) - cmd2 = [get_setting("IMAGEMAGICK_BINARY"), '-delay', "%.02f"%(delay), - "-dispose" ,"%d"%(2 if dispose else 1), - '-loop', '%d'%loop, '-', '-coalesce'] + if program == "ImageMagick": - if (opt in [False, None]): + cmd2 = [ + get_setting("IMAGEMAGICK_BINARY"), + "-delay", + "%.02f" % (delay), + "-dispose", + "%d" % (2 if dispose else 1), + "-loop", + "%d" % loop, + "-", + "-coalesce", + ] + + if opt in [False, None]: popen_params["stdin"] = proc1.stdout popen_params["stdout"] = sp.DEVNULL - proc2 = sp.Popen(cmd2+[filename], **popen_params) + proc2 = sp.Popen(cmd2 + [filename], **popen_params) else: popen_params["stdin"] = proc1.stdout popen_params["stdout"] = sp.PIPE - proc2 = sp.Popen(cmd2+['gif:-'], **popen_params) + proc2 = sp.Popen(cmd2 + ["gif:-"], **popen_params) if opt: - cmd3 = [get_setting("IMAGEMAGICK_BINARY"), '-', - '-fuzz', '%d'%fuzz+'%', '-layers', opt - ]+(["-colors", "%d"%colors] if colors is not None else [])+[ - filename] + cmd3 = ( + [ + get_setting("IMAGEMAGICK_BINARY"), + "-", + "-fuzz", + "%d" % fuzz + "%", + "-layers", + opt, + ] + + (["-colors", "%d" % colors] if colors is not None else []) + + [filename] + ) popen_params["stdin"] = proc2.stdout popen_params["stdout"] = sp.DEVNULL proc3 = sp.Popen(cmd3, **popen_params) # We send all the frames to the first process - logger(message='MoviePy - Building file %s' % filename) - logger(message='MoviePy - - Generating GIF frames.') + logger(message="MoviePy - Building file %s" % filename) + logger(message="MoviePy - - Generating GIF frames.") try: - for t,frame in clip.iter_frames(fps=fps, logger=logger, - with_times=True, dtype="uint8"): + for t, frame in clip.iter_frames( + fps=fps, logger=logger, with_times=True, dtype="uint8" + ): if withmask: mask = 255 * clip.mask.get_frame(t) - frame = np.dstack([frame, mask]).astype('uint8') + frame = np.dstack([frame, mask]).astype("uint8") proc1.stdin.write(frame.tostring()) except IOError as err: - error = ("[MoviePy] Error: creation of %s failed because " - "of the following error:\n\n%s.\n\n."%(filename, str(err))) + error = ( + "[MoviePy] Error: creation of %s failed because " + "of the following error:\n\n%s.\n\n." % (filename, str(err)) + ) if program == "ImageMagick": - error = error + ("This can be due to the fact that " + error = error + ( + "This can be due to the fact that " "ImageMagick is not installed on your computer, or " "(for Windows users) that you didn't specify the " - "path to the ImageMagick binary in file config_defaults.py." ) + "path to the ImageMagick binary in file config_defaults.py." + ) raise IOError(error) - if program == 'ImageMagick': - logger(message='MoviePy - - Optimizing GIF with ImageMagick.') + if program == "ImageMagick": + logger(message="MoviePy - - Optimizing GIF with ImageMagick.") proc1.stdin.close() proc1.wait() - if program == 'ImageMagick': + if program == "ImageMagick": proc2.wait() if opt: proc3.wait() - logger(message='MoviePy - - File ready: %s.' % filename) + logger(message="MoviePy - - File ready: %s." % filename) -def write_gif_with_image_io(clip, filename, fps=None, opt=0, loop=0, - colors=None, verbose=True, logger='bar'): +def write_gif_with_image_io( + clip, filename, fps=None, opt=0, loop=0, colors=None, verbose=True, logger="bar" +): """ Writes the gif with the Python library ImageIO (calls FreeImage). @@ -262,23 +345,21 @@ def write_gif_with_image_io(clip, filename, fps=None, opt=0, loop=0, logger = proglog.default_bar_logger(logger) if not IMAGEIO_FOUND: - raise ImportError("Writing a gif with imageio requires ImageIO installed," - " with e.g. 'pip install imageio'") + raise ImportError( + "Writing a gif with imageio requires ImageIO installed," + " with e.g. 'pip install imageio'" + ) if fps is None: fps = clip.fps - quantizer = 0 if opt != 0 else 'nq' + quantizer = 0 if opt != 0 else "nq" writer = imageio.save( - filename, - duration=1.0/fps, - quantizer=quantizer, - palettesize=colors, - loop=loop - ) - logger(message='MoviePy - Building file %s with imageio.' % filename) + filename, duration=1.0 / fps, quantizer=quantizer, palettesize=colors, loop=loop + ) + logger(message="MoviePy - Building file %s with imageio." % filename) - for frame in clip.iter_frames(fps=fps, logger=logger, dtype='uint8'): + for frame in clip.iter_frames(fps=fps, logger=logger, dtype="uint8"): writer.append_data(frame) diff --git a/moviepy/video/io/html_tools.py b/moviepy/video/io/html_tools.py index f24af513f..5b42705d8 100644 --- a/moviepy/video/io/html_tools.py +++ b/moviepy/video/io/html_tools.py @@ -20,28 +20,37 @@ try: from IPython.display import HTML + ipython_available = True + class HTML2(HTML): def __add__(self, other): - return HTML2(self.data+other.data) + return HTML2(self.data + other.data) + except ImportError: ipython_available = False sorry = "Sorry, seems like your browser doesn't support HTML5 audio/video" -templates = {"audio":(""), - "image":"", - "video":("")} - - -def html_embed(clip, filetype=None, maxduration=60, rd_kwargs=None, - center=True, **html_kwargs): +templates = { + "audio": ( + "" + ), + "image": "", + "video": ( + "" + ), +} + + +def html_embed( + clip, filetype=None, maxduration=60, rd_kwargs=None, center=True, **html_kwargs +): """ Returns HTML5 code embedding the clip clip @@ -79,85 +88,104 @@ def html_embed(clip, filetype=None, maxduration=60, rd_kwargs=None, >>> clip.save_frame("first_frame.jpeg") >>> mpy.ipython_display("first_frame.jpeg") - """ - + """ + if rd_kwargs is None: rd_kwargs = {} if "Clip" in str(clip.__class__): TEMP_PREFIX = "__temp__" - if isinstance(clip,ImageClip): - filename = TEMP_PREFIX+".png" - kwargs = {'filename':filename, 'withmask':True} + if isinstance(clip, ImageClip): + filename = TEMP_PREFIX + ".png" + kwargs = {"filename": filename, "withmask": True} kwargs.update(rd_kwargs) clip.save_frame(**kwargs) - elif isinstance(clip,VideoClip): - filename = TEMP_PREFIX+".mp4" - kwargs = {'filename':filename, 'verbose':False, 'preset':'ultrafast'} + elif isinstance(clip, VideoClip): + filename = TEMP_PREFIX + ".mp4" + kwargs = {"filename": filename, "verbose": False, "preset": "ultrafast"} kwargs.update(rd_kwargs) clip.write_videofile(**kwargs) - elif isinstance(clip,AudioClip): - filename = TEMP_PREFIX+".mp3" - kwargs = {'filename': filename, 'verbose':False} + elif isinstance(clip, AudioClip): + filename = TEMP_PREFIX + ".mp3" + kwargs = {"filename": filename, "verbose": False} kwargs.update(rd_kwargs) clip.write_audiofile(**kwargs) else: - raise ValueError("Unknown class for the clip. Cannot embed and preview.") + raise ValueError("Unknown class for the clip. Cannot embed and preview.") + + return html_embed( + filename, + maxduration=maxduration, + rd_kwargs=rd_kwargs, + center=center, + **html_kwargs, + ) - return html_embed(filename, maxduration=maxduration, rd_kwargs=rd_kwargs, - center=center, **html_kwargs) - filename = clip - options = " ".join(["%s='%s'"%(str(k), str(v)) for k,v in html_kwargs.items()]) + options = " ".join(["%s='%s'" % (str(k), str(v)) for k, v in html_kwargs.items()]) name, ext = os.path.splitext(filename) ext = ext[1:] if filetype is None: - ext = filename.split('.')[-1].lower() + ext = filename.split(".")[-1].lower() if ext == "gif": - filetype = 'image' + filetype = "image" elif ext in extensions_dict: - filetype = extensions_dict[ext]['type'] + filetype = extensions_dict[ext]["type"] else: - raise ValueError("No file type is known for the provided file. Please provide " - "argument `filetype` (one of 'image', 'video', 'sound') to the " - "ipython display function.") - - - if filetype== 'video': + raise ValueError( + "No file type is known for the provided file. Please provide " + "argument `filetype` (one of 'image', 'video', 'sound') to the " + "ipython display function." + ) + + if filetype == "video": # The next lines set the HTML5-cvompatible extension and check that the # extension is HTML5-valid - exts_htmltype = {'mp4': 'mp4', 'webm':'webm', 'ogv':'ogg'} - allowed_exts = " ".join(exts_htmltype.keys()) + exts_htmltype = {"mp4": "mp4", "webm": "webm", "ogv": "ogg"} + allowed_exts = " ".join(exts_htmltype.keys()) try: ext = exts_htmltype[ext] except: - raise ValueError("This video extension cannot be displayed in the " - "IPython Notebook. Allowed extensions: "+allowed_exts) - - if filetype in ['audio', 'video']: + raise ValueError( + "This video extension cannot be displayed in the " + "IPython Notebook. Allowed extensions: " + allowed_exts + ) - duration = ffmpeg_parse_infos(filename)['duration'] + if filetype in ["audio", "video"]: + + duration = ffmpeg_parse_infos(filename)["duration"] if duration > maxduration: - raise ValueError("The duration of video %s (%.1f) exceeds the 'maxduration' "%(filename, duration)+ - "attribute. You can increase 'maxduration', by passing 'maxduration' parameter" - "to ipython_display function." - "But note that embedding large videos may take all the memory away !") - + raise ValueError( + "The duration of video %s (%.1f) exceeds the 'maxduration' " + % (filename, duration) + + "attribute. You can increase 'maxduration', by passing 'maxduration' parameter" + "to ipython_display function." + "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") + data = b64encode(f.read()).decode("utf-8") template = templates[filetype] - result = template%{'data':data, 'options':options, 'ext':ext} + result = template % {"data": data, "options": options, "ext": ext} if center: - result = r"
%s
"%result + result = r"
%s
" % result return result -def ipython_display(clip, filetype=None, maxduration=60, t=None, fps=None, - rd_kwargs=None, center=True, **html_kwargs): +def ipython_display( + clip, + filetype=None, + maxduration=60, + t=None, + fps=None, + rd_kwargs=None, + center=True, + **html_kwargs, +): """ clip Either the name of a file, or a clip to preview. The clip will @@ -204,18 +232,26 @@ def ipython_display(clip, filetype=None, maxduration=60, t=None, fps=None, >>> clip.save_frame("first_frame.jpeg") >>> mpy.ipython_display("first_frame.jpeg") """ - + if not ipython_available: raise ImportError("Only works inside an IPython Notebook") if rd_kwargs is None: rd_kwargs = {} - + if fps is not None: - rd_kwargs['fps'] = fps + rd_kwargs["fps"] = fps if t is not None: clip = clip.to_ImageClip(t) - return HTML2(html_embed(clip, filetype=filetype, maxduration=maxduration, - center=center, rd_kwargs=rd_kwargs, **html_kwargs)) + return HTML2( + html_embed( + clip, + filetype=filetype, + maxduration=maxduration, + center=center, + rd_kwargs=rd_kwargs, + **html_kwargs, + ) + ) diff --git a/moviepy/video/io/preview.py b/moviepy/video/io/preview.py index fbab22d41..846f762d4 100644 --- a/moviepy/video/io/preview.py +++ b/moviepy/video/io/preview.py @@ -8,7 +8,7 @@ from moviepy.tools import cvsecs pg.init() -pg.display.set_caption('MoviePy') +pg.display.set_caption("MoviePy") def imdisplay(imarray, screen=None): @@ -42,6 +42,7 @@ def show(clip, t=0, with_mask=True, interactive=False): if with_mask and (clip.mask is not None): import moviepy.video.compositing.CompositeVideoClip as cvc + clip = cvc.CompositeVideoClip([clip.set_position((0, 0))]) img = clip.get_frame(t) imdisplay(img) @@ -57,16 +58,22 @@ def show(clip, t=0, with_mask=True, interactive=False): elif event.type == pg.MOUSEBUTTONDOWN: x, y = pg.mouse.get_pos() rgb = img[y, x] - result.append({'position': (x, y), 'color': rgb}) - print("position, color : ", "%s, %s" % - (str((x, y)), str(rgb))) - time.sleep(.03) + result.append({"position": (x, y), "color": rgb}) + print("position, color : ", "%s, %s" % (str((x, y)), str(rgb))) + time.sleep(0.03) @requires_duration @convert_masks_to_RGB -def preview(clip, fps=15, audio=True, audio_fps=22050, audio_buffersize=3000, - audio_nbytes=2, fullscreen=False): +def preview( + clip, + fps=15, + audio=True, + audio_fps=22050, + audio_buffersize=3000, + audio_nbytes=2, + fullscreen=False, +): """ Displays the clip in a window, at the given frames per second (of movie) rate. It will avoid that the clip be played faster @@ -95,57 +102,58 @@ def preview(clip, fps=15, audio=True, audio_fps=22050, audio_buffersize=3000, flags = pg.FULLSCREEN else: flags = 0 - + # compute and splash the first image screen = pg.display.set_mode(clip.size, flags) - + audio = audio and (clip.audio is not None) - + if audio: # the sound will be played in parrallel. We are not # parralellizing it on different CPUs because it seems that # 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() # launch the thread - audiothread = threading.Thread(target=clip.audio.preview, - args=(audio_fps, - audio_buffersize, - audio_nbytes, - audioFlag, videoFlag)) + audiothread = threading.Thread( + target=clip.audio.preview, + args=(audio_fps, audio_buffersize, audio_nbytes, audioFlag, videoFlag), + ) 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 - + result = [] - + t0 = time.time() - for t in np.arange(1.0 / fps, clip.duration-.001, 1.0 / fps): - + for t in np.arange(1.0 / fps, clip.duration - 0.001, 1.0 / fps): + img = clip.get_frame(t) - + for event in pg.event.get(): - if event.type == pg.QUIT or \ - (event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE): + if event.type == pg.QUIT or ( + event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE + ): if audio: videoFlag.clear() print("Interrupt") return result - + elif event.type == pg.MOUSEBUTTONDOWN: x, y = pg.mouse.get_pos() rgb = img[y, x] - result.append({'time': t, 'position': (x, y), - 'color': rgb}) - print("time, position, color : ", "%.03f, %s, %s" % - (t, str((x, y)), str(rgb))) - + result.append({"time": t, "position": (x, y), "color": rgb}) + print( + "time, position, color : ", + "%.03f, %s, %s" % (t, str((x, y)), str(rgb)), + ) + t1 = time.time() - time.sleep(max(0, t - (t1-t0))) + time.sleep(max(0, t - (t1 - t0))) imdisplay(img, screen) diff --git a/moviepy/video/io/sliders.py b/moviepy/video/io/sliders.py index f94eae0e1..f83bfd050 100644 --- a/moviepy/video/io/sliders.py +++ b/moviepy/video/io/sliders.py @@ -2,7 +2,7 @@ from matplotlib.widgets import Button, Slider -def sliders(f, sliders_properties, wait_for_validation = False): +def sliders(f, 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 ) @@ -15,60 +15,56 @@ def volume(x,y,z): { 'label' : 'depth', 'valmin': 1 , 'valmax': 5 } ] inputExplorer(volume,intervals) """ - + nVars = len(sliders_properties) - slider_width = 1.0/nVars - + slider_width = 1.0 / nVars + # CREATE THE CANVAS - - figure,ax = plt.subplots(1) - figure.canvas.set_window_title( "Inputs for '%s'"%(f.func_name) ) - + + figure, ax = plt.subplots(1) + figure.canvas.set_window_title("Inputs for '%s'" % (f.func_name)) + # choose an appropriate height - - width,height = figure.get_size_inches() - height = min(0.5*nVars,8) - figure.set_size_inches(width,height,forward = True) - - + + width, height = figure.get_size_inches() + height = min(0.5 * nVars, 8) + figure.set_size_inches(width, height, forward=True) + # hide the axis ax.set_frame_on(False) ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) - # CREATE THE SLIDERS - + sliders = [] - - for i, properties in enumerate(sliders_properties): - ax = plt.axes([0.1 , 0.95-0.9*(i+1)*slider_width, - 0.8 , 0.8* slider_width]) - if not isinstance(properties,dict): - properties =dict(zip(['label','valmin', 'valmax', 'valinit'], - properties)) - sliders.append( Slider(ax=ax, **properties) ) - - + + for i, properties in enumerate(sliders_properties): + ax = plt.axes( + [0.1, 0.95 - 0.9 * (i + 1) * slider_width, 0.8, 0.8 * slider_width] + ) + if not isinstance(properties, dict): + properties = dict(zip(["label", "valmin", "valmax", "valinit"], properties)) + sliders.append(Slider(ax=ax, **properties)) + # CREATE THE CALLBACK FUNCTIONS - - def on_changed(event) : + + def on_changed(event): res = f(*(s.val for s in sliders)) if res is not None: - print( res ) - + print(res) + def on_key_press(event): - if event.key is 'enter': - on_changed(event) - - figure.canvas.mpl_connect('key_press_event', on_key_press) - + if event.key is "enter": + on_changed(event) + + figure.canvas.mpl_connect("key_press_event", on_key_press) + # AUTOMATIC UPDATE ? - + if not wait_for_validation: - for s in sliders : + for s in sliders: s.on_changed(on_changed) - - + # DISPLAY THE SLIDERS plt.show() diff --git a/moviepy/video/tools/credits.py b/moviepy/video/tools/credits.py index a5adfbb1b..5403eb41b 100644 --- a/moviepy/video/tools/credits.py +++ b/moviepy/video/tools/credits.py @@ -9,8 +9,17 @@ from moviepy.video.VideoClip import ImageClip, TextClip -def credits1(creditfile, width, stretch=30, color='white', stroke_color='black', - stroke_width=2, font='Impact-Normal', fontsize=60, gap=0): +def credits1( + creditfile, + width, + stretch=30, + color="white", + stroke_color="black", + stroke_width=2, + font="Impact-Normal", + fontsize=60, + gap=0, +): """ Parameters @@ -75,44 +84,54 @@ def credits1(creditfile, width, stretch=30, color='white', stroke_color='black', # PARSE THE TXT FILE texts = [] oneline = True - + with open(creditfile) as f: for l in f: - if l.startswith(('\n', '#')): + 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:], '']) + 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]) + texts.append(["", l]) oneline = False else: - texts.append(['\n', l]) - + 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), - bg_color=None) - + + # 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), + bg_color=None, + ) + # SCALE TO THE REQUIRED SIZE - + scaled = resize(cc, width=width) - + # TRANSFORM THE WHOLE CREDIT CLIP INTO AN ImageCLip - + imclip = ImageClip(scaled.get_frame(0)) amask = ImageClip(scaled.mask.get_frame(0), ismask=True) - + return imclip.set_mask(amask) diff --git a/moviepy/video/tools/cuts.py b/moviepy/video/tools/cuts.py index de75f788a..d98adfb30 100644 --- a/moviepy/video/tools/cuts.py +++ b/moviepy/video/tools/cuts.py @@ -9,13 +9,13 @@ @use_clip_fps_by_default -def find_video_period(clip,fps=None,tmin=.3): +def find_video_period(clip, fps=None, tmin=0.3): """ Finds the period of a video based on frames correlation """ - + frame = lambda t: clip.get_frame(t).flatten() - tt = np.arange(tmin, clip.duration, 1.0/ fps)[1:] + tt = np.arange(tmin, clip.duration, 1.0 / fps)[1:] ref = frame(0) - corrs = [ np.corrcoef(ref, frame(t))[0,1] for t in tt] + corrs = [np.corrcoef(ref, frame(t))[0, 1] for t in tt] return tt[np.argmax(corrs)] @@ -44,32 +44,39 @@ def __init__(self, t1, t2, d_min, d_max): self.t2 = t2 self.d_min = d_min self.d_max = d_max - self.time_span = t2-t1 + self.time_span = t2 - t1 def __str__(self): - return '(%.04f, %.04f, %.04f, %.04f)'%( - self.t1, self.t2, self.d_min, self.d_max) + return "(%.04f, %.04f, %.04f, %.04f)" % ( + self.t1, + self.t2, + self.d_min, + self.d_max, + ) def __repr__(self): - return '(%.04f, %.04f, %.04f, %.04f)'%( - self.t1, self.t2, self.d_min, self.d_max) + return "(%.04f, %.04f, %.04f, %.04f)" % ( + self.t1, + self.t2, + self.d_min, + self.d_max, + ) def __iter__(self): return iter((self.t1, self.t2, self.d_min, self.d_max)) class FramesMatches(list): - def __init__(self, lst): list.__init__(self, sorted(lst, key=lambda e: e.d_max)) 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]) - + n = len(self) * percent / 100 + return self[0] if n == 1 else FramesMatches(self[:n]) + def filter(self, cond): """ Returns a FramesMatches object obtained by filtering out the FramesMatch @@ -84,8 +91,12 @@ def filter(self, cond): return FramesMatches(filter(cond, self)) def save(self, filename): - np.savetxt(filename, np.array([np.array(list(e)) for e in self]), - fmt='%.03f', delimiter='\t') + np.savetxt( + filename, + np.array([np.array(list(e)) for e in self]), + fmt="%.03f", + delimiter="\t", + ) @staticmethod def load(filename): @@ -96,8 +107,6 @@ def load(filename): mfs = [FramesMatch(*e) for e in arr] return FramesMatches(mfs) - - @staticmethod def from_clip(clip, dist_thr, max_d, fps=None): """ Finds all the frames tht look alike in a clip, for instance to make a @@ -136,71 +145,74 @@ def from_clip(clip, dist_thr, max_d, fps=None): fps Frames per second (default will be clip.fps) - """ - + """ + N_pixels = clip.w * clip.h * 3 - dot_product = lambda F1, F2: (F1*F2).sum()/N_pixels - F = {} # will store the frames and their mutual distances - + dot_product = lambda F1, F2: (F1 * F2).sum() / N_pixels + F = {} # 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'] - return np.sqrt(u+v - 2*uv) - - matching_frames = [] # the final result. - - for (t,frame) in clip.iter_frames(with_times=True, logger='bar'): - - flat_frame = 1.0*frame.flatten() + uv = dot_product(F[t1]["frame"], F[t2]["frame"]) + u, v = F[t1]["|F|sq"], F[t2]["|F|sq"] + return np.sqrt(u + v - 2 * uv) + + matching_frames = [] # the final result. + + for (t, frame) in clip.iter_frames(with_times=True, logger="bar"): + + flat_frame = 1.0 * frame.flatten() F_norm_sq = dot_product(flat_frame, flat_frame) F_norm = np.sqrt(F_norm_sq) - + for t2 in list(F.keys()): # forget old frames, add 't' to the others frames # check for early rejections based on differing norms - if (t-t2) > max_d: + if (t - t2) > max_d: F.pop(t2) else: - F[t2][t] = {'min':abs(F[t2]['|F|'] - F_norm), - 'max':F[t2]['|F|'] + F_norm} - F[t2][t]['rejected']= (F[t2][t]['min'] > dist_thr) - + F[t2][t] = { + "min": abs(F[t2]["|F|"] - F_norm), + "max": F[t2]["|F|"] + F_norm, + } + F[t2][t]["rejected"] = F[t2][t]["min"] > dist_thr + t_F = sorted(F.keys()) - - F[t] = {'frame': flat_frame, '|F|sq': F_norm_sq, '|F|': F_norm} - - for i,t2 in enumerate(t_F): + + F[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 F[t2][t]["rejected"]: continue - + dist = distance(t, t2) - F[t2][t]['min'] = F[t2][t]['max'] = dist - F[t2][t]['rejected'] = (dist >= dist_thr) - - for t3 in t_F[i+1:]: + F[t2][t]["min"] = F[t2][t]["max"] = dist + F[t2][t]["rejected"] = dist >= dist_thr + + for t3 in t_F[i + 1 :]: # For all the next times t3, use d(F(t), F(t2)) 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['max'] = min(t3t['max'], dist+ t2t3['max']) - t3t['min'] = max(t3t['min'], dist - t2t3['max'], - t2t3['min'] - dist) - - if t3t['min'] > dist_thr: - t3t['rejected'] = True - - # Store all the good matches (t2,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']] - - return FramesMatches([FramesMatch(*e) for e in matching_frames]) + t3t["max"] = min(t3t["max"], dist + t2t3["max"]) + t3t["min"] = max(t3t["min"], dist - t2t3["max"], t2t3["min"] - dist) + if t3t["min"] > dist_thr: + t3t["rejected"] = True + # Store all the good matches (t2,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"] + ] - def select_scenes(self, match_thr, min_time_span, nomatch_thr=None, - time_distance=0): + return FramesMatches([FramesMatch(*e) for e in matching_frames]) + + def select_scenes( + self, match_thr, min_time_span, nomatch_thr=None, time_distance=0 + ): """ match_thr @@ -218,58 +230,59 @@ def select_scenes(self, match_thr, min_time_span, nomatch_thr=None, if nomatch_thr is None: nomatch_thr = match_thr - dict_starts = defaultdict(lambda : []) + dict_starts = defaultdict(lambda: []) for (start, end, d_min, d_max) in self: dict_starts[start].append([end, d_min, d_max]) - starts_ends = sorted(dict_starts.items(), key = lambda k: k[0]) - + starts_ends = sorted(dict_starts.items(), key=lambda k: k[0]) + result = [] - min_start= 0 + min_start = 0 for start, ends_distances in starts_ends: if start < min_start: continue ends = [end for (end, d_min, d_max) in ends_distances] - great_matches = [(end,d_min, d_max) - for (end,d_min, d_max) in ends_distances - if d_maxmin_time_span] - - + great_matches = [ + (end, d_min, d_max) + for (end, d_min, d_max) in ends_distances + if d_max < match_thr + ] + + great_long_matches = [ + (end, d_min, d_max) + for (end, d_min, d_max) in great_matches + if (end - start) > min_time_span + ] + if not great_long_matches: - 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} - short_matches = {end for end in ends if (end-start) <= 0.6} - + 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 + } + 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, d_min, d_max = next(e for e in great_long_matches if e[0] == end) + result.append(FramesMatch(start, end, d_min, d_max)) min_start = start + time_distance return FramesMatches(result) - def write_gifs(self, clip, gif_dir): - for (start, end, _, _) in self: - name = "%s/%08d_%08d.gif" % (gif_dir, 100*start, 100*end) + for (start, end, _, _) in self: + name = "%s/%08d_%08d.gif" % (gif_dir, 100 * start, 100 * end) clip.subclip(start, end).write_gif(name, verbose=False) - - @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, thr=10, logger="bar", fps=None): """ Detects scenes of a clip based on luminosity changes. Note that for large clip this may take some time @@ -309,20 +322,21 @@ def detect_scenes(clip=None, luminosities=None, thr=10, fps attribute. - """ + """ if luminosities is None: - luminosities = [f.sum() for f in clip.iter_frames( - fps=fps, dtype='uint32', logger=logger)] - + luminosities = [ + f.sum() for f in clip.iter_frames(fps=fps, dtype="uint32", logger=logger) + ] + luminosities = np.array(luminosities, dtype=float) if clip is not None: end = clip.duration else: - end = len(luminosities)*(1.0/fps) + 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_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:])] return cuts, luminosities diff --git a/moviepy/video/tools/drawing.py b/moviepy/video/tools/drawing.py index fdc370554..d3eabc8e3 100644 --- a/moviepy/video/tools/drawing.py +++ b/moviepy/video/tools/drawing.py @@ -44,14 +44,14 @@ def blit(im1, im2, pos=None, mask=None, ismask=False): if len(im1.shape) == 3: mask = np.dstack(3 * [mask]) 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 + 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 -def color_gradient(size,p1,p2=None,vector=None, r=None, col1=0,col2=1.0, - shape='linear', offset = 0): +def color_gradient( + size, p1, p2=None, vector=None, r=None, col1=0, col2=1.0, shape="linear", offset=0 +): """Draw a linear, bilinear, or radial gradient. The result is a picture of size ``size``, whose color varies @@ -114,70 +114,73 @@ def color_gradient(size,p1,p2=None,vector=None, r=None, col1=0,col2=1.0, >>> grad = color_gradient(blabla).astype('uint8') """ - + # 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) - - if shape == 'bilinear': + w, h = size + + col1 = np.array(col1).astype(float) + col2 = np.array(col2).astype(float) + + if shape == "bilinear": if vector is None: vector = np.array(p2) - np.array(p1) - - m1, m2 = [ color_gradient(size, p1, vector=v, col1 = 1.0, col2 = 0, - shape = 'linear', offset= offset) - for v in [vector,-vector]] - + + m1, m2 = [ + color_gradient( + size, p1, vector=v, col1=1.0, col2=0, shape="linear", offset=offset + ) + for v in [vector, -vector] + ] + arr = np.maximum(m1, m2) if col1.size > 1: - arr = np.dstack(3*[arr]) - return arr*col1 + (1-arr)*col2 - - + arr = np.dstack(3 * [arr]) + return arr * col1 + (1 - arr) * col2 + p1 = np.array(p1[::-1]).astype(float) - + if vector is None and p2: p2 = np.array(p2[::-1]) - vector = p2-p1 + vector = p2 - p1 else: vector = np.array(vector[::-1]) p2 = p1 + vector - - if vector: + + if vector: norm = np.linalg.norm(vector) - - M = np.dstack(np.meshgrid(range(w),range(h))[::-1]).astype(float) - - if shape == 'linear': - - n_vec = vector/norm**2 # norm 1/norm(vector) - - p1 = p1 + offset*vector - arr = (M- p1).dot(n_vec)/(1-offset) - arr = np.minimum(1,np.maximum(0,arr)) + + M = np.dstack(np.meshgrid(range(w), range(h))[::-1]).astype(float) + + if shape == "linear": + + n_vec = vector / norm ** 2 # norm 1/norm(vector) + + p1 = p1 + offset * vector + arr = (M - p1).dot(n_vec) / (1 - offset) + arr = np.minimum(1, np.maximum(0, arr)) if col1.size > 1: - arr = np.dstack(3*[arr]) - return arr*col1 + (1-arr)*col2 - - elif shape == 'radial': + arr = np.dstack(3 * [arr]) + return arr * col1 + (1 - arr) * col2 + + elif shape == "radial": if r is None: - r = norm + r = norm if r == 0: - arr = np.ones((h,w)) + arr = np.ones((h, w)) else: arr = (np.sqrt(((M - p1) ** 2).sum(axis=2))) - offset * r - arr = arr / ((1-offset)*r) + arr = arr / ((1 - offset) * r) arr = np.minimum(1.0, np.maximum(0, arr)) - + if col1.size > 1: - arr = np.dstack(3*[arr]) - return (1-arr)*col1 + arr*col2 - + arr = np.dstack(3 * [arr]) + return (1 - arr) * col1 + arr * col2 -def color_split(size,x=None,y=None,p1=None,p2=None,vector=None, - col1=0,col2=1.0, grad_width=0): + +def color_split( + size, x=None, y=None, p1=None, p2=None, vector=None, col1=0, col2=1.0, grad_width=0 +): """Make an image splitted in 2 colored regions. Returns an array of size ``size`` divided in two regions called 1 and @@ -223,39 +226,41 @@ def color_split(size,x=None,y=None,p1=None,p2=None,vector=None, >>> color_split(size, p1=[20,50], p2=[25,70] col1=0, col2=1) """ - - if grad_width or ( (x is None) and (y is None)): + + if grad_width or ((x is None) and (y is None)): if p2 is not None: - vector = (np.array(p2) - np.array(p1)) + vector = np.array(p2) - np.array(p1) elif x is not None: - vector = np.array([0,-1.0]) + vector = np.array([0, -1.0]) p1 = np.array([x, 0]) elif y is not None: vector = np.array([1.0, 0.0]) - p1 = np.array([0,y]) + p1 = np.array([0, y]) - x,y = vector - vector = np.array([y,-x]).astype('float') + x, y = vector + vector = np.array([y, -x]).astype("float") norm = np.linalg.norm(vector) vector = max(0.1, grad_width) * vector / norm - return color_gradient(size,p1,vector=vector, - col1 = col1, col2 = col2, shape='linear') + return color_gradient( + size, p1, vector=vector, col1=col1, col2=col2, shape="linear" + ) else: w, h = size shape = (h, w) if np.isscalar(col1) else (h, w, len(col1)) arr = np.zeros(shape) if x: - arr[:,:x] = col1 - arr[:,x:] = col2 + arr[:, :x] = col1 + arr[:, x:] = col2 elif y: arr[:y] = col1 arr[y:] = col2 return arr - + # if we are here, it means we didn't exit with a proper 'return' - print( "Arguments in color_split not understood !" ) + print("Arguments in color_split not understood !") raise - + + def circle(screensize, center, radius, col1=1.0, col2=0, blur=1): """ Draw an image with a circle. @@ -264,6 +269,13 @@ def circle(screensize, center, radius, col1=1.0, col2=0, blur=1): with a radius ``radius`` but slightly blurred on the border by ``blur`` pixels """ - offset = 1.0*(radius-blur)/radius if radius else 0 - return color_gradient(screensize,p1=center,r=radius, col1=col1, - col2=col2, shape='radial', offset=offset) + offset = 1.0 * (radius - blur) / radius if radius else 0 + return color_gradient( + screensize, + p1=center, + r=radius, + col1=col1, + col2=col2, + shape="radial", + offset=offset, + ) diff --git a/moviepy/video/tools/interpolators.py b/moviepy/video/tools/interpolators.py index 9d1f4955a..be2f43cbf 100644 --- a/moviepy/video/tools/interpolators.py +++ b/moviepy/video/tools/interpolators.py @@ -8,14 +8,14 @@ class Interpolator: """ Poorman's linear interpolator, doesn't require Scipy. """ - - def __init__(self, tt=None, ss=None, ttss = None, left=None, right=None): + + def __init__(self, tt=None, ss=None, ttss=None, left=None, right=None): if ttss is not None: tt, ss = zip(*ttss) - - self.tt = 1.0*np.array(tt) - self.ss = 1.0*np.array(ss) + + self.tt = 1.0 * np.array(tt) + self.ss = 1.0 * np.array(ss) self.left = left self.right = right self.tmin, self.tmax = min(tt), max(tt) @@ -23,11 +23,11 @@ def __init__(self, tt=None, ss=None, ttss = None, left=None, right=None): def __call__(self, t): return np.interp(t, self.tt, self.ss, self.left, self.right) -class Trajectory: +class Trajectory: def __init__(self, tt, xx, yy): - self.tt = 1.0*np.array(tt) + self.tt = 1.0 * np.array(tt) self.xx = np.array(xx) self.yy = np.array(yy) self.update_interpolators() @@ -36,38 +36,44 @@ def __call__(self, t): return np.array([self.xi(t), self.yi(t)]) def addx(self, x): - return Trajectory(self.tt, self.xx+x, self.yy) + return Trajectory(self.tt, self.xx + x, self.yy) def addy(self, y): - return Trajectory(self.tt, self.xx, self.yy+y) + return Trajectory(self.tt, self.xx, self.yy + y) def update_interpolators(self): - self.xi = Interpolator(self.tt, self.xx) - self.yi = Interpolator(self.tt, self.yy) - + self.xi = Interpolator(self.tt, self.xx) + self.yi = Interpolator(self.tt, self.yy) + def txy(self, tms=False): - return zip((1000 if tms else 1)*self.tt, self.xx, self.yy) + return zip((1000 if tms else 1) * self.tt, self.xx, self.yy) def to_file(self, filename): - np.savetxt(filename, np.array(self.txy(tms=True)), - fmt="%d", delimiter='\t') + np.savetxt(filename, np.array(self.txy(tms=True)), fmt="%d", delimiter="\t") @staticmethod def from_file(filename): - arr = np.loadtxt(filename, delimiter='\t') + arr = np.loadtxt(filename, delimiter="\t") tt, xx, yy = arr.T - return Trajectory(1.0*tt/1000, xx, yy) + return Trajectory(1.0 * tt / 1000, xx, yy) @staticmethod def save_list(trajs, filename): N = len(trajs) arr = np.hstack([np.array(list(t.txy(tms=True))) for t in trajs]) - np.savetxt( filename, arr, fmt="%d", delimiter='\t', - header = "\t".join(N*['t(ms)', 'x', 'y'])) - + np.savetxt( + filename, + arr, + fmt="%d", + delimiter="\t", + header="\t".join(N * ["t(ms)", "x", "y"]), + ) + @staticmethod def load_list(filename): - arr = np.loadtxt(filename, delimiter='\t').T + arr = np.loadtxt(filename, delimiter="\t").T Nlines = arr.shape[0] - return [Trajectory(tt=1.0*a[0]/1000, xx=a[1], yy=a[2]) - for a in np.split(arr, Nlines/3)] + return [ + Trajectory(tt=1.0 * a[0] / 1000, xx=a[1], yy=a[2]) + for a in np.split(arr, Nlines / 3) + ] diff --git a/moviepy/video/tools/segmenting.py b/moviepy/video/tools/segmenting.py index 1e6c06bf5..8ddea0aca 100644 --- a/moviepy/video/tools/segmenting.py +++ b/moviepy/video/tools/segmenting.py @@ -4,7 +4,7 @@ from moviepy.video.VideoClip import ImageClip -def findObjects(clip,rem_thr=500, preview=False): +def findObjects(clip, rem_thr=500, preview=False): """ Returns a list of ImageClips representing each a separate object on the screen. @@ -13,47 +13,48 @@ def findObjects(clip,rem_thr=500, preview=False): considered false positives and will be removed """ - + image = clip.get_frame(0) if not clip.mask: clip = clip.add_mask() - + mask = clip.mask.get_frame(0) - labelled, num_features = ndi.measurements.label(image[:,:,0]) - - #find the objects + labelled, num_features = ndi.measurements.label(image[:, :, 0]) + + # find the objects slices = [] for e in ndi.find_objects(labelled): - if mask[e[0],e[1]].mean() <= 0.2: + if mask[e[0], e[1]].mean() <= 0.2: # remove letter holes (in o,e,a, etc.) continue - if image[e[0],e[1]].size <= rem_thr: + if image[e[0], e[1]].size <= rem_thr: # remove very small slices continue slices.append(e) - islices = sorted(enumerate(slices), key = lambda s : s[1][1].start) - + islices = sorted(enumerate(slices), key=lambda s: s[1][1].start) + letters = [] - for i,(ind,(sy,sx)) in enumerate(islices): + for i, (ind, (sy, sx)) in enumerate(islices): """ 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] - letter = ImageClip(image[sy,sx]) - letter.mask = ImageClip( maskletter,ismask=True) - letter.screenpos = np.array((sx.start,sy.start)) + 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] + letter = ImageClip(image[sy, sx]) + letter.mask = ImageClip(maskletter, ismask=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) ) - fig,ax = plt.subplots(2) - ax[0].axis('off') + + print("found %d objects" % (num_features)) + fig, ax = plt.subplots(2) + ax[0].axis("off") ax[0].imshow(labelled) - ax[1].imshow([range(num_features)],interpolation='nearest') + ax[1].imshow([range(num_features)], interpolation="nearest") ax[1].set_yticks([]) plt.show() - + return letters diff --git a/moviepy/video/tools/subtitles.py b/moviepy/video/tools/subtitles.py index 34a8615c2..25a23a0df 100644 --- a/moviepy/video/tools/subtitles.py +++ b/moviepy/video/tools/subtitles.py @@ -35,35 +35,46 @@ class SubtitlesClip(VideoClip): """ def __init__(self, subtitles, make_textclip=None): - + VideoClip.__init__(self, has_constant_size=False) if isinstance(subtitles, str): subtitles = file_to_subtitles(subtitles) - #subtitles = [(map(cvsecs, tt),txt) for tt, txt in subtitles] + # subtitles = [(map(cvsecs, tt),txt) for tt, txt in subtitles] self.subtitles = subtitles self.textclips = dict() if make_textclip is None: - make_textclip = lambda txt: TextClip(txt, font='Georgia-Bold', - fontsize=24, color='white', - stroke_color='black', stroke_width=0.5) + make_textclip = lambda txt: TextClip( + txt, + font="Georgia-Bold", + fontsize=24, + color="white", + stroke_color="black", + stroke_width=0.5, + ) self.make_textclip = make_textclip - self.start=0 - self.duration = max([tb for ((ta,tb), txt) in self.subtitles]) - self.end=self.duration - + self.start = 0 + self.duration = max([tb for ((ta, tb), txt) in self.subtitles]) + self.end = self.duration + def add_textclip_if_none(t): """ Will generate a textclip if it hasn't been generated asked 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>> traj, = Trajectory.load_list('track.txt') """ - + import pygame as pg screen = pg.display.set_mode(clip.size) step = 1.0 / fps if (t1 is None) and (t2 is None): - t1,t2 = 0, clip.duration - elif (t2 is None): + t1, t2 = 0, clip.duration + elif t2 is None: t2 = t1 + step / 2 t = t1 txy_list = [] - + def gatherClicks(t): - + imdisplay(clip.get_frame(t), screen) objects_to_click = nobjects clicks = [] @@ -98,36 +99,35 @@ def gatherClicks(t): for event in pg.event.get(): if event.type == pg.KEYDOWN: - if (event.key == pg.K_BACKSLASH): + if event.key == pg.K_BACKSLASH: return "return" - elif (event.key == pg.K_ESCAPE): + elif event.key == pg.K_ESCAPE: raise KeyboardInterrupt() - elif event.type == pg.MOUSEBUTTONDOWN: x, y = pg.mouse.get_pos() clicks.append((x, y)) objects_to_click -= 1 - + return clicks - + while t < t2: - - clicks =gatherClicks(t) - if clicks == 'return': + + clicks = gatherClicks(t) + if clicks == "return": txy_list.pop() t -= step else: - txy_list.append((t,clicks)) + txy_list.append((t, clicks)) t += step - tt, xylist = zip(*txy_list) + tt, xylist = zip(*txy_list) result = [] for i in range(nobjects): xys = [e[i] for e in xylist] xx, yy = zip(*xys) result.append(Trajectory(tt, xx, yy)) - + if savefile is not None: Trajectory.save_list(result, savefile) return result @@ -135,22 +135,23 @@ def gatherClicks(t): # AUTOMATED TRACKING OF A PATTERN -def findAround(pic,pat,xy=None,r=None): + +def findAround(pic, pat, xy=None, r=None): """ find image pattern ``pat`` in ``pic[x +/- r, y +/- r]``. if xy is none, consider the whole picture. """ - + if xy and r: - h,w = pat.shape[:2] - x,y = xy - pic = pic[y-r : y+h+r , x-r : x+w+r] - - matches = cv2.matchTemplate(pat,pic,cv2.TM_CCOEFF_NORMED) - yf,xf = np.unravel_index(matches.argmax(),matches.shape) - return (x-r+xf,y-r+yf) if (xy and r) else (xf,yf) - - + h, w = pat.shape[:2] + x, y = xy + pic = pic[y - r : y + h + r, x - r : x + w + r] + + matches = cv2.matchTemplate(pat, pic, cv2.TM_CCOEFF_NORMED) + yf, xf = np.unravel_index(matches.argmax(), matches.shape) + return (x - r + xf, y - r + yf) if (xy and r) else (xf, yf) + + def autoTrack(clip, pattern, tt=None, fps=None, radius=20, xy0=None): """ Tracks a given pattern (small image array) in a video clip. @@ -166,21 +167,21 @@ def autoTrack(clip, pattern, tt=None, fps=None, radius=20, xy0=None): """ if not autotracking_possible: - raise IOError("Sorry, autotrack requires OpenCV for the moment. " - "Install OpenCV (aka cv2) to use it.") - + raise IOError( + "Sorry, autotrack requires OpenCV for the moment. " + "Install OpenCV (aka cv2) to use it." + ) if not xy0: - xy0 = findAround(clip.get_frame(tt[0]),pattern) - + xy0 = findAround(clip.get_frame(tt[0]), pattern) + if tt is None: - tt = np.arange(0, clip.duration, 1.0/fps) - + tt = np.arange(0, clip.duration, 1.0 / fps) + xys = [xy0] for t in tt[1:]: - xys.append( findAround(clip.get_frame(t),pattern, - xy=xys[-1],r=radius)) - - xx,yy = zip(*xys) + xys.append(findAround(clip.get_frame(t), pattern, xy=xys[-1], r=radius)) + + xx, yy = zip(*xys) return Trajectory(tt, xx, yy) diff --git a/setup.py b/setup.py index d143cf10b..c6e41a246 100644 --- a/setup.py +++ b/setup.py @@ -11,17 +11,20 @@ except ImportError: try: import ez_setup + ez_setup.use_setuptools() except ImportError: - raise ImportError('MoviePy could not be installed, probably because' - ' neither setuptools nor ez_setup are installed on this computer.' - '\nInstall ez_setup ([sudo] pip install ez_setup) and try again.') + raise ImportError( + "MoviePy could not be installed, probably because" + " neither setuptools nor ez_setup are installed on this computer." + "\nInstall ez_setup ([sudo] pip install ez_setup) and try again." + ) class PyTest(TestCommand): """Handle test execution from setup.""" - user_options = [('pytest-args=', 'a', "Arguments to pass into pytest")] + user_options = [("pytest-args=", "a", "Arguments to pass into pytest")] def initialize_options(self): """Initialize the PyTest options.""" @@ -39,108 +42,108 @@ def run_tests(self): try: import pytest except ImportError: - raise ImportError('Running tests requires additional dependencies.' - '\nPlease run (pip install moviepy[test])') + raise ImportError( + "Running tests requires additional dependencies." + "\nPlease run (pip install moviepy[test])" + ) errno = pytest.main(self.pytest_args.split(" ")) sys.exit(errno) -cmdclass = {'test': PyTest} # Define custom commands. +cmdclass = {"test": PyTest} # Define custom commands. -if 'build_docs' in sys.argv: +if "build_docs" in sys.argv: try: from sphinx.setup_command import BuildDoc except ImportError: - raise ImportError('Running the documenation builds has additional' - ' dependencies. Please run (pip install moviepy[docs])') + raise ImportError( + "Running the documenation builds has additional" + " dependencies. Please run (pip install moviepy[docs])" + ) - cmdclass['build_docs'] = BuildDoc + cmdclass["build_docs"] = BuildDoc -__version__ = None # Explicitly set version to quieten static code checkers. -exec(open('moviepy/version.py').read()) # loads __version__ +__version__ = None # Explicitly set version to quieten static code checkers. +exec(open("moviepy/version.py").read()) # loads __version__ # Define the requirements for specific execution needs. requires = [ - 'decorator>=4.0.2,<5.0', + "decorator>=4.0.2,<5.0", "imageio>=2.5,<3.0", "imageio_ffmpeg>=0.2.0", - 'tqdm>=4.11.2,<5.0', + "tqdm>=4.11.2,<5.0", "numpy>=1.17.3", - 'requests>=2.8.1,<3.0', - 'proglog<=1.0.0' - ] + "requests>=2.8.1,<3.0", + "proglog<=1.0.0", +] optional_reqs = [ - "opencv-python>=3.0,<4.0", - "scikit-image>=0.13.0,<1.0", - "scikit-learn", - "scipy>=0.19.0,<1.5", - "matplotlib>=2.0.0,<3.0", - "youtube_dl" - ] + "opencv-python>=3.0,<4.0", + "scikit-image>=0.13.0,<1.0", + "scikit-learn", + "scipy>=0.19.0,<1.5", + "matplotlib>=2.0.0,<3.0", + "youtube_dl", +] doc_reqs = [ - "pygame>=1.9.3,<2.0; python_version<'3.8'", - 'numpydoc>=0.6.0,<1.0', - 'sphinx_rtd_theme>=0.1.10b0,<1.0', - 'Sphinx>=1.5.2,<2.0', - ] + "pygame>=1.9.3,<2.0; python_version<'3.8'", + "numpydoc>=0.6.0,<1.0", + "sphinx_rtd_theme>=0.1.10b0,<1.0", + "Sphinx>=1.5.2,<2.0", +] test_reqs = [ - 'coverage<5.0', - 'coveralls>=1.1,<2.0', - 'pytest-cov>=2.5.1,<3.0', - 'pytest>=3.0.0,<4.0', - 'requests>=2.8.1,<3.0' - ] - -extra_reqs = { - "optional": optional_reqs, - "doc": doc_reqs, - "test": test_reqs - } + "coverage<5.0", + "coveralls>=1.1,<2.0", + "pytest-cov>=2.5.1,<3.0", + "pytest>=3.0.0,<4.0", + "requests>=2.8.1,<3.0", +] + +extra_reqs = {"optional": optional_reqs, "doc": doc_reqs, "test": test_reqs} # Load the README. -with open('README.rst', 'r', 'utf-8') as f: +with open("README.rst", "r", "utf-8") as f: readme = f.read() setup( - name='moviepy', + name="moviepy", version=__version__, - author='Zulko 2017', - description='Video editing with Python', + author="Zulko 2017", + description="Video editing with Python", long_description=readme, - url='https://zulko.github.io/moviepy/', - license='MIT License', + url="https://zulko.github.io/moviepy/", + license="MIT License", classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - - 'Topic :: Multimedia', - 'Topic :: Multimedia :: Sound/Audio', - 'Topic :: Multimedia :: Sound/Audio :: Analysis', - 'Topic :: Multimedia :: Video', - 'Topic :: Multimedia :: Video :: Capture', - 'Topic :: Multimedia :: Video :: Conversion', + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Natural Language :: English", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Topic :: Multimedia", + "Topic :: Multimedia :: Sound/Audio", + "Topic :: Multimedia :: Sound/Audio :: Analysis", + "Topic :: Multimedia :: Video", + "Topic :: Multimedia :: Video :: Capture", + "Topic :: Multimedia :: Video :: Conversion", ], - keywords='video editing audio compositing ffmpeg', - packages=find_packages(exclude=['docs', 'tests']), + keywords="video editing audio compositing ffmpeg", + packages=find_packages(exclude=["docs", "tests"]), cmdclass=cmdclass, command_options={ - 'build_docs': { - 'build_dir': ('setup.py', './docs/build'), - 'config_dir': ('setup.py', './docs'), - 'version': ('setup.py', __version__.rsplit('.', 2)[0]), - 'release': ('setup.py', __version__)}}, + "build_docs": { + "build_dir": ("setup.py", "./docs/build"), + "config_dir": ("setup.py", "./docs"), + "version": ("setup.py", __version__.rsplit(".", 2)[0]), + "release": ("setup.py", __version__), + } + }, tests_require=test_reqs, install_requires=requires, extras_require=extra_reqs, diff --git a/tests/test_AudioClips.py b/tests/test_AudioClips.py index 70fb93075..3e1d4ea2b 100644 --- a/tests/test_AudioClips.py +++ b/tests/test_AudioClips.py @@ -6,17 +6,21 @@ import pytest from numpy import pi, sin -from moviepy.audio.AudioClip import (AudioClip, CompositeAudioClip, - concatenate_audioclips) +from moviepy.audio.AudioClip import ( + AudioClip, + CompositeAudioClip, + concatenate_audioclips, +) from moviepy.audio.io.AudioFileClip import AudioFileClip from .test_helper import TMP_DIR skip_if_windows = pytest.mark.skipif( sys.platform.startswith("win"), - reason="Temporarily skipping on windows because otherwise test suite fails with Invalid Handle Error" + reason="Temporarily skipping on windows because otherwise test suite fails with Invalid Handle Error", ) + @skip_if_windows def test_audio_coreader(): sound = AudioFileClip("media/crunching.mp3") @@ -24,11 +28,13 @@ def test_audio_coreader(): sound2 = AudioFileClip("media/crunching.mp3") sound2.write_audiofile(os.path.join(TMP_DIR, "coreader.mp3")) + def test_audioclip(): make_frame = lambda t: [sin(440 * 2 * pi * t)] clip = AudioClip(make_frame, duration=2, fps=22050) clip.write_audiofile(os.path.join(TMP_DIR, "audioclip.mp3")) + def test_audioclip_concat(): make_frame_440 = lambda t: [sin(440 * 2 * pi * t)] make_frame_880 = lambda t: [sin(880 * 2 * pi * t)] @@ -60,7 +66,9 @@ def test_audioclip_with_file_concat(): # Fails with strange error # "ValueError: operands could not be broadcast together with # shapes (1993,2) (1993,1993)1 - concat_clip.write_audiofile(os.path.join(TMP_DIR, "concat_clip_with_file_audio.mp3")) + concat_clip.write_audiofile( + os.path.join(TMP_DIR, "concat_clip_with_file_audio.mp3") + ) def test_audiofileclip_concat(): diff --git a/tests/test_ImageSequenceClip.py b/tests/test_ImageSequenceClip.py index 1fe3d86fe..a2fc91e7b 100644 --- a/tests/test_ImageSequenceClip.py +++ b/tests/test_ImageSequenceClip.py @@ -11,8 +11,8 @@ def test_1(): - images=[] - durations=[] + images = [] + durations = [] for i in range(5): durations.append(i) @@ -24,19 +24,20 @@ def test_1(): assert clip.duration == sum(durations) clip.write_videofile(os.path.join(TMP_DIR, "ImageSequenceClip1.mp4"), fps=30) + def test_2(): - images=[] - durations=[] + images = [] + durations = [] durations.append(1) images.append("media/python_logo.png") durations.append(2) images.append("media/matplotlib_demo1.png") - #images are not the same size.. + # images are not the same size.. with pytest.raises(Exception): - ImageSequenceClip(images, durations=durations).close() + ImageSequenceClip(images, durations=durations).close() -if __name__ == '__main__': - pytest.main() +if __name__ == "__main__": + pytest.main() diff --git a/tests/test_PR.py b/tests/test_PR.py index 0ed03eeb7..4d18460f7 100644 --- a/tests/test_PR.py +++ b/tests/test_PR.py @@ -17,27 +17,36 @@ def test_PR_306(): - assert TextClip.list('font') != [] - assert TextClip.list('color') != [] + assert TextClip.list("font") != [] + assert TextClip.list("color") != [] with pytest.raises(Exception): - TextClip.list('blah') + TextClip.list("blah") close_all_clips(locals()) + def test_PR_339(): # In caption mode. - TextClip(txt='foo', color='white', font=FONT, size=(640, 480), - method='caption', align='center', fontsize=25).close() + TextClip( + txt="foo", + color="white", + font=FONT, + size=(640, 480), + method="caption", + align="center", + fontsize=25, + ).close() # In label mode. - TextClip(txt='foo', font=FONT, method='label').close() + TextClip(txt="foo", font=FONT, method="label").close() + def test_PR_373(): result = Trajectory.load_list("media/traj.txt") Trajectory.save_list(result, os.path.join(TMP_DIR, "traj1.txt")) - result1 = Trajectory.load_list(os.path.join(TMP_DIR,"traj1.txt")) + result1 = Trajectory.load_list(os.path.join(TMP_DIR, "traj1.txt")) assert len(result[0].tt) == len(result1[0].tt) for i in range(len(result[0].tt)): @@ -51,10 +60,12 @@ def test_PR_373(): for i in range(len(result[0].yy)): assert result[0].yy[i] == result1[0].yy[i] + def test_PR_424(): """Ensure deprecation and user warnings are triggered.""" import warnings - warnings.simplefilter('always') # Alert us of deprecation warnings. + + warnings.simplefilter("always") # Alert us of deprecation warnings. # Recommended use ColorClip([1000, 600], color=(60, 60, 60), duration=10).close() @@ -66,29 +77,34 @@ def test_PR_424(): # Catch all warnings as record. with pytest.warns(None) as record: # Should give 2 warnings and use `color`, not `col` - ColorClip([1000, 600], color=(60, 60, 60), duration=10, col=(2,2,2)).close() + ColorClip([1000, 600], color=(60, 60, 60), duration=10, col=(2, 2, 2)).close() - message1 = 'The `ColorClip` parameter `col` has been deprecated. ' + \ - 'Please use `color` instead.' - message2 = 'The arguments `color` and `col` have both been passed to ' + \ - '`ColorClip` so `col` has been ignored.' + message1 = ( + "The `ColorClip` parameter `col` has been deprecated. " + + "Please use `color` instead." + ) + message2 = ( + "The arguments `color` and `col` have both been passed to " + + "`ColorClip` so `col` has been ignored." + ) # Assert that two warnings popped and validate the message text. assert len(record) == 2 assert str(record[0].message) == message1 assert str(record[1].message) == message2 + def test_PR_458(): clip = ColorClip([1000, 600], color=(60, 60, 60), duration=2) - clip.write_videofile(os.path.join(TMP_DIR, "test.mp4"), - logger=None, fps=30) + clip.write_videofile(os.path.join(TMP_DIR, "test.mp4"), logger=None, fps=30) clip.close() + def test_PR_515(): # Won't actually work until video is in download_media - with VideoFileClip("media/fire2.mp4", fps_source='tbr') as clip: + with VideoFileClip("media/fire2.mp4", fps_source="tbr") as clip: assert clip.fps == 90000 - with VideoFileClip("media/fire2.mp4", fps_source='fps') as clip: + with VideoFileClip("media/fire2.mp4", fps_source="fps") as clip: assert clip.fps == 10.51 @@ -117,5 +133,5 @@ def test_PR_610(): assert composite.fps == 25 -if __name__ == '__main__': - pytest.main() +if __name__ == "__main__": + pytest.main() diff --git a/tests/test_TextClip.py b/tests/test_TextClip.py index 76f0b4221..17884c181 100644 --- a/tests/test_TextClip.py +++ b/tests/test_TextClip.py @@ -10,11 +10,11 @@ def test_fontlist(): - TextClip.list('font') + TextClip.list("font") def test_duration(): - clip = TextClip('hello world', size=(1280,720), color='white', font=FONT) + clip = TextClip("hello world", size=(1280, 720), color="white", font=FONT) clip = clip.set_duration(5) assert clip.duration == 5 clip.close() @@ -27,13 +27,20 @@ def test_duration(): # Moved from tests.py. Maybe we can remove these? def test_if_textclip_crashes_in_caption_mode(): - TextClip(txt='foo', color='white', size=(640, 480), method='caption', - align='center', fontsize=25, font=FONT).close() + TextClip( + txt="foo", + color="white", + size=(640, 480), + method="caption", + align="center", + fontsize=25, + font=FONT, + ).close() def test_if_textclip_crashes_in_label_mode(): - TextClip(txt='foo', method='label', font=FONT).close() + TextClip(txt="foo", method="label", font=FONT).close() -if __name__ == '__main__': +if __name__ == "__main__": pytest.main() diff --git a/tests/test_VideoClip.py b/tests/test_VideoClip.py index d040ddd6b..c9e4d931f 100644 --- a/tests/test_VideoClip.py +++ b/tests/test_VideoClip.py @@ -20,8 +20,10 @@ def test_check_codec(): try: clip.write_videofile(location) except ValueError as e: - assert "MoviePy couldn't find the codec associated with the filename." \ - " Provide the 'codec' parameter in write_videofile." in str(e) + assert ( + "MoviePy couldn't find the codec associated with the filename." + " Provide the 'codec' parameter in write_videofile." in str(e) + ) close_all_clips(locals()) @@ -35,8 +37,7 @@ def test_save_frame(): def test_write_image_sequence(): clip = VideoFileClip("media/big_buck_bunny_432_433.webm").subclip(0.2, 0.5) - locations = clip.write_images_sequence( - os.path.join(TMP_DIR, "frame%02d.png")) + locations = clip.write_images_sequence(os.path.join(TMP_DIR, "frame%02d.png")) for location in locations: assert os.path.isfile(location) close_all_clips(locals()) @@ -72,7 +73,7 @@ def test_write_gif_ImageMagick(): clip.write_gif(location, program="ImageMagick") close_all_clips(locals()) # Fails for some reason - #assert os.path.isfile(location) + # assert os.path.isfile(location) def test_write_gif_ImageMagick_tmpfiles(): diff --git a/tests/test_VideoFileClip.py b/tests/test_VideoFileClip.py index 1dc448736..3a2fc7bf8 100644 --- a/tests/test_VideoFileClip.py +++ b/tests/test_VideoFileClip.py @@ -15,9 +15,9 @@ def test_setup(): """Test VideoFileClip setup.""" - red = ColorClip((256,200), color=(255,0,0)) - green = ColorClip((256,200), color=(0,255,0)) - blue = ColorClip((256,200), color=(0,0,255)) + red = ColorClip((256, 200), color=(255, 0, 0)) + green = ColorClip((256, 200), color=(0, 255, 0)) + 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: @@ -28,13 +28,14 @@ def test_setup(): clip = VideoFileClip(os.path.join(TMP_DIR, "test.mp4")) assert clip.duration == 5 assert clip.fps == 10 - assert clip.size == [256*3, 200] + assert clip.size == [256 * 3, 200] close_all_clips(locals()) + def test_ffmpeg_resizing(): """Test FFmpeg resizing, to include downscaling.""" - video_file = 'media/big_buck_bunny_432_433.webm' - target_resolutions = [(128, 128), (128, None),(None, 128), (None, 256)] + video_file = "media/big_buck_bunny_432_433.webm" + target_resolutions = [(128, 128), (128, None), (None, 128), (None, 256)] for target_resolution in target_resolutions: video = VideoFileClip(video_file, target_resolution=target_resolution) frame = video.get_frame(0) @@ -44,5 +45,5 @@ def test_ffmpeg_resizing(): video.close() -if __name__ == '__main__': +if __name__ == "__main__": pytest.main() diff --git a/tests/test_Videos.py b/tests/test_Videos.py index 32b4d1c69..0fa739850 100644 --- a/tests/test_Videos.py +++ b/tests/test_Videos.py @@ -14,13 +14,14 @@ def test_afterimage(): ai = ImageClip("media/afterimage.png") - masked_clip = mask_color(ai, color=[0,255,1]) # for green - some_background_clip = ColorClip((800,600), color=(255,255,255)) - final_clip = CompositeVideoClip([some_background_clip, masked_clip], - use_bgclip=True) + masked_clip = mask_color(ai, color=[0, 255, 1]) # for green + some_background_clip = ColorClip((800, 600), color=(255, 255, 255)) + final_clip = CompositeVideoClip( + [some_background_clip, masked_clip], use_bgclip=True + ) final_clip.duration = 5 - final_clip.write_videofile( - os.path.join(TMP_DIR, "afterimage.mp4"), fps=30) + final_clip.write_videofile(os.path.join(TMP_DIR, "afterimage.mp4"), fps=30) -if __name__ == '__main__': + +if __name__ == "__main__": pytest.main() diff --git a/tests/test_compositing.py b/tests/test_compositing.py index 422070679..50949d5a0 100644 --- a/tests/test_compositing.py +++ b/tests/test_compositing.py @@ -19,8 +19,7 @@ def test_clips_array(): video = clips_array([[red, green, blue]]) with pytest.raises(ValueError): # duration not set - video.resize(width=480).write_videofile( - join(TMP_DIR, "test_clips_array.mp4")) + video.resize(width=480).write_videofile(join(TMP_DIR, "test_clips_array.mp4")) close_all_clips(locals()) diff --git a/tests/test_examples.py b/tests/test_examples.py index 3f24e0890..1443ee341 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -3,33 +3,34 @@ import pytest import numpy as np + try: - import matplotlib + import matplotlib except ImportError: - matplotlib = None + matplotlib = None from moviepy.video.io.bindings import mplfig_to_npimage from moviepy.video.VideoClip import VideoClip from .test_helper import PYTHON_VERSION, TMP_DIR, TRAVIS -@pytest.mark.skipif(not matplotlib, reason="no mpl") -@pytest.mark.skipif(PYTHON_VERSION == '3.5' and TRAVIS, reason="travis py35") +@pytest.mark.skipif(not matplotlib, reason="no mpl") +@pytest.mark.skipif(PYTHON_VERSION == "3.5" and TRAVIS, reason="travis py35") def test_matplotlib(): - #for now, python 3.5 installs a version of matplotlib that complains - #about $DISPLAY variable, so lets just ignore for now. - + # for now, python 3.5 installs a version of matplotlib that complains + # about $DISPLAY variable, so lets just ignore for now. + x = np.linspace(-2, 2, 200) duration = 2 - matplotlib.use('Agg') + matplotlib.use("Agg") fig, ax = matplotlib.pyplot.subplots() def make_frame(t): ax.clear() - ax.plot(x, np.sinc(x**2) + np.sin(x + 2*np.pi/duration * t), lw=3) + ax.plot(x, np.sinc(x ** 2) + np.sin(x + 2 * np.pi / duration * t), lw=3) ax.set_ylim(-1.5, 2.5) return mplfig_to_npimage(fig) animation = VideoClip(make_frame, duration=duration) - animation.write_gif(os.path.join(TMP_DIR, 'matplotlib.gif'), fps=20) + animation.write_gif(os.path.join(TMP_DIR, "matplotlib.gif"), fps=20) diff --git a/tests/test_ffmpeg_reader.py b/tests/test_ffmpeg_reader.py index c70a9efae..8b93b338f 100644 --- a/tests/test_ffmpeg_reader.py +++ b/tests/test_ffmpeg_reader.py @@ -8,25 +8,27 @@ def test_ffmpeg_parse_infos(): - d=ffmpeg_parse_infos("media/big_buck_bunny_432_433.webm") - assert d['duration'] == 1.0 + d = ffmpeg_parse_infos("media/big_buck_bunny_432_433.webm") + assert d["duration"] == 1.0 - d=ffmpeg_parse_infos("media/pigs_in_a_polka.gif") - assert d['video_size'] == [314, 273] - assert d['duration'] == 3.0 - assert not d['audio_found'] + d = ffmpeg_parse_infos("media/pigs_in_a_polka.gif") + assert d["video_size"] == [314, 273] + assert d["duration"] == 3.0 + assert not d["audio_found"] - d=ffmpeg_parse_infos("media/video_with_failing_audio.mp4") - assert d['audio_found'] - assert d['audio_fps'] == 44100 + d = ffmpeg_parse_infos("media/video_with_failing_audio.mp4") + assert d["audio_found"] + assert d["audio_fps"] == 44100 + + d = ffmpeg_parse_infos("media/crunching.mp3") + assert d["audio_found"] + assert d["audio_fps"] == 48000 - d=ffmpeg_parse_infos("media/crunching.mp3") - assert d['audio_found'] - assert d['audio_fps'] == 48000 def test_ffmpeg_parse_infos_for_i926(): d = ffmpeg_parse_infos("tests/resource/sintel_with_15_chapters.mp4") - assert d['audio_found'] + assert d["audio_found"] + -if __name__ == '__main__': - pytest.main() +if __name__ == "__main__": + pytest.main() diff --git a/tests/test_fx.py b/tests/test_fx.py index 67aa8444f..eb46b5ba7 100644 --- a/tests/test_fx.py +++ b/tests/test_fx.py @@ -7,6 +7,7 @@ from moviepy.audio.io.AudioFileClip import AudioFileClip from moviepy.utils import close_all_clips from moviepy.video.fx.blackwhite import blackwhite + # from moviepy.video.fx.blink import blink from moviepy.video.fx.colorx import colorx from moviepy.video.fx.crop import crop @@ -39,6 +40,7 @@ def test_blackwhite(): clip1.write_videofile(os.path.join(TMP_DIR, "blackwhite1.webm")) close_all_clips(locals()) + # This currently fails with a with_mask error! # def test_blink(): # with VideoFileClip("media/big_buck_bunny_0_30.webm").subclip(0,10) as clip: @@ -52,6 +54,7 @@ def test_colorx(): clip1.write_videofile(os.path.join(TMP_DIR, "colorx1.webm")) close_all_clips(locals()) + def test_crop(): clip = get_test_video() @@ -74,6 +77,7 @@ def test_crop(): clip6.write_videofile(os.path.join(TMP_DIR, "crop6.webm")) close_all_clips(locals()) + def test_fadein(): clip = get_test_video() clip1 = fadein(clip, 0.5) @@ -191,7 +195,7 @@ def test_resize(): # I get a general stream error when playing this video. # clip4=clip.resize(lambda t : 1+0.02*t) # slow swelling of the clip - #clip4.write_videofile(os.path.join(TMP_DIR, "resize4.webm")) + # clip4.write_videofile(os.path.join(TMP_DIR, "resize4.webm")) def test_rotate(): @@ -258,11 +262,11 @@ def test_time_symmetrize(): def test_normalize(): - clip = AudioFileClip('media/crunching.mp3') + clip = AudioFileClip("media/crunching.mp3") clip = audio_normalize(clip) assert clip.max_volume() == 1 close_all_clips(locals()) -if __name__ == '__main__': +if __name__ == "__main__": pytest.main() diff --git a/tests/test_helper.py b/tests/test_helper.py index 3ba28ef71..d627e16ae 100644 --- a/tests/test_helper.py +++ b/tests/test_helper.py @@ -6,7 +6,7 @@ TRAVIS = os.getenv("TRAVIS_PYTHON_VERSION") is not None PYTHON_VERSION = "%s.%s" % (sys.version_info.major, sys.version_info.minor) -TMP_DIR = tempfile.gettempdir() # because tempfile.tempdir is sometimes None +TMP_DIR = tempfile.gettempdir() # because tempfile.tempdir is sometimes None # Arbitrary font used in caption testing. if sys.platform in ("win32", "cygwin"): @@ -14,4 +14,6 @@ # Even if Windows users install the Liberation fonts, it is called LiberationMono on Windows, so # it doesn't help. else: - FONT = "Liberation-Mono" # This is available in the fonts-liberation package on Linux. + FONT = ( + "Liberation-Mono" # This is available in the fonts-liberation package on Linux. + ) diff --git a/tests/test_issues.py b/tests/test_issues.py index 4ed4402d8..9b048da41 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -16,81 +16,175 @@ def test_issue_145(): video = ColorClip((800, 600), color=(255, 0, 0)).set_duration(5) with pytest.raises(Exception): - concatenate_videoclips([video], method='composite') + concatenate_videoclips([video], method="composite") def test_issue_190(): - #from PIL import Image - #Image.new('L', (800,600), 'white').save(os.path.join(TMP_DIR, "issue_190.png")) + # from PIL import Image + # Image.new('L', (800,600), 'white').save(os.path.join(TMP_DIR, "issue_190.png")) - #from imageio import imread - #image = imread(os.path.join(TMP_DIR, "issue_190.png")) + # from imageio import imread + # image = imread(os.path.join(TMP_DIR, "issue_190.png")) - #clip = ImageSequenceClip([image, image], fps=1) - #clip.write_videofile(os.path.join(TMP_DIR, "issue_190.mp4")) + # clip = ImageSequenceClip([image, image], fps=1) + # clip.write_videofile(os.path.join(TMP_DIR, "issue_190.mp4")) pass def test_issue_285(): - clip_1, clip_2, clip_3 = ImageClip('media/python_logo.png', duration=10), \ - ImageClip('media/python_logo.png', duration=10), \ - ImageClip('media/python_logo.png', duration=10) + clip_1, clip_2, clip_3 = ( + ImageClip("media/python_logo.png", duration=10), + ImageClip("media/python_logo.png", duration=10), + ImageClip("media/python_logo.png", duration=10), + ) merged_clip = concatenate_videoclips([clip_1, clip_2, clip_3]) assert merged_clip.duration == 30 close_all_clips(locals()) def test_issue_334(): - # NOTE: this is horrible. Any simpler version ? + # NOTE: this is horrible. Any simpler version ? last_move = None last_move1 = None - lis = [(0.0, 113, 167, 47), (0.32, 138, 159, 47), (0.44, 152, 144, 47), - (0.48, 193, 148, 47), (0.6, 193, 148, 47), (0.76, 205, 138, 55), - (0.88, 204, 121, 63), (0.92, 190, 31, 127), (1.2, 183, 59, 127), - (1.4, 137, 22, 127), (1.52, 137, 22, 127), (1.72, 129, 67, 127), - (1.88, 123, 69, 127), (2.04, 131, 123, 63), (2.24, 130, 148, 63), - (2.48, 130, 148, 63), (2.8, 138, 180, 63), (3.0, 138, 180, 63), - (3.2, 146, 192, 63), (3.28, 105, 91, 151), (3.44, 105, 91, 151), - (3.72, 11, 48, 151), (3.96, 5, 78, 151), (4.32, 4, 134, 1), - (4.6, 149, 184, 48), (4.8, 145, 188, 48), (5.0, 154, 217, 48), - (5.08, 163, 199, 48), (5.2, 163, 199, 48), (5.32, 164, 187, 48), - (5.48, 163, 200, 48), (5.76, 163, 200, 48), (5.96, 173, 199, 48), - (6.0, 133, 172, 48), (6.04, 128, 165, 48), (6.28, 128, 165, 48), - (6.4, 129, 180, 48), (6.52, 133, 166, 48), (6.64, 133, 166, 48), - (6.88, 144, 183, 48), (7.0, 153, 174, 48), (7.16, 153, 174, 48), - (7.24, 153, 174, 48), (7.28, 253, 65, 104), (7.64, 253, 65, 104), - (7.8, 279, 116, 80), (8.0, 290, 105, 80), (8.24, 288, 124, 80), - (8.44, 243, 102, 80), (8.56, 243, 102, 80), (8.8, 202, 107, 80), - (8.84, 164, 27, 104), (9.0, 164, 27, 104), (9.12, 121, 9, 104), - (9.28, 77, 33, 104), (9.32, 52, 23, 104), (9.48, 52, 23, 104), - (9.64, 33, 46, 104), (9.8, 93, 49, 104), (9.92, 93, 49, 104), - (10.16, 173, 19, 104), (10.2, 226, 173, 48), (10.36, 226, 173, 48), - (10.48, 211, 172, 48), (10.64, 208, 162, 48), (10.92, 220, 171, 48)] - - lis1 = [(0.0, 113, 167, 47), (0.32, 138, 159, 47), (0.44, 152, 144, 47), - (0.48, 193, 148, 47), (0.6, 193, 148, 47), (0.76, 205, 138, 55), - (0.88, 204, 121, 63), (0.92, 190, 31, 127), (1.2, 183, 59, 127), - (1.4, 137, 22, 127), (1.52, 137, 22, 127), (1.72, 129, 67, 127), - (1.88, 123, 69, 127), (2.04, 131, 123, 63), (2.24, 130, 148, 63), - (2.48, 130, 148, 63), (2.8, 138, 180, 63), (3.0, 138, 180, 63), - (3.2, 146, 192, 63), (3.28, 105, 91, 151), (3.44, 105, 91, 151), - (3.72, 11, 48, 151), (3.96, 5, 78, 151), (4.32, 4, 134, 1), - (4.6, 149, 184, 48), (4.8, 145, 188, 48), (5.0, 154, 217, 48), - (5.08, 163, 199, 48), (5.2, 163, 199, 48), (5.32, 164, 187, 48), - (5.48, 163, 200, 48), (5.76, 163, 200, 48), (5.96, 173, 199, 48), - (6.0, 133, 172, 48), (6.04, 128, 165, 48), (6.28, 128, 165, 48), - (6.4, 129, 180, 48), (6.52, 133, 166, 48), (6.64, 133, 166, 48), - (6.88, 144, 183, 48), (7.0, 153, 174, 48), (7.16, 153, 174, 48), - (7.24, 153, 174, 48), (7.28, 253, 65, 104), (7.64, 253, 65, 104), - (7.8, 279, 116, 80), (8.0, 290, 105, 80), (8.24, 288, 124, 80), - (8.44, 243, 102, 80), (8.56, 243, 102, 80), (8.8, 202, 107, 80), - (8.84, 164, 27, 104), (9.0, 164, 27, 104), (9.12, 121, 9, 104), - (9.28, 77, 33, 104), (9.32, 52, 23, 104), (9.48, 52, 23, 104), - (9.64, 33, 46, 104), (9.8, 93, 49, 104), (9.92, 93, 49, 104), - (10.16, 173, 19, 104), (10.2, 226, 173, 48), (10.36, 226, 173, 48), - (10.48, 211, 172, 48), (10.64, 208, 162, 48), (10.92, 220, 171, 48)] + lis = [ + (0.0, 113, 167, 47), + (0.32, 138, 159, 47), + (0.44, 152, 144, 47), + (0.48, 193, 148, 47), + (0.6, 193, 148, 47), + (0.76, 205, 138, 55), + (0.88, 204, 121, 63), + (0.92, 190, 31, 127), + (1.2, 183, 59, 127), + (1.4, 137, 22, 127), + (1.52, 137, 22, 127), + (1.72, 129, 67, 127), + (1.88, 123, 69, 127), + (2.04, 131, 123, 63), + (2.24, 130, 148, 63), + (2.48, 130, 148, 63), + (2.8, 138, 180, 63), + (3.0, 138, 180, 63), + (3.2, 146, 192, 63), + (3.28, 105, 91, 151), + (3.44, 105, 91, 151), + (3.72, 11, 48, 151), + (3.96, 5, 78, 151), + (4.32, 4, 134, 1), + (4.6, 149, 184, 48), + (4.8, 145, 188, 48), + (5.0, 154, 217, 48), + (5.08, 163, 199, 48), + (5.2, 163, 199, 48), + (5.32, 164, 187, 48), + (5.48, 163, 200, 48), + (5.76, 163, 200, 48), + (5.96, 173, 199, 48), + (6.0, 133, 172, 48), + (6.04, 128, 165, 48), + (6.28, 128, 165, 48), + (6.4, 129, 180, 48), + (6.52, 133, 166, 48), + (6.64, 133, 166, 48), + (6.88, 144, 183, 48), + (7.0, 153, 174, 48), + (7.16, 153, 174, 48), + (7.24, 153, 174, 48), + (7.28, 253, 65, 104), + (7.64, 253, 65, 104), + (7.8, 279, 116, 80), + (8.0, 290, 105, 80), + (8.24, 288, 124, 80), + (8.44, 243, 102, 80), + (8.56, 243, 102, 80), + (8.8, 202, 107, 80), + (8.84, 164, 27, 104), + (9.0, 164, 27, 104), + (9.12, 121, 9, 104), + (9.28, 77, 33, 104), + (9.32, 52, 23, 104), + (9.48, 52, 23, 104), + (9.64, 33, 46, 104), + (9.8, 93, 49, 104), + (9.92, 93, 49, 104), + (10.16, 173, 19, 104), + (10.2, 226, 173, 48), + (10.36, 226, 173, 48), + (10.48, 211, 172, 48), + (10.64, 208, 162, 48), + (10.92, 220, 171, 48), + ] + + lis1 = [ + (0.0, 113, 167, 47), + (0.32, 138, 159, 47), + (0.44, 152, 144, 47), + (0.48, 193, 148, 47), + (0.6, 193, 148, 47), + (0.76, 205, 138, 55), + (0.88, 204, 121, 63), + (0.92, 190, 31, 127), + (1.2, 183, 59, 127), + (1.4, 137, 22, 127), + (1.52, 137, 22, 127), + (1.72, 129, 67, 127), + (1.88, 123, 69, 127), + (2.04, 131, 123, 63), + (2.24, 130, 148, 63), + (2.48, 130, 148, 63), + (2.8, 138, 180, 63), + (3.0, 138, 180, 63), + (3.2, 146, 192, 63), + (3.28, 105, 91, 151), + (3.44, 105, 91, 151), + (3.72, 11, 48, 151), + (3.96, 5, 78, 151), + (4.32, 4, 134, 1), + (4.6, 149, 184, 48), + (4.8, 145, 188, 48), + (5.0, 154, 217, 48), + (5.08, 163, 199, 48), + (5.2, 163, 199, 48), + (5.32, 164, 187, 48), + (5.48, 163, 200, 48), + (5.76, 163, 200, 48), + (5.96, 173, 199, 48), + (6.0, 133, 172, 48), + (6.04, 128, 165, 48), + (6.28, 128, 165, 48), + (6.4, 129, 180, 48), + (6.52, 133, 166, 48), + (6.64, 133, 166, 48), + (6.88, 144, 183, 48), + (7.0, 153, 174, 48), + (7.16, 153, 174, 48), + (7.24, 153, 174, 48), + (7.28, 253, 65, 104), + (7.64, 253, 65, 104), + (7.8, 279, 116, 80), + (8.0, 290, 105, 80), + (8.24, 288, 124, 80), + (8.44, 243, 102, 80), + (8.56, 243, 102, 80), + (8.8, 202, 107, 80), + (8.84, 164, 27, 104), + (9.0, 164, 27, 104), + (9.12, 121, 9, 104), + (9.28, 77, 33, 104), + (9.32, 52, 23, 104), + (9.48, 52, 23, 104), + (9.64, 33, 46, 104), + (9.8, 93, 49, 104), + (9.92, 93, 49, 104), + (10.16, 173, 19, 104), + (10.2, 226, 173, 48), + (10.36, 226, 173, 48), + (10.48, 211, 172, 48), + (10.64, 208, 162, 48), + (10.92, 220, 171, 48), + ] def posi(t): global last_move @@ -129,19 +223,16 @@ 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) + maskclip = ImageClip("media/afterimage.png", ismask=True, transparent=True) avatar.set_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)]) + final = CompositeVideoClip([tt, concatenated.set_position(posi).resize(size)]) final.duration = tt.duration - final.write_videofile( - os.path.join(TMP_DIR, 'issue_334.mp4'), fps=10) + final.write_videofile(os.path.join(TMP_DIR, "issue_334.mp4"), fps=10) def test_issue_354(): @@ -156,7 +247,7 @@ def test_issue_354(): # stroke_width=2, method='caption', # size=(1280, 720), fontsize=60, # align='South-East') - #caption.duration = clip.duration + # caption.duration = clip.duration fadecaption = clip.crossfadein(crosstime).crossfadeout(crosstime) CompositeVideoClip([clip, fadecaption]).close() @@ -165,8 +256,8 @@ def test_issue_354(): def test_issue_359(): with ColorClip((800, 600), color=(255, 0, 0)).set_duration(5) as video: video.fps = 30 - video.write_gif(filename=os.path.join(TMP_DIR, "issue_359.gif"), - tempfiles=True) + video.write_gif(filename=os.path.join(TMP_DIR, "issue_359.gif"), tempfiles=True) + # TODO: Debug matplotlib failures following successful travis builds. # def test_issue_368(): @@ -243,14 +334,14 @@ def test_issue_416(): def test_issue_417(): # failed in python2 - cad = u'media/python_logo.png' + cad = "media/python_logo.png" myclip = ImageClip(cad).fx(resize, newsize=[1280, 660]) CompositeVideoClip([myclip], size=(1280, 720)) - #final.set_duration(7).write_videofile("test.mp4", fps=30) + # final.set_duration(7).write_videofile("test.mp4", fps=30) def test_issue_467(): - cad = 'media/python_logo.png' + cad = "media/python_logo.png" clip = ImageClip(cad) # caused an error, NameError: global name 'copy' is not defined @@ -258,27 +349,28 @@ def test_issue_467(): def test_issue_470(): - audio_clip = AudioFileClip('media/crunching.mp3') + audio_clip = AudioFileClip("media/crunching.mp3") # t_end is out of bounds subclip = audio_clip.subclip(t_start=6, t_end=9) with pytest.raises(IOError): - subclip.write_audiofile(os.path.join( - TMP_DIR, 'issue_470.wav'), write_logfile=True) + subclip.write_audiofile( + os.path.join(TMP_DIR, "issue_470.wav"), write_logfile=True + ) # but this one should work.. subclip = audio_clip.subclip(t_start=6, t_end=8) - subclip.write_audiofile(os.path.join( - TMP_DIR, 'issue_470.wav'), write_logfile=True) + subclip.write_audiofile(os.path.join(TMP_DIR, "issue_470.wav"), write_logfile=True) def test_issue_246(): def test_audio_reader(): - video = VideoFileClip('media/video_with_failing_audio.mp4') + video = VideoFileClip("media/video_with_failing_audio.mp4") subclip = video.subclip(270) - subclip.write_audiofile(os.path.join(TMP_DIR, 'issue_246.wav'), - write_logfile=True) + subclip.write_audiofile( + os.path.join(TMP_DIR, "issue_246.wav"), write_logfile=True + ) def test_issue_547(): @@ -301,7 +393,7 @@ def test_issue_636(): def test_issue_655(): - video_file = 'media/fire2.mp4' + video_file = "media/fire2.mp4" for subclip in [(0, 2), (1, 2), (2, 3)]: with VideoFileClip(video_file) as v: with v.subclip(1, 2) as s: @@ -310,5 +402,5 @@ def test_issue_655(): assert True -if __name__ == '__main__': +if __name__ == "__main__": pytest.main() diff --git a/tests/test_misc.py b/tests/test_misc.py index b52eb946c..10c31ff2b 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -27,18 +27,28 @@ def test_subtitles(): myvideo = concatenate_videoclips([red, green, blue]) assert myvideo.duration == 30 - generator = lambda txt: TextClip(txt, font=FONT, - size=(800, 600), fontsize=24, - method='caption', align='South', - color='white') + generator = lambda txt: TextClip( + txt, + font=FONT, + size=(800, 600), + fontsize=24, + method="caption", + align="South", + color="white", + ) subtitles = SubtitlesClip("media/subtitles1.srt", generator) final = CompositeVideoClip([myvideo, subtitles]) final.write_videofile(os.path.join(TMP_DIR, "subtitles1.mp4"), fps=30) - data = [([0.0, 4.0], 'Red!'), ([5.0, 9.0], 'More Red!'), - ([10.0, 14.0], 'Green!'), ([15.0, 19.0], 'More Green!'), - ([20.0, 24.0], 'Blue'), ([25.0, 29.0], 'More Blue!')] + data = [ + ([0.0, 4.0], "Red!"), + ([5.0, 9.0], "More Red!"), + ([10.0, 14.0], "Green!"), + ([15.0, 19.0], "More Green!"), + ([20.0, 24.0], "Blue"), + ([25.0, 29.0], "More Blue!"), + ] assert subtitles.subtitles == data @@ -48,11 +58,17 @@ def test_subtitles(): def test_file_to_subtitles(): - data = [([0.0, 4.0], 'Red!'), ([5.0, 9.0], 'More Red!'), - ([10.0, 14.0], 'Green!'), ([15.0, 19.0], 'More Green!'), - ([20.0, 24.0], 'Blue'), ([25.0, 29.0], 'More Blue!')] + data = [ + ([0.0, 4.0], "Red!"), + ([5.0, 9.0], "More Red!"), + ([10.0, 14.0], "Green!"), + ([15.0, 19.0], "More Green!"), + ([20.0, 24.0], "Blue"), + ([25.0, 29.0], "More Blue!"), + ] assert data == file_to_subtitles("media/subtitles1.srt") -if __name__ == '__main__': + +if __name__ == "__main__": pytest.main() diff --git a/tests/test_resourcerelease.py b/tests/test_resourcerelease.py index bb9b2c501..4103f748d 100644 --- a/tests/test_resourcerelease.py +++ b/tests/test_resourcerelease.py @@ -32,8 +32,7 @@ def test_release_of_file_via_close(): for i in range(3): # Get the name of a temporary file we can use. local_video_filename = join( - TMP_DIR, - "test_release_of_file_via_close_%s.mp4" % int(time.time()) + TMP_DIR, "test_release_of_file_via_close_%s.mp4" % int(time.time()) ) clip = clips_array([[red, green, blue]]).set_duration(0.5) diff --git a/tests/test_resourcereleasedemo.py b/tests/test_resourcereleasedemo.py index 5cee24a04..ea158d67c 100644 --- a/tests/test_resourcereleasedemo.py +++ b/tests/test_resourcereleasedemo.py @@ -34,7 +34,8 @@ def test_failure_to_release_file(): # Get the name of a temporary file we can use. local_video_filename = join( - TMP_DIR, "test_release_of_file_%s.mp4" % int(time.time())) + TMP_DIR, "test_release_of_file_%s.mp4" % int(time.time()) + ) # Repeat this so we can see that the problems escalate: for i in range(5): @@ -64,9 +65,9 @@ def test_failure_to_release_file(): except IOError: print( "On Windows, this succeeds the first few times around the loop" - " but eventually fails.") - print("Need to shut down the process now. No more tests in" - "this file.") + " but eventually fails." + ) + print("Need to shut down the process now. No more tests in" "this file.") return try: diff --git a/tests/test_tools.py b/tests/test_tools.py index 6fdb2bb8b..b620d3b24 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -7,12 +7,10 @@ import moviepy.tools as tools -@pytest.mark.parametrize('given, expected', [ - ("libx264", "mp4"), - ("libmpeg4", "mp4"), - ("libtheora", "ogv"), - ("libvpx", "webm") -]) +@pytest.mark.parametrize( + "given, expected", + [("libx264", "mp4"), ("libmpeg4", "mp4"), ("libtheora", "ogv"), ("libvpx", "webm")], +) def test_find_extensions(given, expected): """Test for find_extension function.""" assert tools.find_extension(given) == expected @@ -21,22 +19,24 @@ def test_find_extensions(given, expected): def test_find_extensions_not_found(): """Test for raising error if codec not in dictionaries.""" with pytest.raises(ValueError): # asking for a silly video format - tools.find_extension('flashvideo') - - - -@pytest.mark.parametrize('given, expected', [ - (15.4, 15.4), - ((1, 21.5), 81.5), - ((1, 1, 2), 3662), - ([1, 1, 2], 3662), - ('01:01:33.5', 3693.5), - ('01:01:33.045', 3693.045), - ('01:01:33,5', 3693.5), - ('1:33', 93.0), - ('33.4', 33.4), - (None, None) -]) + tools.find_extension("flashvideo") + + +@pytest.mark.parametrize( + "given, expected", + [ + (15.4, 15.4), + ((1, 21.5), 81.5), + ((1, 1, 2), 3662), + ([1, 1, 2], 3662), + ("01:01:33.5", 3693.5), + ("01:01:33.045", 3693.045), + ("01:01:33,5", 3693.5), + ("1:33", 93.0), + ("33.4", 33.4), + (None, None), + ], +) def test_cvsecs(given, expected): """Test the cvsecs funtion outputs correct times as per the docstring.""" assert tools.cvsecs(given) == expected @@ -50,5 +50,5 @@ def test_sys_write_flush(): assert file == b"" -if __name__ == '__main__': - pytest.main() +if __name__ == "__main__": + pytest.main() diff --git a/tests/test_videotools.py b/tests/test_videotools.py index 217c9718c..2609341c7 100644 --- a/tests/test_videotools.py +++ b/tests/test_videotools.py @@ -9,29 +9,32 @@ def test_credits(): - credit_file = "# This is a comment\n" \ - "# The next line says : leave 4 blank lines\n" \ - ".blank 2\n" \ - "\n" \ - "..Executive Story Editor\n" \ - "MARCEL DURAND\n" \ - "\n" \ - ".blank 2\n" \ - "\n" \ - "..Associate Producers\n" \ - "MARTIN MARCEL\n" \ - "DIDIER MARTIN\n" \ - "\n" \ - "..Music Supervisor\n" \ - "JEAN DIDIER\n" + credit_file = ( + "# This is a comment\n" + "# The next line says : leave 4 blank lines\n" + ".blank 2\n" + "\n" + "..Executive Story Editor\n" + "MARCEL DURAND\n" + "\n" + ".blank 2\n" + "\n" + "..Associate Producers\n" + "MARTIN MARCEL\n" + "DIDIER MARTIN\n" + "\n" + "..Music Supervisor\n" + "JEAN DIDIER\n" + ) file_location = os.path.join(TMP_DIR, "credits.txt") vid_location = os.path.join(TMP_DIR, "credits.mp4") with open(file_location, "w") as file: file.write(credit_file) - image = credits1(file_location, 600, gap=100, stroke_color="blue", - stroke_width=5, font=FONT) + image = credits1( + file_location, 600, gap=100, stroke_color="blue", stroke_width=5, font=FONT + ) image = image.set_duration(3) image.write_videofile(vid_location, fps=24) assert os.path.isfile(vid_location)