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

Use audio codec for existing audiofile #2346

Merged
merged 17 commits into from
Jan 26, 2025
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
4 changes: 4 additions & 0 deletions moviepy/video/VideoClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,9 @@ def write_videofile(
write_logfile=write_logfile,
logger=logger,
)
# The audio is already encoded,
# so there is no need to encode it during video export
audio_codec = "copy"

ffmpeg_write_video(
self,
Expand All @@ -396,6 +399,7 @@ def write_videofile(
preset=preset,
write_logfile=write_logfile,
audiofile=audiofile,
audio_codec=audio_codec,
threads=threads,
ffmpeg_params=ffmpeg_params,
logger=logger,
Expand Down
18 changes: 14 additions & 4 deletions moviepy/video/io/ffmpeg_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class FFMPEG_VideoWriter:
audiofile : str, optional
The name of an audio file that will be incorporated to the video.

audio_codec : str, optional
FFMPEG audio codec. If None, ``"copy"`` codec is used.

preset : str, optional
Sets the time that FFMPEG will take to compress the video. The slower,
the better the compression rate. Possibilities are: ``"ultrafast"``,
Expand Down Expand Up @@ -81,6 +84,7 @@ def __init__(
fps,
codec="libx264",
audiofile=None,
audio_codec=None,
preset="medium",
bitrate=None,
with_mask=False,
Expand All @@ -94,6 +98,7 @@ def __init__(
self.logfile = logfile
self.filename = filename
self.codec = codec
self.audio_codec = audio_codec
self.ext = self.filename.split(".")[-1]

pixel_format = "rgba" if with_mask else "rgb24"
Expand All @@ -119,7 +124,9 @@ def __init__(
"-",
]
if audiofile is not None:
cmd.extend(["-i", audiofile, "-acodec", "copy"])
if audio_codec is None:
audio_codec = "copy"
cmd.extend(["-i", audiofile, "-acodec", audio_codec])

if codec == "h264_nvenc":
cmd.extend(["-c:v", codec])
Expand Down Expand Up @@ -175,13 +182,14 @@ def write_frame(self, img_array):
f"writing file {self.filename}:\n\n {ffmpeg_error}"
)

if "Unknown encoder" in ffmpeg_error:
if "Unknown encoder" in ffmpeg_error or "Unknown decoder" in ffmpeg_error:
error += (
"\n\nThe video export failed because FFMPEG didn't find the "
f"specified codec for video encoding {self.codec}. "
"specified codec for video or audio. "
"Please install this codec or change the codec when calling "
"write_videofile.\nFor instance:\n"
" >>> clip.write_videofile('myvid.webm', codec='libvpx')"
" >>> clip.write_videofile('myvid.webm', audio='myaudio.mp3', "
"codec='libvpx', audio_codec='aac')"
)

elif "incorrect codec parameters ?" in ffmpeg_error:
Expand Down Expand Up @@ -240,6 +248,7 @@ def ffmpeg_write_video(
preset="medium",
write_logfile=False,
audiofile=None,
audio_codec=None,
threads=None,
ffmpeg_params=None,
logger="bar",
Expand Down Expand Up @@ -269,6 +278,7 @@ def ffmpeg_write_video(
with_mask=has_mask,
logfile=logfile,
audiofile=audiofile,
audio_codec=audio_codec,
threads=threads,
ffmpeg_params=ffmpeg_params,
pixel_format=pixel_format,
Expand Down
22 changes: 20 additions & 2 deletions tests/test_VideoClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def test_write_frame_errors(util, video):
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)
" codec for video or audio" in str(e.value)
), e.value

autogenerated_location = "unlogged-writeTEMP_MPY_wvf_snd.mp3"
Expand All @@ -84,7 +84,7 @@ def test_write_frame_errors_with_redirected_logs(util, video):
clip.write_videofile(location, codec="nonexistent-codec", write_logfile=True)
assert (
"The video export failed because FFMPEG didn't find the specified"
" codec for video encoding nonexistent-codec" in str(e.value)
" codec for video or audio" in str(e.value)
)

autogenerated_location_mp3 = "logged-writeTEMP_MPY_wvf_snd.mp3"
Expand All @@ -106,6 +106,24 @@ def test_write_videofiles_with_temp_audiofile_path(util):
assert any(file.startswith("temp_audiofile_path") for file in contents_of_temp_dir)


def test_write_videofiles_audio_codec_error(util, video):
"""Checks error cases return helpful messages."""
clip = video()
location = os.path.join(util.TMP_DIR, "unlogged-write.mp4")
with pytest.raises(IOError) as e:
clip.write_videofile(
location, audio="media/crunching.mp3", audio_codec="nonexistent-codec"
)
assert (
"The video export failed because FFMPEG didn't find the specified"
" codec for video or audio" in str(e.value)
), e.value

autogenerated_location = "unlogged-writeTEMP_MPY_wvf_snd.mp3"
if os.path.exists(autogenerated_location):
os.remove(autogenerated_location)


@pytest.mark.parametrize("mask_color", (0, 0.5, 0.8, 1))
@pytest.mark.parametrize(
"with_mask",
Expand Down
Loading