Merge branch 'nightly' into dev

This commit is contained in:
clinton-hall 2019-12-08 14:37:25 +13:00
commit cbd0c25c88
16 changed files with 550 additions and 223 deletions

@ -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:

1
_config.yml Normal file

@ -0,0 +1 @@
theme: jekyll-theme-cayman

@ -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 =

@ -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

@ -12,6 +12,8 @@ import subprocess
import sys
import shutil
sys.dont_write_bytecode = True
FOLDER_STRUCTURE = {
'libs': [
'common',

@ -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:
@ -239,12 +252,23 @@ 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)
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 +288,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)

@ -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'

@ -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]))

@ -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:

@ -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):

@ -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'

1
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'),

@ -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)

@ -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.
#
@ -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.
@ -799,7 +847,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)

268
nzbToWatcher3.py Normal file

@ -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)

@ -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