New feature: Preferred Quality. Specify a target bit rate or let the Music Scanner detect it for you

This commit is contained in:
Remy
2011-07-16 21:48:06 -07:00
parent 8655eecd73
commit 974755927d
7 changed files with 122 additions and 50 deletions
+4
View File
@@ -86,6 +86,10 @@ h1{
.smalltext{
font-size: 11px;
}
.smalltext2{
font-size: 11px;
margin-left: 45px;
}
.mediumtext{
font-size: 16px;
margin-left: 100px;
+14 -10
View File
@@ -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)
+53 -1
View File
@@ -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
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
+6
View File
@@ -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))
+16 -9
View File
@@ -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:
+16 -23
View File
@@ -261,16 +261,24 @@ configform = form = '''
<table class="configtable" summary="Quality & Post Processing">
<tr>
<td>
<p><b>Album Quality:</b></p>
<input type="checkbox" name="prefer_lossless" value="1" %s />Prefer lossless <br>
<input type="checkbox" name="flac_to_mp3" value="1" %s />Convert lossless to mp3
<b>Album Quality:</b>
<p>
<input type="radio" name="preferred_quality" value="0" %s />Highest Quality<br /><br />
<input type="radio" name="preferred_quality" value="1" %s />Highest Quality including Lossless<br /><br />
<input type="radio" name="preferred_quality" value="2" %s />Preferred Bitrate:
<input type="text" name="preferred_bitrate" value="%s" size="5" maxlength="5" />kbps <br>
<i class="smalltext2"><input type="checkbox" name="detect_bitrate" value="1" %s />Auto-Detect Preferred Bitrate </i>
</p>
</td>
<td>
<p>
<p><b>iTunes:</b></p>
<input type="checkbox" name="move_files" value="1" %s />Move downloads to Music Folder
</p>
<b>Post-Processing:</b>
<p>
<input type="checkbox" name="move_files" value="1" %s />Move downloads to Music Folder<br />
<input type="checkbox" name="flac_to_mp3" value="1" %s />Convert lossless to mp3<br>
<input type="checkbox" name="rename_files" value="1" %s />Rename &amp; add metadata<br>
<input type="checkbox" name="cleanup_files" value="1" %s />Delete leftover files<br>
<input type="checkbox" name="add_album_art" value="1" %s>Add album art
</p>
</td>
</tr>
@@ -283,23 +291,8 @@ configform = form = '''
<i class="smalltext">i.e. /Users/name/Music/iTunes or /Volumes/share/music</i>
</p>
</td>
<td>
<b>Renaming &amp; Metadata:</b>
<p>
<input type="checkbox" name="rename_files" value="1" %s />Rename &amp; add metadata
<br>
<input type="checkbox" name="cleanup_files" value="1" %s />Delete leftover files
</p>
</td>
</tr>
<tr>
<td>
<br>
<p><b>Album Art:</b></p>
<input type="checkbox" name="add_album_art" value="1" %s>Add album art
</td>
</tr>
</table>
<p class="center"><input type="submit" value="Save Changes"><br>
+13 -7
View File
@@ -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