Skip to content

# NzbGet Scripts

## Clean

Clean NZB name

- Title: `Clean.py`
- Author(s): ???

Removes the following suffixes from NZB name: NZBgeek / Obfuscated / BUYMORE / Scrambled /etc... Cleans the NZB name by removing the retagged stuff (-Obfuscated, -postbox, etc).

Script

    #!/usr/bin/env python3
    #

    ##############################################################################
    ### NZBGET SCAN SCRIPT                                                     ###

    # Clean NZB name.
    #
    # Removes the following suffixes from NZB name:
    # NZBgeek / Obfuscated / BUYMORE / Scrambled.
    #
    # NOTE: This script requires Python to be installed on your system.

    ### NZBGET SCAN SCRIPT                                                     ###
    ##############################################################################

    from __future__ import print_function
    import os, re, sys

    # Exit codes used by NZBGet
    POSTPROCESS_SUCCESS=93
    POSTPROCESS_ERROR=94
    POSTPROCESS_SKIP=95

    # Check if the script is called from NZBGet 13.0 or later
    if not 'NZBOP_SCRIPTDIR' in os.environ:
      print('*** NZBGet post-processing script ***')
      print('This script is supposed to be called from NZBGet (13.0 or later).')
      sys.exit(POSTPROCESS_ERROR)

    if not 'NZBNP_NZBNAME' in os.environ:
      print('[WARN] Filename not found in environment')
      sys.exit(POSTPROCESS_ERROR)

    fwp = os.environ['NZBNP_NZBNAME']
    fwp = re.sub('(?i)-4P\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-4Planet\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-AsRequested\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-AsRequested-xpost\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-BUYMORE\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-Chamele0n\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-GEROV\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-iNC0GNiTO\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-NZBGeek\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-Obfuscated\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-postbot\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-Rakuv\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-Scrambled\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-WhiteRev\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-xpost\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)\[eztv\]\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)\[TGx\]\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)\[TGx\]-xpost\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)\[ettv\]\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-WRTEAM\.nzb$', '.nzb', fwp)
    fwp = re.sub('(?i)-CAPTCHA\.nzb$', '.nzb', fwp)
    fwp = re.sub(r'(\-[^-.\n]*)(\-.{4})?\.nzb$', r'\1.nzb', fwp)
    if fwp:
      print('[NZB] NZBNAME=', fwp, sep='')

    sys.exit(POSTPROCESS_SUCCESS)

## HashRenamer

Renames hashed media files to match the source NZB

- Title: `HashRenamer.py`
- Author(s): l3uddz
- URL: github.com/cloudbox/cloudbox

Part of the Cloudbox project: https://cloudbox.works Built on top of the NZBGet scripts template created by Clinton Hall. Released under GNU General Public License v2.0

Script

    #!/usr/bin/env python3
    #
    ##############################################################################
    # Title:         HashRenamer.py                                              #
    # Author(s):     l3uddz, desimaniac                                          #
    # URL:           https://github.com/l3uddz/nzbgetScripts                     #
    # Description:   Renames hashed media files to match the source NZB.         #
    # --                                                                         #
    #            Part of the Cloudbox project: https://cloudbox.works            #
    ##############################################################################

    ##############################################################################
    #  Built on top of the NZBGet scripts template created by Clinton Hall       #
    #    (https://github.com/clinton-hall).                                      #
    #  Released under GNU General Public License v2.0                            #
    ##############################################################################

    ##############################################################################
    ### NZBGET POST-PROCESSING SCRIPT                                          ###

    # Rename files with hashes for file name
    #
    # NOTE: This script requires Python to be installed on your system.
    #
    ##############################################################################
    ### NZBGET POST-PROCESSING SCRIPT                                          ###
    ##############################################################################

    import os
    import re
    import shutil
    import sys

    # NZBGet Exit Codes
    NZBGET_POSTPROCESS_PARCHECK = 92
    NZBGET_POSTPROCESS_SUCCESS = 93
    NZBGET_POSTPROCESS_ERROR = 94
    NZBGET_POSTPROCESS_NONE = 95

    ############################################################
    # EXTENSION STUFF
    ############################################################

    def do_check():
        if 'NZBOP_SCRIPTDIR' not in os.environ:
            print("This script can only be called from NZBGet (11.0 or later).")
            sys.exit(0)

        if os.environ['NZBOP_VERSION'][0:5] < '11.0':
            print("[ERROR] NZBGet Version %s is not supported. Please update NZBGet." % (str(os.environ['NZBOP_VERSION'])))
            sys.exit(0)

        print("Script triggered from NZBGet Version %s." % (str(os.environ['NZBOP_VERSION'])))

        status = 0
        if 'NZBPP_TOTALSTATUS' in os.environ:
            if not os.environ['NZBPP_TOTALSTATUS'] == 'SUCCESS':
                print("[ERROR] Download failed with status %s." % (os.environ['NZBPP_STATUS']))
                status = 1
        else:
            # Check par status
            if os.environ['NZBPP_PARSTATUS'] == '1' or os.environ['NZBPP_PARSTATUS'] == '4':
                print("[ERROR] Par-repair failed, setting status \"failed\".")
                status = 1

            # Check unpack status
            if os.environ['NZBPP_UNPACKSTATUS'] == '1':
                print("[ERROR] Unpack failed, setting status \"failed\".")
                status = 1

            if os.environ['NZBPP_UNPACKSTATUS'] == '0' and os.environ['NZBPP_PARSTATUS'] == '0':
                # Unpack was skipped due to nzb-file properties or due to errors during par-check

                if os.environ['NZBPP_HEALTH'] < 1000:
                    print("[ERROR] Download health is compromised and Par-check/repair disabled or no .par2 files found. " \
                          "Setting status \"failed\".")
                    print("[ERROR] Please check your Par-check/repair settings for future downloads.")
                    status = 1

                else:
                    print("[ERROR] Par-check/repair disabled or no .par2 files found, and Unpack not required. Health is " \
                          "ok so handle as though download successful.")
                    print("[WARNING] Please check your Par-check/repair settings for future downloads.")

        # Check if destination directory exists (important for reprocessing of history items)
        if not os.path.isdir(os.environ['NZBPP_DIRECTORY']):
            print("[ERROR] Nothing to post-process: destination directory", os.environ[
                'NZBPP_DIRECTORY'], "doesn't exist. Setting status \"failed\".")
            status = 1

        # All checks done, now launching the script.
        if status == 1:
            sys.exit(NZBGET_POSTPROCESS_NONE)

    def get_file_name(path):
        try:
            file_name = os.path.basename(path)
            extensions = re.findall(r'\.([^.]+)', file_name)
            ext = '.'.join(extensions)
            name = file_name.replace(".%s" % ext, '')
            return name, ext
        except Exception:
            pass
        return None

    def is_file_hash(file_name):
        hash_regexp = [
            r'^[a-fA-F0-9]{40}$',
            r'^[a-fA-F0-9]{32}$',
            r'^[a-f0-9]{128}$',
            r'^[a-zA-Z0-9]{42}$'
        ]
        for hash in hash_regexp:
            if re.match(hash, file_name):
                return True
        return False

    def find_files(folder, extension=None, depth=None):
        file_list = []
        start_count = folder.count(os.sep)
        for path, subdirs, files in os.walk(folder, topdown=True):
            for name in files:
                if depth and path.count(os.sep) - start_count >= depth:
                    del subdirs[:]
                    continue
                file = os.path.join(path, name)
                if not extension:
                    file_list.append(file)
                else:
                    if file.lower().endswith(extension.lower()):
                        file_list.append(file)

        return sorted(file_list, key=lambda x: x.count(os.path.sep), reverse=True)

    ############################################################
    # MAIN
    ############################################################

    # do checks
    do_check()

    # retrieve required variables
    directory = os.path.normpath(os.environ['NZBPP_DIRECTORY'])
    nzb_name = os.environ['NZBPP_NZBFILENAME']
    if nzb_name is None:
        print("[ERROR] Unable to retrieve NZBPP_NZBFILENAME")
        sys.exit(NZBGET_POSTPROCESS_ERROR)
    nzb_name = nzb_name.replace('.nzb', '')

    print(("[INFO] Using \"%s\" for hashed filenames" % nzb_name))
    print(("[INFO] Scanning \"%s\" for hashed filenames" % directory))

    # scan for files
    found_files = find_files(directory)
    if not found_files:
        print(("[INFO] No files were found in \"%s\"" % directory))
        sys.exit(NZBGET_POSTPROCESS_NONE)
    else:
        print(("[INFO] Found %d files to check for hashed filenames" % len(found_files)))
        # loop files checking for file hash
        moved_files = 0
        for found_file_path in found_files:
            # set variable
            dir_name = os.path.dirname(found_file_path)
            file_name, file_ext = get_file_name(found_file_path)

            # is this a file hash
            if is_file_hash(file_name):
                new_file_path = os.path.join(dir_name, "%s.%s" % (nzb_name, file_ext))
                print(("[INFO] Moving \"%s\" to \"%s\"" % (found_file_path, new_file_path)))
                try:
                    shutil.move(found_file_path, new_file_path)
                    moved_files += 1
                except Exception:
                    print(("[ERROR] Failed moving \"%s\" to \"%s\"" % (found_file_path, new_file_path)))

        print(("[INFO] Finished processing \"%s\", moved %d files" % (directory, moved_files)))

    sys.exit(NZBGET_POSTPROCESS_SUCCESS)

## WtFnZb-Renamer

Renames hashed media files to match the source NZB

- Title: `WtFnZb-Renamer.py`
- Author(s): WtFnZb
- URL: ??

NZBGET SCAN SCRIPT

Extract filenames from subjects containing [PRiVATE]-[WtFnZb]

This extensions extracts obfuscated filenames from .nzb files

Script

    #!/usr/bin/env python3
    ### NZBGET SCAN SCRIPT

    # Extract filenames from subjects containing [PRiVATE]-[WtFnZb]
    #
    # This extensions extracts obfuscated filenames from .nzb files
    # created by WtFnZb.
    #
    # Supported subject formats:
    #
    # - [PRiVATE]-[WtFnZb]-[filename]-[1/5] - "" yEnc 0 (1/1)"
    #
    # - [PRiVATE]-[WtFnZb]-[5]-[1/filename] - "" yEnc
    #
    #
    # NOTE: Requires Python and lxml (sudo apt install python3-lxml python-lxml)
    #

    ### NZBGET SCAN SCRIPT

    import sys
    import os
    import re

    # Exit codes used by NZBGet
    POSTPROCESS_SUCCESS = 93
    POSTPROCESS_NONE = 95
    POSTPROCESS_ERROR = 94

    try:
        from lxml import etree
    except ImportError:
        print(u'[ERROR] Python lxml required. Please install with "sudo apt install python-lxml" or "pip install lxml".')
        sys.exit(POSTPROCESS_ERROR)

    patterns = (
        re.compile(r'^(?P<prefix>.*\[PRiVATE\]-\[WtFnZb\]-)'
                   r'\[(?P<total>\d+)\]-\[(?P<segment>\d+)\/(?P<filename>.{3,}?)\]'
                   r'\s+-\s+""\s+yEnc\s+',
                   re.MULTILINE | re.UNICODE),
        re.compile(r'^(?P<prefix>.*\[PRiVATE\]-\[WtFnZb\]-)'
                   r'\[(?P<filename>.{3,}?)\]-\[(?P<segment>\d+)/(?P<total>\d+)\]'
                   r'\s+-\s+""\s+yEnc\s+',
                   re.MULTILINE | re.UNICODE))

    nzb_dir = os.getenv('NZBNP_DIRECTORY')
    nzb_filename = os.getenv('NZBNP_FILENAME')
    nzb_name = os.getenv('NZBNP_NZBNAME')
    nzb_file_naming = os.getenv('NZBOP_FILENAMING')

    if nzb_dir is None or nzb_filename is None or nzb_name is None:
        print('Please run as NZBGet plugin')
        sys.exit(POSTPROCESS_ERROR)

    if nzb_file_naming is not None and nzb_file_naming.lower() != 'nzb':
        print(u'[ERROR] NZBGet setting FileNaming (under Download Queue) '
              u'must be set to "Nzb" for this extension to work correctly, exiting.')
        sys.exit(POSTPROCESS_ERROR)

    if not os.path.exists(nzb_dir):
        print('[ERROR] NZB directory doesn\'t exist, exiting')
        sys.exit(POSTPROCESS_ERROR)

    if not nzb_filename.lower().endswith('.nzb'):
        print(u'[ERROR] {} is not a .nzb file.'.format(nzb_filename))
        sys.exit(POSTPROCESS_ERROR)

    nzb = os.path.join(nzb_dir, nzb_filename)
    if not os.path.exists(nzb):
        print('[ERROR] {nzb} doesn\'t exist, exiting'.format(nzb=nzb))
        sys.exit(POSTPROCESS_ERROR)

    with open(nzb, mode='rb') as infile:
        tree = etree.parse(infile)

    changed = False
    file_count = 0
    totals = set()
    filenames = set()

    for f in tree.getiterator('{http://www.newzbin.com/DTD/2003/nzb}file'):
        subject = f.get('subject')
        if subject is None:
            print(u'[DETAIL] No subject in <file>, skipping')
            continue
        file_count += 1
        result = [re.match(pattern, subject) for pattern in patterns]
        matched = [m for m in result if m is not None]
        if len(matched) == 0:
            print(u'[INFO] No pattern matching subject, exiting.')
            sys.exit(POSTPROCESS_NONE)
        elif len(matched) > 1:
            print(u'[ERROR] Multiple patterns matched, exiting.')
            sys.exit(POSTPROCESS_ERROR)
        else:
            match = matched[0].groupdict()

        if match['filename'].lower().endswith('.par2'):
            print(u'[INFO] par2 exists, exiting')
            sys.exit(POSTPROCESS_NONE)

        if int(match['segment']) > int(match['total']):
            print(u'[DETAIL] Segment index is greater then total, skipping')
            continue

        # NZBGet subject parsing changes when duplicate filenames are present
        # prefix duplicates to avoid that
        if match['filename'] in filenames:
            match['filename'] = u'{}.{}'.format(file_count, match['filename'])

        filenames.add(match['filename'])

        s = u'WtFnZb "{filename}" yEnc ({segment}/{total})'.format(
            filename = match['filename'],
            segment = match['segment'],
            total = match['total'])

        print(u'[INFO] New subject {subject}'.format(subject=s.encode('ascii', 'ignore')))
        f.set('subject', s)
        changed = True
        totals.add(int(match['total']))

    if not changed:
        print(u'[WARNING] No subject changed, exiting.')
        sys.exit(POSTPROCESS_NONE)

    if len(totals) != 1:
        print(u'[WARNING] Mixed values for number of total segments, exiting.')
        sys.exit(POSTPROCESS_NONE)

    if totals.pop() != file_count:
        print(u'[WARNING] Listed segment count does not match <file> count, exiting.')
        sys.exit(POSTPROCESS_NONE)

    org = u'{}.wtfnzb.original.processed'.format(nzb)
    exists_counter = 0
    while os.path.exists(org):
        exists_counter += 1
        org = u'{}.{}.wtfnzb.original.processed'.format(nzb, exists_counter)

    print(u'[INFO] Preserving original nzb as {}'.format(org))
    os.rename(nzb, org)

    print(u'[INFO] Writing {}'.format(nzb))
    with open(nzb, mode='wb') as outfile:
        outfile.write(etree.tostring(tree,
            xml_declaration=True,
            encoding=tree.docinfo.encoding,
            doctype=tree.docinfo.doctype))

    sys.exit(POSTPROCESS_SUCCESS)

Tip

This script doesn't always work and is often needed if you use a certain indexer.

It might be better to use the following Sonarr Regex in your release profile

su season pack issue

Last update: May 31, 2021 19:50:32
Back to top