Post Processing! Very beta, but improvements coming soon. Also: specify log dir, automatically add extras for artists, release group selection process improvements

This commit is contained in:
Remy
2011-07-27 00:38:15 -07:00
parent 004d104efe
commit 738f0fd6af
8 changed files with 353 additions and 48 deletions
+32 -13
View File
@@ -11,7 +11,7 @@ from lib.configobj import ConfigObj
import cherrypy import cherrypy
from headphones import updater, searcher, importer, versioncheck, logger from headphones import updater, searcher, importer, versioncheck, logger, postprocessor
FULL_PATH = None FULL_PATH = None
PROG_DIR = None PROG_DIR = None
@@ -52,13 +52,14 @@ LATEST_VERSION = None
COMMITS_BEHIND = None COMMITS_BEHIND = None
MUSIC_DIR = None MUSIC_DIR = None
DESTINATION_DIR = None
FOLDER_FORMAT = None FOLDER_FORMAT = None
FILE_FORMAT = None FILE_FORMAT = None
PATH_TO_XML = None PATH_TO_XML = None
PREFERRED_QUALITY = None PREFERRED_QUALITY = None
PREFERRED_BITRATE = None PREFERRED_BITRATE = None
DETECT_BITRATE = False DETECT_BITRATE = False
FLAC_TO_MP3 = False CORRECT_METADATA = False
MOVE_FILES = False MOVE_FILES = False
RENAME_FILES = False RENAME_FILES = False
CLEANUP_FILES = False CLEANUP_FILES = False
@@ -67,6 +68,7 @@ DOWNLOAD_DIR = None
BLACKHOLE = None BLACKHOLE = None
BLACKHOLE_DIR = None BLACKHOLE_DIR = None
USENET_RETENTION = None USENET_RETENTION = None
INCLUDE_EXTRAS = False
NZB_SEARCH_INTERVAL = 360 NZB_SEARCH_INTERVAL = 360
LIBRARYSCAN_INTERVAL = 60 LIBRARYSCAN_INTERVAL = 60
@@ -141,8 +143,8 @@ def initialize():
global __INITIALIZED__, FULL_PATH, PROG_DIR, QUIET, DAEMON, DATA_DIR, CONFIG_FILE, CFG, LOG_DIR, CACHE_DIR, \ 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, \ HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, LAUNCH_BROWSER, GIT_PATH, \
CURRENT_VERSION, LATEST_VERSION, MUSIC_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, \ CURRENT_VERSION, LATEST_VERSION, MUSIC_DIR, DESTINATION_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, \
FLAC_TO_MP3, MOVE_FILES, RENAME_FILES, FOLDER_FORMAT, FILE_FORMAT, CLEANUP_FILES, \ CORRECT_METADATA, MOVE_FILES, RENAME_FILES, FOLDER_FORMAT, FILE_FORMAT, CLEANUP_FILES, INCLUDE_EXTRAS, \
ADD_ALBUM_ART, DOWNLOAD_DIR, BLACKHOLE, BLACKHOLE_DIR, USENET_RETENTION, NZB_SEARCH_INTERVAL, \ 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, \ LIBRARYSCAN_INTERVAL, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \
NZBMATRIX, NZBMATRIX_USERNAME, NZBMATRIX_APIKEY, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, \ NZBMATRIX, NZBMATRIX_USERNAME, NZBMATRIX_APIKEY, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, \
@@ -173,22 +175,25 @@ def initialize():
HTTP_ROOT = check_setting_str(CFG, 'General', 'http_root', '/') HTTP_ROOT = check_setting_str(CFG, 'General', 'http_root', '/')
LAUNCH_BROWSER = bool(check_setting_int(CFG, 'General', 'launch_browser', 1)) LAUNCH_BROWSER = bool(check_setting_int(CFG, 'General', 'launch_browser', 1))
GIT_PATH = check_setting_str(CFG, 'General', 'git_path', '') GIT_PATH = check_setting_str(CFG, 'General', 'git_path', '')
LOG_DIR = check_setting_str(CFG, 'General', 'log_dir', '')
MUSIC_DIR = check_setting_str(CFG, 'General', 'music_dir', '') MUSIC_DIR = check_setting_str(CFG, 'General', 'music_dir', '')
DESTINATION_DIR = check_setting_str(CFG, 'General', 'destination_dir', '')
PREFERRED_QUALITY = check_setting_int(CFG, 'General', 'preferred_quality', 0) PREFERRED_QUALITY = check_setting_int(CFG, 'General', 'preferred_quality', 0)
PREFERRED_BITRATE = check_setting_int(CFG, 'General', 'preferred_bitrate', '') PREFERRED_BITRATE = check_setting_int(CFG, 'General', 'preferred_bitrate', '')
DETECT_BITRATE = bool(check_setting_int(CFG, 'General', 'detect_bitrate', 0)) DETECT_BITRATE = bool(check_setting_int(CFG, 'General', 'detect_bitrate', 0))
FLAC_TO_MP3 = bool(check_setting_int(CFG, 'General', 'flac_to_mp3', 0)) CORRECT_METADATA = bool(check_setting_int(CFG, 'General', 'correct_metadata', 0))
MOVE_FILES = bool(check_setting_int(CFG, 'General', 'move_files', 0)) MOVE_FILES = bool(check_setting_int(CFG, 'General', 'move_files', 0))
RENAME_FILES = bool(check_setting_int(CFG, 'General', 'rename_files', 0)) RENAME_FILES = bool(check_setting_int(CFG, 'General', 'rename_files', 0))
FOLDER_FORMAT = check_setting_str(CFG, 'General', 'folder_format', '%artist/%album/%track') FOLDER_FORMAT = check_setting_str(CFG, 'General', 'folder_format', 'artist/album [year]')
FILE_FORMAT = check_setting_str(CFG, 'General', 'file_format', '%tracknumber %artist - %album - %title') FILE_FORMAT = check_setting_str(CFG, 'General', 'file_format', 'tracknumber artist - album [year]- title')
CLEANUP_FILES = bool(check_setting_int(CFG, 'General', 'cleanup_files', 0)) CLEANUP_FILES = bool(check_setting_int(CFG, 'General', 'cleanup_files', 0))
ADD_ALBUM_ART = bool(check_setting_int(CFG, 'General', 'add_album_art', 0)) ADD_ALBUM_ART = bool(check_setting_int(CFG, 'General', 'add_album_art', 0))
DOWNLOAD_DIR = check_setting_str(CFG, 'General', 'download_dir', '') DOWNLOAD_DIR = check_setting_str(CFG, 'General', 'download_dir', '')
BLACKHOLE = bool(check_setting_int(CFG, 'General', 'blackhole', 0)) BLACKHOLE = bool(check_setting_int(CFG, 'General', 'blackhole', 0))
BLACKHOLE_DIR = check_setting_str(CFG, 'General', 'blackhole_dir', '') 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', '')
INCLUDE_EXTRAS = bool(check_setting_int(CFG, 'General', 'include_extras', 0))
NZB_SEARCH_INTERVAL = check_setting_int(CFG, 'General', 'nzb_search_interval', 360) NZB_SEARCH_INTERVAL = check_setting_int(CFG, 'General', 'nzb_search_interval', 360)
LIBRARYSCAN_INTERVAL = check_setting_int(CFG, 'General', 'libraryscan_interval', 180) LIBRARYSCAN_INTERVAL = check_setting_int(CFG, 'General', 'libraryscan_interval', 180)
@@ -211,8 +216,9 @@ def initialize():
NZBSORG_UID = check_setting_str(CFG, 'NZBsorg', 'nzbsorg_uid', '') NZBSORG_UID = check_setting_str(CFG, 'NZBsorg', 'nzbsorg_uid', '')
NZBSORG_HASH = check_setting_str(CFG, 'NZBsorg', 'nzbsorg_hash', '') NZBSORG_HASH = check_setting_str(CFG, 'NZBsorg', 'nzbsorg_hash', '')
# Put the log dir in the data dir for now if not LOG_DIR:
LOG_DIR = os.path.join(DATA_DIR, 'logs') LOG_DIR = os.path.join(DATA_DIR, 'logs')
if not os.path.exists(LOG_DIR): if not os.path.exists(LOG_DIR):
try: try:
os.makedirs(LOG_DIR) os.makedirs(LOG_DIR)
@@ -223,6 +229,11 @@ def initialize():
# Start the logger, silence console logging if we need to # Start the logger, silence console logging if we need to
logger.headphones_log.initLogger(quiet=QUIET) logger.headphones_log.initLogger(quiet=QUIET)
# Update some old config code:
if FOLDER_FORMAT == '%artist/%album/%track':
FOLDER_FORMAT = 'artist/album [year]'
if FILE_FORMAT == '%tracknumber %artist - %album - %title':
FILE_FORMAT = 'tracknumber artist - album - title'
# Put the cache dir in the data dir for now # Put the cache dir in the data dir for now
CACHE_DIR = os.path.join(DATA_DIR, 'cache') CACHE_DIR = os.path.join(DATA_DIR, 'cache')
@@ -232,8 +243,6 @@ def initialize():
except OSError: except OSError:
logger.error('Could not create cache dir. Check permissions of datadir: ' + DATA_DIR) logger.error('Could not create cache dir. Check permissions of datadir: ' + DATA_DIR)
# Initialize the database # Initialize the database
logger.info('Checking to see if the database has all tables....') logger.info('Checking to see if the database has all tables....')
try: try:
@@ -318,13 +327,15 @@ def config_write():
new_config['General']['http_password'] = HTTP_PASSWORD new_config['General']['http_password'] = HTTP_PASSWORD
new_config['General']['http_root'] = HTTP_ROOT new_config['General']['http_root'] = HTTP_ROOT
new_config['General']['launch_browser'] = int(LAUNCH_BROWSER) new_config['General']['launch_browser'] = int(LAUNCH_BROWSER)
new_config['General']['log_dir'] = LOG_DIR
new_config['General']['git_path'] = GIT_PATH new_config['General']['git_path'] = GIT_PATH
new_config['General']['music_dir'] = MUSIC_DIR new_config['General']['music_dir'] = MUSIC_DIR
new_config['General']['destination_dir'] = DESTINATION_DIR
new_config['General']['preferred_quality'] = PREFERRED_QUALITY new_config['General']['preferred_quality'] = PREFERRED_QUALITY
new_config['General']['preferred_bitrate'] = PREFERRED_BITRATE new_config['General']['preferred_bitrate'] = PREFERRED_BITRATE
new_config['General']['detect_bitrate'] = int(DETECT_BITRATE) new_config['General']['detect_bitrate'] = int(DETECT_BITRATE)
new_config['General']['flac_to_mp3'] = int(FLAC_TO_MP3) new_config['General']['correct_metadata'] = int(CORRECT_METADATA)
new_config['General']['move_files'] = int(MOVE_FILES) new_config['General']['move_files'] = int(MOVE_FILES)
new_config['General']['rename_files'] = int(RENAME_FILES) new_config['General']['rename_files'] = int(RENAME_FILES)
new_config['General']['folder_format'] = FOLDER_FORMAT new_config['General']['folder_format'] = FOLDER_FORMAT
@@ -335,6 +346,7 @@ def config_write():
new_config['General']['blackhole'] = int(BLACKHOLE) new_config['General']['blackhole'] = int(BLACKHOLE)
new_config['General']['blackhole_dir'] = BLACKHOLE_DIR new_config['General']['blackhole_dir'] = BLACKHOLE_DIR
new_config['General']['usenet_retention'] = USENET_RETENTION new_config['General']['usenet_retention'] = USENET_RETENTION
new_config['General']['include_extras'] = int(INCLUDE_EXTRAS)
new_config['General']['nzb_search_interval'] = NZB_SEARCH_INTERVAL new_config['General']['nzb_search_interval'] = NZB_SEARCH_INTERVAL
new_config['General']['libraryscan_interval'] = LIBRARYSCAN_INTERVAL new_config['General']['libraryscan_interval'] = LIBRARYSCAN_INTERVAL
@@ -376,6 +388,8 @@ def start():
SCHED.add_interval_job(searcher.searchNZB, minutes=NZB_SEARCH_INTERVAL) SCHED.add_interval_job(searcher.searchNZB, minutes=NZB_SEARCH_INTERVAL)
SCHED.add_interval_job(importer.scanMusic, minutes=LIBRARYSCAN_INTERVAL) SCHED.add_interval_job(importer.scanMusic, minutes=LIBRARYSCAN_INTERVAL)
SCHED.add_interval_job(versioncheck.checkGithub, minutes=300) SCHED.add_interval_job(versioncheck.checkGithub, minutes=300)
SCHED.add_interval_job(postprocessor.checkFolder, minutes=5)
postprocessor.checkFolder()
SCHED.start() SCHED.start()
@@ -388,7 +402,7 @@ def dbcheck():
c.execute('CREATE TABLE IF NOT EXISTS artists (ArtistID TEXT UNIQUE, ArtistName TEXT, ArtistSortName TEXT, DateAdded TEXT, Status TEXT, IncludeExtras INTEGER, LatestAlbum TEXT, ReleaseDate TEXT, AlbumID TEXT, HaveTracks INTEGER, TotalTracks INTEGER)') c.execute('CREATE TABLE IF NOT EXISTS artists (ArtistID TEXT UNIQUE, ArtistName TEXT, ArtistSortName TEXT, DateAdded TEXT, Status TEXT, IncludeExtras INTEGER, LatestAlbum TEXT, ReleaseDate TEXT, AlbumID TEXT, HaveTracks INTEGER, TotalTracks INTEGER)')
c.execute('CREATE TABLE IF NOT EXISTS albums (ArtistID TEXT, ArtistName TEXT, AlbumTitle TEXT, AlbumASIN TEXT, ReleaseDate TEXT, DateAdded TEXT, AlbumID TEXT UNIQUE, Status TEXT, Type TEXT)') c.execute('CREATE TABLE IF NOT EXISTS albums (ArtistID TEXT, ArtistName TEXT, AlbumTitle TEXT, AlbumASIN TEXT, ReleaseDate TEXT, DateAdded TEXT, AlbumID TEXT UNIQUE, Status TEXT, Type TEXT)')
c.execute('CREATE TABLE IF NOT EXISTS tracks (ArtistID TEXT, ArtistName TEXT, AlbumTitle TEXT, AlbumASIN TEXT, AlbumID TEXT, TrackTitle TEXT, TrackDuration, TrackID TEXT)') c.execute('CREATE TABLE IF NOT EXISTS tracks (ArtistID TEXT, ArtistName TEXT, AlbumTitle TEXT, AlbumASIN TEXT, AlbumID TEXT, TrackTitle TEXT, TrackDuration, TrackID TEXT)')
c.execute('CREATE TABLE IF NOT EXISTS snatched (AlbumID TEXT, Title TEXT, Size INTEGER, URL TEXT, DateAdded TEXT, Status TEXT)') c.execute('CREATE TABLE IF NOT EXISTS snatched (AlbumID TEXT, Title TEXT, Size INTEGER, URL TEXT, DateAdded TEXT, Status TEXT, FolderName TEXT)')
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)') 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)')
try: try:
@@ -425,6 +439,11 @@ def dbcheck():
c.execute('SELECT Type from albums') c.execute('SELECT Type from albums')
except sqlite3.OperationalError: except sqlite3.OperationalError:
c.execute('ALTER TABLE albums ADD COLUMN Type TEXT DEFAULT "Album"') c.execute('ALTER TABLE albums ADD COLUMN Type TEXT DEFAULT "Album"')
try:
c.execute('SELECT FolderName from snatched')
except sqlite3.OperationalError:
c.execute('ALTER TABLE snatched ADD COLUMN FolderName TEXT')
conn.commit() conn.commit()
c.close() c.close()
+10
View File
@@ -0,0 +1,10 @@
from headphones import db
def getAlbumArt(albumid):
myDB = db.DBConnection()
asin = myDB.action('SELECT AlbumASIN from albums WHERE AlbumID=?', [albumid]).fetchone()[0]
url = 'http://ec1.images-amazon.com/images/P/%s.01.LZZZZZZZ.jpg' % asin
return url
+9 -5
View File
@@ -4,7 +4,7 @@ import os
from lib.beets.mediafile import MediaFile from lib.beets.mediafile import MediaFile
import headphones import headphones
from headphones import logger, helpers, db, mb from headphones import logger, helpers, db, mb, albumart
various_artists_mbid = '89ad4ac3-39f7-470e-963a-56509c546377' various_artists_mbid = '89ad4ac3-39f7-470e-963a-56509c546377'
@@ -158,6 +158,9 @@ def addArtisttoDB(artistid, extrasonly=False):
"DateAdded": helpers.today(), "DateAdded": helpers.today(),
"Status": "Loading"} "Status": "Loading"}
if headphones.INCLUDE_EXTRAS:
newValueDict['IncludeExtras'] = 1
myDB.upsert("artists", newValueDict, controlValueDict) myDB.upsert("artists", newValueDict, controlValueDict)
for rg in artist['releasegroups']: for rg in artist['releasegroups']:
@@ -170,7 +173,8 @@ def addArtisttoDB(artistid, extrasonly=False):
try: try:
release_dict = mb.getReleaseGroup(rgid) release_dict = mb.getReleaseGroup(rgid)
except Exception, e: except Exception, e:
logger.info('Unable to get release information for %s - it may not be a valid release group' % rg['title']) logger.info('Unable to get release information for %s - it may not be a valid release group \
(or it might just not be tagged right in MusicBrainz)' % rg['title'])
continue continue
if not release_dict: if not release_dict:
@@ -187,7 +191,7 @@ def addArtisttoDB(artistid, extrasonly=False):
if len(rg_exists): if len(rg_exists):
newValueDict = {"AlbumASIN": release['asin'], newValueDict = {"AlbumASIN": release_dict['asin'],
"ReleaseDate": release_dict['releasedate'], "ReleaseDate": release_dict['releasedate'],
} }
@@ -196,7 +200,7 @@ def addArtisttoDB(artistid, extrasonly=False):
newValueDict = {"ArtistID": artistid, newValueDict = {"ArtistID": artistid,
"ArtistName": artist['artist_name'], "ArtistName": artist['artist_name'],
"AlbumTitle": rg['title'], "AlbumTitle": rg['title'],
"AlbumASIN": release['asin'], "AlbumASIN": release_dict['asin'],
"ReleaseDate": release_dict['releasedate'], "ReleaseDate": release_dict['releasedate'],
"DateAdded": helpers.today(), "DateAdded": helpers.today(),
"Type": rg['type'] "Type": rg['type']
@@ -219,7 +223,7 @@ def addArtisttoDB(artistid, extrasonly=False):
newValueDict = {"ArtistID": artistid, newValueDict = {"ArtistID": artistid,
"ArtistName": artist['artist_name'], "ArtistName": artist['artist_name'],
"AlbumTitle": rg['title'], "AlbumTitle": rg['title'],
"AlbumASIN": release['asin'], "AlbumASIN": release_dict['asin'],
"TrackTitle": track['title'], "TrackTitle": track['title'],
"TrackDuration": track['duration'], "TrackDuration": track['duration'],
} }
+53 -13
View File
@@ -9,8 +9,9 @@ import lib.musicbrainz2.utils as u
from lib.musicbrainz2.webservice import WebServiceError from lib.musicbrainz2.webservice import WebServiceError
import headphones
from headphones import logger, db from headphones import logger, db
from headphones.helpers import multikeysort from headphones.helpers import multikeysort, replace_all
q = ws.Query() q = ws.Query()
mb_lock = threading.Lock() mb_lock = threading.Lock()
@@ -83,7 +84,7 @@ def getArtist(artistid, extrasonly=False):
artist = q.getArtistById(artistid, inc) artist = q.getArtistById(artistid, inc)
break break
except WebServiceError, e: except WebServiceError, e:
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s. Sleeping 10 seconds' % artistid) logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s. Sleeping 5 seconds' % artistid)
attempt += 1 attempt += 1
time.sleep(5) time.sleep(5)
@@ -120,7 +121,7 @@ def getArtist(artistid, extrasonly=False):
except IndexError: except IndexError:
includeExtras = False includeExtras = False
if includeExtras: if includeExtras or headphones.INCLUDE_EXTRAS:
includes = [m.Release.TYPE_COMPILATION, m.Release.TYPE_REMIX, m.Release.TYPE_SINGLE, m.Release.TYPE_LIVE, m.Release.TYPE_EP] includes = [m.Release.TYPE_COMPILATION, m.Release.TYPE_REMIX, m.Release.TYPE_SINGLE, m.Release.TYPE_LIVE, m.Release.TYPE_EP]
for include in includes: for include in includes:
inc = ws.ArtistIncludes(releases=(m.Release.TYPE_OFFICIAL, include), releaseGroups=True) inc = ws.ArtistIncludes(releases=(m.Release.TYPE_OFFICIAL, include), releaseGroups=True)
@@ -134,7 +135,7 @@ def getArtist(artistid, extrasonly=False):
artist = q.getArtistById(artistid, inc) artist = q.getArtistById(artistid, inc)
break break
except WebServiceError, e: except WebServiceError, e:
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s. Sleeping 10 seconds' % artistid) logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s. Sleeping 5 seconds' % artistid)
attempt += 1 attempt += 1
time.sleep(5) time.sleep(5)
@@ -161,6 +162,7 @@ def getReleaseGroup(rgid):
with mb_lock: with mb_lock:
releaselist = [] releaselist = []
asinlist = []
inc = ws.ReleaseGroupIncludes(releases=True) inc = ws.ReleaseGroupIncludes(releases=True)
releaseGroup = None releaseGroup = None
@@ -172,7 +174,7 @@ def getReleaseGroup(rgid):
releaseGroup = q.getReleaseGroupById(rgid, inc) releaseGroup = q.getReleaseGroupById(rgid, inc)
break break
except WebServiceError, e: except WebServiceError, e:
logger.warn('Attempt to retrieve information from MusicBrainz for release group "%s" failed. Sleeping 10 seconds' % rgid) logger.warn('Attempt to retrieve information from MusicBrainz for release group "%s" failed. Sleeping 5 seconds' % rgid)
attempt += 1 attempt += 1
time.sleep(5) time.sleep(5)
@@ -194,28 +196,66 @@ def getReleaseGroup(rgid):
releaseResult = q.getReleaseById(release.id, inc) releaseResult = q.getReleaseById(release.id, inc)
break break
except WebServiceError, e: except WebServiceError, e:
logger.warn('Attempt to retrieve release information for %s from MusicBrainz failed: %s. Sleeping 10 seconds' % (releaseResult.title, e)) logger.warn('Attempt to retrieve release information for %s from MusicBrainz failed: %s. Sleeping 5 seconds' % (releaseResult.title, e))
attempt += 1 attempt += 1
time.sleep(5) time.sleep(5)
if not releaseResult: if not releaseResult:
continue continue
if releaseResult.title.lower() != releaseGroup.title.lower():
continue
time.sleep(1) time.sleep(1)
formats = {
'2xVinyl': '2',
'Vinyl': '2',
'CD': '0',
'Cassette': '3',
'2xCD': '1',
'Digital Media': '0'
}
country = {
'US': '0',
'GB': '1',
'JP': '1',
}
try:
format = int(replace_all(u.extractFragment(releaseResult.releaseEvents[0].format), formats))
except:
format = 3
try:
country = int(replace_all(releaseResult.releaseEvents[0].country, country))
except:
country = 2
release_dict = { release_dict = {
'asin': bool(releaseResult.asin), 'hasasin': bool(releaseResult.asin),
'asin': releaseResult.asin,
'tracks': len(releaseResult.getTracks()), 'tracks': len(releaseResult.getTracks()),
'releaseid': u.extractUuid(releaseResult.id), 'releaseid': u.extractUuid(releaseResult.id),
'releasedate': releaseResult.getEarliestReleaseDate() 'releasedate': releaseResult.getEarliestReleaseDate(),
'format': format,
'country': country
} }
releaselist.append(release_dict) releaselist.append(release_dict)
if releaseResult.asin:
asinlist.append(releaseResult.asin)
a = multikeysort(releaselist, ['-asin', '-tracks']) a = multikeysort(releaselist, ['-hasasin', 'country', 'format', 'tracks'])
release_dict = {'releaseid' :a[0]['releaseid'], release_dict = {'releaseid' :a[0]['releaseid'],
'releasedate' : releaselist[0]['releasedate']} 'releasedate' : releaselist[0]['releasedate'],
'trackcount' : a[0]['tracks'],
'asin' : a[0]['asin']
}
return release_dict return release_dict
@@ -237,7 +277,7 @@ def getRelease(releaseid):
results = q.getReleaseById(releaseid, inc) results = q.getReleaseById(releaseid, inc)
break break
except WebServiceError, e: except WebServiceError, e:
logger.warn('Attempt to retrieve information from MusicBrainz for release "%s" failed: %s. SLeeping 10 seconds' % (releaseid, e)) logger.warn('Attempt to retrieve information from MusicBrainz for release "%s" failed: %s. SLeeping 5 seconds' % (releaseid, e))
attempt += 1 attempt += 1
time.sleep(5) time.sleep(5)
@@ -286,7 +326,7 @@ def findArtistbyAlbum(name):
results = q.getReleaseGroups(f) results = q.getReleaseGroups(f)
break break
except WebServiceError, e: except WebServiceError, e:
logger.warn('Attempt to query MusicBrainz for %s failed: %s. Sleeping 10 seconds.' % (name, e)) logger.warn('Attempt to query MusicBrainz for %s failed: %s. Sleeping 5 seconds.' % (name, e))
attempt += 1 attempt += 1
time.sleep(5) time.sleep(5)
+190
View File
@@ -0,0 +1,190 @@
import os
import time
import urllib, shutil
from lib.beets.mediafile import MediaFile
import lib.musicbrainz2.webservice as ws
import headphones
from headphones import db, albumart, logger, helpers
def checkFolder():
myDB = db.DBConnection()
snatched = myDB.select('SELECT * from snatched WHERE Status="Snatched" or Status="Unprocessed"')
for album in snatched:
if album['FolderName']:
album_path = os.path.join(headphones.DOWNLOAD_DIR, album['FolderName'])
if os.path.exists(album_path):
verify(album['AlbumID'], album_path)
def verify(albumid, albumpath):
myDB = db.DBConnection()
release = myDB.action('SELECT * from albums WHERE AlbumID=?', [albumid]).fetchone()
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
downloaded_track_list = []
for r,d,f in os.walk(albumpath):
for files in f:
if any(files.endswith(x) for x in (".mp3", ".flac", ".aac", ".ogg", ".ape")):
downloaded_track_list.append(os.path.join(r, files))
# test #1: filenames
# test #2: metadata.
for downloaded_track in downloaded_track_list:
try:
f = MediaFile(downloaded_track)
except:
continue
if helpers.latinToAscii(f.artist.lower()).encode('UTF-8') == helpers.latinToAscii(release['ArtistName'].lower()).encode('UTF-8') and helpers.latinToAscii(f.album.lower()).encode('UTF-8') == helpers.latinToAscii(release['AlbumTitle'].lower()).encode('UTF-8'):
doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list)
return
# test #3: number of songs and duration
db_track_duration = 0
downloaded_track_duration = 0
for track in tracks:
try:
db_track_duration += track['TrackDuration']/1000
except:
downloaded_track_duration = False
break
for downloaded_track in downloaded_track_list:
try:
f = MediaFile(downloaded_track)
downloaded_track_duration += f.length
except:
downloaded_track_duration = False
break
if downloaded_track_duration and db_track_duration:
delta = abs(downloaded_track_duration - db_track_duration)
if len(tracks) == len(downloaded_track_list) and delta < 100:
doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list)
return
logger.warn('Could not identify album: %s. It may not be the intended album.' % albumpath)
myDB.action('UPDATE snatched SET status = "Unprocessed" WHERE AlbumID=?', [albumid])
def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list):
logger.info('Starting post-processing for: %s - %s' % (release['ArtistName'], release['AlbumTitle']))
if headphones.ADD_ALBUM_ART:
addAlbumArt(albumid, downloaded_track_list)
if headphones.CLEANUP_FILES:
cleanupFiles(albumpath)
if headphones.CORRECT_METADATA:
correctMetadata(albumid, release, downloaded_track_list)
if headphones.RENAME_FILES:
renameFiles(albumpath, downloaded_track_list, release)
if headphones.MOVE_FILES and headphones.DESTINATION_DIR:
moveFiles(albumpath, release, tracks)
myDB = db.DBConnection()
myDB.action('UPDATE albums SET status = "Downloaded" WHERE AlbumID=?', [albumid])
myDB.action('UPDATE snatched SET status = "Processed" WHERE AlbumID=?', [albumid])
logger.info('Post-processing for %s - %s complete' % (release['ArtistName'], release['AlbumTitle']))
def addAlbumArt(albumid, downloaded_track_list):
album_art_path = albumart.getAlbumArt(albumid)
artwork = urllib.urlopen(album_art_path).read()
for downloaded_track in downloaded_track_list:
try:
f = MediaFile(downloaded_track)
except:
continue
f.art = artwork
f.save()
def cleanupFiles(albumpath):
for r,d,f in os.walk(albumpath):
for files in f:
if not any(files.endswith(x) for x in (".mp3", ".flac", ".aac", ".ogg", ".ape", ".m4a")):
os.remove(os.path.join(r, files))
def moveFiles(albumpath, release, tracks):
try:
year = release['ReleaseDate'][:4]
except TypeError:
year = ''
values = { 'artist': release['ArtistName'],
'album': release['AlbumTitle'],
'year': year
}
folder = helpers.replace_all(headphones.FOLDER_FORMAT, values)
destination_path = os.path.join(headphones.DESTINATION_DIR, folder)
try:
os.makedirs(destination_path)
except Exception, e:
logger.error('Could not create folder for %s. Not moving' % release['AlbumName'])
return
for r,d,f in os.walk(albumpath):
for files in f:
shutil.move(os.path.join(r, files), destination_path)
try:
os.rmdir(albumpath)
except Exception, e:
logger.error('Could not remove directory: %s. %s' % (albumpath, e))
def correctMetadata(albumid, release, downloaded_track_list):
pass
def renameFiles(albumpath, downloaded_track_list, release):
try:
year = release['ReleaseDate'][:4]
except TypeError:
year = ''
# Until tagging works better I'm going to rely on the already provided metadata
for downloaded_track in downloaded_track_list:
try:
f = MediaFile(downloaded_track)
except:
continue
tracknumber = '%02d' % f.track
values = { 'tracknumber': tracknumber,
'title': f.title,
'artist': release['ArtistName'],
'album': release['AlbumTitle'],
'year': year
}
ext = os.path.splitext(downloaded_track)[1]
new_file_name = helpers.replace_all(headphones.FILE_FORMAT, values).replace('/','_') + ext
new_file = os.path.join(albumpath, new_file_name)
shutil.move(downloaded_track, new_file)
+6 -4
View File
@@ -244,7 +244,9 @@ def searchNZB(albumid=None, new=False):
logger.info(u"Found best result: %s (%s) - %s" % (bestqual[0], bestqual[2], helpers.bytes_to_mb(bestqual[1]))) logger.info(u"Found best result: %s (%s) - %s" % (bestqual[0], bestqual[2], helpers.bytes_to_mb(bestqual[1])))
downloadurl = bestqual[2] downloadurl = bestqual[2]
nzb_folder_name = '%s - %s [%s]' % (helpers.latinToAscii(albums[0]).encode('UTF-8'), helpers.latinToAscii(albums[1]).encode('UTF-8'), year)
if headphones.SAB_HOST and not headphones.BLACKHOLE: if headphones.SAB_HOST and not headphones.BLACKHOLE:
linkparams = {} linkparams = {}
@@ -262,7 +264,7 @@ def searchNZB(albumid=None, new=False):
linkparams["name"] = downloadurl linkparams["name"] = downloadurl
linkparams["nzbname"] = ('%s - %s [%s]' % (helpers.latinToAscii(albums[0]).encode('UTF-8'), helpers.latinToAscii(albums[1]).encode('UTF-8'), year)) linkparams["nzbname"] = nzb_folder_name
saburl = 'http://' + headphones.SAB_HOST + '/sabnzbd/api?' + urllib.urlencode(linkparams) saburl = 'http://' + headphones.SAB_HOST + '/sabnzbd/api?' + urllib.urlencode(linkparams)
logger.info(u"Sending link to SABNZBD: " + saburl) logger.info(u"Sending link to SABNZBD: " + saburl)
@@ -275,12 +277,12 @@ def searchNZB(albumid=None, new=False):
break break
myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [albums[2]]) myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [albums[2]])
myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?)', [albums[2], bestqual[0], bestqual[1], bestqual[2], "Snatched"]) myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?)', [albums[2], bestqual[0], bestqual[1], bestqual[2], "Snatched", nzb_folder_name])
elif headphones.BLACKHOLE: elif headphones.BLACKHOLE:
nzb_name = ('%s - %s [%s].nzb' % (albums[0], albums[1], year)) nzb_name = nzb_folder_name + '.nzb'
download_path = os.path.join(headphones.BLACKHOLE_DIR, nzb_name) download_path = os.path.join(headphones.BLACKHOLE_DIR, nzb_name)
try: try:
@@ -290,4 +292,4 @@ def searchNZB(albumid=None, new=False):
break break
myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [albums[2]]) myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [albums[2]])
myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?)', [albums[2], bestqual[0], bestqual[1], bestqual[2], "Snatched"]) myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?)', [albums[2], bestqual[0], bestqual[1], bestqual[2], "Snatched", nzb_folder_name])
+39 -8
View File
@@ -62,7 +62,8 @@ configform = form = '''
<a href="#web_interface" >Web Interface</a> | <a href="#web_interface" >Web Interface</a> |
<a href="#download" class="smalltext">Download Settings</a> | <a href="#download" class="smalltext">Download Settings</a> |
<a href="#providers" class="smalltext">Search Providers</a> | <a href="#providers" class="smalltext">Search Providers</a> |
<a href="#post_processing" class="smalltext">Quality &amp; Post Processing</a> <a href="#post_processing" class="smalltext">Quality &amp; Post Processing</a> |
<a href="#advanced_settings" class="smalltext">Advanced Settings</a>
</div> </div>
</center> </center>
<div class="table"> <div class="table">
@@ -152,7 +153,7 @@ configform = form = '''
<p>Music Download Directory:</p><input type="text" name="download_dir" value="%s" size="60"><br> <p>Music Download Directory:</p><input type="text" name="download_dir" value="%s" size="60"><br>
<i class="smalltext">Full path to the directory where SAB downloads your music<br> <i class="smalltext">Full path to the directory where SAB downloads your music<br>
i.e. Downloads/music or /Users/name/Downloads/music</i> i.e. /Users/name/Downloads/music</i>
</td> </td>
</tr> </tr>
@@ -277,10 +278,10 @@ configform = form = '''
<td> <td>
<b>Post-Processing:</b> <b>Post-Processing:</b>
<p> <p>
<input type="checkbox" name="move_files" value="1" %s />Move downloads to Music Folder<br /> <input type="checkbox" name="move_files" value="1" %s />Move downloads to Destination 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 files<br>
<input type="checkbox" name="rename_files" value="1" %s />Rename &amp; add metadata<br> <input type="checkbox" name="correct_metadata" value="1" %s />Correct metadata<br>
<input type="checkbox" name="cleanup_files" value="1" %s />Delete leftover files<br> <input type="checkbox" name="cleanup_files" value="1" %s />Delete leftover files (.m3u, .nfo, .sfv, .nzb, etc.)<br>
<input type="checkbox" name="add_album_art" value="1" %s>Add album art <input type="checkbox" name="add_album_art" value="1" %s>Add album art
</p> </p>
</td> </td>
@@ -290,12 +291,42 @@ configform = form = '''
<td> <td>
<br> <br>
<p><b>Path to Music folder</b>:<br><input type="text" name="music_dir" value="%s" size="60" maxlength="200"> <p><b>Path to Destination folder</b>:<br><input type="text" name="destination_dir" value="%s" size="60">
<br> <br>
<i class="smalltext">i.e. /Users/name/Music/iTunes or /Volumes/share/music</i> <i class="smalltext">i.e. /Users/name/Music/iTunes or /Volumes/share/music</i>
</p> </p>
</td> </td>
</tr> </tr>
</table>
<a name="advanced_settings"><h1><u>Advanced Settings</u></h1></a>
<table class="configtable" summary="Advanced Settings">
<tr>
<td>
<b>Renaming Options:</b>
<br>
<p><b>Folder Format</b>:<br><input type="text" name="folder_format" value="%s" size="60">
<br>
<i class="smalltext">Use: artist, album and year</i>
</p>
<p><b>File Format</b>:<br><input type="text" name="file_format" value="%s" size="60">
<br>
<i class="smalltext">Use: tracknumber, title, artist, album and year</i>
</p>
</td>
<td>
<b>Miscellaneous:</b>
<p>
<input type="checkbox" name="include_extras" value="1" %s />Automatically Include Extras When Adding an Artist<br />
<i class="smalltext">Extras includes: EPs, Compilations, Live Albums, Remix Albums and Singles</i>
</p>
<p><b>Log Directory</b>:<br><input type="text" name="log_dir" value="%s" size="60">
</p>
</td>
</tr>
</table> </table>
@@ -346,7 +377,7 @@ def displayAlbums(ArtistID, Type=None):
elif results[i][3] == 'Wanted': elif results[i][3] == 'Wanted':
newStatus = '''<b>%s</b>[<A class="external" href="unqueueAlbum?AlbumID=%s&ArtistID=%s">skip</a>]''' % (results[i][3], results[i][2], ArtistID) newStatus = '''<b>%s</b>[<A class="external" href="unqueueAlbum?AlbumID=%s&ArtistID=%s">skip</a>]''' % (results[i][3], results[i][2], ArtistID)
elif results[i][3] == 'Downloaded': elif results[i][3] == 'Downloaded':
newStatus = '''<b>%s</b>[<A class="external" href="queueAlbum?AlbumID=%s&ArtistID=%s">retry</a>]''' % (results[i][3], results[i][2], ArtistID) newStatus = '''<b>%s</b>[<A class="external" href="queueAlbum?AlbumID=%s&ArtistID=%s">retry</a>][<A class="external" href="queueAlbum?AlbumID=%s&ArtistID=%s&new=True">new</a>]''' % (results[i][3], results[i][2], ArtistID, results[i][2], ArtistID)
elif results[i][3] == 'Snatched': elif results[i][3] == 'Snatched':
newStatus = '''<b>%s</b>[<A class="external" href="queueAlbum?AlbumID=%s&ArtistID=%s">retry</a>][<A class="external" href="queueAlbum?AlbumID=%s&ArtistID=%s&new=True">new</a>]''' % (results[i][3], results[i][2], ArtistID, results[i][2], ArtistID) newStatus = '''<b>%s</b>[<A class="external" href="queueAlbum?AlbumID=%s&ArtistID=%s">retry</a>][<A class="external" href="queueAlbum?AlbumID=%s&ArtistID=%s&new=True">new</a>]''' % (results[i][3], results[i][2], ArtistID, results[i][2], ArtistID)
else: else:
+14 -5
View File
@@ -550,11 +550,15 @@ class WebInterface(object):
headphones.PREFERRED_BITRATE, headphones.PREFERRED_BITRATE,
checked(headphones.DETECT_BITRATE), checked(headphones.DETECT_BITRATE),
checked(headphones.MOVE_FILES), checked(headphones.MOVE_FILES),
checked(headphones.FLAC_TO_MP3),
checked(headphones.RENAME_FILES), checked(headphones.RENAME_FILES),
checked(headphones.CORRECT_METADATA),
checked(headphones.CLEANUP_FILES), checked(headphones.CLEANUP_FILES),
checked(headphones.ADD_ALBUM_ART), checked(headphones.ADD_ALBUM_ART),
headphones.MUSIC_DIR headphones.DESTINATION_DIR,
headphones.FOLDER_FORMAT,
headphones.FILE_FORMAT,
checked(headphones.INCLUDE_EXTRAS),
headphones.LOG_DIR
)) ))
page.append(templates._footer % headphones.CURRENT_VERSION) page.append(templates._footer % headphones.CURRENT_VERSION)
return page return page
@@ -565,7 +569,8 @@ class WebInterface(object):
def configUpdate(self, http_host='0.0.0.0', http_username=None, http_port=8181, http_password=None, launch_browser=0, 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, 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, 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, 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): nzbsorg=0, nzbsorg_uid=None, nzbsorg_hash=None, preferred_quality=0, preferred_bitrate=None, detect_bitrate=0, move_files=0,
rename_files=0, correct_metadata=0, cleanup_files=0, add_album_art=0, destination_dir=None, folder_format=None, file_format=None, include_extras=0, log_dir=None):
headphones.HTTP_HOST = http_host headphones.HTTP_HOST = http_host
headphones.HTTP_PORT = http_port headphones.HTTP_PORT = http_port
@@ -593,12 +598,16 @@ class WebInterface(object):
headphones.PREFERRED_QUALITY = int(preferred_quality) headphones.PREFERRED_QUALITY = int(preferred_quality)
headphones.PREFERRED_BITRATE = preferred_bitrate headphones.PREFERRED_BITRATE = preferred_bitrate
headphones.DETECT_BITRATE = detect_bitrate headphones.DETECT_BITRATE = detect_bitrate
headphones.FLAC_TO_MP3 = flac_to_mp3
headphones.MOVE_FILES = move_files headphones.MOVE_FILES = move_files
headphones.MUSIC_DIR = music_dir headphones.CORRECT_METADATA = correct_metadata
headphones.RENAME_FILES = rename_files headphones.RENAME_FILES = rename_files
headphones.CLEANUP_FILES = cleanup_files headphones.CLEANUP_FILES = cleanup_files
headphones.ADD_ALBUM_ART = add_album_art headphones.ADD_ALBUM_ART = add_album_art
headphones.DESTINATION_DIR = destination_dir
headphones.FOLDER_FORMAT = folder_format
headphones.FILE_FORMAT = file_format
headphones.INCLUDE_EXTRAS = include_extras
headphones.LOG_DIR = log_dir
headphones.config_write() headphones.config_write()