From 974755927d13024d99d33066b1a6e9a1de094da7 Mon Sep 17 00:00:00 2001 From: Remy Date: Sat, 16 Jul 2011 21:48:06 -0700 Subject: [PATCH] New feature: Preferred Quality. Specify a target bit rate or let the Music Scanner detect it for you --- data/css/style.css | 4 +++ headphones/__init__.py | 24 ++++++++++-------- headphones/helpers.py | 54 ++++++++++++++++++++++++++++++++++++++++- headphones/importer.py | 6 +++++ headphones/searcher.py | 25 ++++++++++++------- headphones/templates.py | 39 ++++++++++++----------------- headphones/webserve.py | 20 +++++++++------ 7 files changed, 122 insertions(+), 50 deletions(-) diff --git a/data/css/style.css b/data/css/style.css index e7f51eb7..6cb1b6cc 100644 --- a/data/css/style.css +++ b/data/css/style.css @@ -86,6 +86,10 @@ h1{ .smalltext{ font-size: 11px; } +.smalltext2{ + font-size: 11px; + margin-left: 45px; + } .mediumtext{ font-size: 16px; margin-left: 100px; diff --git a/headphones/__init__.py b/headphones/__init__.py index 49ffc8bf..f3e47c4a 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -55,7 +55,9 @@ MUSIC_DIR = None FOLDER_FORMAT = None FILE_FORMAT = None PATH_TO_XML = None -PREFER_LOSSLESS = False +PREFERRED_QUALITY = None +PREFERRED_BITRATE = None +DETECT_BITRATE = False FLAC_TO_MP3 = False MOVE_FILES = False RENAME_FILES = False @@ -139,13 +141,11 @@ def initialize(): global __INITIALIZED__, FULL_PATH, PROG_DIR, QUIET, DAEMON, DATA_DIR, CONFIG_FILE, CFG, LOG_DIR, CACHE_DIR, \ HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, LAUNCH_BROWSER, GIT_PATH, \ - CURRENT_VERSION, LATEST_VERSION,\ - MUSIC_DIR, PREFER_LOSSLESS, FLAC_TO_MP3, MOVE_FILES, RENAME_FILES, FOLDER_FORMAT, \ - FILE_FORMAT, CLEANUP_FILES, ADD_ALBUM_ART, DOWNLOAD_DIR, BLACKHOLE, BLACKHOLE_DIR, USENET_RETENTION, \ - NZB_SEARCH_INTERVAL, LIBRARYSCAN_INTERVAL, \ - SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \ - NZBMATRIX, NZBMATRIX_USERNAME, NZBMATRIX_APIKEY, \ - NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, \ + CURRENT_VERSION, LATEST_VERSION, MUSIC_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, \ + FLAC_TO_MP3, MOVE_FILES, RENAME_FILES, FOLDER_FORMAT, FILE_FORMAT, CLEANUP_FILES, \ + ADD_ALBUM_ART, DOWNLOAD_DIR, BLACKHOLE, BLACKHOLE_DIR, USENET_RETENTION, NZB_SEARCH_INTERVAL, \ + LIBRARYSCAN_INTERVAL, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \ + NZBMATRIX, NZBMATRIX_USERNAME, NZBMATRIX_APIKEY, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, \ NZBSORG, NZBSORG_UID, NZBSORG_HASH if __INITIALIZED__: @@ -175,7 +175,9 @@ def initialize(): GIT_PATH = check_setting_str(CFG, 'General', 'git_path', '') MUSIC_DIR = check_setting_str(CFG, 'General', 'music_dir', '') - PREFER_LOSSLESS = bool(check_setting_int(CFG, 'General', 'prefer_lossless', 0)) + PREFERRED_QUALITY = check_setting_int(CFG, 'General', 'preferred_quality', 0) + PREFERRED_BITRATE = check_setting_int(CFG, 'General', 'preferred_bitrate', '') + DETECT_BITRATE = bool(check_setting_int(CFG, 'General', 'detect_bitrate', 0)) FLAC_TO_MP3 = bool(check_setting_int(CFG, 'General', 'flac_to_mp3', 0)) MOVE_FILES = bool(check_setting_int(CFG, 'General', 'move_files', 0)) RENAME_FILES = bool(check_setting_int(CFG, 'General', 'rename_files', 0)) @@ -319,7 +321,9 @@ def config_write(): new_config['General']['git_path'] = GIT_PATH new_config['General']['music_dir'] = MUSIC_DIR - new_config['General']['prefer_lossless'] = int(PREFER_LOSSLESS) + new_config['General']['preferred_quality'] = PREFERRED_QUALITY + new_config['General']['preferred_bitrate'] = PREFERRED_BITRATE + new_config['General']['detect_bitrate'] = int(DETECT_BITRATE) new_config['General']['flac_to_mp3'] = int(FLAC_TO_MP3) new_config['General']['move_files'] = int(MOVE_FILES) new_config['General']['rename_files'] = int(RENAME_FILES) diff --git a/headphones/helpers.py b/headphones/helpers.py index 03bea96c..b82ecba1 100644 --- a/headphones/helpers.py +++ b/headphones/helpers.py @@ -2,6 +2,10 @@ import time from operator import itemgetter import datetime +import headphones + +from headphones import db, logger + def multikeysort(items, columns): comparers = [ ((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1)) for col in columns] @@ -22,6 +26,13 @@ def checked(variable): else: return '' +def radio(variable, pos): + + if variable == pos: + return 'Checked' + else: + return '' + def latinToAscii(unicrap): """ From couch potato @@ -75,4 +86,45 @@ def convert_milliseconds(ms): def today(): today = datetime.date.today() yyyymmdd = datetime.date.isoformat(today) - return yyyymmdd \ No newline at end of file + return yyyymmdd + +def bytes_to_mb(bytes): + + mb = bytes/1048576 + size = '%.1f MB' % mb + return size + +def sortNZBList(resultlist, albumid): + """ + Takes a list of NZBresults from searcher.py and sorts them by distance to + a target size based on bitrate * album duration + """ + + bitrate = headphones.PREFERRED_BITRATE + + myDB = db.DBConnection() + tracks = myDB.select('SELECT TrackDuration from tracks WHERE AlbumID=?', [albumid]) + + # album length in milliseconds + albumlength = sum([pair[0] for pair in tracks]) + + # target size, in bytes + targetsize = albumlength/1000 * bitrate * 128 + + logger.info('Target size: %s' % bytes_to_mb(targetsize)) + + newlist = [] + # resultlist = [(title, size, url), (title2, size2, url2)...] + + for result in resultlist: + + delta = abs(targetsize - result[1]) + newlist.append((result[0], result[1], result[2], delta)) + + bestqual = sorted(newlist, key=lambda title: title[3])[0] + + return bestqual + + + + \ No newline at end of file diff --git a/headphones/importer.py b/headphones/importer.py index 1eff8c8c..1ea0a623 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -30,6 +30,7 @@ def scanMusic(dir=None): if results: lst = [] + bitrates = [] myDB = db.DBConnection() myDB.action('''DELETE from have''') @@ -49,6 +50,11 @@ def scanMusic(dir=None): myDB.action('INSERT INTO have VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?)', [artist, f.album, f.track, f.title, f.length, f.bitrate, f.genre, f.date, f.mb_trackid]) lst.append(artist) + bitrates.append(f.bitrate) + + # Get the average bitrate if the option is selected + if headphones.DETECT_BITRATE: + headphones.PREFERRED_BITRATE = sum(bitrates)/len(bitrates)/1000 artistlist = {}.fromkeys(lst).keys() logger.info(u"Preparing to import %i artists" % len(artistlist)) diff --git a/headphones/searcher.py b/headphones/searcher.py index 1cadb55a..bee94b22 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -17,6 +17,7 @@ def searchNZB(albumid=None): for albums in results: + albumid = albums[2] reldate = albums[3] year = reldate[:4] clname = string.replace(helpers.latinToAscii(albums[0]), ' & ', ' ') @@ -30,7 +31,7 @@ def searchNZB(albumid=None): if headphones.NZBMATRIX: - if headphones.PREFER_LOSSLESS: + if headphones.PREFERRED_QUALITY: categories = "23,22" maxsize = 2000000000 else: @@ -60,7 +61,7 @@ def searchNZB(albumid=None): size = int(item.links[1]['length']) if size < maxsize: resultlist.append((title, size, url)) - logger.info('Found %s. Size: %i' % (title, size)) + logger.info('Found %s. Size: %s' % (title, helpers.bytes_to_mb(size))) else: logger.info('%s is larger than the maxsize for this category, skipping. (Size: %i bytes)' % (title, size)) @@ -69,7 +70,7 @@ def searchNZB(albumid=None): if headphones.NEWZNAB: - if headphones.PREFER_LOSSLESS: + if headphones.PREFERRED_QUALITY: categories = "3040,3010" maxsize = 2000000000 else: @@ -95,7 +96,7 @@ def searchNZB(albumid=None): size = int(item.links[1]['length']) if size < maxsize: resultlist.append((title, size, url)) - logger.info('Found %s. Size: %i' % (title, size)) + logger.info('Found %s. Size: %s' % (title, helpers.bytes_to_mb(size))) else: logger.info('%s is larger than the maxsize for this category, skipping. (Size: %i bytes)' % (title, size)) @@ -104,7 +105,7 @@ def searchNZB(albumid=None): if headphones.NZBSORG: - if headphones.PREFER_LOSSLESS: + if headphones.PREFERRED_QUALITY: categories = "5,3010" maxsize = 2000000000 else: @@ -132,7 +133,7 @@ def searchNZB(albumid=None): size = int(item.links[1]['length']) if size < maxsize: resultlist.append((title, size, url)) - logger.info('Found %s. Size: %i' % (title, size)) + logger.info('Found %s. Size: %s' % (title, helpers.bytes_to_mb(size))) else: logger.info('%s is larger than the maxsize for this category, skipping. (Size: %i bytes)' % (title, size)) @@ -140,9 +141,15 @@ def searchNZB(albumid=None): logger.info(u"No results found. %s" % e) if len(resultlist): - bestqual = sorted(resultlist, key=lambda title: title[1], reverse=True)[0] - - logger.info(u"Found best result: %s (%s) - %s bytes" % (bestqual[0], bestqual[2], bestqual[1])) + + if headphones.PREFERRED_QUALITY == 2 and headphones.PREFERRED_BITRATE: + + bestqual = helpers.sortNZBList(resultlist, albumid) + + else: + bestqual = sorted(resultlist, key=lambda title: title[1], reverse=True)[0] + + logger.info(u"Found best result: %s (%s) - %s" % (bestqual[0], bestqual[2], helpers.bytes_to_mb(bestqual[1]))) downloadurl = bestqual[2] if headphones.SAB_HOST and not headphones.BLACKHOLE: diff --git a/headphones/templates.py b/headphones/templates.py index 1871cf7e..bd5e842a 100644 --- a/headphones/templates.py +++ b/headphones/templates.py @@ -261,16 +261,24 @@ configform = form = ''' - @@ -283,23 +291,8 @@ configform = form = ''' i.e. /Users/name/Music/iTunes or /Volumes/share/music

- - - -
-

Album Quality:

- Prefer lossless
- Convert lossless to mp3 + Album Quality: +

+ Highest Quality

+ Highest Quality including Lossless

+ Preferred Bitrate: + kbps
+ Auto-Detect Preferred Bitrate +

-

-

iTunes:

- Move downloads to Music Folder -

+ Post-Processing: +

+ Move downloads to Music Folder
+ Convert lossless to mp3
+ Rename & add metadata
+ Delete leftover files
+ Add album art +

- Renaming & Metadata: -

- Rename & add metadata -
- Delete leftover files -

-
-
-

Album Art:

- Add album art -


diff --git a/headphones/webserve.py b/headphones/webserve.py index a3097c8c..9beb25bf 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -13,7 +13,7 @@ import threading import headphones from headphones.mb import getReleaseGroup from headphones import templates, logger, searcher, db, importer, helpers, mb -from headphones.helpers import checked +from headphones.helpers import checked, radio class WebInterface(object): @@ -509,13 +509,17 @@ class WebInterface(object): checked(headphones.NZBSORG), headphones.NZBSORG_UID, headphones.NZBSORG_HASH, - checked(headphones.PREFER_LOSSLESS), - checked(headphones.FLAC_TO_MP3), + radio(headphones.PREFERRED_QUALITY, 0), + radio(headphones.PREFERRED_QUALITY, 1), + radio(headphones.PREFERRED_QUALITY, 2), + headphones.PREFERRED_BITRATE, + checked(headphones.DETECT_BITRATE), checked(headphones.MOVE_FILES), - headphones.MUSIC_DIR, + checked(headphones.FLAC_TO_MP3), checked(headphones.RENAME_FILES), checked(headphones.CLEANUP_FILES), - checked(headphones.ADD_ALBUM_ART) + checked(headphones.ADD_ALBUM_ART), + headphones.MUSIC_DIR )) page.append(templates._footer % headphones.CURRENT_VERSION) return page @@ -526,7 +530,7 @@ class WebInterface(object): def configUpdate(self, http_host='0.0.0.0', http_username=None, http_port=8181, http_password=None, launch_browser=0, sab_host=None, sab_username=None, sab_apikey=None, sab_password=None, sab_category=None, download_dir=None, blackhole=0, blackhole_dir=None, usenet_retention=None, nzbmatrix=0, nzbmatrix_username=None, nzbmatrix_apikey=None, newznab=0, newznab_host=None, newznab_apikey=None, - nzbsorg=0, nzbsorg_uid=None, nzbsorg_hash=None, prefer_lossless=0, flac_to_mp3=0, move_files=0, music_dir=None, rename_files=0, cleanup_files=0, add_album_art=0): + nzbsorg=0, nzbsorg_uid=None, nzbsorg_hash=None, preferred_quality=0, preferred_bitrate=None, detect_bitrate=0, flac_to_mp3=0, move_files=0, music_dir=None, rename_files=0, cleanup_files=0, add_album_art=0): headphones.HTTP_HOST = http_host headphones.HTTP_PORT = http_port @@ -551,7 +555,9 @@ class WebInterface(object): headphones.NZBSORG = nzbsorg headphones.NZBSORG_UID = nzbsorg_uid headphones.NZBSORG_HASH = nzbsorg_hash - headphones.PREFER_LOSSLESS = prefer_lossless + headphones.PREFERRED_QUALITY = int(preferred_quality) + headphones.PREFERRED_BITRATE = int(preferred_bitrate) + headphones.DETECT_BITRATE = detect_bitrate headphones.FLAC_TO_MP3 = flac_to_mp3 headphones.MOVE_FILES = move_files headphones.MUSIC_DIR = music_dir