Merge branch 'develop'

This commit is contained in:
rembo10
2012-07-05 12:55:34 +05:30
11 changed files with 218 additions and 128 deletions

View File

@@ -153,7 +153,7 @@
</div>
</fieldset>
<fieldset>
<legend>Blackhole</legend>
<legend>Usenet</legend>
<div class="row checkbox">
<input id="useblackhole" type="checkbox" name="blackhole" value=1 ${config['use_blackhole']} /><label>Use Black Hole</label>
</div>

View File

@@ -866,7 +866,8 @@ div#artistheader h2 a {
text-align: left;
}
#artist_table th#status,
#artist_table th#albumart {
#artist_table th#albumart,
#artist_table th#lastupdated {
min-width: 50px;
text-align: left;
}
@@ -878,7 +879,8 @@ div#artistheader h2 a {
text-align: left;
vertical-align: middle;
}
#artist_table td#status {
#artist_table td#status,
#artist_table td#lastupdated {
min-width: 50px;
text-align: left;
vertical-align: middle;
@@ -1295,6 +1297,7 @@ div#artistheader h2 a {
clear: both;
}
#album_table th#albumname,
#album_table th#artistname,
#upcoming_table th#artistname,
#wanted_table th#artistname {
min-width: 150px;
@@ -1317,6 +1320,7 @@ div#artistheader h2 a {
vertical-align: middle;
}
#album_table td#albumname,
#album_table td#artistname,
#album_table td#reldate,
#album_table td#type,
#track_table td#duration,

View File

@@ -6,6 +6,7 @@
<%def name="headerIncludes()">
<div id="subhead_container">
<div id="subhead_menu">
<a id="menu_link_edit" href="manageAlbums">Manage Albums</a>
<a id="menu_link_edit" href="manageArtists">Manage Artists</a>
%if not headphones.ADD_ARTISTS:
<a id="menu_link_edit" href="manageNew">Manage New Artists</a>
@@ -101,4 +102,4 @@
initThisPage();
});
</script>
</%def>
</%def>

View File

@@ -0,0 +1,148 @@
<%inherit file="base.html" />
<%!
from headphones import db
import headphones
%>
<%def name="headerIncludes()">
<div id="subhead_container">
&nbsp;
</div>
<a href="manage" class="back">&laquo; Back to manage overview</a>
</%def>
<%def name="body()">
<div class="table_wrapper">
<div id="manageheader" class="title">
<h1 class="clearfix"><img src="interfaces/default/images/icon_manage.png" alt="manage"/>Manage Albums</h1>
</div>
<form action="markAlbums" method="get" id="markAlbums">
<div id="markalbum">Mark selected albums as
<select name="action" onChange="doAjaxCall('markAlbums',$(this),'table',true);" data-error="You didn't select any albums">
<option disabled="disabled" selected="selected">Choose...</option>
<option value="Wanted">Wanted</option>
<option value="WantedNew">Wanted (new only)</option>
<option value="WantedLossless">Wanted (lossless)</option>
<option value="Skipped">Skipped</option>
<option value="Downloaded">Downloaded</option>
</select>
<input type="hidden" value="Go">
</div>
<table class="display" id="album_table">
<thead>
<tr>
<th id="select"><input type="checkbox" onClick="toggle(this)" /></th>
<th id="albumname">Album</th>
<th id="artistname">Artist</th>
<th id="reldate">Date</th>
<th id="type">Type</th>
<th id="status">Status</th>
<th id="have">Have</th>
<th id="bitrate">Bitrate</th>
<th id="albumformat">Format</th>
</tr>
</thead>
<tbody>
%for album in albums:
<%
if album['Status'] == 'Skipped':
grade = 'Z'
elif album['Status'] == 'Wanted':
grade = 'X'
elif album['Status'] == 'Snatched':
grade = 'C'
else:
grade = 'A'
myDB = db.DBConnection()
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE AlbumID=?', [album['AlbumID']]))
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE AlbumID=? AND Location IS NOT NULL', [album['AlbumID']])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND AlbumTitle LIKE ?', [album['ArtistName'], album['AlbumTitle']]))
try:
percent = (havetracks*100.0)/totaltracks
if percent > 100:
percent = 100
except (ZeroDivisionError, TypeError):
percent = 0
totaltracks = '?'
avgbitrate = myDB.action("SELECT AVG(BitRate) FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0]
if avgbitrate:
bitrate = str(int(avgbitrate)/1000) + ' kbps'
else:
bitrate = ''
albumformatcount = myDB.action("SELECT COUNT(DISTINCT Format) FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0]
if albumformatcount == 1:
albumformat = myDB.action("SELECT DISTINCT Format FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0]
elif albumformatcount > 1:
albumformat = 'Mixed'
else:
albumformat = ''
lossy_formats = [str.upper(fmt) for fmt in headphones.LOSSY_MEDIA_FORMATS]
%>
<tr class="grade${grade}">
<td id="select"><input type="checkbox" name="${album['AlbumID']}" class="checkbox" /></td>
<td id="albumname"><a href="albumPage?AlbumID=${album['AlbumID']}">${album['AlbumTitle']}</a></td>
<td id="artistname"><a href="artistPage?ArtistID=${album['ArtistID']}">${album['ArtistName']}</a></td>
<td id="reldate">${album['ReleaseDate']}</td>
<td id="type">${album['Type']}</td>
<td id="status">${album['Status']}</td>
<td id="have"><span title="${percent}"><span><div class="progress-container"><div style="width:${percent}%"><div class="havetracks">${havetracks}/${totaltracks}</div></div></div></td>
<td id="bitrate">${bitrate}</td>
<td id="albumformat">${albumformat}</td>
</tr>
%endfor
</tbody>
</table>
</form>
</div>
</%def>
<%def name="headIncludes()">
<link rel="stylesheet" href="interfaces/default/css/data_table.css">
</%def>
<%def name="javascriptIncludes()">
<script src="js/libs/jquery.dataTables.min.js"></script>
<script>
function initThisPage() {
$('#album_table').dataTable({
"bDestroy": true,
"aoColumns": [
null,
null,
null,
null,
null,
null,
{ "sType": "title-numeric"},
null,
null
],
"aoColumnDefs": [
{ 'bSortable': false, 'aTargets': [ 0 ] }
],
"oLanguage": {
"sLengthMenu":"Show _MENU_ albums per page",
"sEmptyTable": "No album information available",
"sInfo":"Showing _TOTAL_ albums",
"sInfoEmpty":"Showing 0 to 0 of 0 albums",
"sInfoFiltered":"(filtered from _MAX_ total albums)",
"sSearch": ""},
"bPaginate": false,
"aaSorting": [[5, 'desc']]
});
resetFilters("albums");
}
$(document).ready(function() {
initThisPage();
});
</script>
</%def>

View File

@@ -35,6 +35,7 @@
<th id="name">Artist Name</th>
<th id="status">Status</th>
<th id="album">Latest Album</th>
<th id="lastupdated">Last Updated</th>
</tr>
</thead>
<tbody>
@@ -56,13 +57,20 @@
else:
releasedate = ''
albumdisplay = '<i>None</i>'
if not artist['LastUpdated']:
lastupdated = "Never"
else:
lastupdated = artist['LastUpdated']
%>
<tr class="grade${grade}">
<td id="select"><input type="checkbox" name="${artist['ArtistID']}" class="checkbox" /></td>
<td id="albumart"><div id="artistImg"><img class="albumArt" height="50" width="50"></div></td>
<td id="name"><span title="${artist['ArtistSortName']}"></span><a href="artistPage?ArtistID=${artist['ArtistID']}" title="${artist['ArtistID']}">${artist['ArtistName']}</a></td>
<td id="albumart"><div id="artistImg"><img class="albumArt" id="${artist['ArtistID']}" height="50" width="50"></div></td>
<td id="name"><span title="${artist['ArtistSortName']}"></span><a href="artistPage?ArtistID=${artist['ArtistID']}">${artist['ArtistName']}</a></td>
<td id="status">${artist['Status']}</td>
<td id="album"><span title="${releasedate}"></span><a href="albumPage?AlbumID=${artist['AlbumID']}">${albumdisplay}</a></td>
<td id="lastupdated">${lastupdated}</td>
</tr>
%endfor
</tbody>
@@ -79,9 +87,9 @@
<script src="js/libs/jquery.dataTables.min.js"></script>
<script>
function getArtistArt() {
$("table#artist_table tr td#name").each(function(){
var id = $(this).children('a').attr('title');
var image = $(this).parent().find("td#albumart img");
$("table#artist_table tr td#albumart #artistImg").each(function(){
var id = $(this).children('img').attr('id');
var image = $(this).children('img');
if ( !image.hasClass('done') ) {
image.addClass('done');
getThumb(image,id,'artist');
@@ -98,7 +106,8 @@
null,
{ "sType": "title-string"},
null,
{ "sType": "title-string"}
{ "sType": "title-string"},
null
],
"oLanguage": {
"sSearch" : ""},

View File

@@ -298,7 +298,7 @@ def initialize():
DOWNLOAD_DIR = check_setting_str(CFG, 'General', 'download_dir', '')
BLACKHOLE = bool(check_setting_int(CFG, 'General', 'blackhole', 0))
BLACKHOLE_DIR = check_setting_str(CFG, 'General', 'blackhole_dir', '')
USENET_RETENTION = check_setting_int(CFG, 'General', 'usenet_retention', '')
USENET_RETENTION = check_setting_int(CFG, 'General', 'usenet_retention', '1500')
INCLUDE_EXTRAS = bool(check_setting_int(CFG, 'General', 'include_extras', 0))
AUTOWANT_UPCOMING = bool(check_setting_int(CFG, 'General', 'autowant_upcoming', 1))
AUTOWANT_ALL = bool(check_setting_int(CFG, 'General', 'autowant_all', 0))
@@ -679,6 +679,7 @@ def dbcheck():
c.execute('CREATE TABLE IF NOT EXISTS have (ArtistName TEXT, AlbumTitle TEXT, TrackNumber TEXT, TrackTitle TEXT, TrackLength TEXT, BitRate TEXT, Genre TEXT, Date TEXT, TrackID TEXT, Location TEXT, CleanName TEXT, Format TEXT)')
c.execute('CREATE TABLE IF NOT EXISTS lastfmcloud (ArtistName TEXT, ArtistID TEXT, Count INTEGER)')
c.execute('CREATE TABLE IF NOT EXISTS descriptions (ArtistID TEXT, ReleaseGroupID TEXT, ReleaseID TEXT, Summary TEXT, Content TEXT, LastUpdated TEXT)')
c.execute('CREATE TABLE IF NOT EXISTS blacklist (ArtistID TEXT UNIQUE)')
c.execute('CREATE TABLE IF NOT EXISTS releases (ReleaseID TEXT, ReleaseGroupID TEXT, UNIQUE(ReleaseID, ReleaseGroupID))')
c.execute('CREATE INDEX IF NOT EXISTS tracks_albumid ON tracks(AlbumID ASC)')
c.execute('CREATE INDEX IF NOT EXISTS album_artistid_reldate ON albums(ArtistID ASC, ReleaseDate DESC)')

View File

@@ -56,14 +56,20 @@ def artistlist_to_mbids(artistlist, forced=False):
except IndexError:
logger.info('MusicBrainz query turned up no matches for: %s' % artist)
continue
# Check if it's blacklisted/various artists
myDB = db.DBConnection()
bl_artist = myDB.action('SELECT * FROM blacklist WHERE ArtistID=?', [artistid]).fetchone()
if bl_artist or artistid == various_artists_mbid:
logger.info("Artist ID for '%s' is either blacklisted or Various Artists. To add artist, you must do it manually (Artist ID: %s)" % (artist, artistid))
continue
# Add to database if it doesn't exist
if artistid != various_artists_mbid and not is_exists(artistid):
if not is_exists(artistid):
addArtisttoDB(artistid)
# Just update the tracks if it does
else:
myDB = db.DBConnection()
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=?', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ?', [artist]))
myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistID=?', [havetracks, artistid])
@@ -82,7 +88,7 @@ def addArtistIDListToDB(artistidlist):
def addArtisttoDB(artistid, extrasonly=False):
# Putting this here to get around the circular import
# Putting this here to get around the circular import. We're using this to update thumbnails for artist/albums
from headphones import cache
# Can't add various artists - throws an error from MB
@@ -91,6 +97,9 @@ def addArtisttoDB(artistid, extrasonly=False):
return
myDB = db.DBConnection()
# Delete from blacklist if it's on there
myDB.action('DELETE from blacklist WHERE ArtistID=?', [artistid])
# We need the current minimal info in the database instantly
# so we don't throw a 500 error when we redirect to the artistPage

View File

@@ -26,13 +26,10 @@ def libraryScan(dir=None):
if not dir:
dir = headphones.MUSIC_DIR
try:
dir = str(dir)
except UnicodeEncodeError:
dir = unicode(dir).encode('unicode_escape')
dir = dir.encode(headphones.SYS_ENCODING)
if not os.path.isdir(dir):
logger.warn('Cannot find directory: %s. Not scanning' % dir)
logger.warn('Cannot find directory: %s. Not scanning' % dir.decode(headphones.SYS_ENCODING))
return
myDB = db.DBConnection()
@@ -57,14 +54,13 @@ def libraryScan(dir=None):
if any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
song = os.path.join(r, files)
file = unicode(os.path.join(r, files), headphones.SYS_ENCODING, errors='replace')
# Try to read the metadata
try:
f = MediaFile(song)
except:
logger.error('Cannot read file: ' + file)
logger.error('Cannot read file: ' + song.decode(headphones.SYS_ENCODING))
continue
# Grab the bitrates for the auto detect bit rate option
@@ -87,7 +83,7 @@ def libraryScan(dir=None):
track = myDB.action('SELECT TrackID from tracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [f_artist, f.album, f.title]).fetchone()
if track:
myDB.action('UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE TrackID=?', [file, f.bitrate, f.format, track['TrackID']])
myDB.action('UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE TrackID=?', [song.decode(headphones.SYS_ENCODING), f.bitrate, f.format, track['TrackID']])
continue
# Try to match on mbid if available and we couldn't find a match based on metadata
@@ -98,114 +94,16 @@ def libraryScan(dir=None):
track = myDB.action('SELECT TrackID from tracks WHERE TrackID=?', [f.mb_trackid]).fetchone()
if track:
myDB.action('UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE TrackID=?', [file, f.bitrate, f.format, track['TrackID']])
myDB.action('UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE TrackID=?', [song.decode(headphones.SYS_ENCODING), f.bitrate, f.format, track['TrackID']])
continue
# if we can't find a match in the database on a track level, it might be a new artist or it might be on a non-mb release
new_artists.append(f_artist)
# The have table will become the new database for unmatched tracks (i.e. tracks with no associated links in the database
myDB.action('INSERT INTO have (ArtistName, AlbumTitle, TrackNumber, TrackTitle, TrackLength, BitRate, Genre, Date, TrackID, Location, CleanName, Format) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [f_artist, f.album, f.track, f.title, f.length, f.bitrate, f.genre, f.date, f.mb_trackid, file, helpers.cleanName(f_artist+' '+f.album+' '+f.title), f.format])
myDB.action('INSERT INTO have (ArtistName, AlbumTitle, TrackNumber, TrackTitle, TrackLength, BitRate, Genre, Date, TrackID, Location, CleanName, Format) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [f_artist, f.album, f.track, f.title, f.length, f.bitrate, f.genre, f.date, f.mb_trackid, song.decode(headphones.SYS_ENCODING), helpers.cleanName(f_artist+' '+f.album+' '+f.title), f.format])
logger.info('Completed scanning of directory: %s' % dir)
logger.info('Checking filepaths to see if we can find any matches')
# Now check empty file paths to see if we can find a match based on their folder format
tracks = myDB.select('SELECT * from tracks WHERE Location IS NULL')
for track in tracks:
release = myDB.action('SELECT * from albums WHERE AlbumID=?', [track['AlbumID']]).fetchone()
try:
year = release['ReleaseDate'][:4]
except TypeError:
year = ''
artist = release['ArtistName'].replace('/', '_')
album = release['AlbumTitle'].replace('/', '_')
releasetype = release['Type'].replace('/', '_')
if release['ArtistName'].startswith('The '):
sortname = release['ArtistName'][4:]
else:
sortname = release['ArtistName']
if sortname.isdigit():
firstchar = '0-9'
else:
firstchar = sortname[0]
albumvalues = { '$Artist': artist,
'$Album': album,
'$Year': year,
'$Type': releasetype,
'$First': firstchar,
'$artist': artist.lower(),
'$album': album.lower(),
'$year': year,
'$type': releasetype.lower(),
'$first': firstchar.lower()
}
folder = helpers.replace_all(headphones.FOLDER_FORMAT, albumvalues)
folder = folder.replace('./', '_/').replace(':','_').replace('?','_')
if folder.endswith('.'):
folder = folder.replace(folder[len(folder)-1], '_')
if not track['TrackNumber']:
tracknumber = ''
else:
tracknumber = '%02d' % track['TrackNumber']
title = track['TrackTitle']
trackvalues = { '$Track': tracknumber,
'$Title': title,
'$Artist': release['ArtistName'],
'$Album': release['AlbumTitle'],
'$Year': year,
'$track': tracknumber,
'$title': title.lower(),
'$artist': release['ArtistName'].lower(),
'$album': release['AlbumTitle'].lower(),
'$year': year
}
new_file_name = helpers.replace_all(headphones.FILE_FORMAT, trackvalues).replace('/','_') + '.*'
new_file_name = new_file_name.replace('?','_').replace(':', '_')
full_path_to_file = os.path.normpath(os.path.join(headphones.MUSIC_DIR, folder, new_file_name)).encode(headphones.SYS_ENCODING, 'replace')
match = glob.glob(full_path_to_file)
if match:
logger.info('Found a match: %s. Writing MBID to metadata' % match[0])
unipath = unicode(match[0], headphones.SYS_ENCODING, errors='replace')
myDB.action('UPDATE tracks SET Location=? WHERE TrackID=?', [unipath, track['TrackID']])
myDB.action('DELETE from have WHERE Location=?', [unipath])
# Try to insert the appropriate track id so we don't have to keep doing this
try:
f = MediaFile(match[0])
f.mb_trackid = track['TrackID']
f.save()
myDB.action('UPDATE tracks SET BitRate=?, Format=? WHERE TrackID=?', [f.bitrate, f.format, track['TrackID']])
logger.debug('Wrote mbid to track: %s' % match[0])
except:
logger.error('Error embedding track id into: %s' % match[0])
continue
logger.info('Done checking empty filepaths')
logger.info('Done syncing library with directory: %s' % dir)
logger.info('Completed scanning directory: %s' % dir)
# Clean up the new artist list
unique_artists = {}.fromkeys(new_artists).keys()

View File

@@ -418,9 +418,10 @@ def moveFiles(albumpath, release, tracks):
except Exception, e:
logger.warn('Error renaming %s: %s' % (files, e))
break
try:
shutil.move(os.path.join(r, files), os.path.join(destination_path, files))
except shutil.Error, e:
except Exception, e:
logger.warn('Error moving file %s: %s' % (files, e))
# Chmod the directories using the folder_format (script courtesy of premiso!)

View File

@@ -916,11 +916,19 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False):
# Get torrent name from .torrent, this is usually used by the torrent client as the folder name
torrent_name = torrent_folder_name + '.torrent'
download_path = os.path.join(headphones.TORRENTBLACKHOLE_DIR, torrent_name)
try:
torrent_file = open(download_path, 'rb').read()
torrent_info = bencode.bdecode(torrent_file)
#Write the torrent file to a path derived from the TORRENTBLACKHOLE_DIR and file name.
torrent_file = open(download_path, 'wb')
torrent_file.write(data)
torrent_file.close()
#Open the fresh torrent file again so we can extract the proper torrent name
#Used later in post-processing.
torrent_file = open(download_path, 'rb')
torrent_info = bencode.bdecode(torrent_file.read())
torrent_file.close()
torrent_folder_name = torrent_info['info'].get('name','')
logger.info('Torrent folder name: %s' % torrent_folder_name)
except Exception, e:

View File

@@ -138,6 +138,7 @@ 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('INSERT OR REPLACE into blacklist VALUES (?)', [ArtistID])
raise cherrypy.HTTPRedirect("home")
deleteArtist.exposed = True
@@ -148,11 +149,12 @@ class WebInterface(object):
def markAlbums(self, ArtistID=None, action=None, **args):
myDB = db.DBConnection()
if action == 'WantedNew':
if action == 'WantedNew' or action == 'WantedLossless':
newaction = 'Wanted'
else:
newaction = action
for mbid in args:
logger.info("Marking %s as %s" % (mbid, newaction))
controlValueDict = {'AlbumID': mbid}
newValueDict = {'Status': newaction}
myDB.upsert("albums", newValueDict, controlValueDict)
@@ -160,6 +162,8 @@ class WebInterface(object):
searcher.searchforalbum(mbid, new=False)
if action == 'WantedNew':
searcher.searchforalbum(mbid, new=True)
if action == 'WantedLossless':
searcher.searchforalbum(mbid, lossless=True)
if ArtistID:
raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID)
else:
@@ -223,7 +227,13 @@ class WebInterface(object):
myDB = db.DBConnection()
artists = myDB.select('SELECT * from artists order by ArtistSortName COLLATE NOCASE')
return serve_template(templatename="manageartists.html", title="Manage Artists", artists=artists)
manageArtists.exposed = True
manageArtists.exposed = True
def manageAlbums(self):
myDB = db.DBConnection()
albums = myDB.select('SELECT * from albums')
return serve_template(templatename="managealbums.html", title="Manage Albums", albums=albums)
manageAlbums.exposed = True
def manageNew(self):
return serve_template(templatename="managenew.html", title="Manage New Artists")
@@ -237,6 +247,7 @@ 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('INSERT OR REPLACE into blacklist VALUES (?)', [ArtistID])
elif action == 'pause':
controlValueDict = {'ArtistID': ArtistID}
newValueDict = {'Status': 'Paused'}