diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 75219f33..643af20e 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -618,7 +618,10 @@ m<%inherit file="base.html"/>
- +
+ +
+ Separate multiple api keys with commas
diff --git a/headphones/__init__.py b/headphones/__init__.py index 3126e793..0e06ba4c 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -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) diff --git a/headphones/importer.py b/headphones/importer.py index 571360e9..d8ceb281 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -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): diff --git a/headphones/librarysync.py b/headphones/librarysync.py index 1a513564..7001c14c 100644 --- a/headphones/librarysync.py +++ b/headphones/librarysync.py @@ -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: diff --git a/headphones/music_encoder.py b/headphones/music_encoder.py index 71da9ba5..2cff0505 100644 --- a/headphones/music_encoder.py +++ b/headphones/music_encoder.py @@ -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 diff --git a/headphones/notifiers.py b/headphones/notifiers.py index 3c16b647..3df88485 100644 --- a/headphones/notifiers.py +++ b/headphones/notifiers.py @@ -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) \ No newline at end of file + self.notify(path) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index f9bfc833..ba4f5169 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -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)) diff --git a/headphones/sab.py b/headphones/sab.py index 6d57214c..9b0e2bd1 100644 --- a/headphones/sab.py +++ b/headphones/sab.py @@ -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": diff --git a/headphones/webserve.py b/headphones/webserve.py index 14f29a80..799d84f3 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -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 diff --git a/init-alt.freebsd b/init-alt.freebsd new file mode 100644 index 00000000..16fd5f97 --- /dev/null +++ b/init-alt.freebsd @@ -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"