From f8e525abe7dd5ce17074a71199d0e0d987063232 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Wed, 22 Feb 2017 21:53:58 +1030 Subject: [PATCH] add Radarr handling. #1170 --- TorrentToMedia.py | 8 ++-- autoProcessMedia.cfg.spec | 30 ++++++++++++++- core/autoProcess/autoProcessMovie.py | 57 ++++++++++++++++++++-------- core/nzbToMediaConfig.py | 31 +++++++++++++-- nzbToMedia.py | 51 +++++++++++++++++++++++-- nzbToNzbDrone.py | 2 +- 6 files changed, 151 insertions(+), 28 deletions(-) diff --git a/TorrentToMedia.py b/TorrentToMedia.py index 514575f1..2202922a 100755 --- a/TorrentToMedia.py +++ b/TorrentToMedia.py @@ -200,7 +200,7 @@ def processTorrent(inputDirectory, inputName, inputCategory, inputHash, inputID, core.flatten(outputDestination) # Now check if video files exist in destination: - if sectionName in ["SickBeard", "NzbDrone", "CouchPotato"]: + if sectionName in ["SickBeard", "NzbDrone", "Sonarr", "CouchPotato", "Radarr"]: numVideos = len( core.listMediaFiles(outputDestination, media=True, audio=False, meta=False, archives=False)) if numVideos > 0: @@ -214,7 +214,7 @@ def processTorrent(inputDirectory, inputName, inputCategory, inputHash, inputID, # Only these sections can handling failed downloads # so make sure everything else gets through without the check for failed - if sectionName not in ['CouchPotato', 'SickBeard', 'NzbDrone']: + if sectionName not in ['CouchPotato', 'Radarr', 'SickBeard', 'NzbDrone', 'Sonarr']: status = 0 logger.info("Calling {0}:{1} to post-process:{2}".format(sectionName, usercat, inputName)) @@ -226,10 +226,10 @@ def processTorrent(inputDirectory, inputName, inputCategory, inputHash, inputID, if sectionName == 'UserScript': result = external_script(outputDestination, inputName, inputCategory, section) - elif sectionName == 'CouchPotato': + elif sectionName in ['CouchPotato', 'Radarr']: result = core.autoProcessMovie().process(sectionName, outputDestination, inputName, status, clientAgent, inputHash, inputCategory) - elif sectionName in ['SickBeard', 'NzbDrone']: + elif sectionName in ['SickBeard', 'NzbDrone', 'Sonarr']: if inputHash: inputHash = inputHash.upper() result = core.autoProcessTV().processEpisode(sectionName, outputDestination, inputName, diff --git a/autoProcessMedia.cfg.spec b/autoProcessMedia.cfg.spec index 1a07534f..42c9f4ad 100644 --- a/autoProcessMedia.cfg.spec +++ b/autoProcessMedia.cfg.spec @@ -69,6 +69,33 @@ ##### Set the recursive directory permissions to the following (0 to disable) chmodDirectory = 0 +[Radarr] + #### autoProcessing for Movies + #### raCategory - category that gets called for post-processing with Radarr + [[movie]] + enabled = 0 + apikey = + host = localhost + port = 8989 + ###### ADVANCED USE - ONLY EDIT IF YOU KNOW WHAT YOU'RE DOING ###### + web_root = + ssl = 0 + delete_failed = 0 + # Enable/Disable linking for Torrents + Torrent_NoLink = 0 + keep_archive = 1 + extract = 1 + nzbExtractionBy = Downloader + wait_for = 2 + # Set this to minimum required size to consider a media file valid (in MB) + minSize = 0 + # Enable/Disable deleting ignored files (samples and invalid media files) + delete_ignored = 0 + ##### Enable if NzbDrone is on a remote server for this category + remote_path = 0 + ##### Set to path where download client places completed downloads locally for this category + watch_dir = + [SickBeard] #### autoProcessing for TV Series #### tv - category that gets called for post-processing with SB @@ -107,8 +134,9 @@ chmodDirectory = 0 [NzbDrone] + #### Formerly known as NzbDrone this is now Sonarr #### autoProcessing for TV Series - #### ndCategory - category that gets called for post-processing with NzbDrone + #### ndCategory - category that gets called for post-processing with NzbDrone/Sonarr [[tv]] enabled = 0 apikey = diff --git a/core/autoProcess/autoProcessMovie.py b/core/autoProcess/autoProcessMovie.py index 76443155..d2789254 100644 --- a/core/autoProcess/autoProcessMovie.py +++ b/core/autoProcess/autoProcessMovie.py @@ -3,6 +3,7 @@ import os import time import requests +import json import core from core.nzbToMediaSceneExceptions import process_all_exceptions @@ -110,7 +111,10 @@ class autoProcessMovie(object): host = cfg["host"] port = cfg["port"] apikey = cfg["apikey"] - method = cfg["method"] + if section == "CouchPotato": + method = cfg["method"] + else: + method = None delete_failed = int(cfg["delete_failed"]) wait_for = int(cfg["wait_for"]) ssl = int(cfg.get("ssl", 0)) @@ -124,12 +128,17 @@ class autoProcessMovie(object): extract = int(cfg.get("extract", 0)) imdbid = find_imdbid(dirName, inputName) - baseURL = "{0}{1}:{2}{3}/api/{4}/".format(protocol, host, port, web_root, apikey) + if section == "CouchPotato": + baseURL = "{0}{1}:{2}{3}/api/{4}/".format(protocol, host, port, web_root, apikey) + if section == "Radarr": + baseURL = "{0}{1}:{2}{3}/api/command".format(protocol, host, port, web_root) + headers = {'X-Api-Key': apikey} if not apikey: - logger.info('No CouchPotato apikey entered. Performing transcoder functions only') + logger.info('No CouchPotato or Radarr apikey entered. Performing transcoder functions only') release = None elif server_responding(baseURL): - release = self.get_release(baseURL, imdbid, download_id) + if section == "CouchPotato": + release = self.get_release(baseURL, imdbid, download_id) else: logger.error("Server did not respond. Exiting", section) return [1, "{0}: Failed to post-process - {1} did not respond.".format(section, section)] @@ -225,20 +234,27 @@ class autoProcessMovie(object): params['media_folder'] = remoteDir(dirName) if remote_path else dirName - if method == "manage": - command = "manage.update" - params = {} - else: - command = "renamer.scan" + if section == "CouchPotato": + if method == "manage": + command = "manage.update" + params = {} + else: + command = "renamer.scan" - url = "{0}{1}".format(baseURL, command) + url = "{0}{1}".format(baseURL, command) + logger.debug("Opening URL: {0} with PARAMS: {1}".format(url, params), section) + logger.postprocess("Starting {0} scan for {1}".format(method, inputName), section) - logger.debug("Opening URL: {0} with PARAMS: {1}".format(url, params), section) - - logger.postprocess("Starting {0} scan for {1}".format(method, inputName), section) + if section == "Radarr": + payload = {'name': 'DownloadedMovieScan', 'path': params['media_folder']} + logger.debug("Opening URL: {0} with PARAMS: {1}".format(baseURL, payload), section) + logger.postprocess("Starting DownloadedMovieScan scan for {1}".format(inputName), section) try: - r = requests.get(url, params=params, verify=False, timeout=(30, 1800)) + if section == "CouchPotato": + r = requests.get(url, params=params, verify=False, timeout=(30, 1800)) + else: + r = requests.post(url, data=json.dumps(payload), headers=headers, stream=True, verify=False, timeout=(30, 1800)) except requests.ConnectionError: logger.error("Unable to open URL", section) return [1, "{0}: Failed to post-process - Unable to connect to {1}".format(section, section)] @@ -247,10 +263,12 @@ class autoProcessMovie(object): if r.status_code not in [requests.codes.ok, requests.codes.created, requests.codes.accepted]: logger.error("Server returned status {0}".format(r.status_code), section) return [1, "{0}: Failed to post-process - Server returned status {1}".format(section, r.status_code)] - elif result['success']: + elif section == "CouchPotato" and result['success']: logger.postprocess("SUCCESS: Finished {0} scan for folder {1}".format(method, dirName), section) if method == "manage": return [0, "{0}: Successfully post-processed {1}".format(section, inputName)] + elif section == "Radarr": + logger.postprocess("Radarr response: {0}".format(result['state'])) else: logger.error("FAILED: {0} scan was unable to finish for folder {1}. exiting!".format(method, dirName), section) @@ -262,6 +280,10 @@ class autoProcessMovie(object): if failureLink: reportNzb(failureLink, clientAgent) + if section == "Radarr": + logger.postprocess("FAILED: The download failed. Sending failed download to {0} for CDH processing".format(fork), section) + return [1, "{0}: Download Failed. Sending back to {1}".format(section, section)] # Return as failed to flag this in the downloader. + if delete_failed and os.path.isdir(dirName) and not os.path.dirname(dirName) == dirName: logger.postprocess("Deleting failed files and folder {0}".format(dirName), section) rmDir(dirName) @@ -325,7 +347,10 @@ class autoProcessMovie(object): timeout = time.time() + 60 * wait_for while time.time() < timeout: # only wait 2 (default) minutes, then return. logger.postprocess("Checking for status change, please stand by ...", section) - release = self.get_release(baseURL, imdbid, download_id, release_id) + if section == "CouchPotato": + release = self.get_release(baseURL, imdbid, download_id, release_id) + else: + release = None if release: try: if release_id is None and release_status_old is None: # we didn't have a release before, but now we do. diff --git a/core/nzbToMediaConfig.py b/core/nzbToMediaConfig.py index f29c8db4..b37cfc1f 100644 --- a/core/nzbToMediaConfig.py +++ b/core/nzbToMediaConfig.py @@ -142,7 +142,7 @@ class ConfigObj(configobj.ConfigObj, Section): if CFG_OLD[section].sections: subsections.update({section: CFG_OLD[section].sections}) for option, value in CFG_OLD[section].items(): - if option in ["category", "cpsCategory", "sbCategory", "hpCategory", "mlCategory", "gzCategory"]: + if option in ["category", "cpsCategory", "sbCategory", "hpCategory", "mlCategory", "gzCategory", "raCategory", "ndCategory"]: if not isinstance(value, list): value = [value] @@ -255,10 +255,14 @@ class ConfigObj(configobj.ConfigObj, Section): try: if 'NZBPO_NDCATEGORY' in os.environ and 'NZBPO_SBCATEGORY' in os.environ: if os.environ['NZBPO_NDCATEGORY'] == os.environ['NZBPO_SBCATEGORY']: - logger.warning("{x} category is set for SickBeard and NzbDrone. " + logger.warning("{x} category is set for SickBeard and Sonarr. " "Please check your config in NZBGet".format (x=os.environ['NZBPO_NDCATEGORY'])) - + if 'NZBPO_RACATEGORY' in os.environ and 'NZBPO_CPSCATEGORY' in os.environ: + if os.environ['NZBPO_RACATEGORY'] == os.environ['NZBPO_CPSCATEGORY']: + logger.warning("{x} category is set for CouchPotato and Radarr. " + "Please check your config in NZBGet".format + (x=os.environ['NZBPO_RACATEGORY'])) section = "Nzb" key = 'NZBOP_DESTDIR' if key in os.environ: @@ -302,6 +306,8 @@ class ConfigObj(configobj.ConfigObj, Section): CFG_NEW[section][os.environ[envCatKey]] = {} CFG_NEW[section][os.environ[envCatKey]][option] = value CFG_NEW[section][os.environ[envCatKey]]['enabled'] = 1 + if os.environ[envCatKey] in CFG_NEW['Radarr'].sections: + CFG_NEW['Radarr'][envCatKey]['enabled'] = 0 section = "SickBeard" envCatKey = 'NZBPO_SBCATEGORY' @@ -388,6 +394,25 @@ class ConfigObj(configobj.ConfigObj, Section): if os.environ[envCatKey] in CFG_NEW['SickBeard'].sections: CFG_NEW['SickBeard'][envCatKey]['enabled'] = 0 + section = "Radarr" + envCatKey = 'NZBPO_RACATEGORY' + envKeys = ['ENABLED', 'HOST', 'APIKEY', 'PORT', 'SSL', 'WEB_ROOT', 'WATCH_DIR', 'FORK', 'DELETE_FAILED', + 'TORRENT_NOLINK', 'NZBEXTRACTIONBY', 'WAIT_FOR', 'DELETE_FAILED', 'REMOTE_PATH'] + cfgKeys = ['enabled', 'host', 'apikey', 'port', 'ssl', 'web_root', 'watch_dir', 'fork', 'delete_failed', + 'Torrent_NoLink', 'nzbExtractionBy', 'wait_for', 'delete_failed', 'remote_path'] + if envCatKey in os.environ: + for index in range(len(envKeys)): + key = 'NZBPO_RA{index}'.format(index=envKeys[index]) + if key in os.environ: + option = cfgKeys[index] + value = os.environ[key] + if os.environ[envCatKey] not in CFG_NEW[section].sections: + CFG_NEW[section][os.environ[envCatKey]] = {} + CFG_NEW[section][os.environ[envCatKey]][option] = value + CFG_NEW[section][os.environ[envCatKey]]['enabled'] = 1 + if os.environ[envCatKey] in CFG_NEW['CouchPotato'].sections: + CFG_NEW['CouchPotato'][envCatKey]['enabled'] = 0 + section = "Extensions" envKeys = ['COMPRESSEDEXTENSIONS', 'MEDIAEXTENSIONS', 'METAEXTENSIONS'] cfgKeys = ['compressedExtensions', 'mediaExtensions', 'metaExtensions'] diff --git a/nzbToMedia.py b/nzbToMedia.py index 87845514..2bc13be2 100755 --- a/nzbToMedia.py +++ b/nzbToMedia.py @@ -83,6 +83,49 @@ # Enable to replace local path with the path as per the mountPoints below. #cpsremote_path=0 +## Radarr + +# Radarr script category. +# +# category that gets called for post-processing with NzbDrone. +#raCategory=movies2 + +# Radarr host. +# +# The ipaddress for your Radarr server. e.g For the Same system use localhost or 127.0.0.1 +#rahost=localhost + +# Radarr port. +#raport=8989 + +# Radarr API key. +#raapikey= + +# Radarr uses ssl (0, 1). +# +# Set to 1 if using ssl, else set to 0. +#rassl=0 + +# Radarr web_root +# +# set this if using a reverse proxy. +#raweb_root= + +# Radarr wait_for +# +# Set the number of minutes to wait after calling the renamer, to check the episode has changed status. +#rawait_for=2 + +# Radarr Delete Failed Downloads (0, 1). +# +# set to 1 to delete failed, or 0 to leave files in place. +#radelete_failed=0 + +# Radarr and NZBGet are a different system (0, 1). +# +# Enable to replace local path with the path as per the mountPoints below. +#raremote_path=0 + ## SickBeard # SickBeard script category. @@ -613,10 +656,10 @@ def process(inputDirectory, inputName=None, status=0, clientAgent='manual', down logger.info("Calling {0}:{1} to post-process:{2}".format(sectionName, inputCategory, inputName)) - if sectionName == "CouchPotato": + if sectionName in ["CouchPotato", "Radarr"]: result = autoProcessMovie().process(sectionName, inputDirectory, inputName, status, clientAgent, download_id, inputCategory, failureLink) - elif sectionName in ["SickBeard", "NzbDrone"]: + elif sectionName in ["SickBeard", "NzbDrone", "Sonarr"]: result = autoProcessTV().processEpisode(sectionName, inputDirectory, inputName, status, clientAgent, download_id, inputCategory, failureLink) elif sectionName == "HeadPhones": @@ -637,7 +680,7 @@ def process(inputDirectory, inputName=None, status=0, clientAgent='manual', down if clientAgent != 'manual': # update download status in our DB update_downloadInfoStatus(inputName, 1) - if sectionName not in ['UserScript', 'NzbDrone']: + if sectionName not in ['UserScript', 'NzbDrone', 'Sonarr', 'Radarr']: # cleanup our processing folders of any misc unwanted files and empty directories cleanDir(inputDirectory, sectionName, inputCategory) @@ -708,6 +751,8 @@ def main(args, section=None): download_id = os.environ['NZBPR_DRONE'] elif 'NZBPR_SONARR' in os.environ: download_id = os.environ['NZBPR_SONARR'] + elif 'NZBPR_RADARR' in os.environ: + download_id = os.environ['NZBPR_RADARR'] if 'NZBPR__DNZB_FAILURE' in os.environ: failureLink = os.environ['NZBPR__DNZB_FAILURE'] diff --git a/nzbToNzbDrone.py b/nzbToNzbDrone.py index 9951a1fd..2f7c8cfc 100755 --- a/nzbToNzbDrone.py +++ b/nzbToNzbDrone.py @@ -4,7 +4,7 @@ ############################################################################## ### NZBGET POST-PROCESSING SCRIPT ### -# Post-Process to NzbDrone. +# Post-Process to NzbDrone/Sonarr. # # This script sends the download to your automated media management servers. #