Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for path-like objects #924

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion moviepy/audio/AudioClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import proglog
from tqdm import tqdm

from moviepy.compat import fspath
from moviepy.audio.io.ffmpeg_audiowriter import ffmpeg_audiowrite
from moviepy.Clip import Clip
from moviepy.decorators import requires_duration
Expand Down Expand Up @@ -153,7 +154,7 @@ def write_audiofile(self, filename, fps=None, nbytes=2, buffersize=2000,
-----------

filename
Name of the output file
Name of the output file, as a string or a path-like object.

fps
Frames per second. If not set, it will try default to self.fps if
Expand Down Expand Up @@ -188,6 +189,8 @@ def write_audiofile(self, filename, fps=None, nbytes=2, buffersize=2000,
Either 'bar' or None or any Proglog logger

"""
filename = fspath(filename)

if not fps:
if not self.fps:
fps = 44100
Expand Down
4 changes: 3 additions & 1 deletion moviepy/audio/io/AudioFileClip.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import division

from moviepy.compat import fspath
from moviepy.audio.AudioClip import AudioClip
from moviepy.audio.io.readers import FFMPEG_AudioReader

Expand All @@ -18,6 +19,7 @@ class AudioFileClip(AudioClip):

filename
Either a soundfile name (of any extension supported by ffmpeg)
as a string or a path-like object,
or an array representing a sound. If the soundfile is not a .wav,
it will be converted to .wav first, using the ``fps`` and
``bitrate`` arguments.
Expand Down Expand Up @@ -67,7 +69,7 @@ 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,
self.reader = FFMPEG_AudioReader(fspath(filename), fps=fps, nbytes=nbytes,
buffersize=buffersize)
self.fps = fps
self.duration = self.reader.duration
Expand Down
17 changes: 17 additions & 0 deletions moviepy/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,20 @@
from subprocess import DEVNULL # Python 3
except ImportError:
DEVNULL = open(os.devnull, 'wb') # Python 2

try:
from os import fspath # Python 3.6+
except ImportError:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We agreed to implement #1081, so this compat function won't be necessary.

try:
from pathlib import Path # Python 3.4-3.5
except ImportError:
from pathlib2 import Path # Python 3.3-
from imageio.core import request

request.Path = Path

def fspath(path):
if isinstance(path, (string_types, Path)):
return str(path)
raise TypeError("expected string or Path object, "
"not %s" % path.__class__.__name__)
19 changes: 12 additions & 7 deletions moviepy/video/VideoClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from imageio import imread, imsave

from ..Clip import Clip
from ..compat import DEVNULL, string_types
from ..compat import DEVNULL, string_types, fspath
from ..config import get_setting
from ..decorators import (add_mask_if_none, apply_to_mask,
convert_masks_to_RGB, convert_to_seconds, outplace,
Expand Down Expand Up @@ -148,7 +148,7 @@ def write_videofile(self, filename, fps=None, codec=None,
-----------

filename
Name of the video file to write in.
Name of the video file to write in, as a string or a path-like object.
The extension must correspond to the "codec" used (see below),
or simply be '.avi' (which will work with any codec).

Expand Down Expand Up @@ -253,6 +253,7 @@ def write_videofile(self, filename, fps=None, codec=None,
>>> clip.close()

"""
filename = fspath(filename)
name, ext = os.path.splitext(os.path.basename(filename))
ext = ext[1:].lower()
logger = proglog.default_bar_logger(logger)
Expand Down Expand Up @@ -403,7 +404,7 @@ def write_gif(self, filename, fps=None, program='imageio',
-----------

filename
Name of the resulting gif file.
Name of the resulting gif file, as a string or a path-like object.

fps
Number of frames per second (see note below). If it
Expand Down Expand Up @@ -446,6 +447,8 @@ 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.

filename = fspath(filename)

if program == 'imageio':
write_gif_with_image_io(self, filename, fps=fps, opt=opt, loop=loop,
Expand Down Expand Up @@ -892,8 +895,8 @@ class ImageClip(VideoClip):
-----------

img
Any picture file (png, tiff, jpeg, etc.) or any array representing
an RGB image (for instance a frame from a VideoClip).
Any picture file (png, tiff, jpeg, etc.) as a string or a path-like object,
or any array representing an RGB image (for instance a frame from a VideoClip).

ismask
Set this parameter to `True` if the clip is a mask.
Expand All @@ -914,7 +917,7 @@ def __init__(self, img, ismask=False, transparent=True,
fromalpha=False, duration=None):
VideoClip.__init__(self, ismask=ismask, duration=duration)

if isinstance(img, string_types):
if not isinstance(img, np.ndarray):
img = imread(img)

if len(img.shape) == 3: # img is (now) a RGB(a) numpy array
Expand Down Expand Up @@ -1060,7 +1063,8 @@ class TextClip(ImageClip):
``filename``.

filename
The name of a file in which there is the text to write.
The name of a file in which there is the text to write,
as a string or a path-like object.
Can be provided instead of argument ``txt``

size
Expand Down Expand Up @@ -1129,6 +1133,7 @@ def __init__(self, txt=None, filename=None, size=None, color='black',
txt = '@' + temptxt
else:
# use a file instead of a text.
filename = fspath(filename)
txt = "@%" + filename

if size is not None:
Expand Down
9 changes: 6 additions & 3 deletions moviepy/video/io/VideoFileClip.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os

from moviepy.compat import fspath
from moviepy.video.VideoClip import VideoClip
from moviepy.audio.io.AudioFileClip import AudioFileClip
from moviepy.Clip import Clip
from moviepy.video.io.ffmpeg_reader import FFMPEG_VideoReader
Expand All @@ -22,7 +24,8 @@ class VideoFileClip(VideoClip):
------------

filename:
The name of the video file. It can have any extension supported
The name of the video file, as a string or a path-like object.
It can have any extension supported
by ffmpeg: .ogv, .mp4, .mpeg, .avi, .mov etc.

has_mask:
Expand Down Expand Up @@ -85,7 +88,7 @@ def __init__(self, filename, has_mask=False,

# Make a reader
pix_fmt = "rgba" if has_mask else "rgb24"
self.reader = FFMPEG_VideoReader(filename, pix_fmt=pix_fmt,
self.reader = FFMPEG_VideoReader(fspath(filename), pix_fmt=pix_fmt,
target_resolution=target_resolution,
resize_algo=resize_algorithm,
fps_source=fps_source)
Expand All @@ -98,7 +101,7 @@ def __init__(self, filename, has_mask=False,
self.size = self.reader.size
self.rotation = self.reader.rotation

self.filename = self.reader.filename
self.filename = filename

if has_mask:

Expand Down
9 changes: 6 additions & 3 deletions moviepy/video/io/ffmpeg_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import sys

from moviepy.config import get_setting
from moviepy.compat import fspath
from moviepy.tools import subprocess_call


Expand All @@ -13,20 +14,22 @@ 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.
"""
folder = fspath(folder)
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]
fspath(filename)]

subprocess_call(cmd)


def ffmpeg_extract_subclip(filename, t1, t2, targetname=None):
""" Makes a new video file playing video file ``filename`` between
the times ``t1`` and ``t2``. """
filename = fspath(filename)
name, ext = os.path.splitext(filename)
if not targetname:
T1, T2 = [int(1000*t) for t in [t1, t2]]
Expand All @@ -46,7 +49,7 @@ def ffmpeg_merge_video_audio(video,audio,output, vcodec='copy',
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,
cmd = [get_setting("FFMPEG_BINARY"), "-y", "-i", fspath(audio),"-i", fspath(video),
"-vcodec", vcodec, "-acodec", acodec, output]

subprocess_call(cmd, logger = logger)
Expand All @@ -63,6 +66,6 @@ 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]
fspath(output)]

subprocess_call(cmd)
3 changes: 2 additions & 1 deletion moviepy/video/tools/credits.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
from moviepy.video.fx.resize import resize
from moviepy.compat import fspath
from moviepy.video.VideoClip import ImageClip, TextClip


Expand Down Expand Up @@ -74,7 +75,7 @@ def credits1(creditfile, width, stretch=30, color='white', stroke_color='black',

# PARSE THE TXT FILE

with open(creditfile) as f:
with open(fspath(creditfile)) as f:
lines = f.readlines()

lines = filter(lambda x: not x.startswith('\n'), lines)
Expand Down
10 changes: 9 additions & 1 deletion moviepy/video/tools/subtitles.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import numpy as np

from moviepy.compat import fspath
from moviepy.video.VideoClip import VideoClip, TextClip
from moviepy.decorators import convert_to_seconds
from moviepy.tools import cvsecs
from moviepy.video.VideoClip import TextClip, VideoClip
Expand All @@ -20,7 +22,7 @@ class SubtitlesClip(VideoClip):
==========

subtitles
Either the name of a file, or a list
Either the name of a file (a string or a path-like object), or a list

Examples
=========
Expand All @@ -39,6 +41,12 @@ def __init__(self, subtitles, make_textclip=None):

VideoClip.__init__(self, has_constant_size=False)

# only convert subtitles if it's a path-like
try:
subtitles = fspath(subtitles)
except TypeError:
pass

if isinstance( subtitles, str):
subtitles = file_to_subtitles(subtitles)

Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ def run_tests(self):
'tqdm>=4.11.2,<5.0',
'numpy',
'requests>=2.8.1,<3.0',
'proglog<=1.0.0'
'proglog<=1.0.0',
"pathlib2; python_version<'3.4'",
]

optional_reqs = [
Expand Down
37 changes: 37 additions & 0 deletions tests/test_PR.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@
from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
from moviepy.video.fx.scroll import scroll
from moviepy.video.io.VideoFileClip import VideoFileClip
from moviepy.audio.io.AudioFileClip import AudioFileClip
from moviepy.video.tools.interpolators import Trajectory
from moviepy.video.VideoClip import ColorClip, ImageClip, TextClip
from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
from moviepy.video.tools.subtitles import SubtitlesClip
from moviepy.utils import close_all_clips

from imageio.core.request import Path

from .test_helper import FONT, TMP_DIR

Expand Down Expand Up @@ -117,5 +123,36 @@ def test_PR_610():
assert composite.fps == 25


def test_PR_924_video():
"""
Test support for path-like objects as arguments for VideoFileClip.
"""
with VideoFileClip(Path('media/big_buck_bunny_432_433.webm')) as video:
video.write_videofile(Path(os.path.join(TMP_DIR, 'pathlike.mp4')))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
video.write_videofile(Path(os.path.join(TMP_DIR, 'pathlike.mp4')))
video.write_videofile(Path(TMP_DIR) / 'pathlike.mp4')

assert isinstance(video.filename, Path)


def test_PR_924_audio():
"""
Test support for path-like objects as arguments for AudioFileClip.
"""
with AudioFileClip(Path('media/crunching.mp3')) as audio:
audio.write_audiofile(Path(os.path.join(TMP_DIR, 'pathlike.mp3')))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
audio.write_audiofile(Path(os.path.join(TMP_DIR, 'pathlike.mp3')))
audio.write_audiofile(Path(TMP_DIR) / 'pathlike.mp3')



def test_PR_924_image():
"""
Test support for path-like objects as arguments for ImageClip.
"""
ImageClip(Path('media/vacation_2017.jpg')).close()


def test_PR_924_subtitles():
"""
Test support for path-like objects as arguments for SubtitlesClip.
"""
SubtitlesClip(Path('media/subtitles1.srt')).close()


if __name__ == '__main__':
pytest.main()