Source code for cutcutcodec.core.analysis.video.properties.nb_frames

"""Recover the number of frames in a video stream.

This allows not only the characteristics of the files but also the tags if there are any.
"""

import collections
import pathlib

import cv2  # pip install opencv-contrib-python-headless

from cutcutcodec.core.analysis._helper_properties import _check_pathexists_index, _mix_and_check
from cutcutcodec.core.analysis.ffprobe import (
    _estimate_len_ffmpeg,
    _map_index_rel_to_abs,
    get_slices_metadata,
)
from cutcutcodec.core.exceptions import MissingInformation, MissingStreamError


def _count_frames_ffmpeg(filename: str, index: int) -> int:
    """Count the number of frames with the ffmpeg decoder.

    Slow but 100% accurate method.

    Examples
    --------
    >>> from cutcutcodec.core.analysis.video.properties.nb_frames import _count_frames_ffmpeg
    >>> from cutcutcodec.utils import get_project_root
    >>> video = str(get_project_root() / "media" / "video" / "intro.webm")
    >>> _count_frames_ffmpeg(video, 0)
    294
    >>>

    """
    _, infos = get_slices_metadata(filename, slice_type="frame")
    if index >= len(infos):
        raise MissingInformation(f"'ffmpeg' did not decode '{filename}' stream {index}")
    frames = infos[index].shape[0]
    return frames


def _count_frames_cv2(filename: str, index: int) -> int:
    """Count the number of frames with the cv2 decoder.

    Slow but 100% accurate method.

    Examples
    --------
    >>> from cutcutcodec.core.analysis.video.properties.nb_frames import _count_frames_cv2
    >>> from cutcutcodec.utils import get_project_root
    >>> video = str(get_project_root() / "media" / "video" / "intro.webm")
    >>> _count_frames_cv2(video, 0)
    294
    >>>

    """
    cap = cv2.VideoCapture(filename, index)
    if not cap.isOpened():
        raise MissingStreamError(f"impossible to open '{filename}' stream {index} with 'cv2'")
    frames = 0
    while True:
        if not cap.read()[0]:
            break
        frames += 1
    cap.release()
    if not frames:
        raise MissingStreamError(f"'cv2' did not find any frames '{filename}' stream {index}")
    return frames


def _estimate_frames_cv2(filename: str, index: int) -> int:
    """Extract the number of frames from the metadata.

    Very fast method but inaccurate. It doesn't work all the time.

    Examples
    --------
    >>> from cutcutcodec.core.analysis.video.properties.nb_frames import _estimate_frames_cv2
    >>> from cutcutcodec.utils import get_project_root
    >>> video = str(get_project_root() / "media" / "video" / "intro.webm")
    >>> _estimate_frames_cv2(video, 0)
    296
    >>>

    """
    cap = cv2.VideoCapture(filename, index)
    if not cap.isOpened():
        raise MissingStreamError(f"impossible to open '{filename}' stream {index} with 'cv2'")
    frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    cap.release()
    if frames <= 0:  # we saw a case at -553402322211286528
        raise MissingInformation(f"'cv2' does not detect any frame in '{filename}' stream {index}")
    return frames


[docs] def get_nb_frames( filename: pathlib.Path | str | bytes, index: int = 0, *, backend: str | None = None, accurate: bool = False, ) -> int: """Recovers the number of frames present in a video stream. Parameters ---------- filename : pathlike The pathlike of the file containing a video stream. index : int The relative index of the video stream being considered, by default the first stream encountered is selected. backend : str, optional - None (default) : Try to read the stream by trying differents backends. - 'ffmpeg' : Uses the ``ffmpeg`` program in the background. - 'cv2' : Uses the module ``pip install opencv-contrib-python-headless``. accurate : boolean, optional If True, recovers the number of frames by fully decoding all the frames in the video. It is very accurate but very slow. If False (default), first tries to get the frame count from the file metadata. It's not accurate but very fast. Returns ------- nbr : int The number of readed frames. Raises ------ MissingStreamError If the file does not contain a playable video stream. MissingInformation If the information is unavailable. Examples -------- >>> from cutcutcodec.core.analysis.video.properties.nb_frames import get_nb_frames >>> from cutcutcodec.utils import get_project_root >>> video = get_project_root() / "media" / "video" / "intro.webm" >>> get_nb_frames(video) 294 >>> """ _check_pathexists_index(filename, index) return _mix_and_check( backend, accurate, (str(pathlib.Path(filename)), index), collections.OrderedDict([ ( ( lambda filename, index: _estimate_len_ffmpeg( filename, _map_index_rel_to_abs(filename, index, "video"), ) ), {"accurate": False, "backend": "ffmpeg"}, ), (_estimate_frames_cv2, {"accurate": False, "backend": "cv2"}), ( ( lambda filename, index: _count_frames_ffmpeg( filename, _map_index_rel_to_abs(filename, index, "video"), ) ), {"accurate": True, "backend": "ffmpeg"}, ), (_count_frames_cv2, {"accurate": True, "backend": "cv2"}), ]), )