diff --git a/Headphones.py b/Headphones.py index 18bb7970..a3896a0f 100644 --- a/Headphones.py +++ b/Headphones.py @@ -41,6 +41,7 @@ def main(): headphones.ARGS = sys.argv[1:] # From sickbeard + headphones.SYS_PLATFORM = sys.platform headphones.SYS_ENCODING = None try: @@ -116,7 +117,7 @@ def main(): # Force the http port if neccessary if args.port: http_port = args.port - logger.info('Starting Headphones on foced port: %i' % http_port) + logger.info('Starting Headphones on forced port: %i' % http_port) else: http_port = int(headphones.HTTP_PORT) @@ -140,7 +141,10 @@ def main(): while True: if not headphones.SIGNAL: - time.sleep(1) + try: + time.sleep(1) + except KeyboardInterrupt: + headphones.SIGNAL = 'shutdown' else: logger.info('Received signal: ' + headphones.SIGNAL) if headphones.SIGNAL == 'shutdown': diff --git a/data/interfaces/brink/manage.html b/data/interfaces/brink/manage.html index 3434e363..1b31bdf8 100644 --- a/data/interfaces/brink/manage.html +++ b/data/interfaces/brink/manage.html @@ -43,6 +43,11 @@ %endif + + + + + @@ -97,4 +102,4 @@ --> - \ No newline at end of file + diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 532e02f2..a3f994cb 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -85,7 +85,6 @@ mins -
mins @@ -417,7 +416,7 @@
Re-Encoding Options - Note: this option requires the lame or ffmpeg encoder + Note: this option requires the lame, ffmpeg or xld encoder
@@ -434,35 +433,42 @@ if config['encoder'] == 'lame': lameselect = 'selected="selected"' ffmpegselect = '' - else: + xldselect = '' + elif config['encoder'] == 'ffmpeg': lameselect = '' ffmpegselect = 'selected="selected"' + xldselect = '' + else: + lameselect = '' + ffmpegselect = '' + xldselect = 'selected="selected"' %>
- +
-
- - -
- +
Audio Properties +
+ + +
-
+ +
+
+
+
+ + +
+
+
- - - @@ -760,7 +772,12 @@ $("#hpserveroptions").slideUp(); }; - handleNewSelection = function () { + hideEncoderDivs = function () { + $("#lameffmpegproperties").slideUp(); + $("#xldproperties").slideUp(); + }; + + handleNewServerSelection = function () { hideServerDivs(); @@ -774,6 +791,23 @@ } }; + handleNewEncoderSelection = function () { + + hideEncoderDivs(); + + switch ($(this).val()) { + case 'xld': + $("#xldproperties").slideDown(); + break; + case 'ffmpeg': + $("#lameffmpegproperties").slideDown(); + break; + case 'lame': + $("#lameffmpegproperties").slideDown(); + break; + } + }; + function openExtrasDialog() { $("#dialog").dialog({ close: function(){ var elem = ''; @@ -914,8 +948,11 @@ } }); - $("#mirror").change(handleNewSelection); - handleNewSelection.apply($("#mirror")); + $("#mirror").change(handleNewServerSelection); + handleNewServerSelection.apply($("#mirror")); + + $("#encoder").change(handleNewEncoderSelection); + handleNewEncoderSelection.apply($("#encoder")); var deletedNewznabs = 0; diff --git a/data/interfaces/default/manage.html b/data/interfaces/default/manage.html index 1b833dfe..66405aab 100644 --- a/data/interfaces/default/manage.html +++ b/data/interfaces/default/manage.html @@ -58,10 +58,14 @@ %endif
- + +
+
+
+
diff --git a/headphones/__init__.py b/headphones/__init__.py index 30fa5902..3f27c709 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -36,6 +36,7 @@ PROG_DIR = None ARGS = None SIGNAL = None +SYS_PLATFORM = None SYS_ENCODING = None VERBOSE = 1 @@ -111,6 +112,7 @@ AUTOWANT_UPCOMING = False AUTOWANT_ALL = False SEARCH_INTERVAL = 360 +LIBRARYSCAN = False LIBRARYSCAN_INTERVAL = 300 DOWNLOAD_SCAN_INTERVAL = 5 @@ -168,6 +170,7 @@ FOLDER_PERMISSIONS = None MUSIC_ENCODER = False ENCODERFOLDER = None ENCODER = None +XLDPROFILE = None BITRATE = None SAMPLINGFREQUENCY = None ADVANCEDENCODER = None @@ -250,7 +253,7 @@ def initialize(): with INIT_LOCK: - global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \ + global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, SYS_PLATFORM, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \ HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, HTTP_PROXY, LAUNCH_BROWSER, API_ENABLED, API_KEY, GIT_PATH, \ CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, MUSIC_DIR, DESTINATION_DIR, \ LOSSLESS_DESTINATION_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, ADD_ARTISTS, CORRECT_METADATA, MOVE_FILES, \ @@ -258,10 +261,10 @@ def initialize(): ADD_ALBUM_ART, EMBED_ALBUM_ART, EMBED_LYRICS, DOWNLOAD_DIR, BLACKHOLE, BLACKHOLE_DIR, USENET_RETENTION, SEARCH_INTERVAL, \ TORRENTBLACKHOLE_DIR, NUMBEROFSEEDERS, ISOHUNT, KAT, MININOVA, WAFFLES, WAFFLES_UID, WAFFLES_PASSKEY, \ RUTRACKER, RUTRACKER_USER, RUTRACKER_PASSWORD, WHATCD, WHATCD_USERNAME, WHATCD_PASSWORD, DOWNLOAD_TORRENT_DIR, \ - LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \ + LIBRARYSCAN, LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \ NZBMATRIX, NZBMATRIX_USERNAME, NZBMATRIX_APIKEY, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, NEWZNAB_ENABLED, EXTRA_NEWZNABS,\ NZBSORG, NZBSORG_UID, NZBSORG_HASH, NEWZBIN, NEWZBIN_UID, NEWZBIN_PASSWORD, LASTFM_USERNAME, INTERFACE, FOLDER_PERMISSIONS, \ - ENCODERFOLDER, ENCODER, BITRATE, SAMPLINGFREQUENCY, MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, \ + ENCODERFOLDER, ENCODER, XLDPROFILE, BITRATE, SAMPLINGFREQUENCY, MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, \ ENCODERVBRCBR, ENCODERLOSSLESS, DELETE_LOSSLESS_FILES, PROWL_ENABLED, PROWL_PRIORITY, PROWL_KEYS, PROWL_ONSNATCH, MIRRORLIST, \ MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \ XBMC_NOTIFY, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \ @@ -341,6 +344,7 @@ def initialize(): AUTOWANT_ALL = bool(check_setting_int(CFG, 'General', 'autowant_all', 0)) SEARCH_INTERVAL = check_setting_int(CFG, 'General', 'search_interval', 360) + LIBRARYSCAN = bool(check_setting_int(CFG, 'General', 'libraryscan', 1)) LIBRARYSCAN_INTERVAL = check_setting_int(CFG, 'General', 'libraryscan_interval', 300) DOWNLOAD_SCAN_INTERVAL = check_setting_int(CFG, 'General', 'download_scan_interval', 5) @@ -397,6 +401,7 @@ def initialize(): ENCODERFOLDER = check_setting_str(CFG, 'General', 'encoderfolder', '') ENCODER = check_setting_str(CFG, 'General', 'encoder', 'ffmpeg') + XLDPROFILE = check_setting_str(CFG, 'General', 'xldprofile', '') BITRATE = check_setting_int(CFG, 'General', 'bitrate', 192) SAMPLINGFREQUENCY= check_setting_int(CFG, 'General', 'samplingfrequency', 44100) MUSIC_ENCODER = bool(check_setting_int(CFG, 'General', 'music_encoder', 0)) @@ -656,6 +661,7 @@ def config_write(): new_config['What.cd']['whatcd_password'] = WHATCD_PASSWORD new_config['General']['search_interval'] = SEARCH_INTERVAL + new_config['General']['libraryscan'] = int(LIBRARYSCAN) new_config['General']['libraryscan_interval'] = LIBRARYSCAN_INTERVAL new_config['General']['download_scan_interval'] = DOWNLOAD_SCAN_INTERVAL @@ -723,6 +729,7 @@ def config_write(): new_config['General']['music_encoder'] = int(MUSIC_ENCODER) new_config['General']['encoder'] = ENCODER + new_config['General']['xldprofile'] = XLDPROFILE new_config['General']['bitrate'] = int(BITRATE) new_config['General']['samplingfrequency'] = int(SAMPLINGFREQUENCY) new_config['General']['encoderfolder'] = ENCODERFOLDER @@ -756,9 +763,9 @@ def start(): # Start our scheduled background tasks from headphones import updater, searcher, librarysync, postprocessor - SCHED.add_interval_job(updater.dbUpdate, hours=48) + SCHED.add_interval_job(updater.dbUpdate, hours=24) SCHED.add_interval_job(searcher.searchforalbum, minutes=SEARCH_INTERVAL) - SCHED.add_interval_job(librarysync.libraryScan, minutes=LIBRARYSCAN_INTERVAL) + SCHED.add_interval_job(librarysync.libraryScan, minutes=LIBRARYSCAN_INTERVAL, kwargs={'cron':True}) if CHECK_GITHUB: SCHED.add_interval_job(versioncheck.checkGithub, minutes=CHECK_GITHUB_INTERVAL) diff --git a/headphones/getXldProfile.py b/headphones/getXldProfile.py new file mode 100755 index 00000000..e9d27015 --- /dev/null +++ b/headphones/getXldProfile.py @@ -0,0 +1,181 @@ +import os.path +import plistlib +import sys +import xml.parsers.expat as expat +import commands +from headphones import logger + +def getXldProfile(xldProfile): + xldProfileNotFound = xldProfile + expandedPath = os.path.expanduser('~/Library/Preferences/jp.tmkk.XLD.plist') + try: + preferences = plistlib.Plist.fromFile(expandedPath) + except (expat.ExpatError): + os.system("/usr/bin/plutil -convert xml1 %s" % expandedPath ) + try: + preferences = plistlib.Plist.fromFile(expandedPath) + except (ImportError): + os.system("/usr/bin/plutil -convert binary1 %s" % expandedPath ) + logger.info('The plist at "%s" has a date in it, and therefore is not useable.' % expandedPath) + return(xldProfileNotFound, None, None) + except (ImportError): + logger.info('The plist at "%s" has a date in it, and therefore is not useable.' % expandedPath) + except: + logger.info('Unexpected error:', sys.exc_info()[0]) + return(xldProfileNotFound, None, None) + + xldProfile = xldProfile.lower() + profiles = preferences.get('Profiles') + + for profile in profiles: + + profilename = profile.get('XLDProfileManager_ProfileName') + xldProfileForCmd = profilename + profilename = profilename.lower() + xldFormat = None + xldBitrate = None + + if profilename == xldProfile: + + OutputFormatName = profile.get('OutputFormatName') + ShortDesc = profile.get('ShortDesc') + + # Determine format and bitrate + + if OutputFormatName == 'WAV': + xldFormat = 'wav' + + elif OutputFormatName == 'AIFF': + xldFormat = 'aiff' + + elif 'PCM' in OutputFormatName: + xldFormat = 'pcm' + + elif OutputFormatName == 'Wave64': + xldFormat = 'w64' + + elif OutputFormatName == 'MPEG-4 AAC': + xldFormat = 'm4a' + if 'CBR' in ShortDesc or 'ABR' in ShortDesc or 'CVBR' in ShortDesc: + xldBitrate = int(profile.get('XLDAacOutput2_Bitrate')) + elif 'TVBR' in ShortDesc: + XLDAacOutput2_VBRQuality = int(profile.get('XLDAacOutput2_VBRQuality')) + if XLDAacOutput2_VBRQuality > 122: + xldBitrate = 320 + elif XLDAacOutput2_VBRQuality > 113 and XLDAacOutput2_VBRQuality <= 122: + xldBitrate = 285 + elif XLDAacOutput2_VBRQuality > 104 and XLDAacOutput2_VBRQuality <= 113: + xldBitrate = 255 + elif XLDAacOutput2_VBRQuality > 95 and XLDAacOutput2_VBRQuality <= 104: + xldBitrate = 225 + elif XLDAacOutput2_VBRQuality > 86 and XLDAacOutput2_VBRQuality <= 95: + xldBitrate = 195 + elif XLDAacOutput2_VBRQuality > 77 and XLDAacOutput2_VBRQuality <= 86: + xldBitrate = 165 + elif XLDAacOutput2_VBRQuality > 68 and XLDAacOutput2_VBRQuality <= 77: + xldBitrate = 150 + elif XLDAacOutput2_VBRQuality > 58 and XLDAacOutput2_VBRQuality <= 68: + xldBitrate = 135 + elif XLDAacOutput2_VBRQuality > 49 and XLDAacOutput2_VBRQuality <= 58: + xldBitrate = 115 + elif XLDAacOutput2_VBRQuality > 40 and XLDAacOutput2_VBRQuality <= 49: + xldBitrate = 105 + elif XLDAacOutput2_VBRQuality > 31 and XLDAacOutput2_VBRQuality <= 40: + xldBitrate = 95 + elif XLDAacOutput2_VBRQuality > 22 and XLDAacOutput2_VBRQuality <= 31: + xldBitrate = 80 + elif XLDAacOutput2_VBRQuality > 13 and XLDAacOutput2_VBRQuality <= 22: + xldBitrate = 75 + elif XLDAacOutput2_VBRQuality > 4 and XLDAacOutput2_VBRQuality <= 13: + xldBitrate = 45 + elif XLDAacOutput2_VBRQuality >= 0 and XLDAacOutput2_VBRQuality <= 4: + xldBitrate = 40 + + elif OutputFormatName == 'Apple Lossless': + xldFormat = 'm4a' + + elif OutputFormatName == 'FLAC': + if 'ogg' in ShortDesc: + xldFormat = 'oga' + else: + xldFormat = 'flac' + + elif OutputFormatName == 'MPEG-4 HE-AAC': + xldFormat = 'm4a' + xldBitrate = int(profile.get('Bitrate')) + + elif OutputFormatName == 'LAME MP3': + xldFormat = 'mp3' + if 'VBR' in ShortDesc: + VbrQuality = float(profile.get('VbrQuality')) + if VbrQuality < 1: + xldBitrate = 260 + elif VbrQuality >= 1 and VbrQuality < 2: + xldBitrate = 250 + elif VbrQuality >= 2 and VbrQuality < 3: + xldBitrate = 210 + elif VbrQuality >= 3 and VbrQuality < 4: + xldBitrate = 195 + elif VbrQuality >= 4 and VbrQuality < 5: + xldBitrate = 185 + elif VbrQuality >= 5 and VbrQuality < 6: + xldBitrate = 150 + elif VbrQuality >= 6 and VbrQuality < 7: + xldBitrate = 130 + elif VbrQuality >= 7 and VbrQuality < 8: + xldBitrate = 120 + elif VbrQuality >= 8 and VbrQuality < 9: + xldBitrate = 105 + elif VbrQuality >= 9: + xldBitrate = 85 + elif 'CBR' in ShortDesc: + xldBitrate = int(profile.get('Bitrate')) + elif 'ABR' in ShortDesc: + xldBitrate = int(profile.get('AbrBitrate')) + + elif OutputFormatName == 'Opus': + xldFormat = 'opus' + xldBitrate = int(profile.get('XLDOpusOutput_Bitrate')) + + elif OutputFormatName == 'Ogg Vorbis': + xldFormat = 'ogg' + XLDVorbisOutput_Quality = float(profile.get('XLDVorbisOutput_Quality')) + if XLDVorbisOutput_Quality <= -2: + xldBitrate = 32 + elif XLDVorbisOutput_Quality > -2 and XLDVorbisOutput_Quality <= -1: + xldBitrate = 48 + elif XLDVorbisOutput_Quality > -1 and XLDVorbisOutput_Quality <= 0: + xldBitrate = 64 + elif XLDVorbisOutput_Quality > 0 and XLDVorbisOutput_Quality <= 1: + xldBitrate = 80 + elif XLDVorbisOutput_Quality > 1 and XLDVorbisOutput_Quality <= 2: + xldBitrate = 96 + elif XLDVorbisOutput_Quality > 2 and XLDVorbisOutput_Quality <= 3: + xldBitrate = 112 + elif XLDVorbisOutput_Quality > 3 and XLDVorbisOutput_Quality <= 4: + xldBitrate = 128 + elif XLDVorbisOutput_Quality > 4 and XLDVorbisOutput_Quality <= 5: + xldBitrate = 160 + elif XLDVorbisOutput_Quality > 5 and XLDVorbisOutput_Quality <= 6: + xldBitrate = 192 + elif XLDVorbisOutput_Quality > 6 and XLDVorbisOutput_Quality <= 7: + xldBitrate = 224 + elif XLDVorbisOutput_Quality > 7 and XLDVorbisOutput_Quality <= 8: + xldBitrate = 256 + elif XLDVorbisOutput_Quality > 8 and XLDVorbisOutput_Quality <= 9: + xldBitrate = 320 + elif XLDVorbisOutput_Quality > 9: + xldBitrate = 500 + + elif OutputFormatName == 'WavPack': + xldFormat = 'wv' + if ShortDesc != 'normal': + xldBitrate = int(profile.get('XLDWavpackOutput_BitRate')) + + # Lossless + if xldFormat and not xldBitrate: + xldBitrate = 500 + + return(xldProfileForCmd, xldFormat, xldBitrate) + + return(xldProfileNotFound, None, None) \ No newline at end of file diff --git a/headphones/helpers.py b/headphones/helpers.py index 89fb20cc..298062ee 100644 --- a/headphones/helpers.py +++ b/headphones/helpers.py @@ -118,6 +118,20 @@ def now(): now = datetime.datetime.now() return now.strftime("%Y-%m-%d %H:%M:%S") +def get_age(date): + + try: + split_date = date.split('-') + except: + return False + + try: + days_old = int(split_date[0])*365 + int(split_date[1])*30 + int(split_date[2]) + except IndexError: + days_old = False + + return days_old + def bytes_to_mb(bytes): mb = int(bytes)/1048576 @@ -130,6 +144,10 @@ def mb_to_bytes(mb_str): return int(float(result.group(1))*1048576) def replace_all(text, dic): + + if not text: + return '' + for i, j in dic.iteritems(): text = text.replace(i, j) return text @@ -153,8 +171,6 @@ def cleanTitle(title): return title def extract_data(s): - - from headphones import logger #headphones default format pattern = re.compile(r'(?P.*?)\s\-\s(?P.*?)\s\[(?P.*?)\]', re.VERBOSE) @@ -165,8 +181,6 @@ def extract_data(s): album = match.group("album") year = match.group("year") return (name, album, year) - else: - logger.info("Couldn't parse " + s + " into a valid default format") #newzbin default format pattern = re.compile(r'(?P.*?)\s\-\s(?P.*?)\s\((?P\d+?\))', re.VERBOSE) @@ -177,8 +191,7 @@ def extract_data(s): year = match.group("year") return (name, album, year) else: - logger.info("Couldn't parse " + s + " into a valid Newbin format") - return (name, album, year) + return (None, None, None) def extract_logline(s): # Default log format @@ -257,3 +270,55 @@ def smartMove(src, dest, delete=True): return True except Exception, e: logger.warn('Error moving file %s: %s' % (filename.decode(headphones.SYS_ENCODING, 'replace'), str(e).decode(headphones.SYS_ENCODING, 'replace'))) + +######################### +#Sab renaming functions # +######################### + +# TODO: Grab config values from sab to know when these options are checked. For now we'll just iterate through all combinations + +def sab_replace_dots(name): + return name.replace('.',' ') +def sab_replace_spaces(name): + return name.replace(' ','_') + +def sab_sanitize_foldername(name): + """ Return foldername with dodgy chars converted to safe ones + Remove any leading and trailing dot and space characters + """ + CH_ILLEGAL = r'\/<>?*|"' + CH_LEGAL = r'++{}!@#`' + + FL_ILLEGAL = CH_ILLEGAL + ':\x92"' + FL_LEGAL = CH_LEGAL + "-''" + + uFL_ILLEGAL = FL_ILLEGAL.decode('latin-1') + uFL_LEGAL = FL_LEGAL.decode('latin-1') + + if not name: + return name + if isinstance(name, unicode): + illegal = uFL_ILLEGAL + legal = uFL_LEGAL + else: + illegal = FL_ILLEGAL + legal = FL_LEGAL + + lst = [] + for ch in name.strip(): + if ch in illegal: + ch = legal[illegal.find(ch)] + lst.append(ch) + else: + lst.append(ch) + name = ''.join(lst) + + name = name.strip('. ') + if not name: + name = 'unknown' + + #maxlen = cfg.folder_max_length() + #if len(name) > maxlen: + # name = name[:maxlen] + + return name diff --git a/headphones/importer.py b/headphones/importer.py index 158d703f..447ff908 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -358,23 +358,22 @@ def addArtisttoDB(artistid, extrasonly=False): if not rg_exists: - newValueDict['DateAdded']= helpers.today() + today = helpers.today() + + newValueDict['DateAdded']= today if headphones.AUTOWANT_ALL: newValueDict['Status'] = "Wanted" - elif album['ReleaseDate'] > helpers.today() and headphones.AUTOWANT_UPCOMING: + elif album['ReleaseDate'] > today and headphones.AUTOWANT_UPCOMING: + newValueDict['Status'] = "Wanted" + # Sometimes "new" albums are added to musicbrainz after their release date, so let's try to catch these + # The first test just makes sure we have year-month-day + elif helpers.get_age(album['ReleaseDate']) and helpers.get_age(today) - helpers.get_age(album['ReleaseDate']) < 21 and headphones.AUTOWANT_UPCOMING: newValueDict['Status'] = "Wanted" else: newValueDict['Status'] = "Skipped" myDB.upsert("albums", newValueDict, controlValueDict) - - #start a search for the album if it's new and autowant_all is selected: - # Should this run in a background thread? Don't know if we want to have a bunch of - # simultaneous threads running - if not rg_exists and headphones.AUTOWANT_ALL: - from headphones import searcher - searcher.searchforalbum(albumid=rg['id']) myDB.action('DELETE from tracks WHERE AlbumID=?', [rg['id']]) tracks = myDB.action('SELECT * from alltracks WHERE ReleaseID=?', [releaseid]).fetchall() @@ -405,16 +404,24 @@ def addArtisttoDB(artistid, extrasonly=False): # Mark albums as downloaded if they have at least 80% (by default, configurable) of the album have_track_count = len(myDB.select('SELECT * from tracks WHERE AlbumID=? AND Location IS NOT NULL', [rg['id']])) + marked_as_downloaded = False if rg_exists: if rg_exists['Status'] == 'Skipped' and ((have_track_count/float(total_track_count)) >= (headphones.ALBUM_COMPLETION_PCT/100.0)): myDB.action('UPDATE albums SET Status=? WHERE AlbumID=?', ['Downloaded', rg['id']]) + marked_as_downloaded = True else: if ((have_track_count/float(total_track_count)) >= (headphones.ALBUM_COMPLETION_PCT/100.0)): myDB.action('UPDATE albums SET Status=? WHERE AlbumID=?', ['Downloaded', rg['id']]) + marked_as_downloaded = True logger.info(u"Seeing if we need album art for " + rg['title']) cache.getThumb(AlbumID=rg['id']) + + #start a search for the album if it's new, hasn't been marked as downloaded and autowant_all is selected: + if not rg_exists and not marked_as_downloaded and headphones.AUTOWANT_ALL: + from headphones import searcher + searcher.searchforalbum(albumid=rg['id']) latestalbum = myDB.action('SELECT AlbumTitle, ReleaseDate, AlbumID from albums WHERE ArtistID=? order by ReleaseDate DESC', [artistid]).fetchone() totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=?', [artistid])) diff --git a/headphones/librarysync.py b/headphones/librarysync.py index ea95efa6..05ff49a0 100644 --- a/headphones/librarysync.py +++ b/headphones/librarysync.py @@ -22,8 +22,11 @@ import headphones from headphones import db, logger, helpers, importer # You can scan a single directory and append it to the current library by specifying append=True, ArtistID & ArtistName -def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None): +def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=False): + if cron and not headphones.LIBRARYSCAN: + return + if not dir: dir = headphones.MUSIC_DIR diff --git a/headphones/mb.py b/headphones/mb.py index b9c03499..5a20e0ad 100644 --- a/headphones/mb.py +++ b/headphones/mb.py @@ -157,7 +157,7 @@ def getArtist(artistid, extrasonly=False): artist = None try: - limit = 100 + limit = 200 artist = musicbrainzngs.get_artist_by_id(artistid)['artist'] newRgs = None artist['release-group-list'] = [] diff --git a/headphones/music_encoder.py b/headphones/music_encoder.py index 04dec8f5..4e057e20 100644 --- a/headphones/music_encoder.py +++ b/headphones/music_encoder.py @@ -27,7 +27,25 @@ try: except ImportError: import lib.argparse as argparse +# xld + +if headphones.ENCODER == 'xld': + import getXldProfile + XLD = True +else: + XLD = False + def encode(albumPath): + + # Return if xld details not found + + if XLD: + global xldProfile + (xldProfile, xldFormat, xldBitrate) = getXldProfile.getXldProfile(headphones.XLDPROFILE) + if not xldFormat: + logger.error(u'Details for xld profile "%s" not found, will not be reencoded' % (xldProfile)) + return None + tempDirEncode=os.path.join(albumPath,"temp") musicFiles=[] musicFinalFiles=[] @@ -46,26 +64,48 @@ def encode(albumPath): for r,d,f in os.walk(albumPath): for music in f: if any(music.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS): + + if not XLD: + encoderFormat = headphones.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING) + else: + xldMusicFile = os.path.join(r, music) + xldInfoMusic = MediaFile(xldMusicFile) + encoderFormat = xldFormat + if (headphones.ENCODERLOSSLESS): - if (music.lower().endswith('.flac')): + ext = os.path.normpath(os.path.splitext(music)[1].lstrip(".")).lower() + if not XLD and ext == 'flac' or XLD and (ext != xldFormat and (xldInfoMusic.bitrate / 1000 > 500)): musicFiles.append(os.path.join(r, music)) - musicTemp = os.path.normpath(os.path.splitext(music)[0]+'.'+headphones.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING)) + musicTemp = os.path.normpath(os.path.splitext(music)[0] + '.' + encoderFormat) musicTempFiles.append(os.path.join(tempDirEncode, musicTemp)) else: logger.debug('Music "%s" is already encoded' % (music)) else: musicFiles.append(os.path.join(r, music)) - musicTemp = os.path.normpath(os.path.splitext(music)[0]+'.'+headphones.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING)) + musicTemp = os.path.normpath(os.path.splitext(music)[0] + '.' + encoderFormat) musicTempFiles.append(os.path.join(tempDirEncode, musicTemp)) - - if headphones.ENCODER=='lame': + + if XLD: + if headphones.ENCODERFOLDER: + encoder = os.path.join(headphones.ENCODERFOLDER.encode(headphones.SYS_ENCODING), 'xld') + else: + encoder = os.path.join('/Applications', 'xld') + elif headphones.ENCODER=='lame': encoder=os.path.join(headphones.ENCODERFOLDER.encode(headphones.SYS_ENCODING),'lame') elif headphones.ENCODER=='ffmpeg': encoder=os.path.join(headphones.ENCODERFOLDER.encode(headphones.SYS_ENCODING),'ffmpeg') + i=0 for music in musicFiles: infoMusic=MediaFile(music) - if headphones.ENCODER == 'lame': + + if XLD: + if xldBitrate and (infoMusic.bitrate / 1000 <= xldBitrate): + logger.info('Music "%s" has bitrate <= "%skbit", will not be reencoded' % (music.decode(headphones.SYS_ENCODING, 'replace'), xldBitrate)) + else: + command(encoder,music,musicTempFiles[i],albumPath) + ifencoded=1 + elif headphones.ENCODER == 'lame': if not any(music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.' + x) for x in ["mp3", "wav"]): logger.warn(u'Lame cant encode "%s" format for "%s", use ffmpeg' % (os.path.splitext(music)[1].decode(headphones.SYS_ENCODING, 'replace'),music.decode(headphones.SYS_ENCODING, 'replace'))) else: @@ -106,7 +146,17 @@ def command(encoder,musicSource,musicDest,albumPath): return_code=1 cmd='' startMusicTime=time.time() - if headphones.ENCODER == 'lame': + + if XLD: + xldDestDir = os.path.split(musicDest)[0] + cmd = encoder + cmd = cmd + ' "' + musicSource + '"' + cmd = cmd + ' --profile' + cmd = cmd + ' "' + xldProfile + '"' + cmd = cmd + ' -o' + cmd = cmd + ' "' + xldDestDir + '"' + + elif headphones.ENCODER == 'lame': if headphones.ADVANCEDENCODER =='': cmd=encoder + ' -h' if headphones.ENCODERVBRCBR=='cbr': diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index ecabb271..1cc07a7f 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -20,6 +20,7 @@ import time import threading import music_encoder import urllib, shutil, re +import uuid from headphones import notifiers import lib.beets as beets from lib.beets import autotag @@ -27,6 +28,7 @@ from lib.beets.mediafile import MediaFile import headphones from headphones import db, albumart, librarysync, lyrics, logger, helpers +from headphones.helpers import sab_replace_dots, sab_replace_spaces postprocessor_lock = threading.Lock() @@ -40,15 +42,27 @@ def checkFolder(): for album in snatched: if album['FolderName']: + + # Need to check for variations due to sab renaming. Ideally we'd check the sab config via api to + # figure out which options are checked, but oh well + + nzb_album_possibilities = [ album['FolderName'], + sab_replace_dots(album['FolderName']), + sab_replace_spaces(album['FolderName']), + sab_replace_dots(sab_replace_spaces(album['FolderName'])) + ] - nzb_album_path = os.path.join(headphones.DOWNLOAD_DIR, album['FolderName']).encode(headphones.SYS_ENCODING) torrent_album_path = os.path.join(headphones.DOWNLOAD_TORRENT_DIR, album['FolderName']).encode(headphones.SYS_ENCODING) - if os.path.exists(nzb_album_path): - logger.debug('Found %s in NZB download folder. Verifying....' % album['FolderName']) - verify(album['AlbumID'], nzb_album_path) + for nzb_folder_name in nzb_album_possibilities: + + nzb_album_path = os.path.join(headphones.DOWNLOAD_DIR, nzb_folder_name).encode(headphones.SYS_ENCODING) - elif os.path.exists(torrent_album_path): + if os.path.exists(nzb_album_path): + logger.debug('Found %s in NZB download folder. Verifying....' % album['FolderName']) + verify(album['AlbumID'], nzb_album_path) + + if os.path.exists(torrent_album_path): logger.debug('Found %s in torrent download folder. Verifying....' % album['FolderName']) verify(album['AlbumID'], torrent_album_path) @@ -147,12 +161,75 @@ def verify(albumid, albumpath): tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid]) downloaded_track_list = [] - + downloaded_cuecount = 0 + for r,d,f in os.walk(albumpath): for files in f: if any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS): downloaded_track_list.append(os.path.join(r, files)) + elif files.lower().endswith('.cue'): + downloaded_cuecount += 1 + # use xld to split cue + + if headphones.ENCODER == 'xld' and headphones.MUSIC_ENCODER and downloaded_cuecount and downloaded_cuecount >= len(downloaded_track_list): + + import getXldProfile + + (xldProfile, xldFormat, xldBitrate) = getXldProfile.getXldProfile(headphones.XLDPROFILE) + if not xldFormat: + logger.info(u'Details for xld profile "%s" not found, cannot split cue' % (xldProfile)) + else: + if headphones.ENCODERFOLDER: + xldencoder = os.path.join(headphones.ENCODERFOLDER, 'xld') + else: + xldencoder = os.path.join('/Applications','xld') + + for r,d,f in os.walk(albumpath): + xldfolder = r + xldfile = '' + xldcue = '' + for file in f: + if any(file.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS) and not xldfile: + xldfile = os.path.join(r, file) + elif file.lower().endswith('.cue') and not xldcue: + xldcue = os.path.join(r, file) + + if xldfile and xldcue and xldfolder: + xldcmd = xldencoder + xldcmd = xldcmd + ' "' + xldfile + '"' + xldcmd = xldcmd + ' -c' + xldcmd = xldcmd + ' "' + xldcue + '"' + xldcmd = xldcmd + ' --profile' + xldcmd = xldcmd + ' "' + xldProfile + '"' + xldcmd = xldcmd + ' -o' + xldcmd = xldcmd + ' "' + xldfolder + '"' + logger.info(u"Cue found, splitting file " + xldfile.decode(headphones.SYS_ENCODING, 'replace')) + logger.debug(xldcmd) + os.system(xldcmd) + + # count files, should now be more than original if xld successfully split + + new_downloaded_track_list_count = 0 + for r,d,f in os.walk(albumpath): + for file in f: + if any(file.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS): + new_downloaded_track_list_count += 1 + + if new_downloaded_track_list_count > len(downloaded_track_list): + + # rename original unsplit files + for downloaded_track in downloaded_track_list: + os.rename(downloaded_track, downloaded_track + '.original') + + #reload + + downloaded_track_list = [] + for r,d,f in os.walk(albumpath): + for file in f: + if any(file.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS): + downloaded_track_list.append(os.path.join(r, file)) + # test #1: metadata - usually works logger.debug('Verifying metadata...') @@ -163,6 +240,11 @@ def verify(albumid, albumpath): logger.info(u"Exception from MediaFile for: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + u" : " + unicode(e)) continue + if not f.artist: + continue + if not f.album: + continue + metaartist = helpers.latinToAscii(f.artist.lower()).encode('UTF-8') dbartist = helpers.latinToAscii(release['ArtistName'].lower()).encode('UTF-8') metaalbum = helpers.latinToAscii(f.album.lower()).encode('UTF-8') @@ -182,6 +264,9 @@ def verify(albumid, albumpath): split_track_name = re.sub('[\.\-\_]', ' ', track_name).lower() for track in tracks: + if not track['TrackTitle']: + continue + dbtrack = helpers.latinToAscii(track['TrackTitle'].lower()).encode('UTF-8') filetrack = helpers.latinToAscii(split_track_name).encode('UTF-8') logger.debug('Checking if track title: %s is in file name: %s' % (dbtrack, filetrack)) @@ -233,9 +318,12 @@ def verify(albumid, albumpath): def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list): logger.info('Starting post-processing for: %s - %s' % (release['ArtistName'], release['AlbumTitle'])) - #start enconding + #start encoding if headphones.MUSIC_ENCODER: downloaded_track_list=music_encoder.encode(albumpath) + + if not downloaded_track_list: + return album_art_path = albumart.getAlbumArt(albumid) @@ -374,8 +462,7 @@ def moveFiles(albumpath, release, tracks): '$first': firstchar.lower() } - - folder = helpers.replace_all(headphones.FOLDER_FORMAT, values) + folder = helpers.replace_all(headphones.FOLDER_FORMAT.strip(), values) folder = folder.replace('./', '_/').replace(':','_').replace('?','_').replace('/.','/_').replace('<','_').replace('>','_') if folder.endswith('.'): @@ -402,8 +489,8 @@ def moveFiles(albumpath, release, tracks): make_lossy_folder = False make_lossless_folder = False - lossy_destination_path = os.path.normpath(os.path.join(headphones.DESTINATION_DIR, folder)).encode(headphones.SYS_ENCODING) - lossless_destination_path = os.path.normpath(os.path.join(headphones.LOSSLESS_DESTINATION_DIR, folder)).encode(headphones.SYS_ENCODING) + lossy_destination_path = os.path.normpath(os.path.join(headphones.DESTINATION_DIR, folder)).encode(headphones.SYS_ENCODING, 'replace') + lossless_destination_path = os.path.normpath(os.path.join(headphones.LOSSLESS_DESTINATION_DIR, folder)).encode(headphones.SYS_ENCODING, 'replace') # If they set a destination dir for lossless media, only create the lossy folder if there is lossy media if headphones.LOSSLESS_DESTINATION_DIR: @@ -415,7 +502,7 @@ def moveFiles(albumpath, release, tracks): else: make_lossy_folder = True - last_folder = headphones.FOLDER_FORMAT.split('/')[-1] + last_folder = headphones.FOLDER_FORMAT.strip().split('/')[-1] if make_lossless_folder: # Only rename the folder if they use the album name, otherwise merge into existing folder @@ -426,7 +513,7 @@ def moveFiles(albumpath, release, tracks): i = 1 while True: newfolder = temp_folder + '[%i]' % i - lossless_destination_path = os.path.normpath(os.path.join(headphones.LOSSLESS_DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING) + lossless_destination_path = os.path.normpath(os.path.join(headphones.LOSSLESS_DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING, 'replace') if os.path.exists(lossless_destination_path): i += 1 else: @@ -439,7 +526,7 @@ def moveFiles(albumpath, release, tracks): except Exception, e: logger.error('Could not create lossless folder for %s. (Error: %s)' % (release['AlbumTitle'], e)) if not make_lossy_folder: - return albumpath + return [albumpath] if make_lossy_folder: if os.path.exists(lossy_destination_path) and 'album' in last_folder.lower(): @@ -449,7 +536,7 @@ def moveFiles(albumpath, release, tracks): i = 1 while True: newfolder = temp_folder + '[%i]' % i - lossy_destination_path = os.path.normpath(os.path.join(headphones.DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING) + lossy_destination_path = os.path.normpath(os.path.join(headphones.DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING, 'replace') if os.path.exists(lossy_destination_path): i += 1 else: @@ -461,7 +548,7 @@ def moveFiles(albumpath, release, tracks): os.makedirs(lossy_destination_path) except Exception, e: logger.error('Could not create folder for %s. Not moving: %s' % (release['AlbumTitle'], e)) - return albumpath + return [albumpath] logger.info('Checking which files we need to move.....') @@ -519,7 +606,7 @@ def moveFiles(albumpath, release, tracks): temp_f = os.path.join(temp_f, f) try: - os.chmod(os.path.normpath(temp_f).encode(headphones.SYS_ENCODING), int(headphones.FOLDER_PERMISSIONS, 8)) + os.chmod(os.path.normpath(temp_f).encode(headphones.SYS_ENCODING, 'replace'), int(headphones.FOLDER_PERMISSIONS, 8)) except Exception, e: logger.error("Error trying to change permissions on folder: %s" % temp_f.decode(headphones.SYS_ENCODING, 'replace')) @@ -663,10 +750,10 @@ def renameFiles(albumpath, downloaded_track_list, release): ext = os.path.splitext(downloaded_track)[1] - new_file_name = helpers.replace_all(headphones.FILE_FORMAT, values).replace('/','_') + ext + new_file_name = helpers.replace_all(headphones.FILE_FORMAT.strip(), values).replace('/','_') + ext - new_file_name = new_file_name.replace('?','_').replace(':', '_').encode(headphones.SYS_ENCODING) + new_file_name = new_file_name.replace('?','_').replace(':', '_').encode(headphones.SYS_ENCODING, 'replace') if new_file_name.startswith('.'): new_file_name = new_file_name.replace(0, '_') @@ -674,7 +761,7 @@ def renameFiles(albumpath, downloaded_track_list, release): new_file = os.path.join(albumpath, new_file_name) if downloaded_track == new_file_name: - logger.debug("Renaming for: " + downloaded_track.decode(headphones.SYS_ENCODING) + " is not neccessary") + logger.debug("Renaming for: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + " is not neccessary") continue logger.debug('Renaming %s ---> %s' % (downloaded_track.decode(headphones.SYS_ENCODING,'replace'), new_file_name.decode(headphones.SYS_ENCODING,'replace'))) @@ -704,9 +791,9 @@ def forcePostProcess(): download_dirs = [] if headphones.DOWNLOAD_DIR: - download_dirs.append(headphones.DOWNLOAD_DIR.encode(headphones.SYS_ENCODING)) + download_dirs.append(headphones.DOWNLOAD_DIR.encode(headphones.SYS_ENCODING, 'replace')) if headphones.DOWNLOAD_TORRENT_DIR: - download_dirs.append(headphones.DOWNLOAD_TORRENT_DIR.encode(headphones.SYS_ENCODING)) + download_dirs.append(headphones.DOWNLOAD_TORRENT_DIR.encode(headphones.SYS_ENCODING, 'replace')) logger.info('Checking to see if there are any folders to process in download_dir(s): %s' % str(download_dirs).decode(headphones.SYS_ENCODING, 'replace')) # Get a list of folders in the download_dir @@ -723,20 +810,21 @@ def forcePostProcess(): logger.info('Found no folders to process in: %s' % str(download_dirs).decode(headphones.SYS_ENCODING, 'replace')) # Parse the folder names to get artist album info + myDB = db.DBConnection() + for folder in folders: folder_basename = os.path.basename(folder).decode(headphones.SYS_ENCODING, 'replace') logger.info('Processing: %s' % folder_basename) - + try: name, album, year = helpers.extract_data(folder_basename) except: - logger.info("Couldn't parse " + folder_basename + " into any valid format.") - continue + name = None + if name and album and year: - myDB = db.DBConnection() release = myDB.action('SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE ArtistName LIKE ? and AlbumTitle LIKE ?', [name, album]).fetchone() if release: logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album' % (release['ArtistName'], release['AlbumTitle'])) @@ -753,3 +841,25 @@ def forcePostProcess(): verify(rgid, folder) else: logger.info('No match found on MusicBrainz for: %s - %s' % (name, album)) + continue + + else: + try: + possible_rgid = folder_basename[-36:] + # re pattern match: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} + rgid = uuid.UUID(possible_rgid) + + except: + logger.info("Couldn't parse " + folder_basename + " into any valid format. If adding albums from another source, they must be in an 'Artist - Album [Year]' format, or end with the musicbrainz release group id") + continue + + + if rgid: + rgid = possible_rgid + release = myDB.action('SELECT ArtistName, AlbumTitle, AlbumID from albums WHERE AlbumID=?', [rgid]).fetchone() + if release: + logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album' % (release['ArtistName'], release['AlbumTitle'])) + verify(release['AlbumID'], folder) + else: + logger.info('Found a (possibly) valid Musicbrainz identifier in album folder name - continuing post-processing') + verify(rgid, folder) diff --git a/headphones/searcher.py b/headphones/searcher.py index 22737fe1..016a4894 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with Headphones. If not, see . -import urllib, urllib2, urlparse +import urllib, urllib2, urlparse, httplib import lib.feedparser as feedparser from lib.pygazelle import api as gazelleapi from lib.pygazelle import encoding as gazelleencoding @@ -84,7 +84,17 @@ def url_fix(s, charset='utf-8'): scheme, netloc, path, qs, anchor = urlparse.urlsplit(s) path = urllib.quote(path, '/%') qs = urllib.quote_plus(qs, ':&=') - return urlparse.urlunsplit((scheme, netloc, path, qs, anchor)) + return urlparse.urlunsplit((scheme, netloc, path, qs, anchor)) + +def patch_http_response_read(func): + def inner(*args): + try: + return func(*args) + except httplib.IncompleteRead, e: + return e.partial + + return inner +httplib.HTTPResponse.read = patch_http_response_read(httplib.HTTPResponse.read) def searchforalbum(albumid=None, new=False, lossless=False): @@ -244,6 +254,17 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): logger.info("Album type is audiobook/spokenword. Using audiobook category") for newznab_host in newznab_hosts: + + # Add a little mod for kere.ws + if newznab_host[0] == "http://kere.ws": + if categories == "3040": + categories = categories + ",4070" + elif categories == "3040,3010": + categories = categories + ",4070,4010" + elif categories == "3010": + categories = categories + ",4010" + else: + categories = categories + ",4050" params = { "t": "search", "apikey": newznab_host[1], @@ -503,7 +524,8 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): if data and bestqual: logger.info(u'Found best result: %s - %s' % (bestqual[2], bestqual[0], helpers.bytes_to_mb(bestqual[1]))) - nzb_folder_name = '%s - %s [%s]' % (helpers.latinToAscii(albums[0]).encode('UTF-8').replace('/', '_'), helpers.latinToAscii(albums[1]).encode('UTF-8').replace('/', '_'), year) + # Get rid of any dodgy chars here so we can prevent sab from renaming our downloads + nzb_folder_name = helpers.sab_sanitize_foldername(bestqual[0]) + '.' + albums[2] if headphones.SAB_HOST and not headphones.BLACKHOLE: nzb = classes.NZBDataSearchResult() @@ -552,6 +574,11 @@ def verifyresult(title, artistterm, term): #another attempt to weed out substrings. We don't want "Vol III" when we were looking for "Vol II" + # Filter out remix search results (if we're not looking for it) + if 'remix' not in term and 'remix' in title: + logger.info("Removed " + title + " from results because it's a remix album and we're not looking for a remix album right now") + return False + tokens = re.split('\W', term, re.IGNORECASE | re.UNICODE) for token in tokens: @@ -674,12 +701,12 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): else: term = cleanartist + ' ' + cleanalbum - semi_clean_artist_term = re.sub('[\.\-\/]', ' ', semi_cleanartist).encode('utf-8') - semi_clean_album_term = re.sub('[\.\-\/]', ' ', semi_cleanalbum).encode('utf-8') + semi_clean_artist_term = re.sub('[\.\-\/]', ' ', semi_cleanartist).encode('utf-8', 'replace') + semi_clean_album_term = re.sub('[\.\-\/]', ' ', semi_cleanalbum).encode('utf-8', 'replace') # Replace bad characters in the term and unicode it term = re.sub('[\.\-\/]', ' ', term).encode('utf-8') - artistterm = re.sub('[\.\-\/]', ' ', cleanartist).encode('utf-8') - albumterm = re.sub('[\.\-\/]', ' ', cleanalbum).encode('utf-8') + artistterm = re.sub('[\.\-\/]', ' ', cleanartist).encode('utf-8', 'replace') + albumterm = re.sub('[\.\-\/]', ' ', cleanalbum).encode('utf-8', 'replace') logger.info("Searching torrents for %s since it was marked as wanted" % term) diff --git a/headphones/searcher_rutracker.py b/headphones/searcher_rutracker.py index 7cdbd949..270b5fc3 100644 --- a/headphones/searcher_rutracker.py +++ b/headphones/searcher_rutracker.py @@ -146,18 +146,18 @@ class Rutracker(): # get headphones track count for album, return if not found - hptrackcount = 0 - myDB = db.DBConnection() - tracks = myDB.select('SELECT TrackTitle from tracks WHERE AlbumID=?', [albumid]) - for track in tracks: - hptrackcount += 1 + tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid]) + hptrackcount = len(tracks) if not hptrackcount: logger.info('headphones track info not found, cannot compare to torrent') return False # Return the first valid torrent, unless we want a preferred bitrate then we want all valid entries + unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd'] + formatlist = ['.ape', '.flac', '.ogg', '.m4a', '.aac', '.mp3', '.wav', '.aif'] + deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive'] for torrent in torrentlist: @@ -170,8 +170,7 @@ class Rutracker(): title = returntitle.lower() - if 'promo' not in title and 'vinyl' not in title and 'songbook' not in title and 'tvrip' not in title and 'hdtv' not in title and 'dvd' not in title \ - and int(size) <= maxsize and int(seeders) >= minseeders: + if not any(unwanted in title for unwanted in unwantedlist) and int(size) <= maxsize and int(seeders) >= minseeders: # Check torrent info @@ -202,7 +201,7 @@ class Rutracker(): for pathfile in metainfo['files']: path = pathfile['path'] for file in path: - if '.ape' in file or '.flac' in file or '.ogg' in file or '.m4a' in file or '.aac' in file or '.mp3' in file or '.wav' in file or '.aif' in file: + if any(format in file for format in formatlist): trackcount += 1 if '.cue' in file: cuecount += 1 @@ -252,7 +251,7 @@ class Rutracker(): if trackcount == hptrackcount: valid = True elif trackcount > hptrackcount: - if 'deluxe' in title or 'edition' in title or 'japanese' or 'exclusive' in title: + if any(deluxe in title for deluxe in deluxelist): valid = True # return 1st valid torrent if not checking by bitrate, else add to list and return at end diff --git a/headphones/webserve.py b/headphones/webserve.py index bac8b3cf..08d1863f 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -368,7 +368,8 @@ class WebInterface(object): raise cherrypy.HTTPRedirect("home") importItunes.exposed = True - def musicScan(self, path, scan=0, redirect=None, autoadd=0): + def musicScan(self, path, scan=0, redirect=None, autoadd=0, libraryscan=0): + headphones.LIBRARYSCAN = libraryscan headphones.ADD_ARTISTS = autoadd headphones.MUSIC_DIR = path headphones.config_write() @@ -623,6 +624,7 @@ class WebInterface(object): "interface_list" : interface_list, "music_encoder": checked(headphones.MUSIC_ENCODER), "encoder": headphones.ENCODER, + "xldprofile": headphones.XLDPROFILE, "bitrate": int(headphones.BITRATE), "encoderfolder": headphones.ENCODERFOLDER, "advancedencoder": headphones.ADVANCEDENCODER, @@ -683,11 +685,11 @@ class WebInterface(object): numberofseeders=10, use_isohunt=0, use_kat=0, use_mininova=0, waffles=0, waffles_uid=None, waffles_passkey=None, whatcd=0, whatcd_username=None, whatcd_password=None, rutracker=0, rutracker_user=None, rutracker_password=None, rename_files=0, correct_metadata=0, cleanup_files=0, add_album_art=0, embed_album_art=0, embed_lyrics=0, destination_dir=None, lossless_destination_dir=None, folder_format=None, file_format=None, include_extras=0, single=0, ep=0, compilation=0, soundtrack=0, live=0, - remix=0, spokenword=0, audiobook=0, autowant_upcoming=False, autowant_all=False, interface=None, log_dir=None, cache_dir=None, music_encoder=0, encoder=None, bitrate=None, - samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0, + remix=0, spokenword=0, audiobook=0, autowant_upcoming=False, autowant_all=False, interface=None, log_dir=None, cache_dir=None, music_encoder=0, encoder=None, xldprofile=None, + bitrate=None, samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0, delete_lossless_files=0, prowl_enabled=0, prowl_onsnatch=0, prowl_keys=None, prowl_priority=0, xbmc_enabled=0, xbmc_host=None, xbmc_username=None, xbmc_password=None, xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, synoindex_enabled=False, mirror=None, customhost=None, customport=None, - customsleep=None, hpuser=None, hppass=None, preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, cache_sizemb=0, **kwargs): + customsleep=None, hpuser=None, hppass=None, preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, cache_sizemb=None, **kwargs): headphones.HTTP_HOST = http_host headphones.HTTP_PORT = http_port @@ -760,6 +762,7 @@ class WebInterface(object): headphones.CACHE_DIR = cache_dir headphones.MUSIC_ENCODER = music_encoder headphones.ENCODER = encoder + headphones.XLDPROFILE = xldprofile headphones.BITRATE = int(bitrate) headphones.SAMPLINGFREQUENCY = int(samplingfrequency) headphones.ENCODERFOLDER = encoderfolder diff --git a/headphones/webstart.py b/headphones/webstart.py index a19a8b3e..b67c801f 100644 --- a/headphones/webstart.py +++ b/headphones/webstart.py @@ -74,6 +74,7 @@ def initialize(options={}): 'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict( {options['http_username']:options['http_password']}) }) + conf['/api'] = { 'tools.auth_basic.on': False } # Prevent time-outs