diff --git a/common_py/src/videoag_common/ffmpeg/ffprobe.py b/common_py/src/videoag_common/ffmpeg/ffprobe.py index 2e8a21a117e74258f99f6653e1d4fd051eb8427d..d2b87dc4906245eb6649a41dd5bff7c5ebf4c4c3 100644 --- a/common_py/src/videoag_common/ffmpeg/ffprobe.py +++ b/common_py/src/videoag_common/ffmpeg/ffprobe.py @@ -1,5 +1,6 @@ import json import math +import re import subprocess from dataclasses import dataclass from pathlib import Path @@ -32,6 +33,33 @@ class FFProbeImageStream(FFProbeStream): height: int +def _parse_duration_string_human(val: str) -> int: + match = re.match("(?>(?>([0-9]+):)?([0-9]+):)?([0-9]+)(?>\\.([0-9]+))?", val) + if match is None: + raise ValueError(f"Unable to parse duration: {val}") + hours = int(match.group(1) or "0") + minutes = int(match.group(2) or "0") + seconds = int(match.group(3)) + nanos = int(match.group(4) or "0") + return math.ceil( + (hours * 60 + minutes) * 60 + seconds + + nanos / math.pow(10, 9) + ) + + +def _get_stream_duration_sec(stream: dict): + duration_string = stream.get("duration") + if duration_string is not None: + return math.ceil(float(stream["duration"])) + tags = stream.get("tags") + if tags is not None: + duration_string = tags.get("DURATION") + if duration_string is not None: + return _parse_duration_string_human(duration_string) + + raise Exception(f"Unable to get duration from stream: {stream}") + + class FFProbe: def __init__(self): @@ -76,7 +104,7 @@ class FFProbe: height=int(stream["height"]), frame_rate_numerator=int(frame_rate_num), frame_rate_denominator=int(frame_rate_den), - duration_sec=math.ceil(float(stream["duration"])) + duration_sec=_get_stream_duration_sec(stream), ) self.video_streams.append(probe_stream) elif stream["codec_type"] == "audio": @@ -84,7 +112,7 @@ class FFProbe: codec=stream["codec_name"], sample_rate=int(stream["sample_rate"]), channel_count=int(stream["channels"]), - duration_sec=math.ceil(float(stream["duration"])) + duration_sec=_get_stream_duration_sec(stream), ) self.audio_streams.append(probe_stream) else: