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

Fix write_videofile error handling and update write_audiofile to be comparable #1164

Merged
Merged
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
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 @@ -26,14 +26,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 @@ -175,5 +191,4 @@ def test_withoutaudio():


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