From 8dfaab2e60c2a9c044194fc0de8118b9f6045d61 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Sun, 29 Jan 2017 22:24:32 +1030 Subject: [PATCH 01/26] do not embed .sub. Fixes #1150 --- core/transcoder/transcoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/transcoder/transcoder.py b/core/transcoder/transcoder.py index 4f97c891..92e53d6e 100644 --- a/core/transcoder/transcoder.py +++ b/core/transcoder/transcoder.py @@ -500,7 +500,7 @@ def buildCommands(file, newDir, movieName, bitbucket): def get_subs(file): filepaths = [] - subExt = ['.srt', '.sub', '.idx'] + subExt = ['.srt'] #, '.sub', '.idx'] #remove .sub + .idx for now. name = os.path.splitext(os.path.split(file)[1])[0] dir = os.path.split(file)[0] for dirname, dirs, filenames in os.walk(dir): From 3af8bc78856b4d24097e6ae5797223123b7eba58 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Tue, 31 Jan 2017 11:18:42 +1030 Subject: [PATCH 02/26] add proper check of sub streams #1150 and filter out commentary. Fixes #1180 --- core/transcoder/transcoder.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/core/transcoder/transcoder.py b/core/transcoder/transcoder.py index 92e53d6e..d9461b03 100644 --- a/core/transcoder/transcoder.py +++ b/core/transcoder/transcoder.py @@ -254,7 +254,15 @@ def buildCommands(file, newDir, movieName, bitbucket): used_audio = 0 a_mapped = [] + commentary = [] if audioStreams: + for i, val in enumerate(audioStreams): + try: + if "Commentary" in val.get("tags").get("title"): # Split out commentry tracks. + commentary.append(audioStreams[i]) + del audioStreams[i] + except: + continue try: audio1 = [item for item in audioStreams if item["tags"]["language"] == core.ALANGUAGE] except: # no language tags. Assume only 1 language. @@ -374,7 +382,7 @@ def buildCommands(file, newDir, movieName, bitbucket): audio_cmd.extend(audio_cmd2) if core.AINCLUDE and core.ACODEC3: - for audio in audioStreams: + for audio in audioStreams.extend(commentary): #add commentry tracks back here. if audio["index"] in a_mapped: continue used_audio += 1 @@ -463,6 +471,10 @@ def buildCommands(file, newDir, movieName, bitbucket): sub_details, result = getVideoDetails(subfile) if not sub_details or not sub_details.get("streams"): continue + if core.SCODEC == "mov_text": + subcode = [stream["codec_name"] for stream in sub_details["streams"]] + if set(subcode).intersection(["dvd_subtitle", "VobSub"]): # We can't convert these. + continue command.extend(['-i', subfile]) lan = os.path.splitext(os.path.splitext(subfile)[0])[1][1:].split('-')[0] metlan = None @@ -500,7 +512,7 @@ def buildCommands(file, newDir, movieName, bitbucket): def get_subs(file): filepaths = [] - subExt = ['.srt'] #, '.sub', '.idx'] #remove .sub + .idx for now. + subExt = ['.srt', '.sub', '.idx'] name = os.path.splitext(os.path.split(file)[1])[0] dir = os.path.split(file)[0] for dirname, dirs, filenames in os.walk(dir): From 9ec9b1302817b44b3607a7addc7eeb272eabff0f Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Wed, 1 Feb 2017 09:48:50 +1030 Subject: [PATCH 03/26] traverse audiostreams in reverse: Fixes #1180 --- core/transcoder/transcoder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/transcoder/transcoder.py b/core/transcoder/transcoder.py index d9461b03..a3b5cdc0 100644 --- a/core/transcoder/transcoder.py +++ b/core/transcoder/transcoder.py @@ -256,10 +256,10 @@ def buildCommands(file, newDir, movieName, bitbucket): a_mapped = [] commentary = [] if audioStreams: - for i, val in enumerate(audioStreams): + for i, val in reversed(list(enumerate(audioStreams))): try: if "Commentary" in val.get("tags").get("title"): # Split out commentry tracks. - commentary.append(audioStreams[i]) + commentary.append(val) del audioStreams[i] except: continue From 868c8c36bfa9a1ebfe395851bd82bcd2ca93d9f7 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Mon, 6 Feb 2017 17:02:14 +1030 Subject: [PATCH 04/26] add catch for OMDB api errors. Fixes #182, #184, #185 --- core/nzbToMediaUtil.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/nzbToMediaUtil.py b/core/nzbToMediaUtil.py index 7edc3701..fd42271a 100644 --- a/core/nzbToMediaUtil.py +++ b/core/nzbToMediaUtil.py @@ -1047,7 +1047,10 @@ def find_imdbid(dirName, inputName): logger.info("Found imdbID [{0}] from DNZB-MoreInfo".format(imdbid)) return imdbid logger.info('Searching IMDB for imdbID ...') - guess = guessit.guessit(inputName) + try: + guess = guessit.guessit(inputName) + except: + guess = None if guess: # Movie Title title = None @@ -1069,12 +1072,15 @@ def find_imdbid(dirName, inputName): logger.error("Unable to open URL {0}".format(url)) return - results = r.json() + try: + results = r.json() + except: + logger.error("No json data returned from omdbapi.com") try: imdbid = results['imdbID'] except: - pass + logger.error("No imdbID returned from omdbapi.com") if imdbid: logger.info("Found imdbID [{0}]".format(imdbid)) From a7fcb7be9e5c3c15a0949d8f8db1f61a8f4cae72 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Fri, 10 Feb 2017 22:12:51 +1030 Subject: [PATCH 05/26] convert all listdir functions to unicode. Fixes #1189 --- core/nzbToMediaUtil.py | 22 +++++++++++----------- core/transcoder/transcoder.py | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/nzbToMediaUtil.py b/core/nzbToMediaUtil.py index fd42271a..bb93e0dd 100644 --- a/core/nzbToMediaUtil.py +++ b/core/nzbToMediaUtil.py @@ -195,7 +195,7 @@ def getDirSize(inputPath): from functools import partial prepend = partial(os.path.join, inputPath) return sum( - [(os.path.getsize(f) if os.path.isfile(f) else getDirSize(f)) for f in map(prepend, os.listdir(inputPath))]) + [(os.path.getsize(f) if os.path.isfile(f) else getDirSize(f)) for f in map(prepend, os.listdir(unicode(inputPath)))]) def is_minSize(inputName, minSize): @@ -322,7 +322,7 @@ def removeEmptyFolders(path, removeRoot=True): # remove empty subfolders logger.debug("Checking for empty folders in:{0}".format(path)) - files = os.listdir(path) + files = os.listdir(unicode(path)) if len(files): for f in files: fullpath = os.path.join(path, f) @@ -330,7 +330,7 @@ def removeEmptyFolders(path, removeRoot=True): removeEmptyFolders(fullpath) # if folder empty, delete it - files = os.listdir(path) + files = os.listdir(unicode(path)) if len(files) == 0 and removeRoot: logger.debug("Removing empty folder:{}".format(path)) os.rmdir(path) @@ -607,9 +607,9 @@ def getDirs(section, subsection, link='hard'): folders = [] logger.info("Searching {0} for mediafiles to post-process ...".format(path)) - sync = [o for o in os.listdir(path) if os.path.splitext(o)[1] in ['.!sync', '.bts']] + sync = [o for o in os.listdir(unicode(path)) if os.path.splitext(o)[1] in ['.!sync', '.bts']] # search for single files and move them into their own folder for post-processing - for mediafile in [os.path.join(path, o) for o in os.listdir(path) if + for mediafile in [os.path.join(path, o) for o in os.listdir(unicode(path)) if os.path.isfile(os.path.join(path, o))]: if len(sync) > 0: break @@ -673,11 +673,11 @@ def getDirs(section, subsection, link='hard'): # removeEmptyFolders(path, removeRoot=False) - if os.listdir(path): - for dir in [os.path.join(path, o) for o in os.listdir(path) if + if os.listdir(unicode(path)): + for dir in [os.path.join(path, o) for o in os.listdir(unicode(path)) if os.path.isdir(os.path.join(path, o))]: - sync = [o for o in os.listdir(dir) if os.path.splitext(o)[1] in ['.!sync', '.bts']] - if len(sync) > 0 or len(os.listdir(dir)) == 0: + sync = [o for o in os.listdir(unicode(dir)) if os.path.splitext(o)[1] in ['.!sync', '.bts']] + if len(sync) > 0 or len(os.listdir(unicode(dir))) == 0: continue folders.extend([dir]) return folders @@ -994,7 +994,7 @@ def listMediaFiles(path, minSize=0, delete_ignored=0, media=True, audio=True, me return files - for curFile in os.listdir(path): + for curFile in os.listdir(unicode(path)): fullCurFile = os.path.join(path, curFile) # if it's a folder do it recursively @@ -1031,7 +1031,7 @@ def find_imdbid(dirName, inputName): logger.info("Found imdbID [{0}]".format(imdbid)) return imdbid if os.path.isdir(dirName): - for file in os.listdir(dirName): + for file in os.listdir(unicode(dirName)): m = re.search('(tt\d{7})', file) if m: imdbid = m.group(1) diff --git a/core/transcoder/transcoder.py b/core/transcoder/transcoder.py index a3b5cdc0..977fbe54 100644 --- a/core/transcoder/transcoder.py +++ b/core/transcoder/transcoder.py @@ -814,7 +814,7 @@ def Transcode_directory(dirName): os.unlink(file) except: pass - if not os.listdir(newDir): # this is an empty directory and we didn't transcode into it. + if not os.listdir(unicode(newDir)): # this is an empty directory and we didn't transcode into it. os.rmdir(newDir) newDir = dirName if not core.PROCESSOUTPUT and core.DUPLICATE: # We postprocess the original files to CP/SB From ad1017712d72970b02f133dbfd7710c18cc3ec37 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Fri, 10 Feb 2017 23:12:04 +1030 Subject: [PATCH 06/26] perform extraction, corruption checks, and transcoding when no server set. Fixes #1183 --- core/autoProcess/autoProcessMovie.py | 11 +++++++---- core/autoProcess/autoProcessTV.py | 19 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/core/autoProcess/autoProcessMovie.py b/core/autoProcess/autoProcessMovie.py index e97e9fd6..4c1c383f 100644 --- a/core/autoProcess/autoProcessMovie.py +++ b/core/autoProcess/autoProcessMovie.py @@ -123,14 +123,17 @@ class autoProcessMovie(object): else: 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 not server_responding(baseURL): + if not apikey: + logger.info('No CouchPotato apikey entered. Performing transcoder functions only') + release = None + elif server_responding(baseURL): + 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)] - imdbid = find_imdbid(dirName, inputName) - release = self.get_release(baseURL, imdbid, download_id) - # pull info from release found if available release_id = None media_id = None diff --git a/core/autoProcess/autoProcessTV.py b/core/autoProcess/autoProcessTV.py index 9cee4b0e..c511d96a 100644 --- a/core/autoProcess/autoProcessTV.py +++ b/core/autoProcess/autoProcessTV.py @@ -60,17 +60,20 @@ class autoProcessTV(object): ssl = int(cfg.get("ssl", 0)) web_root = cfg.get("web_root", "") protocol = "https://" if ssl else "http://" - - if not server_responding("{0}{1}:{2}{3}".format(protocol, host, port, web_root)): - logger.error("Server did not respond. Exiting", section) - return [1, "{0}: Failed to post-process - {1} did not respond.".format(section, section)] - - # auto-detect correct fork - fork, fork_params = autoFork(section, inputCategory) - username = cfg.get("username", "") password = cfg.get("password", "") apikey = cfg.get("apikey", "") + + if not username and not apikey: + logger.info('No SickBeard username or Sonarr apikey entered. Performing transcoder functions only') + fork, fork_params = "None", [] + elif server_responding("{0}{1}:{2}{3}".format(protocol, host, port, web_root)): + # auto-detect correct fork + fork, fork_params = autoFork(section, inputCategory) + else: + logger.error("Server did not respond. Exiting", section) + return [1, "{0}: Failed to post-process - {1} did not respond.".format(section, section)] + delete_failed = int(cfg.get("delete_failed", 0)) nzbExtractionBy = cfg.get("nzbExtractionBy", "Downloader") process_method = cfg.get("process_method") From e0594a6e5196adc6d351959e5626109aafa56b95 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Sun, 12 Feb 2017 12:01:48 +1030 Subject: [PATCH 07/26] fix list indices errors when no fork set. Fixes #1191 --- core/autoProcess/autoProcessTV.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/autoProcess/autoProcessTV.py b/core/autoProcess/autoProcessTV.py index c511d96a..0d93e21a 100644 --- a/core/autoProcess/autoProcessTV.py +++ b/core/autoProcess/autoProcessTV.py @@ -66,7 +66,7 @@ class autoProcessTV(object): if not username and not apikey: logger.info('No SickBeard username or Sonarr apikey entered. Performing transcoder functions only') - fork, fork_params = "None", [] + fork, fork_params = "None", {} elif server_responding("{0}{1}:{2}{3}".format(protocol, host, port, web_root)): # auto-detect correct fork fork, fork_params = autoFork(section, inputCategory) From d34e06e42b81fc567c8acd08ef78520c393028c4 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Mon, 13 Feb 2017 19:44:01 +1030 Subject: [PATCH 08/26] fix CP server responding test. Add trailing /. Fixes #1193 --- core/autoProcess/autoProcessMovie.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/autoProcess/autoProcessMovie.py b/core/autoProcess/autoProcessMovie.py index 4c1c383f..76443155 100644 --- a/core/autoProcess/autoProcessMovie.py +++ b/core/autoProcess/autoProcessMovie.py @@ -20,13 +20,13 @@ class autoProcessMovie(object): # determine cmd and params to send to CouchPotato to get our results section = 'movies' - cmd = "/media.list" + cmd = "media.list" if release_id or imdbid: section = 'media' - cmd = "/media.get" + cmd = "media.get" params['id'] = release_id or imdbid - url = baseURL + cmd + url = "{0}{1}".format(baseURL, cmd) logger.debug("Opening URL: {0} with PARAMS: {1}".format(url, params)) try: @@ -124,7 +124,7 @@ 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) + baseURL = "{0}{1}:{2}{3}/api/{4}/".format(protocol, host, port, web_root, apikey) if not apikey: logger.info('No CouchPotato apikey entered. Performing transcoder functions only') release = None @@ -226,10 +226,10 @@ class autoProcessMovie(object): params['media_folder'] = remoteDir(dirName) if remote_path else dirName if method == "manage": - command = "/manage.update" + command = "manage.update" params = {} else: - command = "/renamer.scan" + command = "renamer.scan" url = "{0}{1}".format(baseURL, command) @@ -274,7 +274,7 @@ class autoProcessMovie(object): if release_id: logger.postprocess("Setting failed release {0} to ignored ...".format(inputName), section) - url = "{url}/release.ignore".format(url=baseURL) + url = "{url}release.ignore".format(url=baseURL) params = {'id': release_id} logger.debug("Opening URL: {0} with PARAMS: {1}".format(url, params), section) @@ -297,7 +297,7 @@ class autoProcessMovie(object): logger.postprocess("Trying to snatch the next highest ranked release.", section) - url = "{0}/movie.searcher.try_next".format(baseURL) + url = "{0}movie.searcher.try_next".format(baseURL) logger.debug("Opening URL: {0}".format(url), section) try: From ee738764c108085be64dc69d150b29d12e5aecd9 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Mon, 13 Feb 2017 20:27:00 +1030 Subject: [PATCH 09/26] use basestring to match unicode path in transcoder. --- core/transcoder/transcoder.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/transcoder/transcoder.py b/core/transcoder/transcoder.py index 977fbe54..67b4fe39 100644 --- a/core/transcoder/transcoder.py +++ b/core/transcoder/transcoder.py @@ -115,7 +115,7 @@ def getVideoDetails(videofile, img=None, bitbucket=None): def buildCommands(file, newDir, movieName, bitbucket): - if isinstance(file, str): + if isinstance(file, basestring): inputFile = file if '"concat:' in file: file = file.split('|')[0].replace('concat:', '') @@ -613,7 +613,7 @@ def processList(List, newDir, bitbucket): if combine: newList.extend(combineCD(combine)) for file in newList: - if isinstance(file, str) and 'concat:' not in file and not os.path.isfile(file): + if isinstance(file, basestring) and 'concat:' not in file and not os.path.isfile(file): success = False break if success and newList: @@ -748,13 +748,13 @@ def Transcode_directory(dirName): return 1, dirName for file in List: - if isinstance(file, str) and os.path.splitext(file)[1] in core.IGNOREEXTENSIONS: + if isinstance(file, basestring) and os.path.splitext(file)[1] in core.IGNOREEXTENSIONS: continue command = buildCommands(file, newDir, movieName, bitbucket) newfilePath = command[-1] # transcoding files may remove the original file, so make sure to extract subtitles first - if core.SEXTRACT and isinstance(file, str): + if core.SEXTRACT and isinstance(file, basestring): extract_subs(file, newfilePath, bitbucket) try: # Try to remove the file that we're transcoding to just in case. (ffmpeg will return an error if it already exists for some reason) @@ -769,7 +769,7 @@ def Transcode_directory(dirName): print_cmd(command) result = 1 # set result to failed in case call fails. try: - if isinstance(file, str): + if isinstance(file, basestring): proc = subprocess.Popen(command, stdout=bitbucket, stderr=bitbucket) else: img, data = iteritems(file).next() @@ -784,7 +784,7 @@ def Transcode_directory(dirName): except: logger.error("Transcoding of video {0} has failed".format(newfilePath)) - if core.SUBSDIR and result == 0 and isinstance(file, str): + if core.SUBSDIR and result == 0 and isinstance(file, basestring): for sub in get_subs(file): name = os.path.splitext(os.path.split(file)[1])[0] subname = os.path.split(sub)[1] From 7dff735fa8e43c9a92bf70d7fc76ed6b924a0d70 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Mon, 13 Feb 2017 21:12:44 +1030 Subject: [PATCH 10/26] attempt autofork even if no username set. Fixes #1191 --- core/autoProcess/autoProcessTV.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/autoProcess/autoProcessTV.py b/core/autoProcess/autoProcessTV.py index 0d93e21a..4c161787 100644 --- a/core/autoProcess/autoProcessTV.py +++ b/core/autoProcess/autoProcessTV.py @@ -64,12 +64,12 @@ class autoProcessTV(object): password = cfg.get("password", "") apikey = cfg.get("apikey", "") - if not username and not apikey: - logger.info('No SickBeard username or Sonarr apikey entered. Performing transcoder functions only') - fork, fork_params = "None", {} - elif server_responding("{0}{1}:{2}{3}".format(protocol, host, port, web_root)): + if server_responding("{0}{1}:{2}{3}".format(protocol, host, port, web_root)): # auto-detect correct fork fork, fork_params = autoFork(section, inputCategory) + elif not username and not apikey: + logger.info('No SickBeard username or Sonarr apikey entered. Performing transcoder functions only') + fork, fork_params = "None", {} else: logger.error("Server did not respond. Exiting", section) return [1, "{0}: Failed to post-process - {1} did not respond.".format(section, section)] From 6dc289909db2306ee92ab1f5532bb889ec24536e Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Tue, 14 Feb 2017 20:32:49 +1030 Subject: [PATCH 11/26] allow long paths in Cleandir. Fixes #1189 --- core/nzbToMediaUtil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/nzbToMediaUtil.py b/core/nzbToMediaUtil.py index bb93e0dd..9a2cab7e 100644 --- a/core/nzbToMediaUtil.py +++ b/core/nzbToMediaUtil.py @@ -728,7 +728,7 @@ def onerror(func, path, exc_info): def rmDir(dirName): logger.info("Deleting {0}".format(dirName)) try: - shutil.rmtree(dirName, onerror=onerror) + shutil.rmtree(unicode(dirName), onerror=onerror) except: logger.error("Unable to delete folder {0}".format(dirName)) From f8e525abe7dd5ce17074a71199d0e0d987063232 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Wed, 22 Feb 2017 21:53:58 +1030 Subject: [PATCH 12/26] 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. # From d7370f533f3434275f2480788e659a82bef07261 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Wed, 22 Feb 2017 22:05:35 +1030 Subject: [PATCH 13/26] add nzbToRadarr --- nzbToRadarr.py | 245 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100755 nzbToRadarr.py diff --git a/nzbToRadarr.py b/nzbToRadarr.py new file mode 100755 index 00000000..2870b03c --- /dev/null +++ b/nzbToRadarr.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python2 +# coding=utf-8 +# +############################################################################## +### NZBGET POST-PROCESSING SCRIPT ### + +# Post-Process to Radarr. +# +# This script sends the download to your automated media management servers. +# +# NOTE: This script requires Python to be installed on your system. + +############################################################################## +### OPTIONS ### + +## General + +# Auto Update nzbToMedia (0, 1). +# +# Set to 1 if you want nzbToMedia to automatically check for and update to the latest version +#auto_update=0 + +# Check Media for corruption (0, 1). +# +# Enable/Disable media file checking using ffprobe. +#check_media=1 + +# Safe Mode protection of DestDir (0, 1). +# +# Enable/Disable a safety check to ensure we don't process all downloads in the default_downloadDirectory by mistake. +#safe_mode=1 + +# Disable additional extraction checks for failed (0, 1). +# +# Turn this on to disable additional extraction attempts for failed downloads. Default = 0 this will attempt to extract and verify if media is present. +#no_extract_failed = 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 + +## Network + +# Network Mount Points (Needed for remote path above) +# +# Enter Mount points as LocalPath,RemotePath and separate each pair with '|' +# e.g. mountPoints=/volume1/Public/,E:\|/volume2/share/,\\NAS\ +#mountPoints= + +## Extensions + +# Media Extensions +# +# This is a list of media extensions that are used to verify that the download does contain valid media. +#mediaExtensions=.mkv,.avi,.divx,.xvid,.mov,.wmv,.mp4,.mpg,.mpeg,.vob,.iso,.ts + +## Posix + +# Niceness for external tasks Extractor and Transcoder. +# +# Set the Niceness value for the nice command. These range from -20 (most favorable to the process) to 19 (least favorable to the process). +#niceness=10 + +# ionice scheduling class (0, 1, 2, 3). +# +# Set the ionice scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle. +#ionice_class=2 + +# ionice scheduling class data. +# +# Set the ionice scheduling class data. This defines the class data, if the class accepts an argument. For real time and best-effort, 0-7 is valid data. +#ionice_classdata=4 + +## Transcoder + +# getSubs (0, 1). +# +# set to 1 to download subtitles. +#getSubs = 0 + +# subLanguages. +# +# subLanguages. create a list of languages in the order you want them in your subtitles. +#subLanguages = eng,spa,fra + +# Transcode (0, 1). +# +# set to 1 to transcode, otherwise set to 0. +#transcode=0 + +# create a duplicate, or replace the original (0, 1). +# +# set to 1 to cretae a new file or 0 to replace the original +#duplicate=1 + +# ignore extensions. +# +# list of extensions that won't be transcoded. +#ignoreExtensions=.avi,.mkv + +# outputFastStart (0,1). +# +# outputFastStart. 1 will use -movflags + faststart. 0 will disable this from being used. +#outputFastStart = 0 + +# outputVideoPath. +# +# outputVideoPath. Set path you want transcoded videos moved to. Leave blank to disable. +#outputVideoPath = + +# processOutput (0,1). +# +# processOutput. 1 will send the outputVideoPath to SickBeard/CouchPotato. 0 will send original files. +#processOutput = 0 + +# audioLanguage. +# +# audioLanguage. set the 3 letter language code you want as your primary audio track. +#audioLanguage = eng + +# allAudioLanguages (0,1). +# +# allAudioLanguages. 1 will keep all audio tracks (uses AudioCodec3) where available. +#allAudioLanguages = 0 + +# allSubLanguages (0,1). +# +# allSubLanguages. 1 will keep all exisiting sub languages. 0 will discare those not in your list above. +#allSubLanguages = 0 + +# embedSubs (0,1). +# +# embedSubs. 1 will embded external sub/srt subs into your video if this is supported. +#embedSubs = 1 + +# burnInSubtitle (0,1). +# +# burnInSubtitle. burns the default sub language into your video (needed for players that don't support subs) +#burnInSubtitle = 0 + +# extractSubs (0,1). +# +# extractSubs. 1 will extract subs from the video file and save these as external srt files. +#extractSubs = 0 + +# externalSubDir. +# +# externalSubDir. set the directory where subs should be saved (if not the same directory as the video) +#externalSubDir = + +# outputDefault (None, iPad, iPad-1080p, iPad-720p, Apple-TV2, iPod, iPhone, PS3, xbox, Roku-1080p, Roku-720p, Roku-480p, mkv, mp4-scene-release, MKV-SD). +# +# outputDefault. Loads default configs for the selected device. The remaining options below are ignored. +# If you want to use your own profile, set None and set the remaining options below. +#outputDefault = None + +# hwAccel (0,1). +# +# hwAccel. 1 will set ffmpeg to enable hardware acceleration (this requires a recent ffmpeg). +#hwAccel=0 + +# ffmpeg output settings. +#outputVideoExtension=.mp4 +#outputVideoCodec=libx264 +#VideoCodecAllow = +#outputVideoResolution=720:-1 +#outputVideoPreset=medium +#outputVideoFramerate=24 +#outputVideoBitrate=800k +#outputAudioCodec=libmp3lame +#AudioCodecAllow = +#outputAudioBitrate=128k +#outputQualityPercent = 0 +#outputAudioTrack2Codec = libfaac +#AudioCodec2Allow = +#outputAudioTrack2Bitrate = 128k +#outputAudioOtherCodec = libmp3lame +#AudioOtherCodecAllow = +#outputAudioOtherBitrate = 128k +#outputSubtitleCodec = + +## WakeOnLan + +# use WOL (0, 1). +# +# set to 1 to send WOL broadcast to the mac and test the server (e.g. xbmc) on the host and port specified. +#wolwake=0 + +# WOL MAC +# +# enter the mac address of the system to be woken. +#wolmac=00:01:2e:2D:64:e1 + +# Set the Host and Port of a server to verify system has woken. +#wolhost=192.168.1.37 +#wolport=80 + +### NZBGET POST-PROCESSING SCRIPT ### +############################################################################## + +import sys +import nzbToMedia + +section = "Radarr" +result = nzbToMedia.main(sys.argv, section) +sys.exit(result) From 7d4079432c99da8c9159793bcc24e03d26487afe Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Thu, 23 Feb 2017 12:54:06 +1030 Subject: [PATCH 14/26] fixed Radarr default port and assigned release before call. Fixes #1170 --- autoProcessMedia.cfg.spec | 2 +- core/autoProcess/autoProcessMovie.py | 2 ++ nzbToMedia.py | 2 +- nzbToRadarr.py | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/autoProcessMedia.cfg.spec b/autoProcessMedia.cfg.spec index 42c9f4ad..d1f9ae31 100644 --- a/autoProcessMedia.cfg.spec +++ b/autoProcessMedia.cfg.spec @@ -76,7 +76,7 @@ enabled = 0 apikey = host = localhost - port = 8989 + port = 7878 ###### ADVANCED USE - ONLY EDIT IF YOU KNOW WHAT YOU'RE DOING ###### web_root = ssl = 0 diff --git a/core/autoProcess/autoProcessMovie.py b/core/autoProcess/autoProcessMovie.py index d2789254..8534e6ee 100644 --- a/core/autoProcess/autoProcessMovie.py +++ b/core/autoProcess/autoProcessMovie.py @@ -139,6 +139,8 @@ class autoProcessMovie(object): elif server_responding(baseURL): if section == "CouchPotato": release = self.get_release(baseURL, imdbid, download_id) + else: + release = None else: logger.error("Server did not respond. Exiting", section) return [1, "{0}: Failed to post-process - {1} did not respond.".format(section, section)] diff --git a/nzbToMedia.py b/nzbToMedia.py index 2bc13be2..4d7b92d9 100755 --- a/nzbToMedia.py +++ b/nzbToMedia.py @@ -96,7 +96,7 @@ #rahost=localhost # Radarr port. -#raport=8989 +#raport=7878 # Radarr API key. #raapikey= diff --git a/nzbToRadarr.py b/nzbToRadarr.py index 2870b03c..0565788a 100755 --- a/nzbToRadarr.py +++ b/nzbToRadarr.py @@ -48,7 +48,7 @@ #rahost=localhost # Radarr port. -#raport=8989 +#raport=7878 # Radarr API key. #raapikey= From f04603691cde5376dd4e79670655a9697227b917 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Thu, 23 Feb 2017 21:49:38 +1030 Subject: [PATCH 15/26] fix index issue: Fixes #1170 --- core/autoProcess/autoProcessMovie.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/autoProcess/autoProcessMovie.py b/core/autoProcess/autoProcessMovie.py index 8534e6ee..e41fe52a 100644 --- a/core/autoProcess/autoProcessMovie.py +++ b/core/autoProcess/autoProcessMovie.py @@ -250,7 +250,7 @@ class autoProcessMovie(object): 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) + logger.postprocess("Starting DownloadedMovieScan scan for {0}".format(inputName), section) try: if section == "CouchPotato": From 199fd5d0df9b438b4486c1c05871b5144ee6f91b Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Fri, 24 Feb 2017 12:14:47 +1030 Subject: [PATCH 16/26] fix baseurl. Fixes #1170 --- core/autoProcess/autoProcessMovie.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/autoProcess/autoProcessMovie.py b/core/autoProcess/autoProcessMovie.py index e41fe52a..89cebeba 100644 --- a/core/autoProcess/autoProcessMovie.py +++ b/core/autoProcess/autoProcessMovie.py @@ -256,7 +256,7 @@ class autoProcessMovie(object): 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)) + r = requests.post(baseURL, 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)] From a0f28804f624007922942f600b08a5a7caf21159 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Sat, 25 Feb 2017 07:49:14 +1030 Subject: [PATCH 17/26] Add download id to Radarr call. Default 6 min wait. Fixes #1170 --- autoProcessMedia.cfg.spec | 4 ++-- core/autoProcess/autoProcessMovie.py | 4 +++- nzbToMedia.py | 4 ++-- nzbToNzbDrone.py | 2 +- nzbToRadarr.py | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/autoProcessMedia.cfg.spec b/autoProcessMedia.cfg.spec index d1f9ae31..02798251 100644 --- a/autoProcessMedia.cfg.spec +++ b/autoProcessMedia.cfg.spec @@ -86,7 +86,7 @@ keep_archive = 1 extract = 1 nzbExtractionBy = Downloader - wait_for = 2 + wait_for = 6 # 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) @@ -153,7 +153,7 @@ keep_archive = 1 extract = 1 nzbExtractionBy = Downloader - wait_for = 2 + wait_for = 6 # 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) diff --git a/core/autoProcess/autoProcessMovie.py b/core/autoProcess/autoProcessMovie.py index 89cebeba..27ff1915 100644 --- a/core/autoProcess/autoProcessMovie.py +++ b/core/autoProcess/autoProcessMovie.py @@ -248,7 +248,9 @@ class autoProcessMovie(object): logger.postprocess("Starting {0} scan for {1}".format(method, inputName), section) if section == "Radarr": - payload = {'name': 'DownloadedMovieScan', 'path': params['media_folder']} + payload = {'name': 'DownloadedMovieScan', 'path': params['media_folder'], 'downloadClientId': download_id} + if not download_id: + payload.pop("downloadClientId") logger.debug("Opening URL: {0} with PARAMS: {1}".format(baseURL, payload), section) logger.postprocess("Starting DownloadedMovieScan scan for {0}".format(inputName), section) diff --git a/nzbToMedia.py b/nzbToMedia.py index 4d7b92d9..f4f8f55b 100755 --- a/nzbToMedia.py +++ b/nzbToMedia.py @@ -114,7 +114,7 @@ # Radarr wait_for # # Set the number of minutes to wait after calling the renamer, to check the episode has changed status. -#rawait_for=2 +#rawait_for=6 # Radarr Delete Failed Downloads (0, 1). # @@ -218,7 +218,7 @@ # NzbDrone wait_for # # Set the number of minutes to wait after calling the renamer, to check the episode has changed status. -#ndwait_for=2 +#ndwait_for=6 # NzbDrone Delete Failed Downloads (0, 1). # diff --git a/nzbToNzbDrone.py b/nzbToNzbDrone.py index 2f7c8cfc..63e51193 100755 --- a/nzbToNzbDrone.py +++ b/nzbToNzbDrone.py @@ -66,7 +66,7 @@ # NzbDrone wait_for # # Set the number of minutes to wait after calling the renamer, to check the episode has changed status. -#ndwait_for=2 +#ndwait_for=6 # NzbDrone Delete Failed Downloads (0, 1). # diff --git a/nzbToRadarr.py b/nzbToRadarr.py index 0565788a..321c8a63 100755 --- a/nzbToRadarr.py +++ b/nzbToRadarr.py @@ -66,7 +66,7 @@ # Radarr wait_for # # Set the number of minutes to wait after calling the renamer, to check the episode has changed status. -#rawait_for=2 +#rawait_for=6 # Radarr Delete Failed Downloads (0, 1). # From 33fb4ce331fdc044c50b3ed77bfe40e97cac6e29 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Sat, 25 Feb 2017 09:31:43 +1030 Subject: [PATCH 18/26] add Raddar command status monitoring and CDH handling. Fixes #1170 --- core/autoProcess/autoProcessMovie.py | 62 +++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/core/autoProcess/autoProcessMovie.py b/core/autoProcess/autoProcessMovie.py index 27ff1915..3b5718ea 100644 --- a/core/autoProcess/autoProcessMovie.py +++ b/core/autoProcess/autoProcessMovie.py @@ -104,6 +104,39 @@ class autoProcessMovie(object): return results + def command_complete(self, url, params, headers, section): + try: + r = requests.get(url, params=params, headers=headers, stream=True, verify=False, timeout=(30, 60)) + except requests.ConnectionError: + logger.error("Unable to open URL: {0}".format(url), section) + return None + 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 None + else: + try: + return r.json()['state'] + except (ValueError, KeyError): + # ValueError catches simplejson's JSONDecodeError and json's ValueError + logger.error("{0} did not return expected json data.".format(section), section) + return None + + def CDH(self, url2, headers, section="MAIN"): + try: + r = requests.get(url2, params={}, headers=headers, stream=True, verify=False, timeout=(30, 60)) + except requests.ConnectionError: + logger.error("Unable to open URL: {0}".format(url2), section) + return False + 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 False + else: + try: + return r.json().get("enableCompletedDownloadHandling", False) + except ValueError: + # ValueError catches simplejson's JSONDecodeError and json's ValueError + return False + def process(self, section, dirName, inputName=None, status=0, clientAgent="manual", download_id="", inputCategory=None, failureLink=None): cfg = dict(core.CFG[section][inputCategory]) @@ -132,6 +165,7 @@ class autoProcessMovie(object): 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) + url2 = "{0}{1}:{2}{3}/api/config/downloadClient".format(protocol, host, port, web_root) headers = {'X-Api-Key': apikey} if not apikey: logger.info('No CouchPotato or Radarr apikey entered. Performing transcoder functions only') @@ -273,6 +307,14 @@ class autoProcessMovie(object): return [0, "{0}: Successfully post-processed {1}".format(section, inputName)] elif section == "Radarr": logger.postprocess("Radarr response: {0}".format(result['state'])) + try: + res = json.loads(r.content) + scan_id = int(res['id']) + logger.debug("Scan started with id: {0}".format(scan_id), section) + Started = True + except Exception as e: + logger.warning("No scan id was returned due to: {0}".format(e), section) + scan_id = None else: logger.error("FAILED: {0} scan was unable to finish for folder {1}. exiting!".format(method, dirName), section) @@ -353,6 +395,7 @@ class autoProcessMovie(object): logger.postprocess("Checking for status change, please stand by ...", section) if section == "CouchPotato": release = self.get_release(baseURL, imdbid, download_id, release_id) + scan_id = None else: release = None if release: @@ -368,6 +411,18 @@ class autoProcessMovie(object): return [0, "{0}: Successfully post-processed {1}".format(section, inputName)] except: pass + elif scan_id: + url = "{0}/{1}".format(baseURL, scan_id) + command_status = self.command_complete(url, params, headers, section) + if command_status: + logger.debug("The Scan command return status: {0}".format(command_status), section) + if command_status in ['completed']: + logger.debug("The Scan command has completed successfully. Renaming was successful.", section) + return [0, "{0}: Successfully post-processed {1}".format(section, inputName)] + elif command_status in ['failed']: + logger.debug("The Scan command has failed. Renaming was not successful.", section) + # return [1, "%s: Failed to post-process %s" % (section, inputName) ] + if not os.path.isdir(dirName): logger.postprocess("SUCCESS: Input Directory [{0}] has been processed and removed".format( dirName), section) @@ -378,10 +433,13 @@ class autoProcessMovie(object): dirName), section) return [0, "{0}: Successfully post-processed {1}".format(section, inputName)] - # pause and let CouchPotatoServer catch its breath + # pause and let CouchPotatoServer/Radarr catch its breath time.sleep(10 * wait_for) - # The status hasn't changed. we have waited 2 minutes which is more than enough. uTorrent can resume seeding now. + # The status hasn't changed. we have waited wait_for minutes which is more than enough. uTorrent can resume seeding now. + if section == "Radarr" and self.CDH(url2, headers, section=section): + logger.debug("The Scan command did not return status completed, but complete Download Handling is enabled. Passing back to {0}.".format(section), section) + return [status, "{0}: Complete DownLoad Handling is enabled. Passing back to {1}".format(section, section)] logger.warning( "{0} does not appear to have changed status after {1} minutes, Please check your logs.".format(inputName, wait_for), section) From 346c0952e179b7253d90cc1c91c552579d73baca Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Sat, 25 Feb 2017 10:08:05 +1030 Subject: [PATCH 19/26] minor fix for transcoder. --- core/transcoder/transcoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/transcoder/transcoder.py b/core/transcoder/transcoder.py index 67b4fe39..f8417f6b 100644 --- a/core/transcoder/transcoder.py +++ b/core/transcoder/transcoder.py @@ -117,7 +117,7 @@ def getVideoDetails(videofile, img=None, bitbucket=None): def buildCommands(file, newDir, movieName, bitbucket): if isinstance(file, basestring): inputFile = file - if '"concat:' in file: + if 'concat:' in file: file = file.split('|')[0].replace('concat:', '') video_details, result = getVideoDetails(file) dir, name = os.path.split(file) From c4fea222a9dd5a6ab1536bc7b3934341b72349d9 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Mon, 27 Feb 2017 16:16:52 +1030 Subject: [PATCH 20/26] fix non-iterable type. Fixes #1210 --- core/transcoder/transcoder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/transcoder/transcoder.py b/core/transcoder/transcoder.py index f8417f6b..19427aa6 100644 --- a/core/transcoder/transcoder.py +++ b/core/transcoder/transcoder.py @@ -382,7 +382,8 @@ def buildCommands(file, newDir, movieName, bitbucket): audio_cmd.extend(audio_cmd2) if core.AINCLUDE and core.ACODEC3: - for audio in audioStreams.extend(commentary): #add commentry tracks back here. + audioStreams.extend(commentary) #add commentry tracks back here. + for audio in audioStreams: if audio["index"] in a_mapped: continue used_audio += 1 From d042536ab7ab1ca1283787fb0ef9c7cd1fbbda40 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Tue, 28 Feb 2017 07:31:26 +1030 Subject: [PATCH 21/26] fix logging error. Fixes #1210 --- core/autoProcess/autoProcessMovie.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/autoProcess/autoProcessMovie.py b/core/autoProcess/autoProcessMovie.py index 3b5718ea..bb73151c 100644 --- a/core/autoProcess/autoProcessMovie.py +++ b/core/autoProcess/autoProcessMovie.py @@ -327,7 +327,7 @@ class autoProcessMovie(object): reportNzb(failureLink, clientAgent) if section == "Radarr": - logger.postprocess("FAILED: The download failed. Sending failed download to {0} for CDH processing".format(fork), section) + logger.postprocess("FAILED: The download failed. Sending failed download to {0} for CDH processing".format(section), 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: From f63300cc11beb8dbd711fe24ac13b32e495d0068 Mon Sep 17 00:00:00 2001 From: sugarfunk Date: Sat, 11 Mar 2017 13:06:48 -0500 Subject: [PATCH 22/26] DownloadedMovieScan updated to DownloadedMoviesScan Radarr devs updated API from DownloadedMovieScan to DownloadedMoviesScan https://github.com/Radarr/Radarr/issues/1123 --- core/autoProcess/autoProcessMovie.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/autoProcess/autoProcessMovie.py b/core/autoProcess/autoProcessMovie.py index bb73151c..54182f06 100644 --- a/core/autoProcess/autoProcessMovie.py +++ b/core/autoProcess/autoProcessMovie.py @@ -282,11 +282,11 @@ class autoProcessMovie(object): logger.postprocess("Starting {0} scan for {1}".format(method, inputName), section) if section == "Radarr": - payload = {'name': 'DownloadedMovieScan', 'path': params['media_folder'], 'downloadClientId': download_id} + payload = {'name': 'DownloadedMoviesScan', 'path': params['media_folder'], 'downloadClientId': download_id} if not download_id: payload.pop("downloadClientId") logger.debug("Opening URL: {0} with PARAMS: {1}".format(baseURL, payload), section) - logger.postprocess("Starting DownloadedMovieScan scan for {0}".format(inputName), section) + logger.postprocess("Starting DownloadedMoviesScan scan for {0}".format(inputName), section) try: if section == "CouchPotato": From fa334e685126292d80fec38e96312a8cc410c993 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Tue, 11 Apr 2017 20:16:46 +0930 Subject: [PATCH 23/26] add check to exception rename to not over-write exisiting. Fixes #1238 --- core/nzbToMediaSceneExceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/nzbToMediaSceneExceptions.py b/core/nzbToMediaSceneExceptions.py index 71ac28b2..3607797b 100644 --- a/core/nzbToMediaSceneExceptions.py +++ b/core/nzbToMediaSceneExceptions.py @@ -61,6 +61,8 @@ def strip_groups(filename): def rename_file(filename, newfilePath): + if os.path.isfile(newfilePath): + newfilePath = os.path.splitext(newfilePath)[0] + ".NTM" + os.path.splitext(newfilePath)[1] logger.debug("Replacing file name {old} with download name {new}".format (old=filename, new=newfilePath), "EXCEPTION") try: From fb786c44f53e794ebbe564285c8278b07212ccda Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Mon, 24 Apr 2017 11:24:37 +0930 Subject: [PATCH 24/26] don't try and process when no api/user. Fixes #1240 --- core/autoProcess/autoProcessMovie.py | 4 ++++ core/autoProcess/autoProcessTV.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/core/autoProcess/autoProcessMovie.py b/core/autoProcess/autoProcessMovie.py index 54182f06..3665e670 100644 --- a/core/autoProcess/autoProcessMovie.py +++ b/core/autoProcess/autoProcessMovie.py @@ -263,6 +263,10 @@ class autoProcessMovie(object): logger.debug('Renaming: {0} to: {1}'.format(video, video2)) os.rename(video, video2) + if not apikey: #If only using Transcoder functions, exit here. + logger.info('No CouchPotato or Radarr apikey entered. Processing completed.') + return [0, "{0}: Successfully post-processed {1}".format(section, inputName)] + params = {} if download_id: params['downloader'] = downloader or clientAgent diff --git a/core/autoProcess/autoProcessTV.py b/core/autoProcess/autoProcessTV.py index 4c161787..4ebc9735 100644 --- a/core/autoProcess/autoProcessTV.py +++ b/core/autoProcess/autoProcessTV.py @@ -226,6 +226,9 @@ class autoProcessTV(object): [fork_params.pop(k) for k, v in fork_params.items() if v is None] if status == 0: + if not username and not apikey: + logger.info('No SickBeard username or Sonarr apikey entered. Processing completed.') + return [0, "{0}: Successfully post-processed {1}".format(section, inputName)] logger.postprocess("SUCCESS: The download succeeded, sending a post-process request", section) else: core.FAILED = True From cd2e466b307532213fc45b4e92c7927a9aef0d65 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Tue, 25 Apr 2017 19:36:41 +0930 Subject: [PATCH 25/26] remove no user bypass for SickBeard. Fixes #1244 --- core/autoProcess/autoProcessTV.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/autoProcess/autoProcessTV.py b/core/autoProcess/autoProcessTV.py index 4ebc9735..69177b80 100644 --- a/core/autoProcess/autoProcessTV.py +++ b/core/autoProcess/autoProcessTV.py @@ -226,8 +226,8 @@ class autoProcessTV(object): [fork_params.pop(k) for k, v in fork_params.items() if v is None] if status == 0: - if not username and not apikey: - logger.info('No SickBeard username or Sonarr apikey entered. Processing completed.') + if not apikey: + logger.info('No Sonarr apikey entered. Processing completed.') return [0, "{0}: Successfully post-processed {1}".format(section, inputName)] logger.postprocess("SUCCESS: The download succeeded, sending a post-process request", section) else: From bd4a448d26d1cb0e43c03bcce46a7a21236b5349 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Wed, 26 Apr 2017 16:27:49 +0930 Subject: [PATCH 26/26] fix no api bypass to apply to Sonarr only. --- core/autoProcess/autoProcessTV.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/autoProcess/autoProcessTV.py b/core/autoProcess/autoProcessTV.py index 69177b80..02cf3514 100644 --- a/core/autoProcess/autoProcessTV.py +++ b/core/autoProcess/autoProcessTV.py @@ -226,7 +226,7 @@ class autoProcessTV(object): [fork_params.pop(k) for k, v in fork_params.items() if v is None] if status == 0: - if not apikey: + if section == "NzbDrone" and not apikey: logger.info('No Sonarr apikey entered. Processing completed.') return [0, "{0}: Successfully post-processed {1}".format(section, inputName)] logger.postprocess("SUCCESS: The download succeeded, sending a post-process request", section)