Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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 segmented video reversal stalls and made segment ordering deterministic.
[@rusetiq](https://github.com/rusetiq) [#668](https://github.com/jeertmans/manim-slides/pull/668)

(v5.6.0)=
## [v5.6.0](https://github.com/jeertmans/manim-slides/compare/v5.5.4...v5.6.0)
Expand Down
2 changes: 1 addition & 1 deletion manim_slides/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def open_with_default(file: Path) -> None:
if system == "Darwin":
subprocess.call(("open", str(file)))
elif system == "Windows":
os.startfile(str(file)) # type: ignore[attr-defined]
os.startfile(str(file)) # nosec B606 # ty: ignore[unresolved-attribute]

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What tool did you use to report a security issue? Because this error should not be raised by the linters used for this project.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i used codacy’s public pr api for that as an external pr check before pushing, ill revert the unrelated stuff

else:
subprocess.call(("xdg-open", str(file)))

Expand Down
11 changes: 7 additions & 4 deletions manim_slides/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import tempfile
from collections.abc import Iterator
from itertools import pairwise
from multiprocessing import Pool
from multiprocessing import get_context
from pathlib import Path
from typing import Any, TypeVar, cast

Expand Down Expand Up @@ -197,7 +197,7 @@ def reverse_video_file(
with tempfile.TemporaryDirectory() as tmpdirname:
tmpdir = Path(tmpdirname)
with av.open(
str(tmpdir / f"%04d.{src.suffix}"),
str(tmpdir / f"%04d{src.suffix}"),
"w",
format="segment",
options={"segment_time": str(max_segment_duration)},
Expand All @@ -215,12 +215,15 @@ def reverse_video_file(
packet.stream = output_stream
output_container.mux(packet)

src_files = list(tmpdir.iterdir())
src_files = sorted(tmpdir.iterdir())
rev_files = [
src_file.with_stem("rev_" + src_file.stem) for src_file in src_files
]

with Pool(num_processes, maxtasksperchild=1) as pool:
# Forking after the renderer and PyAV have initialized can inherit
# locked native state and stall the worker pool. Fresh interpreters
# avoid that state while preserving parallel segment processing.
with get_context("spawn").Pool(num_processes, maxtasksperchild=1) as pool:
for _ in tqdm(
pool.imap_unordered(
reverse_video_file_in_one_chunk,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def test_quoted_enum(enum_type: type[Enum]) -> None:
)
def test_quoted_enum_survives_pydantic_validation(enum_type: type[Enum]) -> None:
class Model(BaseModel):
value: enum_type # type: ignore[valid-type]
value: enum_type # ty: ignore[invalid-type-form]

for enum in enum_type:
if enum in ["true", "false", "null"]:
Expand Down
53 changes: 52 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from collections.abc import Iterator
from pathlib import Path

from manim_slides.utils import merge_basenames
import av
import numpy as np
import pytest

from manim_slides.utils import merge_basenames, reverse_video_file


def test_merge_basenames(paths: list[Path]) -> None:
Expand All @@ -20,3 +25,49 @@ 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 test_reverse_video_file_with_unordered_segments(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
src = tmp_path / "src.mp4"
dest = tmp_path / "dest.mp4"

with av.open(str(src), mode="w") as container:
stream = container.add_stream("libx264", rate=2)
stream.width = 16
stream.height = 16
stream.pix_fmt = "yuv420p"
stream.codec_context.gop_size = 1

for value in range(6):
array = np.full((16, 16, 3), value * 40, dtype=np.uint8)
frame = av.VideoFrame.from_ndarray(array, format="rgb24")
for packet in stream.encode(frame):
container.mux(packet)

for packet in stream.encode():
container.mux(packet)

original_iterdir = Path.iterdir

def reversed_iterdir(path: Path) -> Iterator[Path]:
return iter(reversed(list(original_iterdir(path))))

monkeypatch.setattr(Path, "iterdir", reversed_iterdir)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the context manager and only call the function that should be affected inside the scope :-)


reverse_video_file(
src,
dest,
max_segment_duration=1.0,
num_processes=1,
disable=True,
)

with av.open(str(dest)) as container:
values = [
float(frame.to_ndarray(format="gray").mean())
for frame in container.decode(video=0)
]

np.testing.assert_array_equal(values, sorted(values, reverse=True))
Loading