From 07ee4adc812655fea6e90deaa86ef53c6010cdbb Mon Sep 17 00:00:00 2001 From: rembo10 Date: Wed, 24 Oct 2012 13:26:27 -0300 Subject: [PATCH 01/21] Patched http response read to deal with search providers hanging up and trashing our data --- headphones/searcher.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/headphones/searcher.py b/headphones/searcher.py index 22737fe1..1bf0eb74 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): From 608ccb2c0a5081a85d90d25f1017adcb9fce8590 Mon Sep 17 00:00:00 2001 From: Ade Date: Thu, 25 Oct 2012 11:19:18 +1300 Subject: [PATCH 02/21] XLD encoding + bonus post processing xld cue splitting. Currently uses Advanced Encoding Options to determine xld and the xld profile to use, e.g xld Apple Lossless. Would like to use congig instead but can't figure out how to do it --- headphones/getXldProfile.py | 181 ++++++++++++++++++++++++++++++++++++ headphones/music_encoder.py | 65 +++++++++++-- headphones/postprocessor.py | 77 ++++++++++++++- 3 files changed, 314 insertions(+), 9 deletions(-) create mode 100755 headphones/getXldProfile.py 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/music_encoder.py b/headphones/music_encoder.py index 04dec8f5..8a66228e 100644 --- a/headphones/music_encoder.py +++ b/headphones/music_encoder.py @@ -27,7 +27,26 @@ try: except ImportError: import lib.argparse as argparse +# xld + +if headphones.ADVANCEDENCODER.lower().startswith('xld'): + XLDPROFILE = headphones.ADVANCEDENCODER[4:] + 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(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 +65,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 +147,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..5f79009b 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -28,6 +28,15 @@ from lib.beets.mediafile import MediaFile import headphones from headphones import db, albumart, librarysync, lyrics, logger, helpers +# xld + +if headphones.ADVANCEDENCODER.lower().startswith('xld'): + XLDPROFILE = headphones.ADVANCEDENCODER[4:] + import getXldProfile + XLD = True +else: + XLD = False + postprocessor_lock = threading.Lock() def checkFolder(): @@ -147,12 +156,73 @@ 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 XLD and headphones.MUSIC_ENCODER and downloaded_cuecount and downloaded_cuecount >= len(downloaded_track_list): + + (xldProfile, xldFormat, xldBitrate) = getXldProfile.getXldProfile(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...') @@ -233,9 +303,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) From 2fb45f489ac3bc9b0687e04b956183be625f9c4c Mon Sep 17 00:00:00 2001 From: rembo10 Date: Thu, 25 Oct 2012 20:04:06 -0300 Subject: [PATCH 03/21] Mark new albums added to musicbrainz in the last 21 days as wanted (if AUTOWANT_UPCOMING is selected) --- headphones/helpers.py | 11 +++++++++++ headphones/importer.py | 10 ++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/headphones/helpers.py b/headphones/helpers.py index 89fb20cc..957d9717 100644 --- a/headphones/helpers.py +++ b/headphones/helpers.py @@ -118,6 +118,17 @@ def now(): now = datetime.datetime.now() return now.strftime("%Y-%m-%d %H:%M:%S") +def get_age(date): + + split_date = date.split('-') + + 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 diff --git a/headphones/importer.py b/headphones/importer.py index 158d703f..67021a18 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -358,11 +358,17 @@ 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" From 6d261cd50550d04df26022e6743e753259ac46a3 Mon Sep 17 00:00:00 2001 From: Ewoud Kohl van Wijngaarden Date: Sun, 28 Oct 2012 23:47:12 +0100 Subject: [PATCH 04/21] Typo fix --- Headphones.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Headphones.py b/Headphones.py index 18bb7970..cf911028 100644 --- a/Headphones.py +++ b/Headphones.py @@ -116,7 +116,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) From 34b162877ea16b547ac01c984664f4e42b020400 Mon Sep 17 00:00:00 2001 From: Ewoud Kohl van Wijngaarden Date: Mon, 29 Oct 2012 10:43:03 +0100 Subject: [PATCH 05/21] Allow shutting down using KeyboardInterrupt --- Headphones.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Headphones.py b/Headphones.py index 18bb7970..2843e076 100644 --- a/Headphones.py +++ b/Headphones.py @@ -140,7 +140,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': From 6eab3b47e4829386a2b79abcce08ac60e5c2b006 Mon Sep 17 00:00:00 2001 From: rembo10 Date: Tue, 30 Oct 2012 15:56:25 -0400 Subject: [PATCH 06/21] Added AdeHub's changes to searcher_rutracker to filter out vynyl rips --- headphones/searcher_rutracker.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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 From 705b56c696819c72489d85efa7d7675aa5e0de03 Mon Sep 17 00:00:00 2001 From: rembo10 Date: Tue, 30 Oct 2012 16:38:01 -0400 Subject: [PATCH 07/21] Added xld option to config page --- data/interfaces/default/config.html | 81 +++++++++++++++++++++-------- headphones/__init__.py | 5 +- headphones/music_encoder.py | 5 +- headphones/webserve.py | 6 ++- 4 files changed, 69 insertions(+), 28 deletions(-) diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 532e02f2..508750e4 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -417,7 +417,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 +434,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/headphones/__init__.py b/headphones/__init__.py index 30fa5902..ec6f5ac4 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -168,6 +168,7 @@ FOLDER_PERMISSIONS = None MUSIC_ENCODER = False ENCODERFOLDER = None ENCODER = None +XLDPROFILE = None BITRATE = None SAMPLINGFREQUENCY = None ADVANCEDENCODER = None @@ -261,7 +262,7 @@ def initialize(): 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, \ @@ -397,6 +398,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)) @@ -723,6 +725,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 diff --git a/headphones/music_encoder.py b/headphones/music_encoder.py index 8a66228e..4e057e20 100644 --- a/headphones/music_encoder.py +++ b/headphones/music_encoder.py @@ -29,8 +29,7 @@ except ImportError: # xld -if headphones.ADVANCEDENCODER.lower().startswith('xld'): - XLDPROFILE = headphones.ADVANCEDENCODER[4:] +if headphones.ENCODER == 'xld': import getXldProfile XLD = True else: @@ -42,7 +41,7 @@ def encode(albumPath): if XLD: global xldProfile - (xldProfile, xldFormat, xldBitrate) = getXldProfile.getXldProfile(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 diff --git a/headphones/webserve.py b/headphones/webserve.py index bac8b3cf..a8dd11ae 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -623,6 +623,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,8 +684,8 @@ 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): @@ -760,6 +761,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 From 2e33c7a2f70af82c7a21ea8b58cea8f7819cff5e Mon Sep 17 00:00:00 2001 From: rembo10 Date: Tue, 30 Oct 2012 18:39:34 -0400 Subject: [PATCH 08/21] Fixed some of the xld code in the postprocessor to work with new config values --- headphones/postprocessor.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index 5f79009b..1942ec5d 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -28,15 +28,6 @@ from lib.beets.mediafile import MediaFile import headphones from headphones import db, albumart, librarysync, lyrics, logger, helpers -# xld - -if headphones.ADVANCEDENCODER.lower().startswith('xld'): - XLDPROFILE = headphones.ADVANCEDENCODER[4:] - import getXldProfile - XLD = True -else: - XLD = False - postprocessor_lock = threading.Lock() def checkFolder(): @@ -167,9 +158,11 @@ def verify(albumid, albumpath): # use xld to split cue - if XLD and headphones.MUSIC_ENCODER and downloaded_cuecount and downloaded_cuecount >= len(downloaded_track_list): - - (xldProfile, xldFormat, xldBitrate) = getXldProfile.getXldProfile(XLDPROFILE) + 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: From 78202757dabe3c755be917870c30d6db9ef5b621 Mon Sep 17 00:00:00 2001 From: rembo10 Date: Tue, 30 Oct 2012 22:29:52 -0400 Subject: [PATCH 09/21] Keep path to encoder for xld option in config --- data/interfaces/default/config.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 508750e4..39b689d3 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -537,10 +537,6 @@
-
- - -
@@ -549,6 +545,11 @@ + +
+ + +
From a5fd5c6881bac7428e215adc0c7d03c2f56ef3ee Mon Sep 17 00:00:00 2001 From: rembo10 Date: Wed, 31 Oct 2012 14:49:01 -0400 Subject: [PATCH 10/21] Use original nzb titles for downloading, with mbid appended --- Headphones.py | 1 + headphones/__init__.py | 5 ++-- headphones/helpers.py | 59 +++++++++++++++++++++++++++++++++---- headphones/postprocessor.py | 55 ++++++++++++++++++++++++++++------ headphones/searcher.py | 3 +- 5 files changed, 105 insertions(+), 18 deletions(-) diff --git a/Headphones.py b/Headphones.py index ff469ebd..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: diff --git a/headphones/__init__.py b/headphones/__init__.py index ec6f5ac4..c2c5ce37 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 @@ -251,7 +252,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, \ @@ -759,7 +760,7 @@ 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) diff --git a/headphones/helpers.py b/headphones/helpers.py index 957d9717..2b80f93b 100644 --- a/headphones/helpers.py +++ b/headphones/helpers.py @@ -164,8 +164,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) @@ -176,8 +174,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) @@ -188,8 +184,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 @@ -268,3 +263,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/postprocessor.py b/headphones/postprocessor.py index 1942ec5d..c514bdf8 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) @@ -789,20 +803,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'])) @@ -819,3 +834,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 1bf0eb74..055ddde6 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -513,7 +513,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() From 6968551c42624e2c3bd405cb85b1fd96e2c6125d Mon Sep 17 00:00:00 2001 From: rembo10 Date: Wed, 31 Oct 2012 16:26:21 -0400 Subject: [PATCH 11/21] Return album path as a list if creating dirs fails --- headphones/postprocessor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index c514bdf8..d8fe7a20 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -519,7 +519,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(): @@ -541,7 +541,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.....') From 3ac6e93eaba03f7f484edeb1672fc98ec30a6bf3 Mon Sep 17 00:00:00 2001 From: rembo10 Date: Wed, 31 Oct 2012 18:08:54 -0400 Subject: [PATCH 12/21] Added option to disable automatic library scan --- data/interfaces/brink/manage.html | 7 ++++++- data/interfaces/default/config.html | 1 - data/interfaces/default/manage.html | 6 +++++- headphones/__init__.py | 7 +++++-- headphones/librarysync.py | 5 ++++- headphones/webserve.py | 5 +++-- 6 files changed, 23 insertions(+), 8 deletions(-) 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 39b689d3..a3f994cb 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -85,7 +85,6 @@ mins -
mins 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 c2c5ce37..3f27c709 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -112,6 +112,7 @@ AUTOWANT_UPCOMING = False AUTOWANT_ALL = False SEARCH_INTERVAL = 360 +LIBRARYSCAN = False LIBRARYSCAN_INTERVAL = 300 DOWNLOAD_SCAN_INTERVAL = 5 @@ -260,7 +261,7 @@ 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, XLDPROFILE, BITRATE, SAMPLINGFREQUENCY, MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, \ @@ -343,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) @@ -659,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 @@ -762,7 +765,7 @@ def start(): 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/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/webserve.py b/headphones/webserve.py index a8dd11ae..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() @@ -688,7 +689,7 @@ class WebInterface(object): 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 From cf3c4f1ba679cea8c58f8e80b5ee5f59bda0a454 Mon Sep 17 00:00:00 2001 From: rembo10 Date: Wed, 31 Oct 2012 19:22:23 -0400 Subject: [PATCH 13/21] Fix for helpers.get_age failing if there's no date --- headphones/helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/headphones/helpers.py b/headphones/helpers.py index 2b80f93b..e68abdd6 100644 --- a/headphones/helpers.py +++ b/headphones/helpers.py @@ -120,7 +120,10 @@ def now(): def get_age(date): - split_date = date.split('-') + 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]) From c35ef320fbcbaba09936cf757c113fe90abe6eaa Mon Sep 17 00:00:00 2001 From: rembo10 Date: Wed, 31 Oct 2012 19:45:16 -0400 Subject: [PATCH 14/21] Fix for post processor verify failing if no metadata title (or artist or album) --- headphones/postprocessor.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index d8fe7a20..00d4b78d 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -240,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') @@ -259,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)) From 0a000342c10f509c30e9d6badd07d23c4a34d168 Mon Sep 17 00:00:00 2001 From: rembo10 Date: Thu, 1 Nov 2012 11:41:58 -0400 Subject: [PATCH 15/21] Turn off basic_auth for /api if using username & password --- headphones/webstart.py | 1 + 1 file changed, 1 insertion(+) 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 From ffa05d4c8a79f9ba256641008d35822831863050 Mon Sep 17 00:00:00 2001 From: rembo10 Date: Thu, 1 Nov 2012 18:20:24 -0400 Subject: [PATCH 16/21] Strip leading/trailing whitespace from FOLDER_FORMAT, FILE_FORMAT --- headphones/postprocessor.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index 00d4b78d..b8a7eb6e 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -462,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('.'): @@ -503,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 @@ -751,7 +750,7 @@ 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) From 3f780c2dd17fa3daf877dddc818f5f7e7d70f80f Mon Sep 17 00:00:00 2001 From: rembo10 Date: Thu, 1 Nov 2012 18:27:41 -0400 Subject: [PATCH 17/21] Don't replace text for Nonetype in helpers.replace_all --- headphones/helpers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/headphones/helpers.py b/headphones/helpers.py index e68abdd6..298062ee 100644 --- a/headphones/helpers.py +++ b/headphones/helpers.py @@ -144,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 From c5ee88d82c36f4ad9349e5e10e21bbb60e2a5a67 Mon Sep 17 00:00:00 2001 From: rembo10 Date: Thu, 1 Nov 2012 19:42:20 -0400 Subject: [PATCH 18/21] Don't mark albums as wanted if it's already Downloaded (with AUTOWANT_ALL selected). Don't search until after track info is populated --- headphones/importer.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/headphones/importer.py b/headphones/importer.py index 67021a18..447ff908 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -374,13 +374,6 @@ def addArtisttoDB(artistid, extrasonly=False): 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() @@ -411,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])) From 00ebf2ee608dc1d8077967acdb403b9c9dd0a961 Mon Sep 17 00:00:00 2001 From: rembo10 Date: Sat, 3 Nov 2012 21:52:36 -0400 Subject: [PATCH 19/21] Add support for kere.ws newznab provider --- headphones/searcher.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/headphones/searcher.py b/headphones/searcher.py index 055ddde6..fa93814c 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -254,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], From 937a4e54de25655e2b0b64237b0105cd7273803b Mon Sep 17 00:00:00 2001 From: rembo10 Date: Sat, 3 Nov 2012 22:56:10 -0400 Subject: [PATCH 20/21] Fix for postprocessor failing when trying to rename files where unicode char doesn't map to sys_encoding (basically just replace encoding errors) --- headphones/postprocessor.py | 18 +++++++++--------- headphones/searcher.py | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index b8a7eb6e..1cc07a7f 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -489,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: @@ -513,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: @@ -536,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: @@ -606,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')) @@ -753,7 +753,7 @@ def renameFiles(albumpath, downloaded_track_list, release): 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, '_') @@ -761,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'))) @@ -791,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 diff --git a/headphones/searcher.py b/headphones/searcher.py index fa93814c..71bf7a33 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -696,12 +696,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) From c696b6d534be6a2cf41baab995f9345bef20e512 Mon Sep 17 00:00:00 2001 From: rembo10 Date: Sun, 4 Nov 2012 10:59:23 -0500 Subject: [PATCH 21/21] Filter out remix albums from search results, unless we're specifically looking for a remix album (also bumped up release group limit from 100->200 in mb.py getArtist) --- headphones/mb.py | 2 +- headphones/searcher.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) 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/searcher.py b/headphones/searcher.py index 71bf7a33..016a4894 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -574,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: