Source code for cutcutcodec.core.analysis.stream.rate_video

"""Smartly choose the framerate of a video stream."""

from fractions import Fraction

from cutcutcodec.core.classes.stream_video import StreamVideo
from cutcutcodec.core.io.read_ffmpeg import ContainerInputFFMPEG

FPS_ESTIMATORS = {}  # to each node stream class, associate the func to find the optimal rate


def _add_estimator(node_cls: type) -> callable:
    def _add_func(func) -> callable:
        FPS_ESTIMATORS[node_cls] = func
        return func
    return _add_func


@_add_estimator(ContainerInputFFMPEG)
def _optimal_rate_container_input_ffmpeg(stream: StreamVideo) -> Fraction:
    """Detect the rate of a ContainerInputFFMPEG stream.

    Examples
    --------
    >>> from cutcutcodec.core.analysis.stream.rate_video import optimal_rate_video
    >>> from cutcutcodec.core.io.read_ffmpeg import ContainerInputFFMPEG
    >>> from cutcutcodec.utils import get_project_root
    >>> video = get_project_root() / "media" / "video" / "intro.webm"
    >>> stream = ContainerInputFFMPEG(video).out_select("video")[0]
    >>> optimal_rate_video(stream)
    Fraction(30000, 1001)
    >>>

    """
    assert isinstance(stream.node, ContainerInputFFMPEG), stream.node.__class__.__name__
    return stream.rate


[docs] def optimal_rate_video( stream: StreamVideo, choices: set[Fraction] | None = None, ) -> Fraction: """Find the optimal frame rate for a given video stream. Parameters ---------- stream : cutcutcodec.core.classes.stream_video.StreamVideo The video stream that we want to find the optimal fps. choices : set[Fraction], optional The possible fps. If provide, returns the most appropriate fps of this set. Returns ------- framerate : numbers.Real The framerate (maximum) that allows to minimize / cancel the loss of information, (minimum) and avoids an excess of frame that does not bring more information. """ # verifications assert isinstance(stream, StreamVideo), stream.__class__.__name__ if choices is not None: assert isinstance(choices, set) and all(isinstance(r, Fraction) and r > 0 for r in choices) # optimisation if choices and len(choices) == 1: # case not nescessary to do computing return choices.pop() # estimation of the best fps if (estimator := FPS_ESTIMATORS.get(stream.node.__class__, None)) is not None: fps = estimator(stream) else: fps = max( (optimal_rate_video(s) for s in stream.node.in_streams if s.type == "video"), default=0, ) # select the most appropriate rate among the choices if not choices or not fps: return min(choices) if choices else fps for choice in sorted(choices): if fps <= choice: return choice return max(choices)