chore(lib): filter out video files without video stream (#416)

* chore(lib): filter out video files without video stream

This is a ( hopefully temporary) fix to #390.

Closes #390

* fix: Windows issue
This commit is contained in:
Jérome Eertmans
2024-04-16 17:17:42 +02:00
committed by GitHub
parent 498e9af2bf
commit 8a3bf87db8

View File

@ -1,7 +1,8 @@
import hashlib import hashlib
import os
import tempfile import tempfile
from pathlib import Path from pathlib import Path
from typing import List from typing import Iterator, List
import av import av
@ -10,28 +11,54 @@ from .logger import logger
def concatenate_video_files(files: List[Path], dest: Path) -> None: def concatenate_video_files(files: List[Path], dest: Path) -> None:
"""Concatenate multiple video files into one.""" """Concatenate multiple video files into one."""
f = tempfile.NamedTemporaryFile(mode="w", delete=False)
f.writelines(f"file '{path.absolute()}'\n" for path in files)
f.close()
input_ = av.open(f.name, options={"safe": "0"}, format="concat") def _filter(files: List[Path]) -> Iterator[Path]:
input_stream = input_.streams.video[0] """Patch possibly empty video files."""
output = av.open(str(dest), mode="w") for file in files:
output_stream = output.add_stream( with av.open(str(file)) as container:
template=input_stream, if len(container.streams.video) > 0:
yield file
else:
logger.warn(
f"Skipping video file {file} because it does "
"not contain any video stream. "
"This is probably caused by Manim, see: "
"https://github.com/jeertmans/manim-slides/issues/390."
) )
for packet in input_.demux(input_stream): with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
# We need to skip the "flushing" packets that `demux` generates. f.writelines(f"file '{file}'\n" for file in _filter(files))
tmp_file = f.name
with av.open(
tmp_file, format="concat", options={"safe": "0"}
) as input_container, av.open(str(dest), mode="w") as output_container:
input_video_stream = input_container.streams.video[0]
output_video_stream = output_container.add_stream(
template=input_video_stream,
)
if len(input_container.streams.audio) > 0:
input_audio_stream = input_container.streams.audio[0]
output_audio_stream = output_container.add_stream(
template=input_audio_stream,
)
for packet in input_container.demux():
if packet.dts is None: if packet.dts is None:
continue continue
# We need to assign the packet to the new stream. ptype = packet.stream.type
packet.stream = output_stream
output.mux(packet)
input_.close() if ptype == "video":
output.close() packet.stream = output_video_stream
elif ptype == "audio":
packet.stream = output_audio_stream
else:
continue # We don't support subtitles
output_container.mux(packet)
os.unlink(tmp_file) # https://stackoverflow.com/a/54768241
def merge_basenames(files: List[Path]) -> Path: def merge_basenames(files: List[Path]) -> Path:
@ -63,10 +90,13 @@ def link_nodes(*nodes: av.filter.context.FilterContext) -> None:
def reverse_video_file(src: Path, dest: Path) -> None: def reverse_video_file(src: Path, dest: Path) -> None:
"""Reverses a video file, writting the result to `dest`.""" """Reverses a video file, writting the result to `dest`."""
input_ = av.open(str(src)) with av.open(str(src)) as input_container, av.open(
input_stream = input_.streams.video[0] str(dest), mode="w"
output = av.open(str(dest), mode="w") ) as output_container:
output_stream = output.add_stream(codec_name="libx264", rate=input_stream.base_rate) input_stream = input_container.streams.video[0]
output_stream = output_container.add_stream(
codec_name=input_stream.codec_context.name, rate=input_stream.base_rate
)
output_stream.width = input_stream.width output_stream.width = input_stream.width
output_stream.height = input_stream.height output_stream.height = input_stream.height
output_stream.pix_fmt = input_stream.pix_fmt output_stream.pix_fmt = input_stream.pix_fmt
@ -80,7 +110,7 @@ def reverse_video_file(src: Path, dest: Path) -> None:
graph.configure() graph.configure()
frames_count = 0 frames_count = 0
for frame in input_.decode(video=0): for frame in input_container.decode(video=0):
graph.push(frame) graph.push(frame)
frames_count += 1 frames_count += 1
@ -88,11 +118,9 @@ def reverse_video_file(src: Path, dest: Path) -> None:
for _ in range(frames_count): for _ in range(frames_count):
frame = graph.pull() frame = graph.pull()
frame.pict_type = 5 # Otherwise we get a warning saying it is changed
output.mux(output_stream.encode(frame)) for packet in output_stream.encode(frame):
output_container.mux(packet)
for packet in output_stream.encode(): for packet in output_stream.encode():
output.mux(packet) output_container.mux(packet)
input_.close()
output.close()