Skip to content

Meta Editor

NzbMetaEditor

NzbMetaEditor(nzb: str)

Create an NzbMetaEditor instance.

Parameters:

Name Type Description Default
nzb str

NZB content as a string.

required

Raises:

Type Description
InvalidNzbError

If the string cannot be parsed as valid XML.

Note

This does not validate the given NZB.

Example
from nzb import NzbMetaEditor

text = '''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE nzb PUBLIC "-//newzBin//DTD NZB 1.1//EN" "http://www.newzbin.com/DTD/nzb/nzb-1.1.dtd">
<nzb xmlns="http://www.newzbin.com/DTD/2003/nzb">
    <head>
        <meta type="title">Big Buck Bunny - S01E01.mkv</meta>
        <meta type="password">secret</meta>
        <meta type="tag">HD</meta>
        <meta type="category">TV</meta>
    </head>
    <file poster="John &lt;nzb@nowhere.example&gt;" date="1706440708" subject="[1/1] - &quot;Big Buck Bunny - S01E01.mkv&quot; yEnc (1/2) 1478616">
        <groups>
            <group>alt.binaries.boneless</group>
        </groups>
        <segments>
            <segment bytes="739067" number="1">9cacde4c986547369becbf97003fb2c5-9483514693959@example</segment>
            <segment bytes="739549" number="2">70a3a038ce324e618e2751e063d6a036-7285710986748@example</segment>
        </segments>
    </file>
</nzb>
'''

editor = NzbMetaEditor(text)
edited = editor.set(title="Big Buck Bunny").append(tags="1080p").to_str()
print(edited)
Source code in src/nzb/_core.py
def __init__(self, nzb: str, /) -> None:
    """
    Create an NzbMetaEditor instance.

    Parameters
    ----------
    nzb : str
        NZB content as a string.

    Raises
    ------
    InvalidNzbError
        If the string cannot be parsed as valid XML.

    Note
    ----
    This does not validate the given NZB.

    Example
    -------
    ```python
    from nzb import NzbMetaEditor

    text = '''
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE nzb PUBLIC "-//newzBin//DTD NZB 1.1//EN" "http://www.newzbin.com/DTD/nzb/nzb-1.1.dtd">
    <nzb xmlns="http://www.newzbin.com/DTD/2003/nzb">
        <head>
            <meta type="title">Big Buck Bunny - S01E01.mkv</meta>
            <meta type="password">secret</meta>
            <meta type="tag">HD</meta>
            <meta type="category">TV</meta>
        </head>
        <file poster="John &lt;nzb@nowhere.example&gt;" date="1706440708" subject="[1/1] - &quot;Big Buck Bunny - S01E01.mkv&quot; yEnc (1/2) 1478616">
            <groups>
                <group>alt.binaries.boneless</group>
            </groups>
            <segments>
                <segment bytes="739067" number="1">9cacde4c986547369becbf97003fb2c5-9483514693959@example</segment>
                <segment bytes="739549" number="2">70a3a038ce324e618e2751e063d6a036-7285710986748@example</segment>
            </segments>
        </file>
    </nzb>
    '''

    editor = NzbMetaEditor(text)
    edited = editor.set(title="Big Buck Bunny").append(tags="1080p").to_str()
    print(edited)
    ```

    """  # noqa: E501
    self._nzb = nzb
    self._tree = nzb_to_tree(self._nzb)

append

append(
    *,
    passwords: Iterable[str] | str | None = None,
    tags: Iterable[str] | str | None = None,
) -> Self

Append metadata fields to the existing metadata in the NZB.

Parameters:

Name Type Description Default
passwords Iterable[str] | str

Password(s) for the NZB file.

None
tags Iterable[str] | str

Tag(s) associated with the NZB file.

None

Returns:

Type Description
Self

Returns itself.

Source code in src/nzb/_core.py
def append(
    self,
    *,
    passwords: Iterable[str] | str | None = None,
    tags: Iterable[str] | str | None = None,
) -> Self:
    """
    Append metadata fields to the existing metadata in the NZB.

    Parameters
    ----------
    passwords : Iterable[str] | str, optional
        Password(s) for the NZB file.
    tags : Iterable[str] | str, optional
        Tag(s) associated with the NZB file.

    Returns
    -------
    Self
        Returns itself.

    """

    head = self._tree.find("./head")
    if head is None:
        head = ElementTree.Element("head")
        self._tree.insert(0, head)

    if passwords:
        for password in to_iterable(passwords):
            sub = ElementTree.SubElement(head, "meta")
            sub.set("type", "password")
            sub.text = password

    if tags:
        for tag in to_iterable(tags):
            sub = ElementTree.SubElement(head, "meta")
            sub.set("type", "tag")
            sub.text = tag

    return self.sort()

clear

clear() -> Self

Clear all metadata fields from the NZB.

Returns:

Type Description
Self

Returns itself.

Source code in src/nzb/_core.py
def clear(self) -> Self:
    """
    Clear all metadata fields from the NZB.

    Returns
    -------
    Self
        Returns itself.

    """
    if (head := self._tree.find("./head")) is not None:
        head.clear()
        self._tree.remove(head)
    return self

from_file classmethod

from_file(nzb: StrPath) -> Self

Create an NzbMetaEditor instance from an NZB file path.

Parameters:

Name Type Description Default
nzb StrPath

File path to the NZB.

required

Returns:

Type Description
Self

Returns itself.

Source code in src/nzb/_core.py
@classmethod
def from_file(cls, nzb: StrPath, /) -> Self:
    """
    Create an NzbMetaEditor instance from an NZB file path.

    Parameters
    ----------
    nzb : StrPath
        File path to the NZB.

    Returns
    -------
    Self
        Returns itself.

    """
    return cls(read_nzb_file(nzb))

remove

remove(
    key: Literal["title", "password", "tag", "category"],
) -> Self
remove(key: str) -> Self
remove(
    key: Literal["title", "password", "tag", "category"]
    | str,
) -> Self

Remove a metadata field from the NZB. If the same field is present multiple times, this will remove them all.

Parameters:

Name Type Description Default
key Literal['title', 'password', 'tag', 'category'] | str

The metadata field to remove.

required

Returns:

Type Description
Self

Returns itself.

Source code in src/nzb/_core.py
def remove(self, key: Literal["title", "password", "tag", "category"] | str) -> Self:
    """
    Remove a metadata field from the NZB.
    If the same field is present multiple times, this will remove them all.

    Parameters
    ----------
    key : Literal["title", "password", "tag", "category"] | str, optional
        The metadata field to remove.

    Returns
    -------
    Self
        Returns itself.

    """

    if (head := self._tree.find("./head")) is not None:
        matches = [meta for meta in head.findall("./meta") if meta.get("type") == key]

        for match in matches:
            head.remove(match)

    return self

set

set(
    *,
    title: str | None = None,
    passwords: Iterable[str] | str | None = None,
    tags: Iterable[str] | str | None = None,
    category: str | None = None,
) -> Self

Set metadata fields in the NZB. Provided fields are replaced entirely if they already exist. Fields that aren't provided remain unchanged.

Parameters:

Name Type Description Default
title str

The title metadata field.

None
passwords Iterable[str] | str

Password(s) for the NZB file.

None
tags Iterable[str] | str

Tag(s) associated with the NZB file.

None
category str

Category of the NZB file.

None

Returns:

Type Description
Self

Returns itself.

Source code in src/nzb/_core.py
def set(
    self,
    *,
    title: str | None = None,
    passwords: Iterable[str] | str | None = None,
    tags: Iterable[str] | str | None = None,
    category: str | None = None,
) -> Self:
    """
    Set metadata fields in the NZB.
    Provided fields are replaced entirely if they already exist.
    Fields that aren't provided remain unchanged.

    Parameters
    ----------
    title : str, optional
        The title metadata field.
    passwords : Iterable[str] | str, optional
        Password(s) for the NZB file.
    tags : Iterable[str] | str, optional
        Tag(s) associated with the NZB file.
    category : str, optional
        Category of the NZB file.

    Returns
    -------
    Self
        Returns itself.

    """

    head = self._tree.find("./head")
    if head is None:
        head = ElementTree.Element("head")
        self._tree.insert(0, head)

    if title:
        self.remove("title")
        sub = ElementTree.SubElement(head, "meta")
        sub.set("type", "title")
        sub.text = title

    if passwords:
        self.remove("password")
        for password in to_iterable(passwords):
            sub = ElementTree.SubElement(head, "meta")
            sub.set("type", "password")
            sub.text = password

    if tags:
        self.remove("tag")
        for tag in to_iterable(tags):
            sub = ElementTree.SubElement(head, "meta")
            sub.set("type", "tag")
            sub.text = tag

    if category:
        self.remove("category")
        sub = ElementTree.SubElement(head, "meta")
        sub.set("type", "category")
        sub.text = category

    return self.sort()

sort

sort(
    key: Callable[[Element], SupportsRichComparison]
    | None = None,
) -> Self

Sort the metadata fields.

Parameters:

Name Type Description Default
key Callable[[Element], SupportsRichComparison]

A callable that takes a <meta> Element and returns a comparable value.

None

Returns:

Type Description
Self

Returns itself.

Examples:

Here's an example where we sort the meta fields by their type. This is also what sort() does when you call it without a key.

editor = NzbMetaEditor(...)

def key(element: ElementTree.Element) -> int:
    if typ := element.get("type"):
        return {"title": 0, "category": 1, "password": 2, "tag": 3}.get(typ, -1)
    return -1

editor.sort(key=key)

Source code in src/nzb/_core.py
def sort(self, key: Callable[[ElementTree.Element], SupportsRichComparison] | None = None) -> Self:
    """
    Sort the metadata fields.

    Parameters
    ----------
    key : Callable[[ElementTree.Element], SupportsRichComparison], optional
        A callable that takes a `<meta>` Element and returns a comparable
        value.

    Returns
    -------
    Self
        Returns itself.

    Examples
    --------
    Here's an example where we sort the meta fields by their type.
    This is also what `sort()` does when you call it without a key.
    ```py
    editor = NzbMetaEditor(...)

    def key(element: ElementTree.Element) -> int:
        if typ := element.get("type"):
            return {"title": 0, "category": 1, "password": 2, "tag": 3}.get(typ, -1)
        return -1

    editor.sort(key=key)
    ```

    """
    if key is None:

        def key(element: ElementTree.Element) -> int:  # pragma: no cover
            if typ := element.get("type"):
                return {"title": 0, "category": 1, "password": 2, "tag": 3}.get(typ, -1)
            return -1

    if (head := self._tree.find("./head")) is not None:
        head[:] = sorted(
            head.findall("./meta"),
            key=key,
        )
    return self

to_file

to_file(
    filename: StrPath, *, overwrite: bool = False
) -> Path

Save the edited NZB to a file.

Parameters:

Name Type Description Default
filename StrPath

Destination path for saving the NZB. This will also create the path if it doesn't exist already.

required
overwrite bool

Whether to overwrite the file if it exists, defaults to False.

False

Returns:

Type Description
Path

The path to the saved file.

Raises:

Type Description
FileExistsError

If the file exists and overwrite is False.

Source code in src/nzb/_core.py
def to_file(self, filename: StrPath, *, overwrite: bool = False) -> Path:
    """
    Save the edited NZB to a file.

    Parameters
    ----------
    filename : StrPath, optional
        Destination path for saving the NZB.
        This will also create the path if it doesn't exist already.
    overwrite : bool, optional
        Whether to overwrite the file if it exists, defaults to `False`.

    Returns
    -------
    Path
        The path to the saved file.

    Raises
    ------
    FileExistsError
        If the file exists and overwrite is `False`.

    """

    outfile = realpath(filename)

    if outfile.is_file() and not overwrite:
        raise FileExistsError(outfile)

    outfile.parent.mkdir(parents=True, exist_ok=True)
    outfile.write_text(self.to_str(), encoding="utf-8")
    return outfile

to_str

to_str() -> str

Return the edited NZB as a string.

Returns:

Type Description
str

Edited NZB.

Source code in src/nzb/_core.py
def to_str(self) -> str:
    """
    Return the edited NZB as a string.

    Returns
    -------
    str
        Edited NZB.

    """
    ElementTree.indent(self._tree, space=" " * 4)
    body: str = ElementTree.tostring(
        self._tree,
        encoding="utf-8",
        xml_declaration=False,
    ).decode("utf-8")
    body = body.replace("<nzb>", '<nzb xmlns="http://www.newzbin.com/DTD/2003/nzb">')
    return generate_header(self._nzb) + body