Skip to content

main

Setup dataclass

Something like an environment used for a lot of functions in this package. Mostly used for muxing and data locations (work directory and what not).

If you want to change any of the variables AFTER initialization make sure to use the Setup.edit function to do so. Read its docstring to get why.

Parameters:

Name Type Description Default
episode str

Episode identifier used for workdir and muxing

'01'
config_file str

An ini file where the config will be loaded from. You can disable this by leaving it empty or setting None. Make sure you set the relevant variables in this constructor in that case. You can also set other, technically, not existing variables in there and access them from python after.

'config.ini'
bdmv_dir str

Convenience path for sources and what not.

'BDMV'
show_name str

The name of the show. Used for the $show$ placeholder in muxing.

'Nice Series'
allow_binary_download bool

This will download any executables needed for doing what you're requesting to do. For example x265, opusenc, etc.

True
clean_work_dirs bool

Cleanup the work directories after muxing. Might be useful if you're muxing a ton of stuff.

False
out_dir str

The folder the muxed files will go into.

'premux'
out_name str

The naming template applied to the muxed files.

'$show$ - $ep$ (premux)'
mkv_title_naming str

The naming template applied to the mkv title.

'$show$ - $ep$'
work_dir str | None

In case you want to set a custom work directory for all the temp files.

None
debug bool

Enable or Disable various, possibly interesting, debug output of all functions in this package.

True
Source code in muxtools/main.py
@dataclass
class Setup:
    """
    Something like an environment used for a lot of functions in this package.
    Mostly used for muxing and data locations (work directory and what not).

    If you want to change any of the variables AFTER initialization make sure to use the `Setup.edit` function to do so.
    Read its docstring to get why.

    :param episode:                 Episode identifier used for workdir and muxing
    :param config_file:             An ini file where the config will be loaded from.
                                    You can disable this by leaving it empty or setting None.
                                    Make sure you set the relevant variables in this constructor in that case.
                                    You can also set other, technically, not existing variables in there and access them from python after.

    :param bdmv_dir:                Convenience path for sources and what not.
    :param show_name:               The name of the show. Used for the $show$ placeholder in muxing.
    :param allow_binary_download:   This will download any executables needed for doing what you're requesting to do.
                                    For example x265, opusenc, etc.
    :param clean_work_dirs:         Cleanup the work directories after muxing. Might be useful if you're muxing a ton of stuff.
    :param out_dir:                 The folder the muxed files will go into.
    :param out_name:                The naming template applied to the muxed files.
    :param mkv_title_naming:        The naming template applied to the mkv title.
    :param work_dir:                In case you want to set a custom work directory for all the temp files.
    :param debug:                   Enable or Disable various, possibly interesting, debug output of all functions in this package.
    """

    episode: str = "01"
    config_file: str = "config.ini"

    bdmv_dir: str = "BDMV"
    show_name: str = "Nice Series"
    allow_binary_download: bool = True
    clean_work_dirs: bool = False
    out_dir: str = "premux"
    out_name: str = "$show$ - $ep$ (premux)"
    mkv_title_naming: str = r"$show$ - $ep$"
    work_dir: str | None = None
    debug: bool = True

    def __post_init__(self):
        if self.config_file:
            config = ConfigParser()
            config_name = self.config_file

            if not os.path.exists(config_name):
                config["SETUP"] = {
                    "bdmv_dir": self.bdmv_dir,
                    "show_name": self.show_name,
                    "allow_binary_download": self.allow_binary_download,
                    "clean_work_dirs": self.clean_work_dirs,
                    "out_dir": self.out_dir,
                    "out_name": self.out_name,
                    "mkv_title_naming": self.mkv_title_naming,
                    "debug": self.debug,
                }

                with open(config_name, "w", encoding="utf-8") as config_file:
                    config.write(config_file)

                raise error(f"Template config created at {Path(config_name).resolve()}.\nPlease set it up!")

            config.read(config_name, encoding="utf-8")
            settings = config["SETUP"]

            valid_bools = ["true", "1", "t", "y", "yes"]
            for key in settings:
                if hasattr(self, key) and isinstance(getattr(self, key), bool):
                    setattr(self, key, True if settings[key].lower() in valid_bools else False)
                else:
                    setattr(self, key, settings[key])

        if not self.work_dir:
            self.work_dir = Path(os.getcwd(), "_workdir", self.episode)

        self.work_dir = Path(self.work_dir)
        self.work_dir.mkdir(parents=True, exist_ok=True)
        self.work_dir = str(self.work_dir)

        from .utils.env import save_setup

        save_setup(self)

    def edit(self: SetupSelf, attr: str, value: Any) -> SetupSelf:
        """
        Sets a variable inside of Setup and saves it to the environment variables.
        You should use this to apply any changes because other functions will not make use of them otherwise!

        :param attr:        The name of the variable/attribute you want to change
        :param value:       The value this variable/attribute will have.
        """
        setattr(self, attr, value)

        from .utils.env import save_setup

        save_setup(self)
        return self

    def _toJson(self) -> str:
        return json.dumps(self.__dict__)

edit(attr, value)

Sets a variable inside of Setup and saves it to the environment variables. You should use this to apply any changes because other functions will not make use of them otherwise!

Parameters:

Name Type Description Default
attr str

The name of the variable/attribute you want to change

required
value Any

The value this variable/attribute will have.

required
Source code in muxtools/main.py
def edit(self: SetupSelf, attr: str, value: Any) -> SetupSelf:
    """
    Sets a variable inside of Setup and saves it to the environment variables.
    You should use this to apply any changes because other functions will not make use of them otherwise!

    :param attr:        The name of the variable/attribute you want to change
    :param value:       The value this variable/attribute will have.
    """
    setattr(self, attr, value)

    from .utils.env import save_setup

    save_setup(self)
    return self

do_audio(fileIn, track=0, trims=None, fps=Fraction(24000, 1001), num_frames=0, extractor=FFMpeg.Extractor(), trimmer=AutoTrimmer(), encoder=AutoEncoder(), quiet=True, output=None)

One-liner to handle the whole audio processing

Parameters:

Name Type Description Default
fileIn PathLike | list[PathLike]

Input file

required
track int

Audio track number

0
trims Trim | list[Trim] | None

Frame ranges to trim and/or combine, e.g. (24, -24) or [(24, 500), (700, 900)]

None
fps Fraction | PathLike

FPS Fraction used for the conversion to time. Also accepts a timecode (v2) file.

Fraction(24000, 1001)
num_frames int

Total number of frames, used for negative numbers in trims

0
extractor Extractor

Tool used to extract the audio

Extractor()
trimmer Trimmer | None

Tool used to trim the audio AutoTrimmer means it will choose ffmpeg for lossy and Sox for lossless

AutoTrimmer()
encoder Encoder | None

Tool used to encode the audio AutoEncoder means it won't reencode lossy and choose opus otherwise

AutoEncoder()
quiet bool

Whether the tool output should be visible

True
output PathLike | None

Custom output file or directory, extensions will be automatically added

None

Returns:

Type Description
AudioFile

AudioFile Object containing file path, delays and source

Source code in muxtools/functions.py
def do_audio(
    fileIn: PathLike | list[PathLike],
    track: int = 0,
    trims: Trim | list[Trim] | None = None,
    fps: Fraction | PathLike = Fraction(24000, 1001),
    num_frames: int = 0,
    extractor: Extractor = FFMpeg.Extractor(),
    trimmer: Trimmer | None = AutoTrimmer(),
    encoder: Encoder | None = AutoEncoder(),
    quiet: bool = True,
    output: PathLike | None = None,
) -> AudioFile:
    """
    One-liner to handle the whole audio processing

    :param fileIn:          Input file
    :param track:           Audio track number
    :param trims:           Frame ranges to trim and/or combine, e.g. (24, -24) or [(24, 500), (700, 900)]
    :param fps:             FPS Fraction used for the conversion to time. Also accepts a timecode (v2) file.
    :param num_frames:      Total number of frames, used for negative numbers in trims
    :param extractor:       Tool used to extract the audio
    :param trimmer:         Tool used to trim the audio
                            AutoTrimmer means it will choose ffmpeg for lossy and Sox for lossless

    :param encoder:         Tool used to encode the audio
                            AutoEncoder means it won't reencode lossy and choose opus otherwise

    :param quiet:           Whether the tool output should be visible
    :param output:          Custom output file or directory, extensions will be automatically added
    :return:                AudioFile Object containing file path, delays and source
    """
    if isinstance(fileIn, list) and (not extractor or not isinstance(extractor, FFMpeg.Extractor)):
        raise error("When passing a list of files you have to use the FFMpeg extractor!", do_audio)

    if extractor:
        setattr(extractor, "track", track)
        if not trimmer and not encoder:
            setattr(extractor, "output", output)
        if isinstance(fileIn, list):
            info(f"Extracting audio from {len(fileIn)} files to concatenate...", do_audio)
            extractor._no_print = True
            fileIn = [ensure_path_exists(f, do_audio) for f in fileIn]
            extracted = []
            for f in fileIn:
                try:
                    af = extractor.extract_audio(f, quiet, True, True)
                except:
                    setattr(extractor, "track", 0)
                    af = extractor.extract_audio(f, quiet, True, True)
                    setattr(extractor, "track", track)
                    duration = af.duration or timedelta(milliseconds=0)
                    if duration > timedelta(seconds=2):
                        danger(f"Could not find valid track {track} in '{f.name}' and falling back resulted in suspiciously long file.", do_audio, 1)
                        continue

                    duration = format_timedelta(duration)
                    warn(f"Fell back to track 0 for '{f.name}' with a duration of {duration}", do_audio, 1)

                extracted.append(af)
            audio = FFMpeg.Concat(extracted).concat_audio()
        else:
            audio = extractor.extract_audio(fileIn, quiet)

    if not isinstance(audio, AudioFile):
        audio = AudioFile.from_file(audio, do_audio)

    lossy = audio.is_lossy()

    if isinstance(trimmer, AutoTrimmer) and trims:
        if lossy:
            trimmer = FFMpeg.Trimmer()
        else:
            trimmer = Sox()

    if isinstance(encoder, AutoEncoder):
        if lossy:
            encoder = None
        elif is_fancy_codec(audio.get_mediainfo()):
            encoder = None
            warn("Audio will not be reencoded due to having Atmos or special DTS features.", do_audio, 2)
        else:
            encoder = Opus()

    if trimmer and trims:
        setattr(trimmer, "trim", trims)
        setattr(trimmer, "fps", fps)
        setattr(trimmer, "num_frames", num_frames)
        if not extractor and not encoder:
            setattr(trimmer, "output", output)
        trimmed = trimmer.trim_audio(audio, quiet)
        ensure_path(audio.file, do_audio).unlink(missing_ok=True)
        audio = trimmed

    if encoder:
        setattr(encoder, "output", output)
        encoded = encoder.encode_audio(audio, quiet)
        ensure_path(audio.file, do_audio).unlink(missing_ok=True)
        audio = encoded

    print("")
    return audio