PEP8: Fix formatting

* Remove redundant backslash between brackets
* Fix multiple statements on one line
* Fix missing/excess whitespace
* Fix comments not starting with a single # and a space
* Convert tabs to spaces
* Use triple-quoted docstring
This commit is contained in:
Labrys 2016-06-04 22:07:03 -04:00
commit 8cd0e76ef8
35 changed files with 1342 additions and 947 deletions

View file

@ -34,14 +34,14 @@ from core.autoProcess.autoProcessTV import autoProcessTV
from core import logger, versionCheck, nzbToMediaDB
from core.nzbToMediaConfig import config
from core.nzbToMediaUtil import category_search, sanitizeName, copy_link, parse_args, flatten, getDirs, \
rmReadOnly,rmDir, pause_torrent, resume_torrent, remove_torrent, listMediaFiles, \
rmReadOnly, rmDir, pause_torrent, resume_torrent, remove_torrent, listMediaFiles, \
extractFiles, cleanDir, update_downloadInfoStatus, get_downloadInfo, WakeUp, makeDir, cleanDir, \
create_torrent_class, listMediaFiles, RunningProcess
from core.transcoder import transcoder
from core.databases import mainDB
# Client Agents
NZB_CLIENTS = ['sabnzbd','nzbget']
NZB_CLIENTS = ['sabnzbd', 'nzbget']
TORRENT_CLIENTS = ['transmission', 'deluge', 'utorrent', 'rtorrent', 'other']
# sabnzbd constants
@ -62,7 +62,8 @@ FORKS[FORK_FAILED_TORRENT] = {"dir": None, "failed": None, "process_method": Non
FORKS[FORK_SICKRAGETV] = {"proc_dir": None, "failed": None, "process_method": None, "force": None, "delete_on": None}
FORKS[FORK_SICKRAGE] = {"proc_dir": None, "failed": None, "process_method": None, "force": None, "delete_on": None}
FORKS[FORK_SICKGEAR] = {"dir": None, "failed": None, "process_method": None, "force": None}
ALL_FORKS = {"dir": None, "dirName": None, "proc_dir": None, "failed": None, "process_method": None, "force": None, "delete_on": None}
ALL_FORKS = {"dir": None, "dirName": None, "proc_dir": None, "failed": None, "process_method": None, "force": None,
"delete_on": None}
# NZBGet Exit Codes
NZBGET_POSTPROCESS_PARCHECK = 92
@ -202,6 +203,7 @@ USER_SCRIPT_RUNONCE = None
__INITIALIZED__ = False
def initialize(section=None):
global NZBGET_POSTPROCESS_ERROR, NZBGET_POSTPROCESS_NONE, NZBGET_POSTPROCESS_PARCHECK, NZBGET_POSTPROCESS_SUCCESS, \
NZBTOMEDIA_TIMEOUT, FORKS, FORK_DEFAULT, FORK_FAILED_TORRENT, FORK_FAILED, \
@ -224,7 +226,7 @@ def initialize(section=None):
if __INITIALIZED__:
return False
if os.environ.has_key('NTM_LOGFILE'):
LOG_FILE = os.environ['NTM_LOGFILE']
LOG_DIR = os.path.split(LOG_FILE)[0]
@ -316,7 +318,8 @@ def initialize(section=None):
# restart nzbToMedia
try:
del MYAPP
except: pass
except:
pass
restart()
else:
logger.error("Update wasn't successful, not restarting. Check your log for more information.")
@ -334,8 +337,10 @@ def initialize(section=None):
SABNZBDAPIKEY = CFG["Nzb"]["sabnzbd_apikey"]
NZB_DEFAULTDIR = CFG["Nzb"]["default_downloadDirectory"]
GROUPS = CFG["Custom"]["remove_group"]
if isinstance(GROUPS, str): GROUPS = GROUPS.split(',')
if GROUPS == ['']: GROUPS = None
if isinstance(GROUPS, str):
GROUPS = GROUPS.split(',')
if GROUPS == ['']:
GROUPS = None
TORRENT_CLIENTAGENT = CFG["Torrent"]["clientAgent"] # utorrent | deluge | transmission | rtorrent | vuze |other
USELINK = CFG["Torrent"]["useLink"] # no | hard | sym
@ -343,8 +348,10 @@ def initialize(section=None):
TORRENT_DEFAULTDIR = CFG["Torrent"]["default_downloadDirectory"]
CATEGORIES = (CFG["Torrent"]["categories"]) # music,music_videos,pictures,software
NOFLATTEN = (CFG["Torrent"]["noFlatten"])
if isinstance(NOFLATTEN, str): NOFLATTEN = NOFLATTEN.split(',')
if isinstance(CATEGORIES, str): CATEGORIES = CATEGORIES.split(',')
if isinstance(NOFLATTEN, str):
NOFLATTEN = NOFLATTEN.split(',')
if isinstance(CATEGORIES, str):
CATEGORIES = CATEGORIES.split(',')
DELETE_ORIGINAL = int(CFG["Torrent"]["deleteOriginal"])
TORRENT_CHMOD_DIRECTORY = int(str(CFG["Torrent"]["chmodDirectory"]), 8)
TORRENT_RESUME_ON_FAILURE = int(CFG["Torrent"]["resumeOnFailure"])
@ -365,9 +372,12 @@ def initialize(section=None):
REMOTEPATHS = CFG["Network"]["mount_points"] or []
if REMOTEPATHS:
if isinstance(REMOTEPATHS, list): REMOTEPATHS = ','.join(REMOTEPATHS) # fix in case this imported as list.
REMOTEPATHS = [ tuple(item.split(',')) for item in REMOTEPATHS.split('|') ] # /volume1/Public/,E:\|/volume2/share/,\\NAS\
REMOTEPATHS = [ (local.strip(), remote.strip()) for local, remote in REMOTEPATHS ] # strip trailing and leading whitespaces
if isinstance(REMOTEPATHS, list):
REMOTEPATHS = ','.join(REMOTEPATHS) # fix in case this imported as list.
REMOTEPATHS = [tuple(item.split(',')) for item in
REMOTEPATHS.split('|')] # /volume1/Public/,E:\|/volume2/share/,\\NAS\
REMOTEPATHS = [(local.strip(), remote.strip()) for local, remote in
REMOTEPATHS] # strip trailing and leading whitespaces
PLEXSSL = int(CFG["Plex"]["plex_ssl"])
PLEXHOST = CFG["Plex"]["plex_host"]
@ -375,62 +385,79 @@ def initialize(section=None):
PLEXTOKEN = CFG["Plex"]["plex_token"]
PLEXSEC = CFG["Plex"]["plex_sections"] or []
if PLEXSEC:
if isinstance(PLEXSEC, list): PLEXSEC = ','.join(PLEXSEC) # fix in case this imported as list.
PLEXSEC = [ tuple(item.split(',')) for item in PLEXSEC.split('|') ]
if isinstance(PLEXSEC, list):
PLEXSEC = ','.join(PLEXSEC) # fix in case this imported as list.
PLEXSEC = [tuple(item.split(',')) for item in PLEXSEC.split('|')]
devnull = open(os.devnull, 'w')
try:
subprocess.Popen(["nice"], stdout=devnull, stderr=devnull).communicate()
NICENESS.extend(['nice', '-n%s' % (int(CFG["Posix"]["niceness"]))])
except: pass
except:
pass
try:
subprocess.Popen(["ionice"], stdout=devnull, stderr=devnull).communicate()
try:
NICENESS.extend(['ionice', '-c%s' % (int(CFG["Posix"]["ionice_class"]))])
except: pass
except:
pass
try:
if 'ionice' in NICENESS:
NICENESS.extend(['-n%s' % (int(CFG["Posix"]["ionice_classdata"]))])
else:
NICENESS.extend(['ionice', '-n%s' % (int(CFG["Posix"]["ionice_classdata"]))])
except: pass
except: pass
except:
pass
except:
pass
devnull.close()
COMPRESSEDCONTAINER = [re.compile('.r\d{2}$', re.I),
re.compile('.part\d+.rar$', re.I),
re.compile('.rar$', re.I)]
re.compile('.part\d+.rar$', re.I),
re.compile('.rar$', re.I)]
COMPRESSEDCONTAINER += [re.compile('%s$' % ext, re.I) for ext in CFG["Extensions"]["compressedExtensions"]]
MEDIACONTAINER = CFG["Extensions"]["mediaExtensions"]
AUDIOCONTAINER = CFG["Extensions"]["audioExtensions"]
METACONTAINER = CFG["Extensions"]["metaExtensions"] # .nfo,.sub,.srt
if isinstance(COMPRESSEDCONTAINER, str): COMPRESSEDCONTAINER = COMPRESSEDCONTAINER.split(',')
if isinstance(MEDIACONTAINER, str): MEDIACONTAINER = MEDIACONTAINER.split(',')
if isinstance(AUDIOCONTAINER, str): AUDIOCONTAINER = AUDIOCONTAINER.split(',')
if isinstance(METACONTAINER, str): METACONTAINER = METACONTAINER.split(',')
if isinstance(COMPRESSEDCONTAINER, str):
COMPRESSEDCONTAINER = COMPRESSEDCONTAINER.split(',')
if isinstance(MEDIACONTAINER, str):
MEDIACONTAINER = MEDIACONTAINER.split(',')
if isinstance(AUDIOCONTAINER, str):
AUDIOCONTAINER = AUDIOCONTAINER.split(',')
if isinstance(METACONTAINER, str):
METACONTAINER = METACONTAINER.split(',')
GETSUBS = int(CFG["Transcoder"]["getSubs"])
TRANSCODE = int(CFG["Transcoder"]["transcode"])
DUPLICATE = int(CFG["Transcoder"]["duplicate"])
CONCAT = int(CFG["Transcoder"]["concat"])
IGNOREEXTENSIONS = (CFG["Transcoder"]["ignoreExtensions"])
if isinstance(IGNOREEXTENSIONS, str): IGNOREEXTENSIONS = IGNOREEXTENSIONS.split(',')
if isinstance(IGNOREEXTENSIONS, str):
IGNOREEXTENSIONS = IGNOREEXTENSIONS.split(',')
OUTPUTFASTSTART = int(CFG["Transcoder"]["outputFastStart"])
GENERALOPTS = (CFG["Transcoder"]["generalOptions"])
if isinstance(GENERALOPTS, str): GENERALOPTS = GENERALOPTS.split(',')
if GENERALOPTS == ['']: GENERALOPTS = []
if not '-fflags' in GENERALOPTS: GENERALOPTS.append('-fflags')
if not '+genpts' in GENERALOPTS: GENERALOPTS.append('+genpts')
if isinstance(GENERALOPTS, str):
GENERALOPTS = GENERALOPTS.split(',')
if GENERALOPTS == ['']:
GENERALOPTS = []
if not '-fflags' in GENERALOPTS:
GENERALOPTS.append('-fflags')
if not '+genpts' in GENERALOPTS:
GENERALOPTS.append('+genpts')
try:
OUTPUTQUALITYPERCENT = int(CFG["Transcoder"]["outputQualityPercent"])
except: pass
except:
pass
OUTPUTVIDEOPATH = CFG["Transcoder"]["outputVideoPath"]
PROCESSOUTPUT = int(CFG["Transcoder"]["processOutput"])
ALANGUAGE = CFG["Transcoder"]["audioLanguage"]
AINCLUDE = int(CFG["Transcoder"]["allAudioLanguages"])
SLANGUAGES = CFG["Transcoder"]["subLanguages"]
if isinstance(SLANGUAGES, str): SLANGUAGES = SLANGUAGES.split(',')
if SLANGUAGES == ['']: SLANGUAGES = []
if isinstance(SLANGUAGES, str):
SLANGUAGES = SLANGUAGES.split(',')
if SLANGUAGES == ['']:
SLANGUAGES = []
SINCLUDE = int(CFG["Transcoder"]["allSubLanguages"])
SEXTRACT = int(CFG["Transcoder"]["extractSubs"])
SEMBED = int(CFG["Transcoder"]["embedSubs"])
@ -438,169 +465,215 @@ def initialize(section=None):
VEXTENSION = CFG["Transcoder"]["outputVideoExtension"].strip()
VCODEC = CFG["Transcoder"]["outputVideoCodec"].strip()
VCODEC_ALLOW = CFG["Transcoder"]["VideoCodecAllow"].strip()
if isinstance(VCODEC_ALLOW, str): VCODEC_ALLOW = VCODEC_ALLOW.split(',')
if VCODEC_ALLOW == ['']: VCODEC_ALLOW = []
if isinstance(VCODEC_ALLOW, str):
VCODEC_ALLOW = VCODEC_ALLOW.split(',')
if VCODEC_ALLOW == ['']:
VCODEC_ALLOW = []
VPRESET = CFG["Transcoder"]["outputVideoPreset"].strip()
try:
VFRAMERATE = float(CFG["Transcoder"]["outputVideoFramerate"].strip())
except: pass
except:
pass
try:
VCRF = int(CFG["Transcoder"]["outputVideoCRF"].strip())
except: pass
except:
pass
try:
VLEVEL = CFG["Transcoder"]["outputVideoLevel"].strip()
except: pass
except:
pass
try:
VBITRATE = int((CFG["Transcoder"]["outputVideoBitrate"].strip()).replace('k','000'))
except: pass
VBITRATE = int((CFG["Transcoder"]["outputVideoBitrate"].strip()).replace('k', '000'))
except:
pass
VRESOLUTION = CFG["Transcoder"]["outputVideoResolution"]
ACODEC = CFG["Transcoder"]["outputAudioCodec"].strip()
ACODEC_ALLOW = CFG["Transcoder"]["AudioCodecAllow"].strip()
if isinstance(ACODEC_ALLOW, str): ACODEC_ALLOW = ACODEC_ALLOW.split(',')
if ACODEC_ALLOW == ['']: ACODEC_ALLOW = []
if isinstance(ACODEC_ALLOW, str):
ACODEC_ALLOW = ACODEC_ALLOW.split(',')
if ACODEC_ALLOW == ['']:
ACODEC_ALLOW = []
try:
ACHANNELS = int(CFG["Transcoder"]["outputAudioChannels"].strip())
except: pass
except:
pass
try:
ABITRATE = int((CFG["Transcoder"]["outputAudioBitrate"].strip()).replace('k','000'))
except: pass
ABITRATE = int((CFG["Transcoder"]["outputAudioBitrate"].strip()).replace('k', '000'))
except:
pass
ACODEC2 = CFG["Transcoder"]["outputAudioTrack2Codec"].strip()
ACODEC2_ALLOW = CFG["Transcoder"]["AudioCodec2Allow"].strip()
if isinstance(ACODEC2_ALLOW, str): ACODEC2_ALLOW = ACODEC2_ALLOW.split(',')
if ACODEC2_ALLOW == ['']: ACODEC2_ALLOW = []
if isinstance(ACODEC2_ALLOW, str):
ACODEC2_ALLOW = ACODEC2_ALLOW.split(',')
if ACODEC2_ALLOW == ['']:
ACODEC2_ALLOW = []
try:
ACHANNELS2 = int(CFG["Transcoder"]["outputAudioTrack2Channels"].strip())
except: pass
except:
pass
try:
ABITRATE2 = int((CFG["Transcoder"]["outputAudioTrack2Bitrate"].strip()).replace('k','000'))
except: pass
ABITRATE2 = int((CFG["Transcoder"]["outputAudioTrack2Bitrate"].strip()).replace('k', '000'))
except:
pass
ACODEC3 = CFG["Transcoder"]["outputAudioOtherCodec"].strip()
ACODEC3_ALLOW = CFG["Transcoder"]["AudioOtherCodecAllow"].strip()
if isinstance(ACODEC3_ALLOW, str): ACODEC3_ALLOW = ACODEC3_ALLOW.split(',')
if ACODEC3_ALLOW == ['']: ACODEC3_ALLOW = []
if isinstance(ACODEC3_ALLOW, str):
ACODEC3_ALLOW = ACODEC3_ALLOW.split(',')
if ACODEC3_ALLOW == ['']:
ACODEC3_ALLOW = []
try:
ACHANNELS3 = int(CFG["Transcoder"]["outputAudioOtherChannels"].strip())
except: pass
except:
pass
try:
ABITRATE3 = int((CFG["Transcoder"]["outputAudioOtherBitrate"].strip()).replace('k','000'))
except: pass
ABITRATE3 = int((CFG["Transcoder"]["outputAudioOtherBitrate"].strip()).replace('k', '000'))
except:
pass
SCODEC = CFG["Transcoder"]["outputSubtitleCodec"].strip()
BURN = int(CFG["Transcoder"]["burnInSubtitle"].strip())
DEFAULTS = CFG["Transcoder"]["outputDefault"].strip()
HWACCEL = int(CFG["Transcoder"]["hwAccel"])
allow_subs = ['.mkv','.mp4', '.m4v', 'asf', 'wma', 'wmv']
allow_subs = ['.mkv', '.mp4', '.m4v', 'asf', 'wma', 'wmv']
codec_alias = {
'libx264':['libx264', 'h264', 'h.264', 'AVC', 'MPEG-4'],
'libmp3lame':['libmp3lame', 'mp3'],
'libfaac':['libfaac', 'aac', 'faac']
}
'libx264': ['libx264', 'h264', 'h.264', 'AVC', 'MPEG-4'],
'libmp3lame': ['libmp3lame', 'mp3'],
'libfaac': ['libfaac', 'aac', 'faac']
}
transcode_defaults = {
'iPad':{
'VEXTENSION':'.mp4','VCODEC':'libx264','VPRESET':None,'VFRAMERATE':None,'VBITRATE':None,'VCRF':None,'VLEVEL':None,
'VRESOLUTION':None,'VCODEC_ALLOW':['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC':'aac','ACODEC_ALLOW':['libfaac'],'ABITRATE':None, 'ACHANNELS':2,
'ACODEC2':'ac3','ACODEC2_ALLOW':['ac3'],'ABITRATE2':None, 'ACHANNELS2':6,
'ACODEC3':None,'ACODEC3_ALLOW':[],'ABITRATE3':None, 'ACHANNELS3':None,
'SCODEC':'mov_text'
},
'iPad-1080p':{
'VEXTENSION':'.mp4','VCODEC':'libx264','VPRESET':None,'VFRAMERATE':None,'VBITRATE':None,'VCRF':None,'VLEVEL':None,
'VRESOLUTION':'1920:1080','VCODEC_ALLOW':['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC':'aac','ACODEC_ALLOW':['libfaac'],'ABITRATE':None, 'ACHANNELS':2,
'ACODEC2':'ac3','ACODEC2_ALLOW':['ac3'],'ABITRATE2':None, 'ACHANNELS2':6,
'ACODEC3':None,'ACODEC3_ALLOW':[],'ABITRATE3':None, 'ACHANNELS3':None,
'SCODEC':'mov_text'
},
'iPad-720p':{
'VEXTENSION':'.mp4','VCODEC':'libx264','VPRESET':None,'VFRAMERATE':None,'VBITRATE':None,'VCRF':None,'VLEVEL':None,
'VRESOLUTION':'1280:720','VCODEC_ALLOW':['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC':'aac','ACODEC_ALLOW':['libfaac'],'ABITRATE':None, 'ACHANNELS':2,
'ACODEC2':'ac3','ACODEC2_ALLOW':['ac3'],'ABITRATE2':None, 'ACHANNELS2':6,
'ACODEC3':None,'ACODEC3_ALLOW':[],'ABITRATE3':None, 'ACHANNELS3':None,
'SCODEC':'mov_text'
},
'Apple-TV':{
'VEXTENSION':'.mp4','VCODEC':'libx264','VPRESET':None,'VFRAMERATE':None,'VBITRATE':None,'VCRF':None,'VLEVEL':None,
'VRESOLUTION':'1280:720','VCODEC_ALLOW':['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC':'ac3','ACODEC_ALLOW':['ac3'],'ABITRATE':None, 'ACHANNELS':6,
'ACODEC2':'aac','ACODEC2_ALLOW':['libfaac'],'ABITRATE2':None, 'ACHANNELS2':2,
'ACODEC3':None,'ACODEC3_ALLOW':[],'ABITRATE3':None, 'ACHANNELS3':None,
'SCODEC':'mov_text'
},
'iPod':{
'VEXTENSION':'.mp4','VCODEC':'libx264','VPRESET':None,'VFRAMERATE':None,'VBITRATE':None,'VCRF':None,'VLEVEL':None,
'VRESOLUTION':'1280:720','VCODEC_ALLOW':['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC':'aac','ACODEC_ALLOW':['libfaac'],'ABITRATE':128000, 'ACHANNELS':2,
'ACODEC2':None,'ACODEC2_ALLOW':[],'ABITRATE2':None, 'ACHANNELS2':None,
'ACODEC3':None,'ACODEC3_ALLOW':[],'ABITRATE3':None, 'ACHANNELS3':None,
'SCODEC':'mov_text'
},
'iPhone':{
'VEXTENSION':'.mp4','VCODEC':'libx264','VPRESET':None,'VFRAMERATE':None,'VBITRATE':None,'VCRF':None,'VLEVEL':None,
'VRESOLUTION':'460:320','VCODEC_ALLOW':['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC':'aac','ACODEC_ALLOW':['libfaac'],'ABITRATE':128000, 'ACHANNELS':2,
'ACODEC2':None,'ACODEC2_ALLOW':[],'ABITRATE2':None, 'ACHANNELS2':None,
'ACODEC3':None,'ACODEC3_ALLOW':[],'ABITRATE3':None, 'ACHANNELS3':None,
'SCODEC':'mov_text'
},
'PS3':{
'VEXTENSION':'.mp4','VCODEC':'libx264','VPRESET':None,'VFRAMERATE':None,'VBITRATE':None,'VCRF':None,'VLEVEL':None,
'VRESOLUTION':None,'VCODEC_ALLOW':['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC':'ac3','ACODEC_ALLOW':['ac3'],'ABITRATE':None, 'ACHANNELS':6,
'ACODEC2':'aac','ACODEC2_ALLOW':['libfaac'],'ABITRATE2':None, 'ACHANNELS2':2,
'ACODEC3':None,'ACODEC3_ALLOW':[],'ABITRATE3':None, 'ACHANNELS3':None,
'SCODEC':'mov_text'
},
'xbox':{
'VEXTENSION':'.mp4','VCODEC':'libx264','VPRESET':None,'VFRAMERATE':None,'VBITRATE':None,'VCRF':None,'VLEVEL':None,
'VRESOLUTION':None,'VCODEC_ALLOW':['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC':'ac3','ACODEC_ALLOW':['ac3'],'ABITRATE':None, 'ACHANNELS':6,
'ACODEC2':None,'ACODEC2_ALLOW':[],'ABITRATE2':None, 'ACHANNELS2':None,
'ACODEC3':None,'ACODEC3_ALLOW':[],'ABITRATE3':None, 'ACHANNELS3':None,
'SCODEC':'mov_text'
},
'Roku-480p':{
'VEXTENSION':'.mp4','VCODEC':'libx264','VPRESET':None,'VFRAMERATE':None,'VBITRATE':None,'VCRF':None,'VLEVEL':None,
'VRESOLUTION':None,'VCODEC_ALLOW':['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC':'aac','ACODEC_ALLOW':['libfaac'],'ABITRATE':128000, 'ACHANNELS':2,
'ACODEC2':'ac3','ACODEC2_ALLOW':['ac3'],'ABITRATE2':None, 'ACHANNELS2':6,
'ACODEC3':None,'ACODEC3_ALLOW':[],'ABITRATE3':None, 'ACHANNELS3':None,
'SCODEC':'mov_text'
},
'Roku-720p':{
'VEXTENSION':'.mp4','VCODEC':'libx264','VPRESET':None,'VFRAMERATE':None,'VBITRATE':None,'VCRF':None,'VLEVEL':None,
'VRESOLUTION':None,'VCODEC_ALLOW':['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC':'aac','ACODEC_ALLOW':['libfaac'],'ABITRATE':128000, 'ACHANNELS':2,
'ACODEC2':'ac3','ACODEC2_ALLOW':['ac3'],'ABITRATE2':None, 'ACHANNELS2':6,
'ACODEC3':None,'ACODEC3_ALLOW':[],'ABITRATE3':None, 'ACHANNELS3':None,
'SCODEC':'mov_text'
},
'Roku-1080p':{
'VEXTENSION':'.mp4','VCODEC':'libx264','VPRESET':None,'VFRAMERATE':None,'VBITRATE':None,'VCRF':None,'VLEVEL':None,
'VRESOLUTION':None,'VCODEC_ALLOW':['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC':'aac','ACODEC_ALLOW':['libfaac'],'ABITRATE':160000, 'ACHANNELS':2,
'ACODEC2':'ac3','ACODEC2_ALLOW':['ac3'],'ABITRATE2':None, 'ACHANNELS2':6,
'ACODEC3':None,'ACODEC3_ALLOW':[],'ABITRATE3':None, 'ACHANNELS3':None,
'SCODEC':'mov_text'
},
'mkv':{
'VEXTENSION':'.mkv','VCODEC':'libx264','VPRESET':None,'VFRAMERATE':None,'VBITRATE':None,'VCRF':None,'VLEVEL':None,
'VRESOLUTION':None,'VCODEC_ALLOW':['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4', 'mpeg2video'],
'ACODEC':'dts','ACODEC_ALLOW':['libfaac', 'dts', 'ac3', 'mp2', 'mp3'],'ABITRATE':None, 'ACHANNELS':8,
'ACODEC2':None,'ACODEC2_ALLOW':[],'ABITRATE2':None, 'ACHANNELS2':None,
'ACODEC3':'ac3','ACODEC3_ALLOW':['libfaac', 'dts', 'ac3', 'mp2', 'mp3'],'ABITRATE3':None, 'ACHANNELS3':8,
'SCODEC':'mov_text'
},
'mp4-scene-release':{
'VEXTENSION':'.mp4','VCODEC':'libx264','VPRESET':None,'VFRAMERATE':None,'VBITRATE':None,'VCRF':19,'VLEVEL':'3.1',
'VRESOLUTION':None,'VCODEC_ALLOW':['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4', 'mpeg2video'],
'ACODEC':'dts','ACODEC_ALLOW':['libfaac', 'dts', 'ac3', 'mp2', 'mp3'],'ABITRATE':None, 'ACHANNELS':8,
'ACODEC2':None,'ACODEC2_ALLOW':[],'ABITRATE2':None, 'ACHANNELS2':None,
'ACODEC3':'ac3','ACODEC3_ALLOW':['libfaac', 'dts', 'ac3', 'mp2', 'mp3'],'ABITRATE3':None, 'ACHANNELS3':8,
'SCODEC':'mov_text'
}
'iPad': {
'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None,
'VCRF': None, 'VLEVEL': None,
'VRESOLUTION': None,
'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': None, 'ACHANNELS': 2,
'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6,
'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None,
'SCODEC': 'mov_text'
},
'iPad-1080p': {
'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None,
'VCRF': None, 'VLEVEL': None,
'VRESOLUTION': '1920:1080',
'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': None, 'ACHANNELS': 2,
'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6,
'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None,
'SCODEC': 'mov_text'
},
'iPad-720p': {
'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None,
'VCRF': None, 'VLEVEL': None,
'VRESOLUTION': '1280:720',
'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': None, 'ACHANNELS': 2,
'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6,
'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None,
'SCODEC': 'mov_text'
},
'Apple-TV': {
'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None,
'VCRF': None, 'VLEVEL': None,
'VRESOLUTION': '1280:720',
'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC': 'ac3', 'ACODEC_ALLOW': ['ac3'], 'ABITRATE': None, 'ACHANNELS': 6,
'ACODEC2': 'aac', 'ACODEC2_ALLOW': ['libfaac'], 'ABITRATE2': None, 'ACHANNELS2': 2,
'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None,
'SCODEC': 'mov_text'
},
'iPod': {
'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None,
'VCRF': None, 'VLEVEL': None,
'VRESOLUTION': '1280:720',
'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2,
'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None,
'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None,
'SCODEC': 'mov_text'
},
'iPhone': {
'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None,
'VCRF': None, 'VLEVEL': None,
'VRESOLUTION': '460:320',
'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2,
'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None,
'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None,
'SCODEC': 'mov_text'
},
'PS3': {
'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None,
'VCRF': None, 'VLEVEL': None,
'VRESOLUTION': None,
'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC': 'ac3', 'ACODEC_ALLOW': ['ac3'], 'ABITRATE': None, 'ACHANNELS': 6,
'ACODEC2': 'aac', 'ACODEC2_ALLOW': ['libfaac'], 'ABITRATE2': None, 'ACHANNELS2': 2,
'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None,
'SCODEC': 'mov_text'
},
'xbox': {
'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None,
'VCRF': None, 'VLEVEL': None,
'VRESOLUTION': None,
'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC': 'ac3', 'ACODEC_ALLOW': ['ac3'], 'ABITRATE': None, 'ACHANNELS': 6,
'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None,
'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None,
'SCODEC': 'mov_text'
},
'Roku-480p': {
'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None,
'VCRF': None, 'VLEVEL': None,
'VRESOLUTION': None,
'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2,
'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6,
'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None,
'SCODEC': 'mov_text'
},
'Roku-720p': {
'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None,
'VCRF': None, 'VLEVEL': None,
'VRESOLUTION': None,
'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 128000, 'ACHANNELS': 2,
'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6,
'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None,
'SCODEC': 'mov_text'
},
'Roku-1080p': {
'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None,
'VCRF': None, 'VLEVEL': None,
'VRESOLUTION': None,
'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4'],
'ACODEC': 'aac', 'ACODEC_ALLOW': ['libfaac'], 'ABITRATE': 160000, 'ACHANNELS': 2,
'ACODEC2': 'ac3', 'ACODEC2_ALLOW': ['ac3'], 'ABITRATE2': None, 'ACHANNELS2': 6,
'ACODEC3': None, 'ACODEC3_ALLOW': [], 'ABITRATE3': None, 'ACHANNELS3': None,
'SCODEC': 'mov_text'
},
'mkv': {
'VEXTENSION': '.mkv', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None,
'VCRF': None, 'VLEVEL': None,
'VRESOLUTION': None,
'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4', 'mpeg2video'],
'ACODEC': 'dts', 'ACODEC_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE': None, 'ACHANNELS': 8,
'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None,
'ACODEC3': 'ac3', 'ACODEC3_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE3': None,
'ACHANNELS3': 8,
'SCODEC': 'mov_text'
},
'mp4-scene-release': {
'VEXTENSION': '.mp4', 'VCODEC': 'libx264', 'VPRESET': None, 'VFRAMERATE': None, 'VBITRATE': None,
'VCRF': 19, 'VLEVEL': '3.1',
'VRESOLUTION': None,
'VCODEC_ALLOW': ['libx264', 'h264', 'h.264', 'AVC', 'avc', 'mpeg4', 'msmpeg4', 'MPEG-4', 'mpeg2video'],
'ACODEC': 'dts', 'ACODEC_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE': None, 'ACHANNELS': 8,
'ACODEC2': None, 'ACODEC2_ALLOW': [], 'ABITRATE2': None, 'ACHANNELS2': None,
'ACODEC3': 'ac3', 'ACODEC3_ALLOW': ['libfaac', 'dts', 'ac3', 'mp2', 'mp3'], 'ABITRATE3': None,
'ACHANNELS3': 8,
'SCODEC': 'mov_text'
}
}
if DEFAULTS and DEFAULTS in transcode_defaults:
VEXTENSION = transcode_defaults[DEFAULTS]['VEXTENSION']
VCODEC = transcode_defaults[DEFAULTS]['VCODEC']
@ -630,25 +703,29 @@ def initialize(section=None):
if VEXTENSION in allow_subs:
ALLOWSUBS = 1
if not VCODEC_ALLOW and VCODEC: VCODEC_ALLOW.extend([VCODEC])
if not VCODEC_ALLOW and VCODEC:
VCODEC_ALLOW.extend([VCODEC])
for codec in VCODEC_ALLOW:
if codec in codec_alias:
extra = [ item for item in codec_alias[codec] if item not in VCODEC_ALLOW ]
extra = [item for item in codec_alias[codec] if item not in VCODEC_ALLOW]
VCODEC_ALLOW.extend(extra)
if not ACODEC_ALLOW and ACODEC: ACODEC_ALLOW.extend([ACODEC])
if not ACODEC_ALLOW and ACODEC:
ACODEC_ALLOW.extend([ACODEC])
for codec in ACODEC_ALLOW:
if codec in codec_alias:
extra = [ item for item in codec_alias[codec] if item not in ACODEC_ALLOW ]
extra = [item for item in codec_alias[codec] if item not in ACODEC_ALLOW]
ACODEC_ALLOW.extend(extra)
if not ACODEC2_ALLOW and ACODEC2: ACODEC2_ALLOW.extend([ACODEC2])
if not ACODEC2_ALLOW and ACODEC2:
ACODEC2_ALLOW.extend([ACODEC2])
for codec in ACODEC2_ALLOW:
if codec in codec_alias:
extra = [ item for item in codec_alias[codec] if item not in ACODEC2_ALLOW ]
extra = [item for item in codec_alias[codec] if item not in ACODEC2_ALLOW]
ACODEC2_ALLOW.extend(extra)
if not ACODEC3_ALLOW and ACODEC3: ACODEC3_ALLOW.extend([ACODEC3])
if not ACODEC3_ALLOW and ACODEC3:
ACODEC3_ALLOW.extend([ACODEC3])
for codec in ACODEC3_ALLOW:
if codec in codec_alias:
extra = [ item for item in codec_alias[codec] if item not in ACODEC3_ALLOW ]
extra = [item for item in codec_alias[codec] if item not in ACODEC3_ALLOW]
ACODEC3_ALLOW.extend(extra)
codec_alias = {} # clear memory
@ -674,47 +751,59 @@ def initialize(section=None):
else:
try:
SEVENZIP = subprocess.Popen(['which', '7z'], stdout=subprocess.PIPE).communicate()[0].strip()
except: pass
if not SEVENZIP:
except:
pass
if not SEVENZIP:
try:
SEVENZIP = subprocess.Popen(['which', '7zr'], stdout=subprocess.PIPE).communicate()[0].strip()
except: pass
if not SEVENZIP:
except:
pass
if not SEVENZIP:
try:
SEVENZIP = subprocess.Popen(['which', '7za'], stdout=subprocess.PIPE).communicate()[0].strip()
except: pass
except:
pass
if not SEVENZIP:
SEVENZIP = None
logger.warning("Failed to locate 7zip. Transcosing of disk images and extraction of .7z files will not be possible!")
if os.path.isfile(os.path.join(FFMPEG_PATH, 'ffmpeg')) or os.access(os.path.join(FFMPEG_PATH, 'ffmpeg'), os.X_OK):
logger.warning(
"Failed to locate 7zip. Transcosing of disk images and extraction of .7z files will not be possible!")
if os.path.isfile(os.path.join(FFMPEG_PATH, 'ffmpeg')) or os.access(os.path.join(FFMPEG_PATH, 'ffmpeg'),
os.X_OK):
FFMPEG = os.path.join(FFMPEG_PATH, 'ffmpeg')
elif os.path.isfile(os.path.join(FFMPEG_PATH, 'avconv')) or os.access(os.path.join(FFMPEG_PATH, 'avconv'), os.X_OK):
elif os.path.isfile(os.path.join(FFMPEG_PATH, 'avconv')) or os.access(os.path.join(FFMPEG_PATH, 'avconv'),
os.X_OK):
FFMPEG = os.path.join(FFMPEG_PATH, 'avconv')
else:
try:
FFMPEG = subprocess.Popen(['which', 'ffmpeg'], stdout=subprocess.PIPE).communicate()[0].strip()
except: pass
if not FFMPEG:
except:
pass
if not FFMPEG:
try:
FFMPEG = subprocess.Popen(['which', 'avconv'], stdout=subprocess.PIPE).communicate()[0].strip()
except: pass
except:
pass
if not FFMPEG:
FFMPEG = None
logger.warning("Failed to locate ffmpeg. Transcoding disabled!")
logger.warning("Install ffmpeg with x264 support to enable this feature ...")
if os.path.isfile(os.path.join(FFMPEG_PATH, 'ffprobe')) or os.access(os.path.join(FFMPEG_PATH, 'ffprobe'), os.X_OK):
if os.path.isfile(os.path.join(FFMPEG_PATH, 'ffprobe')) or os.access(os.path.join(FFMPEG_PATH, 'ffprobe'),
os.X_OK):
FFPROBE = os.path.join(FFMPEG_PATH, 'ffprobe')
elif os.path.isfile(os.path.join(FFMPEG_PATH, 'avprobe')) or os.access(os.path.join(FFMPEG_PATH, 'avprobe'), os.X_OK):
elif os.path.isfile(os.path.join(FFMPEG_PATH, 'avprobe')) or os.access(os.path.join(FFMPEG_PATH, 'avprobe'),
os.X_OK):
FFPROBE = os.path.join(FFMPEG_PATH, 'avprobe')
else:
try:
FFPROBE = subprocess.Popen(['which', 'ffprobe'], stdout=subprocess.PIPE).communicate()[0].strip()
except: pass
if not FFPROBE:
except:
pass
if not FFPROBE:
try:
FFPROBE = subprocess.Popen(['which', 'avprobe'], stdout=subprocess.PIPE).communicate()[0].strip()
except: pass
except:
pass
if not FFPROBE:
FFPROBE = None
if CHECK_MEDIA:
@ -723,7 +812,7 @@ def initialize(section=None):
# check for script-defied section and if None set to allow sections
SECTIONS = CFG[tuple(x for x in CFG if CFG[x].sections and CFG[x].isenabled()) if not section else (section,)]
for section,subsections in SECTIONS.items():
for section, subsections in SECTIONS.items():
CATEGORIES.extend([subsection for subsection in subsections if CFG[section][subsection].isenabled()])
CATEGORIES = list(set(CATEGORIES))
@ -733,6 +822,7 @@ def initialize(section=None):
# finished initalizing
return True
def restart():
install_type = versionCheck.CheckVersion().install_type
@ -752,11 +842,12 @@ def restart():
os._exit(status)
def rchmod(path, mod):
logger.log("Changing file mode of %s to %s" % (path, oct(mod)))
os.chmod(path, mod)
if not os.path.isdir(path):
return # Skip files
return # Skip files
for root, dirs, files in os.walk(path):
for d in dirs:

View file

@ -1 +1 @@
# coding=utf-8
# coding=utf-8

View file

@ -1,2 +1,2 @@
# coding=utf-8
__all__ = ["mainDB"]
__all__ = ["mainDB"]

View file

@ -14,6 +14,7 @@ def backupDatabase(version):
else:
logger.info("Proceeding with upgrade")
# ======================
# = Main DB Migrations =
# ======================
@ -45,21 +46,21 @@ class InitialSchema(nzbToMediaDB.SchemaUpgrade):
cur_db_version) + ") is too old to migrate from what this version of nzbToMedia supports (" + \
str(MIN_DB_VERSION) + ").\n" + \
"Please remove nzbtomedia.db file to begin fresh."
)
)
if cur_db_version > MAX_DB_VERSION:
logger.log_error_and_exit("Your database version (" + str(
cur_db_version) + ") has been incremented past what this version of nzbToMedia supports (" + \
str(MAX_DB_VERSION) + ").\n" + \
"If you have used other forks of nzbToMedia, your database may be unusable due to their modifications."
)
)
if cur_db_version < MAX_DB_VERSION: # We need to upgrade.
queries = [
"CREATE TABLE downloads2 (input_directory TEXT, input_name TEXT, input_hash TEXT, input_id TEXT, client_agent TEXT, status INTEGER, last_update NUMERIC, CONSTRAINT pk_downloadID PRIMARY KEY (input_directory, input_name));",
"INSERT INTO downloads2 SELECT * FROM downloads;",
"DROP TABLE IF EXISTS downloads;",
"ALTER TABLE downloads2 RENAME TO downloads;",
"ALTER TABLE downloads2 RENAME TO downloads;",
"INSERT INTO db_version (db_version) VALUES (2);"
]
for query in queries:
self.connection.action(query)
self.connection.action(query)

View file

@ -1 +1 @@
# coding=utf-8
# coding=utf-8

View file

@ -8,6 +8,7 @@ import core
from subprocess import call, Popen
import subprocess
def extract(filePath, outputDestination):
success = 0
# Using Windows
@ -22,9 +23,9 @@ def extract(filePath, outputDestination):
# Using unix
else:
required_cmds = ["unrar", "unzip", "tar", "unxz", "unlzma", "7zr", "bunzip2"]
## Possible future suport:
# ## Possible future suport:
# gunzip: gz (cmd will delete original archive)
## the following do not extract to dest dir
# ## the following do not extract to dest dir
# ".xz": ["xz", "-d --keep"],
# ".lzma": ["xz", "-d --format=lzma --keep"],
# ".bz2": ["bzip2", "-d --keep"],
@ -43,12 +44,13 @@ def extract(filePath, outputDestination):
if not os.getenv('TR_TORRENT_DIR'):
devnull = open(os.devnull, 'w')
for cmd in required_cmds:
if call(['which', cmd], stdout=devnull, stderr=devnull): #note, returns 0 if exists, or 1 if doesn't exist.
if call(['which', cmd], stdout=devnull,
stderr=devnull): # note, returns 0 if exists, or 1 if doesn't exist.
if cmd == "7zr" and not call(["which", "7z"]): # we do have "7z" command
EXTRACT_COMMANDS[".7z"] = ["7z", "x"]
elif cmd == "7zr" and not call(["which", "7za"]): # we do have "7za" command
EXTRACT_COMMANDS[".7z"] = ["7za", "x"]
else:
else:
for k, v in EXTRACT_COMMANDS.items():
if cmd in v[0]:
core.logger.error("EXTRACTOR: %s not found, disabling support for %s" % (cmd, k))
@ -77,7 +79,7 @@ def extract(filePath, outputDestination):
core.logger.debug("EXTRACTOR: Unknown file type: %s" % ext[1])
return False
# Create outputDestination folder
# Create outputDestination folder
core.makeDir(outputDestination)
if core.PASSWORDSFILE != "" and os.path.isfile(os.path.normpath(core.PASSWORDSFILE)):
@ -99,7 +101,7 @@ def extract(filePath, outputDestination):
pwd = os.getcwd() # Get our Present Working Directory
os.chdir(outputDestination) # Not all unpack commands accept full paths, so just extract into this directory
devnull = open(os.devnull, 'w')
try: # now works same for nt and *nix
info = None
cmd.append(filePath) # add filePath to final cmd arg.
@ -112,7 +114,8 @@ def extract(filePath, outputDestination):
cmd2.append("-p-") # don't prompt for password.
p = Popen(cmd2, stdout=devnull, stderr=devnull, startupinfo=info) # should extract files fine.
res = p.wait()
if (res >= 0 and os.name == 'nt') or res == 0: # for windows chp returns process id if successful or -1*Error code. Linux returns 0 for successful.
if (
res >= 0 and os.name == 'nt') or res == 0: # for windows chp returns process id if successful or -1*Error code. Linux returns 0 for successful.
core.logger.info("EXTRACTOR: Extraction was successful for %s to %s" % (filePath, outputDestination))
success = 1
elif len(passwords) > 0:
@ -121,14 +124,14 @@ def extract(filePath, outputDestination):
if password == "": # if edited in windows or otherwise if blank lines.
continue
cmd2 = cmd
#append password here.
# append password here.
passcmd = "-p" + password
cmd2.append(passcmd)
p = Popen(cmd2, stdout=devnull, stderr=devnull, startupinfo=info) # should extract files fine.
res = p.wait()
if (res >= 0 and platform == 'Windows') or res == 0:
core.logger.info("EXTRACTOR: Extraction was successful for %s to %s using password: %s" % (
filePath, outputDestination, password))
filePath, outputDestination, password))
success = 1
break
else:
@ -142,19 +145,21 @@ def extract(filePath, outputDestination):
os.chdir(pwd) # Go back to our Original Working Directory
if success:
# sleep to let files finish writing to disk
sleep (3)
sleep(3)
perms = stat.S_IMODE(os.lstat(os.path.split(filePath)[0]).st_mode)
for dir, subdirs, files in os.walk(outputDestination):
for subdir in subdirs:
if not os.path.join(dir, subdir) in origFiles:
try:
os.chmod(os.path.join(dir, subdir), perms)
except: pass
except:
pass
for file in files:
if not os.path.join(dir, file) in origFiles:
try:
shutil.copymode(filePath, os.path.join(dir, file))
except: pass
except:
pass
return True
else:
core.logger.error("EXTRACTOR: Extraction failed for %s. Result was %s" % (filePath, res))

View file

@ -2,6 +2,7 @@
import json
import requests
class GitHub(object):
"""
Simple api wrapper for the Github API v3.

View file

@ -1 +1 @@
# coding=utf-8
# coding=utf-8

View file

@ -30,6 +30,7 @@ if os.name == 'nt':
info = subprocess.STARTUPINFO()
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
# Prevent spaces from messing with us!
def _escape_param(param):
return '"%s"' % param
@ -45,9 +46,9 @@ def _link_windows(src, dest):
raise IOError(err.output.decode('utf-8'))
# TODO, find out what kind of messages Windows sends us from mklink
# print(stdout)
# assume if they ret-coded 0 we're good
# TODO, find out what kind of messages Windows sends us from mklink
# print(stdout)
# assume if they ret-coded 0 we're good
def _symlink_windows(src, dest):
@ -58,9 +59,10 @@ def _symlink_windows(src, dest):
except CalledProcessError as err:
raise IOError(err.output.decode('utf-8'))
# TODO, find out what kind of messages Windows sends us from mklink
# print(stdout)
# assume if they ret-coded 0 we're good
# TODO, find out what kind of messages Windows sends us from mklink
# print(stdout)
# assume if they ret-coded 0 we're good
def _dirlink_windows(src, dest):
try:
@ -70,9 +72,10 @@ def _dirlink_windows(src, dest):
except CalledProcessError as err:
raise IOError(err.output.decode('utf-8'))
# TODO, find out what kind of messages Windows sends us from mklink
# print(stdout)
# assume if they ret-coded 0 we're good
# TODO, find out what kind of messages Windows sends us from mklink
# print(stdout)
# assume if they ret-coded 0 we're good
def _junctionlink_windows(src, dest):
try:
@ -82,9 +85,10 @@ def _junctionlink_windows(src, dest):
except CalledProcessError as err:
raise IOError(err.output.decode('utf-8'))
# TODO, find out what kind of messages Windows sends us from mklink
# print(stdout)
# assume if they ret-coded 0 we're good
# TODO, find out what kind of messages Windows sends us from mklink
# print(stdout)
# assume if they ret-coded 0 we're good
# Create a hard link to src named as dest
# This version of link, unlike os.link, supports nt systems as well
@ -102,6 +106,7 @@ def symlink(src, dest):
else:
os.symlink(src, dest)
# Create a symlink to src named as dest, but don't fail if you're on nt
def dirlink(src, dest):
if os.name == 'nt':
@ -109,9 +114,10 @@ def dirlink(src, dest):
else:
os.symlink(src, dest)
# Create a symlink to src named as dest, but don't fail if you're on nt
def junctionlink(src, dest):
if os.name == 'nt':
_junctionlink_windows(src, dest)
else:
os.symlink(src, dest)
os.symlink(src, dest)

View file

@ -27,6 +27,7 @@ reverseNames = {u'ERROR': ERROR,
u'POSTPROCESS': POSTPROCESS,
u'DB': DB}
class NTMRotatingLogHandler(object):
def __init__(self, log_file, num_files, num_bytes):
self.num_files = num_files
@ -68,7 +69,7 @@ class NTMRotatingLogHandler(object):
if self.cur_handler:
old_handler = self.cur_handler
else:
#Add a new logging levels
# Add a new logging levels
logging.addLevelName(21, 'POSTPROCESS')
logging.addLevelName(5, 'DB')
@ -85,7 +86,7 @@ class NTMRotatingLogHandler(object):
{'nzbtomedia': logging.Formatter('[%(asctime)s] [%(levelname)s]::%(message)s', '%H:%M:%S'),
'postprocess': logging.Formatter('[%(asctime)s] [%(levelname)s]::%(message)s', '%H:%M:%S'),
'db': logging.Formatter('[%(asctime)s] [%(levelname)s]::%(message)s', '%H:%M:%S')
},
},
logging.Formatter('%(message)s'), ))
# add the handler to the root logger
@ -122,7 +123,7 @@ class NTMRotatingLogHandler(object):
{'nzbtomedia': logging.Formatter('%(asctime)s %(levelname)-8s::%(message)s', '%Y-%m-%d %H:%M:%S'),
'postprocess': logging.Formatter('%(asctime)s %(levelname)-8s::%(message)s', '%Y-%m-%d %H:%M:%S'),
'db': logging.Formatter('%(asctime)s %(levelname)-8s::%(message)s', '%Y-%m-%d %H:%M:%S')
},
},
logging.Formatter('%(message)s'), ))
return file_handler
@ -234,6 +235,7 @@ class NTMRotatingLogHandler(object):
else:
sys.exit(1)
class DispatchingFormatter:
def __init__(self, formatters, default_formatter):
self._formatters = formatters
@ -243,31 +245,41 @@ class DispatchingFormatter:
formatter = self._formatters.get(record.name, self._default_formatter)
return formatter.format(record)
ntm_log_instance = NTMRotatingLogHandler(core.LOG_FILE, NUM_LOGS, LOG_SIZE)
def log(toLog, logLevel=MESSAGE, section='MAIN'):
ntm_log_instance.log(toLog, logLevel, section)
def info(toLog, section='MAIN'):
log(toLog, MESSAGE, section)
def error(toLog, section='MAIN'):
log(toLog, ERROR, section)
def warning(toLog, section='MAIN'):
log(toLog, WARNING, section)
def debug(toLog, section='MAIN'):
log(toLog, DEBUG, section)
def postprocess(toLog, section='POSTPROCESS'):
log(toLog, POSTPROCESS, section)
def db(toLog, section='DB'):
log(toLog, DB, section)
def log_error_and_exit(error_msg):
ntm_log_instance.log_error_and_exit(error_msg)
def close():
ntm_log_instance.close_log()

View file

@ -4,6 +4,7 @@ import core
import requests
from core import logger
def autoFork(section, inputCategory):
# auto-detect correct section
# config settings
@ -49,13 +50,13 @@ def autoFork(section, inputCategory):
detected = False
if section == "NzbDrone":
logger.info("Attempting to verify %s fork" % inputCategory)
url = "%s%s:%s%s/api/rootfolder" % (protocol,host,port,web_root)
headers={"X-Api-Key": apikey}
url = "%s%s:%s%s/api/rootfolder" % (protocol, host, port, web_root)
headers = {"X-Api-Key": apikey}
try:
r = requests.get(url, headers=headers, stream=True, verify=False)
except requests.ConnectionError:
logger.warning("Could not connect to %s:%s to verify fork!" % (section, inputCategory))
if not r.ok:
logger.warning("Connection to %s:%s failed! Check your configuration" % (section, inputCategory))
@ -67,12 +68,12 @@ def autoFork(section, inputCategory):
logger.info("Attempting to auto-detect %s fork" % inputCategory)
# define the order to test. Default must be first since the default fork doesn't reject parameters.
# then in order of most unique parameters.
url = "%s%s:%s%s/home/postprocess/" % (protocol,host,port,web_root)
url = "%s%s:%s%s/home/postprocess/" % (protocol, host, port, web_root)
# attempting to auto-detect fork
try:
if username and password:
s = requests.Session()
login = "%s%s:%s%s/login" % (protocol,host,port,web_root)
login = "%s%s:%s%s/login" % (protocol, host, port, web_root)
login_params = {'username': username, 'password': password}
s.post(login, data=login_params, stream=True, verify=False)
r = s.get(url, auth=(username, password), verify=False)
@ -83,10 +84,10 @@ def autoFork(section, inputCategory):
r = []
if r and r.ok:
for param in params:
if not 'name="%s"' %(param) in r.text:
if not 'name="%s"' % (param) in r.text:
rem_params.append(param)
for param in rem_params:
params.pop(param)
params.pop(param)
for fork in sorted(core.FORKS.iteritems(), reverse=False):
if params == fork[1]:
detected = True
@ -101,4 +102,4 @@ def autoFork(section, inputCategory):
fork = core.FORKS.items()[core.FORKS.keys().index(core.FORK_DEFAULT)]
logger.info("%s:%s fork set to %s" % (section, inputCategory, fork[0]))
return fork[0], fork[1]
return fork[0], fork[1]

View file

@ -8,13 +8,15 @@ from core import logger
from itertools import chain
class Section(configobj.Section):
def isenabled(section):
# checks if subsection enabled, returns true/false if subsection specified otherwise returns true/false in {}
if not section.sections:
try:
value = list(ConfigObj.find_key(section, 'enabled'))[0]
except:value = 0
except:
value = 0
if int(value) == 1:
return section
else:
@ -23,7 +25,8 @@ class Section(configobj.Section):
for subsection in subsections:
try:
value = list(ConfigObj.find_key(subsections, 'enabled'))[0]
except:value = 0
except:
value = 0
if int(value) != 1:
del to_return[section_name][subsection]
@ -39,7 +42,8 @@ class Section(configobj.Section):
for subsection in to_return:
try:
value = list(ConfigObj.find_key(to_return[subsection], key))[0]
except:value = None
except:
value = None
if not value:
del to_return[subsection]
@ -80,6 +84,7 @@ class Section(configobj.Section):
return to_return
class ConfigObj(configobj.ConfigObj, Section):
def __init__(self, *args, **kw):
if len(args) == 0:
@ -190,7 +195,8 @@ class ConfigObj(configobj.ConfigObj, Section):
if not list(ConfigObj.find_key(CFG_NEW, option)):
try:
values.pop(option)
except: pass
except:
pass
return values
@ -221,7 +227,7 @@ class ConfigObj(configobj.ConfigObj, Section):
subsection = None
if section in list(chain.from_iterable(subsections.values())):
subsection = section
section = ''.join([k for k,v in subsections.iteritems() if subsection in v])
section = ''.join([k for k, v in subsections.iteritems() if subsection in v])
process_section(section, subsection)
elif section in subsections.keys():
subsection = subsections[section]
@ -247,7 +253,8 @@ class ConfigObj(configobj.ConfigObj, Section):
try:
if os.environ.has_key('NZBPO_NDCATEGORY') and os.environ.has_key('NZBPO_SBCATEGORY'):
if os.environ['NZBPO_NDCATEGORY'] == os.environ['NZBPO_SBCATEGORY']:
logger.warning("%s category is set for SickBeard and NzbDrone. Please check your config in NZBGet" % (os.environ['NZBPO_NDCATEGORY']))
logger.warning("%s category is set for SickBeard and NzbDrone. "
"Please check your config in NZBGet" % (os.environ['NZBPO_NDCATEGORY']))
section = "Nzb"
key = 'NZBOP_DESTDIR'
@ -274,12 +281,14 @@ class ConfigObj(configobj.ConfigObj, Section):
if os.environ.has_key(key):
option = cfgKeys[index]
value = os.environ[key]
CFG_NEW[section][option] = value
CFG_NEW[section][option] = value
section = "CouchPotato"
envCatKey = 'NZBPO_CPSCATEGORY'
envKeys = ['ENABLED', 'APIKEY', 'HOST', 'PORT', 'SSL', 'WEB_ROOT', 'METHOD', 'DELETE_FAILED', 'REMOTE_PATH', 'WAIT_FOR', 'WATCH_DIR']
cfgKeys = ['enabled', 'apikey', 'host', 'port', 'ssl', 'web_root', 'method', 'delete_failed', 'remote_path', 'wait_for', 'watch_dir']
envKeys = ['ENABLED', 'APIKEY', 'HOST', 'PORT', 'SSL', 'WEB_ROOT', 'METHOD', 'DELETE_FAILED', 'REMOTE_PATH',
'WAIT_FOR', 'WATCH_DIR']
cfgKeys = ['enabled', 'apikey', 'host', 'port', 'ssl', 'web_root', 'method', 'delete_failed', 'remote_path',
'wait_for', 'watch_dir']
if os.environ.has_key(envCatKey):
for index in range(len(envKeys)):
key = 'NZBPO_CPS' + envKeys[index]
@ -293,8 +302,10 @@ class ConfigObj(configobj.ConfigObj, Section):
section = "SickBeard"
envCatKey = 'NZBPO_SBCATEGORY'
envKeys = ['ENABLED', 'HOST', 'PORT', 'USERNAME', 'PASSWORD', 'SSL', 'WEB_ROOT', 'WATCH_DIR', 'FORK', 'DELETE_FAILED', 'TORRENT_NOLINK', 'NZBEXTRACTIONBY', 'REMOTE_PATH', 'PROCESS_METHOD']
cfgKeys = ['enabled', 'host', 'port', 'username', 'password', 'ssl', 'web_root', 'watch_dir', 'fork', 'delete_failed', 'Torrent_NoLink', 'nzbExtractionBy', 'remote_path', 'process_method']
envKeys = ['ENABLED', 'HOST', 'PORT', 'USERNAME', 'PASSWORD', 'SSL', 'WEB_ROOT', 'WATCH_DIR', 'FORK',
'DELETE_FAILED', 'TORRENT_NOLINK', 'NZBEXTRACTIONBY', 'REMOTE_PATH', 'PROCESS_METHOD']
cfgKeys = ['enabled', 'host', 'port', 'username', 'password', 'ssl', 'web_root', 'watch_dir', 'fork',
'delete_failed', 'Torrent_NoLink', 'nzbExtractionBy', 'remote_path', 'process_method']
if os.environ.has_key(envCatKey):
for index in range(len(envKeys)):
key = 'NZBPO_SB' + envKeys[index]
@ -325,8 +336,10 @@ class ConfigObj(configobj.ConfigObj, Section):
section = "Mylar"
envCatKey = 'NZBPO_MYCATEGORY'
envKeys = ['ENABLED', 'HOST', 'PORT', 'USERNAME', 'PASSWORD', 'APIKEY', 'SSL', 'WEB_ROOT', 'WATCH_DIR', 'REMOTE_PATH']
cfgKeys = ['enabled', 'host', 'port', 'username', 'password', 'apikey', 'ssl', 'web_root', 'watch_dir', 'remote_path']
envKeys = ['ENABLED', 'HOST', 'PORT', 'USERNAME', 'PASSWORD', 'APIKEY', 'SSL', 'WEB_ROOT', 'WATCH_DIR',
'REMOTE_PATH']
cfgKeys = ['enabled', 'host', 'port', 'username', 'password', 'apikey', 'ssl', 'web_root', 'watch_dir',
'remote_path']
if os.environ.has_key(envCatKey):
for index in range(len(envKeys)):
key = 'NZBPO_MY' + envKeys[index]
@ -355,8 +368,10 @@ class ConfigObj(configobj.ConfigObj, Section):
section = "NzbDrone"
envCatKey = 'NZBPO_NDCATEGORY'
envKeys = ['ENABLED', 'HOST', 'APIKEY', 'PORT', 'SSL', 'WEB_ROOT', 'WATCH_DIR', 'FORK', 'DELETE_FAILED', 'TORRENT_NOLINK', 'NZBEXTRACTIONBY', 'WAIT_FOR', 'DELETE_FAILED', 'REMOTE_PATH']
cfgKeys = ['enabled', 'host', 'apikey', 'port', 'ssl', 'web_root', 'watch_dir', 'fork', 'delete_failed', 'Torrent_NoLink', 'nzbExtractionBy', 'wait_for', 'delete_failed', 'remote_path']
envKeys = ['ENABLED', 'HOST', 'APIKEY', 'PORT', 'SSL', 'WEB_ROOT', 'WATCH_DIR', 'FORK', 'DELETE_FAILED',
'TORRENT_NOLINK', 'NZBEXTRACTIONBY', 'WAIT_FOR', 'DELETE_FAILED', 'REMOTE_PATH']
cfgKeys = ['enabled', 'host', 'apikey', 'port', 'ssl', 'web_root', 'watch_dir', 'fork', 'delete_failed',
'Torrent_NoLink', 'nzbExtractionBy', 'wait_for', 'delete_failed', 'remote_path']
if os.environ.has_key(envCatKey):
for index in range(len(envKeys)):
key = 'NZBPO_ND' + envKeys[index]
@ -391,16 +406,26 @@ class ConfigObj(configobj.ConfigObj, Section):
CFG_NEW[section][option] = value
section = "Transcoder"
envKeys = ['TRANSCODE', 'DUPLICATE', 'IGNOREEXTENSIONS', 'OUTPUTFASTSTART', 'OUTPUTVIDEOPATH', 'PROCESSOUTPUT', 'AUDIOLANGUAGE', 'ALLAUDIOLANGUAGES', 'SUBLANGUAGES',
'ALLSUBLANGUAGES', 'EMBEDSUBS', 'BURNINSUBTITLE', 'EXTRACTSUBS', 'EXTERNALSUBDIR', 'OUTPUTDEFAULT', 'OUTPUTVIDEOEXTENSION', 'OUTPUTVIDEOCODEC', 'VIDEOCODECALLOW',
'OUTPUTVIDEOPRESET', 'OUTPUTVIDEOFRAMERATE', 'OUTPUTVIDEOBITRATE', 'OUTPUTAUDIOCODEC', 'AUDIOCODECALLOW', 'OUTPUTAUDIOBITRATE', 'OUTPUTQUALITYPERCENT', 'GETSUBS',
'OUTPUTAUDIOTRACK2CODEC', 'AUDIOCODEC2ALLOW', 'OUTPUTAUDIOTRACK2BITRATE', 'OUTPUTAUDIOOTHERCODEC', 'AUDIOOTHERCODECALLOW', 'OUTPUTAUDIOOTHERBITRATE',
'OUTPUTSUBTITLECODEC', 'OUTPUTAUDIOCHANNELS', 'OUTPUTAUDIOTRACK2CHANNELS', 'OUTPUTAUDIOOTHERCHANNELS']
cfgKeys = ['transcode', 'duplicate', 'ignoreExtensions', 'outputFastStart', 'outputVideoPath', 'processOutput', 'audioLanguage', 'allAudioLanguages', 'subLanguages',
'allSubLanguages', 'embedSubs', 'burnInSubtitle', 'extractSubs', 'externalSubDir', 'outputDefault', 'outputVideoExtension', 'outputVideoCodec', 'VideoCodecAllow',
'outputVideoPreset', 'outputVideoFramerate', 'outputVideoBitrate', 'outputAudioCodec', 'AudioCodecAllow', 'outputAudioBitrate', 'outputQualityPercent', 'getSubs',
'outputAudioTrack2Codec', 'AudioCodec2Allow', 'outputAudioTrack2Bitrate', 'outputAudioOtherCodec', 'AudioOtherCodecAllow', 'outputAudioOtherBitrate',
'outputSubtitleCodec', 'outputAudioChannels', 'outputAudioTrack2Channels', 'outputAudioOtherChannels']
envKeys = ['TRANSCODE', 'DUPLICATE', 'IGNOREEXTENSIONS', 'OUTPUTFASTSTART', 'OUTPUTVIDEOPATH',
'PROCESSOUTPUT', 'AUDIOLANGUAGE', 'ALLAUDIOLANGUAGES', 'SUBLANGUAGES',
'ALLSUBLANGUAGES', 'EMBEDSUBS', 'BURNINSUBTITLE', 'EXTRACTSUBS', 'EXTERNALSUBDIR',
'OUTPUTDEFAULT', 'OUTPUTVIDEOEXTENSION', 'OUTPUTVIDEOCODEC', 'VIDEOCODECALLOW',
'OUTPUTVIDEOPRESET', 'OUTPUTVIDEOFRAMERATE', 'OUTPUTVIDEOBITRATE', 'OUTPUTAUDIOCODEC',
'AUDIOCODECALLOW', 'OUTPUTAUDIOBITRATE', 'OUTPUTQUALITYPERCENT', 'GETSUBS',
'OUTPUTAUDIOTRACK2CODEC', 'AUDIOCODEC2ALLOW', 'OUTPUTAUDIOTRACK2BITRATE',
'OUTPUTAUDIOOTHERCODEC', 'AUDIOOTHERCODECALLOW', 'OUTPUTAUDIOOTHERBITRATE',
'OUTPUTSUBTITLECODEC', 'OUTPUTAUDIOCHANNELS', 'OUTPUTAUDIOTRACK2CHANNELS',
'OUTPUTAUDIOOTHERCHANNELS']
cfgKeys = ['transcode', 'duplicate', 'ignoreExtensions', 'outputFastStart', 'outputVideoPath',
'processOutput', 'audioLanguage', 'allAudioLanguages', 'subLanguages',
'allSubLanguages', 'embedSubs', 'burnInSubtitle', 'extractSubs', 'externalSubDir',
'outputDefault', 'outputVideoExtension', 'outputVideoCodec', 'VideoCodecAllow',
'outputVideoPreset', 'outputVideoFramerate', 'outputVideoBitrate', 'outputAudioCodec',
'AudioCodecAllow', 'outputAudioBitrate', 'outputQualityPercent', 'getSubs',
'outputAudioTrack2Codec', 'AudioCodec2Allow', 'outputAudioTrack2Bitrate',
'outputAudioOtherCodec', 'AudioOtherCodecAllow', 'outputAudioOtherBitrate',
'outputSubtitleCodec', 'outputAudioChannels', 'outputAudioTrack2Channels',
'outputAudioOtherChannels']
for index in range(len(envKeys)):
key = 'NZBPO_' + envKeys[index]
if os.environ.has_key(key):
@ -420,8 +445,10 @@ class ConfigObj(configobj.ConfigObj, Section):
section = "UserScript"
envCatKey = 'NZBPO_USCATEGORY'
envKeys = ['USER_SCRIPT_MEDIAEXTENSIONS', 'USER_SCRIPT_PATH', 'USER_SCRIPT_PARAM', 'USER_SCRIPT_RUNONCE', 'USER_SCRIPT_SUCCESSCODES', 'USER_SCRIPT_CLEAN', 'USDELAY', 'USREMOTE_PATH']
cfgKeys = ['user_script_mediaExtensions', 'user_script_path', 'user_script_param', 'user_script_runOnce', 'user_script_successCodes', 'user_script_clean', 'delay', 'remote_path']
envKeys = ['USER_SCRIPT_MEDIAEXTENSIONS', 'USER_SCRIPT_PATH', 'USER_SCRIPT_PARAM', 'USER_SCRIPT_RUNONCE',
'USER_SCRIPT_SUCCESSCODES', 'USER_SCRIPT_CLEAN', 'USDELAY', 'USREMOTE_PATH']
cfgKeys = ['user_script_mediaExtensions', 'user_script_path', 'user_script_param', 'user_script_runOnce',
'user_script_successCodes', 'user_script_clean', 'delay', 'remote_path']
if os.environ.has_key(envCatKey):
for index in range(len(envKeys)):
key = 'NZBPO_' + envKeys[index]
@ -441,10 +468,11 @@ class ConfigObj(configobj.ConfigObj, Section):
CFG_NEW.filename = core.CONFIG_FILE
CFG_NEW.write()
except Exception, e:
logger.debug("Error %s when writing changes to .cfg" % (e))
logger.debug("Error %s when writing changes to .cfg" % (e))
return CFG_NEW
configobj.Section = Section
configobj.ConfigObj = ConfigObj
config = ConfigObj

View file

@ -8,6 +8,7 @@ import time
import core
from core import logger
def dbFilename(filename="nzbtomedia.db", suffix=None):
"""
@param filename: The sqlite database filename to use. If not specified,
@ -153,7 +154,6 @@ class DBConnection:
return sqlResult
def select(self, query, args=None):
sqlResults = self.action(query, args).fetchall()
@ -244,7 +244,7 @@ class SchemaUpgrade(object):
self.connection = connection
def hasTable(self, tableName):
return len(self.connection.action("SELECT 1 FROM sqlite_master WHERE name = ?;", (tableName, )).fetchall()) > 0
return len(self.connection.action("SELECT 1 FROM sqlite_master WHERE name = ?;", (tableName,)).fetchall()) > 0
def hasColumn(self, tableName, column):
return column in self.connection.tableInfo(tableName)
@ -264,4 +264,3 @@ class SchemaUpgrade(object):
new_version = self.checkDBVersion() + 1
self.connection.action("UPDATE db_version SET db_version = ?", [new_version])
return new_version

View file

@ -2,23 +2,28 @@
import os
import re
import core
import shlex
import shlex
from core import logger
from core.nzbToMediaUtil import listMediaFiles
reverse_list = [r"\.\d{2}e\d{2}s\.", r"\.[pi]0801\.", r"\.p027\.", r"\.[pi]675\.", r"\.[pi]084\.", r"\.p063\.", r"\b[45]62[xh]\.", r"\.yarulb\.", r"\.vtd[hp]\.",
r"\.ld[.-]?bew\.", r"\.pir.?(dov|dvd|bew|db|rb)\.", r"\brdvd\.", r"\.vts\.", r"\.reneercs\.", r"\.dcv\.", r"\b(pir|mac)dh\b", r"\.reporp\.", r"\.kcaper\.",
reverse_list = [r"\.\d{2}e\d{2}s\.", r"\.[pi]0801\.", r"\.p027\.", r"\.[pi]675\.", r"\.[pi]084\.", r"\.p063\.",
r"\b[45]62[xh]\.", r"\.yarulb\.", r"\.vtd[hp]\.",
r"\.ld[.-]?bew\.", r"\.pir.?(dov|dvd|bew|db|rb)\.", r"\brdvd\.", r"\.vts\.", r"\.reneercs\.",
r"\.dcv\.", r"\b(pir|mac)dh\b", r"\.reporp\.", r"\.kcaper\.",
r"\.lanretni\.", r"\b3ca\b", r"\.cstn\."]
reverse_pattern = re.compile('|'.join(reverse_list), flags=re.IGNORECASE)
season_pattern = re.compile(r"(.*\.\d{2}e\d{2}s\.)(.*)", flags=re.IGNORECASE)
word_pattern = re.compile(r"([^A-Z0-9]*[A-Z0-9]+)")
media_list = [r"\.s\d{2}e\d{2}\.", r"\.1080[pi]\.", r"\.720p\.", r"\.576[pi]", r"\.480[pi]\.", r"\.360p\.", r"\.[xh]26[45]\b", r"\.bluray\.", r"\.[hp]dtv\.",
r"\.web[.-]?dl\.", r"\.(vod|dvd|web|bd|br).?rip\.", r"\.dvdr\b", r"\.stv\.", r"\.screener\.", r"\.vcd\.", r"\bhd(cam|rip)\b", r"\.proper\.", r"\.repack\.",
media_list = [r"\.s\d{2}e\d{2}\.", r"\.1080[pi]\.", r"\.720p\.", r"\.576[pi]", r"\.480[pi]\.", r"\.360p\.",
r"\.[xh]26[45]\b", r"\.bluray\.", r"\.[hp]dtv\.",
r"\.web[.-]?dl\.", r"\.(vod|dvd|web|bd|br).?rip\.", r"\.dvdr\b", r"\.stv\.", r"\.screener\.", r"\.vcd\.",
r"\bhd(cam|rip)\b", r"\.proper\.", r"\.repack\.",
r"\.internal\.", r"\bac3\b", r"\.ntsc\.", r"\.pal\.", r"\.secam\.", r"\bdivx\b", r"\bxvid\b"]
media_pattern = re.compile('|'.join(media_list), flags=re.IGNORECASE)
garbage_name = re.compile(r"^[a-zA-Z0-9]*$")
char_replace = [[r"(\w)1\.(\w)",r"\1i\2"]
]
char_replace = [[r"(\w)1\.(\w)", r"\1i\2"]
]
def process_all_exceptions(name, dirname):
rename_script(dirname)
@ -27,7 +32,7 @@ def process_all_exceptions(name, dirname):
parentDir = os.path.dirname(filename)
head, fileExtension = os.path.splitext(os.path.basename(filename))
if reverse_pattern.search(head) is not None:
exception = reverse_filename
exception = reverse_filename
elif garbage_name.search(head) is not None:
exception = replace_filename
else:
@ -38,7 +43,8 @@ def process_all_exceptions(name, dirname):
if core.GROUPS:
newfilename = strip_groups(newfilename)
if newfilename != filename:
rename_file(filename, newfilename)
rename_file(filename, newfilename)
def strip_groups(filename):
if not core.GROUPS:
@ -48,33 +54,36 @@ def strip_groups(filename):
newname = head.replace(' ', '.')
for group in core.GROUPS:
newname = newname.replace(group, '')
newname = newname.replace('[]', '')
newname = newname.replace('[]', '')
newfile = newname + fileExtension
newfilePath = os.path.join(dirname, newfile)
return newfilePath
def rename_file(filename, newfilePath):
logger.debug("Replacing file name %s with download name %s" % (filename, newfilePath), "EXCEPTION")
try:
os.rename(filename, newfilePath)
except Exception,e:
except Exception, e:
logger.error("Unable to rename file due to: %s" % (str(e)), "EXCEPTION")
def replace_filename(filename, dirname, name):
head, fileExtension = os.path.splitext(os.path.basename(filename))
if media_pattern.search(os.path.basename(dirname).replace(' ','.')) is not None:
if media_pattern.search(os.path.basename(dirname).replace(' ', '.')) is not None:
newname = os.path.basename(dirname).replace(' ', '.')
logger.debug("Replacing file name %s with directory name %s" % (head, newname), "EXCEPTION")
elif media_pattern.search(name.replace(' ','.').lower()) is not None:
elif media_pattern.search(name.replace(' ', '.').lower()) is not None:
newname = name.replace(' ', '.')
logger.debug("Replacing file name %s with download name %s" % (head, newname), "EXCEPTION")
else:
logger.warning("No name replacement determined for %s" % (head), "EXCEPTION")
newname = name
newname = name
newfile = newname + fileExtension
newfilePath = os.path.join(dirname, newfile)
return newfilePath
def reverse_filename(filename, dirname, name):
head, fileExtension = os.path.splitext(os.path.basename(filename))
na_parts = season_pattern.search(head)
@ -85,11 +94,11 @@ def reverse_filename(filename, dirname, name):
for wp in word_p:
if wp[0] == ".":
new_words += "."
new_words += re.sub(r"\W","",wp)
new_words += re.sub(r"\W", "", wp)
else:
new_words = na_parts.group(2)
for cr in char_replace:
new_words = re.sub(cr[0],cr[1],new_words)
new_words = re.sub(cr[0], cr[1], new_words)
newname = new_words[::-1] + na_parts.group(1)[::-1]
else:
newname = head[::-1].title()
@ -99,15 +108,16 @@ def reverse_filename(filename, dirname, name):
newfilePath = os.path.join(dirname, newfile)
return newfilePath
def rename_script(dirname):
rename_file = ""
for dir, dirs, files in os.walk(dirname):
for file in files:
if re.search('(rename\S*\.(sh|bat)$)',file,re.IGNORECASE):
if re.search('(rename\S*\.(sh|bat)$)', file, re.IGNORECASE):
rename_file = os.path.join(dir, file)
dirname = dir
break
if rename_file:
if rename_file:
rename_lines = [line.strip() for line in open(rename_file)]
for line in rename_lines:
if re.search('^(mv|Move)', line, re.IGNORECASE):
@ -122,10 +132,9 @@ def rename_script(dirname):
logger.debug("Renaming file %s to %s" % (orig, dest), "EXCEPTION")
try:
os.rename(orig, dest)
except Exception,e:
except Exception, e:
logger.error("Unable to rename file due to: %s" % (str(e)), "EXCEPTION")
# dict for custom groups
# we can add more to this list
#__customgroups__ = {'Q o Q': process_qoq, '-ECI': process_eci}
# _customgroups = {'Q o Q': process_qoq, '-ECI': process_eci}

View file

@ -6,12 +6,14 @@ from core.transcoder import transcoder
from core.nzbToMediaUtil import import_subs, listMediaFiles, rmDir
from core import logger
def external_script(outputDestination, torrentName, torrentLabel, settings):
final_result = 0 # start at 0.
num_files = 0
try:
core.USER_SCRIPT_MEDIAEXTENSIONS = settings["user_script_mediaExtensions"]
if isinstance(core.USER_SCRIPT_MEDIAEXTENSIONS, str): core.USER_SCRIPT_MEDIAEXTENSIONS = core.USER_SCRIPT_MEDIAEXTENSIONS.split(',')
if isinstance(core.USER_SCRIPT_MEDIAEXTENSIONS, str):
core.USER_SCRIPT_MEDIAEXTENSIONS = core.USER_SCRIPT_MEDIAEXTENSIONS.split(',')
except:
core.USER_SCRIPT_MEDIAEXTENSIONS = []
try:
@ -22,12 +24,14 @@ def external_script(outputDestination, torrentName, torrentLabel, settings):
return [0, ""]
try:
core.USER_SCRIPT_PARAM = settings["user_script_param"]
if isinstance(core.USER_SCRIPT_PARAM, str): core.USER_SCRIPT_PARAM = core.USER_SCRIPT_PARAM.split(',')
if isinstance(core.USER_SCRIPT_PARAM, str):
core.USER_SCRIPT_PARAM = core.USER_SCRIPT_PARAM.split(',')
except:
core.USER_SCRIPT_PARAM = []
try:
core.USER_SCRIPT_SUCCESSCODES = settings["user_script_successCodes"]
if isinstance(core.USER_SCRIPT_SUCCESSCODES, str): core.USER_SCRIPT_SUCCESSCODES = core.USER_SCRIPT_SUCCESSCODES.split(',')
if isinstance(core.USER_SCRIPT_SUCCESSCODES, str):
core.USER_SCRIPT_SUCCESSCODES = core.USER_SCRIPT_SUCCESSCODES.split(',')
except:
core.USER_SCRIPT_SUCCESSCODES = 0
try:

View file

@ -14,7 +14,7 @@ import beets
import requests
import core
from babelfish import Language
import subliminal
import subliminal
from core.extractor import extractor
from core.linktastic import linktastic
@ -25,13 +25,14 @@ from core import logger, nzbToMediaDB
requests.packages.urllib3.disable_warnings()
def reportNzb(failure_link, clientAgent):
# Contact indexer site
logger.info("Sending failure notification to indexer site")
if clientAgent == 'nzbget':
headers = {'User-Agent' : 'NZBGet / nzbToMedia.py'}
headers = {'User-Agent': 'NZBGet / nzbToMedia.py'}
elif clientAgent == 'sabnzbd':
headers = {'User-Agent' : 'SABnzbd / nzbToMedia.py'}
headers = {'User-Agent': 'SABnzbd / nzbToMedia.py'}
else:
return
try:
@ -40,8 +41,9 @@ def reportNzb(failure_link, clientAgent):
logger.error("Unable to open URL %s due to %s" % (failure_link, e))
return
def sanitizeName(name):
'''
"""
>>> sanitizeName('a/b/c')
'a-b-c'
>>> sanitizeName('abc')
@ -50,7 +52,7 @@ def sanitizeName(name):
'ab'
>>> sanitizeName('.a.b..')
'a.b'
'''
"""
# remove bad chars from the filename
name = re.sub(r'[\\\/*]', '-', name)
@ -60,10 +62,12 @@ def sanitizeName(name):
name = name.strip(' .')
try:
name = name.encode(core.SYS_ENCODING)
except: pass
except:
pass
return name
def makeDir(path):
if not os.path.isdir(path):
try:
@ -72,12 +76,13 @@ def makeDir(path):
return False
return True
def remoteDir(path):
if not core.REMOTEPATHS:
return path
for local,remote in core.REMOTEPATHS:
for local, remote in core.REMOTEPATHS:
if local in path:
base_dirs = path.replace(local,"").split(os.sep)
base_dirs = path.replace(local, "").split(os.sep)
if '/' in remote:
remote_sep = '/'
else:
@ -89,22 +94,25 @@ def remoteDir(path):
return new_path
return path
def category_search(inputDirectory, inputName, inputCategory, root, categories):
tordir = False
try:
inputName = inputName.encode(core.SYS_ENCODING)
except: pass
except:
pass
try:
inputDirectory = inputDirectory.encode(core.SYS_ENCODING)
except: pass
except:
pass
if inputDirectory is None: # =Nothing to process here.
return inputDirectory, inputName, inputCategory, root
pathlist = os.path.normpath(inputDirectory).split(os.sep)
if inputCategory and inputCategory in pathlist:
if inputCategory and inputCategory in pathlist:
logger.debug("SEARCH: Found the Category: %s in directory structure" % (inputCategory))
elif inputCategory:
logger.debug("SEARCH: Could not find the category: %s in the directory structure" % (inputCategory))
@ -116,7 +124,8 @@ def category_search(inputDirectory, inputName, inputCategory, root, categories):
inputCategory = ""
logger.debug("SEARCH: Could not find a category in the directory structure")
if not os.path.isdir(inputDirectory) and os.path.isfile(inputDirectory): # If the input directory is a file
if not inputName: inputName = os.path.split(os.path.normpath(inputDirectory))[1]
if not inputName:
inputName = os.path.split(os.path.normpath(inputDirectory))[1]
return inputDirectory, inputName, inputCategory, root
if inputCategory and os.path.isdir(os.path.join(inputDirectory, inputCategory)):
@ -158,7 +167,8 @@ def category_search(inputDirectory, inputName, inputCategory, root, categories):
if index + 1 < len(pathlist):
tordir = True
logger.info("SEARCH: Found a unique directory %s in the category directory" % (pathlist[index + 1]))
if not inputName: inputName = pathlist[index + 1]
if not inputName:
inputName = pathlist[index + 1]
except ValueError:
pass
@ -177,15 +187,17 @@ def category_search(inputDirectory, inputName, inputCategory, root, categories):
return inputDirectory, inputName, inputCategory, root
def getDirSize(inputPath):
from functools import partial
prepend = partial(os.path.join, inputPath)
return sum([(os.path.getsize(f) if os.path.isfile(f) else getDirSize(f)) for f in map(prepend, os.listdir(inputPath))])
from functools import partial
prepend = partial(os.path.join, inputPath)
return sum(
[(os.path.getsize(f) if os.path.isfile(f) else getDirSize(f)) for f in map(prepend, os.listdir(inputPath))])
def is_minSize(inputName, minSize):
fileName, fileExt = os.path.splitext(os.path.basename(inputName))
# audio files we need to check directory size not file size
inputSize = os.path.getsize(inputName)
if fileExt in (core.AUDIOCONTAINER):
@ -199,11 +211,13 @@ def is_minSize(inputName, minSize):
if inputSize > minSize * 1048576:
return True
def is_sample(inputName):
# Ignore 'sample' in files
if re.search('(^|[\W_])sample\d*[\W_]', inputName.lower()):
return True
def copy_link(src, targetLink, useLink):
logger.info("MEDIAFILE: [%s]" % (os.path.basename(targetLink)), 'COPYLINK')
logger.info("SOURCE FOLDER: [%s]" % (os.path.dirname(src)), 'COPYLINK')
@ -254,6 +268,7 @@ def copy_link(src, targetLink, useLink):
return True
def replace_links(link):
n = 0
target = link
@ -277,6 +292,7 @@ def replace_links(link):
os.unlink(link)
linktastic.symlink(target, link)
def flatten(outputDestination):
logger.info("FLATTEN: Flattening directory: %s" % (outputDestination))
for outputFile in listMediaFiles(outputDestination):
@ -295,29 +311,31 @@ def flatten(outputDestination):
removeEmptyFolders(outputDestination) # Cleanup empty directories
def removeEmptyFolders(path, removeRoot=True):
'Function to remove empty folders'
if not os.path.isdir(path):
return
"""Function to remove empty folders"""
if not os.path.isdir(path):
return
# remove empty subfolders
logger.debug("Checking for empty folders in:%s" % (path))
files = os.listdir(path)
if len(files):
for f in files:
fullpath = os.path.join(path, f)
if os.path.isdir(fullpath):
removeEmptyFolders(fullpath)
# remove empty subfolders
logger.debug("Checking for empty folders in:%s" % (path))
files = os.listdir(path)
if len(files):
for f in files:
fullpath = os.path.join(path, f)
if os.path.isdir(fullpath):
removeEmptyFolders(fullpath)
# if folder empty, delete it
files = os.listdir(path)
if len(files) == 0 and removeRoot:
logger.debug("Removing empty folder:%s" % (path))
os.rmdir(path)
# if folder empty, delete it
files = os.listdir(path)
if len(files) == 0 and removeRoot:
logger.debug("Removing empty folder:%s" % (path))
os.rmdir(path)
def rmReadOnly(filename):
if os.path.isfile(filename):
#check first the read-only attribute
# check first the read-only attribute
file_attribute = os.stat(filename)[0]
if (not file_attribute & stat.S_IWRITE):
# File is read-only, so make it writeable
@ -327,7 +345,8 @@ def rmReadOnly(filename):
except:
logger.warning('Cannot change permissions of ' + filename, logger.WARNING)
#Wake function
# Wake function
def WakeOnLan(ethernet_address):
addr_byte = ethernet_address.split(':')
hw_addr = struct.pack(b'BBBBBB', int(addr_byte[0], 16),
@ -349,7 +368,7 @@ def WakeOnLan(ethernet_address):
ss.close()
#Test Connection function
# Test Connection function
def TestCon(host, port):
try:
socket.create_connection((host, port))
@ -372,7 +391,7 @@ def WakeUp():
if TestCon(host, port) == "Down": # final check.
logger.warning("System with mac: %s has not woken after 3 attempts. Continuing with the rest of the script." % (
mac))
mac))
else:
logger.info("System with mac: %s has been woken. Continuing with the rest of the script." % (mac))
@ -392,7 +411,8 @@ def CharReplace(Name):
# /!\ detection is done 2char by 2char for UTF-8 special character
if (len(Name) != 1) & (Idx < (len(Name) - 1)):
# Detect UTF-8
if ((Name[Idx] == '\xC2') | (Name[Idx] == '\xC3')) & ((Name[Idx+1] >= '\xA0') & (Name[Idx+1] <= '\xFF')):
if ((Name[Idx] == '\xC2') | (Name[Idx] == '\xC3')) & (
(Name[Idx + 1] >= '\xA0') & (Name[Idx + 1] <= '\xFF')):
encoding = 'utf-8'
break
# Detect CP850
@ -433,7 +453,7 @@ def convert_to_ascii(inputName, dirName):
if encoded:
dirName = os.path.join(dir, base2)
logger.info("Renaming directory to: %s." % (base2), 'ENCODER')
os.rename(os.path.join(dir,base), dirName)
os.rename(os.path.join(dir, base), dirName)
if os.environ.has_key('NZBOP_SCRIPTDIR'):
print "[NZB] DIRECTORY=%s" % (dirName) # Return the new directory to NZBGet.
@ -576,23 +596,23 @@ def parse_args(clientAgent, args):
return None, None, None, None, None
def getDirs(section, subsection, link = 'hard'):
def getDirs(section, subsection, link='hard'):
to_return = []
def processDir(path):
folders = []
logger.info("Searching %s for mediafiles to post-process ..." % (path))
sync = [ o for o in os.listdir(path) if os.path.splitext(o)[1] in ['.!sync','.bts'] ]
sync = [o for o in os.listdir(path) if os.path.splitext(o)[1] in ['.!sync', '.bts']]
# search for single files and move them into their own folder for post-processing
for mediafile in [ os.path.join(path, o) for o in os.listdir(path) if
os.path.isfile(os.path.join(path, o)) ]:
for mediafile in [os.path.join(path, o) for o in os.listdir(path) if
os.path.isfile(os.path.join(path, o))]:
if len(sync) > 0:
break
if os.path.split(mediafile)[1] in ['Thumbs.db', 'thumbs.db']:
continue
try:
logger.debug("Found file %s in root directory %s." % (os.path.split(mediafile)[1], path))
logger.debug("Found file %s in root directory %s." % (os.path.split(mediafile)[1], path))
newPath = None
fileExt = os.path.splitext(mediafile)[1]
try:
@ -627,8 +647,9 @@ def getDirs(section, subsection, link = 'hard'):
newPath = os.path.join(path, sanitizeName(title))
try:
newPath = newPath.encode(core.SYS_ENCODING)
except: pass
newPath = newPath.encode(core.SYS_ENCODING)
except:
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(newPath):
@ -642,19 +663,20 @@ def getDirs(section, subsection, link = 'hard'):
newfile = os.path.join(newPath, sanitizeName(os.path.split(mediafile)[1]))
try:
newfile = newfile.encode(core.SYS_ENCODING)
except: pass
except:
pass
# link file to its new path
copy_link(mediafile, newfile, link)
except Exception as e:
logger.error("Failed to move %s to its own directory: %s" % (os.path.split(mediafile)[1], e))
#removeEmptyFolders(path, removeRoot=False)
# removeEmptyFolders(path, removeRoot=False)
if os.listdir(path):
for dir in [os.path.join(path, o) for o in os.listdir(path) if
os.path.isdir(os.path.join(path, o))]:
sync = [ o for o in os.listdir(dir) if os.path.splitext(o)[1] in ['.!sync','.bts'] ]
os.path.isdir(os.path.join(path, o))]:
sync = [o for o in os.listdir(dir) if os.path.splitext(o)[1] in ['.!sync', '.bts']]
if len(sync) > 0 or len(os.listdir(dir)) == 0:
continue
folders.extend([dir])
@ -667,7 +689,8 @@ def getDirs(section, subsection, link = 'hard'):
elif os.path.exists(core.CFG[section][subsection]["watch_dir"]):
to_return.extend(processDir(core.CFG[section][subsection]["watch_dir"]))
except Exception as e:
logger.error("Failed to add directories from %s for post-processing: %s" % (core.CFG[section][subsection]["watch_dir"], e))
logger.error("Failed to add directories from %s for post-processing: %s" % (
core.CFG[section][subsection]["watch_dir"], e))
if core.USELINK == 'move':
try:
@ -678,10 +701,11 @@ def getDirs(section, subsection, link = 'hard'):
logger.error("Failed to add directories from %s for post-processing: %s" % (core.OUTPUTDIRECTORY, e))
if not to_return:
logger.debug("No directories identified in %s:%s for post-processing" % (section,subsection))
logger.debug("No directories identified in %s:%s for post-processing" % (section, subsection))
return list(set(to_return))
def onerror(func, path, exc_info):
"""
Error handler for ``shutil.rmtree``.
@ -700,6 +724,7 @@ def onerror(func, path, exc_info):
else:
raise
def rmDir(dirName):
logger.info("Deleting %s" % (dirName))
try:
@ -707,6 +732,7 @@ def rmDir(dirName):
except:
logger.error("Unable to delete folder %s" % (dirName))
def cleanDir(path, section, subsection):
if not os.path.exists(path):
logger.info('Directory %s has been processed and removed ...' % (path), 'CLEANDIR')
@ -717,10 +743,12 @@ def cleanDir(path, section, subsection):
return
try:
minSize = int(core.CFG[section][subsection]['minSize'])
except:minSize = 0
except:
minSize = 0
try:
delete_ignored = int(core.CFG[section][subsection]['delete_ignored'])
except:delete_ignored = 0
except:
delete_ignored = 0
try:
num_files = len(listMediaFiles(path, minSize=minSize, delete_ignored=delete_ignored))
except:
@ -737,6 +765,7 @@ def cleanDir(path, section, subsection):
except:
logger.error("Unable to delete directory %s" % (path))
def create_torrent_class(clientAgent):
# Hardlink solution for Torrents
tc = None
@ -753,8 +782,8 @@ def create_torrent_class(clientAgent):
logger.debug("Connecting to %s: http://%s:%s" % (
clientAgent, core.TRANSMISSIONHOST, core.TRANSMISSIONPORT))
tc = TransmissionClient(core.TRANSMISSIONHOST, core.TRANSMISSIONPORT,
core.TRANSMISSIONUSR,
core.TRANSMISSIONPWD)
core.TRANSMISSIONUSR,
core.TRANSMISSIONPWD)
except:
logger.error("Failed to connect to Transmission")
@ -763,12 +792,13 @@ def create_torrent_class(clientAgent):
logger.debug("Connecting to %s: http://%s:%s" % (clientAgent, core.DELUGEHOST, core.DELUGEPORT))
tc = DelugeClient()
tc.connect(host=core.DELUGEHOST, port=core.DELUGEPORT, username=core.DELUGEUSR,
password=core.DELUGEPWD)
password=core.DELUGEPWD)
except:
logger.error("Failed to connect to Deluge")
return tc
def pause_torrent(clientAgent, inputHash, inputID, inputName):
logger.debug("Stopping torrent %s in %s while processing" % (inputName, clientAgent))
try:
@ -782,6 +812,7 @@ def pause_torrent(clientAgent, inputHash, inputID, inputName):
except:
logger.warning("Failed to stop torrent %s in %s" % (inputName, clientAgent))
def resume_torrent(clientAgent, inputHash, inputID, inputName):
if not core.TORRENT_RESUME == 1:
return
@ -797,6 +828,7 @@ def resume_torrent(clientAgent, inputHash, inputID, inputName):
except:
logger.warning("Failed to start torrent %s in %s" % (inputName, clientAgent))
def remove_torrent(clientAgent, inputHash, inputID, inputName):
if core.DELETE_ORIGINAL == 1 or core.USELINK == 'move':
logger.debug("Deleting torrent %s from %s" % (inputName, clientAgent))
@ -811,9 +843,10 @@ def remove_torrent(clientAgent, inputHash, inputID, inputName):
time.sleep(5)
except:
logger.warning("Failed to delete torrent %s in %s" % (inputName, clientAgent))
else:
else:
resume_torrent(clientAgent, inputHash, inputID, inputName)
def find_download(clientAgent, download_id):
logger.debug("Searching for Download on %s ..." % (clientAgent))
if clientAgent == 'utorrent':
@ -851,6 +884,7 @@ def find_download(clientAgent, download_id):
return True
return False
def get_nzoid(inputName):
nzoid = None
slots = []
@ -923,6 +957,7 @@ def is_archive_file(filename):
return regext.split(filename)[0]
return False
def isMediaFile(mediafile, media=True, audio=True, meta=True, archives=True):
fileName, fileExt = os.path.splitext(mediafile)
@ -933,17 +968,18 @@ def isMediaFile(mediafile, media=True, audio=True, meta=True, archives=True):
except:
pass
if (media and fileExt.lower() in core.MEDIACONTAINER)\
or (audio and fileExt.lower() in core.AUDIOCONTAINER)\
or (meta and fileExt.lower() in core.METACONTAINER)\
or (archives and is_archive_file(mediafile)):
if (media and fileExt.lower() in core.MEDIACONTAINER) \
or (audio and fileExt.lower() in core.AUDIOCONTAINER) \
or (meta and fileExt.lower() in core.METACONTAINER) \
or (archives and is_archive_file(mediafile)):
return True
else:
return False
def listMediaFiles(path, minSize=0, delete_ignored=0, media=True, audio=True, meta=True, archives=True):
files = []
if not os.path.isdir(path):
if not os.path.isdir(path):
if os.path.isfile(path): # Single file downloads.
curFile = os.path.split(path)[1]
if isMediaFile(curFile, media, audio, meta, archives):
@ -953,7 +989,8 @@ def listMediaFiles(path, minSize=0, delete_ignored=0, media=True, audio=True, me
try:
os.unlink(path)
logger.debug('Ignored file %s has been removed ...' % (curFile))
except:pass
except:
pass
else:
files.append(path)
@ -973,12 +1010,14 @@ def listMediaFiles(path, minSize=0, delete_ignored=0, media=True, audio=True, me
try:
os.unlink(fullCurFile)
logger.debug('Ignored file %s has been removed ...' % (curFile))
except:pass
except:
pass
continue
files.append(fullCurFile)
return sorted(files,key=len)
return sorted(files, key=len)
def find_imdbid(dirName, inputName):
imdbid = None
@ -987,7 +1026,7 @@ def find_imdbid(dirName, inputName):
# find imdbid in dirName
logger.info('Searching folder and file names for imdbID ...')
m = re.search('(tt\d{7})', dirName+inputName)
m = re.search('(tt\d{7})', dirName + inputName)
if m:
imdbid = m.group(1)
logger.info("Found imdbID [%s]" % imdbid)
@ -1000,14 +1039,14 @@ def find_imdbid(dirName, inputName):
logger.info("Found imdbID [%s] via file name" % imdbid)
return imdbid
if os.environ.has_key('NZBPR__DNZB_MOREINFO'):
dnzb_more_info=os.environ.get('NZBPR__DNZB_MOREINFO', '')
dnzb_more_info = os.environ.get('NZBPR__DNZB_MOREINFO', '')
if dnzb_more_info != '':
regex = re.compile(r'^http://www.imdb.com/title/(tt[0-9]+)/$', re.IGNORECASE)
m = regex.match(dnzb_more_info)
if m:
imdbid = m.group(1)
logger.info("Found imdbID [%s] from DNZB-MoreInfo" % imdbid)
return imdbid
return imdbid
logger.info('Searching IMDB for imdbID ...')
guess = guessit.guess_movie_info(inputName)
if guess:
@ -1045,7 +1084,8 @@ def find_imdbid(dirName, inputName):
logger.warning('Unable to find a imdbID for %s' % (inputName))
return imdbid
def extractFiles(src, dst=None, keep_archive = None):
def extractFiles(src, dst=None, keep_archive=None):
extracted_folder = []
extracted_archive = []
@ -1081,13 +1121,14 @@ def extractFiles(src, dst=None, keep_archive = None):
except Exception as e:
logger.error("Unable to remove file %s due to: %s" % (inputFile, e))
def import_subs(filename):
if not core.GETSUBS:
return
try:
subliminal.cache_region.configure('dogpile.cache.memory')
except:
pass
pass
languages = set()
for item in core.SLANGUAGES:
@ -1098,13 +1139,14 @@ def import_subs(filename):
if not languages:
return
logger.debug("Attempting to download subtitles for %s" %(filename), 'SUBTITLES')
logger.debug("Attempting to download subtitles for %s" % (filename), 'SUBTITLES')
try:
video = subliminal.scan_video(filename, subtitles=True, embedded_subtitles=True)
subtitles = subliminal.download_best_subtitles([video], languages, hearing_impaired=False)
subliminal.save_subtitles(subtitles)
except Exception as e:
logger.error("Failed to download subtitles for %s due to: %s" %(filename, e), 'SUBTITLES')
logger.error("Failed to download subtitles for %s due to: %s" % (filename, e), 'SUBTITLES')
def server_responding(baseURL):
try:
@ -1113,6 +1155,7 @@ def server_responding(baseURL):
except (requests.ConnectionError, requests.exceptions.Timeout):
return False
def plex_update(category):
if core.FAILED:
return
@ -1124,7 +1167,7 @@ def plex_update(category):
section = None
if not core.PLEXSEC:
return
logger.debug("Attempting to update Plex Library for category %s." %(category), 'PLEX')
logger.debug("Attempting to update Plex Library for category %s." % (category), 'PLEX')
for item in core.PLEXSEC:
if item[0] == category:
section = item[1]
@ -1136,6 +1179,7 @@ def plex_update(category):
else:
logger.debug("Could not identify section for plex update", 'PLEX')
def backupVersionedFile(old_file, version):
numTries = 0
@ -1152,7 +1196,8 @@ def backupVersionedFile(old_file, version):
logger.log(u"Backup done", logger.DEBUG)
break
except Exception, e:
logger.log(u"Error while trying to back up " + old_file + " to " + new_file + " : " + str(e), logger.WARNING)
logger.log(u"Error while trying to back up " + old_file + " to " + new_file + " : " + str(e),
logger.WARNING)
numTries += 1
time.sleep(1)
logger.log(u"Trying again.", logger.DEBUG)
@ -1181,6 +1226,7 @@ def get_downloadInfo(inputName, status):
return sqlResults
class RunningProcess():
""" Limits application to single instance """
@ -1193,13 +1239,13 @@ class RunningProcess():
def alreadyrunning(self):
return self.process.alreadyrunning()
#def __del__(self):
# self.process.__del__()
# def __del__(self):
# self.process.__del__()
class WindowsProcess():
def __init__(self):
self.mutexname = "nzbtomedia_" + core.PID_FILE.replace('\\','/') # {D0E858DF-985E-4907-B7FB-8D732C3FC3B9}"
self.mutexname = "nzbtomedia_" + core.PID_FILE.replace('\\', '/') # {D0E858DF-985E-4907-B7FB-8D732C3FC3B9}"
if platform.system() == 'Windows':
from win32event import CreateMutex
from win32api import CloseHandle, GetLastError
@ -1208,7 +1254,7 @@ class WindowsProcess():
self.CloseHandle = CloseHandle
self.GetLastError = GetLastError
self.ERROR_ALREADY_EXISTS = ERROR_ALREADY_EXISTS
def alreadyrunning(self):
self.mutex = self.CreateMutex(None, 0, self.mutexname)
self.lasterror = self.GetLastError()
@ -1217,14 +1263,13 @@ class WindowsProcess():
return True
else:
return False
def __del__(self):
if self.mutex:
self.CloseHandle(self.mutex)
class PosixProcess():
class PosixProcess():
def __init__(self):
self.pidpath = core.PID_FILE
self.lock_socket = None
@ -1239,7 +1284,8 @@ class PosixProcess():
if "Address already in use" in e:
self.lasterror = True
return self.lasterror
except AttributeError: pass
except AttributeError:
pass
if os.path.exists(self.pidpath):
# Make sure it is not a "stale" pidFile
try:
@ -1256,7 +1302,7 @@ class PosixProcess():
else:
self.lasterror = False
else:
self.lasterror = False
self.lasterror = False
if not self.lasterror:
# Write my pid into pidFile to keep multiple copies of program from running
@ -1264,7 +1310,8 @@ class PosixProcess():
fp = open(self.pidpath, 'w')
fp.write(str(os.getpid()))
fp.close()
except: pass
except:
pass
return self.lasterror

View file

@ -8,10 +8,8 @@ from exceptions import DelugeRPCError
from protocol import DelugeRPCRequest, DelugeRPCResponse
from transfer import DelugeTransfer
__all__ = ["DelugeClient"]
RPC_RESPONSE = 1
RPC_ERROR = 2
RPC_EVENT = 3
@ -31,7 +29,8 @@ class DelugeClient(object):
appDataPath = os.environ.get("APPDATA")
if not appDataPath:
import _winreg
hkey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders")
hkey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders")
appDataReg = _winreg.QueryValueEx(hkey, "AppData")
appDataPath = appDataReg[0]
_winreg.CloseKey(hkey)
@ -44,7 +43,6 @@ class DelugeClient(object):
except OSError, e:
return username, password
if os.path.exists(auth_file):
for line in open(auth_file):
if line.startswith("#"):
@ -108,20 +106,20 @@ class DelugeClient(object):
message_type = message[0]
# if message_type == RPC_EVENT:
# event = message[1]
# values = message[2]
#
# if event in self._event_handlers:
# for handler in self._event_handlers[event]:
# gevent.spawn(handler, *values)
#
# elif message_type in (RPC_RESPONSE, RPC_ERROR):
# if message_type == RPC_EVENT:
# event = message[1]
# values = message[2]
#
# if event in self._event_handlers:
# for handler in self._event_handlers[event]:
# gevent.spawn(handler, *values)
#
# elif message_type in (RPC_RESPONSE, RPC_ERROR):
if message_type in (RPC_RESPONSE, RPC_ERROR):
request_id = message[1]
value = message[2]
if request_id == self._request_counter :
if request_id == self._request_counter:
if message_type == RPC_RESPONSE:
response.set(value)
elif message_type == RPC_ERROR:
@ -160,4 +158,3 @@ class DelugeClient(object):
def disconnect(self):
"""Disconnects from the daemon."""
self.transfer.disconnect()

View file

@ -1,6 +1,7 @@
# coding=utf-8
__all__ = ["DelugeRPCError"]
class DelugeRPCError(Exception):
def __init__(self, name, msg, traceback):
self.name = name
@ -9,4 +10,3 @@ class DelugeRPCError(Exception):
def __str__(self):
return "{0}: {1}: {2}".format(self.__class__.__name__, self.name, self.msg)

View file

@ -1,6 +1,7 @@
# coding=utf-8
__all__ = ["DelugeRPCRequest", "DelugeRPCResponse"]
class DelugeRPCRequest(object):
def __init__(self, request_id, method, *args, **kwargs):
self.request_id = request_id
@ -11,6 +12,7 @@ class DelugeRPCRequest(object):
def format(self):
return (self.request_id, self.method, self.args, self.kwargs)
class DelugeRPCResponse(object):
def __init__(self):
self.value = None
@ -36,4 +38,3 @@ class DelugeRPCResponse(object):
return self.value
else:
raise self._exception

View file

@ -9,9 +9,9 @@ BitTorrent project. For complex, heterogeneous data structures with
many small elements, r-encodings take up significantly less space than
b-encodings:
>>> len(rencode.dumps({'a':0, 'b':[1,2], 'c':99}))
>>> len(rencode.dumps({'a': 0, 'b': [1, 2], 'c': 99}))
13
>>> len(bencode.bencode({'a':0, 'b':[1,2], 'c':99}))
>>> len(bencode.bencode({'a': 0, 'b': [1, 2], 'c': 99}))
26
The rencode format is not standardized, and may change with different
@ -73,19 +73,19 @@ MAX_INT_LENGTH = 64
# The bencode 'typecodes' such as i, d, etc have been extended and
# relocated on the base-256 character set.
CHR_LIST = chr(59)
CHR_DICT = chr(60)
CHR_INT = chr(61)
CHR_INT1 = chr(62)
CHR_INT2 = chr(63)
CHR_INT4 = chr(64)
CHR_INT8 = chr(65)
CHR_LIST = chr(59)
CHR_DICT = chr(60)
CHR_INT = chr(61)
CHR_INT1 = chr(62)
CHR_INT2 = chr(63)
CHR_INT4 = chr(64)
CHR_INT8 = chr(65)
CHR_FLOAT32 = chr(66)
CHR_FLOAT64 = chr(44)
CHR_TRUE = chr(67)
CHR_FALSE = chr(68)
CHR_NONE = chr(69)
CHR_TERM = chr(127)
CHR_TRUE = chr(67)
CHR_FALSE = chr(68)
CHR_NONE = chr(69)
CHR_TERM = chr(127)
# Positive integers with value embedded in typecode.
INT_POS_FIXED_START = 0
@ -104,9 +104,10 @@ STR_FIXED_START = 128
STR_FIXED_COUNT = 64
# Lists with length embedded in typecode.
LIST_FIXED_START = STR_FIXED_START+STR_FIXED_COUNT
LIST_FIXED_START = STR_FIXED_START + STR_FIXED_COUNT
LIST_FIXED_COUNT = 64
def decode_int(x, f):
f += 1
newf = x.index(CHR_TERM, f)
@ -119,35 +120,42 @@ def decode_int(x, f):
if x[f] == '-':
if x[f + 1] == '0':
raise ValueError
elif x[f] == '0' and newf != f+1:
elif x[f] == '0' and newf != f + 1:
raise ValueError
return (n, newf+1)
return (n, newf + 1)
def decode_intb(x, f):
f += 1
return (struct.unpack('!b', x[f:f+1])[0], f+1)
return (struct.unpack('!b', x[f:f + 1])[0], f + 1)
def decode_inth(x, f):
f += 1
return (struct.unpack('!h', x[f:f+2])[0], f+2)
return (struct.unpack('!h', x[f:f + 2])[0], f + 2)
def decode_intl(x, f):
f += 1
return (struct.unpack('!l', x[f:f+4])[0], f+4)
return (struct.unpack('!l', x[f:f + 4])[0], f + 4)
def decode_intq(x, f):
f += 1
return (struct.unpack('!q', x[f:f+8])[0], f+8)
return (struct.unpack('!q', x[f:f + 8])[0], f + 8)
def decode_float32(x, f):
f += 1
n = struct.unpack('!f', x[f:f+4])[0]
return (n, f+4)
n = struct.unpack('!f', x[f:f + 4])[0]
return (n, f + 4)
def decode_float64(x, f):
f += 1
n = struct.unpack('!d', x[f:f+8])[0]
return (n, f+8)
n = struct.unpack('!d', x[f:f + 8])[0]
return (n, f + 8)
def decode_string(x, f):
colon = x.index(':', f)
@ -155,40 +163,46 @@ def decode_string(x, f):
n = int(x[f:colon])
except (OverflowError, ValueError):
n = long(x[f:colon])
if x[f] == '0' and colon != f+1:
if x[f] == '0' and colon != f + 1:
raise ValueError
colon += 1
s = x[colon:colon+n]
s = x[colon:colon + n]
try:
t = s.decode("utf8")
if len(t) != len(s):
s = t
except UnicodeDecodeError:
pass
return (s, colon+n)
return (s, colon + n)
def decode_list(x, f):
r, f = [], f+1
r, f = [], f + 1
while x[f] != CHR_TERM:
v, f = decode_func[x[f]](x, f)
r.append(v)
return (tuple(r), f + 1)
def decode_dict(x, f):
r, f = {}, f+1
r, f = {}, f + 1
while x[f] != CHR_TERM:
k, f = decode_func[x[f]](x, f)
r[k], f = decode_func[x[f]](x, f)
return (r, f + 1)
def decode_true(x, f):
return (True, f+1)
return (True, f + 1)
def decode_false(x, f):
return (False, f+1)
return (False, f + 1)
def decode_none(x, f):
return (None, f+1)
return (None, f + 1)
decode_func = {}
decode_func['0'] = decode_string
@ -201,77 +215,94 @@ decode_func['6'] = decode_string
decode_func['7'] = decode_string
decode_func['8'] = decode_string
decode_func['9'] = decode_string
decode_func[CHR_LIST ] = decode_list
decode_func[CHR_DICT ] = decode_dict
decode_func[CHR_INT ] = decode_int
decode_func[CHR_INT1 ] = decode_intb
decode_func[CHR_INT2 ] = decode_inth
decode_func[CHR_INT4 ] = decode_intl
decode_func[CHR_INT8 ] = decode_intq
decode_func[CHR_LIST] = decode_list
decode_func[CHR_DICT] = decode_dict
decode_func[CHR_INT] = decode_int
decode_func[CHR_INT1] = decode_intb
decode_func[CHR_INT2] = decode_inth
decode_func[CHR_INT4] = decode_intl
decode_func[CHR_INT8] = decode_intq
decode_func[CHR_FLOAT32] = decode_float32
decode_func[CHR_FLOAT64] = decode_float64
decode_func[CHR_TRUE ] = decode_true
decode_func[CHR_FALSE ] = decode_false
decode_func[CHR_NONE ] = decode_none
decode_func[CHR_TRUE] = decode_true
decode_func[CHR_FALSE] = decode_false
decode_func[CHR_NONE] = decode_none
def make_fixed_length_string_decoders():
def make_decoder(slen):
def f(x, f):
s = x[f+1:f+1+slen]
s = x[f + 1:f + 1 + slen]
try:
t = s.decode("utf8")
if len(t) != len(s):
s = t
except UnicodeDecodeError:
pass
return (s, f+1+slen)
return (s, f + 1 + slen)
return f
for i in range(STR_FIXED_COUNT):
decode_func[chr(STR_FIXED_START+i)] = make_decoder(i)
decode_func[chr(STR_FIXED_START + i)] = make_decoder(i)
make_fixed_length_string_decoders()
def make_fixed_length_list_decoders():
def make_decoder(slen):
def f(x, f):
r, f = [], f+1
r, f = [], f + 1
for i in range(slen):
v, f = decode_func[x[f]](x, f)
r.append(v)
return (tuple(r), f)
return f
for i in range(LIST_FIXED_COUNT):
decode_func[chr(LIST_FIXED_START+i)] = make_decoder(i)
decode_func[chr(LIST_FIXED_START + i)] = make_decoder(i)
make_fixed_length_list_decoders()
def make_fixed_length_int_decoders():
def make_decoder(j):
def f(x, f):
return (j, f+1)
return (j, f + 1)
return f
for i in range(INT_POS_FIXED_COUNT):
decode_func[chr(INT_POS_FIXED_START+i)] = make_decoder(i)
decode_func[chr(INT_POS_FIXED_START + i)] = make_decoder(i)
for i in range(INT_NEG_FIXED_COUNT):
decode_func[chr(INT_NEG_FIXED_START+i)] = make_decoder(-1-i)
decode_func[chr(INT_NEG_FIXED_START + i)] = make_decoder(-1 - i)
make_fixed_length_int_decoders()
def make_fixed_length_dict_decoders():
def make_decoder(slen):
def f(x, f):
r, f = {}, f+1
r, f = {}, f + 1
for j in range(slen):
k, f = decode_func[x[f]](x, f)
r[k], f = decode_func[x[f]](x, f)
return (r, f)
return f
for i in range(DICT_FIXED_COUNT):
decode_func[chr(DICT_FIXED_START+i)] = make_decoder(i)
decode_func[chr(DICT_FIXED_START + i)] = make_decoder(i)
make_fixed_length_dict_decoders()
def encode_dict(x,r):
def encode_dict(x, r):
r.append(CHR_DICT)
for k, v in x.items():
encode_func[type(k)](k, r)
@ -288,13 +319,15 @@ def loads(x):
raise ValueError
return r
from types import StringType, IntType, LongType, DictType, ListType, TupleType, FloatType, NoneType, UnicodeType
def encode_int(x, r):
if 0 <= x < INT_POS_FIXED_COUNT:
r.append(chr(INT_POS_FIXED_START+x))
r.append(chr(INT_POS_FIXED_START + x))
elif -INT_NEG_FIXED_COUNT <= x < 0:
r.append(chr(INT_NEG_FIXED_START-1-x))
r.append(chr(INT_NEG_FIXED_START - 1 - x))
elif -128 <= x < 128:
r.extend((CHR_INT1, struct.pack('!b', x)))
elif -32768 <= x < 32768:
@ -309,27 +342,34 @@ def encode_int(x, r):
raise ValueError('overflow')
r.extend((CHR_INT, s, CHR_TERM))
def encode_float32(x, r):
r.extend((CHR_FLOAT32, struct.pack('!f', x)))
def encode_float64(x, r):
r.extend((CHR_FLOAT64, struct.pack('!d', x)))
def encode_bool(x, r):
r.extend({False: CHR_FALSE, True: CHR_TRUE}[bool(x)])
def encode_none(x, r):
r.extend(CHR_NONE)
def encode_string(x, r):
if len(x) < STR_FIXED_COUNT:
r.extend((chr(STR_FIXED_START + len(x)), x))
else:
r.extend((str(len(x)), ':', x))
def encode_unicode(x, r):
encode_string(x.encode("utf8"), r)
def encode_list(x, r):
if len(x) < LIST_FIXED_COUNT:
r.append(chr(LIST_FIXED_START + len(x)))
@ -341,7 +381,8 @@ def encode_list(x, r):
encode_func[type(i)](i, r)
r.append(CHR_TERM)
def encode_dict(x,r):
def encode_dict(x, r):
if len(x) < DICT_FIXED_COUNT:
r.append(chr(DICT_FIXED_START + len(x)))
for k, v in x.items():
@ -354,6 +395,7 @@ def encode_dict(x,r):
encode_func[type(v)](v, r)
r.append(CHR_TERM)
encode_func = {}
encode_func[IntType] = encode_int
encode_func[LongType] = encode_int
@ -368,10 +410,12 @@ lock = Lock()
try:
from types import BooleanType
encode_func[BooleanType] = encode_bool
except ImportError:
pass
def dumps(x, float_bits=DEFAULT_FLOAT_BITS):
"""
Dump data structure to str.
@ -392,41 +436,46 @@ def dumps(x, float_bits=DEFAULT_FLOAT_BITS):
lock.release()
return ''.join(r)
def test():
f1 = struct.unpack('!f', struct.pack('!f', 25.5))[0]
f2 = struct.unpack('!f', struct.pack('!f', 29.3))[0]
f3 = struct.unpack('!f', struct.pack('!f', -0.6))[0]
L = (({'a':15, 'bb':f1, 'ccc':f2, '':(f3,(),False,True,'')},('a',10**20),tuple(range(-100000,100000)),'b'*31,'b'*62,'b'*64,2**30,2**33,2**62,2**64,2**30,2**33,2**62,2**64,False,False, True, -1, 2, 0),)
L = (({'a': 15, 'bb': f1, 'ccc': f2, '': (f3, (), False, True, '')}, ('a', 10 ** 20), tuple(range(-100000, 100000)),
'b' * 31, 'b' * 62, 'b' * 64, 2 ** 30, 2 ** 33, 2 ** 62, 2 ** 64, 2 ** 30, 2 ** 33, 2 ** 62, 2 ** 64, False,
False, True, -1, 2, 0),)
assert loads(dumps(L)) == L
d = dict(zip(range(-100000,100000),range(-100000,100000)))
d.update({'a':20, 20:40, 40:41, f1:f2, f2:f3, f3:False, False:True, True:False})
L = (d, {}, {5:6}, {7:7,True:8}, {9:10, 22:39, 49:50, 44: ''})
d = dict(zip(range(-100000, 100000), range(-100000, 100000)))
d.update({'a': 20, 20: 40, 40: 41, f1: f2, f2: f3, f3: False, False: True, True: False})
L = (d, {}, {5: 6}, {7: 7, True: 8}, {9: 10, 22: 39, 49: 50, 44: ''})
assert loads(dumps(L)) == L
L = ('', 'a'*10, 'a'*100, 'a'*1000, 'a'*10000, 'a'*100000, 'a'*1000000, 'a'*10000000)
L = ('', 'a' * 10, 'a' * 100, 'a' * 1000, 'a' * 10000, 'a' * 100000, 'a' * 1000000, 'a' * 10000000)
assert loads(dumps(L)) == L
L = tuple([dict(zip(range(n),range(n))) for n in range(100)]) + ('b',)
L = tuple([dict(zip(range(n), range(n))) for n in range(100)]) + ('b',)
assert loads(dumps(L)) == L
L = tuple([dict(zip(range(n),range(-n,0))) for n in range(100)]) + ('b',)
L = tuple([dict(zip(range(n), range(-n, 0))) for n in range(100)]) + ('b',)
assert loads(dumps(L)) == L
L = tuple([tuple(range(n)) for n in range(100)]) + ('b',)
assert loads(dumps(L)) == L
L = tuple(['a'*n for n in range(1000)]) + ('b',)
L = tuple(['a' * n for n in range(1000)]) + ('b',)
assert loads(dumps(L)) == L
L = tuple(['a'*n for n in range(1000)]) + (None,True,None)
L = tuple(['a' * n for n in range(1000)]) + (None, True, None)
assert loads(dumps(L)) == L
assert loads(dumps(None)) == None
assert loads(dumps({None:None})) == {None:None}
assert 1e-10<abs(loads(dumps(1.1))-1.1)<1e-6
assert 1e-10<abs(loads(dumps(1.1,32))-1.1)<1e-6
assert abs(loads(dumps(1.1,64))-1.1)<1e-12
assert loads(dumps({None: None})) == {None: None}
assert 1e-10 < abs(loads(dumps(1.1)) - 1.1) < 1e-6
assert 1e-10 < abs(loads(dumps(1.1, 32)) - 1.1) < 1e-6
assert abs(loads(dumps(1.1, 64)) - 1.1) < 1e-12
assert loads(dumps(u"Hello World!!"))
try:
import psyco
psyco.bind(dumps)
psyco.bind(loads)
except ImportError:
pass
if __name__ == '__main__':
test()
test()

View file

@ -6,9 +6,9 @@ import ssl
from core.synchronousdeluge import rencode
__all__ = ["DelugeTransfer"]
class DelugeTransfer(object):
def __init__(self):
self.sock = None
@ -54,5 +54,3 @@ class DelugeTransfer(object):
buf = dobj.unused_data
yield message

View file

@ -12,6 +12,7 @@ import re
from core import logger
from core.nzbToMediaUtil import makeDir
def isVideoGood(videofile, status):
fileNameExt = os.path.basename(videofile)
fileName, fileExt = os.path.splitext(fileNameExt)
@ -20,7 +21,7 @@ def isVideoGood(videofile, status):
disable = True
else:
test_details, res = getVideoDetails(core.TEST_FILE)
if res !=0 or test_details.get("error"):
if res != 0 or test_details.get("error"):
disable = True
logger.info("DISABLED: ffprobe failed to analyse test file. Stopping corruption check.", 'TRANSCODER')
if test_details.get("streams"):
@ -28,7 +29,8 @@ def isVideoGood(videofile, status):
audStreams = [item for item in test_details["streams"] if item["codec_type"] == "audio"]
if not (len(vidStreams) > 0 and len(audStreams) > 0):
disable = True
logger.info("DISABLED: ffprobe failed to analyse streams from test file. Stopping corruption check.", 'TRANSCODER')
logger.info("DISABLED: ffprobe failed to analyse streams from test file. Stopping corruption check.",
'TRANSCODER')
if disable:
if status: # if the download was "failed", assume bad. If it was successful, assume good.
return False
@ -51,9 +53,11 @@ def isVideoGood(videofile, status):
logger.info("SUCCESS: [%s] has no corruption." % (fileNameExt), 'TRANSCODER')
return True
else:
logger.info("FAILED: [%s] has %s video streams and %s audio streams. Assume corruption." % (fileNameExt, str(len(videoStreams)), str(len(audioStreams))), 'TRANSCODER')
logger.info("FAILED: [%s] has %s video streams and %s audio streams. Assume corruption." % (
fileNameExt, str(len(videoStreams)), str(len(audioStreams))), 'TRANSCODER')
return False
def zip_out(file, img, bitbucket):
procin = None
cmd = [core.SEVENZIP, '-so', 'e', img, file]
@ -63,6 +67,7 @@ def zip_out(file, img, bitbucket):
logger.error("Extracting [%s] has failed" % (file), 'TRANSCODER')
return procin
def getVideoDetails(videofile, img=None, bitbucket=None):
video_details = {}
result = 1
@ -76,7 +81,8 @@ def getVideoDetails(videofile, img=None, bitbucket=None):
try:
if img:
videofile = '-'
command = [core.FFPROBE, '-v', 'quiet', print_format, 'json', '-show_format', '-show_streams', '-show_error', videofile]
command = [core.FFPROBE, '-v', 'quiet', print_format, 'json', '-show_format', '-show_streams', '-show_error',
videofile]
print_cmd(command)
if img:
procin = zip_out(file, img, bitbucket)
@ -87,7 +93,8 @@ def getVideoDetails(videofile, img=None, bitbucket=None):
out, err = proc.communicate()
result = proc.returncode
video_details = json.loads(out)
except: pass
except:
pass
if not video_details:
try:
command = [core.FFPROBE, '-v', 'quiet', print_format, 'json', '-show_format', '-show_streams', videofile]
@ -104,6 +111,7 @@ def getVideoDetails(videofile, img=None, bitbucket=None):
logger.error("Checking [%s] has failed" % (file), 'TRANSCODER')
return video_details, result
def buildCommands(file, newDir, movieName, bitbucket):
if isinstance(file, str):
inputFile = file
@ -119,8 +127,8 @@ def buildCommands(file, newDir, movieName, bitbucket):
name = ('%s.cd%s' % (movieName, check.groups()[0]))
elif core.CONCAT and re.match("(.+)[cC][dD][0-9]", name):
name = re.sub("([\ \.\-\_\=\:]+[cC][dD][0-9])", "", name)
if ext == core.VEXTENSION and newDir == dir: # we need to change the name to prevent overwriting itself.
core.VEXTENSION = '-transcoded' + core.VEXTENSION # adds '-transcoded.ext'
if ext == core.VEXTENSION and newDir == dir: # we need to change the name to prevent overwriting itself.
core.VEXTENSION = '-transcoded' + core.VEXTENSION # adds '-transcoded.ext'
else:
img, data = file.iteritems().next()
name = data['name']
@ -139,7 +147,8 @@ def buildCommands(file, newDir, movieName, bitbucket):
meta_cmd = []
other_cmd = []
if not video_details or not video_details.get("streams"): # we couldn't read streams with ffprobe. Set defaults to try transcoding.
if not video_details or not video_details.get(
"streams"): # we couldn't read streams with ffprobe. Set defaults to try transcoding.
videoStreams = []
audioStreams = []
subStreams = []
@ -166,12 +175,13 @@ def buildCommands(file, newDir, movieName, bitbucket):
if core.ACODEC:
audio_cmd.extend(['-c:a', core.ACODEC])
if core.ACODEC in ['aac', 'dts']: # Allow users to use the experimental AAC codec that's built into recent versions of ffmpeg
if core.ACODEC in ['aac',
'dts']: # Allow users to use the experimental AAC codec that's built into recent versions of ffmpeg
audio_cmd.extend(['-strict', '-2'])
else:
audio_cmd.extend(['-c:a', 'copy'])
if core.ACHANNELS:
audio_cmd.extend(['-ac', str(core.ACHANNELS)])
audio_cmd.extend(['-ac', str(core.ACHANNELS)])
if core.ABITRATE:
audio_cmd.extend(['-b:a', str(core.ABITRATE)])
if core.OUTPUTQUALITYPERCENT:
@ -183,7 +193,7 @@ def buildCommands(file, newDir, movieName, bitbucket):
sub_cmd.extend(['-c:s', 'copy'])
else: # http://en.wikibooks.org/wiki/FFMPEG_An_Intermediate_Guide/subtitle_options
sub_cmd.extend(['-sn']) # Don't copy the subtitles over
if core.OUTPUTFASTSTART:
other_cmd.extend(['-movflags', '+faststart'])
@ -192,23 +202,29 @@ def buildCommands(file, newDir, movieName, bitbucket):
audioStreams = [item for item in video_details["streams"] if item["codec_type"] == "audio"]
subStreams = [item for item in video_details["streams"] if item["codec_type"] == "subtitle"]
if core.VEXTENSION not in ['.mkv', '.mpegts']:
subStreams = [item for item in video_details["streams"] if item["codec_type"] == "subtitle" and item["codec_name"] != "hdmv_pgs_subtitle" and item["codec_name"] != "pgssub"]
subStreams = [item for item in video_details["streams"] if
item["codec_type"] == "subtitle" and item["codec_name"] != "hdmv_pgs_subtitle" and item[
"codec_name"] != "pgssub"]
for video in videoStreams:
codec = video["codec_name"]
try:
fr = video["avg_frame_rate"]
except: fr = 0
except:
fr = 0
try:
width = video["width"]
except: width = 0
except:
width = 0
try:
height = video["height"]
except: height = 0
except:
height = 0
scale = core.VRESOLUTION
try:
framerate = float(fr.split('/')[0])/float(fr.split('/')[1])
except: framerate = 0
framerate = float(fr.split('/')[0]) / float(fr.split('/')[1])
except:
framerate = 0
if codec in core.VCODEC_ALLOW or not core.VCODEC:
video_cmd.extend(['-c:v', 'copy'])
else:
@ -216,16 +232,16 @@ def buildCommands(file, newDir, movieName, bitbucket):
if core.VFRAMERATE and not (core.VFRAMERATE * 0.999 <= fr <= core.VFRAMERATE * 1.001):
video_cmd.extend(['-r', str(core.VFRAMERATE)])
if scale:
w_scale = width/float(scale.split(':')[0])
h_scale = height/float(scale.split(':')[1])
if w_scale > h_scale: # widescreen, Scale by width only.
scale = scale.split(':')[0] + ":" + str(int((height/w_scale)/2)*2)
if w_scale > 1:
video_cmd.extend(['-vf', 'scale=' + scale])
w_scale = width / float(scale.split(':')[0])
h_scale = height / float(scale.split(':')[1])
if w_scale > h_scale: # widescreen, Scale by width only.
scale = scale.split(':')[0] + ":" + str(int((height / w_scale) / 2) * 2)
if w_scale > 1:
video_cmd.extend(['-vf', 'scale=' + scale])
else: # lower or mathcing ratio, scale by height only.
scale = str(int((width/h_scale)/2)*2) + ":" + scale.split(':')[1]
if h_scale > 1:
video_cmd.extend(['-vf', 'scale=' + scale])
scale = str(int((width / h_scale) / 2) * 2) + ":" + scale.split(':')[1]
if h_scale > 1:
video_cmd.extend(['-vf', 'scale=' + scale])
if core.VBITRATE:
video_cmd.extend(['-b:v', str(core.VBITRATE)])
if core.VPRESET:
@ -238,7 +254,7 @@ def buildCommands(file, newDir, movieName, bitbucket):
if video_cmd[1] == 'copy' and any(i in video_cmd for i in no_copy):
video_cmd[1] = core.VCODEC
if core.VCODEC == 'copy': # force copy. therefore ignore all other video transcoding.
video_cmd = ['-c:v', 'copy']
video_cmd = ['-c:v', 'copy']
map_cmd.extend(['-map', '0:' + str(video["index"])])
break # Only one video needed
@ -246,12 +262,12 @@ def buildCommands(file, newDir, movieName, bitbucket):
a_mapped = []
if audioStreams:
try:
audio1 = [ item for item in audioStreams if item["tags"]["language"] == core.ALANGUAGE ]
audio1 = [item for item in audioStreams if item["tags"]["language"] == core.ALANGUAGE]
except: # no language tags. Assume only 1 language.
audio1 = audioStreams
audio2 = [ item for item in audio1 if item["codec_name"] in core.ACODEC_ALLOW ]
audio2 = [item for item in audio1 if item["codec_name"] in core.ACODEC_ALLOW]
try:
audio3 = [ item for item in audioStreams if item["tags"]["language"] != core.ALANGUAGE ]
audio3 = [item for item in audioStreams if item["tags"]["language"] != core.ALANGUAGE]
except:
audio3 = []
@ -259,21 +275,25 @@ def buildCommands(file, newDir, movieName, bitbucket):
map_cmd.extend(['-map', '0:' + str(audio2[0]["index"])])
a_mapped.extend([audio2[0]["index"]])
try:
bitrate = int(audio2[0]["bit_rate"])/1000
except: bitrate = 0
bitrate = int(audio2[0]["bit_rate"]) / 1000
except:
bitrate = 0
try:
channels = int(audio2[0]["channels"])
except: channels = 0
except:
channels = 0
audio_cmd.extend(['-c:a:' + str(used_audio), 'copy'])
elif audio1: # right language wrong codec.
map_cmd.extend(['-map', '0:' + str(audio1[0]["index"])])
a_mapped.extend([audio1[0]["index"]])
try:
bitrate = int(audio1[0]["bit_rate"])/1000
except: bitrate = 0
bitrate = int(audio1[0]["bit_rate"]) / 1000
except:
bitrate = 0
try:
channels = int(audio1[0]["channels"])
except: channels = 0
except:
channels = 0
if core.ACODEC:
audio_cmd.extend(['-c:a:' + str(used_audio), core.ACODEC])
else:
@ -282,11 +302,13 @@ def buildCommands(file, newDir, movieName, bitbucket):
map_cmd.extend(['-map', '0:' + str(audio3[0]["index"])])
a_mapped.extend([audio3[0]["index"]])
try:
bitrate = int(audio3[0]["bit_rate"])/1000
except: bitrate = 0
bitrate = int(audio3[0]["bit_rate"]) / 1000
except:
bitrate = 0
try:
channels = int(audio3[0]["channels"])
except: channels = 0
except:
channels = 0
if core.ACODEC:
audio_cmd.extend(['-c:a:' + str(used_audio), core.ACODEC])
else:
@ -309,26 +331,30 @@ def buildCommands(file, newDir, movieName, bitbucket):
if core.ACODEC2_ALLOW:
used_audio += 1
audio4 = [ item for item in audio1 if item["codec_name"] in core.ACODEC2_ALLOW ]
audio4 = [item for item in audio1 if item["codec_name"] in core.ACODEC2_ALLOW]
if audio4: # right language and codec.
map_cmd.extend(['-map', '0:' + str(audio4[0]["index"])])
a_mapped.extend([audio4[0]["index"]])
try:
bitrate = int(audio4[0]["bit_rate"])/1000
except: bitrate = 0
bitrate = int(audio4[0]["bit_rate"]) / 1000
except:
bitrate = 0
try:
channels = int(audio4[0]["channels"])
except: channels = 0
except:
channels = 0
audio_cmd2.extend(['-c:a:' + str(used_audio), 'copy'])
elif audio1: # right language wrong codec.
map_cmd.extend(['-map', '0:' + str(audio1[0]["index"])])
a_mapped.extend([audio1[0]["index"]])
try:
bitrate = int(audio1[0]["bit_rate"])/1000
except: bitrate = 0
bitrate = int(audio1[0]["bit_rate"]) / 1000
except:
bitrate = 0
try:
channels = int(audio1[0]["channels"])
except: channels = 0
except:
channels = 0
if core.ACODEC2:
audio_cmd2.extend(['-c:a:' + str(used_audio), core.ACODEC2])
else:
@ -337,11 +363,13 @@ def buildCommands(file, newDir, movieName, bitbucket):
map_cmd.extend(['-map', '0:' + str(audio3[0]["index"])])
a_mapped.extend([audio3[0]["index"]])
try:
bitrate = int(audio3[0]["bit_rate"])/1000
except: bitrate = 0
bitrate = int(audio3[0]["bit_rate"]) / 1000
except:
bitrate = 0
try:
channels = int(audio3[0]["channels"])
except: channels = 0
except:
channels = 0
if core.ACODEC2:
audio_cmd2.extend(['-c:a:' + str(used_audio), core.ACODEC2])
else:
@ -371,11 +399,13 @@ def buildCommands(file, newDir, movieName, bitbucket):
map_cmd.extend(['-map', '0:' + str(audio["index"])])
audio_cmd3 = []
try:
bitrate = int(audio["bit_rate"])/1000
except: bitrate = 0
bitrate = int(audio["bit_rate"]) / 1000
except:
bitrate = 0
try:
channels = int(audio["channels"])
except: channels = 0
except:
channels = 0
if audio["codec_name"] in core.ACODEC3_ALLOW:
audio_cmd3.extend(['-c:a:' + str(used_audio), 'copy'])
else:
@ -406,8 +436,9 @@ def buildCommands(file, newDir, movieName, bitbucket):
n = 0
for lan in core.SLANGUAGES:
try:
subs1 = [ item for item in subStreams if item["tags"]["language"] == lan ]
except: subs1 = []
subs1 = [item for item in subStreams if item["tags"]["language"] == lan]
except:
subs1 = []
if core.BURN and not subs1 and not burnt and os.path.isfile(file):
for subfile in get_subs(file):
if lan in os.path.split(subfile)[1]:
@ -426,7 +457,7 @@ def buildCommands(file, newDir, movieName, bitbucket):
break
map_cmd.extend(['-map', '0:' + str(sub["index"])])
s_mapped.extend([sub["index"]])
if core.SINCLUDE:
for sub in subStreams:
if not core.ALLOWSUBS:
@ -434,7 +465,7 @@ def buildCommands(file, newDir, movieName, bitbucket):
if sub["index"] in s_mapped:
continue
map_cmd.extend(['-map', '0:' + str(sub["index"])])
s_mapped.extend([sub["index"]])
s_mapped.extend([sub["index"]])
if core.OUTPUTFASTSTART:
other_cmd.extend(['-movflags', '+faststart'])
@ -446,7 +477,7 @@ def buildCommands(file, newDir, movieName, bitbucket):
if core.GENERALOPTS:
command.extend(core.GENERALOPTS)
command.extend([ '-i', inputFile])
command.extend(['-i', inputFile])
if core.SEMBED and os.path.isfile(file):
for subfile in get_subs(file):
@ -461,7 +492,7 @@ def buildCommands(file, newDir, movieName, bitbucket):
if not core.ALLOWSUBS or (not s_mapped and not n):
sub_cmd.extend(['-sn'])
else:
else:
if core.SCODEC:
sub_cmd.extend(['-c:s', core.SCODEC])
else:
@ -478,6 +509,7 @@ def buildCommands(file, newDir, movieName, bitbucket):
command = core.NICENESS + command
return command
def get_subs(file):
filepaths = []
subExt = ['.srt', '.sub', '.idx']
@ -486,9 +518,10 @@ def get_subs(file):
for dirname, dirs, filenames in os.walk(dir):
for filename in filenames:
filepaths.extend([os.path.join(dirname, filename)])
subfiles = [ item for item in filepaths if os.path.splitext(item)[1] in subExt and name in item ]
subfiles = [item for item in filepaths if os.path.splitext(item)[1] in subExt and name in item]
return subfiles
def extract_subs(file, newfilePath, bitbucket):
video_details, result = getVideoDetails(file)
if not video_details:
@ -501,34 +534,39 @@ def extract_subs(file, newfilePath, bitbucket):
name = os.path.splitext(os.path.split(newfilePath)[1])[0]
try:
subStreams = [item for item in video_details["streams"] if item["codec_type"] == "subtitle" and item["tags"]["language"] in core.SLANGUAGES and item["codec_name"] != "hdmv_pgs_subtitle" and item["codec_name"] != "pgssub"]
subStreams = [item for item in video_details["streams"] if
item["codec_type"] == "subtitle" and item["tags"]["language"] in core.SLANGUAGES and item[
"codec_name"] != "hdmv_pgs_subtitle" and item["codec_name"] != "pgssub"]
except:
subStreams = [item for item in video_details["streams"] if item["codec_type"] == "subtitle" and item["codec_name"] != "hdmv_pgs_subtitle" and item["codec_name"] != "pgssub"]
subStreams = [item for item in video_details["streams"] if
item["codec_type"] == "subtitle" and item["codec_name"] != "hdmv_pgs_subtitle" and item[
"codec_name"] != "pgssub"]
num = len(subStreams)
for n in range(num):
sub = subStreams[n]
idx = sub["index"]
try:
lan = sub["tags"]["language"]
lan = sub["tags"]["language"]
except:
lan = "unk"
lan = "unk"
if num == 1:
outputFile = os.path.join(subdir, "%s.srt" %(name))
if os.path.isfile(outputFile):
outputFile = os.path.join(subdir, "%s.%s.srt" %(name, n))
outputFile = os.path.join(subdir, "%s.srt" % (name))
if os.path.isfile(outputFile):
outputFile = os.path.join(subdir, "%s.%s.srt" % (name, n))
else:
outputFile = os.path.join(subdir, "%s.%s.srt" %(name, lan))
if os.path.isfile(outputFile):
outputFile = os.path.join(subdir, "%s.%s.%s.srt" %(name, lan, n))
outputFile = os.path.join(subdir, "%s.%s.srt" % (name, lan))
if os.path.isfile(outputFile):
outputFile = os.path.join(subdir, "%s.%s.%s.srt" % (name, lan, n))
command = [core.FFMPEG, '-loglevel', 'warning', '-i', file, '-vn', '-an', '-codec:' + str(idx), 'srt', outputFile]
command = [core.FFMPEG, '-loglevel', 'warning', '-i', file, '-vn', '-an', '-codec:' + str(idx), 'srt',
outputFile]
if platform.system() != 'Windows':
command = core.NICENESS + command
logger.info("Extracting %s subtitle from: %s" % (lan, file))
print_cmd(command)
result = 1 # set result to failed in case call fails.
result = 1 # set result to failed in case call fails.
try:
proc = subprocess.Popen(command, stdout=bitbucket, stderr=bitbucket)
proc.communicate()
@ -539,11 +577,13 @@ def extract_subs(file, newfilePath, bitbucket):
if result == 0:
try:
shutil.copymode(file, outputFile)
except: pass
except:
pass
logger.info("Extracting %s subtitle from %s has succeeded" % (lan, file))
else:
logger.error("Extracting subtitles has failed")
def processList(List, newDir, bitbucket):
remList = []
newList = []
@ -562,7 +602,7 @@ def processList(List, newDir, bitbucket):
logger.debug("Found VIDEO_TS image file: %s" % (item), "TRANSCODER")
if not vtsPath:
try:
vtsPath = re.match("(.+VIDEO_TS)",item).groups()[0]
vtsPath = re.match("(.+VIDEO_TS)", item).groups()[0]
except:
vtsPath = os.path.split(item)[0]
remList.append(item)
@ -571,7 +611,8 @@ def processList(List, newDir, bitbucket):
elif core.CONCAT and re.match(".+[cC][dD][0-9].", item):
remList.append(item)
combine.append(item)
else: continue
else:
continue
if vtsPath:
newList.extend(combineVTS(vtsPath))
if combine:
@ -589,7 +630,8 @@ def processList(List, newDir, bitbucket):
newList = []
remList = []
logger.error("Failed extracting .vob files from disk image. Stopping transcoding.", "TRANSCODER")
return List, remList, newList, success
return List, remList, newList, success
def ripISO(item, newDir, bitbucket):
newFiles = []
@ -606,13 +648,14 @@ def ripISO(item, newDir, bitbucket):
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=bitbucket)
out, err = proc.communicate()
result = proc.returncode
fileList = [ re.match(".+(VIDEO_TS[\\\/]VTS_[0-9][0-9]_[0-9].[Vv][Oo][Bb])", line).groups()[0] for line in out.splitlines() if re.match(".+VIDEO_TS[\\\/]VTS_[0-9][0-9]_[0-9].[Vv][Oo][Bb]", line) ]
fileList = [re.match(".+(VIDEO_TS[\\\/]VTS_[0-9][0-9]_[0-9].[Vv][Oo][Bb])", line).groups()[0] for line in
out.splitlines() if re.match(".+VIDEO_TS[\\\/]VTS_[0-9][0-9]_[0-9].[Vv][Oo][Bb]", line)]
combined = []
for n in range(99):
concat = []
m = 1
while True:
vtsName = 'VIDEO_TS%sVTS_%02d_%d.VOB' % (os.sep, n+1, m)
vtsName = 'VIDEO_TS%sVTS_%02d_%d.VOB' % (os.sep, n + 1, m)
if vtsName in fileList:
concat.append(vtsName)
m += 1
@ -623,11 +666,11 @@ def ripISO(item, newDir, bitbucket):
if core.CONCAT:
combined.extend(concat)
continue
name = '%s.cd%s' % (os.path.splitext(os.path.split(item)[1])[0] ,str(n+1))
newFiles.append({item: {'name': name , 'files': concat}})
name = '%s.cd%s' % (os.path.splitext(os.path.split(item)[1])[0], str(n + 1))
newFiles.append({item: {'name': name, 'files': concat}})
if core.CONCAT:
name = os.path.splitext(os.path.split(item)[1])[0]
newFiles.append({item: {'name': name , 'files': combined}})
newFiles.append({item: {'name': name, 'files': combined}})
if not newFiles:
logger.error("No VIDEO_TS folder found in image file %s" % (item), "TRANSCODER")
newFiles = [failure_dir]
@ -636,6 +679,7 @@ def ripISO(item, newDir, bitbucket):
newFiles = [failure_dir]
return newFiles
def combineVTS(vtsPath):
newFiles = []
combined = ''
@ -643,7 +687,7 @@ def combineVTS(vtsPath):
concat = ''
m = 1
while True:
vtsName = 'VTS_%02d_%d.VOB' % (n+1, m)
vtsName = 'VTS_%02d_%d.VOB' % (n + 1, m)
if os.path.isfile(os.path.join(vtsPath, vtsName)):
concat = concat + os.path.join(vtsPath, vtsName) + '|'
m += 1
@ -659,12 +703,14 @@ def combineVTS(vtsPath):
newFiles.append('concat:%s' % combined[:-1])
return newFiles
def combineCD(combine):
newFiles = []
for item in set([ re.match("(.+)[cC][dD][0-9].",item).groups()[0] for item in combine ]):
for item in set([re.match("(.+)[cC][dD][0-9].", item).groups()[0] for item in combine]):
concat = ''
for n in range(99):
files = [ file for file in combine if n+1 == int(re.match(".+[cC][dD]([0-9]+).",file).groups()[0]) and item in file ]
files = [file for file in combine if
n + 1 == int(re.match(".+[cC][dD]([0-9]+).", file).groups()[0]) and item in file]
if files:
concat = concat + files[0] + '|'
else:
@ -673,17 +719,19 @@ def combineCD(combine):
newFiles.append('concat:%s' % concat[:-1])
return newFiles
def print_cmd(command):
cmd = ""
for item in command:
cmd = cmd + " " + str(item)
logger.debug("calling command:%s" % (cmd))
def Transcode_directory(dirName):
if not core.FFMPEG:
return 1, dirName
logger.info("Checking for files to be transcoded")
final_result = 0 # initialize as successful
final_result = 0 # initialize as successful
if core.OUTPUTVIDEOPATH:
newDir = core.OUTPUTVIDEOPATH
makeDir(newDir)
@ -713,17 +761,17 @@ def Transcode_directory(dirName):
if core.SEXTRACT and isinstance(file, str):
extract_subs(file, newfilePath, bitbucket)
try: # Try to remove the file that we're transcoding to just in case. (ffmpeg will return an error if it already exists for some reason)
try: # Try to remove the file that we're transcoding to just in case. (ffmpeg will return an error if it already exists for some reason)
os.remove(newfilePath)
except OSError, e:
if e.errno != errno.ENOENT: # Ignore the error if it's just telling us that the file doesn't exist
if e.errno != errno.ENOENT: # Ignore the error if it's just telling us that the file doesn't exist
logger.debug("Error when removing transcoding target: %s" % (e))
except Exception, e:
logger.debug("Error when removing transcoding target: %s" % (e))
logger.info("Transcoding video: %s" % (newfilePath))
print_cmd(command)
result = 1 # set result to failed in case call fails.
result = 1 # set result to failed in case call fails.
try:
if isinstance(file, str):
proc = subprocess.Popen(command, stdout=bitbucket, stderr=bitbucket)
@ -752,12 +800,14 @@ def Transcode_directory(dirName):
if result == 0:
try:
shutil.copymode(file, newfilePath)
except: pass
except:
pass
logger.info("Transcoding of video to %s succeeded" % (newfilePath))
if os.path.isfile(newfilePath) and (file in newList or not core.DUPLICATE):
try:
os.unlink(file)
except: pass
except:
pass
else:
logger.error("Transcoding of video to %s failed with result %s" % (newfilePath, str(result)))
# this will be 0 (successful) it all are successful, else will return a positive integer for failure.
@ -766,8 +816,9 @@ def Transcode_directory(dirName):
for file in remList:
try:
os.unlink(file)
except: pass
if not os.listdir(newDir): #this is an empty directory and we didn't transcode into it.
except:
pass
if not os.listdir(newDir): # this is an empty directory and we didn't transcode into it.
os.rmdir(newDir)
newDir = dirName
if not core.PROCESSOUTPUT and core.DUPLICATE: # We postprocess the original files to CP/SB

View file

@ -10,9 +10,9 @@ from core.transmissionrpc.session import Session
from core.transmissionrpc.client import Client
from core.transmissionrpc.utils import add_stdout_logger, add_file_logger
__author__ = 'Erik Svensson <erik.public@gmail.com>'
__version_major__ = 0
__version_minor__ = 11
__version__ = '{0}.{1}'.format(__version_major__, __version_minor__)
__copyright__ = 'Copyright (c) 2008-2013 Erik Svensson'
__license__ = 'MIT'
__author__ = 'Erik Svensson <erik.public@gmail.com>'
__version_major__ = 0
__version_minor__ = 11
__version__ = '{0}.{1}'.format(__version_major__, __version_minor__)
__copyright__ = 'Copyright (c) 2008-2013 Erik Svensson'
__license__ = 'MIT'

View file

@ -18,7 +18,6 @@ from core.transmissionrpc.torrent import Torrent
from core.transmissionrpc.session import Session
from six import PY3, integer_types, string_types, iteritems
if PY3:
from urllib.parse import urlparse
from urllib.request import urlopen
@ -26,6 +25,7 @@ else:
from urlparse import urlparse
from urllib2 import urlopen
def debug_httperror(error):
"""
Log the Transmission RPC HTTP error.
@ -49,6 +49,7 @@ def debug_httperror(error):
)
)
def parse_torrent_id(arg):
"""Parse an torrent id or torrent hashString."""
torrent_id = None
@ -62,7 +63,7 @@ def parse_torrent_id(arg):
elif isinstance(arg, string_types):
try:
torrent_id = int(arg)
if torrent_id >= 2**31:
if torrent_id >= 2 ** 31:
torrent_id = None
except (ValueError, TypeError):
pass
@ -75,6 +76,7 @@ def parse_torrent_id(arg):
pass
return torrent_id
def parse_torrent_ids(args):
"""
Take things and make them valid torrent identifiers
@ -115,6 +117,7 @@ def parse_torrent_ids(args):
ids = [torrent_id]
return ids
"""
Torrent ids
@ -129,12 +132,14 @@ possible to provide a argument called ``timeout``. Timeout is only effective
when using Python 2.6 or later and the default timeout is 30 seconds.
"""
class Client(object):
"""
Client is the class handling the Transmission JSON-RPC client protocol.
"""
def __init__(self, address='localhost', port=DEFAULT_PORT, user=None, password=None, http_handler=None, timeout=None):
def __init__(self, address='localhost', port=DEFAULT_PORT, user=None, password=None, http_handler=None,
timeout=None):
if isinstance(timeout, (integer_types, float)):
self._query_timeout = float(timeout)
else:
@ -204,7 +209,8 @@ class Client(object):
if timeout is None:
timeout = self._query_timeout
while True:
LOGGER.debug(json.dumps({'url': self.url, 'headers': headers, 'query': query, 'timeout': timeout}, indent=2))
LOGGER.debug(
json.dumps({'url': self.url, 'headers': headers, 'query': query, 'timeout': timeout}, indent=2))
try:
result = self.http_handler.request(self.url, query, headers, timeout)
break
@ -244,8 +250,7 @@ class Client(object):
elif require_ids:
raise ValueError('request require ids')
query = json.dumps({'tag': self._sequence, 'method': method
, 'arguments': arguments})
query = json.dumps({'tag': self._sequence, 'method': method, 'arguments': arguments})
self._sequence += 1
start = time.time()
http_data = self._http_query(query, timeout)
@ -348,7 +353,7 @@ class Client(object):
"""
if self.rpc_version < version:
LOGGER.warning('Using feature not supported by server. RPC version for server %d, feature introduced in %d.'
% (self.rpc_version, version))
% (self.rpc_version, version))
def add_torrent(self, torrent, timeout=None, **kwargs):
"""
@ -476,7 +481,7 @@ class Client(object):
"""
self._rpc_version_warning(3)
self._request('torrent-remove',
{'delete-local-data':rpc_bool(delete_data)}, ids, True, timeout=timeout)
{'delete-local-data': rpc_bool(delete_data)}, ids, True, timeout=timeout)
def remove(self, ids, delete_data=False, timeout=None):
"""
@ -606,34 +611,34 @@ class Client(object):
the new methods. list returns a dictionary indexed by torrent id.
"""
warnings.warn('list has been deprecated, please use get_torrent or get_torrents instead.', DeprecationWarning)
fields = ['id', 'hashString', 'name', 'sizeWhenDone', 'leftUntilDone'
, 'eta', 'status', 'rateUpload', 'rateDownload', 'uploadedEver'
, 'downloadedEver', 'uploadRatio', 'queuePosition']
fields = ['id', 'hashString', 'name', 'sizeWhenDone', 'leftUntilDone',
'eta', 'status', 'rateUpload', 'rateDownload', 'uploadedEver',
'downloadedEver', 'uploadRatio', 'queuePosition']
return self._request('torrent-get', {'fields': fields}, timeout=timeout)
def get_files(self, ids=None, timeout=None):
"""
Get list of files for provided torrent id(s). If ids is empty,
information for all torrents are fetched. This function returns a dictionary
for each requested torrent id holding the information about the files.
Get list of files for provided torrent id(s). If ids is empty,
information for all torrents are fetched. This function returns a dictionary
for each requested torrent id holding the information about the files.
::
::
{
<torrent id>: {
<file id>: {
'name': <file name>,
'size': <file size in bytes>,
'completed': <bytes completed>,
'priority': <priority ('high'|'normal'|'low')>,
'selected': <selected for download (True|False)>
}
{
<torrent id>: {
<file id>: {
'name': <file name>,
'size': <file size in bytes>,
'completed': <bytes completed>,
'priority': <priority ('high'|'normal'|'low')>,
'selected': <selected for download (True|False)>
}
...
}
...
}
...
}
...
}
"""
fields = ['id', 'name', 'hashString', 'files', 'priorities', 'wanted']
request_result = self._request('torrent-get', {'fields': fields}, ids, timeout=timeout)
@ -645,22 +650,22 @@ class Client(object):
def set_files(self, items, timeout=None):
"""
Set file properties. Takes a dictionary with similar contents as the result
of `get_files`.
of `get_files`.
::
::
{
<torrent id>: {
<file id>: {
'priority': <priority ('high'|'normal'|'low')>,
'selected': <selected for download (True|False)>
}
{
<torrent id>: {
<file id>: {
'priority': <priority ('high'|'normal'|'low')>,
'selected': <selected for download (True|False)>
}
...
}
...
}
...
}
...
}
"""
if not isinstance(items, dict):
raise ValueError('Invalid file description')
@ -703,8 +708,8 @@ class Client(object):
def change_torrent(self, ids, timeout=None, **kwargs):
"""
Change torrent parameters for the torrent(s) with the supplied id's. The
parameters are:
Change torrent parameters for the torrent(s) with the supplied id's. The
parameters are:
============================ ===== =============== =======================================================================================
Argument RPC Replaced by Description
@ -736,13 +741,13 @@ class Client(object):
``uploadLimited`` 5 - Enable upload speed limiter.
============================ ===== =============== =======================================================================================
.. NOTE::
transmissionrpc will try to automatically fix argument errors.
.. NOTE::
transmissionrpc will try to automatically fix argument errors.
"""
args = {}
for key, value in iteritems(kwargs):
argument = make_rpc_name(key)
(arg, val) = argument_value_convert('torrent-set' , argument, value, self.rpc_version)
(arg, val) = argument_value_convert('torrent-set', argument, value, self.rpc_version)
args[arg] = val
if len(args) > 0:
@ -814,7 +819,7 @@ class Client(object):
"""Move transfer to the bottom of the queue."""
self._rpc_version_warning(14)
self._request('queue-move-bottom', ids=ids, require_ids=True, timeout=timeout)
def queue_up(self, ids, timeout=None):
"""Move transfer up in the queue."""
self._rpc_version_warning(14)
@ -888,14 +893,14 @@ class Client(object):
================================ ===== ================= ==========================================================================================================================
.. NOTE::
transmissionrpc will try to automatically fix argument errors.
transmissionrpc will try to automatically fix argument errors.
"""
args = {}
for key, value in iteritems(kwargs):
if key == 'encryption' and value not in ['required', 'preferred', 'tolerated']:
raise ValueError('Invalid encryption value')
argument = make_rpc_name(key)
(arg, val) = argument_value_convert('session-set' , argument, value, self.rpc_version)
(arg, val) = argument_value_convert('session-set', argument, value, self.rpc_version)
args[arg] = val
if len(args) > 0:
self._request('session-set', args, timeout=timeout)

View file

@ -6,10 +6,10 @@ import logging
from core.transmissionrpc.six import iteritems
LOGGER = logging.getLogger('transmissionrpc')
LOGGER.setLevel(logging.ERROR)
def mirror_dict(source):
"""
Creates a dictionary with all values as keys and all keys as values.
@ -17,38 +17,39 @@ def mirror_dict(source):
source.update(dict((value, key) for key, value in iteritems(source)))
return source
DEFAULT_PORT = 9091
DEFAULT_TIMEOUT = 30.0
TR_PRI_LOW = -1
TR_PRI_NORMAL = 0
TR_PRI_HIGH = 1
TR_PRI_LOW = -1
TR_PRI_NORMAL = 0
TR_PRI_HIGH = 1
PRIORITY = mirror_dict({
'low' : TR_PRI_LOW,
'normal' : TR_PRI_NORMAL,
'high' : TR_PRI_HIGH
'low': TR_PRI_LOW,
'normal': TR_PRI_NORMAL,
'high': TR_PRI_HIGH
})
TR_RATIOLIMIT_GLOBAL = 0 # follow the global settings
TR_RATIOLIMIT_SINGLE = 1 # override the global settings, seeding until a certain ratio
TR_RATIOLIMIT_UNLIMITED = 2 # override the global settings, seeding regardless of ratio
TR_RATIOLIMIT_GLOBAL = 0 # follow the global settings
TR_RATIOLIMIT_SINGLE = 1 # override the global settings, seeding until a certain ratio
TR_RATIOLIMIT_UNLIMITED = 2 # override the global settings, seeding regardless of ratio
RATIO_LIMIT = mirror_dict({
'global' : TR_RATIOLIMIT_GLOBAL,
'single' : TR_RATIOLIMIT_SINGLE,
'unlimited' : TR_RATIOLIMIT_UNLIMITED
'global': TR_RATIOLIMIT_GLOBAL,
'single': TR_RATIOLIMIT_SINGLE,
'unlimited': TR_RATIOLIMIT_UNLIMITED
})
TR_IDLELIMIT_GLOBAL = 0 # follow the global settings
TR_IDLELIMIT_SINGLE = 1 # override the global settings, seeding until a certain idle time
TR_IDLELIMIT_UNLIMITED = 2 # override the global settings, seeding regardless of activity
TR_IDLELIMIT_GLOBAL = 0 # follow the global settings
TR_IDLELIMIT_SINGLE = 1 # override the global settings, seeding until a certain idle time
TR_IDLELIMIT_UNLIMITED = 2 # override the global settings, seeding regardless of activity
IDLE_LIMIT = mirror_dict({
'global' : TR_RATIOLIMIT_GLOBAL,
'single' : TR_RATIOLIMIT_SINGLE,
'unlimited' : TR_RATIOLIMIT_UNLIMITED
'global': TR_RATIOLIMIT_GLOBAL,
'single': TR_RATIOLIMIT_SINGLE,
'unlimited': TR_RATIOLIMIT_UNLIMITED
})
# A note on argument maps
@ -62,236 +63,266 @@ IDLE_LIMIT = mirror_dict({
# Arguments for torrent methods
TORRENT_ARGS = {
'get' : {
'activityDate': ('number', 1, None, None, None, 'Last time of upload or download activity.'),
'addedDate': ('number', 1, None, None, None, 'The date when this torrent was first added.'),
'announceResponse': ('string', 1, 7, None, None, 'The announce message from the tracker.'),
'announceURL': ('string', 1, 7, None, None, 'Current announce URL.'),
'bandwidthPriority': ('number', 5, None, None, None, 'Bandwidth priority. Low (-1), Normal (0) or High (1).'),
'comment': ('string', 1, None, None, None, 'Torrent comment.'),
'corruptEver': ('number', 1, None, None, None, 'Number of bytes of corrupt data downloaded.'),
'creator': ('string', 1, None, None, None, 'Torrent creator.'),
'dateCreated': ('number', 1, None, None, None, 'Torrent creation date.'),
'desiredAvailable': ('number', 1, None, None, None, 'Number of bytes avalable and left to be downloaded.'),
'doneDate': ('number', 1, None, None, None, 'The date when the torrent finished downloading.'),
'downloadDir': ('string', 4, None, None, None, 'The directory path where the torrent is downloaded to.'),
'downloadedEver': ('number', 1, None, None, None, 'Number of bytes of good data downloaded.'),
'downloaders': ('number', 4, 7, None, None, 'Number of downloaders.'),
'downloadLimit': ('number', 1, None, None, None, 'Download limit in Kbps.'),
'downloadLimited': ('boolean', 5, None, None, None, 'Download limit is enabled'),
'downloadLimitMode': ('number', 1, 5, None, None, 'Download limit mode. 0 means global, 1 means signle, 2 unlimited.'),
'error': ('number', 1, None, None, None, 'Kind of error. 0 means OK, 1 means tracker warning, 2 means tracker error, 3 means local error.'),
'errorString': ('number', 1, None, None, None, 'Error message.'),
'eta': ('number', 1, None, None, None, 'Estimated number of seconds left when downloading or seeding. -1 means not available and -2 means unknown.'),
'etaIdle': ('number', 15, None, None, None, 'Estimated number of seconds left until the idle time limit is reached. -1 means not available and -2 means unknown.'),
'files': ('array', 1, None, None, None, 'Array of file object containing key, bytesCompleted, length and name.'),
'fileStats': ('array', 5, None, None, None, 'Aray of file statistics containing bytesCompleted, wanted and priority.'),
'hashString': ('string', 1, None, None, None, 'Hashstring unique for the torrent even between sessions.'),
'haveUnchecked': ('number', 1, None, None, None, 'Number of bytes of partial pieces.'),
'haveValid': ('number', 1, None, None, None, 'Number of bytes of checksum verified data.'),
'honorsSessionLimits': ('boolean', 5, None, None, None, 'True if session upload limits are honored'),
'id': ('number', 1, None, None, None, 'Session unique torrent id.'),
'isFinished': ('boolean', 9, None, None, None, 'True if the torrent is finished. Downloaded and seeded.'),
'isPrivate': ('boolean', 1, None, None, None, 'True if the torrent is private.'),
'isStalled': ('boolean', 14, None, None, None, 'True if the torrent has stalled (been idle for a long time).'),
'lastAnnounceTime': ('number', 1, 7, None, None, 'The time of the last announcement.'),
'lastScrapeTime': ('number', 1, 7, None, None, 'The time af the last successful scrape.'),
'leechers': ('number', 1, 7, None, None, 'Number of leechers.'),
'leftUntilDone': ('number', 1, None, None, None, 'Number of bytes left until the download is done.'),
'magnetLink': ('string', 7, None, None, None, 'The magnet link for this torrent.'),
'manualAnnounceTime': ('number', 1, None, None, None, 'The time until you manually ask for more peers.'),
'maxConnectedPeers': ('number', 1, None, None, None, 'Maximum of connected peers.'),
'metadataPercentComplete': ('number', 7, None, None, None, 'Download progress of metadata. 0.0 to 1.0.'),
'name': ('string', 1, None, None, None, 'Torrent name.'),
'nextAnnounceTime': ('number', 1, 7, None, None, 'Next announce time.'),
'nextScrapeTime': ('number', 1, 7, None, None, 'Next scrape time.'),
'peer-limit': ('number', 5, None, None, None, 'Maximum number of peers.'),
'peers': ('array', 2, None, None, None, 'Array of peer objects.'),
'peersConnected': ('number', 1, None, None, None, 'Number of peers we are connected to.'),
'peersFrom': ('object', 1, None, None, None, 'Object containing download peers counts for different peer types.'),
'peersGettingFromUs': ('number', 1, None, None, None, 'Number of peers we are sending data to.'),
'peersKnown': ('number', 1, 13, None, None, 'Number of peers that the tracker knows.'),
'peersSendingToUs': ('number', 1, None, None, None, 'Number of peers sending to us'),
'percentDone': ('double', 5, None, None, None, 'Download progress of selected files. 0.0 to 1.0.'),
'pieces': ('string', 5, None, None, None, 'String with base64 encoded bitfield indicating finished pieces.'),
'pieceCount': ('number', 1, None, None, None, 'Number of pieces.'),
'pieceSize': ('number', 1, None, None, None, 'Number of bytes in a piece.'),
'priorities': ('array', 1, None, None, None, 'Array of file priorities.'),
'queuePosition': ('number', 14, None, None, None, 'The queue position.'),
'rateDownload': ('number', 1, None, None, None, 'Download rate in bps.'),
'rateUpload': ('number', 1, None, None, None, 'Upload rate in bps.'),
'recheckProgress': ('double', 1, None, None, None, 'Progress of recheck. 0.0 to 1.0.'),
'secondsDownloading': ('number', 15, None, None, None, ''),
'secondsSeeding': ('number', 15, None, None, None, ''),
'scrapeResponse': ('string', 1, 7, None, None, 'Scrape response message.'),
'scrapeURL': ('string', 1, 7, None, None, 'Current scrape URL'),
'seeders': ('number', 1, 7, None, None, 'Number of seeders reported by the tracker.'),
'seedIdleLimit': ('number', 10, None, None, None, 'Idle limit in minutes.'),
'seedIdleMode': ('number', 10, None, None, None, 'Use global (0), torrent (1), or unlimited (2) limit.'),
'seedRatioLimit': ('double', 5, None, None, None, 'Seed ratio limit.'),
'seedRatioMode': ('number', 5, None, None, None, 'Use global (0), torrent (1), or unlimited (2) limit.'),
'sizeWhenDone': ('number', 1, None, None, None, 'Size of the torrent download in bytes.'),
'startDate': ('number', 1, None, None, None, 'The date when the torrent was last started.'),
'status': ('number', 1, None, None, None, 'Current status, see source'),
'swarmSpeed': ('number', 1, 7, None, None, 'Estimated speed in Kbps in the swarm.'),
'timesCompleted': ('number', 1, 7, None, None, 'Number of successful downloads reported by the tracker.'),
'trackers': ('array', 1, None, None, None, 'Array of tracker objects.'),
'trackerStats': ('object', 7, None, None, None, 'Array of object containing tracker statistics.'),
'totalSize': ('number', 1, None, None, None, 'Total size of the torrent in bytes'),
'torrentFile': ('string', 5, None, None, None, 'Path to .torrent file.'),
'uploadedEver': ('number', 1, None, None, None, 'Number of bytes uploaded, ever.'),
'uploadLimit': ('number', 1, None, None, None, 'Upload limit in Kbps'),
'uploadLimitMode': ('number', 1, 5, None, None, 'Upload limit mode. 0 means global, 1 means signle, 2 unlimited.'),
'uploadLimited': ('boolean', 5, None, None, None, 'Upload limit enabled.'),
'uploadRatio': ('double', 1, None, None, None, 'Seed ratio.'),
'wanted': ('array', 1, None, None, None, 'Array of booleans indicated wanted files.'),
'webseeds': ('array', 1, None, None, None, 'Array of webseeds objects'),
'webseedsSendingToUs': ('number', 1, None, None, None, 'Number of webseeds seeding to us.'),
'get': {
'activityDate': ('number', 1, None, None, None, 'Last time of upload or download activity.'),
'addedDate': ('number', 1, None, None, None, 'The date when this torrent was first added.'),
'announceResponse': ('string', 1, 7, None, None, 'The announce message from the tracker.'),
'announceURL': ('string', 1, 7, None, None, 'Current announce URL.'),
'bandwidthPriority': ('number', 5, None, None, None, 'Bandwidth priority. Low (-1), Normal (0) or High (1).'),
'comment': ('string', 1, None, None, None, 'Torrent comment.'),
'corruptEver': ('number', 1, None, None, None, 'Number of bytes of corrupt data downloaded.'),
'creator': ('string', 1, None, None, None, 'Torrent creator.'),
'dateCreated': ('number', 1, None, None, None, 'Torrent creation date.'),
'desiredAvailable': ('number', 1, None, None, None, 'Number of bytes avalable and left to be downloaded.'),
'doneDate': ('number', 1, None, None, None, 'The date when the torrent finished downloading.'),
'downloadDir': ('string', 4, None, None, None, 'The directory path where the torrent is downloaded to.'),
'downloadedEver': ('number', 1, None, None, None, 'Number of bytes of good data downloaded.'),
'downloaders': ('number', 4, 7, None, None, 'Number of downloaders.'),
'downloadLimit': ('number', 1, None, None, None, 'Download limit in Kbps.'),
'downloadLimited': ('boolean', 5, None, None, None, 'Download limit is enabled'),
'downloadLimitMode': (
'number', 1, 5, None, None, 'Download limit mode. 0 means global, 1 means signle, 2 unlimited.'),
'error': ('number', 1, None, None, None,
'Kind of error. 0 means OK, 1 means tracker warning, 2 means tracker error, 3 means local error.'),
'errorString': ('number', 1, None, None, None, 'Error message.'),
'eta': ('number', 1, None, None, None,
'Estimated number of seconds left when downloading or seeding. -1 means not available and -2 means unknown.'),
'etaIdle': ('number', 15, None, None, None,
'Estimated number of seconds left until the idle time limit is reached. -1 means not available and -2 means unknown.'),
'files': (
'array', 1, None, None, None, 'Array of file object containing key, bytesCompleted, length and name.'),
'fileStats': (
'array', 5, None, None, None, 'Aray of file statistics containing bytesCompleted, wanted and priority.'),
'hashString': ('string', 1, None, None, None, 'Hashstring unique for the torrent even between sessions.'),
'haveUnchecked': ('number', 1, None, None, None, 'Number of bytes of partial pieces.'),
'haveValid': ('number', 1, None, None, None, 'Number of bytes of checksum verified data.'),
'honorsSessionLimits': ('boolean', 5, None, None, None, 'True if session upload limits are honored'),
'id': ('number', 1, None, None, None, 'Session unique torrent id.'),
'isFinished': ('boolean', 9, None, None, None, 'True if the torrent is finished. Downloaded and seeded.'),
'isPrivate': ('boolean', 1, None, None, None, 'True if the torrent is private.'),
'isStalled': ('boolean', 14, None, None, None, 'True if the torrent has stalled (been idle for a long time).'),
'lastAnnounceTime': ('number', 1, 7, None, None, 'The time of the last announcement.'),
'lastScrapeTime': ('number', 1, 7, None, None, 'The time af the last successful scrape.'),
'leechers': ('number', 1, 7, None, None, 'Number of leechers.'),
'leftUntilDone': ('number', 1, None, None, None, 'Number of bytes left until the download is done.'),
'magnetLink': ('string', 7, None, None, None, 'The magnet link for this torrent.'),
'manualAnnounceTime': ('number', 1, None, None, None, 'The time until you manually ask for more peers.'),
'maxConnectedPeers': ('number', 1, None, None, None, 'Maximum of connected peers.'),
'metadataPercentComplete': ('number', 7, None, None, None, 'Download progress of metadata. 0.0 to 1.0.'),
'name': ('string', 1, None, None, None, 'Torrent name.'),
'nextAnnounceTime': ('number', 1, 7, None, None, 'Next announce time.'),
'nextScrapeTime': ('number', 1, 7, None, None, 'Next scrape time.'),
'peer-limit': ('number', 5, None, None, None, 'Maximum number of peers.'),
'peers': ('array', 2, None, None, None, 'Array of peer objects.'),
'peersConnected': ('number', 1, None, None, None, 'Number of peers we are connected to.'),
'peersFrom': (
'object', 1, None, None, None, 'Object containing download peers counts for different peer types.'),
'peersGettingFromUs': ('number', 1, None, None, None, 'Number of peers we are sending data to.'),
'peersKnown': ('number', 1, 13, None, None, 'Number of peers that the tracker knows.'),
'peersSendingToUs': ('number', 1, None, None, None, 'Number of peers sending to us'),
'percentDone': ('double', 5, None, None, None, 'Download progress of selected files. 0.0 to 1.0.'),
'pieces': ('string', 5, None, None, None, 'String with base64 encoded bitfield indicating finished pieces.'),
'pieceCount': ('number', 1, None, None, None, 'Number of pieces.'),
'pieceSize': ('number', 1, None, None, None, 'Number of bytes in a piece.'),
'priorities': ('array', 1, None, None, None, 'Array of file priorities.'),
'queuePosition': ('number', 14, None, None, None, 'The queue position.'),
'rateDownload': ('number', 1, None, None, None, 'Download rate in bps.'),
'rateUpload': ('number', 1, None, None, None, 'Upload rate in bps.'),
'recheckProgress': ('double', 1, None, None, None, 'Progress of recheck. 0.0 to 1.0.'),
'secondsDownloading': ('number', 15, None, None, None, ''),
'secondsSeeding': ('number', 15, None, None, None, ''),
'scrapeResponse': ('string', 1, 7, None, None, 'Scrape response message.'),
'scrapeURL': ('string', 1, 7, None, None, 'Current scrape URL'),
'seeders': ('number', 1, 7, None, None, 'Number of seeders reported by the tracker.'),
'seedIdleLimit': ('number', 10, None, None, None, 'Idle limit in minutes.'),
'seedIdleMode': ('number', 10, None, None, None, 'Use global (0), torrent (1), or unlimited (2) limit.'),
'seedRatioLimit': ('double', 5, None, None, None, 'Seed ratio limit.'),
'seedRatioMode': ('number', 5, None, None, None, 'Use global (0), torrent (1), or unlimited (2) limit.'),
'sizeWhenDone': ('number', 1, None, None, None, 'Size of the torrent download in bytes.'),
'startDate': ('number', 1, None, None, None, 'The date when the torrent was last started.'),
'status': ('number', 1, None, None, None, 'Current status, see source'),
'swarmSpeed': ('number', 1, 7, None, None, 'Estimated speed in Kbps in the swarm.'),
'timesCompleted': ('number', 1, 7, None, None, 'Number of successful downloads reported by the tracker.'),
'trackers': ('array', 1, None, None, None, 'Array of tracker objects.'),
'trackerStats': ('object', 7, None, None, None, 'Array of object containing tracker statistics.'),
'totalSize': ('number', 1, None, None, None, 'Total size of the torrent in bytes'),
'torrentFile': ('string', 5, None, None, None, 'Path to .torrent file.'),
'uploadedEver': ('number', 1, None, None, None, 'Number of bytes uploaded, ever.'),
'uploadLimit': ('number', 1, None, None, None, 'Upload limit in Kbps'),
'uploadLimitMode': (
'number', 1, 5, None, None, 'Upload limit mode. 0 means global, 1 means signle, 2 unlimited.'),
'uploadLimited': ('boolean', 5, None, None, None, 'Upload limit enabled.'),
'uploadRatio': ('double', 1, None, None, None, 'Seed ratio.'),
'wanted': ('array', 1, None, None, None, 'Array of booleans indicated wanted files.'),
'webseeds': ('array', 1, None, None, None, 'Array of webseeds objects'),
'webseedsSendingToUs': ('number', 1, None, None, None, 'Number of webseeds seeding to us.'),
},
'set': {
'bandwidthPriority': ('number', 5, None, None, None, 'Priority for this transfer.'),
'downloadLimit': ('number', 5, None, 'speed-limit-down', None, 'Set the speed limit for download in Kib/s.'),
'downloadLimited': ('boolean', 5, None, 'speed-limit-down-enabled', None, 'Enable download speed limiter.'),
'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
'honorsSessionLimits': ('boolean', 5, None, None, None, "Enables or disables the transfer to honour the upload limit set in the session."),
'location': ('array', 1, None, None, None, 'Local download location.'),
'peer-limit': ('number', 1, None, None, None, 'The peer limit for the torrents.'),
'priority-high': ('array', 1, None, None, None, "A list of file id's that should have high priority."),
'priority-low': ('array', 1, None, None, None, "A list of file id's that should have normal priority."),
'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have low priority."),
'queuePosition': ('number', 14, None, None, None, 'Position of this transfer in its queue.'),
'seedIdleLimit': ('number', 10, None, None, None, 'Seed inactivity limit in minutes.'),
'seedIdleMode': ('number', 10, None, None, None, 'Seed inactivity mode. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
'seedRatioLimit': ('double', 5, None, None, None, 'Seeding ratio.'),
'seedRatioMode': ('number', 5, None, None, None, 'Which ratio to use. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
'speed-limit-down': ('number', 1, 5, None, 'downloadLimit', 'Set the speed limit for download in Kib/s.'),
'speed-limit-down-enabled': ('boolean', 1, 5, None, 'downloadLimited', 'Enable download speed limiter.'),
'speed-limit-up': ('number', 1, 5, None, 'uploadLimit', 'Set the speed limit for upload in Kib/s.'),
'speed-limit-up-enabled': ('boolean', 1, 5, None, 'uploadLimited', 'Enable upload speed limiter.'),
'trackerAdd': ('array', 10, None, None, None, 'Array of string with announce URLs to add.'),
'trackerRemove': ('array', 10, None, None, None, 'Array of ids of trackers to remove.'),
'trackerReplace': ('array', 10, None, None, None, 'Array of (id, url) tuples where the announce URL should be replaced.'),
'uploadLimit': ('number', 5, None, 'speed-limit-up', None, 'Set the speed limit for upload in Kib/s.'),
'uploadLimited': ('boolean', 5, None, 'speed-limit-up-enabled', None, 'Enable upload speed limiter.'),
'bandwidthPriority': ('number', 5, None, None, None, 'Priority for this transfer.'),
'downloadLimit': ('number', 5, None, 'speed-limit-down', None, 'Set the speed limit for download in Kib/s.'),
'downloadLimited': ('boolean', 5, None, 'speed-limit-down-enabled', None, 'Enable download speed limiter.'),
'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
'honorsSessionLimits': ('boolean', 5, None, None, None,
"Enables or disables the transfer to honour the upload limit set in the session."),
'location': ('array', 1, None, None, None, 'Local download location.'),
'peer-limit': ('number', 1, None, None, None, 'The peer limit for the torrents.'),
'priority-high': ('array', 1, None, None, None, "A list of file id's that should have high priority."),
'priority-low': ('array', 1, None, None, None, "A list of file id's that should have normal priority."),
'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have low priority."),
'queuePosition': ('number', 14, None, None, None, 'Position of this transfer in its queue.'),
'seedIdleLimit': ('number', 10, None, None, None, 'Seed inactivity limit in minutes.'),
'seedIdleMode': ('number', 10, None, None, None,
'Seed inactivity mode. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
'seedRatioLimit': ('double', 5, None, None, None, 'Seeding ratio.'),
'seedRatioMode': ('number', 5, None, None, None,
'Which ratio to use. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
'speed-limit-down': ('number', 1, 5, None, 'downloadLimit', 'Set the speed limit for download in Kib/s.'),
'speed-limit-down-enabled': ('boolean', 1, 5, None, 'downloadLimited', 'Enable download speed limiter.'),
'speed-limit-up': ('number', 1, 5, None, 'uploadLimit', 'Set the speed limit for upload in Kib/s.'),
'speed-limit-up-enabled': ('boolean', 1, 5, None, 'uploadLimited', 'Enable upload speed limiter.'),
'trackerAdd': ('array', 10, None, None, None, 'Array of string with announce URLs to add.'),
'trackerRemove': ('array', 10, None, None, None, 'Array of ids of trackers to remove.'),
'trackerReplace': (
'array', 10, None, None, None, 'Array of (id, url) tuples where the announce URL should be replaced.'),
'uploadLimit': ('number', 5, None, 'speed-limit-up', None, 'Set the speed limit for upload in Kib/s.'),
'uploadLimited': ('boolean', 5, None, 'speed-limit-up-enabled', None, 'Enable upload speed limiter.'),
},
'add': {
'bandwidthPriority': ('number', 8, None, None, None, 'Priority for this transfer.'),
'download-dir': ('string', 1, None, None, None, 'The directory where the downloaded contents will be saved in.'),
'cookies': ('string', 13, None, None, None, 'One or more HTTP cookie(s).'),
'filename': ('string', 1, None, None, None, "A file path or URL to a torrent file or a magnet link."),
'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
'metainfo': ('string', 1, None, None, None, 'The content of a torrent file, base64 encoded.'),
'paused': ('boolean', 1, None, None, None, 'If True, does not start the transfer when added.'),
'peer-limit': ('number', 1, None, None, None, 'Maximum number of peers allowed.'),
'priority-high': ('array', 1, None, None, None, "A list of file id's that should have high priority."),
'priority-low': ('array', 1, None, None, None, "A list of file id's that should have low priority."),
'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have normal priority."),
'bandwidthPriority': ('number', 8, None, None, None, 'Priority for this transfer.'),
'download-dir': (
'string', 1, None, None, None, 'The directory where the downloaded contents will be saved in.'),
'cookies': ('string', 13, None, None, None, 'One or more HTTP cookie(s).'),
'filename': ('string', 1, None, None, None, "A file path or URL to a torrent file or a magnet link."),
'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
'metainfo': ('string', 1, None, None, None, 'The content of a torrent file, base64 encoded.'),
'paused': ('boolean', 1, None, None, None, 'If True, does not start the transfer when added.'),
'peer-limit': ('number', 1, None, None, None, 'Maximum number of peers allowed.'),
'priority-high': ('array', 1, None, None, None, "A list of file id's that should have high priority."),
'priority-low': ('array', 1, None, None, None, "A list of file id's that should have low priority."),
'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have normal priority."),
}
}
# Arguments for session methods
SESSION_ARGS = {
'get': {
"alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
"alt-speed-enabled": ('boolean', 5, None, None, None, 'True if alternate global download speed limiter is ebabled.'),
"alt-speed-time-begin": ('number', 5, None, None, None, 'Time when alternate speeds should be enabled. Minutes after midnight.'),
"alt-speed-time-enabled": ('boolean', 5, None, None, None, 'True if alternate speeds scheduling is enabled.'),
"alt-speed-time-end": ('number', 5, None, None, None, 'Time when alternate speeds should be disabled. Minutes after midnight.'),
"alt-speed-time-day": ('number', 5, None, None, None, 'Days alternate speeds scheduling is enabled.'),
"alt-speed-up": ('number', 5, None, None, None, 'Alternate session upload speed limit (in Kib/s)'),
"blocklist-enabled": ('boolean', 5, None, None, None, 'True when blocklist is enabled.'),
"blocklist-size": ('number', 5, None, None, None, 'Number of rules in the blocklist'),
"blocklist-url": ('string', 11, None, None, None, 'Location of the block list. Updated with blocklist-update.'),
"cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'),
"config-dir": ('string', 8, None, None, None, 'location of transmissions configuration directory'),
"dht-enabled": ('boolean', 6, None, None, None, 'True if DHT enabled.'),
"download-dir": ('string', 1, None, None, None, 'The download directory.'),
"download-dir-free-space": ('number', 12, None, None, None, 'Free space in the download directory, in bytes'),
"download-queue-size": ('number', 14, None, None, None, 'Number of slots in the download queue.'),
"download-queue-enabled": ('boolean', 14, None, None, None, 'True if the download queue is enabled.'),
"encryption": ('string', 1, None, None, None, 'Encryption mode, one of ``required``, ``preferred`` or ``tolerated``.'),
"idle-seeding-limit": ('number', 10, None, None, None, 'Seed inactivity limit in minutes.'),
"idle-seeding-limit-enabled": ('boolean', 10, None, None, None, 'True if the seed activity limit is enabled.'),
"incomplete-dir": ('string', 7, None, None, None, 'The path to the directory for incomplete torrent transfer data.'),
"incomplete-dir-enabled": ('boolean', 7, None, None, None, 'True if the incomplete dir is enabled.'),
"lpd-enabled": ('boolean', 9, None, None, None, 'True if local peer discovery is enabled.'),
"peer-limit": ('number', 1, 5, None, 'peer-limit-global', 'Maximum number of peers.'),
"peer-limit-global": ('number', 5, None, 'peer-limit', None, 'Maximum number of peers.'),
"peer-limit-per-torrent": ('number', 5, None, None, None, 'Maximum number of peers per transfer.'),
"pex-allowed": ('boolean', 1, 5, None, 'pex-enabled', 'True if PEX is allowed.'),
"pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'True if PEX is enabled.'),
"port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
"peer-port": ('number', 5, None, 'port', None, 'Peer port.'),
"peer-port-random-on-start": ('boolean', 5, None, None, None, 'Enables randomized peer port on start of Transmission.'),
"port-forwarding-enabled": ('boolean', 1, None, None, None, 'True if port forwarding is enabled.'),
"queue-stalled-minutes": ('number', 14, None, None, None, 'Number of minutes of idle that marks a transfer as stalled.'),
"queue-stalled-enabled": ('boolean', 14, None, None, None, 'True if stalled tracking of transfers is enabled.'),
"rename-partial-files": ('boolean', 8, None, None, None, 'True if ".part" is appended to incomplete files'),
"rpc-version": ('number', 4, None, None, None, 'Transmission RPC API Version.'),
"rpc-version-minimum": ('number', 4, None, None, None, 'Minimum accepted RPC API Version.'),
"script-torrent-done-enabled": ('boolean', 9, None, None, None, 'True if the done script is enabled.'),
"script-torrent-done-filename": ('string', 9, None, None, None, 'Filename of the script to run when the transfer is done.'),
"seedRatioLimit": ('double', 5, None, None, None, 'Seed ratio limit. 1.0 means 1:1 download and upload ratio.'),
"seedRatioLimited": ('boolean', 5, None, None, None, 'True if seed ration limit is enabled.'),
"seed-queue-size": ('number', 14, None, None, None, 'Number of slots in the upload queue.'),
"seed-queue-enabled": ('boolean', 14, None, None, None, 'True if upload queue is enabled.'),
"speed-limit-down": ('number', 1, None, None, None, 'Download speed limit (in Kib/s).'),
"speed-limit-down-enabled": ('boolean', 1, None, None, None, 'True if the download speed is limited.'),
"speed-limit-up": ('number', 1, None, None, None, 'Upload speed limit (in Kib/s).'),
"speed-limit-up-enabled": ('boolean', 1, None, None, None, 'True if the upload speed is limited.'),
"start-added-torrents": ('boolean', 9, None, None, None, 'When true uploaded torrents will start right away.'),
"trash-original-torrent-files": ('boolean', 9, None, None, None, 'When true added .torrent files will be deleted.'),
'units': ('object', 10, None, None, None, 'An object containing units for size and speed.'),
'utp-enabled': ('boolean', 13, None, None, None, 'True if Micro Transport Protocol (UTP) is enabled.'),
"version": ('string', 3, None, None, None, 'Transmission version.'),
"alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
"alt-speed-enabled": (
'boolean', 5, None, None, None, 'True if alternate global download speed limiter is ebabled.'),
"alt-speed-time-begin": (
'number', 5, None, None, None, 'Time when alternate speeds should be enabled. Minutes after midnight.'),
"alt-speed-time-enabled": ('boolean', 5, None, None, None, 'True if alternate speeds scheduling is enabled.'),
"alt-speed-time-end": (
'number', 5, None, None, None, 'Time when alternate speeds should be disabled. Minutes after midnight.'),
"alt-speed-time-day": ('number', 5, None, None, None, 'Days alternate speeds scheduling is enabled.'),
"alt-speed-up": ('number', 5, None, None, None, 'Alternate session upload speed limit (in Kib/s)'),
"blocklist-enabled": ('boolean', 5, None, None, None, 'True when blocklist is enabled.'),
"blocklist-size": ('number', 5, None, None, None, 'Number of rules in the blocklist'),
"blocklist-url": ('string', 11, None, None, None, 'Location of the block list. Updated with blocklist-update.'),
"cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'),
"config-dir": ('string', 8, None, None, None, 'location of transmissions configuration directory'),
"dht-enabled": ('boolean', 6, None, None, None, 'True if DHT enabled.'),
"download-dir": ('string', 1, None, None, None, 'The download directory.'),
"download-dir-free-space": ('number', 12, None, None, None, 'Free space in the download directory, in bytes'),
"download-queue-size": ('number', 14, None, None, None, 'Number of slots in the download queue.'),
"download-queue-enabled": ('boolean', 14, None, None, None, 'True if the download queue is enabled.'),
"encryption": (
'string', 1, None, None, None, 'Encryption mode, one of ``required``, ``preferred`` or ``tolerated``.'),
"idle-seeding-limit": ('number', 10, None, None, None, 'Seed inactivity limit in minutes.'),
"idle-seeding-limit-enabled": ('boolean', 10, None, None, None, 'True if the seed activity limit is enabled.'),
"incomplete-dir": (
'string', 7, None, None, None, 'The path to the directory for incomplete torrent transfer data.'),
"incomplete-dir-enabled": ('boolean', 7, None, None, None, 'True if the incomplete dir is enabled.'),
"lpd-enabled": ('boolean', 9, None, None, None, 'True if local peer discovery is enabled.'),
"peer-limit": ('number', 1, 5, None, 'peer-limit-global', 'Maximum number of peers.'),
"peer-limit-global": ('number', 5, None, 'peer-limit', None, 'Maximum number of peers.'),
"peer-limit-per-torrent": ('number', 5, None, None, None, 'Maximum number of peers per transfer.'),
"pex-allowed": ('boolean', 1, 5, None, 'pex-enabled', 'True if PEX is allowed.'),
"pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'True if PEX is enabled.'),
"port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
"peer-port": ('number', 5, None, 'port', None, 'Peer port.'),
"peer-port-random-on-start": (
'boolean', 5, None, None, None, 'Enables randomized peer port on start of Transmission.'),
"port-forwarding-enabled": ('boolean', 1, None, None, None, 'True if port forwarding is enabled.'),
"queue-stalled-minutes": (
'number', 14, None, None, None, 'Number of minutes of idle that marks a transfer as stalled.'),
"queue-stalled-enabled": ('boolean', 14, None, None, None, 'True if stalled tracking of transfers is enabled.'),
"rename-partial-files": ('boolean', 8, None, None, None, 'True if ".part" is appended to incomplete files'),
"rpc-version": ('number', 4, None, None, None, 'Transmission RPC API Version.'),
"rpc-version-minimum": ('number', 4, None, None, None, 'Minimum accepted RPC API Version.'),
"script-torrent-done-enabled": ('boolean', 9, None, None, None, 'True if the done script is enabled.'),
"script-torrent-done-filename": (
'string', 9, None, None, None, 'Filename of the script to run when the transfer is done.'),
"seedRatioLimit": ('double', 5, None, None, None, 'Seed ratio limit. 1.0 means 1:1 download and upload ratio.'),
"seedRatioLimited": ('boolean', 5, None, None, None, 'True if seed ration limit is enabled.'),
"seed-queue-size": ('number', 14, None, None, None, 'Number of slots in the upload queue.'),
"seed-queue-enabled": ('boolean', 14, None, None, None, 'True if upload queue is enabled.'),
"speed-limit-down": ('number', 1, None, None, None, 'Download speed limit (in Kib/s).'),
"speed-limit-down-enabled": ('boolean', 1, None, None, None, 'True if the download speed is limited.'),
"speed-limit-up": ('number', 1, None, None, None, 'Upload speed limit (in Kib/s).'),
"speed-limit-up-enabled": ('boolean', 1, None, None, None, 'True if the upload speed is limited.'),
"start-added-torrents": ('boolean', 9, None, None, None, 'When true uploaded torrents will start right away.'),
"trash-original-torrent-files": (
'boolean', 9, None, None, None, 'When true added .torrent files will be deleted.'),
'units': ('object', 10, None, None, None, 'An object containing units for size and speed.'),
'utp-enabled': ('boolean', 13, None, None, None, 'True if Micro Transport Protocol (UTP) is enabled.'),
"version": ('string', 3, None, None, None, 'Transmission version.'),
},
'set': {
"alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
"alt-speed-enabled": ('boolean', 5, None, None, None, 'Enables alternate global download speed limiter.'),
"alt-speed-time-begin": ('number', 5, None, None, None, 'Time when alternate speeds should be enabled. Minutes after midnight.'),
"alt-speed-time-enabled": ('boolean', 5, None, None, None, 'Enables alternate speeds scheduling.'),
"alt-speed-time-end": ('number', 5, None, None, None, 'Time when alternate speeds should be disabled. Minutes after midnight.'),
"alt-speed-time-day": ('number', 5, None, None, None, 'Enables alternate speeds scheduling these days.'),
"alt-speed-up": ('number', 5, None, None, None, 'Alternate session upload speed limit (in Kib/s).'),
"blocklist-enabled": ('boolean', 5, None, None, None, 'Enables the block list'),
"blocklist-url": ('string', 11, None, None, None, 'Location of the block list. Updated with blocklist-update.'),
"cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'),
"dht-enabled": ('boolean', 6, None, None, None, 'Enables DHT.'),
"download-dir": ('string', 1, None, None, None, 'Set the session download directory.'),
"download-queue-size": ('number', 14, None, None, None, 'Number of slots in the download queue.'),
"download-queue-enabled": ('boolean', 14, None, None, None, 'Enables download queue.'),
"encryption": ('string', 1, None, None, None, 'Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``.'),
"idle-seeding-limit": ('number', 10, None, None, None, 'The default seed inactivity limit in minutes.'),
"idle-seeding-limit-enabled": ('boolean', 10, None, None, None, 'Enables the default seed inactivity limit'),
"incomplete-dir": ('string', 7, None, None, None, 'The path to the directory of incomplete transfer data.'),
"incomplete-dir-enabled": ('boolean', 7, None, None, None, 'Enables the incomplete transfer data directory. Otherwise data for incomplete transfers are stored in the download target.'),
"lpd-enabled": ('boolean', 9, None, None, None, 'Enables local peer discovery for public torrents.'),
"peer-limit": ('number', 1, 5, None, 'peer-limit-global', 'Maximum number of peers.'),
"peer-limit-global": ('number', 5, None, 'peer-limit', None, 'Maximum number of peers.'),
"peer-limit-per-torrent": ('number', 5, None, None, None, 'Maximum number of peers per transfer.'),
"pex-allowed": ('boolean', 1, 5, None, 'pex-enabled', 'Allowing PEX in public torrents.'),
"pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'Allowing PEX in public torrents.'),
"port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
"peer-port": ('number', 5, None, 'port', None, 'Peer port.'),
"peer-port-random-on-start": ('boolean', 5, None, None, None, 'Enables randomized peer port on start of Transmission.'),
"port-forwarding-enabled": ('boolean', 1, None, None, None, 'Enables port forwarding.'),
"rename-partial-files": ('boolean', 8, None, None, None, 'Appends ".part" to incomplete files'),
"queue-stalled-minutes": ('number', 14, None, None, None, 'Number of minutes of idle that marks a transfer as stalled.'),
"queue-stalled-enabled": ('boolean', 14, None, None, None, 'Enable tracking of stalled transfers.'),
"script-torrent-done-enabled": ('boolean', 9, None, None, None, 'Whether or not to call the "done" script.'),
"script-torrent-done-filename": ('string', 9, None, None, None, 'Filename of the script to run when the transfer is done.'),
"seed-queue-size": ('number', 14, None, None, None, 'Number of slots in the upload queue.'),
"seed-queue-enabled": ('boolean', 14, None, None, None, 'Enables upload queue.'),
"seedRatioLimit": ('double', 5, None, None, None, 'Seed ratio limit. 1.0 means 1:1 download and upload ratio.'),
"seedRatioLimited": ('boolean', 5, None, None, None, 'Enables seed ration limit.'),
"speed-limit-down": ('number', 1, None, None, None, 'Download speed limit (in Kib/s).'),
"speed-limit-down-enabled": ('boolean', 1, None, None, None, 'Enables download speed limiting.'),
"speed-limit-up": ('number', 1, None, None, None, 'Upload speed limit (in Kib/s).'),
"speed-limit-up-enabled": ('boolean', 1, None, None, None, 'Enables upload speed limiting.'),
"start-added-torrents": ('boolean', 9, None, None, None, 'Added torrents will be started right away.'),
"trash-original-torrent-files": ('boolean', 9, None, None, None, 'The .torrent file of added torrents will be deleted.'),
'utp-enabled': ('boolean', 13, None, None, None, 'Enables Micro Transport Protocol (UTP).'),
"alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
"alt-speed-enabled": ('boolean', 5, None, None, None, 'Enables alternate global download speed limiter.'),
"alt-speed-time-begin": (
'number', 5, None, None, None, 'Time when alternate speeds should be enabled. Minutes after midnight.'),
"alt-speed-time-enabled": ('boolean', 5, None, None, None, 'Enables alternate speeds scheduling.'),
"alt-speed-time-end": (
'number', 5, None, None, None, 'Time when alternate speeds should be disabled. Minutes after midnight.'),
"alt-speed-time-day": ('number', 5, None, None, None, 'Enables alternate speeds scheduling these days.'),
"alt-speed-up": ('number', 5, None, None, None, 'Alternate session upload speed limit (in Kib/s).'),
"blocklist-enabled": ('boolean', 5, None, None, None, 'Enables the block list'),
"blocklist-url": ('string', 11, None, None, None, 'Location of the block list. Updated with blocklist-update.'),
"cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'),
"dht-enabled": ('boolean', 6, None, None, None, 'Enables DHT.'),
"download-dir": ('string', 1, None, None, None, 'Set the session download directory.'),
"download-queue-size": ('number', 14, None, None, None, 'Number of slots in the download queue.'),
"download-queue-enabled": ('boolean', 14, None, None, None, 'Enables download queue.'),
"encryption": ('string', 1, None, None, None,
'Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``.'),
"idle-seeding-limit": ('number', 10, None, None, None, 'The default seed inactivity limit in minutes.'),
"idle-seeding-limit-enabled": ('boolean', 10, None, None, None, 'Enables the default seed inactivity limit'),
"incomplete-dir": ('string', 7, None, None, None, 'The path to the directory of incomplete transfer data.'),
"incomplete-dir-enabled": ('boolean', 7, None, None, None,
'Enables the incomplete transfer data directory. Otherwise data for incomplete transfers are stored in the download target.'),
"lpd-enabled": ('boolean', 9, None, None, None, 'Enables local peer discovery for public torrents.'),
"peer-limit": ('number', 1, 5, None, 'peer-limit-global', 'Maximum number of peers.'),
"peer-limit-global": ('number', 5, None, 'peer-limit', None, 'Maximum number of peers.'),
"peer-limit-per-torrent": ('number', 5, None, None, None, 'Maximum number of peers per transfer.'),
"pex-allowed": ('boolean', 1, 5, None, 'pex-enabled', 'Allowing PEX in public torrents.'),
"pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'Allowing PEX in public torrents.'),
"port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
"peer-port": ('number', 5, None, 'port', None, 'Peer port.'),
"peer-port-random-on-start": (
'boolean', 5, None, None, None, 'Enables randomized peer port on start of Transmission.'),
"port-forwarding-enabled": ('boolean', 1, None, None, None, 'Enables port forwarding.'),
"rename-partial-files": ('boolean', 8, None, None, None, 'Appends ".part" to incomplete files'),
"queue-stalled-minutes": (
'number', 14, None, None, None, 'Number of minutes of idle that marks a transfer as stalled.'),
"queue-stalled-enabled": ('boolean', 14, None, None, None, 'Enable tracking of stalled transfers.'),
"script-torrent-done-enabled": ('boolean', 9, None, None, None, 'Whether or not to call the "done" script.'),
"script-torrent-done-filename": (
'string', 9, None, None, None, 'Filename of the script to run when the transfer is done.'),
"seed-queue-size": ('number', 14, None, None, None, 'Number of slots in the upload queue.'),
"seed-queue-enabled": ('boolean', 14, None, None, None, 'Enables upload queue.'),
"seedRatioLimit": ('double', 5, None, None, None, 'Seed ratio limit. 1.0 means 1:1 download and upload ratio.'),
"seedRatioLimited": ('boolean', 5, None, None, None, 'Enables seed ration limit.'),
"speed-limit-down": ('number', 1, None, None, None, 'Download speed limit (in Kib/s).'),
"speed-limit-down-enabled": ('boolean', 1, None, None, None, 'Enables download speed limiting.'),
"speed-limit-up": ('number', 1, None, None, None, 'Upload speed limit (in Kib/s).'),
"speed-limit-up-enabled": ('boolean', 1, None, None, None, 'Enables upload speed limiting.'),
"start-added-torrents": ('boolean', 9, None, None, None, 'Added torrents will be started right away.'),
"trash-original-torrent-files": (
'boolean', 9, None, None, None, 'The .torrent file of added torrents will be deleted.'),
'utp-enabled': ('boolean', 13, None, None, None, 'Enables Micro Transport Protocol (UTP).'),
},
}

View file

@ -4,11 +4,13 @@
from core.transmissionrpc.six import string_types, integer_types
class TransmissionError(Exception):
"""
This exception is raised when there has occurred an error related to
communication with Transmission. It is a subclass of Exception.
This exception is raised when there has occurred an error related to
communication with Transmission. It is a subclass of Exception.
"""
def __init__(self, message='', original=None):
Exception.__init__(self)
self.message = message
@ -21,11 +23,13 @@ class TransmissionError(Exception):
else:
return self.message
class HTTPHandlerError(Exception):
"""
This exception is raised when there has occurred an error related to
the HTTP handler. It is a subclass of Exception.
This exception is raised when there has occurred an error related to
the HTTP handler. It is a subclass of Exception.
"""
def __init__(self, httpurl=None, httpcode=None, httpmsg=None, httpheaders=None, httpdata=None):
Exception.__init__(self)
self.url = ''

View file

@ -18,10 +18,12 @@ else:
from urllib2 import HTTPError, URLError
from httplib import BadStatusLine
class HTTPHandler(object):
"""
Prototype for HTTP handling.
"""
def set_authentication(self, uri, login, password):
"""
Transmission use basic authentication in earlier versions and digest
@ -44,10 +46,12 @@ class HTTPHandler(object):
"""
raise NotImplementedError("Bad HTTPHandler, failed to implement request.")
class DefaultHTTPHandler(HTTPHandler):
"""
The default HTTP handler provided with transmissionrpc.
"""
def __init__(self):
HTTPHandler.__init__(self)
self.http_opener = build_opener()

View file

@ -6,6 +6,7 @@ from core.transmissionrpc.utils import Field
from core.transmissionrpc.six import iteritems, integer_types
class Session(object):
"""
Session is a class holding the session data for a Transmission daemon.

View file

@ -28,7 +28,6 @@ import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.4.1"
# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
@ -56,6 +55,8 @@ else:
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
@ -79,7 +80,6 @@ def _import_module(name):
class _LazyDescr(object):
def __init__(self, name):
self.name = name
@ -92,7 +92,6 @@ class _LazyDescr(object):
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
@ -107,7 +106,6 @@ class MovedModule(_LazyDescr):
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
@ -131,7 +129,6 @@ class MovedAttribute(_LazyDescr):
return getattr(module, self.attr)
class _MovedItems(types.ModuleType):
"""Lazy loading of moved objects"""
@ -199,7 +196,6 @@ del attr
moves = sys.modules[__name__ + ".moves"] = _MovedItems(__name__ + ".moves")
class Module_six_moves_urllib_parse(types.ModuleType):
"""Lazy loading of moved objects in six.moves.urllib_parse"""
@ -320,8 +316,10 @@ for attr in _urllib_robotparser_moved_attributes:
setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
del attr
sys.modules[__name__ + ".moves.urllib_robotparser"] = Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib_robotparser")
sys.modules[__name__ + ".moves.urllib.robotparser"] = Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser")
sys.modules[__name__ + ".moves.urllib_robotparser"] = Module_six_moves_urllib_robotparser(
__name__ + ".moves.urllib_robotparser")
sys.modules[__name__ + ".moves.urllib.robotparser"] = Module_six_moves_urllib_robotparser(
__name__ + ".moves.urllib.robotparser")
class Module_six_moves_urllib(types.ModuleType):
@ -379,7 +377,6 @@ else:
_iteritems = "iteritems"
_iterlists = "iterlists"
try:
advance_iterator = next
except NameError:
@ -387,18 +384,17 @@ except NameError:
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
if PY3:
def get_unbound_function(unbound):
return unbound
create_bound_method = types.MethodType
Iterator = object
@ -406,19 +402,21 @@ else:
def get_unbound_function(unbound):
return unbound.im_func
def create_bound_method(func, obj):
return types.MethodType(func, obj, obj.__class__)
class Iterator(object):
def next(self):
return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_closure = operator.attrgetter(_func_closure)
@ -431,14 +429,17 @@ def iterkeys(d, **kw):
"""Return an iterator over the keys of a dictionary."""
return iter(getattr(d, _iterkeys)(**kw))
def itervalues(d, **kw):
"""Return an iterator over the values of a dictionary."""
return iter(getattr(d, _itervalues)(**kw))
def iteritems(d, **kw):
"""Return an iterator over the (key, value) pairs of a dictionary."""
return iter(getattr(d, _iteritems)(**kw))
def iterlists(d, **kw):
"""Return an iterator over the (key, [values]) pairs of a dictionary."""
return iter(getattr(d, _iterlists)(**kw))
@ -447,8 +448,12 @@ def iterlists(d, **kw):
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
unichr = chr
if sys.version_info[1] <= 1:
def int2byte(i):
@ -460,29 +465,43 @@ if PY3:
indexbytes = operator.getitem
iterbytes = iter
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
else:
def b(s):
return s
def u(s):
return unicode(s, "unicode_escape")
unichr = unichr
int2byte = chr
def byte2int(bs):
return ord(bs[0])
def indexbytes(buf, i):
return ord(buf[i])
def iterbytes(buf):
return (ord(byte) for byte in buf)
import StringIO
StringIO = BytesIO = StringIO.StringIO
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
if PY3:
import builtins
exec_ = getattr(builtins, "exec")
@ -506,7 +525,7 @@ else:
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
exec ("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
@ -519,10 +538,12 @@ else:
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
@ -566,8 +587,10 @@ def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
return meta("NewBase", bases, {})
def add_metaclass(metaclass):
"""Class decorator for creating a class with a metaclass."""
def wrapper(cls):
orig_vars = cls.__dict__.copy()
orig_vars.pop('__dict__', None)
@ -575,4 +598,5 @@ def add_metaclass(metaclass):
for slots_var in orig_vars.get('__slots__', ()):
orig_vars.pop(slots_var)
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper

View file

@ -13,14 +13,15 @@ from six import integer_types, string_types, text_type, iteritems
def get_status_old(code):
"""Get the torrent status using old status codes"""
mapping = {
(1<<0): 'check pending',
(1<<1): 'checking',
(1<<2): 'downloading',
(1<<3): 'seeding',
(1<<4): 'stopped',
(1 << 0): 'check pending',
(1 << 1): 'checking',
(1 << 2): 'downloading',
(1 << 3): 'seeding',
(1 << 4): 'stopped',
}
return mapping[code]
def get_status_new(code):
"""Get the torrent status using new status codes"""
mapping = {
@ -34,6 +35,7 @@ def get_status_new(code):
}
return mapping[code]
class Torrent(object):
"""
Torrent is a class holding the data received from Transmission regarding a bittorrent transfer.
@ -99,8 +101,9 @@ class Torrent(object):
def _dirty_fields(self):
"""Enumerate changed fields"""
outgoing_keys = ['bandwidthPriority', 'downloadLimit', 'downloadLimited', 'peer_limit', 'queuePosition'
, 'seedIdleLimit', 'seedIdleMode', 'seedRatioLimit', 'seedRatioMode', 'uploadLimit', 'uploadLimited']
outgoing_keys = ['bandwidthPriority', 'downloadLimit', 'downloadLimited', 'peer_limit', 'queuePosition',
'seedIdleLimit', 'seedIdleMode', 'seedRatioLimit', 'seedRatioMode', 'uploadLimit',
'uploadLimited']
fields = []
for key in outgoing_keys:
if key in self._fields and self._fields[key].dirty:
@ -131,7 +134,7 @@ class Torrent(object):
else:
raise ValueError('Cannot update with supplied data')
self._incoming_pending = False
def _status(self):
"""Get the torrent status"""
code = self._fields['status'].value
@ -270,7 +273,8 @@ class Torrent(object):
else:
raise ValueError("Not a valid limit")
download_limit = property(_get_download_limit, _set_download_limit, None, "Download limit in Kbps or None. This is a mutator.")
download_limit = property(_get_download_limit, _set_download_limit, None,
"Download limit in Kbps or None. This is a mutator.")
def _get_peer_limit(self):
"""
@ -307,7 +311,7 @@ class Torrent(object):
self._push()
priority = property(_get_priority, _set_priority, None
, "Bandwidth priority as string. Can be one of 'low', 'normal', 'high'. This is a mutator.")
, "Bandwidth priority as string. Can be one of 'low', 'normal', 'high'. This is a mutator.")
def _get_seed_idle_limit(self):
"""
@ -326,7 +330,7 @@ class Torrent(object):
raise ValueError("Not a valid limit")
seed_idle_limit = property(_get_seed_idle_limit, _set_seed_idle_limit, None
, "Torrent seed idle limit in minutes. Also see seed_idle_mode. This is a mutator.")
, "Torrent seed idle limit in minutes. Also see seed_idle_mode. This is a mutator.")
def _get_seed_idle_mode(self):
"""
@ -345,7 +349,7 @@ class Torrent(object):
raise ValueError("Not a valid limit")
seed_idle_mode = property(_get_seed_idle_mode, _set_seed_idle_mode, None,
"""
"""
Seed idle mode as string. Can be one of 'global', 'single' or 'unlimited'.
* global, use session seed idle limit.
@ -354,7 +358,7 @@ class Torrent(object):
This is a mutator.
"""
)
)
def _get_seed_ratio_limit(self):
"""
@ -373,7 +377,7 @@ class Torrent(object):
raise ValueError("Not a valid limit")
seed_ratio_limit = property(_get_seed_ratio_limit, _set_seed_ratio_limit, None
, "Torrent seed ratio limit as float. Also see seed_ratio_mode. This is a mutator.")
, "Torrent seed ratio limit as float. Also see seed_ratio_mode. This is a mutator.")
def _get_seed_ratio_mode(self):
"""
@ -392,7 +396,7 @@ class Torrent(object):
raise ValueError("Not a valid limit")
seed_ratio_mode = property(_get_seed_ratio_mode, _set_seed_ratio_mode, None,
"""
"""
Seed ratio mode as string. Can be one of 'global', 'single' or 'unlimited'.
* global, use session seed ratio limit.
@ -401,7 +405,7 @@ class Torrent(object):
This is a mutator.
"""
)
)
def _get_upload_limit(self):
"""
@ -428,7 +432,8 @@ class Torrent(object):
else:
raise ValueError("Not a valid limit")
upload_limit = property(_get_upload_limit, _set_upload_limit, None, "Upload limit in Kbps or None. This is a mutator.")
upload_limit = property(_get_upload_limit, _set_upload_limit, None,
"Upload limit in Kbps or None. This is a mutator.")
def _get_queue_position(self):
"""Get the queue position for this torrent."""

View file

@ -10,6 +10,7 @@ from six import string_types, iteritems
UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']
def format_size(size):
"""
Format byte size into IEC prefixes, B, KiB, MiB ...
@ -21,6 +22,7 @@ def format_size(size):
size /= 1024.0
return (size, UNITS[i])
def format_speed(size):
"""
Format bytes per second speed into IEC prefixes, B/s, KiB/s, MiB/s ...
@ -28,6 +30,7 @@ def format_speed(size):
(size, unit) = format_size(size)
return (size, unit + '/s')
def format_timedelta(delta):
"""
Format datetime.timedelta into <days> <hours>:<minutes>:<seconds>.
@ -36,6 +39,7 @@ def format_timedelta(delta):
hours, minutes = divmod(minutes, 60)
return '%d %02d:%02d:%02d' % (delta.days, hours, minutes, seconds)
def format_timestamp(timestamp, utc=False):
"""
Format unix timestamp into ISO date format.
@ -49,12 +53,14 @@ def format_timestamp(timestamp, utc=False):
else:
return '-'
class INetAddressError(Exception):
"""
Error parsing / generating a internet address.
"""
pass
def inet_address(address, default_port, default_address='localhost'):
"""
Parse internet address.
@ -84,6 +90,7 @@ def inet_address(address, default_port, default_address='localhost'):
raise INetAddressError('Cannot look up address "%s".' % address)
return (addr, port)
def rpc_bool(arg):
"""
Convert between Python boolean and Transmission RPC boolean.
@ -95,27 +102,31 @@ def rpc_bool(arg):
arg = arg.lower() in ['true', 'yes']
return 1 if bool(arg) else 0
TR_TYPE_MAP = {
'number' : int,
'string' : str,
'number': int,
'string': str,
'double': float,
'boolean' : rpc_bool,
'boolean': rpc_bool,
'array': list,
'object': dict
}
def make_python_name(name):
"""
Convert Transmission RPC name to python compatible name.
"""
return name.replace('-', '_')
def make_rpc_name(name):
"""
Convert python compatible name to Transmission RPC name.
"""
return name.replace('_', '-')
def argument_value_convert(method, argument, value, rpc_version):
"""
Check and fix Transmission RPC issues with regards to methods, arguments and values.
@ -154,6 +165,7 @@ def argument_value_convert(method, argument, value, rpc_version):
raise ValueError('Argument "%s" does not exists for method "%s".',
(argument, method))
def get_arguments(method, rpc_version):
"""
Get arguments for method in specified Transmission RPC version.
@ -175,6 +187,7 @@ def get_arguments(method, rpc_version):
accessible.append(argument)
return accessible
def add_stdout_logger(level='debug'):
"""
Add a stdout target for the transmissionrpc logging.
@ -189,6 +202,7 @@ def add_stdout_logger(level='debug'):
loghandler.setLevel(loglevel)
trpc_logger.addHandler(loghandler)
def add_file_logger(filepath, level='debug'):
"""
Add a stdout target for the transmissionrpc logging.
@ -203,4 +217,5 @@ def add_file_logger(filepath, level='debug'):
loghandler.setLevel(loglevel)
trpc_logger.addHandler(loghandler)
Field = namedtuple('Field', ['value', 'dirty'])

View file

@ -1 +1 @@
# coding=utf-8
# coding=utf-8

View file

@ -1,29 +1,31 @@
#coding=utf8
# coding=utf8
import urllib
import urllib2
import urlparse
import cookielib
import re
import StringIO
try:
import json
import json
except ImportError:
import simplejson as json
from core.utorrent.upload import MultiPartForm
class UTorrentClient(object):
class UTorrentClient(object):
def __init__(self, base_url, username, password):
self.base_url = base_url
self.username = username
self.password = password
self.opener = self._make_opener('uTorrent', base_url, username, password)
self.token = self._get_token()
#TODO refresh token, when necessary
# TODO refresh token, when necessary
def _make_opener(self, realm, base_url, username, password):
'''uTorrent API need HTTP Basic Auth and cookie support for token verify.'''
"""uTorrent API need HTTP Basic Auth and cookie support for token verify."""
auth_handler = urllib2.HTTPBasicAuthHandler()
auth_handler.add_password(realm=realm,
@ -31,7 +33,7 @@ class UTorrentClient(object):
user=username,
passwd=password)
opener = urllib2.build_opener(auth_handler)
urllib2.install_opener(opener)
urllib2.install_opener(opener)
cookie_jar = cookielib.CookieJar()
cookie_handler = urllib2.HTTPCookieProcessor(cookie_jar)
@ -47,69 +49,68 @@ class UTorrentClient(object):
match = re.search(token_re, response.read())
return match.group(1)
def list(self, **kwargs):
params = [('list', '1')]
params += kwargs.items()
return self._action(params)
def start(self, *hashes):
params = [('action', 'start'),]
params = [('action', 'start'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
def stop(self, *hashes):
params = [('action', 'stop'),]
params = [('action', 'stop'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
def pause(self, *hashes):
params = [('action', 'pause'),]
params = [('action', 'pause'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
def forcestart(self, *hashes):
params = [('action', 'forcestart'),]
params = [('action', 'forcestart'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
def remove(self, *hashes):
params = [('action', 'remove'),]
params = [('action', 'remove'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
def removedata(self, *hashes):
params = [('action', 'removedata'),]
params = [('action', 'removedata'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
def recheck(self, *hashes):
params = [('action', 'recheck'),]
params = [('action', 'recheck'), ]
for hash in hashes:
params.append(('hash', hash))
return self._action(params)
def getfiles(self, hash):
params = [('action', 'getfiles'), ('hash', hash)]
return self._action(params)
def getprops(self, hash):
params = [('action', 'getprops'), ('hash', hash)]
return self._action(params)
def setprio(self, hash, priority, *files):
params = [('action', 'setprio'), ('hash', hash), ('p', str(priority))]
for file_index in files:
params.append(('f', str(file_index)))
return self._action(params)
def addfile(self, filename, filepath=None, bytes=None):
params = [('action', 'add-file')]
@ -118,13 +119,13 @@ class UTorrentClient(object):
file_handler = open(filepath)
else:
file_handler = StringIO.StringIO(bytes)
form.add_file('torrent_file', filename.encode('utf-8'), file_handler)
return self._action(params, str(form), form.get_content_type())
def _action(self, params, body=None, content_type=None):
#about token, see https://github.com/bittorrent/webui/wiki/TokenSystem
# about token, see https://github.com/bittorrent/webui/wiki/TokenSystem
url = self.base_url + '?token=' + self.token + '&' + urllib.urlencode(params)
request = urllib2.Request(url)
@ -137,6 +138,5 @@ class UTorrentClient(object):
try:
response = self.opener.open(request)
return response.code, json.loads(response.read())
except urllib2.HTTPError,e:
raise
except urllib2.HTTPError, e:
raise

View file

@ -1,5 +1,5 @@
# coding=utf-8
#code copied from http://www.doughellmann.com/PyMOTW/urllib2/
# code copied from http://www.doughellmann.com/PyMOTW/urllib2/
import itertools
import mimetools
@ -14,7 +14,7 @@ class MultiPartForm(object):
self.files = []
self.boundary = mimetools.choose_boundary()
return
def get_content_type(self):
return 'multipart/form-data; boundary=%s' % self.boundary
@ -30,7 +30,7 @@ class MultiPartForm(object):
mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
self.files.append((fieldname, filename, mimetype, body))
return
def __str__(self):
"""Return a string representing the form data, including attached files."""
# Build a list of lists, each containing "lines" of the
@ -39,29 +39,28 @@ class MultiPartForm(object):
# line is separated by '\r\n'.
parts = []
part_boundary = '--' + self.boundary
# Add the form fields
parts.extend(
[ part_boundary,
'Content-Disposition: form-data; name="%s"' % name,
'',
value,
]
[part_boundary,
'Content-Disposition: form-data; name="%s"' % name,
'',
value,
]
for name, value in self.form_fields
)
)
# Add the files to upload
parts.extend(
[ part_boundary,
'Content-Disposition: file; name="%s"; filename="%s"' % \
(field_name, filename),
'Content-Type: %s' % content_type,
'',
body,
]
[part_boundary,
'Content-Disposition: file; name="%s"; filename="%s"' % (field_name, filename),
'Content-Type: %s' % content_type,
'',
body,
]
for field_name, filename, content_type, body in self.files
)
)
# Flatten the list and add closing boundary marker,
# then return CR+LF separated data
flattened = list(itertools.chain(*parts))

View file

@ -16,6 +16,7 @@ import gh_api as github
import core
from core import logger
class CheckVersion():
"""
Version check class meant to run as a thread object with the SB scheduler.
@ -80,6 +81,7 @@ class CheckVersion():
if self.updater.need_update():
return self.updater.update()
class UpdateManager():
def get_github_repo_user(self):
return core.GIT_USER
@ -90,6 +92,7 @@ class UpdateManager():
def get_github_branch(self):
return core.GIT_BRANCH
class GitUpdateManager(UpdateManager):
def __init__(self):
self._git_path = self._find_working_git()
@ -103,7 +106,8 @@ class GitUpdateManager(UpdateManager):
self._num_commits_ahead = 0
def _git_error(self):
logger.debug('Unable to find your git executable - Set git_path in your autoProcessMedia.cfg OR delete your .git folder and run from source to enable updates.')
logger.debug(
'Unable to find your git executable - Set git_path in your autoProcessMedia.cfg OR delete your .git folder and run from source to enable updates.')
def _find_working_git(self):
test_cmd = 'version'
@ -148,7 +152,8 @@ class GitUpdateManager(UpdateManager):
logger.log(u"Not using: " + cur_git, logger.DEBUG)
# Still haven't found a working git
logger.debug('Unable to find your git executable - Set git_path in your autoProcessMedia.cfg OR delete your .git folder and run from source to enable updates.')
logger.debug(
'Unable to find your git executable - Set git_path in your autoProcessMedia.cfg OR delete your .git folder and run from source to enable updates.')
return None
@ -279,9 +284,10 @@ class GitUpdateManager(UpdateManager):
logger.log(u"git didn't return numbers for behind and ahead, not using it", logger.DEBUG)
return
logger.log(u"cur_commit = " + str(self._cur_commit_hash) + u" % (newest_commit)= " + str(self._newest_commit_hash)
+ u", num_commits_behind = " + str(self._num_commits_behind) + u", num_commits_ahead = " + str(
self._num_commits_ahead), logger.DEBUG)
logger.log(
u"cur_commit = " + str(self._cur_commit_hash) + u" % (newest_commit)= " + str(self._newest_commit_hash) +
u", num_commits_behind = " + str(self._num_commits_behind) + u", num_commits_ahead = " +
str(self._num_commits_ahead), logger.DEBUG)
def set_newest_text(self):
if self._num_commits_ahead:
@ -411,8 +417,9 @@ class SourceUpdateManager(UpdateManager):
# when _cur_commit_hash doesn't match anything _num_commits_behind == 100
self._num_commits_behind += 1
logger.log(u"cur_commit = " + str(self._cur_commit_hash) + u" % (newest_commit)= " + str(self._newest_commit_hash)
+ u", num_commits_behind = " + str(self._num_commits_behind), logger.DEBUG)
logger.log(
u"cur_commit = " + str(self._cur_commit_hash) + u" % (newest_commit)= " + str(self._newest_commit_hash) +
u", num_commits_behind = " + str(self._num_commits_behind), logger.DEBUG)
def set_newest_text(self):
@ -489,9 +496,9 @@ class SourceUpdateManager(UpdateManager):
old_path = os.path.join(content_dir, dirname, curfile)
new_path = os.path.join(core.PROGRAM_DIR, dirname, curfile)
#Avoid DLL access problem on WIN32/64
#These files needing to be updated manually
#or find a way to kill the access from memory
# Avoid DLL access problem on WIN32/64
# These files needing to be updated manually
# or find a way to kill the access from memory
if curfile in ('unrar.dll', 'unrar64.dll'):
try:
os.chmod(new_path, stat.S_IWRITE)
@ -519,4 +526,4 @@ class SourceUpdateManager(UpdateManager):
logger.log(u"Traceback: " + traceback.format_exc(), logger.DEBUG)
return False
return True
return True