Skip to content

API Reference

Warning

This API is considered unstable and might change in the future. If you're using this, pin your install to a specific version

juicenet

juicenet(path: StrPath, /, *, config: Union[StrPath, JuicenetConfig], public: bool = False, bdmv_naming: bool = False, resume: bool = True, skip_raw: bool = False, debug: bool = False) -> JuiceBox

Upload a file or folder to usenet. This will always produce one NZB for one input.

Parameters:

Name Type Description Default
path str or Path

The path to an existing file. This can either be a string representing the path or a pathlib.Path object.

required
config str or Path or JuicenetConfig

The configuration to use when processing the file or directory. This can either be a string representing the path to a YAML configuration file, a pathlib.Path object pointing to a YAML configuration file, or a juicenet.JuicenetConfig dataclass.

required
public bool

Whether the upload is meant to be public or not. Uses the public config if specified, falls back to using the private one if not. Default is False.

False
bdmv_naming bool

Whether to use an alternate naming for BDMVs. This will try to fix the awful BDMV disc naming in some cases, i.e, if you pass /BDMVs/Big Buck Bunny [Vol.1]/DISC_01, then the resulting NZB will be renamed to Big Buck Bunny [Vol.1]_DISC_01.nzb instead of DISC_01.nzb. Recommended if your input is a BDMV disc and you're uncertain about how they are named. Keep it False if you're certain that your BDMV discs are named appropriately. Only works on directory input, does nothing (i.e, False) if your input is a file.

False
resume bool

Whether to enable resumability. Files uploaded by previous runs will be skipped if True. Default is True.

True
skip_raw bool

Skip checking and reposting failed raw articles. Default is False.

False
debug bool

Whether to enable debug logs. Default is False.

False

Returns:

Type Description
JuiceBox

Dataclass used to represent the output of Juicenet.

Raises:

Type Description
JuicenetInputError

Invalid input.

Notes
  • You should never upload an entire directory consisting of several files as a single NZB. Use juicenet.get_files or juicenet.get_glob_matches to first get the relevant files and then pass each one to juicenet.

  • You should never upload an entire BDMV consisting of several discs as a single NZB. Use juicenet.get_bdmv_discs to first get each individual disc and then pass each one to juicenet.

Examples:

from pathlib import Path

from juicenet import JuicenetConfig, juicenet

file = Path("C:/Users/raven/Videos/Big Buck Bunny.mkv") # Path works

config = "D:/data/usenet/config/juicenet.yaml" # string also works

# Convenient config class instead of a config file
config = JuicenetConfig(
    nyuu_config_private="D:/data/usenet/juicenetConfig/nyuu-config.json",
    nzb_output_path=Path("D:/data/usenet/nzbs"),
)

upload = juicenet(file, config=config)

print(upload.nyuu.nzb)
# D:/data/usenet/nzbs/private/Videos/Big Buck Bunny.mkv.nzb
Source code in src/juicenet/api/main.py
def juicenet(
    path: StrPath,
    /,
    *,
    config: Union[StrPath, JuicenetConfig],
    public: bool = False,
    bdmv_naming: bool = False,
    resume: bool = True,
    skip_raw: bool = False,
    debug: bool = False,
) -> JuiceBox:
    """
    Upload a file or folder to usenet. This will always produce one NZB for one input.

    Parameters
    ----------
    path : str or pathlib.Path
        The path to an existing file. This can either be a string representing the path or a pathlib.Path object.
    config : str or pathlib.Path or JuicenetConfig
        The configuration to use when processing the file or directory.
        This can either be a string representing the path to a YAML configuration file,
        a `pathlib.Path` object pointing to a YAML configuration file,
        or a `juicenet.JuicenetConfig` dataclass.
    public : bool, optional
        Whether the upload is meant to be public or not. Uses the public config if specified,
        falls back to using the private one if not. Default is False.
    bdmv_naming : bool, optional
        Whether to use an alternate naming for BDMVs.
        This will try to fix the awful BDMV disc naming in some cases, i.e, if you pass `/BDMVs/Big Buck Bunny [Vol.1]/DISC_01`,
        then the resulting NZB will be renamed to `Big Buck Bunny [Vol.1]_DISC_01.nzb` instead of `DISC_01.nzb`.
        Recommended if your input is a BDMV disc and you're uncertain about how they are named. Keep it `False` if you're
        certain that your BDMV discs are named appropriately. Only works on directory input,
        does nothing (i.e, `False`) if your input is a file.
    resume: bool, optional
        Whether to enable resumability. Files uploaded by previous runs will be skipped if True. Default is True.
    skip_raw: bool, optional
        Skip checking and reposting failed raw articles. Default is False.
    debug : bool, optional
        Whether to enable debug logs. Default is False.

    Returns
    -------
    JuiceBox
        Dataclass used to represent the output of Juicenet.

    Raises
    ------
    JuicenetInputError
        Invalid input.

    Notes
    -----
    - You should never upload an entire directory consisting of several files as a single NZB.
      Use `juicenet.get_files` or `juicenet.get_glob_matches` to first get the relevant files and
      then pass each one to juicenet.

    - You should never upload an entire BDMV consisting of several discs as a single NZB.
      Use `juicenet.get_bdmv_discs` to first get each individual disc and then pass each one to juicenet.

    Examples
    --------
    ```python
    from pathlib import Path

    from juicenet import JuicenetConfig, juicenet

    file = Path("C:/Users/raven/Videos/Big Buck Bunny.mkv") # Path works

    config = "D:/data/usenet/config/juicenet.yaml" # string also works

    # Convenient config class instead of a config file
    config = JuicenetConfig(
        nyuu_config_private="D:/data/usenet/juicenetConfig/nyuu-config.json",
        nzb_output_path=Path("D:/data/usenet/nzbs"),
    )

    upload = juicenet(file, config=config)

    print(upload.nyuu.nzb)
    # D:/data/usenet/nzbs/private/Videos/Big Buck Bunny.mkv.nzb
    ```
    """

    if isinstance(path, str):
        _path = Path(path).resolve()
    elif isinstance(path, Path):
        _path = path.resolve()
    else:
        raise JuicenetInputError("Path must be a string or pathlib.Path")

    if not _path.exists():
        raise JuicenetInputError(f"{_path} must be an existing file or directory")

    filelist = filter_empty_files([_path])

    if len(filelist) == 1:
        file = filelist[0]
    else:
        raise JuicenetInputError(f"{_path} is empty (0-byte)!")

    if isinstance(config, str):
        _config = Path(config).resolve()
    elif isinstance(config, Path):
        _config = config.resolve()
    elif isinstance(config, JuicenetConfig):
        _config = config  # type: ignore
    else:
        raise JuicenetInputError("Config must be a path or a juicenet.JuicenetConfig")

    # Read config file
    config_data = read_config(_config)

    # Get the values from config
    nyuu_bin = config_data.nyuu
    parpar_bin = config_data.parpar
    priv_conf = config_data.nyuu_config_private
    pub_conf = config_data.nyuu_config_public or priv_conf
    nzb_out = config_data.nzb_output_path
    parpar_args = config_data.parpar_args
    related_exts = config_data.related_extensions

    appdata_dir = config_data.appdata_dir_path
    appdata_dir.mkdir(parents=True, exist_ok=True)
    resume_file = appdata_dir / "juicenet.resume"
    resume_file.touch(exist_ok=True)

    if config_data.use_temp_dir:
        work_dir = config_data.temp_dir_path
    else:
        work_dir = None

    # Decide which config file to use
    configurations = {"public": pub_conf, "private": priv_conf}
    scope = "public" if public else "private"
    conf = configurations[scope]

    # Check and get `dump-failed-posts` as defined in Nyuu config
    dump = get_dump_failed_posts(conf)
    raw_articles = get_glob_matches(dump, ["*"])
    raw_count = len(raw_articles)

    # Initialize Resume class
    no_resume = not resume
    _resume = Resume(resume_file, scope, no_resume)

    # Initialize ParPar class for generating par2 files ahead
    parpar = ParPar(parpar_bin, parpar_args, work_dir, debug)

    # Force disable BDMV naming for file input
    if file.is_file():
        bdmv_naming = False

    # Initialize Nyuu class for uploading stuff ahead
    nyuu = Nyuu(file.parent.parent, nyuu_bin, conf, work_dir, nzb_out, scope, debug, bdmv_naming)

    if raw_count and (not skip_raw):
        rawoutput = {}
        for article in raw_articles:
            raw_out = nyuu.repost_raw(article=article)
            rawoutput[article] = raw_out

    else:
        rawoutput = {}

    if _resume.already_uploaded(file):
        return JuiceBox(
            nyuu=NyuuOutput(nzb=None, success=False, args=[], returncode=1, stdout="", stderr=""),
            parpar=ParParOutput(
                par2files=[],
                success=False,
                filepathbase=file.parent,
                filepathformat="basename" if file.is_file() else "path",
                args=[],
                returncode=1,
                stdout="",
                stderr="",
            ),
            raw={},
            skipped=True,
        )
    else:
        related_files = None

        if file.is_file():
            related_files = get_related_files(file, exts=related_exts)

        parpar_out = parpar.generate_par2_files(file, related_files=related_files)
        nyuu_out = nyuu.upload(file=file, related_files=related_files, par2files=parpar_out.par2files)

        if nyuu_out.success:
            # Only save it to resume if it was successful
            _resume.log_file_info(file)

    return JuiceBox(nyuu=nyuu_out, parpar=parpar_out, raw=rawoutput, skipped=False)

get_files

get_files(path: StrPath, /, *, exts: list[str] = ['mkv']) -> list[Path]

Get a list of files with specified extensions from the given path. This is recursive.

Parameters:

Name Type Description Default
path str or Path

The path to an existing file. This can either be a string representing the path or a pathlib.Path object.

required
exts list[str]

List of file extensions to match. Default will match all mkv files in base path.

['mkv']

Returns:

Type Description
list[Path]

A list of Path objects representing the files with the specified extensions.

Raises:

Type Description
JuicenetInputError

Invalid input.

Examples:

>>> get_files(Path('/path/to/directory'))
[PosixPath('/path/to/directory/Big Buck Bunny S01E01.mkv'), PosixPath('/path/to/directory/Big Buck Bunny S01E02.mkv')]
>>> get_files(Path('/path/to/directory'), ['txt', 'csv'])
[PosixPath('/path/to/directory/file1.txt'), PosixPath('/path/to/directory/file2.csv')]
Source code in src/juicenet/api/utils.py
def get_files(path: StrPath, /, *, exts: list[str] = ["mkv"]) -> list[Path]:
    """
    Get a list of files with specified extensions from the given path. This is recursive.

    Parameters
    ----------
    path : str or pathlib.Path
        The path to an existing file. This can either be a string representing the path or a pathlib.Path object.
    exts : list[str], optional
        List of file extensions to match. Default will match all `mkv` files in base path.

    Returns
    -------
    list[Path]
        A list of Path objects representing the files with the specified extensions.

    Raises
    ------
    JuicenetInputError
        Invalid input.

    Examples
    --------
    >>> get_files(Path('/path/to/directory'))
    [PosixPath('/path/to/directory/Big Buck Bunny S01E01.mkv'), PosixPath('/path/to/directory/Big Buck Bunny S01E02.mkv')]

    >>> get_files(Path('/path/to/directory'), ['txt', 'csv'])
    [PosixPath('/path/to/directory/file1.txt'), PosixPath('/path/to/directory/file2.csv')]
    """

    if isinstance(path, str):
        basepath = Path(path).resolve()
    elif isinstance(path, Path):
        basepath = path.resolve()
    else:
        raise JuicenetInputError(f"{path} must be a string or pathlib.Path")

    if not basepath.is_dir():
        raise JuicenetInputError(f"{basepath} must be an directory")

    files = []

    for ext in exts:
        matches = list(basepath.rglob(f"*.{ext.strip('.')}"))
        files.extend(matches)

    return files

get_glob_matches

get_glob_matches(path: Path, /, *, globs: list[str] = ['*.mkv']) -> list[Path]

Get a list of files which match at least one of the given glob patterns in the specified path.

Parameters:

Name Type Description Default
path Path or str

The path to an existing directory. This can either be a string representing the path or a pathlib.Path object.

required
globs list[str]

List of glob patterns to match. Default will match all mkv files in the base path.

['*.mkv']

Returns:

Type Description
list[Path]

A list of Path objects representing the files matching the given glob patterns.

Raises:

Type Description
JuicenetInputError

Invalid input.

Examples:

>>> get_glob_matches(Path('/path/to/directory'))
[PosixPath('/path/to/directory/Big Buck Bunny S01E01.mkv'), PosixPath('/path/to/directory/Big Buck Bunny S01E02.mkv')]
>>> get_glob_matches(Path('/path/to/directory'), globs=['*.txt', '*.csv'])
[PosixPath('/path/to/directory/file1.txt'), PosixPath('/path/to/directory/file2.csv')]
Source code in src/juicenet/api/utils.py
def get_glob_matches(path: Path, /, *, globs: list[str] = ["*.mkv"]) -> list[Path]:
    """
    Get a list of files which match at least one of the given glob patterns in the specified path.

    Parameters
    ----------
    path : Path or str
        The path to an existing directory. This can either be a string representing the path or a pathlib.Path object.
    globs : list[str], optional
        List of glob patterns to match. Default will match all `mkv` files in the base path.

    Returns
    -------
    list[Path]
        A list of Path objects representing the files matching the given glob patterns.

    Raises
    ------
    JuicenetInputError
        Invalid input.

    Examples
    --------
    >>> get_glob_matches(Path('/path/to/directory'))
    [PosixPath('/path/to/directory/Big Buck Bunny S01E01.mkv'), PosixPath('/path/to/directory/Big Buck Bunny S01E02.mkv')]

    >>> get_glob_matches(Path('/path/to/directory'), globs=['*.txt', '*.csv'])
    [PosixPath('/path/to/directory/file1.txt'), PosixPath('/path/to/directory/file2.csv')]
    """

    if isinstance(path, str):
        basepath = Path(path).resolve()
    elif isinstance(path, Path):
        basepath = path.resolve()
    else:
        raise JuicenetInputError(f"{path} must be a string or pathlib.Path")

    if not basepath.is_dir():
        raise JuicenetInputError(f"{basepath} must be an directory")

    files = []

    for glob in globs:
        matches = list(basepath.glob(glob))
        files.extend(matches)

    return files

get_bdmv_discs

get_bdmv_discs(path: StrPath, globs: list[str] = ['*/']) -> list[Path]

Finds individual discs in BDMVs by looking for BDMV/index.bdmv.

Parameters:

Name Type Description Default
path str or Path

The path to an existing file. This can either be a string representing the path or a pathlib.Path object.

required
globs list[str]

List of glob patterns to match. Default will match all sub folders in base path.

['*/']

Returns:

Type Description
list[Path]

List of paths where each path is a BDMV disc.

Raises:

Type Description
JuicenetInputError

Invalid input.

Notes

The choice to use BDMV/index.bdmv is arbitrary, I just needed something unique enough.

There's two aspects to it, if the BDMV has multiple BDMV/index.bdmv files it means it's got multiple discs and each disc will be returned seperately and if there's only one BDMV/index.bdmv then return the folder as is because it's likely a movie BDMV

A typical BDMV might look like this:

[BDMV] Big Buck Bunny [US]
├──  Big Buck Bunny [Vol.1]
│   └── DISC_01
│       └── BDMV
│           ├── BACKUP
│           ├── CLIPINF
│           ├── META
│           ├── PLAYLIST
│           ├── index.bdmv
│           └── MovieObject.bdmv
└── Big Buck Bunny [Vol.2]
    └── DISC_01
        └── BDMV
            ├── BACKUP
            ├── CLIPINF
            ├── META
            ├── PLAYLIST
            ├── index.bdmv
            └── MovieObject.bdmv
From the above example, this function finds the BDMV/index.bdmv file and then goes 1 directory up relative to BDMV/index.bdmv which ends up being DISC_01

Something like:

  • Found:

    • Big Buck Bunny [Vol.1]/DISC_01/BDMV/index.bdmv
    • Big Buck Bunny [Vol.2]/DISC_01/BDMV/index.bdmv
  • Returns:

    • Big Buck Bunny [Vol.1]/DISC_01
    • Big Buck Bunny [Vol.2]/DISC_01

Examples:

from pathlib import Path

from juicenet import get_bdmv_discs

folder = Path("C:/Users/raven/BDMVs")

bdmvs = get_bdmv_discs(folder)

print(bdmvs)
# [
#   WindowsPath('C:/Users/raven/BDMVs/[BDMV] Big Buck Bunny [US]/Big Buck Bunny [Vol.1]/DISC_01'),
#   WindowsPath('C:/Users/raven/BDMVs/[BDMV] Big Buck Bunny [US]/Big Buck Bunny [Vol.2]/DISC_01'),
#   WindowsPath('C:/Users/raven/BDMVs/[BDMV] Big Buck Bunny [US]/Big Buck Bunny [Vol.3]/DISC_01'),
#   WindowsPath('C:/Users/raven/BDMVs/[BDMV] Big Buck Bunny [US]/Big Buck Bunny [Vol.4]/DISC_01'),
#   WindowsPath('C:/Users/raven/BDMVs/[BDMV] Big Buck Bunny [US]/Big Buck Bunny [Vol.5]/DISC_01'),
#   WindowsPath('C:/Users/raven/BDMVs/[BDMV] Big Buck Bunny [US]/Big Buck Bunny [Vol.6]/DISC_01'),
# ]
Source code in src/juicenet/api/utils.py
def get_bdmv_discs(path: StrPath, globs: list[str] = ["*/"]) -> list[Path]:
    """
    Finds individual discs in BDMVs by looking for `BDMV/index.bdmv`.

    Parameters
    ----------
    path : str or pathlib.Path
        The path to an existing file. This can either be a string representing the path or a pathlib.Path object.
    globs : list[str], optional
        List of glob patterns to match. Default will match all sub folders in base path.

    Returns
    -------
    list[Path]
        List of paths where each path is a BDMV disc.

    Raises
    ------
    JuicenetInputError
        Invalid input.

    Notes
    -----
    The choice to use `BDMV/index.bdmv` is arbitrary,
    I just needed something unique enough.

    There's two aspects to it, if the BDMV has multiple `BDMV/index.bdmv` files
    it means it's got multiple discs and each disc will be returned seperately
    and if there's only one `BDMV/index.bdmv` then return the folder as is
    because it's likely a movie BDMV

    A typical BDMV might look like this:

    ```
    [BDMV] Big Buck Bunny [US]
    ├──  Big Buck Bunny [Vol.1]
    │   └── DISC_01
    │       └── BDMV
    │           ├── BACKUP
    │           ├── CLIPINF
    │           ├── META
    │           ├── PLAYLIST
    │           ├── index.bdmv
    │           └── MovieObject.bdmv
    └── Big Buck Bunny [Vol.2]
        └── DISC_01
            └── BDMV
                ├── BACKUP
                ├── CLIPINF
                ├── META
                ├── PLAYLIST
                ├── index.bdmv
                └── MovieObject.bdmv
    ```
    From the above example, this function finds the
    `BDMV/index.bdmv` file and then goes 1 directory up
    relative to `BDMV/index.bdmv` which ends up being `DISC_01`

    Something like:

    - Found:
        - `Big Buck Bunny [Vol.1]/DISC_01/BDMV/index.bdmv`
        - `Big Buck Bunny [Vol.2]/DISC_01/BDMV/index.bdmv`

    - Returns:
        - `Big Buck Bunny [Vol.1]/DISC_01`
        - `Big Buck Bunny [Vol.2]/DISC_01`

    Examples
    --------
    ```python
    from pathlib import Path

    from juicenet import get_bdmv_discs

    folder = Path("C:/Users/raven/BDMVs")

    bdmvs = get_bdmv_discs(folder)

    print(bdmvs)
    # [
    #   WindowsPath('C:/Users/raven/BDMVs/[BDMV] Big Buck Bunny [US]/Big Buck Bunny [Vol.1]/DISC_01'),
    #   WindowsPath('C:/Users/raven/BDMVs/[BDMV] Big Buck Bunny [US]/Big Buck Bunny [Vol.2]/DISC_01'),
    #   WindowsPath('C:/Users/raven/BDMVs/[BDMV] Big Buck Bunny [US]/Big Buck Bunny [Vol.3]/DISC_01'),
    #   WindowsPath('C:/Users/raven/BDMVs/[BDMV] Big Buck Bunny [US]/Big Buck Bunny [Vol.4]/DISC_01'),
    #   WindowsPath('C:/Users/raven/BDMVs/[BDMV] Big Buck Bunny [US]/Big Buck Bunny [Vol.5]/DISC_01'),
    #   WindowsPath('C:/Users/raven/BDMVs/[BDMV] Big Buck Bunny [US]/Big Buck Bunny [Vol.6]/DISC_01'),
    # ]
    ```
    """

    if isinstance(path, str):
        basepath = Path(path).resolve()
    elif isinstance(path, Path):
        basepath = path.resolve()
    else:
        raise JuicenetInputError(f"{path} must be a string or pathlib.Path")

    if not basepath.exists():
        raise JuicenetInputError(f"{basepath} must be an existing file or directory")

    bdmvs = []

    folders = get_glob_matches(basepath, globs=globs)

    for folder in folders:
        if folder.is_dir():
            index = list(folder.rglob("BDMV/index.bdmv"))
            if len(index) == 1:
                bdmvs.append(folder.resolve())
            else:
                for file in index:
                    bdmvs.append(file.parents[1].resolve())

    return bdmvs

JuicenetConfig

Bases: BaseModel

Pydantic model for setting defaults and validating Juicenet's config

Attributes:

Name Type Description
parpar FilePath

The path to the ParPar executable

nyuu FilePath

The path to the Nyuu executable

nyuu_config_private FilePath

The path to the private Nyuu configuration file

nzb_output_path DirectoryPath

The path where output NZBs will be saved

nyuu_config_public (FilePath, optional)

The path to the public Nyuu configuration file

extensions (list[str], optional)

The list of file extensions to be processed. Default is ["mkv"]

related_extensions (list[str], optional)

The list of file extensions associated with an input file. For example, if you have a file named Big Buck Bunny The Movie (2023).mkv, another file named Big Buck Bunny The Movie (2023).srt is considered related. Default is ["*"]

parpar_args (list[str], optional)

The arguments to be passed to the ParPar executable Ddefault is ["--overwrite", "-s700k", "--slice-size-multiple=700K", "--max-input-slices=4000", "-r1n*1.2", "-R"]

use_temp_dir (bool, optional)

Whether or not to use a temporary directory for processing. Default is True

temp_dir_path (DirectoryPath, optional)

Path to a specific temporary directory if use_temp_dir is True. If unspecified, it uses %Temp% or /tmp

appdata_dir_path (Path, optional)

The path to the folder where Juicenet will store its data. Default is ~/.juicenet

Source code in src/juicenet/model.py
class JuicenetConfig(BaseModel):
    """
    Pydantic model for setting defaults and validating Juicenet's config

    Attributes
    ----------
    parpar : FilePath
        The path to the ParPar executable
    nyuu : FilePath
        The path to the Nyuu executable
    nyuu_config_private : FilePath
        The path to the private Nyuu configuration file
    nzb_output_path : DirectoryPath
        The path where output NZBs will be saved
    nyuu_config_public : FilePath, optional
        The path to the public Nyuu configuration file
    extensions : list[str], optional
        The list of file extensions to be processed. Default is `["mkv"]`
    related_extensions : list[str], optional
        The list of file extensions associated with an input file.
        For example, if you have a file named `Big Buck Bunny The Movie (2023).mkv`,
        another file named `Big Buck Bunny The Movie (2023).srt` is considered related.
        Default is `["*"]`
    parpar_args : list[str], optional
        The arguments to be passed to the ParPar executable
        Ddefault is `["--overwrite", "-s700k", "--slice-size-multiple=700K", "--max-input-slices=4000", "-r1n*1.2", "-R"]`
    use_temp_dir : bool, optional
        Whether or not to use a temporary directory for processing. Default is `True`
    temp_dir_path : DirectoryPath, optional
        Path to a specific temporary directory if `use_temp_dir` is `True`. If unspecified, it uses `%Temp%` or `/tmp`
    appdata_dir_path : Path, optional
        The path to the folder where Juicenet will store its data. Default is `~/.juicenet`
    """

    parpar: Annotated[FilePath, Field(validate_default=True)] = which("parpar") # type: ignore
    """The path to the ParPar executable"""

    nyuu: Annotated[FilePath, Field(validate_default=True)] = which("nyuu") # type: ignore
    """The path to the Nyuu executable"""

    nyuu_config_private: FilePath
    """The path to the private Nyuu configuration file"""

    nzb_output_path: DirectoryPath
    """The path where output NZBs will be saved"""

    nyuu_config_public: Optional[FilePath] = None
    """The path to the public Nyuu configuration file"""

    extensions: list[str] = ["mkv"]
    """The list of file extensions to be processed"""

    related_extensions: list[str] = ["ass", "srt"]
    """
    The list of file extensions associated with an input file. 
    For example, if you have a file named `Big Buck Bunny The Movie (2023).mkv`, 
    another file named `Big Buck Bunny The Movie (2023).srt` is considered related
    """

    parpar_args: list[str] = ["--overwrite", "-s700k", "--slice-size-multiple=700K", "--max-input-slices=4000", "-r1n*1.2", "-R"]
    """The arguments to be passed to the ParPar executable"""

    use_temp_dir: bool = True
    """Whether or not to use a temporary directory for processing"""

    temp_dir_path: DirectoryPath = Path(TemporaryDirectory(prefix=".JUICENET_").name).resolve()
    """Path to a specific temporary directory if use_temp_dir is True. If unspecified, it uses %Temp% or /tmp"""

    appdata_dir_path: Path = Path.home() / ".juicenet"
    """The path to the folder where juicenet will store it's data"""

    @field_validator("parpar", "nyuu", "nyuu_config_private", "nzb_output_path", "nyuu_config_public", "temp_dir_path", "appdata_dir_path")
    @classmethod
    def resolve_path(cls, path: Path) -> Path:
            """Resolve all given Path fields"""
            return path.expanduser().resolve()

JuiceBox dataclass

A class used to represent the output of juicenet. Each attribute in this class is an instance of the corresponding output class (NyuuOutput, ParParOutput, RawOutput) and captures the output of the respective subprocess.

Attributes:

Name Type Description
nyuu NyuuOutput

NyuuOutput object for the processed file.

parpar ParParOutput

ParParOutput object for the processed file.

raw dict[ArticleFilePath, RawOutput]

Dictionary where each key is an article and the value is RawOutput object. Empty if no articles were processed.

skipped bool

True if the upload process was skipped because the file was already uploaded

Source code in src/juicenet/types.py
@dataclass(order=True)
class JuiceBox:
    """
    A class used to represent the output of juicenet.
    Each attribute in this class is an instance of the corresponding output class (`NyuuOutput`, `ParParOutput`, `RawOutput`) and
    captures the output of the respective subprocess.

    Attributes
    ----------
    nyuu : NyuuOutput
        `NyuuOutput` object for the processed file.
    parpar : ParParOutput
        `ParParOutput` object for the processed file.
    raw: dict[ArticleFilePath, RawOutput]
        Dictionary where each key is an article and the value is `RawOutput` object.
        Empty if no articles were processed.
    skipped: bool
        True if the upload process was skipped because the file was already uploaded
    """

    nyuu: NyuuOutput
    """`NyuuOutput` object for the processed file."""

    parpar: ParParOutput
    """`ParParOutput` object for the processed file."""

    raw: dict[ArticleFilePath, RawOutput]
    """`RawOutput` object for any processed articles or `None` if not available."""

    skipped: bool
    """True if the upload process was skipped because the file was already uploaded"""

NyuuOutput dataclass

A class used to represent the output of Nyuu.

Attributes:

Name Type Description
nzb (NZBFilePath, optional)

Absolute pathlib.Path to the resulting NZB file or None if the upload failed.

success bool

True if Nyuu exited successfully with code 0 or 32, False otherwise.

args list[str]

List of arguments passed to Nyuu.

returncode int

Nyuu's exit code.

stdout str

Nyuu's stdout.

stderr str

Nyuu's stderr.

Notes

Nyuu exits with a code 0 if the process completes successfully or 32 if the process completes successfully after skipping the skippable errors.

Refer to Nyuu's help-full.txt for more details.

Source code in src/juicenet/types.py
@dataclass(order=True)
class NyuuOutput:
    """
    A class used to represent the output of Nyuu.

    Attributes
    ----------
    nzb : NZBFilePath, optional
        Absolute pathlib.Path to the resulting `NZB` file or `None` if the upload failed.
    success : bool
        `True` if Nyuu exited successfully with code 0 or 32, `False` otherwise.
    args : list[str]
        List of arguments passed to Nyuu.
    returncode : int
        Nyuu's exit code.
    stdout : str
        Nyuu's stdout.
    stderr : str
        Nyuu's stderr.

    Notes
    -----
    Nyuu exits with a code 0 if the process completes successfully or 32 if the
    process completes successfully after skipping the skippable errors.

    Refer to Nyuu's [`help-full.txt`](https://github.com/animetosho/Nyuu/blob/master/help-full.txt#L204-L228)
    for more details.
    """

    nzb: Optional[NZBFilePath]
    """Absolute pathlib.Path to the resulting `NZB` file or `None` if the upload failed."""

    success: bool
    """`True` if Nyuu exited successfully with code 0 or 32, `False` otherwise."""

    args: list[str]
    """List of arguments passed to Nyuu."""

    returncode: int
    """Nyuu's exit code."""

    stdout: str
    """Nyuu's stdout."""

    stderr: str
    """Nyuu's stderr."""

ParParOutput dataclass

A class used to represent the output of ParPar.

Attributes:

Name Type Description
par2files list[PAR2FilePath]

List of absolute pathlib.Path objects pointing to the generated PAR2 files.

filepathformat Literal['basename', 'path']

The --filepath-format used to generate the PAR2 files.

filepathbase Path

The --filepath-base used to generate the PAR2 files.

success bool

True if ParPar exited successfully with code 0, False otherwise.

args list[str]

List of arguments passed to ParPar.

returncode int

ParPar's exit code.

stdout str

ParPar's stdout.

stderr str

ParPar's stderr.

Source code in src/juicenet/types.py
@dataclass(order=True)
class ParParOutput:
    """
    A class used to represent the output of ParPar.

    Attributes
    ----------
    par2files : list[PAR2FilePath]
        List of absolute pathlib.Path objects pointing to the generated `PAR2` files.
    filepathformat : Literal["basename", "path"]
        The `--filepath-format` used to generate the `PAR2` files.
    filepathbase : Path
        The `--filepath-base` used to generate the `PAR2` files.
    success : bool
        `True` if ParPar exited successfully with code 0, `False` otherwise.
    args : list[str]
        List of arguments passed to ParPar.
    returncode : int
        ParPar's exit code.
    stdout : str
        ParPar's stdout.
    stderr : str
        ParPar's stderr.
    """

    par2files: list[PAR2FilePath]
    """List of absolute pathlib.Path objects pointing to the generated `PAR2` files."""

    filepathformat: Literal["basename", "path"]
    """The `--filepath-format` used to generate the `PAR2` files."""

    filepathbase: Path
    """The `--filepath-base` used to generate the `PAR2` files."""

    success: bool
    """`True` if ParPar exited successfully with code 0, `False` otherwise."""

    args: list[str]
    """List of arguments passed to ParPar."""

    returncode: int
    """ParPar's exit code."""

    stdout: str
    """ParPar's stdout."""

    stderr: str
    """ParPar's stderr."""

RawOutput dataclass

A class used to represent the output of Nyuu's raw article upload process.

Attributes:

Name Type Description
article ArticleFilePath

Absolute pathlib.Path to the raw article.

success bool

True if Nyuu exited successfully with code 0 or 32, False otherwise.

args list[str]

List of arguments passed to Nyuu.

returncode int

Nyuu's exit code.

stdout str

Nyuu's stdout.

stderr str

Nyuu's stderr.

Notes

Nyuu exits with a code 0 if the process completes successfully or 32 if the process completes successfully after skipping the skippable errors.

Refer to Nyuu's help-full.txt for more details.

Source code in src/juicenet/types.py
@dataclass(order=True)
class RawOutput:
    """
    A class used to represent the output of Nyuu's raw article upload process.

    Attributes
    ----------
    article : ArticleFilePath
        Absolute pathlib.Path to the raw article.
    success : bool
        `True` if Nyuu exited successfully with code 0 or 32, `False` otherwise.
    args : list[str]
        List of arguments passed to Nyuu.
    returncode : int
        Nyuu's exit code.
    stdout : str
        Nyuu's stdout.
    stderr : str
        Nyuu's stderr.

    Notes
    -----
    Nyuu exits with a code 0 if the process completes successfully or 32 if the
    process completes successfully after skipping the skippable errors.

    Refer to Nyuu's [`help-full.txt`](https://github.com/animetosho/Nyuu/blob/master/help-full.txt#L204-L228)
    for more details.
    """

    article: ArticleFilePath
    """Absolute pathlib.Path to the raw article."""

    success: bool
    """`True` if Nyuu exited successfully with code 0 or 32, `False` otherwise."""

    args: list[str]
    """List of arguments passed to Nyuu."""

    returncode: int
    """Nyuu's exit code."""

    stdout: str
    """Nyuu's stdout."""

    stderr: str
    """Nyuu's stderr."""