Merge branch 'develop'

This commit is contained in:
rembo10
2012-10-20 22:25:24 -03:00
9 changed files with 137 additions and 42 deletions

View File

@@ -34,7 +34,7 @@
<ul>
<li><a href="#tabs-1">Scan Music Library</a></li>
<li><a href="#tabs-2">Imports</a></li>
<li><a href="#tabs-3">Force search</a></li>
<li><a href="#tabs-3">Force Actions</a></li>
</ul>
<div id="tabs-1" class="configtable">
<fieldset>
@@ -58,11 +58,12 @@
%endif
</div>
<div class="row checkbox">
<input type="checkbox" name="autoadd" value="1" ${checked(headphones.ADD_ARTISTS)}><label>Automatically add new artists</label>
<input type="checkbox" name="autoadd" id="autoadd" value="1" ${checked(headphones.ADD_ARTISTS)}><label>Automatically add new artists</label>
</div>
</fieldset>
<input type="button" value="Save Changes" onclick="doAjaxCall('musicScan',$(this),'tabs',true);return false;" data-success="Library will be scanned">
<input type="button" value="Save Changes and Scan" onclick="addScanAction();doAjaxCall('musicScan',$(this),'tabs',true);return false;" data-success="Changes saved. Library will be scanned">
<input type="button" value="Save Changes without Scanning Library" onclick="doAjaxCall('musicScan',$(this),'tabs',true);return false;" data-success="Changes Saved Successfully">
</form>
</div>
@@ -87,7 +88,27 @@
</fieldset>
<input type="button" value="Save changes" onclick="doAjaxCall('importLastFM',$(this),'tabs',true);return false;" data-success="Last.fm artists will be imported" data-error="Fill in a last.fm username"/>
</form>
</div>
<br/>
<form action="importLastFMTag" method="GET" id="importLastFMTag">
<fieldset>
<legend>Import Last.FM Tag</legend>
<p>Enter tag from which you want import top artists:</p>
<br/>
<div class="row">
<label>Tag</label>
<input type="text" value="" onfocus="if
(this.value==this.defaultValue) this.value='';" name="tag" id="tag" size="18" />
<br/>
<label>Limit</label>
<input type="text" value="50" onfocus="if
(this.value==this.defaultValue) this.value='';" name="limit" id="limit" size="18" />
</div>
</fieldset>
<input type="submit" />
</form>
</div>
<div id="tabs-3" class="configtable">
<fieldset>
@@ -97,7 +118,19 @@
<a href="#" onclick="doAjaxCall('forceUpdate',$(this))" data-success="Update active artists successful" data-error="Error forcing update artists"><span class="ui-icon ui-icon-heart"></span>Force Update Active Artists</a>
<a href="#" onclick="doAjaxCall('forcePostProcess',$(this))" data-success="Post-Processor is being loaded" data-error="Error during Post-Processing"><span class="ui-icon ui-icon-wrench"></span>Force Post-Process Albums in Download Folder</a>
<a href="#" onclick="doAjaxCall('checkGithub',$(this))" data-success="Checking for update successful" data-error="Error checking for update"><span class="ui-icon ui-icon-refresh"></span>Check for Headphones Updates</a>
<a href="#" onclick="doAjaxCall('deleteEmptyArtists',$(this))" data-success="Empty Artists deleted" data-error="Error deleting empty artists"><span class="ui-icon ui-icon-trash"></span>Delete empty Artists</a>
<a href="#" id="delete_empty_artists"><span class="ui-icon ui-icon-trash"></span>Delete empty Artists</a>
<div id="emptyartistdialog" title="Confirm Artist Deletion" style="display:none" class="configtable">
%if emptyArtists:
<h3>The following artists will be deleted:</h3>
%for emptyArtist in emptyArtists:
<p>${emptyArtist['ArtistName']}</p>
%endfor
<input type="button" value="Delete Empty Artists" onclick="doAjaxCall('deleteEmptyArtists',$(this))" data-success="Empty Artists deleted" data-error="Error deleting empty artists">
%else:
No empty artists found.
%endif
</div>
</div>
</fieldset>
@@ -106,11 +139,19 @@
</%def>
<%def name="javascriptIncludes()">
<script>
function addScanAction() {
$('#autoadd').append('<input type="hidden" name="scan" value=1 />');
};
function initThisPage() {
$('#manage_albums').click(function() {
$('#dialog').dialog();
return false;
});
$('#delete_empty_artists').click(function() {
$('#emptyartistdialog').dialog();
return false;
});
jQuery( "#tabs" ).tabs();
initActions();
};

View File

@@ -584,7 +584,7 @@ def launch_browser(host, port, root):
def config_write():
new_config = ConfigObj()
new_config = ConfigObj(encoding="UTF-8")
new_config.filename = CONFIG_FILE
new_config['General'] = {}

View File

@@ -35,6 +35,10 @@ def dbFilename(filename="headphones.db"):
return os.path.join(headphones.DATA_DIR, filename)
def getCacheSize():
#this will protect against typecasting problems produced by empty string and None settings
if not headphones.CACHE_SIZEMB:
#sqlite will work with this (very slowly)
return 0
return int(headphones.CACHE_SIZEMB)
class DBConnection:

View File

@@ -226,11 +226,13 @@ def extract_song_data(s):
def smartMove(src, dest, delete=True):
from headphones import logger
source_dir = os.path.dirname(src)
filename = os.path.basename(src)
if os.path.isfile(os.path.join(dest, filename)):
logger.info('Destination file exists: %s' % os.path.join(dest, filename).decode(headphones.SYS_ENCODING))
logger.info('Destination file exists: %s' % os.path.join(dest, filename).decode(headphones.SYS_ENCODING, 'replace'))
title = os.path.splitext(filename)[0]
ext = os.path.splitext(filename)[1]
i = 1
@@ -244,7 +246,7 @@ def smartMove(src, dest, delete=True):
os.rename(src, os.path.join(source_dir, newfile))
filename = newfile
except Exception, e:
logger.warn('Error renaming %s: %s' % (src.decode(headphones.SYS_ENCODING), e))
logger.warn('Error renaming %s: %s' % (src.decode(headphones.SYS_ENCODING, 'replace'), str(e).decode(headphones.SYS_ENCODING, 'replace')))
break
try:
@@ -254,4 +256,4 @@ def smartMove(src, dest, delete=True):
shutil.copy(os.path.join(source_dir, filename), os.path.join(dest, filename))
return True
except Exception, e:
logger.warn('Error moving file %s: %s' % (filename.decode(headphones.SYS_ENCODING), e))
logger.warn('Error moving file %s: %s' % (filename.decode(headphones.SYS_ENCODING, 'replace'), str(e).decode(headphones.SYS_ENCODING, 'replace')))

View File

@@ -126,6 +126,41 @@ def getArtists():
for artistid in artistlist:
importer.addArtisttoDB(artistid)
def getTagTopArtists(tag, limit=50):
myDB = db.DBConnection()
results = myDB.select('SELECT ArtistID from artists')
url = 'http://ws.audioscrobbler.com/2.0/?method=tag.gettopartists&limit=%s&tag=%s&api_key=%s' % (limit, tag, api_key)
data = urllib2.urlopen(url, timeout=20).read()
try:
d = minidom.parseString(data)
except:
logger.error("Could not parse artist list from last.fm data")
return
artists = d.getElementsByTagName("artist")
artistlist = []
for artist in artists:
mbidnode = artist.getElementsByTagName("mbid")[0].childNodes
for node in mbidnode:
artist_mbid = node.data
try:
if not any(artist_mbid in x for x in results):
artistlist.append(artist_mbid)
except:
continue
from headphones import importer
for artistid in artistlist:
importer.addArtisttoDB(artistid)
def getAlbumDescription(rgid, artist, album):
myDB = db.DBConnection()

View File

@@ -66,23 +66,23 @@ def encode(albumPath):
for music in musicFiles:
infoMusic=MediaFile(music)
if headphones.ENCODER == 'lame':
if not any(music.lower().endswith('.' + x) for x in ["mp3", "wav"]):
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:
if (music.lower().endswith('.mp3') and (infoMusic.bitrate/1000<=headphones.BITRATE)):
if (music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.mp3') and (int(infoMusic.bitrate/1000)<=headphones.BITRATE)):
logger.info('Music "%s" has bitrate<="%skbit" will not be reencoded' % (music.decode(headphones.SYS_ENCODING, 'replace'),headphones.BITRATE))
else:
command(encoder,music,musicTempFiles[i],albumPath)
ifencoded=1
else:
if headphones.ENCODEROUTPUTFORMAT=='ogg':
if music.lower().endswith('.ogg'):
if music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.ogg'):
logger.warn('Can not reencode .ogg music "%s"' % (music.decode(headphones.SYS_ENCODING, 'replace')))
else:
command(encoder,music,musicTempFiles[i],albumPath)
ifencoded=1
elif (headphones.ENCODEROUTPUTFORMAT=='mp3' or headphones.ENCODEROUTPUTFORMAT=='m4a'):
if (music.lower().endswith('.'+headphones.ENCODEROUTPUTFORMAT) and (infoMusic.bitrate/1000<=headphones.BITRATE)):
if (music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.'+headphones.ENCODEROUTPUTFORMAT) and (int(infoMusic.bitrate/1000)<=headphones.BITRATE)):
logger.info('Music "%s" has bitrate<="%skbit" will not be reencoded' % (music.decode(headphones.SYS_ENCODING, 'replace'),headphones.BITRATE))
else:
command(encoder,music,musicTempFiles[i],albumPath)
@@ -102,6 +102,7 @@ def encode(albumPath):
return musicFinalFiles
def command(encoder,musicSource,musicDest,albumPath):
return_code=1
cmd=''
startMusicTime=time.time()
@@ -134,15 +135,20 @@ def command(encoder,musicSource,musicDest,albumPath):
else:
cmd=cmd+' '+ headphones.ADVANCEDENCODER
cmd=cmd+ ' "' + musicDest + '"'
print cmd
time.sleep(10)
return_code = call(cmd, shell=True)
if (return_code==0) and (os.path.exists(musicDest)):
if headphones.DELETE_LOSSLESS_FILES:
os.remove(musicSource)
shutil.move(musicDest,albumPath)
logger.info('Music "%s" encoded in %s' % (musicSource,getTimeEncode(startMusicTime)))
logger.debug(cmd)
try:
return_code = call(cmd, shell=True)
if (return_code==0) and (os.path.exists(musicDest)):
if headphones.DELETE_LOSSLESS_FILES:
os.remove(musicSource)
shutil.move(musicDest,albumPath)
logger.info('Music "%s" encoded in %s' % (musicSource,getTimeEncode(startMusicTime)))
except subprocess.CalledProcessError, e:
logger.warn('Music "%s" encoding error : %s' % (musicSource, e.output))
def getTimeEncode(start):
seconds =int(time.time()-start)
hours = seconds / 3600

View File

@@ -376,7 +376,7 @@ def moveFiles(albumpath, release, tracks):
folder = helpers.replace_all(headphones.FOLDER_FORMAT, values)
folder = folder.replace('./', '_/').replace(':','_').replace('?','_').replace('/.','/_')
folder = folder.replace('./', '_/').replace(':','_').replace('?','_').replace('/.','/_').replace('<','_').replace('>','_')
if folder.endswith('.'):
folder = folder.replace(folder[len(folder)-1], '_')
@@ -676,7 +676,7 @@ def renameFiles(albumpath, downloaded_track_list, release):
logger.debug("Renaming for: " + downloaded_track.decode(headphones.SYS_ENCODING) + " is not neccessary")
continue
logger.debug('Renaming %s ---> %s' % (downloaded_track.decode(headphones.SYS_ENCODING), new_file_name.decode(headphones.SYS_ENCODING)))
logger.debug('Renaming %s ---> %s' % (downloaded_track.decode(headphones.SYS_ENCODING,'replace'), new_file_name.decode(headphones.SYS_ENCODING,'replace')))
try:
os.rename(downloaded_track, new_file)
except Exception, e:

View File

@@ -620,8 +620,8 @@ def preprocess(resultlist):
#TODO: Do we want rar checking in here to try to keep unknowns out?
#or at least the option to do so?
except ExpatError:
logger.error('Unable to parse the best result NZB. Skipping.')
except Exception, e:
logger.error('Unable to parse the best result NZB. Error: ' + str(e) + '. (Make sure your username/password/API is correct for provider: ' + result[3])
continue
return nzb, result
else:

View File

@@ -188,24 +188,26 @@ class WebInterface(object):
myDB.action('DELETE from artists WHERE ArtistID=?', [ArtistID])
myDB.action('DELETE from albums WHERE ArtistID=?', [ArtistID])
myDB.action('DELETE from tracks WHERE ArtistID=?', [ArtistID])
myDB.action('DELETE from allalbums WHERE ArtistID=?', [ArtistID])
myDB.action('DELETE from alltracks WHERE ArtistID=?', [ArtistID])
myDB.action('INSERT OR REPLACE into blacklist VALUES (?)', [ArtistID])
raise cherrypy.HTTPRedirect("home")
deleteArtist.exposed = True
def deleteEmptyArtists(self):
logger.info(u"Deleting all empty artists")
myDB = db.DBConnection()
emptyArtistIDs = [row['ArtistID'] for row in myDB.select("SELECT ArtistID FROM artists WHERE HaveTracks == 0")]
emptyArtistIDs = [row['ArtistID'] for row in myDB.select("SELECT ArtistID FROM artists WHERE LatestAlbum IS NULL")]
for ArtistID in emptyArtistIDs:
logger.info(u"Deleting all traces of artist: " + ArtistID)
myDB.action('DELETE from artists WHERE ArtistID=?', [ArtistID])
myDB.action('DELETE from albums WHERE ArtistID=?', [ArtistID])
myDB.action('DELETE from tracks WHERE ArtistID=?', [ArtistID])
myDB.action('DELETE from allalbums WHERE ArtistID=?', [ArtistID])
myDB.action('DELETE from alltracks WHERE ArtistID=?', [ArtistID])
myDB.action('INSERT OR REPLACE into blacklist VALUES (?)', [ArtistID])
deleteEmptyArtists.exposed = True
def refreshArtist(self, ArtistID):
threading.Thread(target=importer.addArtisttoDB, args=[ArtistID]).start()
raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID)
@@ -293,7 +295,9 @@ class WebInterface(object):
upcoming.exposed = True
def manage(self):
return serve_template(templatename="manage.html", title="Manage")
myDB = db.DBConnection()
emptyArtists = myDB.select("SELECT * FROM artists WHERE LatestAlbum IS NULL")
return serve_template(templatename="manage.html", title="Manage", emptyArtists=emptyArtists)
manage.exposed = True
def manageArtists(self):
@@ -348,10 +352,14 @@ class WebInterface(object):
headphones.LASTFM_USERNAME = username
headphones.config_write()
threading.Thread(target=lastfm.getArtists).start()
time.sleep(10)
raise cherrypy.HTTPRedirect("home")
importLastFM.exposed = True
def importLastFMTag(self, tag, limit):
threading.Thread(target=lastfm.getTagTopArtists, args=(tag, limit)).start()
raise cherrypy.HTTPRedirect("home")
importLastFMTag.exposed = True
def importItunes(self, path):
headphones.PATH_TO_XML = path
headphones.config_write()
@@ -360,15 +368,15 @@ class WebInterface(object):
raise cherrypy.HTTPRedirect("home")
importItunes.exposed = True
def musicScan(self, path, redirect=None, autoadd=0):
def musicScan(self, path, scan=0, redirect=None, autoadd=0):
headphones.ADD_ARTISTS = autoadd
headphones.MUSIC_DIR = path
headphones.config_write()
try:
threading.Thread(target=librarysync.libraryScan).start()
except Exception, e:
logger.error('Unable to complete the scan: %s' % e)
time.sleep(10)
headphones.config_write()
if scan:
try:
threading.Thread(target=librarysync.libraryScan).start()
except Exception, e:
logger.error('Unable to complete the scan: %s' % e)
if redirect:
raise cherrypy.HTTPRedirect(redirect)
else:
@@ -403,7 +411,6 @@ class WebInterface(object):
myDB = db.DBConnection()
history = myDB.select('''SELECT * from snatched order by DateAdded DESC''')
return serve_template(templatename="history.html", title="History", history=history)
return page
history.exposed = True
def logs(self):
@@ -680,7 +687,7 @@ class WebInterface(object):
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=None, **kwargs):
customsleep=None, hpuser=None, hppass=None, preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, cache_sizemb=0, **kwargs):
headphones.HTTP_HOST = http_host
headphones.HTTP_PORT = http_port
@@ -783,7 +790,7 @@ class WebInterface(object):
headphones.CUSTOMSLEEP = customsleep
headphones.HPUSER = hpuser
headphones.HPPASS = hppass
headphones.CACHE_SIZEMB = cache_sizemb
headphones.CACHE_SIZEMB = int(cache_sizemb)
# Handle the variable config options. Note - keys with False values aren't getting passed