mirror of
https://github.com/rembo10/headphones.git
synced 2026-05-15 16:19:28 +01:00
Merge pull request #1883 from basilfx/improvements2
Again, a set of improvements
This commit is contained in:
@@ -63,7 +63,7 @@ def main():
|
||||
headphones.SYS_ENCODING = 'UTF-8'
|
||||
|
||||
# Set up and gather command line arguments
|
||||
parser = argparse.ArgumentParser(description='Music add-on for SABnzbd+')
|
||||
parser = argparse.ArgumentParser(description='Music add-on for SABnzbd+, Transmission and more.')
|
||||
|
||||
parser.add_argument('-v', '--verbose', action='store_true', help='Increase console logging verbosity')
|
||||
parser.add_argument('-q', '--quiet', action='store_true', help='Turn off console logging')
|
||||
|
||||
@@ -896,8 +896,26 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<h3>Subsonic</h3>
|
||||
<div class="row checkbox">
|
||||
<input type="checkbox" name="subsonic_enabled" id="subsonic" value="1" ${config['subsonic_enabled']} /><label>Enable Subsonic Updates</label>
|
||||
</div>
|
||||
<div id="subsonicoptions">
|
||||
<div class="row">
|
||||
<label>Subsonic URL</label><input type="text" name="subsonic_host" value="${config['subsonic_host']}" size="30">
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>Subsonic Username</label><input type="text" name="subsonic_username" value="${config['subsonic_username']}" size="30">
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>Subsonic Password</label><input type="password" name="subsonic_password" value="${config['subsonic_password']}" size="30">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<td>
|
||||
|
||||
<fieldset>
|
||||
<h3>Synology NAS</h3>
|
||||
@@ -1730,6 +1748,26 @@
|
||||
}
|
||||
});
|
||||
|
||||
if ($("#subsonic").is(":checked"))
|
||||
{
|
||||
$("#subsonicoptions").show();
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#subsonicoptions").hide();
|
||||
}
|
||||
|
||||
$("#subsonic").click(function(){
|
||||
if ($("#subsonic").is(":checked"))
|
||||
{
|
||||
$("#subsonicoptions").slideDown();
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#subsonicoptions").slideUp();
|
||||
}
|
||||
});
|
||||
|
||||
if ($("#songkick").is(":checked"))
|
||||
{
|
||||
$("#songkickoptions").show();
|
||||
|
||||
@@ -283,6 +283,10 @@ OSX_NOTIFY_APP = None
|
||||
BOXCAR_ENABLED = False
|
||||
BOXCAR_ONSNATCH = False
|
||||
BOXCAR_TOKEN = None
|
||||
SUBSONIC_ENABLED = False
|
||||
SUBSONIC_HOST = None
|
||||
SUBSONIC_USERNAME = None
|
||||
SUBSONIC_PASSWORD = None
|
||||
MIRRORLIST = ["musicbrainz.org","headphones","custom"]
|
||||
MIRROR = None
|
||||
CUSTOMHOST = None
|
||||
@@ -371,7 +375,7 @@ def initialize():
|
||||
XBMC_NOTIFY, LMS_ENABLED, LMS_HOST, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
|
||||
PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, LOSSLESS_BITRATE_FROM, LOSSLESS_BITRATE_TO, CACHE_SIZEMB, JOURNAL_MODE, UMASK, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \
|
||||
PLEX_ENABLED, PLEX_SERVER_HOST, PLEX_CLIENT_HOST, PLEX_USERNAME, PLEX_PASSWORD, PLEX_UPDATE, PLEX_NOTIFY, PUSHALOT_ENABLED, PUSHALOT_APIKEY, \
|
||||
PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED, VERIFY_SSL_CERT
|
||||
PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED, SUBSONIC_ENABLED, SUBSONIC_HOST, SUBSONIC_USERNAME, SUBSONIC_PASSWORD, VERIFY_SSL_CERT
|
||||
|
||||
|
||||
if __INITIALIZED__:
|
||||
@@ -627,6 +631,11 @@ def initialize():
|
||||
BOXCAR_ONSNATCH = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_onsnatch', 0))
|
||||
BOXCAR_TOKEN = check_setting_str(CFG, 'Boxcar', 'boxcar_token', '')
|
||||
|
||||
SUBSONIC_ENABLED = bool(check_setting_int(CFG, 'Subsonic', 'subsonic_enabled', 0))
|
||||
SUBSONIC_HOST = check_setting_str(CFG, 'Subsonic', 'subsonic_host', '')
|
||||
SUBSONIC_USERNAME = check_setting_str(CFG, 'Subsonic', 'subsonic_username', '')
|
||||
SUBSONIC_PASSWORD = check_setting_str(CFG, 'Subsonic', 'subsonic_password', '')
|
||||
|
||||
SONGKICK_ENABLED = bool(check_setting_int(CFG, 'Songkick', 'songkick_enabled', 1))
|
||||
SONGKICK_APIKEY = check_setting_str(CFG, 'Songkick', 'songkick_apikey', 'nd1We7dFW2RqxPw8')
|
||||
SONGKICK_LOCATION = check_setting_str(CFG, 'Songkick', 'songkick_location', '')
|
||||
@@ -1071,6 +1080,12 @@ def config_write():
|
||||
new_config['Boxcar']['boxcar_onsnatch'] = int(BOXCAR_ONSNATCH)
|
||||
new_config['Boxcar']['boxcar_token'] = BOXCAR_TOKEN
|
||||
|
||||
new_config['Subsonic'] = {}
|
||||
new_config['Subsonic']['subsonic_enabled'] = int(SUBSONIC_ENABLED)
|
||||
new_config['Subsonic']['subsonic_host'] = SUBSONIC_HOST
|
||||
new_config['Subsonic']['subsonic_username'] = SUBSONIC_USERNAME
|
||||
new_config['Subsonic']['subsonic_password'] = SUBSONIC_PASSWORD
|
||||
|
||||
new_config['Songkick'] = {}
|
||||
new_config['Songkick']['songkick_enabled'] = int(SONGKICK_ENABLED)
|
||||
new_config['Songkick']['songkick_apikey'] = SONGKICK_APIKEY
|
||||
|
||||
@@ -42,24 +42,22 @@ class Cache(object):
|
||||
|
||||
path_to_art_cache = os.path.join(headphones.CACHE_DIR, 'artwork')
|
||||
|
||||
id = None
|
||||
id_type = None # 'artist' or 'album' - set automatically depending on whether ArtistID or AlbumID is passed
|
||||
query_type = None # 'artwork','thumb' or 'info' - set automatically
|
||||
|
||||
artwork_files = []
|
||||
thumb_files = []
|
||||
|
||||
artwork_errors = False
|
||||
artwork_url = None
|
||||
|
||||
thumb_errors = False
|
||||
thumb_url = None
|
||||
|
||||
info_summary = None
|
||||
info_content = None
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
self.id = None
|
||||
self.id_type = None # 'artist' or 'album' - set automatically depending on whether ArtistID or AlbumID is passed
|
||||
self.query_type = None # 'artwork','thumb' or 'info' - set automatically
|
||||
|
||||
self.artwork_files = []
|
||||
self.thumb_files = []
|
||||
|
||||
self.artwork_errors = False
|
||||
self.artwork_url = None
|
||||
|
||||
self.thumb_errors = False
|
||||
self.thumb_url = None
|
||||
|
||||
self.info_summary = None
|
||||
self.info_content = None
|
||||
|
||||
def _findfilesstartingwith(self,pattern,folder):
|
||||
files = []
|
||||
@@ -125,9 +123,9 @@ class Cache(object):
|
||||
return thumb_url
|
||||
|
||||
def get_artwork_from_cache(self, ArtistID=None, AlbumID=None):
|
||||
'''
|
||||
"""
|
||||
Pass a musicbrainz id to this function (either ArtistID or AlbumID)
|
||||
'''
|
||||
"""
|
||||
|
||||
self.query_type = 'artwork'
|
||||
|
||||
@@ -151,9 +149,9 @@ class Cache(object):
|
||||
return None
|
||||
|
||||
def get_thumb_from_cache(self, ArtistID=None, AlbumID=None):
|
||||
'''
|
||||
"""
|
||||
Pass a musicbrainz id to this function (either ArtistID or AlbumID)
|
||||
'''
|
||||
"""
|
||||
|
||||
self.query_type = 'thumb'
|
||||
|
||||
@@ -215,7 +213,7 @@ class Cache(object):
|
||||
|
||||
try:
|
||||
image_url = data['artist']['image'][-1]['#text']
|
||||
except Exception:
|
||||
except (KeyError, IndexError):
|
||||
logger.debug('No artist image found')
|
||||
image_url = None
|
||||
|
||||
@@ -233,7 +231,7 @@ class Cache(object):
|
||||
|
||||
try:
|
||||
image_url = data['album']['image'][-1]['#text']
|
||||
except Exception:
|
||||
except (KeyError, IndexError):
|
||||
logger.debug('No album image found on last.fm')
|
||||
image_url = None
|
||||
|
||||
@@ -260,17 +258,17 @@ class Cache(object):
|
||||
|
||||
try:
|
||||
self.info_summary = data['artist']['bio']['summary']
|
||||
except Exception:
|
||||
except KeyError:
|
||||
logger.debug('No artist bio summary found')
|
||||
self.info_summary = None
|
||||
try:
|
||||
self.info_content = data['artist']['bio']['content']
|
||||
except Exception:
|
||||
except KeyError:
|
||||
logger.debug('No artist bio found')
|
||||
self.info_content = None
|
||||
try:
|
||||
image_url = data['artist']['image'][-1]['#text']
|
||||
except Exception:
|
||||
except KeyError:
|
||||
logger.debug('No artist image found')
|
||||
image_url = None
|
||||
|
||||
@@ -288,17 +286,17 @@ class Cache(object):
|
||||
|
||||
try:
|
||||
self.info_summary = data['album']['wiki']['summary']
|
||||
except Exception:
|
||||
except KeyError:
|
||||
logger.debug('No album summary found')
|
||||
self.info_summary = None
|
||||
try:
|
||||
self.info_content = data['album']['wiki']['content']
|
||||
except Exception:
|
||||
except KeyError:
|
||||
logger.debug('No album infomation found')
|
||||
self.info_content = None
|
||||
try:
|
||||
image_url = data['album']['image'][-1]['#text']
|
||||
except Exception:
|
||||
except KeyError:
|
||||
logger.debug('No album image link found')
|
||||
image_url = None
|
||||
|
||||
@@ -358,10 +356,9 @@ class Cache(object):
|
||||
|
||||
artwork_path = os.path.join(self.path_to_art_cache, self.id + '.' + helpers.today() + ext)
|
||||
try:
|
||||
f = open(artwork_path, 'wb')
|
||||
f.write(artwork)
|
||||
f.close()
|
||||
except Exception, e:
|
||||
with open(artwork_path, 'wb') as f:
|
||||
f.write(artwork)
|
||||
except IOError as e:
|
||||
logger.error('Unable to write to the cache dir: %s', e)
|
||||
self.artwork_errors = True
|
||||
self.artwork_url = image_url
|
||||
@@ -375,7 +372,7 @@ class Cache(object):
|
||||
if not os.path.isdir(self.path_to_art_cache):
|
||||
try:
|
||||
os.makedirs(self.path_to_art_cache)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
logger.error('Unable to create artwork cache dir. Error: %s' + e)
|
||||
self.thumb_errors = True
|
||||
self.thumb_url = thumb_url
|
||||
@@ -384,17 +381,16 @@ class Cache(object):
|
||||
for thumb_file in self.thumb_files:
|
||||
try:
|
||||
os.remove(thumb_file)
|
||||
except:
|
||||
except Exception as e:
|
||||
logger.error('Error deleting file from the cache: %s', thumb_file)
|
||||
|
||||
ext = os.path.splitext(image_url)[1]
|
||||
|
||||
thumb_path = os.path.join(self.path_to_art_cache, 'T_' + self.id + '.' + helpers.today() + ext)
|
||||
try:
|
||||
f = open(thumb_path, 'wb')
|
||||
f.write(artwork)
|
||||
f.close()
|
||||
except Exception, e:
|
||||
with open(thumb_path, 'wb') as f:
|
||||
f.write(artwork)
|
||||
except IOError as e:
|
||||
logger.error('Unable to write to the cache dir: %s', e)
|
||||
self.thumb_errors = True
|
||||
self.thumb_url = image_url
|
||||
|
||||
@@ -13,29 +13,12 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
def ex(e):
|
||||
"""
|
||||
Returns a string from the exception text if it exists.
|
||||
"""
|
||||
|
||||
# sanity check
|
||||
if not e.args or not e.args[0]:
|
||||
return ""
|
||||
|
||||
e_message = e.args[0]
|
||||
|
||||
# if fixStupidEncodings doesn't fix it then maybe it's not a string, in which case we'll try printing it anyway
|
||||
if not e_message:
|
||||
try:
|
||||
e_message = str(e.args[0])
|
||||
except:
|
||||
e_message = ""
|
||||
|
||||
return e_message
|
||||
|
||||
|
||||
class HeadphonesException(Exception):
|
||||
"Generic Headphones Exception - should never be thrown, only subclassed"
|
||||
"""
|
||||
Generic Headphones Exception - should never be thrown, only subclassed
|
||||
"""
|
||||
|
||||
class NewzbinAPIThrottled(HeadphonesException):
|
||||
"Newzbin has throttled us, deal with it"
|
||||
"""
|
||||
Newzbin has throttled us, deal with it
|
||||
"""
|
||||
|
||||
@@ -603,9 +603,11 @@ def create_https_certificates(ssl_cert, ssl_key):
|
||||
|
||||
# Save the key and certificate to disk
|
||||
try:
|
||||
open(ssl_key, 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
|
||||
open(ssl_cert, 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||
except Exception, e:
|
||||
with open(ssl_key, 'w') as f:
|
||||
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
|
||||
with open(ssl_cert, 'w') as f:
|
||||
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||
except IOError as e:
|
||||
logger.error("Error creating SSL key and certificate: %s", e)
|
||||
return False
|
||||
|
||||
|
||||
@@ -13,24 +13,26 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from lib.pyItunes import *
|
||||
import time
|
||||
import threading
|
||||
import os
|
||||
from beets.mediafile import MediaFile
|
||||
|
||||
import headphones
|
||||
from headphones import logger, helpers, db, mb, lastfm
|
||||
|
||||
blacklisted_special_artist_names = ['[anonymous]','[data]','[no artist]','[traditional]','[unknown]','Various Artists']
|
||||
blacklisted_special_artists = ['f731ccc4-e22a-43af-a747-64213329e088','33cf029c-63b0-41a0-9855-be2a3665fb3b',\
|
||||
'314e1c25-dde7-4e4d-b2f4-0a7b9f7c56dc','eec63d3c-3b81-4ad4-b1e4-7c147d4d2b61',\
|
||||
'9be7f096-97ec-4615-8957-8d40b5dcbc41','125ec42a-7229-4250-afc5-e057484327fe',\
|
||||
'89ad4ac3-39f7-470e-963a-56509c546377']
|
||||
from beets.mediafile import MediaFile
|
||||
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
import headphones
|
||||
|
||||
blacklisted_special_artist_names = ['[anonymous]', '[data]', '[no artist]',
|
||||
'[traditional]','[unknown]','Various Artists']
|
||||
blacklisted_special_artists = ['f731ccc4-e22a-43af-a747-64213329e088',
|
||||
'33cf029c-63b0-41a0-9855-be2a3665fb3b',
|
||||
'314e1c25-dde7-4e4d-b2f4-0a7b9f7c56dc',
|
||||
'eec63d3c-3b81-4ad4-b1e4-7c147d4d2b61',
|
||||
'9be7f096-97ec-4615-8957-8d40b5dcbc41',
|
||||
'125ec42a-7229-4250-afc5-e057484327fe',
|
||||
'89ad4ac3-39f7-470e-963a-56509c546377']
|
||||
|
||||
def is_exists(artistid):
|
||||
|
||||
myDB = db.DBConnection()
|
||||
|
||||
# See if the artist is already in the database
|
||||
@@ -56,7 +58,7 @@ def artistlist_to_mbids(artistlist, forced=False):
|
||||
if not isinstance(artist, unicode):
|
||||
try:
|
||||
artist = artist.decode('utf-8', 'replace')
|
||||
except:
|
||||
except Exception:
|
||||
logger.warn("Unable to convert artist to unicode so cannot do a database lookup")
|
||||
continue
|
||||
|
||||
@@ -68,7 +70,6 @@ def artistlist_to_mbids(artistlist, forced=False):
|
||||
|
||||
try:
|
||||
artistid = results[0]['id']
|
||||
|
||||
except IndexError:
|
||||
logger.info('MusicBrainz query turned up no matches for: %s' % artist)
|
||||
continue
|
||||
@@ -130,11 +131,9 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
|
||||
# We need the current minimal info in the database instantly
|
||||
# so we don't throw a 500 error when we redirect to the artistPage
|
||||
|
||||
controlValueDict = {"ArtistID": artistid}
|
||||
|
||||
# Don't replace a known artist name with an "Artist ID" placeholder
|
||||
|
||||
dbartist = myDB.action('SELECT * FROM artists WHERE ArtistID=?', [artistid]).fetchone()
|
||||
|
||||
# Only modify the Include Extras stuff if it's a new artist. We need it early so we know what to fetch
|
||||
@@ -183,20 +182,20 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
|
||||
myDB.upsert("artists", newValueDict, controlValueDict)
|
||||
|
||||
# See if we need to grab extras. Artist specific extras take precedence over global option
|
||||
# Global options are set when adding a new artist
|
||||
myDB = db.DBConnection()
|
||||
|
||||
# See if we need to grab extras. Artist specific extras take precedence
|
||||
# over global option. Global options are set when adding a new artist
|
||||
try:
|
||||
db_artist = myDB.action('SELECT IncludeExtras, Extras from artists WHERE ArtistID=?', [artistid]).fetchone()
|
||||
includeExtras = db_artist['IncludeExtras']
|
||||
except IndexError:
|
||||
includeExtras = False
|
||||
|
||||
#Clean all references to release group in dB that are no longer referenced from the musicbrainz refresh
|
||||
# Clean all references to release group in dB that are no longer referenced
|
||||
# from the musicbrainz refresh
|
||||
group_list = []
|
||||
force_repackage = 0
|
||||
#Don't nuke the database if there's a MusicBrainz error
|
||||
|
||||
# Don't nuke the database if there's a MusicBrainz error
|
||||
if len(artist['releasegroups']) != 0:
|
||||
for groups in artist['releasegroups']:
|
||||
group_list.append(groups['id'])
|
||||
@@ -233,7 +232,6 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
rg_exists = myDB.action("SELECT * from albums WHERE AlbumID=?", [rg['id']]).fetchone()
|
||||
|
||||
if not forcefull:
|
||||
|
||||
new_release_group = False
|
||||
|
||||
try:
|
||||
@@ -244,12 +242,10 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
|
||||
|
||||
if new_release_group:
|
||||
|
||||
logger.info("[%s] Now adding: %s (New Release Group)" % (artist['artist_name'], rg['title']))
|
||||
new_releases = mb.get_new_releases(rgid,includeExtras)
|
||||
|
||||
else:
|
||||
|
||||
if check_release_date is None or check_release_date == u"None":
|
||||
logger.info("[%s] Now updating: %s (No Release Date)" % (artist['artist_name'], rg['title']))
|
||||
new_releases = mb.get_new_releases(rgid,includeExtras,True)
|
||||
@@ -263,33 +259,35 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
else:
|
||||
release_date = today
|
||||
if helpers.get_age(today) - helpers.get_age(release_date) < pause_delta:
|
||||
logger.info("[%s] Now updating: %s (Release Date <%s Days) " % (artist['artist_name'], rg['title'], pause_delta))
|
||||
logger.info("[%s] Now updating: %s (Release Date <%s Days)", artist['artist_name'], rg['title'], pause_delta)
|
||||
new_releases = mb.get_new_releases(rgid,includeExtras,True)
|
||||
else:
|
||||
logger.info("[%s] Skipping: %s (Release Date >%s Days)" % (artist['artist_name'], rg['title'], pause_delta))
|
||||
logger.info("[%s] Skipping: %s (Release Date >%s Days)", artist['artist_name'], rg['title'], pause_delta)
|
||||
skip_log = 1
|
||||
new_releases = 0
|
||||
|
||||
if force_repackage == 1:
|
||||
new_releases = -1
|
||||
logger.info('[%s] Forcing repackage of %s (Release Group Removed)' % (artist['artist_name'], al_title))
|
||||
logger.info('[%s] Forcing repackage of %s (Release Group Removed)', artist['artist_name'], al_title)
|
||||
else:
|
||||
new_releases = new_releases
|
||||
else:
|
||||
logger.info("[%s] Now adding/updating: %s (Comprehensive Force)" % (artist['artist_name'], rg['title']))
|
||||
logger.info("[%s] Now adding/updating: %s (Comprehensive Force)", artist['artist_name'], rg['title'])
|
||||
new_releases = mb.get_new_releases(rgid,includeExtras,forcefull)
|
||||
|
||||
if new_releases != 0:
|
||||
#Dump existing hybrid release since we're repackaging/replacing it
|
||||
# Dump existing hybrid release since we're repackaging/replacing it
|
||||
myDB.action("DELETE from albums WHERE ReleaseID=?", [rg['id']])
|
||||
myDB.action("DELETE from allalbums WHERE ReleaseID=?", [rg['id']])
|
||||
myDB.action("DELETE from tracks WHERE ReleaseID=?", [rg['id']])
|
||||
myDB.action("DELETE from alltracks WHERE ReleaseID=?", [rg['id']])
|
||||
myDB.action('DELETE from releases WHERE ReleaseGroupID=?', [rg['id']])
|
||||
|
||||
# This will be used later to build a hybrid release
|
||||
fullreleaselist = []
|
||||
#Search for releases within a release group
|
||||
# Search for releases within a release group
|
||||
find_hybrid_releases = myDB.action("SELECT * from allalbums WHERE AlbumID=?", [rg['id']])
|
||||
|
||||
# Build the dictionary for the fullreleaselist
|
||||
for items in find_hybrid_releases:
|
||||
if items['ReleaseID'] != rg['id']: #don't include hybrid information, since that's what we're replacing
|
||||
@@ -320,14 +318,12 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
newValueDict['Tracks'] = hybrid_track_array
|
||||
fullreleaselist.append(newValueDict)
|
||||
|
||||
#print fullreleaselist
|
||||
|
||||
# Basically just do the same thing again for the hybrid release
|
||||
# This may end up being called with an empty fullreleaselist
|
||||
try:
|
||||
hybridrelease = getHybridRelease(fullreleaselist)
|
||||
logger.info('[%s] Packaging %s releases into hybrid title' % (artist['artist_name'], rg['title']))
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
errors = True
|
||||
logger.warn('[%s] Unable to get hybrid release information for %s: %s' % (artist['artist_name'],rg['title'],e))
|
||||
continue
|
||||
@@ -387,7 +383,6 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
# If there's no release in the main albums tables, add the default (hybrid)
|
||||
# If there is a release, check the ReleaseID against the AlbumID to see if they differ (user updated)
|
||||
# check if the album already exists
|
||||
|
||||
if not rg_exists:
|
||||
releaseid = rg['id']
|
||||
else:
|
||||
@@ -445,7 +440,6 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
continue
|
||||
|
||||
for track in tracks:
|
||||
|
||||
controlValueDict = {"TrackID": track['TrackID'],
|
||||
"AlbumID": rg['id']}
|
||||
|
||||
@@ -569,7 +563,7 @@ def addReleaseById(rid, rgid=None):
|
||||
logger.debug("Didn't find releaseID " + rid + " in the cache. Looking up its ReleaseGroupID")
|
||||
try:
|
||||
release_dict = mb.getRelease(rid)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
logger.info('Unable to get release information for Release %s: %s', rid, e)
|
||||
if status == 'Loading':
|
||||
myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid])
|
||||
@@ -639,7 +633,6 @@ def addReleaseById(rid, rgid=None):
|
||||
myDB.action('INSERT INTO releases VALUES( ?, ?)', [rid, release_dict['rgid']])
|
||||
|
||||
for track in release_dict['tracks']:
|
||||
|
||||
cleanname = helpers.cleanName(release_dict['artist_name'] + ' ' + release_dict['rg_title'] + ' ' + track['title'])
|
||||
|
||||
controlValueDict = {"TrackID": track['id'],
|
||||
@@ -676,7 +669,7 @@ def addReleaseById(rid, rgid=None):
|
||||
newValueDict = {"Status": "Wanted"}
|
||||
myDB.upsert("albums", newValueDict, controlValueDict)
|
||||
|
||||
#start a search for the album
|
||||
# Start a search for the album
|
||||
import searcher
|
||||
searcher.searchforalbum(rgid, False)
|
||||
elif not rg_exists and not release_dict:
|
||||
@@ -718,39 +711,43 @@ def updateFormat():
|
||||
|
||||
def getHybridRelease(fullreleaselist):
|
||||
"""
|
||||
Returns a dictionary of best group of tracks from the list of releases & earliest release date
|
||||
Returns a dictionary of best group of tracks from the list of releases and
|
||||
earliest release date
|
||||
"""
|
||||
|
||||
if len(fullreleaselist) == 0:
|
||||
raise Exception("getHybridRelease was called with an empty fullreleaselist")
|
||||
raise ValueError("Empty fullreleaselist")
|
||||
|
||||
sortable_release_list = []
|
||||
|
||||
formats = {
|
||||
'2xVinyl': '2',
|
||||
'Vinyl': '2',
|
||||
'CD': '0',
|
||||
'Cassette': '3',
|
||||
'2xCD': '1',
|
||||
'Digital Media': '0'
|
||||
}
|
||||
|
||||
countries = {
|
||||
'US': '0',
|
||||
'GB': '1',
|
||||
'JP': '2',
|
||||
}
|
||||
|
||||
for release in fullreleaselist:
|
||||
|
||||
formats = {
|
||||
'2xVinyl': '2',
|
||||
'Vinyl': '2',
|
||||
'CD': '0',
|
||||
'Cassette': '3',
|
||||
'2xCD': '1',
|
||||
'Digital Media': '0'
|
||||
}
|
||||
|
||||
countries = {
|
||||
'US': '0',
|
||||
'GB': '1',
|
||||
'JP': '2',
|
||||
}
|
||||
|
||||
# Find values for format and country
|
||||
try:
|
||||
format = int(formats[release['Format']])
|
||||
except:
|
||||
except (ValueError, KeyError):
|
||||
format = 3
|
||||
|
||||
try:
|
||||
country = int(countries[release['Country']])
|
||||
except:
|
||||
except (ValueError, KeyError):
|
||||
country = 3
|
||||
|
||||
# Create record
|
||||
release_dict = {
|
||||
'hasasin': bool(release['AlbumASIN']),
|
||||
'asin': release['AlbumASIN'],
|
||||
@@ -760,15 +757,19 @@ def getHybridRelease(fullreleaselist):
|
||||
'format': format,
|
||||
'country': country,
|
||||
'tracks': release['Tracks']
|
||||
}
|
||||
}
|
||||
|
||||
sortable_release_list.append(release_dict)
|
||||
|
||||
#necessary to make dates that miss the month and/or day show up after full dates
|
||||
# Necessary to make dates that miss the month and/or day show up after full
|
||||
# dates
|
||||
def getSortableReleaseDate(releaseDate):
|
||||
# Change this value to change the sorting behaviour of none, returning
|
||||
# 'None' will put it at the top which was normal behaviour for pre-ngs
|
||||
# versions
|
||||
if releaseDate == None:
|
||||
return 'None';#change this value to change the sorting behaviour of none, returning 'None' will put it at the top
|
||||
#which was normal behaviour for pre-ngs versions
|
||||
return 'None';
|
||||
|
||||
if releaseDate.count('-') == 2:
|
||||
return releaseDate
|
||||
elif releaseDate.count('-') == 1:
|
||||
|
||||
@@ -30,8 +30,8 @@ def request_lastfm(method, **kwargs):
|
||||
Call a Last.FM API method. Automatically sets the method and API key. Method
|
||||
will return the result if no error occured.
|
||||
|
||||
By default, this method will request the JSON format, since it is lighter
|
||||
than XML.
|
||||
By default, this method will request the JSON format, since it is more
|
||||
lightweight than XML.
|
||||
"""
|
||||
|
||||
# Prepare request
|
||||
@@ -41,6 +41,8 @@ def request_lastfm(method, **kwargs):
|
||||
|
||||
# Send request
|
||||
logger.debug("Calling Last.FM method: %s", method)
|
||||
logger.debug("Last.FM call parameters: %s", kwargs)
|
||||
|
||||
data = request.request_json(ENTRY_POINT, timeout=TIMEOUT, params=kwargs)
|
||||
|
||||
# Parse response and check for errors.
|
||||
@@ -49,7 +51,7 @@ def request_lastfm(method, **kwargs):
|
||||
return
|
||||
|
||||
if "error" in data:
|
||||
logger.debug("Last.FM returned an error: %s", data["message"])
|
||||
logger.error("Last.FM returned an error: %s", data["message"])
|
||||
return
|
||||
|
||||
return data
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
|
||||
import os
|
||||
import glob
|
||||
|
||||
from beets.mediafile import MediaFile
|
||||
|
||||
import headphones
|
||||
|
||||
from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError
|
||||
|
||||
from headphones import db, logger, helpers, importer, lastfm
|
||||
|
||||
# You can scan a single directory and append it to the current library by specifying append=True, ArtistID & ArtistName
|
||||
@@ -84,8 +84,8 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
for directory in d[:]:
|
||||
if directory.startswith("."):
|
||||
d.remove(directory)
|
||||
for files in f:
|
||||
|
||||
for files in f:
|
||||
# MEDIA_FORMATS = music file extensions, e.g. mp3, flac, etc
|
||||
if any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
||||
|
||||
@@ -104,9 +104,8 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
# Try to read the metadata
|
||||
try:
|
||||
f = MediaFile(song)
|
||||
|
||||
except:
|
||||
logger.error('Cannot read file: ' + unicode_song_path)
|
||||
except (FileTypeError, UnreadableFileError):
|
||||
logger.error("Cannot read file media file '%s'. It may be corrupted or not a media file.", unicode_song_path)
|
||||
continue
|
||||
|
||||
# Grab the bitrates for the auto detect bit rate option
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from headphones import logger, helpers, common, request
|
||||
from headphones.exceptions import ex
|
||||
|
||||
from xml.dom import minidom
|
||||
from httplib import HTTPSConnection
|
||||
@@ -40,7 +39,10 @@ try:
|
||||
except ImportError:
|
||||
from cgi import parse_qsl
|
||||
|
||||
class GROWL:
|
||||
class GROWL(object):
|
||||
"""
|
||||
Growl notifications, for OS X
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.enabled = headphones.GROWL_ENABLED
|
||||
@@ -90,7 +92,9 @@ class GROWL:
|
||||
|
||||
# Send it, including an image
|
||||
image_file = os.path.join(str(headphones.PROG_DIR), 'data/images/headphoneslogo.png')
|
||||
image = open(image_file, 'rb').read()
|
||||
|
||||
with open(image_file, 'rb') as f:
|
||||
image = f.read()
|
||||
|
||||
try:
|
||||
growl.notify(
|
||||
@@ -116,10 +120,10 @@ class GROWL:
|
||||
|
||||
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
||||
|
||||
class PROWL:
|
||||
|
||||
keys = []
|
||||
priority = []
|
||||
class PROWL(object):
|
||||
"""
|
||||
Prowl notifications.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.enabled = headphones.PROWL_ENABLED
|
||||
@@ -163,25 +167,29 @@ class PROWL:
|
||||
return
|
||||
|
||||
def test(self, keys, priority):
|
||||
|
||||
self.enabled = True
|
||||
self.keys = keys
|
||||
self.priority = priority
|
||||
|
||||
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
||||
|
||||
class MPC:
|
||||
class MPC(object):
|
||||
"""
|
||||
MPC library update
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
pass
|
||||
|
||||
def notify( self ):
|
||||
|
||||
subprocess.call( ["mpc", "update"] )
|
||||
|
||||
|
||||
class XBMC:
|
||||
class XBMC(object):
|
||||
"""
|
||||
XBMC notifications
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -249,15 +257,15 @@ class XBMC:
|
||||
if not request:
|
||||
raise Exception
|
||||
|
||||
except:
|
||||
logger.warn('Error sending notification request to XBMC')
|
||||
except Exception:
|
||||
logger.error('Error sending notification request to XBMC')
|
||||
|
||||
class LMS:
|
||||
|
||||
#Class for updating a Logitech Media Server
|
||||
class LMS(object):
|
||||
"""
|
||||
Class for updating a Logitech Media Server
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.hosts = headphones.LMS_HOST
|
||||
|
||||
def _sendjson(self, host):
|
||||
@@ -293,8 +301,7 @@ class LMS:
|
||||
if not request:
|
||||
logger.warn('Error sending rescan request to LMS')
|
||||
|
||||
class Plex:
|
||||
|
||||
class Plex(object):
|
||||
def __init__(self):
|
||||
|
||||
self.server_hosts = headphones.PLEX_SERVER_HOST
|
||||
@@ -380,7 +387,7 @@ class Plex:
|
||||
except:
|
||||
logger.warn('Error sending notification request to Plex Media Server')
|
||||
|
||||
class NMA:
|
||||
class NMA(object):
|
||||
def notify(self, artist=None, album=None, snatched=None):
|
||||
title = 'Headphones'
|
||||
api = headphones.NMA_APIKEY
|
||||
@@ -416,7 +423,7 @@ class NMA:
|
||||
else:
|
||||
return True
|
||||
|
||||
class PUSHBULLET:
|
||||
class PUSHBULLET(object):
|
||||
|
||||
def __init__(self):
|
||||
self.apikey = headphones.PUSHBULLET_APIKEY
|
||||
@@ -469,7 +476,7 @@ class PUSHBULLET:
|
||||
|
||||
self.notify('Main Screen Activate', 'Test Message')
|
||||
|
||||
class PUSHALOT:
|
||||
class PUSHALOT(object):
|
||||
|
||||
def notify(self, message, event):
|
||||
if not headphones.PUSHALOT_ENABLED:
|
||||
@@ -508,7 +515,7 @@ class PUSHALOT:
|
||||
logger.info(u"Pushalot notification failed.")
|
||||
return False
|
||||
|
||||
class Synoindex:
|
||||
class Synoindex(object):
|
||||
def __init__(self, util_loc='/usr/syno/bin/synoindex'):
|
||||
self.util_loc = util_loc
|
||||
|
||||
@@ -544,19 +551,17 @@ class Synoindex:
|
||||
for path in path_list:
|
||||
self.notify(path)
|
||||
|
||||
class PUSHOVER:
|
||||
|
||||
application_token = "LdPCoy0dqC21ktsbEyAVCcwvQiVlsz"
|
||||
keys = []
|
||||
priority = []
|
||||
class PUSHOVER(object):
|
||||
|
||||
def __init__(self):
|
||||
self.enabled = headphones.PUSHOVER_ENABLED
|
||||
self.keys = headphones.PUSHOVER_KEYS
|
||||
self.priority = headphones.PUSHOVER_PRIORITY
|
||||
|
||||
if headphones.PUSHOVER_APITOKEN:
|
||||
self.application_token = headphones.PUSHOVER_APITOKEN
|
||||
pass
|
||||
else:
|
||||
self.application_token = "LdPCoy0dqC21ktsbEyAVCcwvQiVlsz"
|
||||
|
||||
def conf(self, options):
|
||||
return cherrypy.config['config'].get('Pushover', options)
|
||||
@@ -598,23 +603,23 @@ class PUSHOVER:
|
||||
return
|
||||
|
||||
def test(self, keys, priority):
|
||||
|
||||
self.enabled = True
|
||||
self.keys = keys
|
||||
self.priority = priority
|
||||
|
||||
self.notify('Main Screen Activate', 'Test Message')
|
||||
|
||||
class TwitterNotifier:
|
||||
|
||||
consumer_key = "oYKnp2ddX5gbARjqX8ZAAg"
|
||||
consumer_secret = "A4Xkw9i5SjHbTk7XT8zzOPqivhj9MmRDR9Qn95YA9sk"
|
||||
class TwitterNotifier(object):
|
||||
|
||||
REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
|
||||
ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token'
|
||||
AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize'
|
||||
SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
|
||||
|
||||
def __init__(self):
|
||||
self.consumer_key = "oYKnp2ddX5gbARjqX8ZAAg"
|
||||
self.consumer_secret = "A4Xkw9i5SjHbTk7XT8zzOPqivhj9MmRDR9Qn95YA9sk"
|
||||
|
||||
def notify_snatch(self, title):
|
||||
if headphones.TWITTER_ONSNATCH:
|
||||
self._notifyTwitter(common.notifyStrings[common.NOTIFY_SNATCH]+': '+title+' at '+helpers.now())
|
||||
@@ -708,11 +713,7 @@ class TwitterNotifier:
|
||||
|
||||
return self._send_tweet(prefix+": "+message)
|
||||
|
||||
notifier = TwitterNotifier
|
||||
|
||||
class OSX_NOTIFY:
|
||||
|
||||
objc = None
|
||||
class OSX_NOTIFY(object):
|
||||
|
||||
def __init__(self):
|
||||
try:
|
||||
@@ -767,14 +768,12 @@ class OSX_NOTIFY:
|
||||
def swizzled_bundleIdentifier(self, original, swizzled):
|
||||
return 'ade.headphones.osxnotify'
|
||||
|
||||
class BOXCAR:
|
||||
class BOXCAR(object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.url = 'https://new.boxcar.io/api/notifications'
|
||||
|
||||
def notify(self, title, message, rgid=None):
|
||||
|
||||
try:
|
||||
if rgid:
|
||||
message += '<br></br><a href="http://musicbrainz.org/release-group/%s">MusicBrainz</a>' % rgid
|
||||
@@ -791,6 +790,25 @@ class BOXCAR:
|
||||
handle.close()
|
||||
return True
|
||||
|
||||
except urllib2.URLError, e:
|
||||
except urllib2.URLError as e:
|
||||
logger.warn('Error sending Boxcar2 Notification: %s' % e)
|
||||
return False
|
||||
|
||||
class SubSonicNotifier(object):
|
||||
|
||||
def __init__(self):
|
||||
self.host = headphones.SUBSONIC_HOST
|
||||
self.username = headphones.SUBSONIC_USERNAME
|
||||
self.password = headphones.SUBSONIC_PASSWORD
|
||||
|
||||
def notify(self, albumpaths):
|
||||
# Correct URL
|
||||
if not self.host.lower().startswith("http"):
|
||||
self.host = "http://" + self.host
|
||||
|
||||
if not self.host.lower().endswith("/"):
|
||||
self.host = self.host + "/"
|
||||
|
||||
# Invoke request
|
||||
request.request_response(self.host + "musicFolderSettings.view?scanNow",
|
||||
auth=(self.username, self.password))
|
||||
@@ -519,6 +519,11 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
boxcar.notify('Headphones processed: ' + pushmessage,
|
||||
statusmessage, release['AlbumID'])
|
||||
|
||||
if headphones.SUBSONIC_ENABLED:
|
||||
logger.info(u"Sending Subsonic update")
|
||||
subsonic = notifiers.SubSonicNotifier()
|
||||
subsonic.notify(albumpaths)
|
||||
|
||||
if headphones.MPC_ENABLED:
|
||||
mpc = notifiers.MPC()
|
||||
mpc.notify()
|
||||
@@ -569,25 +574,27 @@ def addAlbumArt(artwork, albumpath, release):
|
||||
album_art_name = album_art_name.replace(".", "_", 1)
|
||||
|
||||
try:
|
||||
file = open(os.path.join(albumpath, album_art_name), 'wb')
|
||||
file.write(artwork)
|
||||
file.close()
|
||||
except Exception, e:
|
||||
logger.error('Error saving album art: %s' % str(e))
|
||||
with open(os.path.join(albumpath, album_art_name), 'wb') as f:
|
||||
f.write(artwork)
|
||||
except IOError as e:
|
||||
logger.error('Error saving album art: %s', e)
|
||||
return
|
||||
|
||||
def cleanupFiles(albumpath):
|
||||
logger.info('Cleaning up files')
|
||||
|
||||
for r,d,f in os.walk(albumpath):
|
||||
for files in f:
|
||||
if not any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
||||
logger.debug('Removing: %s' % files)
|
||||
try:
|
||||
os.remove(os.path.join(r, files))
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
logger.error(u'Could not remove file: %s. Error: %s' % (files.decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||
|
||||
def renameNFO(albumpath):
|
||||
logger.info('Renaming NFO')
|
||||
|
||||
for r,d,f in os.walk(albumpath):
|
||||
for file in f:
|
||||
if file.lower().endswith('.nfo'):
|
||||
@@ -595,11 +602,10 @@ def renameNFO(albumpath):
|
||||
try:
|
||||
new_file_name = os.path.join(r, file)[:-3] + 'orig.nfo'
|
||||
os.rename(os.path.join(r, file), new_file_name)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
logger.error(u'Could not rename file: %s. Error: %s' % (os.path.join(r, file).decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||
|
||||
def moveFiles(albumpath, release, tracks):
|
||||
|
||||
try:
|
||||
year = release['ReleaseDate'][:4]
|
||||
except TypeError:
|
||||
|
||||
@@ -54,7 +54,7 @@ def request_response(url, method="get", auto_raise=True,
|
||||
try:
|
||||
response.raise_for_status()
|
||||
except:
|
||||
logger.debug("Response status code %d is not white " +
|
||||
logger.debug("Response status code %d is not white " \
|
||||
"listed, raised exception", response.status_code)
|
||||
raise
|
||||
elif auto_raise:
|
||||
@@ -62,17 +62,17 @@ def request_response(url, method="get", auto_raise=True,
|
||||
|
||||
return response
|
||||
except requests.ConnectionError:
|
||||
logger.error("Unable to connect to remote host. Check if the remote " +
|
||||
logger.error("Unable to connect to remote host. Check if the remote " \
|
||||
"host is up and running.")
|
||||
except requests.Timeout:
|
||||
logger.error("Request timed out. The remote host did not respeond " +
|
||||
logger.error("Request timed out. The remote host did not respeond " \
|
||||
"timely.")
|
||||
except requests.HTTPError as e:
|
||||
if e.response is not None:
|
||||
if e.response.status_code >= 500:
|
||||
cause = "remote server error"
|
||||
elif e.response.status_code >= 400:
|
||||
cause = "local request error"
|
||||
cause = "local client error"
|
||||
else:
|
||||
# I don't think we will end up here, but for completeness
|
||||
cause = "unknown"
|
||||
@@ -80,25 +80,9 @@ def request_response(url, method="get", auto_raise=True,
|
||||
logger.error("Request raise HTTP error with status code %d (%s).",
|
||||
e.response.status_code, cause)
|
||||
|
||||
# Some servers return extra information in the result. Try to parse
|
||||
# it for debugging purpose. Messages are limited to 150 characters,
|
||||
# since it may return the whole page in case of normal web page URLs
|
||||
# Debug response
|
||||
if headphones.VERBOSE:
|
||||
if e.response.headers.get("content-type") == "text/html":
|
||||
soup = BeautifulSoup(e.response.content, "html5lib")
|
||||
|
||||
message = soup.find("body")
|
||||
message = message.text if message else soup.text
|
||||
message = message.strip()
|
||||
else:
|
||||
message = e.response.content.strip()
|
||||
|
||||
if message:
|
||||
# Truncate message if it is too long.
|
||||
if len(message) > 150:
|
||||
message = message[:150] + "..."
|
||||
|
||||
logger.debug("Server responded with message: %s", message)
|
||||
server_message(e.response)
|
||||
else:
|
||||
logger.error("Request raised HTTP error.")
|
||||
except requests.RequestException as e:
|
||||
@@ -144,12 +128,16 @@ def request_json(url, **kwargs):
|
||||
result = response.json()
|
||||
|
||||
if validator and not validator(result):
|
||||
logger.error("JSON validation result vailed")
|
||||
logger.error("JSON validation result failed")
|
||||
else:
|
||||
return result
|
||||
except ValueError:
|
||||
logger.error("Response returned invalid JSON data")
|
||||
|
||||
# Debug response
|
||||
if headphones.VERBOSE:
|
||||
server_message(response)
|
||||
|
||||
def request_content(url, **kwargs):
|
||||
"""
|
||||
Wrapper for `request_response', which will return the raw content.
|
||||
@@ -168,4 +156,37 @@ def request_feed(url, **kwargs):
|
||||
response = request_response(url, **kwargs)
|
||||
|
||||
if response is not None:
|
||||
return feedparser.parse(response.content)
|
||||
return feedparser.parse(response.content)
|
||||
|
||||
def server_message(response):
|
||||
"""
|
||||
Extract server message from response and log in to logger with DEBUG level.
|
||||
|
||||
Some servers return extra information in the result. Try to parse it for
|
||||
debugging purpose. Messages are limited to 150 characters, since it may
|
||||
return the whole page in case of normal web page URLs
|
||||
"""
|
||||
|
||||
message = None
|
||||
|
||||
# First attempt is to 'read' the response as HTML
|
||||
if response.headers.get("content-type") == "text/html":
|
||||
try:
|
||||
soup = BeautifulSoup(response.content, "html5lib")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
message = soup.find("body")
|
||||
message = message.text if message else soup.text
|
||||
message = message.strip()
|
||||
|
||||
# Second attempt is to just take the response
|
||||
if message is None:
|
||||
message = response.content.strip()
|
||||
|
||||
if message:
|
||||
# Truncate message if it is too long.
|
||||
if len(message) > 150:
|
||||
message = message[:150] + "..."
|
||||
|
||||
logger.debug("Server responded with message: %s", message)
|
||||
@@ -51,7 +51,7 @@ class Rutracker():
|
||||
|
||||
try:
|
||||
self.opener.open("http://login.rutracker.org/forum/login.php", params)
|
||||
except :
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Check if we're logged in
|
||||
@@ -286,17 +286,17 @@ class Rutracker():
|
||||
else:
|
||||
tempdir = mkdtemp(suffix='_rutracker_torrents')
|
||||
download_path = os.path.join(tempdir, torrent_name)
|
||||
fp = open (download_path, 'wb')
|
||||
fp.write (torrent)
|
||||
fp.close ()
|
||||
|
||||
with open(download_path, 'wb') as f:
|
||||
f.write(torrent)
|
||||
os.umask(prev)
|
||||
|
||||
# Add file to utorrent
|
||||
if headphones.TORRENT_DOWNLOADER == 2:
|
||||
self.utorrent_add_file(download_path)
|
||||
|
||||
except Exception, e:
|
||||
logger.error('Error getting torrent: %s' % e)
|
||||
except Exception as e:
|
||||
logger.error('Error getting torrent: %s', e)
|
||||
return False
|
||||
|
||||
return download_path, tor_hash
|
||||
@@ -322,9 +322,10 @@ class Rutracker():
|
||||
|
||||
try:
|
||||
r = session.get(url + 'token.html')
|
||||
except:
|
||||
logger.debug('Error getting token')
|
||||
except Exception:
|
||||
logger.exception('Error getting token')
|
||||
return
|
||||
|
||||
if r.status_code == '401':
|
||||
logger.debug('Error reaching utorrent')
|
||||
return
|
||||
@@ -336,15 +337,11 @@ class Rutracker():
|
||||
|
||||
session.params = {'token': regex.group(1)}
|
||||
|
||||
params = {'action': 'add-file'}
|
||||
f = open(filename, 'rb')
|
||||
files = {'torrent_file': f}
|
||||
|
||||
try:
|
||||
session.post(url, params=params, files=files)
|
||||
except:
|
||||
logger.debug('Error adding file to utorrent')
|
||||
return
|
||||
finally:
|
||||
f.close()
|
||||
with open(filename, 'rb') as f:
|
||||
try:
|
||||
session.post(url, params={'action': 'add-file'},
|
||||
files={'torrent_file': f})
|
||||
except Exception:
|
||||
logger.exception('Error adding file to utorrent')
|
||||
return
|
||||
|
||||
|
||||
@@ -31,9 +31,8 @@ def addTorrent(link):
|
||||
method = 'torrent-add'
|
||||
|
||||
if link.endswith('.torrent'):
|
||||
f = open(link,'rb')
|
||||
metainfo = str(base64.b64encode(f.read()))
|
||||
f.close()
|
||||
with open(link, 'rb') as f:
|
||||
metainfo = str(base64.b64encode(f.read()))
|
||||
arguments = {'metainfo': metainfo, 'download-dir':headphones.DOWNLOAD_TORRENT_DIR}
|
||||
else:
|
||||
arguments = {'filename': link, 'download-dir': headphones.DOWNLOAD_TORRENT_DIR}
|
||||
|
||||
@@ -21,7 +21,6 @@ import headphones
|
||||
import subprocess
|
||||
|
||||
from headphones import logger, version, request
|
||||
from headphones.exceptions import ex
|
||||
|
||||
def runGit(args):
|
||||
|
||||
@@ -63,7 +62,6 @@ def runGit(args):
|
||||
def getVersion():
|
||||
|
||||
if version.HEADPHONES_VERSION.startswith('win32build'):
|
||||
|
||||
headphones.INSTALL_TYPE = 'win'
|
||||
|
||||
# Don't have a way to update exe yet, but don't want to set VERSION to None
|
||||
@@ -109,9 +107,8 @@ def getVersion():
|
||||
if not os.path.isfile(version_file):
|
||||
return None, 'master'
|
||||
|
||||
fp = open(version_file, 'r')
|
||||
current_version = fp.read().strip(' \n\r')
|
||||
fp.close()
|
||||
with open(version_file, 'r') as f:
|
||||
current_version = f.read().strip(' \n\r')
|
||||
|
||||
if current_version:
|
||||
return current_version, headphones.GIT_BRANCH
|
||||
@@ -199,9 +196,8 @@ def update():
|
||||
tar_download_path = os.path.join(headphones.PROG_DIR, download_name)
|
||||
|
||||
# Save tar to disk
|
||||
f = open(tar_download_path, 'wb')
|
||||
f.write(data)
|
||||
f.close()
|
||||
with open(tar_download_path, 'wb') as f:
|
||||
f.write(data)
|
||||
|
||||
# Extract the tar to update folder
|
||||
logger.info('Extracting file: ' + tar_download_path)
|
||||
@@ -233,9 +229,9 @@ def update():
|
||||
|
||||
# Update version.txt
|
||||
try:
|
||||
ver_file = open(version_path, 'w')
|
||||
ver_file.write(str(headphones.LATEST_VERSION))
|
||||
ver_file.close()
|
||||
except IOError, e:
|
||||
logger.error("Unable to write current version to version.txt, update not complete: "+ex(e))
|
||||
with open(version_path, 'w') as f:
|
||||
f.write(str(headphones.LATEST_VERSION))
|
||||
except IOError as e:
|
||||
logger.error("Unable to write current version to version.txt, " \
|
||||
"update not complete: ", e)
|
||||
return
|
||||
|
||||
@@ -1124,6 +1124,10 @@ class WebInterface(object):
|
||||
"pushbullet_onsnatch": checked(headphones.PUSHBULLET_ONSNATCH),
|
||||
"pushbullet_apikey": headphones.PUSHBULLET_APIKEY,
|
||||
"pushbullet_deviceid": headphones.PUSHBULLET_DEVICEID,
|
||||
"subsonic_enabled": checked(headphones.SUBSONIC_ENABLED),
|
||||
"subsonic_host": headphones.SUBSONIC_HOST,
|
||||
"subsonic_username": headphones.SUBSONIC_USERNAME,
|
||||
"subsonic_password": headphones.SUBSONIC_PASSWORD,
|
||||
"twitter_enabled": checked(headphones.TWITTER_ENABLED),
|
||||
"twitter_onsnatch": checked(headphones.TWITTER_ONSNATCH),
|
||||
"osx_notify_enabled": checked(headphones.OSX_NOTIFY_ENABLED),
|
||||
@@ -1181,7 +1185,7 @@ class WebInterface(object):
|
||||
rutracker=0, rutracker_user=None, rutracker_password=None, rutracker_ratio=None, rename_files=0, correct_metadata=0, cleanup_files=0, keep_nfo=0, add_album_art=0, album_art_format=None, embed_album_art=0, embed_lyrics=0, replace_existing_folders=False,
|
||||
destination_dir=None, lossless_destination_dir=None, folder_format=None, file_format=None, file_underscores=0, include_extras=0, single=0, ep=0, compilation=0, soundtrack=0, live=0, remix=0, spokenword=0, audiobook=0, other=0, djmix=0, mixtape_street=0, broadcast=0, interview=0, demo=0,
|
||||
autowant_upcoming=False, autowant_all=False, keep_torrent_files=False, prefer_torrents=0, open_magnet_links=0, interface=None, log_dir=None, cache_dir=None, music_encoder=0, encoder=None, xldprofile=None,
|
||||
bitrate=None, samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0,
|
||||
bitrate=None, samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0, subsonic_enabled=False, subsonic_host=None, subsonic_username=None, subsonic_password=None,
|
||||
delete_lossless_files=0, growl_enabled=0, growl_onsnatch=0, growl_host=None, growl_password=None, prowl_enabled=0, prowl_onsnatch=0, prowl_keys=None, prowl_priority=0, xbmc_enabled=0, xbmc_host=None, xbmc_username=None, xbmc_password=None,
|
||||
xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, pushalot_enabled=False, pushalot_apikey=None, pushalot_onsnatch=0, synoindex_enabled=False, lms_enabled=0, lms_host=None,
|
||||
pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, pushover_apitoken=None, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0,
|
||||
@@ -1350,6 +1354,10 @@ class WebInterface(object):
|
||||
headphones.PUSHBULLET_ONSNATCH = pushbullet_onsnatch
|
||||
headphones.PUSHBULLET_APIKEY = pushbullet_apikey
|
||||
headphones.PUSHBULLET_DEVICEID = pushbullet_deviceid
|
||||
headphones.SUBSONIC_ENABLED = subsonic_enabled
|
||||
headphones.SUBSONIC_HOST = subsonic_host
|
||||
headphones.SUBSONIC_USERNAME = subsonic_username
|
||||
headphones.SUBSONIC_PASSWORD = subsonic_password
|
||||
headphones.SONGKICK_ENABLED = songkick_enabled
|
||||
headphones.SONGKICK_APIKEY = songkick_apikey
|
||||
headphones.SONGKICK_LOCATION = songkick_location
|
||||
|
||||
@@ -114,7 +114,7 @@ def initialize(options=None):
|
||||
|
||||
# Prevent time-outs
|
||||
cherrypy.engine.timeout_monitor.unsubscribe()
|
||||
cherrypy.tree.mount(WebInterface(), options['http_root'], config = conf)
|
||||
cherrypy.tree.mount(WebInterface(), options['http_root'], config=conf)
|
||||
|
||||
try:
|
||||
cherrypy.process.servers.check_port(options['http_host'], options['http_port'])
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
from lib.pyItunes.Song import Song
|
||||
import time
|
||||
class Library:
|
||||
def __init__(self,dictionary):
|
||||
self.songs = self.parseDictionary(dictionary)
|
||||
|
||||
def parseDictionary(self,dictionary):
|
||||
songs = []
|
||||
format = "%Y-%m-%dT%H:%M:%SZ"
|
||||
for song,attributes in dictionary.iteritems():
|
||||
s = Song()
|
||||
s.name = attributes.get('Name')
|
||||
s.artist = attributes.get('Artist')
|
||||
s.album_artist = attributes.get('Album Aritst')
|
||||
s.composer = attributes.get('Composer')
|
||||
s.album = attributes.get('Album')
|
||||
s.genre = attributes.get('Genre')
|
||||
s.kind = attributes.get('Kind')
|
||||
if attributes.get('Size'):
|
||||
s.size = int(attributes.get('Size'))
|
||||
s.total_time = attributes.get('Total Time')
|
||||
s.track_number = attributes.get('Track Number')
|
||||
if attributes.get('Year'):
|
||||
s.year = int(attributes.get('Year'))
|
||||
if attributes.get('Date Modified'):
|
||||
s.date_modified = time.strptime(attributes.get('Date Modified'),format)
|
||||
if attributes.get('Date Added'):
|
||||
s.date_added = time.strptime(attributes.get('Date Added'),format)
|
||||
if attributes.get('Bit Rate'):
|
||||
s.bit_rate = int(attributes.get('Bit Rate'))
|
||||
if attributes.get('Sample Rate'):
|
||||
s.sample_rate = int(attributes.get('Sample Rate'))
|
||||
s.comments = attributes.get("Comments ")
|
||||
if attributes.get('Rating'):
|
||||
s.rating = int(attributes.get('Rating'))
|
||||
if attributes.get('Play Count'):
|
||||
s.play_count = int(attributes.get('Play Count'))
|
||||
if attributes.get('Location'):
|
||||
s.location = attributes.get('Location')
|
||||
songs.append(s)
|
||||
return songs
|
||||
@@ -1,46 +0,0 @@
|
||||
class Song:
|
||||
"""
|
||||
Song Attributes:
|
||||
name (String)
|
||||
artist (String)
|
||||
album_arist (String)
|
||||
composer = None (String)
|
||||
album = None (String)
|
||||
genre = None (String)
|
||||
kind = None (String)
|
||||
size = None (Integer)
|
||||
total_time = None (Integer)
|
||||
track_number = None (Integer)
|
||||
year = None (Integer)
|
||||
date_modified = None (Time)
|
||||
date_added = None (Time)
|
||||
bit_rate = None (Integer)
|
||||
sample_rate = None (Integer)
|
||||
comments = None (String)
|
||||
rating = None (Integer)
|
||||
album_rating = None (Integer)
|
||||
play_count = None (Integer)
|
||||
location = None (String)
|
||||
"""
|
||||
name = None
|
||||
artist = None
|
||||
album_arist = None
|
||||
composer = None
|
||||
album = None
|
||||
genre = None
|
||||
kind = None
|
||||
size = None
|
||||
total_time = None
|
||||
track_number = None
|
||||
year = None
|
||||
date_modified = None
|
||||
date_added = None
|
||||
bit_rate = None
|
||||
sample_rate = None
|
||||
comments = None
|
||||
rating = None
|
||||
album_rating = None
|
||||
play_count = None
|
||||
location = None
|
||||
|
||||
#title = property(getTitle,setTitle)
|
||||
@@ -1,42 +0,0 @@
|
||||
import re
|
||||
class XMLLibraryParser:
|
||||
def __init__(self,xmlLibrary):
|
||||
f = open(xmlLibrary)
|
||||
s = f.read()
|
||||
lines = s.split("\n")
|
||||
self.dictionary = self.parser(lines)
|
||||
|
||||
def getValue(self,restOfLine):
|
||||
value = re.sub("<.*?>","",restOfLine)
|
||||
u = unicode(value,"utf-8")
|
||||
cleanValue = u.encode("ascii","xmlcharrefreplace")
|
||||
return cleanValue
|
||||
|
||||
def keyAndRestOfLine(self,line):
|
||||
rawkey = re.search('<key>(.*?)</key>',line).group(0)
|
||||
key = re.sub("</*key>","",rawkey)
|
||||
restOfLine = re.sub("<key>.*?</key>","",line).strip()
|
||||
return key,restOfLine
|
||||
|
||||
def parser(self,lines):
|
||||
dicts = 0
|
||||
songs = {}
|
||||
inSong = False
|
||||
for line in lines:
|
||||
if re.search('<dict>',line):
|
||||
dicts += 1
|
||||
if re.search('</dict>',line):
|
||||
dicts -= 1
|
||||
inSong = False
|
||||
songs[songkey] = temp
|
||||
if dicts == 2 and re.search('<key>(.*?)</key>',line):
|
||||
rawkey = re.search('<key>(.*?)</key>',line).group(0)
|
||||
songkey = re.sub("</*key>","",rawkey)
|
||||
inSong = True
|
||||
temp = {}
|
||||
if dicts == 3 and re.search('<key>(.*?)</key>',line):
|
||||
key,restOfLine = self.keyAndRestOfLine(line)
|
||||
temp[key] = self.getValue(restOfLine)
|
||||
if len(songs) > 0 and dicts < 2:
|
||||
return songs
|
||||
return songs
|
||||
@@ -1,3 +0,0 @@
|
||||
from lib.pyItunes.XMLLibraryParser import XMLLibraryParser
|
||||
from lib.pyItunes.Library import Library
|
||||
from lib.pyItunes.Song import Song
|
||||
Reference in New Issue
Block a user