mirror of
https://github.com/rembo10/headphones.git
synced 2026-05-15 16:19:28 +01:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -618,7 +618,10 @@ m<%inherit file="base.html"/>
|
||||
</div>
|
||||
<div id="nmaoptions">
|
||||
<div class="row">
|
||||
<label>NotifyMyAndroid API Key</label>
|
||||
<div class="row checkbox">
|
||||
<input type="checkbox" name="nma_onsnatch" value="1" ${config['nma_onsnatch']} /><label>Notify on snatch?</label>
|
||||
</div>
|
||||
<label>NotifyMyAndroid API Key</label>
|
||||
<input type="text" name="nma_apikey" value="${config['nma_apikey']}" size="30">
|
||||
<small>Separate multiple api keys with commas</small>
|
||||
</div>
|
||||
|
||||
@@ -183,6 +183,7 @@ XBMC_NOTIFY = False
|
||||
NMA_ENABLED = False
|
||||
NMA_APIKEY = None
|
||||
NMA_PRIORITY = None
|
||||
NMA_ONSNATCH = None
|
||||
SYNOINDEX_ENABLED = False
|
||||
MIRRORLIST = ["musicbrainz.org","headphones","custom"]
|
||||
MIRROR = None
|
||||
@@ -254,7 +255,7 @@ def initialize():
|
||||
ENCODERFOLDER, ENCODER, BITRATE, SAMPLINGFREQUENCY, MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, \
|
||||
ENCODERVBRCBR, ENCODERLOSSLESS, DELETE_LOSSLESS_FILES, PROWL_ENABLED, PROWL_PRIORITY, PROWL_KEYS, PROWL_ONSNATCH, MIRRORLIST, \
|
||||
MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \
|
||||
XBMC_NOTIFY, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
|
||||
XBMC_NOTIFY, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
|
||||
PREFERRED_BITRATE_LOW_BUFFER
|
||||
|
||||
if __INITIALIZED__:
|
||||
@@ -401,6 +402,7 @@ def initialize():
|
||||
NMA_ENABLED = bool(check_setting_int(CFG, 'NMA', 'nma_enabled', 0))
|
||||
NMA_APIKEY = check_setting_str(CFG, 'NMA', 'nma_apikey', '')
|
||||
NMA_PRIORITY = check_setting_int(CFG, 'NMA', 'nma_priority', 0)
|
||||
NMA_ONSNATCH = bool(check_setting_int(CFG, 'NMA', 'nma_onsnatch', 0))
|
||||
|
||||
SYNOINDEX_ENABLED = bool(check_setting_int(CFG, 'Synoindex', 'synoindex_enabled', 0))
|
||||
|
||||
@@ -676,6 +678,7 @@ def config_write():
|
||||
new_config['NMA']['nma_enabled'] = int(NMA_ENABLED)
|
||||
new_config['NMA']['nma_apikey'] = NMA_APIKEY
|
||||
new_config['NMA']['nma_priority'] = NMA_PRIORITY
|
||||
new_config['NMA']['nma_onsnatch'] = int(PROWL_ONSNATCH)
|
||||
|
||||
new_config['Synoindex'] = {}
|
||||
new_config['Synoindex']['synoindex_enabled'] = int(SYNOINDEX_ENABLED)
|
||||
|
||||
@@ -41,7 +41,7 @@ def artistlist_to_mbids(artistlist, forced=False):
|
||||
|
||||
for artist in artistlist:
|
||||
|
||||
if not artist:
|
||||
if not artist and not (artist == ' '):
|
||||
continue
|
||||
|
||||
results = mb.findArtist(artist, limit=1)
|
||||
@@ -103,6 +103,9 @@ def addArtisttoDB(artistid, extrasonly=False):
|
||||
logger.warn('Cannot import Various Artists.')
|
||||
return
|
||||
|
||||
# We'll use this to see if we should update the 'LastUpdated' time stamp
|
||||
errors = False
|
||||
|
||||
myDB = db.DBConnection()
|
||||
|
||||
# Delete from blacklist if it's on there
|
||||
@@ -171,6 +174,7 @@ def addArtisttoDB(artistid, extrasonly=False):
|
||||
continue
|
||||
|
||||
if not releaselist:
|
||||
errors = True
|
||||
continue
|
||||
|
||||
# This will be used later to build a hybrid release
|
||||
@@ -186,10 +190,12 @@ def addArtisttoDB(artistid, extrasonly=False):
|
||||
try:
|
||||
releasedict = mb.getRelease(releaseid, include_artist_info=False)
|
||||
except Exception, e:
|
||||
errors = True
|
||||
logger.info('Unable to get release information for %s: %s' % (release['id'], e))
|
||||
continue
|
||||
|
||||
if not releasedict:
|
||||
errors = True
|
||||
continue
|
||||
|
||||
controlValueDict = {"ReleaseID": release['id']}
|
||||
@@ -342,17 +348,19 @@ def addArtisttoDB(artistid, extrasonly=False):
|
||||
|
||||
if headphones.AUTOWANT_ALL:
|
||||
newValueDict['Status'] = "Wanted"
|
||||
|
||||
#start a search for the album
|
||||
import searcher
|
||||
searcher.searchforalbum(albumid=rg['id'])
|
||||
|
||||
elif album['ReleaseDate'] > helpers.today() and headphones.AUTOWANT_UPCOMING:
|
||||
newValueDict['Status'] = "Wanted"
|
||||
else:
|
||||
newValueDict['Status'] = "Skipped"
|
||||
|
||||
myDB.upsert("albums", newValueDict, controlValueDict)
|
||||
|
||||
#start a search for the album if it's new and autowant_all is selected:
|
||||
# Should this run in a background thread? Don't know if we want to have a bunch of
|
||||
# simultaneous threads running
|
||||
if not rg_exists and headphones.AUTOWANT_ALL:
|
||||
from headphones import searcher
|
||||
searcher.searchforalbum(albumid=rg['id'])
|
||||
|
||||
myDB.action('DELETE from tracks WHERE AlbumID=?', [rg['id']])
|
||||
tracks = myDB.action('SELECT * from alltracks WHERE ReleaseID=?', [releaseid]).fetchall()
|
||||
@@ -412,14 +420,18 @@ def addArtisttoDB(artistid, extrasonly=False):
|
||||
"TotalTracks": totaltracks,
|
||||
"HaveTracks": havetracks}
|
||||
|
||||
newValueDict['LastUpdated'] = helpers.now()
|
||||
if not errors:
|
||||
newValueDict['LastUpdated'] = helpers.now()
|
||||
|
||||
myDB.upsert("artists", newValueDict, controlValueDict)
|
||||
|
||||
logger.info(u"Seeing if we need album art for: " + artist['artist_name'])
|
||||
cache.getThumb(ArtistID=artistid)
|
||||
|
||||
logger.info(u"Updating complete for: " + artist['artist_name'])
|
||||
if errors:
|
||||
logger.info("Finished updating artist: " + artist['artist_name'] + " but with errors, so not marking it as updated in the database")
|
||||
else:
|
||||
logger.info(u"Updating complete for: " + artist['artist_name'])
|
||||
|
||||
def addReleaseById(rid):
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None):
|
||||
dir = dir.encode(headphones.SYS_ENCODING)
|
||||
|
||||
if not os.path.isdir(dir):
|
||||
logger.warn('Cannot find directory: %s. Not scanning' % dir.decode(headphones.SYS_ENCODING))
|
||||
logger.warn('Cannot find directory: %s. Not scanning' % dir.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
return
|
||||
|
||||
myDB = db.DBConnection()
|
||||
@@ -48,7 +48,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None):
|
||||
|
||||
myDB.action('DELETE from have')
|
||||
|
||||
logger.info('Scanning music directory: %s' % dir)
|
||||
logger.info('Scanning music directory: %s' % dir.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
|
||||
new_artists = []
|
||||
bitrates = []
|
||||
@@ -105,7 +105,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None):
|
||||
|
||||
# Now we start track matching
|
||||
total_number_of_songs = len(song_list)
|
||||
logger.info("Found " + str(total_number_of_songs) + " tracks in: '" + dir + "'. Matching tracks to the appropriate releases....")
|
||||
logger.info("Found " + str(total_number_of_songs) + " tracks in: '" + dir.decode(headphones.SYS_ENCODING, 'replace') + "'. Matching tracks to the appropriate releases....")
|
||||
|
||||
# Sort the song_list by most vague (e.g. no trackid or releaseid) to most specific (both trackid & releaseid)
|
||||
# When we insert into the database, the tracks with the most specific information will overwrite the more general matches
|
||||
@@ -299,7 +299,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None):
|
||||
|
||||
myDB.action('INSERT INTO have (ArtistName, AlbumTitle, TrackNumber, TrackTitle, TrackLength, BitRate, Genre, Date, TrackID, Location, CleanName, Format) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [song['ArtistName'], song['AlbumTitle'], song['TrackNumber'], song['TrackTitle'], song['TrackLength'], song['BitRate'], song['Genre'], song['Date'], song['TrackID'], song['Location'], CleanName, song['Format']])
|
||||
|
||||
logger.info('Completed matching tracks from directory: %s' % dir)
|
||||
logger.info('Completed matching tracks from directory: %s' % dir.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
|
||||
|
||||
if not append:
|
||||
|
||||
@@ -49,41 +49,41 @@ def encode(albumPath):
|
||||
if (headphones.ENCODERLOSSLESS):
|
||||
if (music.lower().endswith('.flac')):
|
||||
musicFiles.append(os.path.join(r, music))
|
||||
musicTemp = os.path.normpath(os.path.splitext(music)[0]+'.'+headphones.ENCODEROUTPUTFORMAT)
|
||||
musicTemp = os.path.normpath(os.path.splitext(music)[0]+'.'+headphones.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING))
|
||||
musicTempFiles.append(os.path.join(tempDirEncode, musicTemp))
|
||||
else:
|
||||
logger.debug('Music "%s" is already encoded' % (music))
|
||||
else:
|
||||
musicFiles.append(os.path.join(r, music))
|
||||
musicTemp = os.path.normpath(os.path.splitext(music)[0]+'.'+headphones.ENCODEROUTPUTFORMAT)
|
||||
musicTemp = os.path.normpath(os.path.splitext(music)[0]+'.'+headphones.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING))
|
||||
musicTempFiles.append(os.path.join(tempDirEncode, musicTemp))
|
||||
|
||||
if headphones.ENCODER=='lame':
|
||||
encoder=os.path.join(headphones.ENCODERFOLDER,'lame')
|
||||
encoder=os.path.join(headphones.ENCODERFOLDER.encode(headphones.SYS_ENCODING),'lame')
|
||||
elif headphones.ENCODER=='ffmpeg':
|
||||
encoder=os.path.join(headphones.ENCODERFOLDER,'ffmpeg')
|
||||
encoder=os.path.join(headphones.ENCODERFOLDER.encode(headphones.SYS_ENCODING),'ffmpeg')
|
||||
i=0
|
||||
for music in musicFiles:
|
||||
infoMusic=MediaFile(music)
|
||||
if headphones.ENCODER == 'lame':
|
||||
if not any(music.lower().endswith('.' + x) for x in ["mp3", "wav"]):
|
||||
logger.warn('Lame cant encode "%s" format for "%s", use ffmpeg' % (os.path.splitext(music)[1],music))
|
||||
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)):
|
||||
logger.info('Music "%s" has bitrate<="%skbit" will not be reencoded' % (music,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'):
|
||||
logger.warn('Can not reencode .ogg music "%s"' % (music))
|
||||
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)):
|
||||
logger.info('Music "%s" has bitrate<="%skbit" will not be reencoded' % (music,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
|
||||
@@ -97,7 +97,7 @@ def encode(albumPath):
|
||||
musicFinalFiles.append(os.path.join(r, music))
|
||||
|
||||
if ifencoded==0:
|
||||
logger.info('Encoding for folder "%s" is not needed' % (albumPath))
|
||||
logger.info('Encoding for folder "%s" is not needed' % (albumPath.decode(headphones.SYS_ENCODING, 'replace')))
|
||||
|
||||
return musicFinalFiles
|
||||
|
||||
|
||||
@@ -171,14 +171,17 @@ class NMA:
|
||||
|
||||
return response
|
||||
|
||||
def notify(self, artist, album):
|
||||
def notify(self, artist=None, album=None, snatched_nzb=None):
|
||||
|
||||
apikey = self.apikey
|
||||
priority = self.priority
|
||||
|
||||
event = artist + ' - ' + album + ' complete!'
|
||||
|
||||
description = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
|
||||
if snatched_nzb:
|
||||
event = snatched_nzb + " snatched!"
|
||||
description = "Headphones has snatched: " + snatched_nzb + " and has sent it to SABnzbd+"
|
||||
else:
|
||||
event = artist + ' - ' + album + ' complete!'
|
||||
description = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
|
||||
|
||||
data = { 'apikey': apikey, 'application':'Headphones', 'event': event, 'description': description, 'priority': priority}
|
||||
|
||||
@@ -223,4 +226,4 @@ class Synoindex:
|
||||
def notify_multiple(self, path_list):
|
||||
if isinstance(path_list, list):
|
||||
for path in path_list:
|
||||
self.notify(path)
|
||||
self.notify(path)
|
||||
|
||||
@@ -160,7 +160,7 @@ def verify(albumid, albumpath):
|
||||
try:
|
||||
f = MediaFile(downloaded_track)
|
||||
except Exception, e:
|
||||
logger.info("Exception from MediaFile for: " + downloaded_track + " : " + str(e))
|
||||
logger.info(u"Exception from MediaFile for: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + u" : " + unicode(e))
|
||||
continue
|
||||
|
||||
metaartist = helpers.latinToAscii(f.artist.lower()).encode('UTF-8')
|
||||
@@ -222,13 +222,13 @@ def verify(albumid, albumpath):
|
||||
doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list)
|
||||
return
|
||||
|
||||
logger.warn('Could not identify album: %s. It may not be the intended album.' % albumpath)
|
||||
logger.warn(u'Could not identify album: %s. It may not be the intended album.' % albumpath.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
myDB.action('UPDATE snatched SET status = "Unprocessed" WHERE AlbumID=?', [albumid])
|
||||
processed = re.search(r' \(Unprocessed\)(?:\[\d+\])?', albumpath)
|
||||
if not processed:
|
||||
renameUnprocessedFolder(albumpath)
|
||||
else:
|
||||
logger.info("Already marked as unprocessed: " + albumpath)
|
||||
logger.info(u"Already marked as unprocessed: " + albumpath.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
|
||||
def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list):
|
||||
|
||||
@@ -266,12 +266,13 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list)
|
||||
if headphones.RENAME_FILES:
|
||||
renameFiles(albumpath, downloaded_track_list, release)
|
||||
|
||||
if headphones.MOVE_FILES and headphones.DESTINATION_DIR:
|
||||
albumpaths = moveFiles(albumpath, release, tracks)
|
||||
|
||||
if headphones.MOVE_FILES and not headphones.DESTINATION_DIR:
|
||||
logger.error('No DESTINATION_DIR has been set. Set "Destination Directory" to the parent directory you want to move the files to')
|
||||
pass
|
||||
albumpaths = [albumpath]
|
||||
elif headphones.MOVE_FILES and headphones.DESTINATION_DIR:
|
||||
albumpaths = moveFiles(albumpath, release, tracks)
|
||||
else:
|
||||
albumpaths = [albumpath]
|
||||
|
||||
myDB = db.DBConnection()
|
||||
myDB.action('UPDATE albums SET status = "Downloaded" WHERE AlbumID=?', [albumid])
|
||||
@@ -281,9 +282,9 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list)
|
||||
for albumpath in albumpaths:
|
||||
librarysync.libraryScan(dir=albumpath, append=True, ArtistID=release['ArtistID'], ArtistName=release['ArtistName'])
|
||||
|
||||
logger.info('Post-processing for %s - %s complete' % (release['ArtistName'], release['AlbumTitle']))
|
||||
logger.info(u'Post-processing for %s - %s complete' % (release['ArtistName'], release['AlbumTitle']))
|
||||
|
||||
if headphones.PROWL_ONSNATCH:
|
||||
if headphones.PROWL_ENABLED:
|
||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
||||
logger.info(u"Prowl request")
|
||||
prowl = notifiers.PROWL()
|
||||
@@ -312,7 +313,7 @@ def embedAlbumArt(artwork, downloaded_track_list):
|
||||
try:
|
||||
f = MediaFile(downloaded_track)
|
||||
except:
|
||||
logger.error('Could not read %s. Not adding album art' % downloaded_track)
|
||||
logger.error(u'Could not read %s. Not adding album art' % downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
continue
|
||||
|
||||
logger.debug('Adding album art to: %s' % downloaded_track)
|
||||
@@ -336,7 +337,7 @@ def cleanupFiles(albumpath):
|
||||
try:
|
||||
os.remove(os.path.join(r, files))
|
||||
except Exception, e:
|
||||
logger.error('Could not remove file: %s. Error: %s' % (files, e))
|
||||
logger.error(u'Could not remove file: %s. Error: %s' % (files.decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||
|
||||
def moveFiles(albumpath, release, tracks):
|
||||
|
||||
@@ -486,9 +487,9 @@ def moveFiles(albumpath, release, tracks):
|
||||
try:
|
||||
os.remove(file_to_move)
|
||||
except Exception, e:
|
||||
logger.error("Error deleting file '" + file_to_move.decode(headphones.SYS_ENCODING) + "' from source directory")
|
||||
logger.error("Error deleting file '" + file_to_move.decode(headphones.SYS_ENCODING, 'replace') + "' from source directory")
|
||||
else:
|
||||
logger.error("Error copying '" + file_to_move.decode(headphones.SYS_ENCODING) + "'. Not deleting from download directory")
|
||||
logger.error("Error copying '" + file_to_move.decode(headphones.SYS_ENCODING, 'replace') + "'. Not deleting from download directory")
|
||||
|
||||
elif make_lossless_folder and not make_lossy_folder:
|
||||
|
||||
@@ -519,13 +520,13 @@ def moveFiles(albumpath, release, tracks):
|
||||
try:
|
||||
os.chmod(os.path.normpath(temp_f).encode(headphones.SYS_ENCODING), int(headphones.FOLDER_PERMISSIONS, 8))
|
||||
except Exception, e:
|
||||
logger.error("Error trying to change permissions on folder: %s" % temp_f)
|
||||
logger.error("Error trying to change permissions on folder: %s" % temp_f.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
|
||||
# If we failed to move all the files out of the directory, this will fail too
|
||||
try:
|
||||
shutil.rmtree(albumpath)
|
||||
except Exception, e:
|
||||
logger.error('Could not remove directory: %s. %s' % (albumpath, e))
|
||||
logger.error('Could not remove directory: %s. %s' % (albumpath.decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||
|
||||
destination_paths = []
|
||||
|
||||
@@ -552,10 +553,10 @@ def correctMetadata(albumid, release, downloaded_track_list):
|
||||
elif any(downloaded_track.lower().endswith('.' + x.lower()) for x in headphones.LOSSY_MEDIA_FORMATS):
|
||||
lossy_items.append(beets.library.Item.from_path(downloaded_track))
|
||||
else:
|
||||
logger.warn("Skipping: " + downloaded_track.decode(headphones.SYS_ENCODING) + " because it is not a mutagen friendly file format")
|
||||
logger.warn("Skipping: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + " because it is not a mutagen friendly file format")
|
||||
except Exception, e:
|
||||
|
||||
logger.error("Beets couldn't create an Item from: " + downloaded_track.decode(headphones.SYS_ENCODING) + " - not a media file?" + str(e))
|
||||
logger.error("Beets couldn't create an Item from: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + " - not a media file?" + str(e))
|
||||
|
||||
for items in [lossy_items, lossless_items]:
|
||||
|
||||
@@ -586,9 +587,9 @@ def correctMetadata(albumid, release, downloaded_track_list):
|
||||
for item in items:
|
||||
try:
|
||||
item.write()
|
||||
logger.info("Successfully applied metadata to: " + item.path.decode(headphones.SYS_ENCODING))
|
||||
logger.info("Successfully applied metadata to: " + item.path.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
except Exception, e:
|
||||
logger.warn("Error writing metadata to " + item.path.decode(headphones.SYS_ENCODING) + ": " + str(e))
|
||||
logger.warn("Error writing metadata to " + item.path.decode(headphones.SYS_ENCODING, 'replace') + ": " + str(e))
|
||||
|
||||
def embedLyrics(downloaded_track_list):
|
||||
logger.info('Adding lyrics')
|
||||
@@ -600,14 +601,14 @@ def embedLyrics(downloaded_track_list):
|
||||
try:
|
||||
f = MediaFile(downloaded_track)
|
||||
except:
|
||||
logger.error('Could not read %s. Not checking lyrics' % downloaded_track)
|
||||
logger.error('Could not read %s. Not checking lyrics' % downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
|
||||
if f.albumartist and f.title:
|
||||
metalyrics = lyrics.getLyrics(f.albumartist, f.title)
|
||||
elif f.artist and f.title:
|
||||
metalyrics = lyrics.getLyrics(f.artist, f.title)
|
||||
else:
|
||||
logger.info('No artist/track metadata found for track: %s. Not fetching lyrics' % downloaded_track)
|
||||
logger.info('No artist/track metadata found for track: %s. Not fetching lyrics' % downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
metalyrics = None
|
||||
|
||||
if lyrics:
|
||||
@@ -627,7 +628,7 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
||||
try:
|
||||
f = MediaFile(downloaded_track)
|
||||
except:
|
||||
logger.info("MediaFile couldn't parse: " + downloaded_track.decode(headphones.SYS_ENCODING))
|
||||
logger.info("MediaFile couldn't parse: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
continue
|
||||
|
||||
if not f.track:
|
||||
@@ -637,7 +638,7 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
||||
|
||||
if not f.title:
|
||||
|
||||
basename = unicode(os.path.basename(downloaded_track), headphones.SYS_ENCODING, errors='replace')
|
||||
basename = os.path.basename(downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
title = os.path.splitext(basename)[0]
|
||||
ext = os.path.splitext(basename)[1]
|
||||
|
||||
@@ -678,7 +679,7 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
||||
try:
|
||||
os.rename(downloaded_track, new_file)
|
||||
except Exception, e:
|
||||
logger.error('Error renaming file: %s. Error: %s' % (downloaded_track.decode(headphones.SYS_ENCODING), e))
|
||||
logger.error('Error renaming file: %s. Error: %s' % (downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||
continue
|
||||
|
||||
def renameUnprocessedFolder(albumpath):
|
||||
@@ -698,66 +699,55 @@ def renameUnprocessedFolder(albumpath):
|
||||
return
|
||||
|
||||
def forcePostProcess():
|
||||
|
||||
if not headphones.DOWNLOAD_DIR:
|
||||
logger.error('No DOWNLOAD_DIR has been set. Set "Music Download Directory:" to your SAB download directory on the settings page.')
|
||||
return
|
||||
|
||||
download_dirs = []
|
||||
if headphones.DOWNLOAD_DIR:
|
||||
download_dirs.append(headphones.DOWNLOAD_DIR.encode(headphones.SYS_ENCODING))
|
||||
if headphones.DOWNLOAD_TORRENT_DIR:
|
||||
download_dirs.append(headphones.DOWNLOAD_TORRENT_DIR.encode(headphones.SYS_ENCODING))
|
||||
|
||||
logger.info('Checking to see if there are any folders to process in download_dir(s): %s' % str(download_dirs).decode(headphones.SYS_ENCODING, 'replace'))
|
||||
# Get a list of folders in the download_dir
|
||||
folders = []
|
||||
for download_dir in download_dirs:
|
||||
for folder in os.listdir(download_dir):
|
||||
path_to_folder = os.path.join(download_dir, folder)
|
||||
if os.path.isdir(path_to_folder):
|
||||
folders.append(path_to_folder)
|
||||
|
||||
if len(folders):
|
||||
logger.info('Found %i folders to process' % len(folders))
|
||||
else:
|
||||
processing = "nzb"
|
||||
processing_next = "torrent"
|
||||
while processing != "done":
|
||||
if headphones.DOWNLOAD_DIR and processing == "nzb":
|
||||
download_dir = headphones.DOWNLOAD_DIR.encode('utf-8')
|
||||
if not headphones.DOWNLOAD_TORRENT_DIR:
|
||||
processing_next = "done"
|
||||
if headphones.DOWNLOAD_TORRENT_DIR and processing == "torrent":
|
||||
download_dir = headphones.DOWNLOAD_TORRENT_DIR.encode('utf-8')
|
||||
processing_next = "done"
|
||||
if not headphones.DOWNLOAD_DIR and processing == "nzb":
|
||||
download_dir = headphones.DOWNLOAD_TORRENT_DIR.encode('utf-8')
|
||||
processing_next = "done"
|
||||
|
||||
logger.info('Checking to see if there are any folders to process in download_dir: %s' % download_dir)
|
||||
# Get a list of folders in the download_dir
|
||||
folders = [d for d in os.listdir(download_dir) if os.path.isdir(os.path.join(download_dir, d))]
|
||||
logger.info('Found no folders to process in: %s' % str(download_dirs).decode(headphones.SYS_ENCODING, 'replace'))
|
||||
|
||||
# Parse the folder names to get artist album info
|
||||
for folder in folders:
|
||||
|
||||
folder_basename = os.path.basename(folder).decode(headphones.SYS_ENCODING, 'replace')
|
||||
|
||||
logger.info('Processing: %s' % folder_basename)
|
||||
|
||||
if len(folders):
|
||||
logger.info('Found %i folders to process' % len(folders))
|
||||
pass
|
||||
try:
|
||||
name, album, year = helpers.extract_data(folder_basename)
|
||||
except:
|
||||
logger.info("Couldn't parse " + folder_basename + " into any valid format.")
|
||||
continue
|
||||
if name and album and year:
|
||||
|
||||
myDB = db.DBConnection()
|
||||
release = myDB.action('SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE ArtistName LIKE ? and AlbumTitle LIKE ?', [name, album]).fetchone()
|
||||
if release:
|
||||
logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album' % (release['ArtistName'], release['AlbumTitle']))
|
||||
verify(release['AlbumID'], folder)
|
||||
else:
|
||||
logger.info('Found no folders to process in: %s' % download_dir)
|
||||
return
|
||||
|
||||
# Parse the folder names to get artist album info
|
||||
for folder in folders:
|
||||
|
||||
albumpath = os.path.join(download_dir, folder)
|
||||
folder = unicode(folder, headphones.SYS_ENCODING, errors='replace')
|
||||
|
||||
logger.info('Processing: %s' % folder)
|
||||
|
||||
logger.info('Querying MusicBrainz for the release group id for: %s - %s' % (name, album))
|
||||
from headphones import mb
|
||||
try:
|
||||
name, album, year = helpers.extract_data(folder)
|
||||
rgid = mb.findAlbumID(helpers.latinToAscii(name), helpers.latinToAscii(album))
|
||||
except:
|
||||
logger.info("Couldn't parse " + folder + " into any valid format.")
|
||||
logger.error('Can not get release information for this album')
|
||||
continue
|
||||
if name and album and year:
|
||||
|
||||
myDB = db.DBConnection()
|
||||
release = myDB.action('SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE ArtistName LIKE ? and AlbumTitle LIKE ?', [name, album]).fetchone()
|
||||
if release:
|
||||
logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album' % (release['ArtistName'], release['AlbumTitle']))
|
||||
verify(release['AlbumID'], albumpath)
|
||||
else:
|
||||
logger.info('Querying MusicBrainz for the release group id for: %s - %s' % (name, album))
|
||||
from headphones import mb
|
||||
try:
|
||||
rgid = mb.findAlbumID(helpers.latinToAscii(name), helpers.latinToAscii(album))
|
||||
except:
|
||||
logger.error('Can not get release information for this album')
|
||||
continue
|
||||
if rgid:
|
||||
verify(rgid, albumpath)
|
||||
else:
|
||||
logger.info('No match found on MusicBrainz for: %s - %s' % (name, album))
|
||||
processing = processing_next
|
||||
if rgid:
|
||||
verify(rgid, folder)
|
||||
else:
|
||||
logger.info('No match found on MusicBrainz for: %s - %s' % (name, album))
|
||||
|
||||
@@ -27,7 +27,7 @@ import urllib2, cookielib
|
||||
|
||||
from headphones.common import USER_AGENT
|
||||
from headphones import logger
|
||||
from headphones import notifiers
|
||||
from headphones import notifiers, helpers
|
||||
|
||||
def sendNZB(nzb):
|
||||
|
||||
@@ -64,8 +64,10 @@ def sendNZB(nzb):
|
||||
|
||||
# if we get a raw data result we want to upload it to SAB
|
||||
elif nzb.resultType == "nzbdata":
|
||||
# Sanitize the file a bit, since we can only use ascii chars with MultiPartPostHandler
|
||||
nzbdata = helpers.latinToAscii(nzb.extraInfo[0])
|
||||
params['mode'] = 'addfile'
|
||||
multiPartParams = {"nzbfile": (nzb.name+".nzb", nzb.extraInfo[0])}
|
||||
multiPartParams = {"nzbfile": (nzb.name+".nzb", nzbdata)}
|
||||
|
||||
if not headphones.SAB_HOST.startswith('http'):
|
||||
headphones.SAB_HOST = 'http://' + headphones.SAB_HOST
|
||||
@@ -78,7 +80,7 @@ def sendNZB(nzb):
|
||||
try:
|
||||
|
||||
if nzb.resultType == "nzb":
|
||||
f = urllib.urlopen(url)
|
||||
f = urllib.urlopen(url)
|
||||
elif nzb.resultType == "nzbdata":
|
||||
cookies = cookielib.CookieJar()
|
||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies),
|
||||
@@ -97,7 +99,11 @@ def sendNZB(nzb):
|
||||
except httplib.InvalidURL, e:
|
||||
logger.error(u"Invalid SAB host, check your config. Current host: %s" % headphones.SAB_HOST)
|
||||
return False
|
||||
|
||||
|
||||
except Exception, e:
|
||||
logger.error(u"Error: " + str(e))
|
||||
return False
|
||||
|
||||
if f == None:
|
||||
logger.info(u"No data returned from SABnzbd, NZB not sent")
|
||||
return False
|
||||
@@ -118,10 +124,14 @@ def sendNZB(nzb):
|
||||
|
||||
if sabText == "ok":
|
||||
logger.info(u"NZB sent to SAB successfully")
|
||||
if headphones.PROWL_ONSNATCH:
|
||||
logger.info(u"Prowl request")
|
||||
if headphones.PROWL_ENABLED and headphones.PROWL_ONSNATCH:
|
||||
logger.info(u"Sending Prowl notification")
|
||||
prowl = notifiers.PROWL()
|
||||
prowl.notify(nzb.name,"Download started")
|
||||
if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
|
||||
logger.debug(u"Sending NMA notification")
|
||||
nma = notifiers.NMA()
|
||||
nma.notify(snatched_nzb=nzb.name)
|
||||
|
||||
return True
|
||||
elif sabText == "Missing authentication":
|
||||
|
||||
@@ -511,6 +511,7 @@ class WebInterface(object):
|
||||
"nma_enabled": checked(headphones.NMA_ENABLED),
|
||||
"nma_apikey": headphones.NMA_APIKEY,
|
||||
"nma_priority": int(headphones.NMA_PRIORITY),
|
||||
"nma_onsnatch": checked(headphones.NMA_ONSNATCH),
|
||||
"synoindex_enabled": checked(headphones.SYNOINDEX_ENABLED),
|
||||
"mirror_list": headphones.MIRRORLIST,
|
||||
"mirror": headphones.MIRROR,
|
||||
@@ -550,7 +551,7 @@ class WebInterface(object):
|
||||
interface=None, log_dir=None, music_encoder=0, encoder=None, bitrate=None, 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, synoindex_enabled=False, mirror=None, customhost=None, customport=None, customsleep=None, hpuser=None, hppass=None,
|
||||
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, **kwargs):
|
||||
|
||||
headphones.HTTP_HOST = http_host
|
||||
@@ -639,6 +640,7 @@ class WebInterface(object):
|
||||
headphones.NMA_ENABLED = nma_enabled
|
||||
headphones.NMA_APIKEY = nma_apikey
|
||||
headphones.NMA_PRIORITY = nma_priority
|
||||
headphones.NMA_ONSNATCH = nma_onsnatch
|
||||
headphones.SYNOINDEX_ENABLED = synoindex_enabled
|
||||
headphones.MIRROR = mirror
|
||||
headphones.CUSTOMHOST = customhost
|
||||
|
||||
88
init-alt.freebsd
Normal file
88
init-alt.freebsd
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# PROVIDE: headphones
|
||||
# REQUIRE: sabnzbd
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
|
||||
# to enable this service:
|
||||
#
|
||||
# headphones_enable (bool): Set to NO by default.
|
||||
# Set it to YES to enable it.
|
||||
# headphones_user: The user account Headphones daemon runs as what
|
||||
# you want it to be. It uses '_sabnzbd' user by
|
||||
# default. Do not sets it as empty or it will run
|
||||
# as root.
|
||||
# headphones_dir: Directory where Headphones lives.
|
||||
# Default: /usr/local/headphones
|
||||
# headphones_chdir: Change to this directory before running Headphones.
|
||||
# Default is same as headphones_dir.
|
||||
# headphones_pid: The name of the pidfile to create.
|
||||
# Default is headphones.pid in headphones_dir.
|
||||
|
||||
. /etc/rc.subr
|
||||
|
||||
name="headphones"
|
||||
rcvar=${name}_enable
|
||||
|
||||
load_rc_config ${name}
|
||||
|
||||
: ${headphones_enable:="NO"}
|
||||
: ${headphones_user:="_sabnzbd"}
|
||||
: ${headphones_dir:="/usr/local/headphones"}
|
||||
: ${headphones_chdir:="${headphones_dir}"}
|
||||
: ${headphones_pid:="${headphones_dir}/headphones.pid"}
|
||||
: ${headphones_conf:="${headphones_dir}/config.ini"}
|
||||
|
||||
WGET="/usr/local/bin/wget" # You need wget for this script to safely shutdown Headphones.
|
||||
if [ -e "${headphones_conf}" ]; then
|
||||
HOST=`grep -A64 "\[General\]" "${headphones_conf}"|egrep "^http_host"|perl -wple 's/^http_host = (.*)$/$1/'`
|
||||
PORT=`grep -A64 "\[General\]" "${headphones_conf}"|egrep "^http_port"|perl -wple 's/^http_port = (.*)$/$1/'`
|
||||
fi
|
||||
|
||||
status_cmd="${name}_status"
|
||||
stop_cmd="${name}_stop"
|
||||
|
||||
command="${headphones_dir}/Headphones.py"
|
||||
command_args="--daemon --quiet --nolaunch --port ${PORT} --pidfile ${headphones_pid} --config ${headphones_conf}"
|
||||
|
||||
# Check for wget and refuse to start without it.
|
||||
if [ ! -x "${WGET}" ]; then
|
||||
warn "Headphones not started: You need wget to safely shut down Headphones."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure user is root when running this script.
|
||||
if [ `id -u` != "0" ]; then
|
||||
echo "Oops, you should be root before running this!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_headphones_pid() {
|
||||
# Make sure the pid corresponds to the Headphones process.
|
||||
pid=`cat ${headphones_pid} 2>/dev/null`
|
||||
ps -p ${pid} | grep -q "python ${headphones_dir}/Headphones.py"
|
||||
return $?
|
||||
}
|
||||
|
||||
# Try to stop Headphones cleanly by calling shutdown over http.
|
||||
headphones_stop() {
|
||||
if [ ! -e "${headphones_conf}" ]; then
|
||||
echo "Headphones' settings file does not exist. Try starting Headphones, as this should create the file."
|
||||
exit 1
|
||||
fi
|
||||
echo "Stopping $name"
|
||||
verify_headphones_pid
|
||||
${WGET} -O - -q --user=${SBUSR} --password=${SBPWD} "http://${HOST}:${PORT}/shutdown/" >/dev/null
|
||||
|
||||
if [ -n "${pid}" ]; then
|
||||
wait_for_pids ${pid}
|
||||
echo "Stopped $name"
|
||||
fi
|
||||
}
|
||||
|
||||
headphones_status() {
|
||||
verify_headphones_pid && echo "$name is running as ${pid}" || echo "$name is not running"
|
||||
}
|
||||
|
||||
run_rc_command "$1"
|
||||
Reference in New Issue
Block a user