diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bb2017f..d2fb1a8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Suuport for Python 3.12, drop Python 3.7 #118 - Replace "iso-369" iso639-lang by "iso639-lang" library - Rework the VideoWebmLow preset for faster encoding and smaller file size (preset has been bumped to version 2) +- When reencoding a video, ffmpeg now uses only 1 CPU thread by default (new arg to `reencode` allows to override this default value) ## [3.2.0] - 2023-12-16 diff --git a/src/zimscraperlib/video/encoding.py b/src/zimscraperlib/video/encoding.py index 20509e7a..f66618af 100644 --- a/src/zimscraperlib/video/encoding.py +++ b/src/zimscraperlib/video/encoding.py @@ -6,11 +6,35 @@ import shutil import subprocess import tempfile +from typing import List, Optional from zimscraperlib import logger from zimscraperlib.logging import nicer_args_join +def _build_ffmpeg_args( + src_path: pathlib.Path, + tmp_path: pathlib.Path, + ffmpeg_args: List[str], + threads: Optional[int], +) -> List[str]: + if threads: + if "-threads" in ffmpeg_args: + raise AttributeError("Cannot set the number of threads, already set") + else: + ffmpeg_args += ["-threads", str(threads)] + args = [ + "/usr/bin/env", + "ffmpeg", + "-y", + "-i", + f"file:{src_path}", + *ffmpeg_args, + f"file:{tmp_path}", + ] + return args + + def reencode( src_path, dst_path, @@ -18,6 +42,7 @@ def reencode( delete_src=False, # noqa: FBT002 with_process=False, # noqa: FBT002 failsafe=True, # noqa: FBT002 + threads: Optional[int] = 1, ): """Runs ffmpeg with given ffmpeg_args @@ -25,6 +50,7 @@ def reencode( src_path - Path to source file dst_path - Path to destination file ffmpeg_args - A list of ffmpeg arguments + threads - Number of encoding threads used by ffmpeg delete_src - Delete source file after convertion with_process - Optionally return the output from ffmpeg (stderr and stdout) failsafe - Run in failsafe mode @@ -32,15 +58,12 @@ def reencode( with tempfile.TemporaryDirectory() as tmp_dir: tmp_path = pathlib.Path(tmp_dir).joinpath(f"video.tmp{dst_path.suffix}") - args = [ - "/usr/bin/env", - "ffmpeg", - "-y", - "-i", - f"file:{src_path}", - *ffmpeg_args, - f"file:{tmp_path}", - ] + args = _build_ffmpeg_args( + src_path=src_path, + tmp_path=tmp_path, + ffmpeg_args=ffmpeg_args, + threads=threads, + ) logger.debug( f"Encode {src_path} -> {dst_path} video format = {dst_path.suffix}" ) diff --git a/tests/video/test_encoding.py b/tests/video/test_encoding.py new file mode 100644 index 00000000..292f660a --- /dev/null +++ b/tests/video/test_encoding.py @@ -0,0 +1,94 @@ +import re +from pathlib import Path +from typing import List, Optional + +import pytest + +from zimscraperlib.video.encoding import _build_ffmpeg_args + + +@pytest.mark.parametrize( + "src_path,tmp_path,ffmpeg_args,threads,expected", + [ + ( + Path("path1/file1.mp4"), + Path("path1/fileout.mp4"), + [ + "-codec:v", + "libx265", + ], + None, + [ + "/usr/bin/env", + "ffmpeg", + "-y", + "-i", + "file:path1/file1.mp4", + "-codec:v", + "libx265", + "file:path1/fileout.mp4", + ], + ), + ( + Path("path2/file2.mp4"), + Path("path12/tmpfile.mp4"), + [ + "-b:v", + "300k", + ], + 1, + [ + "/usr/bin/env", + "ffmpeg", + "-y", + "-i", + "file:path2/file2.mp4", + "-b:v", + "300k", + "-threads", + "1", + "file:path12/tmpfile.mp4", + ], + ), + ( + Path("path2/file2.mp4"), + Path("path12/tmpfile.mp4"), + [ + "-b:v", + "300k", + "-threads", + "1", + ], + 1, + None, + ), + ], +) +def test_build_ffmpeg_args( + src_path: Path, + tmp_path: Path, + ffmpeg_args: List[str], + threads: Optional[int], + expected: Optional[List[str]], +): + if expected: + assert ( + _build_ffmpeg_args( + src_path=src_path, + tmp_path=tmp_path, + ffmpeg_args=ffmpeg_args, + threads=threads, + ) + == expected + ) + else: + with pytest.raises( + AttributeError, + match=re.escape("Cannot set the number of threads, already set"), + ): + _build_ffmpeg_args( + src_path=src_path, + tmp_path=tmp_path, + ffmpeg_args=ffmpeg_args, + threads=threads, + )