Zuhair
Build SORT MOT17 demo
426d46b
Raw
History Blame Contribute Delete
2.89 kB
from __future__ import annotations
from pathlib import Path
import subprocess
from typing import Iterable
import cv2
import numpy as np
try:
from imageio_ffmpeg import get_ffmpeg_exe
except ImportError: # pragma: no cover
get_ffmpeg_exe = None
Frame = np.ndarray
def _ffmpeg_executable() -> str | None:
if get_ffmpeg_exe is None:
return None
try:
return get_ffmpeg_exe()
except Exception:
return None
class MP4Writer:
"""Incremental MP4 writer that prefers a final H.264 output."""
def __init__(self, output_path: str | Path, fps: int | float) -> None:
self.output_path = Path(output_path)
self.fps = float(fps)
self._temp_output_path = self.output_path.with_name(f"{self.output_path.stem}.tmp.mp4")
self._writer: cv2.VideoWriter | None = None
self._frame_size: tuple[int, int] | None = None
def add_frame(self, frame: Frame) -> None:
if frame is None:
raise ValueError("Cannot write an empty frame.")
height, width = frame.shape[:2]
frame_size = (width, height)
if self._writer is None:
self.output_path.parent.mkdir(parents=True, exist_ok=True)
self._frame_size = frame_size
self._writer = cv2.VideoWriter(
str(self._temp_output_path),
cv2.VideoWriter_fourcc(*"mp4v"),
self.fps,
frame_size,
)
if not self._writer.isOpened():
raise RuntimeError(f"Could not open output video writer for '{self._temp_output_path}'.")
elif frame_size != self._frame_size:
raise ValueError(
f"All frames must share one size. Expected {self._frame_size}, got {frame_size}."
)
self._writer.write(frame)
def close(self) -> Path:
if self._writer is None:
return self.output_path
self._writer.release()
self._writer = None
ffmpeg_executable = _ffmpeg_executable()
if ffmpeg_executable is None:
self._temp_output_path.replace(self.output_path)
return self.output_path
command = [
ffmpeg_executable,
"-y",
"-i",
str(self._temp_output_path),
"-c:v",
"libx264",
"-pix_fmt",
"yuv420p",
str(self.output_path),
]
subprocess.run(command, check=True, capture_output=True, text=True)
self._temp_output_path.unlink(missing_ok=True)
return self.output_path
def frames2mp4(frames: Iterable[Frame], output_path: str | Path, fps: int | float) -> Path:
"""Write an iterable of frames to a mp4 file."""
writer = MP4Writer(output_path=output_path, fps=fps)
for frame in frames:
writer.add_frame(frame)
return writer.close()