120 lines
3.3 KiB
Python
120 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
from fractions import Fraction
|
|
|
|
|
|
def join_parts(parts: list[str | None]) -> str:
|
|
return ", ".join(part for part in parts if part)
|
|
|
|
|
|
def video_parts(
|
|
*,
|
|
codec: str | None = None,
|
|
width: int | None = None,
|
|
height: int | None = None,
|
|
pixel_format: str | None = None,
|
|
bit_depth: int | None = None,
|
|
matrix: str | None = None,
|
|
bit_rate: int | None = None,
|
|
) -> list[str | None]:
|
|
return [
|
|
codec,
|
|
format_size(width, height),
|
|
format_chroma(pixel_format),
|
|
f"{bit_depth}-bit" if bit_depth is not None else None,
|
|
matrix,
|
|
format_bitrate(bit_rate),
|
|
]
|
|
|
|
|
|
def audio_parts(
|
|
*,
|
|
codec: str | None = None,
|
|
channels: int | None = None,
|
|
sample_rate: int | None = None,
|
|
bit_rate: int | None = None,
|
|
) -> list[str | None]:
|
|
return [
|
|
codec,
|
|
format_channels(channels),
|
|
f"{sample_rate} Hz" if sample_rate is not None else None,
|
|
format_bitrate(bit_rate),
|
|
]
|
|
|
|
|
|
def format_size(width: int | None, height: int | None) -> str | None:
|
|
return f"{width}x{height}" if width is not None and height is not None else None
|
|
|
|
|
|
def format_chroma(pixel_format: str | None) -> str | None:
|
|
value = (pixel_format or "").lower()
|
|
for marker, label in (("420", "4:2:0"), ("422", "4:2:2"), ("444", "4:4:4")):
|
|
if marker in value:
|
|
return label
|
|
if value.startswith(("rgb", "bgr", "gbr")):
|
|
return "RGB"
|
|
if value.startswith("gray"):
|
|
return "gray"
|
|
return pixel_format
|
|
|
|
|
|
def chroma_family(pixel_format: str | None) -> str:
|
|
value = (pixel_format or "").lower()
|
|
if "444" in value:
|
|
return "444"
|
|
if "422" in value:
|
|
return "422"
|
|
return "420"
|
|
|
|
|
|
def format_channels(channels: int | None) -> str | None:
|
|
if channels is None:
|
|
return None
|
|
return f"{channels} channel{'s' if channels != 1 else ''}"
|
|
|
|
|
|
def format_bitrate(bit_rate: int | None) -> str | None:
|
|
if bit_rate is None:
|
|
return None
|
|
if bit_rate >= 1_000_000:
|
|
return f"{bit_rate / 1_000_000:.3g} Mbit/s"
|
|
return f"{bit_rate / 1_000:.3g} kbit/s"
|
|
|
|
|
|
def format_seconds(seconds: float | None) -> str:
|
|
return "unknown" if seconds is None else f"{seconds:.3f}s"
|
|
|
|
|
|
def format_frame_count(stream_count: int | None, timestamp_count: int) -> str:
|
|
if stream_count is not None and stream_count != timestamp_count:
|
|
return f"{stream_count} stream frames, {timestamp_count} timestamped frames"
|
|
count = stream_count if stream_count is not None else timestamp_count
|
|
return f"{count} frames" if count else "unknown frames"
|
|
|
|
|
|
def rate_value(value: str | None) -> float | None:
|
|
if not value:
|
|
return None
|
|
try:
|
|
rate = Fraction(value)
|
|
except (ValueError, ZeroDivisionError):
|
|
return None
|
|
return float(rate) if rate else None
|
|
|
|
|
|
def format_framerate(kind: str, value: str | None) -> str:
|
|
rate = rate_value(value)
|
|
rate_text = "unknown" if rate is None else f"{rate:.3f} fps"
|
|
kind = kind.upper()
|
|
return f"{kind}, average {rate_text}" if kind == "VFR" else f"{kind}, {rate_text}"
|
|
|
|
|
|
def matrix_name(matrix: int | None) -> str:
|
|
if matrix == 1:
|
|
return "Rec709"
|
|
if matrix in {5, 6}:
|
|
return "Rec601"
|
|
if matrix in {9, 10}:
|
|
return "Rec2020"
|
|
return "unknown" if matrix is None else f"unknown ({matrix})"
|