Skip to content

Commit

Permalink
Fix write_videofile error handling and update write_audiofile to be c…
Browse files Browse the repository at this point in the history
…omparable (#1164)
  • Loading branch information
tburrows13 committed May 7, 2020
1 parent 5eb20a7 commit 843c0ae
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 122 deletions.
144 changes: 72 additions & 72 deletions moviepy/audio/io/ffmpeg_audiowriter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import subprocess as sp
import warnings

import proglog

Expand Down Expand Up @@ -45,42 +46,42 @@ def __init__(
logfile=None,
ffmpeg_params=None,
):

self.filename = filename
self.codec = codec

if logfile is None:
logfile = sp.PIPE

cmd = (
[
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]
)
self.logfile = logfile
self.filename = filename
self.codec = codec
self.ext = self.filename.split(".")[-1]

# order is important
cmd = [
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",
"-",
]
if input_video is None:
cmd.extend(["-vn"])
else:
cmd.extend(["-i", input_video, "-vcodec", "copy"])

cmd.extend(["-acodec", codec] + ["-ar", "%d" % fps_input])
cmd.extend(["-strict", "-2"]) # needed to support codec 'aac'
if bitrate is not None:
cmd.extend(["-ab", bitrate])
if ffmpeg_params is not None:
cmd.extend(ffmpeg_params)
cmd.extend([filename])

popen_params = {"stdout": sp.DEVNULL, "stderr": logfile, "stdin": sp.PIPE}

Expand All @@ -91,53 +92,52 @@ def __init__(

def write_frames(self, frames_array):
try:
try:
self.proc.stdin.write(frames_array.tobytes())
except NameError:
self.proc.stdin.write(frames_array.tostring())
self.proc.stdin.write(frames_array.tobytes())
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)
_, ffmpeg_error = self.proc.communicate()
if ffmpeg_error is not None:
ffmpeg_error = ffmpeg_error.decode()
else:
# The error was redirected to a logfile with `write_logfile=True`,
# so read the error from that file instead
self.logfile.seek(0)
ffmpeg_error = self.logfile.read()

error = (
f"{err}\n\nMoviePy error: FFMPEG encountered the following error while "
f"writing file {self.filename}:\n\n {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 write_videofile or write_audiofile. For instance "
"for mp3:\n"
if "Unknown encoder" in ffmpeg_error:
error += (
"\n\nThe audio export failed because FFMPEG didn't find the "
f"specified codec for audio encoding {self.codec}. "
"Please install this codec or change the codec when calling "
"write_videofile or write_audiofile.\nFor instance for mp3:\n"
" >>> write_videofile('myvid.mp4', audio_codec='libmp3lame')"
) % (self.codec)

elif b"incorrect codec parameters ?" in ffmpeg_error:
)

error = error + (
elif "incorrect codec parameters ?" in ffmpeg_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 write_videofile. This would "
"be 'libmp3lame' for mp3, 'libvorbis' for ogg..."
) % (self.codec, self.ext)

elif b"encoder setup failed" in ffmpeg_error:
f"codec specified for the video {self.codec} is not compatible"
f" with the given extension {self.ext}. Please specify a "
"valid 'codec' argument in write_audiofile or 'audio_codoc'"
"argument in write_videofile. This would be "
"'libmp3lame' for mp3, 'libvorbis' for ogg..."
)

error = error + (
elif "bitrate not specified" in ffmpeg_error:
error += (
"\n\nThe audio export failed, possily because the "
"bitrate you specified was two high or too low for "
"the video codec."
"bitrate you specified was too high or too low for "
"the audio 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)."
elif "Invalid encoder type" in ffmpeg_error:
error += (
"\n\nThe audio export failed because the codec "
"or file extension you provided is not suitable for audio"
)

raise IOError(error)
Expand All @@ -148,7 +148,7 @@ def close(self):
self.proc.stdin = None
if self.proc.stderr is not None:
self.proc.stderr.close()
self.proc.stdee = None
self.proc.stderr = None
# If this causes deadlocks, consider terminating instead.
self.proc.wait()
self.proc = None
Expand Down
87 changes: 41 additions & 46 deletions moviepy/video/io/ffmpeg_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ def __init__(
threads=None,
ffmpeg_params=None,
):

if logfile is None:
logfile = sp.PIPE
self.logfile = logfile
Expand Down Expand Up @@ -135,57 +134,55 @@ def write_frame(self, img_array):
try:
self.proc.stdin.write(img_array.tobytes())
except IOError as err:
logs = self.logfile.name
_, ffmpeg_error = self.proc.communicate()
if not ffmpeg_error:
with open(logs, "rb") as f:
ffmpeg_error = f.read()

error = str(err) + (
"\n\nMoviePy error: FFMPEG encountered "
"the following error while writing file %s:"
"\n\n %s" % (self.filename, str(ffmpeg_error))
if ffmpeg_error is not None:
ffmpeg_error = ffmpeg_error.decode()
else:
# The error was redirected to a logfile with `write_logfile=True`,
# so read the error from that file instead
self.logfile.seek(0)
ffmpeg_error = self.logfile.read()

error = (
f"{err}\n\nMoviePy error: FFMPEG encountered the following error while "
f"writing file {self.filename}:\n\n {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"
if "Unknown encoder" in ffmpeg_error:
error += (
"\n\nThe video export failed because FFMPEG didn't find the "
f"specified codec for video encoding {self.codec}. "
"Please install this codec or change the codec when calling "
"write_videofile.\nFor 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. "
elif "incorrect codec parameters ?" in ffmpeg_error:
error += (
"\n\nThe video export failed, possibly because the codec "
f"specified for the video {self.codec} is not compatible with "
f"the given extension {self.ext}.\n"
"Please specify a valid 'codec' argument in write_videofile.\n"
"This would be 'libx264' or 'mpeg4' for mp4, "
"'libtheora' for ogv, 'libvpx for webm.\n"
"Another possible reason is that the audio codec was not "
"compatible with the video codec. For instance the video "
"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:
elif "bitrate not specified" 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 += (
"\n\nThe video export failed, possibly because the bitrate "
"specified was too high or too low for the video codec."
)

elif b"Invalid encoder type" in ffmpeg_error:
elif "Invalid encoder type" in ffmpeg_error:

error = error + (
error += (
"\n\nThe video export failed because the codec "
"or file extension you provided is not a video"
"or file extension you provided is not suitable for video"
)

raise IOError(error)
Expand All @@ -197,7 +194,7 @@ def close(self):
self.proc.stderr.close()
self.proc.wait()

self.proc = None
self.proc = None

# Support the Context Manager protocol, to ensure that resources are cleaned up.

Expand Down Expand Up @@ -295,13 +292,11 @@ 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"),
]
error = (
f"{err}\n\nMoviePy error: FFMPEG encountered the following error while "
f"writing file {filename} with command {cmd}:\n\n {err.decode()}"
)
raise IOError(err)

raise IOError(error)

del proc
23 changes: 19 additions & 4 deletions tests/test_VideoClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,30 @@ def test_check_codec():
close_all_clips(locals())


def test_errors_with_redirected_logs():
def test_write_frame_errors():
"""Checks error cases return helpful messages"""
clip = VideoFileClip("media/big_buck_bunny_432_433.webm")
location = os.path.join(TMP_DIR, "unlogged-write.mp4")
with pytest.raises(IOError) as e:
clip.write_videofile(location, codec="nonexistent-codec")
assert (
"The video export failed because FFMPEG didn't find the specified codec for video "
"encoding nonexistent-codec" in str(e.value)
), e.value
close_all_clips(locals())


def test_write_frame_errors_with_redirected_logs():
"""Checks error cases return helpful messages even when logs redirected
See https://github.com/Zulko/moviepy/issues/877"""
clip = VideoFileClip("media/big_buck_bunny_432_433.webm")
location = os.path.join(TMP_DIR, "logged-write.mp4")
with pytest.raises(IOError) as e:
clip.write_videofile(location, codec="nonexistent-codec", write_logfile=True)
assert "Unknown encoder 'nonexistent-codec'" in str(e.value)
assert (
"The video export failed because FFMPEG didn't find the specified codec for video "
"encoding nonexistent-codec" in str(e.value)
)
close_all_clips(locals())


Expand Down Expand Up @@ -192,5 +208,4 @@ def test_withoutaudio():


if __name__ == "__main__":
# pytest.main()
test_write_videofiles_with_temp_audiofile_path()
pytest.main()

0 comments on commit 843c0ae

Please sign in to comment.