Source code for mendevi.convert

"""Get ffmpeg filter chain command."""

import fractions
import pathlib
import re

from mendevi.utils import get_pix_fmt, get_rate_video, get_resolution


[docs] def filter_best_order( video: pathlib.Path | str, additional_filter: str, fps: fractions.Fraction | None, pix_fmt: str | None, resolution: tuple[int, int] | None, ) -> str: """Generate the ffmpeg filter command that performs this conversion. Parameters ---------- video : pathlike The video to be filtered. It is required to know the relevant filters and find the best possible order. additional_filter : str The additional video filter, (can be an empty string). fps : fractions.Fraction, optional The new framerate. pix_fmt : str, optional The new pixel format, it has to match the regex ``yuv4[24][024]p(?:10le|12le)?``. resolution : tuple[int, int], optional The new (height, width) video shape. Returns ------- filter : str The ffmpeg video filter argument. It can be an empty string if nothing has to be done. Examples -------- >>> from fractions import Fraction >>> import cutcutcodec >>> from mendevi.convert import filter_best_order >>> video = cutcutcodec.utils.get_project_root() / "media" / "video" / "intro.webm" >>> filter_best_order(video, additional_filter="", fps=None, pix_fmt=None, resolution=None) '' >>> filter_best_order( ... video, ... additional_filter="", ... fps=Fraction(60000, 1001), ... pix_fmt="yuv420p10le", ... resolution=(1080, 1920), ... ) 'scale=h=1080:w=1920:sws_flags=bicubic,format=yuv420p10le,fps=60000/1001' >>> filter_best_order( ... video, ... additional_filter="", ... fps=Fraction(24000, 1001), ... pix_fmt="yuv420p", ... resolution=(480, 720), ... ) 'fps=24000/1001,scale=h=480:w=720:sws_flags=bicubic' >>> """ if fps is not None: assert isinstance(fps, fractions.Fraction), fps.__class__.__name__ assert fps > 0, fps if pix_fmt is not None: assert isinstance(pix_fmt, str), pix_fmt.__class__.__name__ assert re.fullmatch(r"(?:rgb24)|(?:yuv4[24][024]p)(?:10le|12le)?", pix_fmt), pix_fmt if resolution is not None: assert isinstance(resolution, tuple), resolution.__class__.__name__ assert len(resolution) == 2, resolution assert isinstance(resolution[0], int), resolution assert isinstance(resolution[1], int), resolution assert (resolution[0], resolution[1]) > (0, 0), resolution filters: list[str] = [] # scale (the slowest) if resolution is not None and (src := get_resolution(video)) != resolution: filters = [f"scale=h={resolution[0]}:w={resolution[1]}:sws_flags=bicubic"] # format (medium) if pix_fmt is not None and (src := get_pix_fmt(video)) != pix_fmt: match = re.search(r"yuv(?P<samp>\d{3})p(?P<bit>\d+)le", src + "8le") bit_per_bloc_src = ( 96.0 if match is None else float(match["bit"]) * sum(map(int, match["samp"])) ) match = re.search(r"yuv(?P<samp>\d{3})p(?P<bit>\d+)le", pix_fmt + "8le") bit_per_bloc_dst = ( 96.0 if match is None else float(match["bit"]) * sum(map(int, match["samp"])) ) if bit_per_bloc_dst < bit_per_bloc_src: filters.insert(0, f"format={pix_fmt}") else: filters.append(f"format={pix_fmt}") # fps (the fastest) if fps is not None and (src := get_rate_video(video)) != fps: if fps < src: filters.insert(0, f"fps={fps}") else: filters.append(f"fps={fps}") # additional filter if additional_filter: filters.insert(0, additional_filter) return ",".join(filters)