From 80ef0d094e1c316ff9e688d34489b4a1846c6895 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Thu, 19 Sep 2019 20:47:13 +1200 Subject: [PATCH 01/11] Fix autofork fallback. #163 --- core/forks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/forks.py b/core/forks.py index f49ed5a9..ac73c547 100644 --- a/core/forks.py +++ b/core/forks.py @@ -168,7 +168,8 @@ def auto_fork(section, input_category): else: logger.info('{section}:{category} fork auto-detection failed'.format (section=section, category=input_category)) - fork = core.FORKS.items()[core.FORKS.keys().index(core.FORK_DEFAULT)] + fork = list(core.FORKS.items())[list(core.FORKS.keys()).index(core.FORK_DEFAULT)] + logger.info('{section}:{category} fork set to {fork}'.format (section=section, category=input_category, fork=fork[0])) From 1814bd5ae1a40885217e7a4427768b689b93542b Mon Sep 17 00:00:00 2001 From: Sergio Cambra Date: Mon, 4 Nov 2019 00:05:00 +0100 Subject: [PATCH 02/11] add watcher3 integration (#1665) --- TorrentToMedia.py | 8 ++--- _config.yml | 1 + autoProcessMedia.cfg.spec | 60 +++++++++++++++++++++++++++---------- core/auto_process/movies.py | 41 ++++++++++++++++++++++++- nzbToMedia.py | 4 +-- 5 files changed, 92 insertions(+), 22 deletions(-) create mode 100644 _config.yml diff --git a/TorrentToMedia.py b/TorrentToMedia.py index 39b08329..28f09d76 100755 --- a/TorrentToMedia.py +++ b/TorrentToMedia.py @@ -79,7 +79,7 @@ def process_torrent(input_directory, input_name, input_category, input_hash, inp if section is None: #Check for user_scripts for 'ALL' and 'UNCAT' if usercat in core.CATEGORIES: section = core.CFG.findsection('ALL').isenabled() - usercat = 'ALL' + usercat = 'ALL' else: section = core.CFG.findsection('UNCAT').isenabled() usercat = 'UNCAT' @@ -213,7 +213,7 @@ def process_torrent(input_directory, input_name, input_category, input_hash, inp core.flatten(output_destination) # Now check if video files exist in destination: - if section_name in ['SickBeard', 'NzbDrone', 'Sonarr', 'CouchPotato', 'Radarr']: + if section_name in ['SickBeard', 'NzbDrone', 'Sonarr', 'CouchPotato', 'Radarr', 'Watcher3']: num_videos = len( core.list_media_files(output_destination, media=True, audio=False, meta=False, archives=False)) if num_videos > 0: @@ -227,7 +227,7 @@ def process_torrent(input_directory, input_name, input_category, input_hash, inp # Only these sections can handling failed downloads # so make sure everything else gets through without the check for failed - if section_name not in ['CouchPotato', 'Radarr', 'SickBeard', 'NzbDrone', 'Sonarr']: + if section_name not in ['CouchPotato', 'Radarr', 'SickBeard', 'NzbDrone', 'Sonarr', 'Watcher3']: status = 0 logger.info('Calling {0}:{1} to post-process:{2}'.format(section_name, usercat, input_name)) @@ -241,7 +241,7 @@ def process_torrent(input_directory, input_name, input_category, input_hash, inp ) if section_name == 'UserScript': result = external_script(output_destination, input_name, input_category, section) - elif section_name in ['CouchPotato', 'Radarr']: + elif section_name in ['CouchPotato', 'Radarr', 'Watcher3']: result = movies.process(section_name, output_destination, input_name, status, client_agent, input_hash, input_category) elif section_name in ['SickBeard', 'NzbDrone', 'Sonarr']: if input_hash: diff --git a/_config.yml b/_config.yml new file mode 100644 index 00000000..c4192631 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/autoProcessMedia.cfg.spec b/autoProcessMedia.cfg.spec index e3d11cb6..7af48e01 100644 --- a/autoProcessMedia.cfg.spec +++ b/autoProcessMedia.cfg.spec @@ -12,7 +12,7 @@ git_user = # GitHUB branch for repo git_branch = - # Enable/Disable forceful cleaning of leftover files following postprocess + # Enable/Disable forceful cleaning of leftover files following postprocess force_clean = 0 # Enable/Disable logging debug messages to nzbtomedia.log log_debug = 0 @@ -36,7 +36,7 @@ [Posix] ### Process priority setting for External commands (Extractor and Transcoder) on Posix (Unix/Linux/OSX) systems. # Set the Niceness value for the nice command. These range from -20 (most favorable to the process) to 19 (least favorable to the process). - # If entering an integer e.g 'niceness = 4', this is added to the nice command and passed as 'nice -n4' (Default). + # If entering an integer e.g 'niceness = 4', this is added to the nice command and passed as 'nice -n4' (Default). # If entering a comma separated list e.g. 'niceness = nice,4' this will be passed as 'nice 4' (Safer). niceness = nice,-n0 # Set the ionice scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle. @@ -111,6 +111,36 @@ ##### Set to define import behavior Move or Copy importMode = Copy +[Watcher3] + #### autoProcessing for Movies + #### movie - category that gets called for post-processing with CPS + [[movie]] + enabled = 0 + apikey = + host = localhost + port = 9090 + ###### ADVANCED USE - ONLY EDIT IF YOU KNOW WHAT YOU'RE DOING ###### + ssl = 0 + web_root = + # api key for www.omdbapi.com (used as alternative to imdb) + omdbapikey = + # Enable/Disable linking for Torrents + Torrent_NoLink = 0 + keep_archive = 1 + delete_failed = 0 + wait_for = 0 + extract = 1 + # 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 Watcher3 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 = + ##### Set the recursive directory permissions to the following (0 to disable) + chmodDirectory = 0 + [SickBeard] #### autoProcessing for TV Series #### tv - category that gets called for post-processing with SB @@ -266,7 +296,7 @@ apikey = host = localhost port = 8085 - ###### + ###### library = Set to path where you want the processed games to be moved to. ###### ADVANCED USE - ONLY EDIT IF YOU KNOW WHAT YOU'RE DOING ###### ssl = 0 @@ -312,7 +342,7 @@ [Network] # Enter Mount points as LocalPath,RemotePath and separate each pair with '|' # e.g. MountPoints = /volume1/Public/,E:\|/volume2/share/,\\NAS\ - mount_points = + mount_points = [Nzb] ###### clientAgent - Supported clients: sabnzbd, nzbget @@ -331,7 +361,7 @@ useLink = hard ###### outputDirectory - Default output directory (categories will be appended as sub directory to outputDirectory) outputDirectory = /abs/path/to/complete/ - ###### Enter the default path to your default download directory (non-category downloads). this directory is protected by safe_mode. + ###### Enter the default path to your default download directory (non-category downloads). this directory is protected by safe_mode. default_downloadDirectory = ###### Other categories/labels defined for your downloader. Does not include CouchPotato, SickBeard, HeadPhones, Mylar categories. categories = music_videos,pictures,software,manual @@ -374,15 +404,15 @@ plex_host = localhost plex_port = 32400 plex_token = - plex_ssl = 0 + plex_ssl = 0 # Enter Plex category to section mapping as Category,section and separate each pair with '|' # e.g. plex_sections = movie,3|tv,4 - plex_sections = + plex_sections = [Transcoder] # getsubs. enable to download subtitles. getSubs = 0 - # subLanguages. create a list of languages in the order you want them in your subtitles. + # subLanguages. create a list of languages in the order you want them in your subtitles. subLanguages = eng,spa,fra # transcode. enable to use transcoder transcode = 0 @@ -397,7 +427,7 @@ # outputQualityPercent. used as -q:a value. 0 will disable this from being used. outputQualityPercent = 0 # outputVideoPath. Set path you want transcoded videos moved to. Leave blank to disable. - outputVideoPath = + outputVideoPath = # processOutput. 1 will send the outputVideoPath to SickBeard/CouchPotato. 0 will send original files. processOutput = 0 # audioLanguage. set the 3 letter language code you want as your primary audio track. @@ -427,7 +457,7 @@ #### Define custom settings below. outputVideoExtension = .mp4 outputVideoCodec = libx264 - VideoCodecAllow = + VideoCodecAllow = outputVideoPreset = medium outputVideoResolution = 1920:1080 outputVideoFramerate = 24 @@ -435,15 +465,15 @@ outputVideoCRF = 19 outputVideoLevel = 3.1 outputAudioCodec = ac3 - AudioCodecAllow = + AudioCodecAllow = outputAudioChannels = 6 outputAudioBitrate = 640k outputAudioTrack2Codec = libfaac - AudioCodec2Allow = - outputAudioTrack2Channels = 2 + AudioCodec2Allow = + outputAudioTrack2Channels = 2 outputAudioTrack2Bitrate = 128000 outputAudioOtherCodec = libmp3lame - AudioOtherCodecAllow = + AudioOtherCodecAllow = outputAudioOtherChannels = outputAudioOtherBitrate = 128000 outputSubtitleCodec = @@ -500,4 +530,4 @@ # enter a list (comma separated) of Group Tags you want removed from filenames to help with subtitle matching. # e.g remove_group = [rarbag],-NZBgeek # be careful if your "group" is a common "real" word. Please report if you have any group replacements that would fall in this category. - remove_group = + remove_group = diff --git a/core/auto_process/movies.py b/core/auto_process/movies.py index 2661edd1..e7439aa0 100644 --- a/core/auto_process/movies.py +++ b/core/auto_process/movies.py @@ -72,6 +72,8 @@ def process(section, dir_name, input_name=None, status=0, client_agent='manual', base_url = '{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 section == 'Watcher3': + base_url = '{0}{1}:{2}{3}/postprocessing'.format(protocol, host, port, web_root) if not apikey: logger.info('No CouchPotato or Radarr apikey entered. Performing transcoder functions only') release = None @@ -178,7 +180,7 @@ def process(section, dir_name, input_name=None, status=0, client_agent='manual', os.rename(video, video2) if not apikey: # If only using Transcoder functions, exit here. - logger.info('No CouchPotato or Radarr apikey entered. Processing completed.') + logger.info('No CouchPotato or Radarr or Watcher3 apikey entered. Processing completed.') return ProcessResult( message='{0}: Successfully post-processed {1}'.format(section, input_name), status_code=0, @@ -210,9 +212,20 @@ def process(section, dir_name, input_name=None, status=0, client_agent='manual', logger.debug('Opening URL: {0} with PARAMS: {1}'.format(base_url, payload), section) logger.postprocess('Starting DownloadedMoviesScan scan for {0}'.format(input_name), section) + if section == 'Watcher3': + if input_name and os.path.isfile(os.path.join(dir_name, input_name)): + params['media_folder'] = os.path.join(params['media_folder'], input_name) + payload = {'apikey': apikey, 'path': params['media_folder'], 'guid': download_id, 'mode': 'complete'} + if not download_id: + payload.pop('guid') + logger.debug('Opening URL: {0} with PARAMS: {1}'.format(base_url, payload), section) + logger.postprocess('Starting postprocessing scan for {0}'.format(input_name), section) + try: if section == 'CouchPotato': r = requests.get(url, params=params, verify=False, timeout=(30, 1800)) + elif section == 'Watcher3': + r = requests.post(base_url, data=payload, verify=False, timeout=(30, 1800)) else: r = requests.post(base_url, data=json.dumps(payload), headers=headers, stream=True, verify=False, timeout=(30, 1800)) except requests.ConnectionError: @@ -245,6 +258,18 @@ def process(section, dir_name, input_name=None, status=0, client_agent='manual', except Exception as e: logger.warning('No scan id was returned due to: {0}'.format(e), section) scan_id = None + elif section == 'Watcher3' and result['status'] == 'finished': + logger.postprocess('Watcher3 updated status to {0}'.format(result['tasks']['update_movie_status'])) + if result['tasks']['update_movie_status'] == 'Finished': + return ProcessResult( + message='{0}: Successfully post-processed {1}'.format(section, input_name), + status_code=status, + ) + else: + return ProcessResult( + message='{0}: Failed to post-process - changed status to {1}'.format(section, result['tasks']['update_movie_status']), + status_code=1, + ) else: logger.error('FAILED: {0} scan was unable to finish for folder {1}. exiting!'.format(method, dir_name), section) @@ -264,6 +289,20 @@ def process(section, dir_name, input_name=None, status=0, client_agent='manual', message='{0}: Sending failed download back to {0}'.format(section), status_code=1, # Return as failed to flag this in the downloader. ) # Return failed flag, but log the event as successful. + elif section == 'Watcher3': + logger.postprocess('Sending failed download to {0} for CDH processing'.format(section), section) + path = remote_dir(dir_name) if remote_path else dir_name + if input_name and os.path.isfile(os.path.join(dir_name, input_name)): + path = os.path.join(path, input_name) + payload = {'apikey': apikey, 'path': path, 'guid': download_id, 'mode': 'failed'} + r = requests.post(base_url, data=payload, verify=False, timeout=(30, 1800)) + result = r.json() + logger.postprocess('Watcher3 response: {0}'.format(result)) + if result['status'] == 'finished': + return ProcessResult( + message='{0}: Sending failed download back to {0}'.format(section), + status_code=1, # Return as failed to flag this in the downloader. + ) # Return failed flag, but log the event as successful. if delete_failed and os.path.isdir(dir_name) and not os.path.dirname(dir_name) == dir_name: logger.postprocess('Deleting failed files and folder {0}'.format(dir_name), section) diff --git a/nzbToMedia.py b/nzbToMedia.py index 2ab16d59..1c04cd7b 100755 --- a/nzbToMedia.py +++ b/nzbToMedia.py @@ -5,7 +5,7 @@ ### NZBGET POST-PROCESSING SCRIPT ### # Post-Process to CouchPotato, SickBeard, Sonarr, Mylar, Gamez, HeadPhones, -# LazyLibrarian, Radarr, Lidarr +# LazyLibrarian, Radarr, Lidarr, Watcher3 # # This script sends the download to your automated media management servers. # @@ -799,7 +799,7 @@ def process(input_directory, input_name=None, status=0, client_agent='manual', d logger.info('Calling {0}:{1} to post-process:{2}'.format(section_name, input_category, input_name)) - if section_name in ['CouchPotato', 'Radarr']: + if section_name in ['CouchPotato', 'Radarr', 'Watcher3']: result = movies.process(section_name, input_directory, input_name, status, client_agent, download_id, input_category, failure_link) elif section_name in ['SickBeard', 'NzbDrone', 'Sonarr']: result = tv.process(section_name, input_directory, input_name, status, client_agent, download_id, input_category, failure_link) From c92588c3bea0b614a4b70cc77d036139c5775f80 Mon Sep 17 00:00:00 2001 From: Sergio Cambra Date: Mon, 4 Nov 2019 00:10:20 +0100 Subject: [PATCH 03/11] fix downloading subtitles, no provider was registered (#1664) --- core/plugins/subtitles.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/plugins/subtitles.py b/core/plugins/subtitles.py index df37e532..181975e5 100644 --- a/core/plugins/subtitles.py +++ b/core/plugins/subtitles.py @@ -11,6 +11,10 @@ import subliminal import core from core import logger +for provider in subliminal.provider_manager.internal_extensions: + if provider not in [str(x) for x in subliminal.provider_manager.list_entry_points()]: + subliminal.provider_manager.register(str(provider)) + def import_subs(filename): if not core.GETSUBS: From fde87148627ff66650201087dab840dfd6fb5d70 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Mon, 4 Nov 2019 12:28:35 +1300 Subject: [PATCH 04/11] Update all qBittorrent WebAPI paths for client v4.1.0+ (#1666) --- libs/common/qbittorrent/client.py | 234 +++++++++++------------------- 1 file changed, 83 insertions(+), 151 deletions(-) diff --git a/libs/common/qbittorrent/client.py b/libs/common/qbittorrent/client.py index 73d8d753..08df1e63 100644 --- a/libs/common/qbittorrent/client.py +++ b/libs/common/qbittorrent/client.py @@ -1,7 +1,6 @@ import requests import json - class LoginRequired(Exception): def __str__(self): return 'Please login first.' @@ -15,7 +14,7 @@ class Client(object): self.url = url session = requests.Session() - check_prefs = session.get(url+'query/preferences') + check_prefs = session.get(url+'api/v2/app/preferences') if check_prefs.status_code == 200: self._is_authenticated = True @@ -24,9 +23,9 @@ class Client(object): elif check_prefs.status_code == 404: self._is_authenticated = False raise RuntimeError(""" - This wrapper only supports qBittorrent applications - with version higher than 3.1.x. - Please use the latest qBittorrent release. + This wrapper only supports qBittorrent applications with + version higher than 4.1.0 (which implemented Web API v2.0). + Please use the latest qBittorrent release. """) else: @@ -35,10 +34,8 @@ class Client(object): def _get(self, endpoint, **kwargs): """ Method to perform GET request on the API. - :param endpoint: Endpoint of the API. :param kwargs: Other keyword arguments for requests. - :return: Response of the GET request. """ return self._request(endpoint, 'get', **kwargs) @@ -46,11 +43,9 @@ class Client(object): def _post(self, endpoint, data, **kwargs): """ Method to perform POST request on the API. - :param endpoint: Endpoint of the API. :param data: POST DATA for the request. :param kwargs: Other keyword arguments for requests. - :return: Response of the POST request. """ return self._request(endpoint, 'post', data, **kwargs) @@ -58,12 +53,10 @@ class Client(object): def _request(self, endpoint, method, data=None, **kwargs): """ Method to hanle both GET and POST requests. - :param endpoint: Endpoint of the API. :param method: Method of HTTP request. :param data: POST DATA for the request. :param kwargs: Other keyword arguments. - :return: Response for the request. """ final_url = self.url + endpoint @@ -93,18 +86,15 @@ class Client(object): def login(self, username='admin', password='admin'): """ Method to authenticate the qBittorrent Client. - Declares a class attribute named ``session`` which stores the authenticated session if the login is correct. Else, shows the login error. - :param username: Username. :param password: Password. - :return: Response to login request to the API. """ self.session = requests.Session() - login = self.session.post(self.url+'login', + login = self.session.post(self.url+'api/v2/auth/login', data={'username': username, 'password': password}) if login.text == 'Ok.': @@ -116,7 +106,7 @@ class Client(object): """ Logout the current session. """ - response = self._get('logout') + response = self._get('api/v2/auth/logout') self._is_authenticated = False return response @@ -125,39 +115,31 @@ class Client(object): """ Get qBittorrent version. """ - return self._get('version/qbittorrent') + return self._get('api/v2/app/version') @property def api_version(self): """ Get WEB API version. """ - return self._get('version/api') - - @property - def api_min_version(self): - """ - Get minimum WEB API version. - """ - return self._get('version/api_min') + return self._get('api/v2/app/webapiVersion') def shutdown(self): """ Shutdown qBittorrent. """ - return self._get('command/shutdown') + return self._get('api/v2/app/shutdown') def torrents(self, **filters): """ Returns a list of torrents matching the supplied filters. - :param filter: Current status of the torrents. :param category: Fetch all torrents with the supplied label. :param sort: Sort torrents by. :param reverse: Enable reverse sorting. :param limit: Limit the number of torrents returned. :param offset: Set offset (if less than 0, offset from end). - + :param hashes: Filter by hashes. Can contain multiple hashes separated by |. :return: list() of torrent with matching filter. """ params = {} @@ -166,46 +148,42 @@ class Client(object): name = 'filter' if name == 'status' else name params[name] = value - return self._get('query/torrents', params=params) + return self._get('api/v2/torrents/info', params=params) def get_torrent(self, infohash): """ Get details of the torrent. - :param infohash: INFO HASH of the torrent. """ - return self._get('query/propertiesGeneral/' + infohash.lower()) + return self._get('api/v2/torrents/properties', params={'hash': infohash.lower()}) def get_torrent_trackers(self, infohash): """ Get trackers for the torrent. - :param infohash: INFO HASH of the torrent. """ - return self._get('query/propertiesTrackers/' + infohash.lower()) + return self._get('api/v2/torrents/trackers', params={'hash': infohash.lower()}) def get_torrent_webseeds(self, infohash): """ Get webseeds for the torrent. - :param infohash: INFO HASH of the torrent. """ - return self._get('query/propertiesWebSeeds/' + infohash.lower()) + return self._get('api/v2/torrents/webseeds', params={'hash': infohash.lower()}) def get_torrent_files(self, infohash): """ Get list of files for the torrent. - :param infohash: INFO HASH of the torrent. """ - return self._get('query/propertiesFiles/' + infohash.lower()) + return self._get('api/v2/torrents/files', params={'hash': infohash.lower()}) @property def global_transfer_info(self): """ Get JSON data of the global transfer info of qBittorrent. """ - return self._get('query/transferInfo') + return self._get('api/v2/transfer/info') @property def preferences(self): @@ -214,39 +192,27 @@ class Client(object): Can also be used to assign individual preferences. For setting multiple preferences at once, see ``set_preferences`` method. - Note: Even if this is a ``property``, to fetch the current preferences dict, you are required to call it like a bound method. - Wrong:: - qb.preferences - Right:: - qb.preferences() - """ - prefs = self._get('query/preferences') + prefs = self._get('api/v2/app/preferences') class Proxy(Client): """ Proxy class to to allow assignment of individual preferences. this class overrides some methods to ease things. - Because of this, settings can be assigned like:: - In [5]: prefs = qb.preferences() - In [6]: prefs['autorun_enabled'] Out[6]: True - In [7]: prefs['autorun_enabled'] = False - In [8]: prefs['autorun_enabled'] Out[8]: False - """ def __init__(self, url, prefs, auth, session): @@ -270,78 +236,74 @@ class Client(object): def sync(self, rid=0): """ Sync the torrents by supplied LAST RESPONSE ID. - Read more @ http://git.io/vEgXr - + Read more @ https://git.io/fxgB8 :param rid: Response ID of last request. """ - return self._get('sync/maindata', params={'rid': rid}) + return self._get('api/v2/sync/maindata', params={'rid': rid}) def download_from_link(self, link, **kwargs): """ Download torrent using a link. - :param link: URL Link or list of. :param savepath: Path to download the torrent. :param category: Label or Category of the torrent(s). - :return: Empty JSON data. """ - # old:new format - old_arg_map = {'save_path': 'savepath'} # , 'label': 'category'} - - # convert old option names to new option names - options = kwargs.copy() - for old_arg, new_arg in old_arg_map.items(): - if options.get(old_arg) and not options.get(new_arg): - options[new_arg] = options[old_arg] - - options['urls'] = link - - # workaround to send multipart/formdata request - # http://stackoverflow.com/a/23131823/4726598 - dummy_file = {'_dummy': (None, '_dummy')} - - return self._post('command/download', data=options, files=dummy_file) + # qBittorrent requires adds to be done with multipath/form-data + # POST requests for both URLs and .torrent files. Info on this + # can be found here, and here: + # http://docs.python-requests.org/en/master/user/quickstart/#post-a-multipart-encoded-file + # http://docs.python-requests.org/en/master/user/advanced/#post-multiple-multipart-encoded-files + if isinstance(link, list): + links = '\n'.join(link) + else: + links = link + torrent_data = {} + torrent_data['urls'] = (None, links) + for k, v in kwargs.iteritems(): + torrent_data[k] = (None, v) + return self._post('api/v2/torrents/add', data=None, files=torrent_data) def download_from_file(self, file_buffer, **kwargs): """ Download torrent using a file. - :param file_buffer: Single file() buffer or list of. :param save_path: Path to download the torrent. :param label: Label of the torrent(s). - :return: Empty JSON data. """ + # qBittorrent requires adds to be done with multipath/form-data + # POST requests for both URLs and .torrent files. Info on this + # can be found here, and here: + # http://docs.python-requests.org/en/master/user/quickstart/#post-a-multipart-encoded-file + # http://docs.python-requests.org/en/master/user/advanced/#post-multiple-multipart-encoded-files if isinstance(file_buffer, list): - torrent_files = {} - for i, f in enumerate(file_buffer): - torrent_files.update({'torrents%s' % i: f}) + torrent_data = [] + for f in file_buffer: + fname = f.name + torrent_data.append(('torrents', (fname, f))) else: - torrent_files = {'torrents': file_buffer} + fname = file_buffer.name + torrent_data = [('torrents', (fname, file_buffer))] + for k, v in kwargs.iteritems(): + torrent_data.append((k, (None, v))) - data = kwargs.copy() - - if data.get('save_path'): - data.update({'savepath': data['save_path']}) - return self._post('command/upload', data=data, files=torrent_files) + return self._post('api/v2/torrents/add', data=None, files=torrent_data) def add_trackers(self, infohash, trackers): """ Add trackers to a torrent. - :param infohash: INFO HASH of torrent. :param trackers: Trackers. """ data = {'hash': infohash.lower(), 'urls': trackers} - return self._post('command/addTrackers', data=data) + return self._post('api/v2/torrents/addTrackers', data=data) @staticmethod def _process_infohash_list(infohash_list): """ Method to convert the infohash_list to qBittorrent API friendly values. - :param infohash_list: List of infohash. """ if isinstance(infohash_list, list): @@ -353,142 +315,122 @@ class Client(object): def pause(self, infohash): """ Pause a torrent. - :param infohash: INFO HASH of torrent. """ - return self._post('command/pause', data={'hash': infohash.lower()}) + return self._post('api/v2/torrents/pause', data={'hashes': infohash.lower()}) def pause_all(self): """ Pause all torrents. """ - return self._get('command/pauseAll') + return self._post('api/v2/torrents/pause', data={'hashes': 'all'}) def pause_multiple(self, infohash_list): """ Pause multiple torrents. - :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) - return self._post('command/pauseAll', data=data) - - def set_label(self, infohash_list, label): - """ - Set the label on multiple torrents. - IMPORTANT: OLD API method, kept as it is to avoid breaking stuffs. - - :param infohash_list: Single or list() of infohashes. - """ - data = self._process_infohash_list(infohash_list) - data['label'] = label - return self._post('command/setLabel', data=data) + return self._post('api/v2/torrents/pause', data=data) def set_category(self, infohash_list, category): """ Set the category on multiple torrents. - :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) data['category'] = category - return self._post('command/setCategory', data=data) + return self._post('api/v2/torrents/setCategory', data=data) def resume(self, infohash): """ Resume a paused torrent. - :param infohash: INFO HASH of torrent. """ - return self._post('command/resume', data={'hash': infohash.lower()}) + return self._post('api/v2/torrents/resume', data={'hashes': infohash.lower()}) def resume_all(self): """ Resume all torrents. """ - return self._get('command/resumeAll') + return self._get('api/v2/torrents/resume', data={'hashes': 'all'}) def resume_multiple(self, infohash_list): """ Resume multiple paused torrents. - :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) - return self._post('command/resumeAll', data=data) + return self._post('api/v2/torrents/resume', data=data) def delete(self, infohash_list): """ Delete torrents. - :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) - return self._post('command/delete', data=data) + data['deleteFiles'] = 'false' + return self._post('api/v2/torrents/delete', data=data) def delete_permanently(self, infohash_list): """ Permanently delete torrents. - + *** WARNING : This will instruct qBittorrent to delete files + *** from your hard disk. Use with caution. :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) - return self._post('command/deletePerm', data=data) + data['deleteFiles'] = 'true' + return self._post('api/v2/torrents/delete', data=data) def recheck(self, infohash_list): """ Recheck torrents. - :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) - return self._post('command/recheck', data=data) + return self._post('api/v2/torrents/recheck', data=data) def increase_priority(self, infohash_list): """ Increase priority of torrents. - :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) - return self._post('command/increasePrio', data=data) + return self._post('api/v2/torrents/increasePrio', data=data) def decrease_priority(self, infohash_list): """ Decrease priority of torrents. - :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) - return self._post('command/decreasePrio', data=data) + return self._post('api/v2/torrents/decreasePrio', data=data) def set_max_priority(self, infohash_list): """ Set torrents to maximum priority level. - :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) - return self._post('command/topPrio', data=data) + return self._post('api/v2/torrents/topPrio', data=data) def set_min_priority(self, infohash_list): """ Set torrents to minimum priority level. - :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) - return self._post('command/bottomPrio', data=data) + return self._post('api/v2/torrents/bottomPrio', data=data) def set_file_priority(self, infohash, file_id, priority): """ Set file of a torrent to a supplied priority level. - :param infohash: INFO HASH of torrent. :param file_id: ID of the file to set priority. :param priority: Priority level of the file. """ - if priority not in [0, 1, 2, 7]: + if priority not in [0, 1, 6, 7]: raise ValueError("Invalid priority, refer WEB-UI docs for info.") elif not isinstance(file_id, int): raise TypeError("File ID must be an int") @@ -497,7 +439,7 @@ class Client(object): 'id': file_id, 'priority': priority} - return self._post('command/setFilePrio', data=data) + return self._post('api/v2/torrents/filePrio', data=data) # Get-set global download and upload speed limits. @@ -505,15 +447,14 @@ class Client(object): """ Get global download speed limit. """ - return self._get('command/getGlobalDlLimit') + return self._get('api/v2/transfer/downloadLimit') def set_global_download_limit(self, limit): """ Set global download speed limit. - :param limit: Speed limit in bytes. """ - return self._post('command/setGlobalDlLimit', data={'limit': limit}) + return self._post('api/v2/transfer/setDownloadLimit', data={'limit': limit}) global_download_limit = property(get_global_download_limit, set_global_download_limit) @@ -522,15 +463,14 @@ class Client(object): """ Get global upload speed limit. """ - return self._get('command/getGlobalUpLimit') + return self._get('api/v2/transfer/uploadLimit') def set_global_upload_limit(self, limit): """ Set global upload speed limit. - :param limit: Speed limit in bytes. """ - return self._post('command/setGlobalUpLimit', data={'limit': limit}) + return self._post('api/v2/transfer/setUploadLimit', data={'limit': limit}) global_upload_limit = property(get_global_upload_limit, set_global_upload_limit) @@ -539,61 +479,56 @@ class Client(object): def get_torrent_download_limit(self, infohash_list): """ Get download speed limit of the supplied torrents. - :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) - return self._post('command/getTorrentsDlLimit', data=data) + return self._post('api/v2/torrents/downloadLimit', data=data) def set_torrent_download_limit(self, infohash_list, limit): """ Set download speed limit of the supplied torrents. - :param infohash_list: Single or list() of infohashes. :param limit: Speed limit in bytes. """ data = self._process_infohash_list(infohash_list) data.update({'limit': limit}) - return self._post('command/setTorrentsDlLimit', data=data) + return self._post('api/v2/torrents/setDownloadLimit', data=data) def get_torrent_upload_limit(self, infohash_list): """ Get upoload speed limit of the supplied torrents. - :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) - return self._post('command/getTorrentsUpLimit', data=data) + return self._post('api/v2/torrents/uploadLimit', data=data) def set_torrent_upload_limit(self, infohash_list, limit): """ Set upload speed limit of the supplied torrents. - :param infohash_list: Single or list() of infohashes. :param limit: Speed limit in bytes. """ data = self._process_infohash_list(infohash_list) data.update({'limit': limit}) - return self._post('command/setTorrentsUpLimit', data=data) + return self._post('api/v2/torrents/setUploadLimit', data=data) # setting preferences def set_preferences(self, **kwargs): """ Set preferences of qBittorrent. - Read all possible preferences @ http://git.io/vEgDQ - + Read all possible preferences @ https://git.io/fx2Y9 :param kwargs: set preferences in kwargs form. """ json_data = "json={}".format(json.dumps(kwargs)) headers = {'content-type': 'application/x-www-form-urlencoded'} - return self._post('command/setPreferences', data=json_data, + return self._post('api/v2/app/setPreferences', data=json_data, headers=headers) def get_alternative_speed_status(self): """ Get Alternative speed limits. (1/0) """ - return self._get('command/alternativeSpeedLimitsEnabled') + return self._get('api/v2/transfer/speedLimitsMode') alternative_speed_status = property(get_alternative_speed_status) @@ -601,33 +536,30 @@ class Client(object): """ Toggle alternative speed limits. """ - return self._get('command/toggleAlternativeSpeedLimits') + return self._get('api/v2/transfer/toggleSpeedLimitsMode') def toggle_sequential_download(self, infohash_list): """ Toggle sequential download in supplied torrents. - :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) - return self._post('command/toggleSequentialDownload', data=data) + return self._post('api/v2/torrents/toggleSequentialDownload', data=data) def toggle_first_last_piece_priority(self, infohash_list): """ Toggle first/last piece priority of supplied torrents. - :param infohash_list: Single or list() of infohashes. """ data = self._process_infohash_list(infohash_list) - return self._post('command/toggleFirstLastPiecePrio', data=data) + return self._post('api/v2/torrents/toggleFirstLastPiecePrio', data=data) def force_start(self, infohash_list, value=True): """ Force start selected torrents. - :param infohash_list: Single or list() of infohashes. :param value: Force start value (bool) """ data = self._process_infohash_list(infohash_list) data.update({'value': json.dumps(value)}) - return self._post('command/setForceStart', data=data) + return self._post('api/v2/torrents/setForceStart', data=data) From 70ab7d3d611475d0085082aade49d7163e6eb0d1 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Mon, 4 Nov 2019 13:17:38 +1300 Subject: [PATCH 05/11] Add Watcher3 Config (#1667) * Set NZBGet config #1665 --- core/configuration.py | 37 +++++- nzbToMedia.py | 48 ++++++++ nzbToWatcher3.py | 268 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 nzbToWatcher3.py diff --git a/core/configuration.py b/core/configuration.py index c8e2a465..13dc8f72 100644 --- a/core/configuration.py +++ b/core/configuration.py @@ -150,7 +150,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', 'raCategory', 'ndCategory']: + if option in ['category', 'cpsCategory', 'sbCategory', 'hpCategory', 'mlCategory', 'gzCategory', 'raCategory', 'ndCategory', 'W3Category']: if not isinstance(value, list): value = [value] @@ -271,6 +271,16 @@ class ConfigObj(configobj.ConfigObj, Section): logger.warning('{x} category is set for CouchPotato and Radarr. ' 'Please check your config in NZBGet'.format (x=os.environ['NZBPO_RACATEGORY'])) + if 'NZBPO_RACATEGORY' in os.environ and 'NZBPO_W3CATEGORY' in os.environ: + if os.environ['NZBPO_RACATEGORY'] == os.environ['NZBPO_W3CATEGORY']: + logger.warning('{x} category is set for Watcher3 and Radarr. ' + 'Please check your config in NZBGet'.format + (x=os.environ['NZBPO_RACATEGORY'])) + if 'NZBPO_W3CATEGORY' in os.environ and 'NZBPO_CPSCATEGORY' in os.environ: + if os.environ['NZBPO_W3CATEGORY'] == os.environ['NZBPO_CPSCATEGORY']: + logger.warning('{x} category is set for CouchPotato and Watcher3. ' + 'Please check your config in NZBGet'.format + (x=os.environ['NZBPO_W3CATEGORY'])) if 'NZBPO_LICATEGORY' in os.environ and 'NZBPO_HPCATEGORY' in os.environ: if os.environ['NZBPO_LICATEGORY'] == os.environ['NZBPO_HPCATEGORY']: logger.warning('{x} category is set for HeadPhones and Lidarr. ' @@ -321,6 +331,29 @@ class ConfigObj(configobj.ConfigObj, Section): cfg_new[section][os.environ[env_cat_key]]['enabled'] = 1 if os.environ[env_cat_key] in cfg_new['Radarr'].sections: cfg_new['Radarr'][env_cat_key]['enabled'] = 0 + if os.environ[env_cat_key] in cfg_new['Watcher3'].sections: + cfg_new['Watcher3'][env_cat_key]['enabled'] = 0 + + section = 'Watcher3' + env_cat_key = 'NZBPO_W3CATEGORY' + env_keys = ['ENABLED', 'APIKEY', 'HOST', 'PORT', 'SSL', 'WEB_ROOT', 'METHOD', 'DELETE_FAILED', 'REMOTE_PATH', + 'WAIT_FOR', 'WATCH_DIR', 'OMDBAPIKEY'] + cfg_keys = ['enabled', 'apikey', 'host', 'port', 'ssl', 'web_root', 'method', 'delete_failed', 'remote_path', + 'wait_for', 'watch_dir', 'omdbapikey'] + if env_cat_key in os.environ: + for index in range(len(env_keys)): + key = 'NZBPO_W3{index}'.format(index=env_keys[index]) + if key in os.environ: + option = cfg_keys[index] + value = os.environ[key] + if os.environ[env_cat_key] not in cfg_new[section].sections: + cfg_new[section][os.environ[env_cat_key]] = {} + cfg_new[section][os.environ[env_cat_key]][option] = value + cfg_new[section][os.environ[env_cat_key]]['enabled'] = 1 + if os.environ[env_cat_key] in cfg_new['Radarr'].sections: + cfg_new['Radarr'][env_cat_key]['enabled'] = 0 + if os.environ[env_cat_key] in cfg_new['CouchPotato'].sections: + cfg_new['CouchPotato'][env_cat_key]['enabled'] = 0 section = 'SickBeard' env_cat_key = 'NZBPO_SBCATEGORY' @@ -444,6 +477,8 @@ class ConfigObj(configobj.ConfigObj, Section): cfg_new[section][os.environ[env_cat_key]]['enabled'] = 1 if os.environ[env_cat_key] in cfg_new['CouchPotato'].sections: cfg_new['CouchPotato'][env_cat_key]['enabled'] = 0 + if os.environ[env_cat_key] in cfg_new['Wacther3'].sections: + cfg_new['Watcher3'][env_cat_key]['enabled'] = 0 section = 'Lidarr' env_cat_key = 'NZBPO_LICATEGORY' diff --git a/nzbToMedia.py b/nzbToMedia.py index 1c04cd7b..073cb6f1 100755 --- a/nzbToMedia.py +++ b/nzbToMedia.py @@ -142,6 +142,54 @@ # Enable to replace local path with the path as per the mountPoints below. #raremote_path=0 +## Watcher3 + +# Wather3 script category. +# +# category that gets called for post-processing with Watcher3. +#W3Category=movie + +# Watcher3 api key. +#W3apikey= + +# Watcher3 host. +# +# The ipaddress for your Watcher3 server. e.g For the Same system use localhost or 127.0.0.1 +#W3host=localhost + +# Watcher3 port. +#W3port=9090 + +# Watcher3 uses ssl (0, 1). +# +# Set to 1 if using ssl, else set to 0. +#W3ssl=0 + +# Watcher3 URL_Base +# +# set this if using a reverse proxy. +#W3web_root= + +# OMDB API Key. +# +# api key for www.omdbapi.com (used as alternative to imdb to assist with movie identification). +#W3omdbapikey= + +# Wacther3 Delete Failed Downloads (0, 1). +# +# set to 1 to delete failed, or 0 to leave files in place. +#W3delete_failed=0 + +# Wacther3 wait_for +# +# Set the number of minutes to wait after calling the renamer, to check the movie has changed status. +#W3wait_for=2 + +# Watcher3 and NZBGet are a different system (0, 1). +# +# Enable to replace local path with the path as per the mountPoints below. +#W3remote_path=0 + ## SickBeard # SickBeard script category. diff --git a/nzbToWatcher3.py b/nzbToWatcher3.py new file mode 100644 index 00000000..6fafcba2 --- /dev/null +++ b/nzbToWatcher3.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python +# coding=utf-8 +# +############################################################################## +### NZBGET POST-PROCESSING SCRIPT ### + +# Post-Process to Watcher3 +# +# 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 + +## Watcher3 + +# Watcher3 script category. +# +# category that gets called for post-processing with Watcher3. +#W3Category=movie + +# Watcher3 api key. +#W3apikey= + +# Watcher3 host. +# +# The ipaddress for your Watcher3 server. e.g For the Same system use localhost or 127.0.0.1 +#W3host=localhost + +# Watcher3 port. +#W3port=5050 + +# Watcher3 uses ssl (0, 1). +# +# Set to 1 if using ssl, else set to 0. +#W3ssl=0 + +# Watcher3 URL_Base +# +# set this if using a reverse proxy. +#W3web_root= + +# Watcher3 watch directory. +# +# set this to where your Watcher3 completed downloads are. +#W3watch_dir= + +# OMDB API Key. +# +# api key for www.omdbapi.com (used as alternative to imdb to assist with movie identification). +#W3omdbapikey= + +# Watcher3 Delete Failed Downloads (0, 1). +# +# set to 1 to delete failed, or 0 to leave files in place. +#W3delete_failed=0 + +# Watcher3 wait_for +# +# Set the number of minutes to wait after calling the renamer, to check the movie has changed status. +#W3wait_for=2 + +# Watcher3 and NZBGet are a different system (0, 1). +# +# Enable to replace local path with the path as per the mountPoints below. +#W3remote_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). +# If entering an integer e.g 'niceness=4', this is added to the nice command and passed as 'nice -n4' (Default). +# If entering a comma separated list e.g. 'niceness=nice,4' this will be passed as 'nice 4' (Safer). +#niceness=nice,-n0 + +# 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, mkv-bluray, 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=ac3 +#AudioCodecAllow= +#outputAudioChannels=6 +#outputAudioBitrate=640k +#outputQualityPercent= +#outputAudioTrack2Codec=libfaac +#AudioCodec2Allow= +#outputAudioTrack2Channels=2 +#outputAudioTrack2Bitrate=160k +#outputAudioOtherCodec=libmp3lame +#AudioOtherCodecAllow= +#outputAudioOtherChannels=2 +#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 ### +############################################################################## + +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) + +import sys + +import nzbToMedia + +section = 'Watcher3' +result = nzbToMedia.main(sys.argv, section) +sys.exit(result) From 5cd449632f1f46eb7ad840cd2b39d4206242ef9f Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Fri, 8 Nov 2019 14:13:07 +1300 Subject: [PATCH 06/11] Py3.8 (#1659) * Add Python3.8 and CI Tests * Force testing of video in case ffmpeg not working --- azure-pipelines.yml | 46 ++++++---------------------------------- eol.py | 1 + tests/test_transcoder.py | 2 +- 3 files changed, 8 insertions(+), 41 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index db2d4f2b..948047c9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -21,45 +21,14 @@ jobs: python.version: '3.6' Python37: python.version: '3.7' - maxParallel: 4 + Python38: + python.version: '3.8' + maxParallel: 5 steps: - #- script: | - # Make sure all packages are pulled from latest - #sudo apt-get update - - # Fail out if any setups fail - #set -e - - # Delete old Pythons - #rm -rf $AGENT_TOOLSDIRECTORY/Python/2.7.16 - #rm -rf $AGENT_TOOLSDIRECTORY/Python/3.5.7 - #rm -rf $AGENT_TOOLSDIRECTORY/Python/3.7.3 - - # Download new Pythons - #azcopy --recursive \ - #--source https://vstsagenttools.blob.core.windows.net/tools/hostedtoolcache/linux/Python/2.7.15 \ - #--destination $AGENT_TOOLSDIRECTORY/Python/2.7.15 - - #azcopy --recursive \ - #--source https://vstsagenttools.blob.core.windows.net/tools/hostedtoolcache/linux/Python/3.5.5 \ - #--destination $AGENT_TOOLSDIRECTORY/Python/3.5.5 - - #azcopy --recursive \ - #--source https://vstsagenttools.blob.core.windows.net/tools/hostedtoolcache/linux/Python/3.7.2 \ - #--destination $AGENT_TOOLSDIRECTORY/Python/3.7.2 - - # Install new Pythons - #original_directory=$PWD - #setups=$(find $AGENT_TOOLSDIRECTORY/Python -name setup.sh) - #for setup in $setups; do - #chmod +x $setup; - #cd $(dirname $setup); - #./$(basename $setup); - #cd $original_directory; - #done; - #displayName: 'Workaround: update apt and roll back Python versions' - + - script: sudo apt-get install ffmpeg + displayName: 'Install ffmpeg' + - task: UsePythonVersion@0 inputs: versionSpec: '$(python.version)' @@ -68,9 +37,6 @@ jobs: - script: python -m pip install --upgrade pip displayName: 'Install dependencies' - - script: sudo apt-get install ffmpeg - displayName: 'Install ffmpeg' - - script: | pip install pytest pytest tests --doctest-modules --junitxml=junit/test-results.xml diff --git a/eol.py b/eol.py index 7310cfd6..fa328fdd 100644 --- a/eol.py +++ b/eol.py @@ -28,6 +28,7 @@ def date(string, fmt='%Y-%m-%d'): # https://devguide.python.org/ # https://devguide.python.org/devcycle/#devcycle PYTHON_EOL = { + (3, 8): date('2024-10-14'), (3, 7): date('2023-06-27'), (3, 6): date('2021-12-23'), (3, 5): date('2020-09-13'), diff --git a/tests/test_transcoder.py b/tests/test_transcoder.py index 448ceeaf..5c5b73ac 100755 --- a/tests/test_transcoder.py +++ b/tests/test_transcoder.py @@ -11,4 +11,4 @@ from core import transcoder def test_transcoder_check(): - assert transcoder.is_video_good(core.TEST_FILE, 0) is True + assert transcoder.is_video_good(core.TEST_FILE, 1) is True From fdaa00775674a887bb4002e82b89ba6de3fa9083 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Sun, 10 Nov 2019 09:38:48 +1300 Subject: [PATCH 07/11] Don't write byte code (#1669) --- cleanup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cleanup.py b/cleanup.py index 70c9ce29..3724844b 100644 --- a/cleanup.py +++ b/cleanup.py @@ -12,6 +12,8 @@ import subprocess import sys import shutil +sys.dont_write_bytecode = True + FOLDER_STRUCTURE = { 'libs': [ 'common', From 0d7c59f1f035d51a7409449bc4e335928a6b29bd Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Wed, 13 Nov 2019 18:32:03 +1300 Subject: [PATCH 08/11] Remove Encode of directory #1671 (#1672) --- core/utils/files.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/utils/files.py b/core/utils/files.py index 726d4209..f29efa6b 100644 --- a/core/utils/files.py +++ b/core/utils/files.py @@ -53,10 +53,11 @@ def move_file(mediafile, path, link): title = os.path.splitext(os.path.basename(mediafile))[0] new_path = os.path.join(path, sanitize_name(title)) - try: - new_path = new_path.encode(core.SYS_ENCODING) - except Exception: - pass + # Removed as encoding of directory no-longer required + #try: + # new_path = new_path.encode(core.SYS_ENCODING) + #except Exception: + # pass # Just fail-safe incase we already have afile with this clean-name (was actually a bug from earlier code, but let's be safe). if os.path.isfile(new_path): From d95e4e56c8569bc41a085336a797044ca2c4a1d9 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Sun, 8 Dec 2019 12:31:46 +1300 Subject: [PATCH 09/11] remove redundant json.loads #1671 (#1681) --- core/auto_process/movies.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/auto_process/movies.py b/core/auto_process/movies.py index e7439aa0..6535cddd 100644 --- a/core/auto_process/movies.py +++ b/core/auto_process/movies.py @@ -252,8 +252,7 @@ def process(section, dir_name, input_name=None, status=0, client_agent='manual', elif section == 'Radarr': logger.postprocess('Radarr response: {0}'.format(result['state'])) try: - res = json.loads(r.content) - scan_id = int(res['id']) + scan_id = int(result['id']) logger.debug('Scan started with id: {0}'.format(scan_id), section) except Exception as e: logger.warning('No scan id was returned due to: {0}'.format(e), section) From 75ecbd48629f31c0456c99b726eff99a650e1530 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Sun, 8 Dec 2019 14:35:15 +1300 Subject: [PATCH 10/11] Add Submodule checks (#1682) --- core/version_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/version_check.py b/core/version_check.py index f1c05ba9..a6dde88d 100644 --- a/core/version_check.py +++ b/core/version_check.py @@ -53,7 +53,7 @@ class CheckVersion(object): 'source': running from source without git """ # check if we're a windows build - if os.path.isdir(os.path.join(core.APP_ROOT, u'.git')): + if os.path.exists(os.path.join(core.APP_ROOT, u'.git')): install_type = 'git' else: install_type = 'source' From feb4e36c4c1dab5d6b4d467a1355076c50d28de6 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Sun, 8 Dec 2019 14:42:59 +1300 Subject: [PATCH 11/11] update to v12.1.02 --- .bumpversion.cfg | 2 +- core/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 1edaabcf..92813093 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 12.1.01 +current_version = 12.1.02 commit = True tag = False diff --git a/core/__init__.py b/core/__init__.py index 76798031..3dff1f4c 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -83,7 +83,7 @@ from core.utils import ( wake_up, ) -__version__ = '12.1.01' +__version__ = '12.1.02' # Client Agents NZB_CLIENTS = ['sabnzbd', 'nzbget', 'manual'] diff --git a/setup.py b/setup.py index 5e79ab2c..abe29733 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ def read(*names, **kwargs): setup( name='nzbToMedia', - version='12.1.01', + version='12.1.02', license='GPLv3', description='Efficient on demand post processing', long_description="""