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))
if packet.dts is None: tmp_file = f.name
continue
# We need to assign the packet to the new stream. with av.open(
packet.stream = output_stream tmp_file, format="concat", options={"safe": "0"}
output.mux(packet) ) 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,
)
input_.close() if len(input_container.streams.audio) > 0:
output.close() 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:
continue
ptype = packet.stream.type
if ptype == "video":
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,36 +90,37 @@ 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.width = input_stream.width output_stream = output_container.add_stream(
output_stream.height = input_stream.height codec_name=input_stream.codec_context.name, rate=input_stream.base_rate
output_stream.pix_fmt = input_stream.pix_fmt )
output_stream.width = input_stream.width
output_stream.height = input_stream.height
output_stream.pix_fmt = input_stream.pix_fmt
graph = av.filter.Graph() graph = av.filter.Graph()
link_nodes( link_nodes(
graph.add_buffer(template=input_stream), graph.add_buffer(template=input_stream),
graph.add("reverse"), graph.add("reverse"),
graph.add("buffersink"), graph.add("buffersink"),
) )
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
graph.push(None) # EOF: https://github.com/PyAV-Org/PyAV/issues/886. graph.push(None) # EOF: https://github.com/PyAV-Org/PyAV/issues/886.
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(): for packet in output_stream.encode(frame):
output.mux(packet) output_container.mux(packet)
input_.close() for packet in output_stream.encode():
output.close() output_container.mux(packet)