Skip to content

muxtools.muxing.tracks

VT module-attribute

VT = VideoTrack

AT module-attribute

AT = AudioTrack

ST module-attribute

ST = SubTrack

MkvTrack module-attribute

MkvTrack = Premux

_track

_track(file: PathLike, type: str | int | TrackType, name: str = '', lang: str = '', default: bool = True, forced: bool = False, delay: int = 0, args: list[str] | None = None, tags: dict[str, str] | None = None, **kwargs: Any)

Parameters:

Name Type Description Default
file PathLike

Filepath as string or Path object

required
type str | int | TrackType

TrackType enum, or int or string (1 = 'video', 2 = 'audio', 3 = 'sub')

required
name str

The track name in the resulting mkv file

''
lang str

The language tag for the track

''
default bool

Default flag

True
forced bool

Forced flag

False
delay int

Container delay of track in ms

0
args list[str] | None

Custom parameters as a list of strings to be applied to the track.

None
tags dict[str, str] | None

Custom tags to be applied to the track.

None
kwargs Any

Any other custom parameters to be added to the track.

This follows the same patterns as the dynamic dataclasses.

{}
Source code in muxtools/muxing/tracks.py
def __init__(
    self,
    file: PathLike,
    type: str | int | TrackType,
    name: str = "",
    lang: str = "",
    default: bool = True,
    forced: bool = False,
    delay: int = 0,
    args: list[str] | None = None,
    tags: dict[str, str] | None = None,
    **kwargs: Any,
) -> None:
    """
    :param file:        Filepath as string or Path object
    :param type:        TrackType enum, or int or string (1 = 'video', 2 = 'audio', 3 = 'sub')
    :param name:        The track name in the resulting mkv file
    :param lang:        The language tag for the track
    :param default:     Default flag
    :param forced:      Forced flag
    :param delay:       Container delay of track in ms
    :param args:        Custom parameters as a list of strings to be applied to the track.
    :param tags:        Custom tags to be applied to the track.
    :param kwargs:      Any other custom parameters to be added to the track.\n
                        This follows the same patterns as the [dynamic dataclasses](https://muxtools.vodes.pw/guide/dynamic-dataclasses/).
    """
    self.file = ensure_path_exists(file, self)
    self.default = default
    self.forced = forced
    self.name = name
    self.delay = file.container_delay if isinstance(file, FileMixin) else delay
    self.lang = lang
    self.type = type if isinstance(type, TrackType) else (TrackType(type) if isinstance(type, int) else TrackType[type.upper()])
    self.args = args or []
    self.tags = tags

    self.args.extend(kwargs_to_cli_args(**kwargs))

file instance-attribute

file: Path = ensure_path_exists(file, self)

default instance-attribute

default: bool = default

forced instance-attribute

forced: bool = forced

name instance-attribute

name: str = name

delay instance-attribute

delay: int = file.container_delay if isinstance(file, FileMixin) else delay

lang instance-attribute

lang: str = lang

type instance-attribute

type: TrackType = type if isinstance(type, TrackType) else TrackType(type) if isinstance(type, int) else TrackType[type.upper()]

args instance-attribute

args: list[str] = args or []

tags instance-attribute

tags: dict[str, str] | None = tags

mkvmerge_args

mkvmerge_args() -> list[str]
Source code in muxtools/muxing/tracks.py
def mkvmerge_args(self) -> list[str]:
    filepath = str(self.file.resolve())
    if self.type == TrackType.ATTACHMENT:
        is_font = self.file.suffix.lower() in [".ttf", ".otf", ".ttc", ".otc"]
        if not is_font and not self.lang:
            raise ValueError("Please specify a mimetype for the attachments if they're not fonts!")
        args = ["--attachment-mime-type", self.lang or "application/x-truetype-font"]
        if self.name:
            args.extend(["--attachment-name", self.name])

        args.extend(["--attach-file", filepath])
        return args

    elif self.type == TrackType.MKV:
        if self.args:
            return [*self.args, filepath]
        return [filepath]
    elif self.type == TrackType.CHAPTERS:
        return ["--chapters", filepath]

    name = apply_dynamic_tokens(self.name, self.file, False, self.lang if self.lang else None)
    args = ["--no-global-tags", "--track-name", f"0:{name}"]

    if self.tags and not all([not bool(v) for _, v in self.tags.items()]):
        tags_file = make_output(self.file, "xml", "_tags", temp=True)
        create_tags_xml(tags_file, self.tags)
        args.extend(["--tags", f"0:{str(tags_file)}"])

    if self.lang:
        args.extend(["--language", f"0:{standardize_tag(self.lang)[1]}"])
    if self.delay:
        args.extend(["--sync", f"0:{self.delay}"])
    args.extend(
        [
            "--default-track-flag",
            f"0:{'yes' if self.default else 'no'}",
            "--forced-display-flag",
            f"0:{'yes' if self.forced else 'no'}",
        ]
    )
    if self.args:
        args.extend(self.args)
    args.append(filepath)
    return args

VideoTrack

VideoTrack(file: PathLike | GlobSearch, name: str = '', lang: str = 'ja', default: bool = True, forced: bool = False, delay: int = 0, timecode_file: PathLike | GlobSearch | None = None, crop: int | tuple[int, int] | tuple[int, int, int, int] | None = None, args: list[str] | None = None, tags: dict[str, str] | None = None, **kwargs: Any)

Bases: _track

_track object with VIDEO type preselected and japanese language default

Parameters:

Name Type Description Default
timecode_file PathLike | GlobSearch | None

Pass a path for proper vfr playback if needed.

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
Source code in muxtools/muxing/tracks.py
def __init__(
    self,
    file: PathLike | GlobSearch,
    name: str = "",
    lang: str = "ja",
    default: bool = True,
    forced: bool = False,
    delay: int = 0,
    timecode_file: PathLike | GlobSearch | None = None,
    crop: int | tuple[int, int] | tuple[int, int, int, int] | None = None,
    args: list[str] | None = None,
    tags: dict[str, str] | None = None,
    **kwargs: Any,
) -> None:
    file = ensure_path(file, self)
    args = args or []
    if timecode_file is not None:
        args.extend(["--timestamps", f"0:{ensure_path_exists(timecode_file, self).resolve()}"])
    if crop:
        if isinstance(crop, int):
            croplist = [crop] * 4
        elif len(crop) == 2:
            croplist = list[int](crop) * 2
        else:
            croplist = list[int](crop)
        args.extend(["--cropping", f"0:{croplist[0]},{croplist[1]},{croplist[2]},{croplist[3]}"])

    # Check for SAR in bitstream and get new display-width / height params for mkvmerge
    if "--display-dimensions" not in args and "display_dimensions" not in kwargs:
        parsed = ParsedFile.from_file(file, self, False)
        video = parsed.find_tracks(type=TrackType.VIDEO, relative_id=0, caller=self)
        if not video:
            raise error(f"File '{file.name}' does not have a video track!", self)
        if not video[0].raw_ffprobe.width or not video[0].raw_ffprobe.height:
            raise error(f"Video track 0 in file '{file.name}' does not have valid dimensions.", self)

        if video[0].raw_ffprobe.sample_aspect_ratio:
            if (sar_fraction := Fraction(str(video[0].raw_ffprobe.sample_aspect_ratio).replace(":", "/"))) != 1:
                new_display_sizes = sar_fraction * video[0].raw_ffprobe.width / video[0].raw_ffprobe.height
                kwargs.update(display_dimensions=f"0:{new_display_sizes.numerator}x{new_display_sizes.denominator}")

    super().__init__(file, TrackType.VIDEO, name, lang, default, forced, delay, args, tags, **kwargs)

file instance-attribute

file: Path = ensure_path_exists(file, self)

type instance-attribute

type: TrackType = type if isinstance(type, TrackType) else TrackType(type) if isinstance(type, int) else TrackType[type.upper()]

default instance-attribute

default: bool = default

forced instance-attribute

forced: bool = forced

name instance-attribute

name: str = name

lang instance-attribute

lang: str = lang

delay instance-attribute

delay: int = file.container_delay if isinstance(file, FileMixin) else delay

args instance-attribute

args: list[str] = args or []

tags instance-attribute

tags: dict[str, str] | None = tags

mkvmerge_args

mkvmerge_args() -> list[str]
Source code in muxtools/muxing/tracks.py
def mkvmerge_args(self) -> list[str]:
    filepath = str(self.file.resolve())
    if self.type == TrackType.ATTACHMENT:
        is_font = self.file.suffix.lower() in [".ttf", ".otf", ".ttc", ".otc"]
        if not is_font and not self.lang:
            raise ValueError("Please specify a mimetype for the attachments if they're not fonts!")
        args = ["--attachment-mime-type", self.lang or "application/x-truetype-font"]
        if self.name:
            args.extend(["--attachment-name", self.name])

        args.extend(["--attach-file", filepath])
        return args

    elif self.type == TrackType.MKV:
        if self.args:
            return [*self.args, filepath]
        return [filepath]
    elif self.type == TrackType.CHAPTERS:
        return ["--chapters", filepath]

    name = apply_dynamic_tokens(self.name, self.file, False, self.lang if self.lang else None)
    args = ["--no-global-tags", "--track-name", f"0:{name}"]

    if self.tags and not all([not bool(v) for _, v in self.tags.items()]):
        tags_file = make_output(self.file, "xml", "_tags", temp=True)
        create_tags_xml(tags_file, self.tags)
        args.extend(["--tags", f"0:{str(tags_file)}"])

    if self.lang:
        args.extend(["--language", f"0:{standardize_tag(self.lang)[1]}"])
    if self.delay:
        args.extend(["--sync", f"0:{self.delay}"])
    args.extend(
        [
            "--default-track-flag",
            f"0:{'yes' if self.default else 'no'}",
            "--forced-display-flag",
            f"0:{'yes' if self.forced else 'no'}",
        ]
    )
    if self.args:
        args.extend(self.args)
    args.append(filepath)
    return args

AudioTrack

AudioTrack(file: PathLike | GlobSearch, name: str = '', lang: str = 'ja', default: bool = True, forced: bool = False, delay: int = 0, args: list[str] | None = None, tags: dict[str, str] | None = None, **kwargs: Any)

Bases: _track

_track object with AUDIO type preselected and japanese language default

Source code in muxtools/muxing/tracks.py
def __init__(
    self,
    file: PathLike | GlobSearch,
    name: str = "",
    lang: str = "ja",
    default: bool = True,
    forced: bool = False,
    delay: int = 0,
    args: list[str] | None = None,
    tags: dict[str, str] | None = None,
    **kwargs: Any,
) -> None:
    super().__init__(ensure_path(file, self), TrackType.AUDIO, name, lang, default, forced, delay, args, tags, **kwargs)

file instance-attribute

file: Path = ensure_path_exists(file, self)

type instance-attribute

type: TrackType = type if isinstance(type, TrackType) else TrackType(type) if isinstance(type, int) else TrackType[type.upper()]

default instance-attribute

default: bool = default

forced instance-attribute

forced: bool = forced

name instance-attribute

name: str = name

lang instance-attribute

lang: str = lang

delay instance-attribute

delay: int = file.container_delay if isinstance(file, FileMixin) else delay

args instance-attribute

args: list[str] = args or []

tags instance-attribute

tags: dict[str, str] | None = tags

mkvmerge_args

mkvmerge_args() -> list[str]
Source code in muxtools/muxing/tracks.py
def mkvmerge_args(self) -> list[str]:
    filepath = str(self.file.resolve())
    if self.type == TrackType.ATTACHMENT:
        is_font = self.file.suffix.lower() in [".ttf", ".otf", ".ttc", ".otc"]
        if not is_font and not self.lang:
            raise ValueError("Please specify a mimetype for the attachments if they're not fonts!")
        args = ["--attachment-mime-type", self.lang or "application/x-truetype-font"]
        if self.name:
            args.extend(["--attachment-name", self.name])

        args.extend(["--attach-file", filepath])
        return args

    elif self.type == TrackType.MKV:
        if self.args:
            return [*self.args, filepath]
        return [filepath]
    elif self.type == TrackType.CHAPTERS:
        return ["--chapters", filepath]

    name = apply_dynamic_tokens(self.name, self.file, False, self.lang if self.lang else None)
    args = ["--no-global-tags", "--track-name", f"0:{name}"]

    if self.tags and not all([not bool(v) for _, v in self.tags.items()]):
        tags_file = make_output(self.file, "xml", "_tags", temp=True)
        create_tags_xml(tags_file, self.tags)
        args.extend(["--tags", f"0:{str(tags_file)}"])

    if self.lang:
        args.extend(["--language", f"0:{standardize_tag(self.lang)[1]}"])
    if self.delay:
        args.extend(["--sync", f"0:{self.delay}"])
    args.extend(
        [
            "--default-track-flag",
            f"0:{'yes' if self.default else 'no'}",
            "--forced-display-flag",
            f"0:{'yes' if self.forced else 'no'}",
        ]
    )
    if self.args:
        args.extend(self.args)
    args.append(filepath)
    return args

SubTrack

SubTrack(file: PathLike | GlobSearch, name: str = '', lang: str = 'en', default: bool = True, forced: bool = False, delay: int = 0, args: list[str] | None = None, tags: dict[str, str] | None = None, **kwargs: Any)

Bases: _track

_track object with SUB type preselected and english language default

Supports merging multiple files by passing a List of Path objects or filepath strings and of course also a GlobSearch

Source code in muxtools/muxing/tracks.py
def __init__(
    self,
    file: PathLike | GlobSearch,
    name: str = "",
    lang: str = "en",
    default: bool = True,
    forced: bool = False,
    delay: int = 0,
    args: list[str] | None = None,
    tags: dict[str, str] | None = None,
    **kwargs: Any,
) -> None:
    super().__init__(ensure_path(file, self), TrackType.SUB, name, lang, default, forced, delay, args, tags, **kwargs)

file instance-attribute

file: Path = ensure_path_exists(file, self)

type instance-attribute

type: TrackType = type if isinstance(type, TrackType) else TrackType(type) if isinstance(type, int) else TrackType[type.upper()]

default instance-attribute

default: bool = default

forced instance-attribute

forced: bool = forced

name instance-attribute

name: str = name

lang instance-attribute

lang: str = lang

delay instance-attribute

delay: int = file.container_delay if isinstance(file, FileMixin) else delay

args instance-attribute

args: list[str] = args or []

tags instance-attribute

tags: dict[str, str] | None = tags

mkvmerge_args

mkvmerge_args() -> list[str]
Source code in muxtools/muxing/tracks.py
def mkvmerge_args(self) -> list[str]:
    filepath = str(self.file.resolve())
    if self.type == TrackType.ATTACHMENT:
        is_font = self.file.suffix.lower() in [".ttf", ".otf", ".ttc", ".otc"]
        if not is_font and not self.lang:
            raise ValueError("Please specify a mimetype for the attachments if they're not fonts!")
        args = ["--attachment-mime-type", self.lang or "application/x-truetype-font"]
        if self.name:
            args.extend(["--attachment-name", self.name])

        args.extend(["--attach-file", filepath])
        return args

    elif self.type == TrackType.MKV:
        if self.args:
            return [*self.args, filepath]
        return [filepath]
    elif self.type == TrackType.CHAPTERS:
        return ["--chapters", filepath]

    name = apply_dynamic_tokens(self.name, self.file, False, self.lang if self.lang else None)
    args = ["--no-global-tags", "--track-name", f"0:{name}"]

    if self.tags and not all([not bool(v) for _, v in self.tags.items()]):
        tags_file = make_output(self.file, "xml", "_tags", temp=True)
        create_tags_xml(tags_file, self.tags)
        args.extend(["--tags", f"0:{str(tags_file)}"])

    if self.lang:
        args.extend(["--language", f"0:{standardize_tag(self.lang)[1]}"])
    if self.delay:
        args.extend(["--sync", f"0:{self.delay}"])
    args.extend(
        [
            "--default-track-flag",
            f"0:{'yes' if self.default else 'no'}",
            "--forced-display-flag",
            f"0:{'yes' if self.forced else 'no'}",
        ]
    )
    if self.args:
        args.extend(self.args)
    args.append(filepath)
    return args

Premux

Premux(file: PathLike | GlobSearch, video: int | list[int] | None = -1, audio: int | list[int] | None = -1, subtitles: int | list[int] | None = -1, keep_attachments: bool = True, keep_chapters: bool = True, mkvmerge_args: str | list[str] = '--no-global-tags', assume_absolute: bool = False)

Bases: _track

Custom Track object to arbitrarily grab tracks from an existing file.

For all track params: None means there won't be any chosen and -1 means all will be chosen. You can also specify a single or multiple relative track numbers to choose any.

Parameters:

Name Type Description Default
video int | list[int] | None

Video Track(s) to choose

-1
audio int | list[int] | None

Audio Track(s) to choose

-1
subtitles int | list[int] | None

Subtitle Track(s) to choose

-1
keep_attachments bool

Whether to keep attachments from the file. Fonts for example.

True
keep_chapters bool

Whether or not you want to keep existing chapters in the file.

True
mkvmerge_args str | list[str]

Any other args you may want to pass.

'--no-global-tags'
assume_absolute bool

Assume that the track numbers passed were already absolute to begin with. If False it will simply get absolute numbers derived from the relative ones.

False
Source code in muxtools/muxing/tracks.py
def __init__(
    self,
    file: PathLike | GlobSearch,
    video: int | list[int] | None = -1,
    audio: int | list[int] | None = -1,
    subtitles: int | list[int] | None = -1,
    keep_attachments: bool = True,
    keep_chapters: bool = True,
    mkvmerge_args: str | list[str] = "--no-global-tags",
    assume_absolute: bool = False,
) -> None:
    """
    Custom Track object to arbitrarily grab tracks from an existing file.

    For all track params:
    `None` means there won't be any chosen and `-1` means all will be chosen.
    You can also specify a single or multiple *relative* track numbers to choose any.

    :param video:               Video Track(s) to choose
    :param audio:               Audio Track(s) to choose
    :param subtitles:           Subtitle Track(s) to choose
    :param keep_attachments:    Whether to keep attachments from the file. Fonts for example.
    :param keep_chapters:       Whether or not you want to keep existing chapters in the file.
    :param mkvmerge_args:       Any other args you may want to pass.
    :param assume_absolute:     Assume that the track numbers passed were already absolute to begin with.
                                If False it will simply get absolute numbers derived from the relative ones.
    """
    args = ""
    parsed = ParsedFile.from_file(file, self)
    if video is None:
        args += " -D"
    elif video != -1:
        if not isinstance(video, list):
            video = [video]
        lv = []
        for num in video:
            abso = parsed.find_tracks(relative_id=num, type=TrackType.VIDEO, error_if_empty=True)[0].index if not assume_absolute else num
            lv.append(abso)
        args += f" -d {','.join(str(i) for i in lv)}"

    if audio is None:
        args += " -A"
    elif audio != -1:
        if not isinstance(audio, list):
            audio = [audio]
        la = []
        for num in audio:
            abso = parsed.find_tracks(relative_id=num, type=TrackType.AUDIO, error_if_empty=True)[0].index if not assume_absolute else num
            la.append(abso)
        args += f" -a {','.join(str(i) for i in la)}"

    if subtitles is None:
        args += " -S"
    elif subtitles != -1:
        if not isinstance(subtitles, list):
            subtitles = [subtitles]
        ls = []
        for num in subtitles:
            abso = parsed.find_tracks(relative_id=num, type=TrackType.SUB, error_if_empty=True)[0].index if not assume_absolute else num
            ls.append(abso)
        args += f" -s {','.join(str(i) for i in ls)}"

    if not keep_attachments:
        args += " -M"

    if not keep_chapters:
        args += " --no-chapters"

    args = split_args(args.strip())
    mkvmerge_args = split_args(mkvmerge_args.strip()) if isinstance(mkvmerge_args, str) else mkvmerge_args
    super().__init__(ensure_path(file, self), TrackType.MKV, args=args + mkvmerge_args)

file instance-attribute

file: Path = ensure_path_exists(file, self)

type instance-attribute

type: TrackType = type if isinstance(type, TrackType) else TrackType(type) if isinstance(type, int) else TrackType[type.upper()]

default instance-attribute

default: bool = default

forced instance-attribute

forced: bool = forced

name instance-attribute

name: str = name

lang instance-attribute

lang: str = lang

delay instance-attribute

delay: int = file.container_delay if isinstance(file, FileMixin) else delay

args instance-attribute

args: list[str] = args or []

tags instance-attribute

tags: dict[str, str] | None = tags

mkvmerge_args

mkvmerge_args() -> list[str]
Source code in muxtools/muxing/tracks.py
def mkvmerge_args(self) -> list[str]:
    filepath = str(self.file.resolve())
    if self.type == TrackType.ATTACHMENT:
        is_font = self.file.suffix.lower() in [".ttf", ".otf", ".ttc", ".otc"]
        if not is_font and not self.lang:
            raise ValueError("Please specify a mimetype for the attachments if they're not fonts!")
        args = ["--attachment-mime-type", self.lang or "application/x-truetype-font"]
        if self.name:
            args.extend(["--attachment-name", self.name])

        args.extend(["--attach-file", filepath])
        return args

    elif self.type == TrackType.MKV:
        if self.args:
            return [*self.args, filepath]
        return [filepath]
    elif self.type == TrackType.CHAPTERS:
        return ["--chapters", filepath]

    name = apply_dynamic_tokens(self.name, self.file, False, self.lang if self.lang else None)
    args = ["--no-global-tags", "--track-name", f"0:{name}"]

    if self.tags and not all([not bool(v) for _, v in self.tags.items()]):
        tags_file = make_output(self.file, "xml", "_tags", temp=True)
        create_tags_xml(tags_file, self.tags)
        args.extend(["--tags", f"0:{str(tags_file)}"])

    if self.lang:
        args.extend(["--language", f"0:{standardize_tag(self.lang)[1]}"])
    if self.delay:
        args.extend(["--sync", f"0:{self.delay}"])
    args.extend(
        [
            "--default-track-flag",
            f"0:{'yes' if self.default else 'no'}",
            "--forced-display-flag",
            f"0:{'yes' if self.forced else 'no'}",
        ]
    )
    if self.args:
        args.extend(self.args)
    args.append(filepath)
    return args

Attachment

Attachment(file: str | Path, mimetype: str = '', name: str = '')

Bases: _track

pseudo _track object for attachments

Source code in muxtools/muxing/tracks.py
def __init__(self, file: str | Path, mimetype: str = "", name: str = "") -> None:
    super().__init__(ensure_path(file, self), TrackType.ATTACHMENT, lang=mimetype, name=name)

file instance-attribute

file: Path = ensure_path_exists(file, self)

type instance-attribute

type: TrackType = type if isinstance(type, TrackType) else TrackType(type) if isinstance(type, int) else TrackType[type.upper()]

default instance-attribute

default: bool = default

forced instance-attribute

forced: bool = forced

name instance-attribute

name: str = name

lang instance-attribute

lang: str = lang

delay instance-attribute

delay: int = file.container_delay if isinstance(file, FileMixin) else delay

args instance-attribute

args: list[str] = args or []

tags instance-attribute

tags: dict[str, str] | None = tags

mkvmerge_args

mkvmerge_args() -> list[str]
Source code in muxtools/muxing/tracks.py
def mkvmerge_args(self) -> list[str]:
    filepath = str(self.file.resolve())
    if self.type == TrackType.ATTACHMENT:
        is_font = self.file.suffix.lower() in [".ttf", ".otf", ".ttc", ".otc"]
        if not is_font and not self.lang:
            raise ValueError("Please specify a mimetype for the attachments if they're not fonts!")
        args = ["--attachment-mime-type", self.lang or "application/x-truetype-font"]
        if self.name:
            args.extend(["--attachment-name", self.name])

        args.extend(["--attach-file", filepath])
        return args

    elif self.type == TrackType.MKV:
        if self.args:
            return [*self.args, filepath]
        return [filepath]
    elif self.type == TrackType.CHAPTERS:
        return ["--chapters", filepath]

    name = apply_dynamic_tokens(self.name, self.file, False, self.lang if self.lang else None)
    args = ["--no-global-tags", "--track-name", f"0:{name}"]

    if self.tags and not all([not bool(v) for _, v in self.tags.items()]):
        tags_file = make_output(self.file, "xml", "_tags", temp=True)
        create_tags_xml(tags_file, self.tags)
        args.extend(["--tags", f"0:{str(tags_file)}"])

    if self.lang:
        args.extend(["--language", f"0:{standardize_tag(self.lang)[1]}"])
    if self.delay:
        args.extend(["--sync", f"0:{self.delay}"])
    args.extend(
        [
            "--default-track-flag",
            f"0:{'yes' if self.default else 'no'}",
            "--forced-display-flag",
            f"0:{'yes' if self.forced else 'no'}",
        ]
    )
    if self.args:
        args.extend(self.args)
    args.append(filepath)
    return args