Coverage for /home/ubuntu/cv-depot/python/cv_depot/core/video.py: 100%
35 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-08 20:26 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-08 20:26 +0000
1from typing import Any, Dict, Union # noqa F401
2from cv_depot.core.types import Filepath # noqa F401
4import os
5from pathlib import Path
6import re
8import ffmpeg
9from lunchbox.enforce import Enforce
11from cv_depot.core.enum import VideoCodec, VideoFormat
12# ------------------------------------------------------------------------------
15def get_video_metadata(filepath):
16 # type: (Filepath) -> Dict[str, Any]
17 '''
18 Retrieve video file metadata with ffmpeg.
20 Args:
21 filepath (str or Path): Path to video file.
23 Raises:
24 EnforceError: If filepath is not a file or does not exist.
25 EnforceError: If filepath format is unsupported.
27 Returns:
28 dict: Metadata.
29 '''
30 filepath = Path(filepath)
32 msg = f'{filepath} is not a file or does not exist.'
33 Enforce(filepath.is_file(), '==', True, message=msg)
35 ext = filepath.suffix[1:]
36 formats = ['m4v', 'mov', 'mp4', 'mpg', 'mpeg']
37 msg = 'Illegal format: {a}. Legal formats: {b}.'
38 Enforce(ext.lower(), 'in', formats, message=msg)
39 # --------------------------------------------------------------------------
41 meta = ffmpeg.probe(filepath.as_posix())['streams'][0]
42 lut = {'16': 'FLOAT16', '32': 'FLOAT32', '8': 'UINT8'}
43 output = dict(
44 width=meta['width'],
45 height=meta['height'],
46 num_frames=int(meta['nb_frames']),
47 frame_rate=int(meta['r_frame_rate'].split('/')[0]),
48 bit_depth=lut[meta['bits_per_raw_sample']],
49 codec=meta['codec_name'],
50 )
51 return output
54def write_video(
55 source, # type: Filepath
56 target, # type: Filepath
57 framerate=24, # type: int
58 codec=VideoCodec.H264, # type: VideoCodec
59 format_=VideoFormat.MP4 # type: VideoFormat
60):
61 # type: (...) -> None
62 '''
63 Writes given input file or file pattern to given target file.
65 Args:
66 source (str or Path): Source image filepath or file pattern.
67 target (str or Path): Target file.
68 framerate (int, optional): Video framerate. Default: 24 fps.
69 codec (VideoCodec, optional): Video codec. Default: VideoCodec.H264.
70 format_ (VideoFormat, optional): Video container format.
71 Default: VideoFormat.MP4
73 Raises:
74 EnforceError: If source is not a filepath or file pattern.
75 EnforceError: framerate is not an integer greater than 0.
76 EnforceError: If codec is illegal.
77 EnforceError: If format is illegal.
78 '''
79 source = Path(source).as_posix()
80 target = Path(target).as_posix()
81 if not Path(source).is_file():
82 msg = 'Source must be a video file or file pattern with {a} in it. '
83 msg += 'Given value: {b}.'
84 Enforce('{frame}', 'in', source, message=msg)
85 source = re.sub(r'\{frame\}', '%04d', source)
87 msg = 'Framerate must be an integer greater than 0. Given value: {a}.'
88 Enforce(framerate, 'instance of', int, message=msg)
89 Enforce(framerate, '>', 0, message=msg)
91 Enforce(codec, 'instance of', VideoCodec)
92 Enforce(format_, 'instance of', VideoFormat)
93 # --------------------------------------------------------------------------
95 os.makedirs(Path(target).parent, exist_ok=True)
96 ffmpeg \
97 .input(source, framerate=framerate) \
98 .output(target, vcodec=codec.ffmpeg_code, format=format_.extension) \
99 .run(overwrite_output=True)