Skip to content

convert

format_timedelta(time, precision=3)

Formats a timedelta to hh:mm:ss.s[*precision] and pads with 0 if there aren't more numbers to work with. Mostly to be used for ogm/xml files.

Parameters:

Name Type Description Default
time timedelta

The timedelta

required
precision int

3 = milliseconds, 6 = microseconds, 9 = nanoseconds

3

Returns:

Type Description
str

The formatted string

Source code in muxtools/utils/convert.py
def format_timedelta(time: timedelta, precision: int = 3) -> str:
    """
    Formats a timedelta to hh:mm:ss.s[*precision] and pads with 0 if there aren't more numbers to work with.
    Mostly to be used for ogm/xml files.

    :param time:        The timedelta
    :param precision:   3 = milliseconds, 6 = microseconds, 9 = nanoseconds

    :return:            The formatted string
    """
    dec = Decimal(time.total_seconds())
    pattern = "." + "".join(["0"] * (precision - 1)) + "1"
    rounded = float(dec.quantize(Decimal(pattern), rounding=ROUND_HALF_DOWN))
    s = trunc(rounded)
    m = s // 60
    s %= 60
    h = m // 60
    m %= 60
    return f"{h:02d}:{m:02d}:{s:02d}.{str(rounded).split('.')[1].ljust(precision, '0')}"

get_timemeta_from_video(video_file, out_file=None, caller=None)

Parse timestamps from an existing video file using ffprobe.

They're saved as a custom meta file in the current workdir and named based on the input.

Also automatically reused (with a debug log) if already exists.

Parameters:

Name Type Description Default
video_file PathLike

Input video. Path or String.

required
out_file PathLike | None

Output file. If None given, the above behavior applies.

None
caller Any | None

Caller used for the logging

None

Returns:

Type Description
VideoMeta

Videometa object

Source code in muxtools/utils/convert.py
def get_timemeta_from_video(video_file: PathLike, out_file: PathLike | None = None, caller: Any | None = None) -> VideoMeta:
    """
    Parse timestamps from an existing video file using ffprobe.\n
    They're saved as a custom meta file in the current workdir and named based on the input.

    Also automatically reused (with a debug log) if already exists.

    :param video_file:      Input video. Path or String.
    :param out_file:        Output file. If None given, the above behavior applies.
    :param caller:          Caller used for the logging

    :return:                Videometa object
    """
    video_file = ensure_path_exists(video_file, get_timemeta_from_video)
    assert get_executable("ffprobe")
    if not out_file:
        out_file = get_workdir() / f"{video_file.stem}_meta.json"

    out_file = ensure_path(out_file, get_timemeta_from_video)
    if not out_file.exists() or out_file.stat().st_size < 1:
        info(f"Generating timestamps for '{video_file.name}'...", caller)
        timestamps = VideoTimestamps.from_video_file(video_file)
        meta = VideoMeta(timestamps.pts_list, timestamps.fps, timestamps.time_scale, str(video_file.resolve()))
        with open(out_file, "w", encoding="utf-8") as f:
            f.write(meta.to_json())
    else:
        meta = VideoMeta.from_json(out_file)
        debug(f"Reusing existing timestamps for '{video_file.name}'", caller)
    return meta

resolve_timesource_and_scale(timesource=None, timescale=None, rounding_method=RoundingMethod.ROUND, allow_warn=True, fetch_from_setup=False, caller=None)

Instantiates a timestamps class from various inputs.

Parameters:

Name Type Description Default
timesource PathLike | Fraction | float | list[int] | VideoMeta | ABCTimestamps | None

The source of timestamps/timecodes. For actual timestamps, this can be a timestamps (v1/v2/v4) file, a muxtools VideoMeta json file, a video file or a list of integers. For FPS based timestamps, this can be a Fraction object, a float or even a string representing a fraction. Like '24000/1001'. (None will also fallback to this and print a warning)

None
timescale TimeScale | Fraction | int | None

Unit of time (in seconds) in terms of which frame timestamps are represented. While you can pass an int, the needed type is always a Fraction and will be converted via Fraction(your_int). If None falls back to a generic Matroska timescale.

None
rounding_method RoundingMethod

The rounding method used to round/floor the PTS (Presentation Time Stamp).

ROUND
allow_warn bool

Allow this function to print warnings. If you know what you're doing feel free to disable this for your own use.

True
fetch_from_setup bool

Whether or not this function should fallback to the sub defaults from the current Setup.

False
caller Any | None

Caller used for the logging

None

Returns:

Type Description
ABCTimestamps

Instantiated timestamps object from the videotimestamps library

Source code in muxtools/utils/convert.py
def resolve_timesource_and_scale(
    timesource: PathLike | Fraction | float | list[int] | VideoMeta | ABCTimestamps | None = None,
    timescale: TimeScale | Fraction | int | None = None,
    rounding_method: RoundingMethod = RoundingMethod.ROUND,
    allow_warn: bool = True,
    fetch_from_setup: bool = False,
    caller: Any | None = None,
) -> ABCTimestamps:
    """
    Instantiates a timestamps class from various inputs.

    :param timesource:          The source of timestamps/timecodes.\n
                                For actual timestamps, this can be a timestamps (v1/v2/v4) file, a muxtools VideoMeta json file, a video file or a list of integers.\n
                                For FPS based timestamps, this can be a Fraction object, a float or even a string representing a fraction.\n
                                Like `'24000/1001'`. (`None` will also fallback to this and print a warning)

    :param timescale:           Unit of time (in seconds) in terms of which frame timestamps are represented.\n
                                While you can pass an int, the needed type is always a Fraction and will be converted via `Fraction(your_int)`.\n
                                If `None` falls back to a generic Matroska timescale.

    :param rounding_method:     The rounding method used to round/floor the PTS (Presentation Time Stamp).
    :param allow_warn:          Allow this function to print warnings. If you know what you're doing feel free to disable this for your own use.
    :param fetch_from_setup:    Whether or not this function should fallback to the sub defaults from the current Setup.
    :param caller:              Caller used for the logging

    :return:                    Instantiated timestamps object from the videotimestamps library
    """
    if fetch_from_setup:
        if timesource is None and (setup_timesource := get_setup_attr("sub_timesource", None)) is not None:
            if not isinstance(setup_timesource, TimeSourceT):
                raise error("Invalid timesource type in Setup!", caller)
            debug("Using default timesource from setup.", caller)
            timesource = setup_timesource
        if timescale is None and (setup_timescale := get_setup_attr("sub_timescale", None)) is not None:
            if not isinstance(setup_timescale, TimeScaleT):
                raise error("Invalid timescale type in Setup!", caller)
            debug("Using default timescale from setup.", caller)
            timescale = setup_timescale

    def check_timescale(timescale) -> Fraction:
        if not timescale:
            if allow_warn:
                warn("No timescale was given, defaulting to Matroska scaling.", caller)
            timescale = Fraction(1000)
        return Fraction(timescale)

    if timesource is None:
        if allow_warn:
            warn("No timesource was given, generating timestamps for FPS (24000/1001).", caller)
        timescale = check_timescale(timescale)
        return FPSTimestamps(rounding_method, timescale, Fraction(24000, 1001))

    if isinstance(timesource, VideoMeta):
        return VideoTimestamps(timesource.pts, timesource.timescale, fps=timesource.fps, rounding_method=rounding_method)

    if isinstance(timesource, ABCTimestamps):
        return timesource

    if isinstance(timesource, PathLike):
        if isinstance(timesource, Path) or os.path.isfile(timesource):
            timesource = ensure_path(timesource, caller)
            is_video = is_video_file(timesource)

            if is_video:
                meta = get_timemeta_from_video(timesource, caller=caller)
                return VideoTimestamps(meta.pts, meta.timescale, fps=meta.fps, rounding_method=rounding_method)
            else:
                try:
                    return VideoMeta.from_json(timesource)
                except:
                    timescale = check_timescale(timescale)
                    return TextFileTimestamps(timesource, timescale, rounding_method=rounding_method)

    elif isinstance(timesource, list) and isinstance(timesource[0], int):
        timescale = check_timescale(timescale)
        return VideoTimestamps(timesource, timescale, rounding_method=rounding_method)

    if isinstance(timesource, float) or isinstance(timesource, str) or isinstance(timesource, Fraction):
        fps = Fraction(timesource)
        timescale = check_timescale(timescale)
        return FPSTimestamps(rounding_method, timescale, fps)

    raise crit("Invalid timesource passed!", caller)

timedelta_from_formatted(formatted)

Parses a string with the format of hh:mm:ss.sss Mostly to be used for ogm/xml files.

Parameters:

Name Type Description Default
formatted str

The timestamp string

required

Returns:

Type Description
timedelta

The parsed timedelta

Source code in muxtools/utils/convert.py
def timedelta_from_formatted(formatted: str) -> timedelta:
    """
    Parses a string with the format of hh:mm:ss.sss
    Mostly to be used for ogm/xml files.

    :param formatted:       The timestamp string

    :return:                The parsed timedelta
    """
    # 00:05:25.534...
    split = formatted.split(":")
    seconds = Decimal(split[0]) * Decimal(3600)
    seconds = seconds + (Decimal(split[1]) * Decimal(60))
    seconds = seconds + (Decimal(split[2]))
    return timedelta(seconds=seconds.__float__())