Skip to content
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[@lapotist](https://github.com/lapotist) [#664](https://github.com/jeertmans/manim-slides/pull/664)
- Fixed presenter looping when reversing multiple slides.
[@wuerfelfreak](https://github.com/wuerfelfreak) [#665](https://github.com/jeertmans/manim-slides/pull/665)
- Fixed `concatenate_video_files` failing on relative input paths and on paths containing single quotes, by writing absolute, properly escaped entries to the concat list file.
[@Agi-Asi](https://github.com/Agi-Asi) [#673](https://github.com/jeertmans/manim-slides/pull/673)

(v5.6.0)=
## [v5.6.0](https://github.com/jeertmans/manim-slides/compare/v5.5.4...v5.6.0)
Expand Down
7 changes: 6 additions & 1 deletion manim_slides/utils.py
Comment thread
jeertmans marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ def _filter(files: list[Path]) -> Iterator[Path]:
with tempfile.NamedTemporaryFile(
mode="w", suffix=".txt", delete=False, encoding="utf-8"
) as f:
f.writelines(f"file '{file}'\n" for file in _filter(files))
# Write absolute paths and escape single quotes ('\'') so that ffmpeg's
# concat demuxer parses every entry correctly, see #673.
f.writelines(
"file '{}'\n".format(file.resolve().as_posix().replace("'", "'\\''"))
for file in _filter(files)
)
tmp_file = f.name

with (
Expand Down
41 changes: 40 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import shutil
from pathlib import Path

from manim_slides.utils import merge_basenames
import av
import pytest

from manim_slides.utils import concatenate_video_files, merge_basenames


def test_merge_basenames(paths: list[Path]) -> None:
Expand All @@ -20,3 +24,38 @@ def test_merge_basenames_same_with_different_parent_directories(
p4 = d2 / "d/e/f/two.txt"

assert merge_basenames([p1, p2]).name == merge_basenames([p3, p4]).name


def assert_has_decodable_video_stream(dest: Path) -> None:
assert dest.exists()
with av.open(str(dest)) as container:
assert sum(1 for _ in container.decode(video=0)) > 0


def test_concatenate_video_files_relative_paths(
video_file: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
# The concat list file lives in the system temp folder, and the concat
# demuxer resolves relative entries against *that* folder — so relative
# input paths used to fail with FileNotFoundError.
monkeypatch.chdir(video_file.parent)
relative = Path(video_file.name)
dest = tmp_path / "out.mp4"

concatenate_video_files([relative, relative], dest)

assert_has_decodable_video_stream(dest)


def test_concatenate_video_files_quoted_path(video_file: Path, tmp_path: Path) -> None:
# A single quote in a path used to terminate the quoted list entry early,
# making the entry unparsable for the concat demuxer.
quoted_dir = tmp_path / "John's slides"
quoted_dir.mkdir()
src = quoted_dir / video_file.name
shutil.copy(video_file, src)
dest = tmp_path / "out.mp4"

concatenate_video_files([src, src], dest)

assert_has_decodable_video_stream(dest)
Loading