Skip to content

muxtools.utils.parsing

parse_ogm

parse_ogm(file: Path) -> list[Chapter]

Parses chapters from an OGM file

Parameters:

Name Type Description Default
file Path

Input file

required

Returns:

Type Description
list[Chapter]

List of parsed chapters

Source code in muxtools/utils/parsing.py
def parse_ogm(file: Path) -> list[Chapter]:
    """
    Parses chapters from an OGM file

    :param file:    Input file

    :return:        List of parsed chapters
    """
    return _parse_chapters(file, OGM_REGEX, re.I | re.M)

parse_xml

parse_xml(file: Path) -> list[Chapter]

Parses chapters from an XML file

Parameters:

Name Type Description Default
file Path

Input file

required

Returns:

Type Description
list[Chapter]

List of parsed chapters

Source code in muxtools/utils/parsing.py
def parse_xml(file: Path) -> list[Chapter]:
    """
    Parses chapters from an XML file

    :param file:    Input file

    :return:        List of parsed chapters
    """
    return _parse_chapters(file, XML_REGEX, re.I | re.M | re.S)

parse_m2ts_path

parse_m2ts_path(dgiFile: Path) -> Path

Parses actual source location from a dgi file.

Parameters:

Name Type Description Default
dgiFile Path

Input file

required

Returns:

Type Description
Path

Path of source file

Source code in muxtools/utils/parsing.py
def parse_m2ts_path(dgiFile: Path) -> Path:
    """
    Parses actual source location from a dgi file.

    :param dgiFile: Input file

    :return:        Path of source file
    """
    with open(dgiFile, "r") as fp:
        for i, line in enumerate(fp):
            for match in re.finditer(re.compile(r"^(.*\.m2ts) \d+$", re.IGNORECASE), line):
                m2tsFile = Path(match.group(1))
                if m2tsFile.exists():
                    return m2tsFile
    print("Warning!\nCould not resolve origin file path from the dgindex input!")
    return dgiFile

parse_chapters_bdmv

parse_chapters_bdmv(src: PathLike, clip_fps: Fraction | PathLike = Fraction(24000, 1001), clip_frames: int = -1, _print: bool = False) -> list[Chapter]

Attempts to parse chapters from the bluray metadata

Parameters:

Name Type Description Default
src PathLike

The m2ts file you're currently using

required
clip_fps Fraction | PathLike

The fps of the clip. Also accepts a timestamps (v1/v2/v4) file.

Fraction(24000, 1001)
clip_frames int

Total frames of the clip

-1
_print bool

Prints the chapters after parsing if true

False

Returns:

Type Description
list[Chapter]

List of parsed chapters

Source code in muxtools/utils/parsing.py
def parse_chapters_bdmv(
    src: PathLike,
    clip_fps: Fraction | PathLike = Fraction(24000, 1001),
    clip_frames: int = -1,
    _print: bool = False,
) -> list[Chapter]:
    """
    Attempts to parse chapters from the bluray metadata

    :param src:         The m2ts file you're currently using
    :param clip_fps:    The fps of the clip. Also accepts a timestamps (v1/v2/v4) file.
    :param clip_frames: Total frames of the clip
    :param _print:      Prints the chapters after parsing if true

    :return:            List of parsed chapters
    """
    src = ensure_path_exists(src, parse_chapters_bdmv)

    stream_dir = src.parent
    if stream_dir.name.lower() != "stream":
        raise error("Your source file is not in a default bdmv structure!", parse_chapters_bdmv)
    playlist_dir = Path(os.path.join(stream_dir.parent, "PLAYLIST"))
    if not playlist_dir.exists():
        raise error("PLAYLIST folder couldn't have been found!", parse_chapters_bdmv)

    chapters: list[Chapter] = []
    for f in playlist_dir.rglob("*"):
        if not os.path.isfile(f) or f.suffix.lower() != ".mpls":
            continue
        with f.open("rb") as file:
            header = mpls.load_movie_playlist(file)
            file.seek(header.playlist_start_address, os.SEEK_SET)
            playlist = mpls.load_playlist(file)
            if not playlist.play_items:
                continue

            file.seek(header.playlist_mark_start_address, os.SEEK_SET)
            playlist_mark = mpls.load_playlist_mark(file)
            if (plsmarks := playlist_mark.playlist_marks) is not None:
                marks = plsmarks
            else:
                raise error("There are no playlist marks in this file!", parse_chapters_bdmv)

        for i, playitem in enumerate(playlist.play_items):
            if not playitem.clip_information_filename or not playitem.clip_codec_identifier:
                continue
            if playitem.clip_information_filename == src.stem and playitem.clip_codec_identifier.lower() == src.suffix.lower().split(".")[1]:
                if _print:
                    info(f'Found chapters for "{src.name}" in "{f.name}":')
                linked_marks = [mark for mark in marks if mark.ref_to_play_item_id == i]
                try:
                    assert playitem.intime
                    offset = min(playitem.intime, linked_marks[0].mark_timestamp)
                except IndexError:
                    continue
                if (
                    playitem.stn_table
                    and playitem.stn_table.length != 0
                    and playitem.stn_table.prim_video_stream_entries
                    and (fps_n := playitem.stn_table.prim_video_stream_entries[0][1].framerate)
                ):
                    try:
                        fps = mpls.FRAMERATE[fps_n]
                    except:
                        warn("Couldn't parse fps from playlist! Will take fps from source clip.", parse_chapters_bdmv)
                        fps = clip_fps

                    m2ts_ts = resolve_timesource_and_scale(fps, TimeScale.M2TS, RoundingMethod.FLOOR, allow_warn=False, caller=parse_chapters_bdmv)
                    mkv_ts = resolve_timesource_and_scale(fps, TimeScale.MKV, allow_warn=False, caller=parse_chapters_bdmv)

                    for i, lmark in enumerate(linked_marks, start=1):
                        parsed_frame = m2ts_ts.pts_to_frame(lmark.mark_timestamp - offset, TimeType.START, Fraction(45000))
                        time_ms = mkv_ts.frame_to_time(parsed_frame, TimeType.START, 3)

                        time = timedelta(milliseconds=time_ms)
                        time_cutoff = mkv_ts.frame_to_time(clip_frames - 50, TimeType.EXACT, 3, False)
                        if clip_frames > 0 and time > timedelta(milliseconds=time_cutoff):
                            continue
                        chapters.append((time, f"Chapter {i:02.0f}"))
                    if chapters and _print:
                        for time, name in chapters:
                            frame = mkv_ts.time_to_frame(int(time.total_seconds() * 1000), TimeType.EXACT, 3)
                            print(f"{name}: {format_timedelta(time)} | {frame}")

        if chapters:
            break

    return chapters