Skip to content

mkvpropedit

MKVPropEdit

Source code in .venv/lib/python3.11/site-packages/muxtools/helpers/propedit.py
class MKVPropEdit:
    _main_args: list[str]
    _track_args: list[str]
    _fileIn: Path
    _parsed: ParsedFile
    _has_info: bool = False
    _video_index: int = 1
    _audio_index: int = 1
    _subtitle_index: int = 1
    _executable: Path

    def __init__(
        self, fileIn: PathLike, track_statistics: bool | None = None, chapters: PathLike | Chapters = None, tags: dict[str, str] | None = None
    ):
        """
        Creates the mkvpropedit helper including any modifications possible on the global scope.

        :param fileIn:              File to edit.
        :param track_statistics:    Whether to update or remove track statistics like bitrate.\n
                                    `None` will do nothing while `True` will add/replace them and `False` will remove them.

        :param chapters:            Chapters to add to the file. This can be any txt or xml file in the same formats that mkvmerge takes.\n
                                    It can also take a muxtools Chapters object and create a txt from that.\n
                                    An empty string will remove any chapters. `None` will do nothing.

        :param tags:                Global tags to add/replace. An empty string as value will remove a tag.\n
                                    An empty dict will remove any global tags. `None` will do nothing.
        """
        self._executable = ensure_path(get_executable("mkvpropedit"), self)
        self._fileIn = ensure_path_exists(fileIn, self)
        self._parsed = ParsedFile.from_file(self._fileIn, self)
        self._main_args = []
        self._track_args = []

        if track_statistics is not None:
            self._main_args.append("--add-track-statistics-tags" if track_statistics else "--delete-track-statistics-tags")

        if chapters is not None:
            if isinstance(chapters, str) and not chapters:
                self._main_args.extend(["-c", ""])
            else:
                if isinstance(chapters, Chapters):
                    chapters = chapters.to_file()
                chapters = ensure_path_exists(chapters, self)
                if chapters.suffix.lower() not in [".txt", ".xml"]:
                    raise error("Chapters have to be a txt or xml file.", self)
                self._main_args.extend(["-c", str(chapters)])
        if tags is not None:
            out = ""
            if len(tags) > 0:
                if self._parsed.container_info.tags:
                    tags = dict(**self._parsed.container_info.tags) | tags
                out = make_output(fileIn, "xml", "global_tags", temp=True)
                create_tags_xml(out, tags)
            self._main_args.extend(["-t", f"global:{str(out)}"])

    def _edit_args(self, name: str, value: bool | str | None) -> list[str]:
        if value is None:
            return []

        if isinstance(value, bool):
            return ["-s", f"{name}={str(int(value))}"]
        else:
            if not value:
                return ["-d", name]
            else:
                return ["-s", f"{name}={value}"]

    def _edit_track(
        self,
        type: str,
        index: int,
        title: str | None,
        language: str | None,
        default: bool | None,
        forced: bool | None,
        tags: dict[str, str] | None,
        **kwargs: bool | str | None,
    ):
        selector = type if index <= 0 else f"track:{type}{index}"
        if tags is not None and type != "info":
            out = ""
            if len(tags) > 0:
                match type:
                    case "v":
                        tracktype = TrackType.VIDEO
                    case "a":
                        tracktype = TrackType.AUDIO
                    case _:
                        tracktype = TrackType.SUB
                track = self._parsed.find_tracks(type=tracktype, relative_id=index - 1, error_if_empty=True, caller=self)[0]
                if track.other_tags:
                    tags = dict(**track.other_tags) | tags
                out = make_output(self._fileIn, "xml", f"{type}{index}_tags", temp=True)
                create_tags_xml(out, tags)
            self._main_args.extend(["-t", f"{selector}:{str(out)}"])
        if not any([not_none for not_none in (title, language, default, forced) if not_none is not None]) and not kwargs:
            return

        args = ["-e", selector]
        to_append = [
            *self._edit_args("title" if type == "info" else "name", title),
            *self._edit_args("language", language),
            *self._edit_args("flag-default", default),
            *self._edit_args("flag-forced", forced),
        ]
        args.extend(to_append)

        for k, v in kwargs.items():
            if not k.endswith("_"):
                k = k.replace("_", "-")
            else:
                k = k[:-1]
            if e_args := self._edit_args(k, v):
                args.extend(e_args)

        self._track_args.extend(args)

    def info(
        self,
        title: str | None = None,
        date: str | None = None,
        muxing_application: str | None = None,
        writing_application: str | None = None,
        **kwargs: bool | str | None,
    ):
        """
        Edit properties for the main info section.

        `None` always means the property will be left untouched while an empty string will remove the property outright.\n
        Bool values are converted to their respective integer to be passed on to mkvpropedit.

        :param title:               The title for the whole movie
        :param date:                The date the file was created
        :param muxing_application:  The name of the application or library used for multiplexing the file
        :param writing_application: The name of the application or library used for writing the file
        :param kwargs:              Any other properties to set or remove.\n
                                    Check out the 'Segment information' section in `mkvpropedit -l` to see what's available.
        """
        if self._has_info:
            raise error("Info tagging was already added!", self)
        self._edit_track("info", -1, title, date=date, muxing_application=muxing_application, writing_application=writing_application, **kwargs)
        self._has_info = True
        return self

    def video_track(
        self,
        name: str | None = None,
        language: str | None = None,
        default: bool | None = None,
        forced: bool | None = None,
        tags: dict[str, str] | None = None,
        crop: int | tuple[int, int] | tuple[int, int, int, int] | None = None,
        **kwargs: bool | str | None,
    ) -> Self:
        """
        Edit properties for the next video track in the file.

        `None` always means the property will be left untouched while an empty string will remove the property outright.\n
        Bool values are converted to their respective integer to be passed on to mkvpropedit.

        :param name:                A human-readable track name
        :param language:            Specifies the language of the track
        :param default:             Specifies whether a track should be eligible for automatic selection
        :param forced:              Specifies whether a track should be played with tracks of a different type but same language

        :param tags:                Any custom/arbitrary tags to set for the track. An empty string as value will remove a tag\n
                                    An empty dict will remove any custom tags. `None` will do nothing.

        :param crop:                Container based cropping with (horizontal, vertical) or (left, top, right, bottom).\n
                                    Will crop the same on all sides if passed a single integer.

        :param kwargs:              Any other properties to set or remove.\n
                                    Check out the 'Track headers' section in `mkvpropedit -l` to see what's available.
        """
        if crop is not None:
            if isinstance(crop, int):
                crop = tuple([crop] * 4)
            elif len(crop) == 2:
                crop = crop * 2
            kwargs.update(pixel_crop_left=str(crop[0]), pixel_crop_top=str(crop[1]), pixel_crop_right=str(crop[2]), pixel_crop_bottom=str(crop[3]))
        self._edit_track("v", self._video_index, name, language, default, forced, tags, **kwargs)
        self._video_index += 1
        return self

    def audio_track(
        self,
        name: str | None = None,
        language: str | None = None,
        default: bool | None = None,
        forced: bool | None = None,
        tags: dict[str, str] | None = None,
        **kwargs: bool | str | None,
    ) -> Self:
        """
        Edit properties for the next audio track in the file.

        `None` always means the property will be left untouched while an empty string will remove the property outright.\n
        Bool values are converted to their respective integer to be passed on to mkvpropedit.

        :param name:                A human-readable track name
        :param language:            Specifies the language of the track
        :param default:             Specifies whether a track should be eligible for automatic selection
        :param forced:              Specifies whether a track should be played with tracks of a different type but same language

        :param tags:                Any custom/arbitrary tags to set for the track. An empty string as value will remove a tag.\n
                                    An empty dict will remove any custom tags. `None` will do nothing.

        :param kwargs:              Any other properties to set or remove.\n
                                    Check out the 'Track headers' section in `mkvpropedit -l` to see what's available.
        """
        self._edit_track("a", self._audio_index, name, language, default, forced, tags, **kwargs)
        self._audio_index += 1
        return self

    def sub_track(
        self,
        name: str | None = None,
        language: str | None = None,
        default: bool | None = None,
        forced: bool | None = None,
        tags: dict[str, str] | None = None,
        **kwargs: bool | str | None,
    ) -> Self:
        """
        Edit properties for the next subtitle track in the file.

        `None` always means the property will be left untouched while an empty string will remove the property outright.\n
        Bool values are converted to their respective integer to be passed on to mkvpropedit.

        :param name:                A human-readable track name
        :param language:            Specifies the language of the track
        :param default:             Specifies whether a track should be eligible for automatic selection
        :param forced:              Specifies whether a track should be played with tracks of a different type but same language

        :param tags:                Any custom/arbitrary tags to set for the track. An empty string as value will remove a tag.\n
                                    An empty dict will remove any custom tags. `None` will do nothing.

        :param kwargs:              Any other properties to set or remove.\n
                                    Check out the 'Track headers' section in `mkvpropedit -l` to see what's available.
        """
        self._edit_track("s", self._subtitle_index, name, language, default, forced, tags, **kwargs)
        self._subtitle_index += 1
        return self

    def run(self, quiet: bool = True, error_on_failure: bool = True) -> bool:
        """
        Run the mkvpropedit process.

        :param quiet:               Suppresses the output.\n
                                    The stdout will still be printed on failure regardless of this setting.

        :param error_on_failure:    Raise an exception on failure.\n
                                    Otherwise this function will return a bool indicating the success.
        """
        if not self._main_args and not self._track_args:
            raise error("No changes made to the file!", self)
        args = [str(self._executable), str(self._fileIn)] + self._main_args + self._track_args
        code = run_commandline(args, quiet, mkvmerge=True)
        clean_temp_files()
        if code > 1 and error_on_failure:
            raise error(f"Failed to edit properties for '{self._fileIn.name}'!")

        return code < 2

__init__(fileIn, track_statistics=None, chapters=None, tags=None)

Creates the mkvpropedit helper including any modifications possible on the global scope.

Parameters:

Name Type Description Default
fileIn PathLike

File to edit.

required
track_statistics bool | None

Whether to update or remove track statistics like bitrate. None will do nothing while True will add/replace them and False will remove them.

None
chapters PathLike | Chapters

Chapters to add to the file. This can be any txt or xml file in the same formats that mkvmerge takes. It can also take a muxtools Chapters object and create a txt from that. An empty string will remove any chapters. None will do nothing.

None
tags dict[str, str] | None

Global tags to add/replace. An empty string as value will remove a tag. An empty dict will remove any global tags. None will do nothing.

None
Source code in .venv/lib/python3.11/site-packages/muxtools/helpers/propedit.py
def __init__(
    self, fileIn: PathLike, track_statistics: bool | None = None, chapters: PathLike | Chapters = None, tags: dict[str, str] | None = None
):
    """
    Creates the mkvpropedit helper including any modifications possible on the global scope.

    :param fileIn:              File to edit.
    :param track_statistics:    Whether to update or remove track statistics like bitrate.\n
                                `None` will do nothing while `True` will add/replace them and `False` will remove them.

    :param chapters:            Chapters to add to the file. This can be any txt or xml file in the same formats that mkvmerge takes.\n
                                It can also take a muxtools Chapters object and create a txt from that.\n
                                An empty string will remove any chapters. `None` will do nothing.

    :param tags:                Global tags to add/replace. An empty string as value will remove a tag.\n
                                An empty dict will remove any global tags. `None` will do nothing.
    """
    self._executable = ensure_path(get_executable("mkvpropedit"), self)
    self._fileIn = ensure_path_exists(fileIn, self)
    self._parsed = ParsedFile.from_file(self._fileIn, self)
    self._main_args = []
    self._track_args = []

    if track_statistics is not None:
        self._main_args.append("--add-track-statistics-tags" if track_statistics else "--delete-track-statistics-tags")

    if chapters is not None:
        if isinstance(chapters, str) and not chapters:
            self._main_args.extend(["-c", ""])
        else:
            if isinstance(chapters, Chapters):
                chapters = chapters.to_file()
            chapters = ensure_path_exists(chapters, self)
            if chapters.suffix.lower() not in [".txt", ".xml"]:
                raise error("Chapters have to be a txt or xml file.", self)
            self._main_args.extend(["-c", str(chapters)])
    if tags is not None:
        out = ""
        if len(tags) > 0:
            if self._parsed.container_info.tags:
                tags = dict(**self._parsed.container_info.tags) | tags
            out = make_output(fileIn, "xml", "global_tags", temp=True)
            create_tags_xml(out, tags)
        self._main_args.extend(["-t", f"global:{str(out)}"])

info(title=None, date=None, muxing_application=None, writing_application=None, **kwargs)

Edit properties for the main info section.

None always means the property will be left untouched while an empty string will remove the property outright.

Bool values are converted to their respective integer to be passed on to mkvpropedit.

Parameters:

Name Type Description Default
title str | None

The title for the whole movie

None
date str | None

The date the file was created

None
muxing_application str | None

The name of the application or library used for multiplexing the file

None
writing_application str | None

The name of the application or library used for writing the file

None
kwargs bool | str | None

Any other properties to set or remove. Check out the 'Segment information' section in mkvpropedit -l to see what's available.

{}
Source code in .venv/lib/python3.11/site-packages/muxtools/helpers/propedit.py
def info(
    self,
    title: str | None = None,
    date: str | None = None,
    muxing_application: str | None = None,
    writing_application: str | None = None,
    **kwargs: bool | str | None,
):
    """
    Edit properties for the main info section.

    `None` always means the property will be left untouched while an empty string will remove the property outright.\n
    Bool values are converted to their respective integer to be passed on to mkvpropedit.

    :param title:               The title for the whole movie
    :param date:                The date the file was created
    :param muxing_application:  The name of the application or library used for multiplexing the file
    :param writing_application: The name of the application or library used for writing the file
    :param kwargs:              Any other properties to set or remove.\n
                                Check out the 'Segment information' section in `mkvpropedit -l` to see what's available.
    """
    if self._has_info:
        raise error("Info tagging was already added!", self)
    self._edit_track("info", -1, title, date=date, muxing_application=muxing_application, writing_application=writing_application, **kwargs)
    self._has_info = True
    return self

video_track(name=None, language=None, default=None, forced=None, tags=None, crop=None, **kwargs)

Edit properties for the next video track in the file.

None always means the property will be left untouched while an empty string will remove the property outright.

Bool values are converted to their respective integer to be passed on to mkvpropedit.

Parameters:

Name Type Description Default
name str | None

A human-readable track name

None
language str | None

Specifies the language of the track

None
default bool | None

Specifies whether a track should be eligible for automatic selection

None
forced bool | None

Specifies whether a track should be played with tracks of a different type but same language

None
tags dict[str, str] | None

Any custom/arbitrary tags to set for the track. An empty string as value will remove a tag An empty dict will remove any custom tags. None will do nothing.

None
crop int | tuple[int, int] | tuple[int, int, int, int] | None

Container based cropping with (horizontal, vertical) or (left, top, right, bottom). Will crop the same on all sides if passed a single integer.

None
kwargs bool | str | None

Any other properties to set or remove. Check out the 'Track headers' section in mkvpropedit -l to see what's available.

{}
Source code in .venv/lib/python3.11/site-packages/muxtools/helpers/propedit.py
def video_track(
    self,
    name: str | None = None,
    language: str | None = None,
    default: bool | None = None,
    forced: bool | None = None,
    tags: dict[str, str] | None = None,
    crop: int | tuple[int, int] | tuple[int, int, int, int] | None = None,
    **kwargs: bool | str | None,
) -> Self:
    """
    Edit properties for the next video track in the file.

    `None` always means the property will be left untouched while an empty string will remove the property outright.\n
    Bool values are converted to their respective integer to be passed on to mkvpropedit.

    :param name:                A human-readable track name
    :param language:            Specifies the language of the track
    :param default:             Specifies whether a track should be eligible for automatic selection
    :param forced:              Specifies whether a track should be played with tracks of a different type but same language

    :param tags:                Any custom/arbitrary tags to set for the track. An empty string as value will remove a tag\n
                                An empty dict will remove any custom tags. `None` will do nothing.

    :param crop:                Container based cropping with (horizontal, vertical) or (left, top, right, bottom).\n
                                Will crop the same on all sides if passed a single integer.

    :param kwargs:              Any other properties to set or remove.\n
                                Check out the 'Track headers' section in `mkvpropedit -l` to see what's available.
    """
    if crop is not None:
        if isinstance(crop, int):
            crop = tuple([crop] * 4)
        elif len(crop) == 2:
            crop = crop * 2
        kwargs.update(pixel_crop_left=str(crop[0]), pixel_crop_top=str(crop[1]), pixel_crop_right=str(crop[2]), pixel_crop_bottom=str(crop[3]))
    self._edit_track("v", self._video_index, name, language, default, forced, tags, **kwargs)
    self._video_index += 1
    return self

audio_track(name=None, language=None, default=None, forced=None, tags=None, **kwargs)

Edit properties for the next audio track in the file.

None always means the property will be left untouched while an empty string will remove the property outright.

Bool values are converted to their respective integer to be passed on to mkvpropedit.

Parameters:

Name Type Description Default
name str | None

A human-readable track name

None
language str | None

Specifies the language of the track

None
default bool | None

Specifies whether a track should be eligible for automatic selection

None
forced bool | None

Specifies whether a track should be played with tracks of a different type but same language

None
tags dict[str, str] | None

Any custom/arbitrary tags to set for the track. An empty string as value will remove a tag. An empty dict will remove any custom tags. None will do nothing.

None
kwargs bool | str | None

Any other properties to set or remove. Check out the 'Track headers' section in mkvpropedit -l to see what's available.

{}
Source code in .venv/lib/python3.11/site-packages/muxtools/helpers/propedit.py
def audio_track(
    self,
    name: str | None = None,
    language: str | None = None,
    default: bool | None = None,
    forced: bool | None = None,
    tags: dict[str, str] | None = None,
    **kwargs: bool | str | None,
) -> Self:
    """
    Edit properties for the next audio track in the file.

    `None` always means the property will be left untouched while an empty string will remove the property outright.\n
    Bool values are converted to their respective integer to be passed on to mkvpropedit.

    :param name:                A human-readable track name
    :param language:            Specifies the language of the track
    :param default:             Specifies whether a track should be eligible for automatic selection
    :param forced:              Specifies whether a track should be played with tracks of a different type but same language

    :param tags:                Any custom/arbitrary tags to set for the track. An empty string as value will remove a tag.\n
                                An empty dict will remove any custom tags. `None` will do nothing.

    :param kwargs:              Any other properties to set or remove.\n
                                Check out the 'Track headers' section in `mkvpropedit -l` to see what's available.
    """
    self._edit_track("a", self._audio_index, name, language, default, forced, tags, **kwargs)
    self._audio_index += 1
    return self

sub_track(name=None, language=None, default=None, forced=None, tags=None, **kwargs)

Edit properties for the next subtitle track in the file.

None always means the property will be left untouched while an empty string will remove the property outright.

Bool values are converted to their respective integer to be passed on to mkvpropedit.

Parameters:

Name Type Description Default
name str | None

A human-readable track name

None
language str | None

Specifies the language of the track

None
default bool | None

Specifies whether a track should be eligible for automatic selection

None
forced bool | None

Specifies whether a track should be played with tracks of a different type but same language

None
tags dict[str, str] | None

Any custom/arbitrary tags to set for the track. An empty string as value will remove a tag. An empty dict will remove any custom tags. None will do nothing.

None
kwargs bool | str | None

Any other properties to set or remove. Check out the 'Track headers' section in mkvpropedit -l to see what's available.

{}
Source code in .venv/lib/python3.11/site-packages/muxtools/helpers/propedit.py
def sub_track(
    self,
    name: str | None = None,
    language: str | None = None,
    default: bool | None = None,
    forced: bool | None = None,
    tags: dict[str, str] | None = None,
    **kwargs: bool | str | None,
) -> Self:
    """
    Edit properties for the next subtitle track in the file.

    `None` always means the property will be left untouched while an empty string will remove the property outright.\n
    Bool values are converted to their respective integer to be passed on to mkvpropedit.

    :param name:                A human-readable track name
    :param language:            Specifies the language of the track
    :param default:             Specifies whether a track should be eligible for automatic selection
    :param forced:              Specifies whether a track should be played with tracks of a different type but same language

    :param tags:                Any custom/arbitrary tags to set for the track. An empty string as value will remove a tag.\n
                                An empty dict will remove any custom tags. `None` will do nothing.

    :param kwargs:              Any other properties to set or remove.\n
                                Check out the 'Track headers' section in `mkvpropedit -l` to see what's available.
    """
    self._edit_track("s", self._subtitle_index, name, language, default, forced, tags, **kwargs)
    self._subtitle_index += 1
    return self

run(quiet=True, error_on_failure=True)

Run the mkvpropedit process.

Parameters:

Name Type Description Default
quiet bool

Suppresses the output. The stdout will still be printed on failure regardless of this setting.

True
error_on_failure bool

Raise an exception on failure. Otherwise this function will return a bool indicating the success.

True
Source code in .venv/lib/python3.11/site-packages/muxtools/helpers/propedit.py
def run(self, quiet: bool = True, error_on_failure: bool = True) -> bool:
    """
    Run the mkvpropedit process.

    :param quiet:               Suppresses the output.\n
                                The stdout will still be printed on failure regardless of this setting.

    :param error_on_failure:    Raise an exception on failure.\n
                                Otherwise this function will return a bool indicating the success.
    """
    if not self._main_args and not self._track_args:
        raise error("No changes made to the file!", self)
    args = [str(self._executable), str(self._fileIn)] + self._main_args + self._track_args
    code = run_commandline(args, quiet, mkvmerge=True)
    clean_temp_files()
    if code > 1 and error_on_failure:
        raise error(f"Failed to edit properties for '{self._fileIn.name}'!")

    return code < 2