mirror of
https://github.com/rembo10/headphones.git
synced 2026-05-20 02:25:31 +01:00
Merge branch 'develop'
This commit is contained in:
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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'] = {}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user