mirror of
https://github.com/rembo10/headphones.git
synced 2026-05-24 12:27:45 +01:00
Merge branch 'feature/autopep8' into feature/travis-ci
* feature/autopep8: Fix more config saving options autopep8 E272 whitespace before multiple spaces before keyword manual pep8 W291,W293,W391 blank lines, trailing whitespace autopep8 E221,E222,E225,E227,E228,E251 spaces around operators and in parameters autopep8 E301,E302,E303 too many / too few blank lines autopep8 E203 whitespace before colon autopep8 E231 whitespace after some punctuation autopep8 api.py autopep8 albumswitcher autopep8 headphones/albumart.py autopep8 headphones/__init__.py autopep8 Headphones.py
This commit is contained in:
+36
-18
@@ -14,7 +14,8 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os, sys
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Ensure lib added to path, before any other imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib/'))
|
||||
@@ -31,6 +32,7 @@ import headphones
|
||||
signal.signal(signal.SIGINT, headphones.sig_handler)
|
||||
signal.signal(signal.SIGTERM, headphones.sig_handler)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Headphones application entry point. Parses arguments, setups encoding and
|
||||
@@ -61,16 +63,24 @@ def main():
|
||||
headphones.SYS_ENCODING = 'UTF-8'
|
||||
|
||||
# Set up and gather command line arguments
|
||||
parser = argparse.ArgumentParser(description='Music add-on for SABnzbd+, Transmission and more.')
|
||||
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')
|
||||
parser.add_argument('-d', '--daemon', action='store_true', help='Run as a daemon')
|
||||
parser.add_argument('-p', '--port', type=int, help='Force Headphones to run on a specified port')
|
||||
parser.add_argument('--datadir', help='Specify a directory where to store your data files')
|
||||
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')
|
||||
parser.add_argument(
|
||||
'-d', '--daemon', action='store_true', help='Run as a daemon')
|
||||
parser.add_argument(
|
||||
'-p', '--port', type=int, help='Force Headphones to run on a specified port')
|
||||
parser.add_argument(
|
||||
'--datadir', help='Specify a directory where to store your data files')
|
||||
parser.add_argument('--config', help='Specify a config file to use')
|
||||
parser.add_argument('--nolaunch', action='store_true', help='Prevent browser from launching on startup')
|
||||
parser.add_argument('--pidfile', help='Create a pid file (only relevant when running as a daemon)')
|
||||
parser.add_argument('--nolaunch', action='store_true',
|
||||
help='Prevent browser from launching on startup')
|
||||
parser.add_argument(
|
||||
'--pidfile', help='Create a pid file (only relevant when running as a daemon)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -81,7 +91,8 @@ def main():
|
||||
|
||||
if args.daemon:
|
||||
if sys.platform == 'win32':
|
||||
sys.stderr.write("Daemonizing not supported under Windows, starting normally\n")
|
||||
sys.stderr.write(
|
||||
"Daemonizing not supported under Windows, starting normally\n")
|
||||
else:
|
||||
headphones.DAEMON = True
|
||||
headphones.QUIET = True
|
||||
@@ -89,11 +100,14 @@ def main():
|
||||
if args.pidfile:
|
||||
headphones.PIDFILE = str(args.pidfile)
|
||||
|
||||
# If the pidfile already exists, headphones may still be running, so exit
|
||||
# If the pidfile already exists, headphones may still be running, so
|
||||
# exit
|
||||
if os.path.exists(headphones.PIDFILE):
|
||||
sys.exit("PID file '" + headphones.PIDFILE + "' already exists. Exiting.")
|
||||
sys.exit(
|
||||
"PID file '" + headphones.PIDFILE + "' already exists. Exiting.")
|
||||
|
||||
# The pidfile is only useful in daemon mode, make sure we can write the file properly
|
||||
# The pidfile is only useful in daemon mode, make sure we can write the
|
||||
# file properly
|
||||
if headphones.DAEMON:
|
||||
headphones.CREATEPID = True
|
||||
|
||||
@@ -101,9 +115,11 @@ def main():
|
||||
with open(headphones.PIDFILE, 'w') as fp:
|
||||
fp.write("pid\n")
|
||||
except IOError as e:
|
||||
raise SystemExit("Unable to write PID file: %s [%d]", e.strerror, e.errno)
|
||||
raise SystemExit(
|
||||
"Unable to write PID file: %s [%d]", e.strerror, e.errno)
|
||||
else:
|
||||
logger.warn("Not running in daemon mode. PID file creation disabled.")
|
||||
logger.warn(
|
||||
"Not running in daemon mode. PID file creation disabled.")
|
||||
|
||||
# Determine which data directory and config file to use
|
||||
if args.datadir:
|
||||
@@ -121,11 +137,13 @@ def main():
|
||||
try:
|
||||
os.makedirs(headphones.DATA_DIR)
|
||||
except OSError:
|
||||
raise SystemExit('Could not create data directory: ' + headphones.DATA_DIR + '. Exiting....')
|
||||
raise SystemExit(
|
||||
'Could not create data directory: ' + headphones.DATA_DIR + '. Exiting....')
|
||||
|
||||
# Make sure the DATA_DIR is writeable
|
||||
if not os.access(headphones.DATA_DIR, os.W_OK):
|
||||
raise SystemExit('Cannot write to the data directory: ' + headphones.DATA_DIR + '. Exiting...')
|
||||
raise SystemExit(
|
||||
'Cannot write to the data directory: ' + headphones.DATA_DIR + '. Exiting...')
|
||||
|
||||
# Put the database in the DATA_DIR
|
||||
headphones.DB_FILE = os.path.join(headphones.DATA_DIR, 'headphones.db')
|
||||
@@ -162,7 +180,7 @@ def main():
|
||||
|
||||
if headphones.CONFIG.LAUNCH_BROWSER and not args.nolaunch:
|
||||
headphones.launch_browser(headphones.CONFIG.HTTP_HOST, http_port,
|
||||
headphones.CONFIG.HTTP_ROOT)
|
||||
headphones.CONFIG.HTTP_ROOT)
|
||||
|
||||
# Start the background threads
|
||||
headphones.start()
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
<fieldset>
|
||||
<legend>API</legend>
|
||||
<div class="row checkbox">
|
||||
<input type="checkbox" id="useapi" name="api_enabled" id="api_enabled" value="1" ${config['api_enabled']} />
|
||||
<input type="checkbox" id="api_enabled" name="api_enabled" value="1" ${config['api_enabled']} />
|
||||
<label title="Allow remote applications to interface with Headphones">
|
||||
Enable API
|
||||
</label>
|
||||
@@ -414,7 +414,8 @@
|
||||
<fieldset>
|
||||
<legend>Headphones Indexer</legend>
|
||||
<div class="row checkbox">
|
||||
<input id="headphones_indexer" type="checkbox" name="headphones_indexer" onclick="initConfigCheckbox($(this));" value="1" ${config['headphones_indexer']} /><label>Use Headphones Indexer</label>
|
||||
<input id="headphones_indexer" type="checkbox" name="headphones_indexer" onclick="initConfigCheckbox($(this));" value="1" ${config['headphones_indexer']} />
|
||||
<label>Use Headphones Indexer</label>
|
||||
</div>
|
||||
<div class="config">
|
||||
<div class="row">
|
||||
@@ -434,7 +435,7 @@
|
||||
<fieldset>
|
||||
<legend>Custom Newznab Providers</legend>
|
||||
<div class="row checkbox">
|
||||
<input id="usenewznab" type="checkbox" name="newznab" onclick="initConfigCheckbox($(this));" value="1" ${config['use_newznab']} /><label>Use Newznab</label>
|
||||
<input id="use_newznab" type="checkbox" name="use_newznab" onclick="initConfigCheckbox($(this));" value="1" ${config['use_newznab']} /><label>Use Newznab</label>
|
||||
</div>
|
||||
<div id="newznab_providers">
|
||||
<div class="config" id="newznab1">
|
||||
@@ -488,7 +489,7 @@
|
||||
<fieldset>
|
||||
<legend>NZBs.org</legend>
|
||||
<div class="row checkbox">
|
||||
<input id="usenzbsorg" type="checkbox" name="nzbsorg" onclick="initConfigCheckbox($(this));" value="1" ${config['use_nzbsorg']} /><label>Use NZBs.org</label>
|
||||
<input id="use_nzbsorg" type="checkbox" name="use_nzbsorg" onclick="initConfigCheckbox($(this));" value="1" ${config['use_nzbsorg']} /><label>Use NZBs.org</label>
|
||||
</div>
|
||||
<div class="config">
|
||||
<div class="row">
|
||||
@@ -500,7 +501,7 @@
|
||||
<fieldset>
|
||||
<legend>omgwtfnzbs</legend>
|
||||
<div class="row checkbox">
|
||||
<input id="useomgwtfnzbs" type="checkbox" name="omgwtfnzbs" onclick="initConfigCheckbox($(this));" value="1" ${config['use_omgwtfnzbs']} /><label>Use omgwtfnzbs</label>
|
||||
<input id="use_omgwtfnzbs" type="checkbox" name="use_omgwtfnzbs" onclick="initConfigCheckbox($(this));" value="1" ${config['use_omgwtfnzbs']} /><label>Use omgwtfnzbs</label>
|
||||
</div>
|
||||
<div class="config">
|
||||
<div class="row">
|
||||
@@ -521,7 +522,7 @@
|
||||
<fieldset>
|
||||
<legend>The Pirate Bay</legend>
|
||||
<div class="row checkbox">
|
||||
<input id="usepiratebay" type="checkbox" name="use_piratebay" value="1" ${config['use_piratebay']} /><label>Use The Pirate Bay</label>
|
||||
<input id="use_piratebay" type="checkbox" name="use_piratebay" value="1" ${config['use_piratebay']} /><label>Use The Pirate Bay</label>
|
||||
</div>
|
||||
<div class="config">
|
||||
<div class="row">
|
||||
@@ -538,7 +539,7 @@
|
||||
<fieldset>
|
||||
<legend>Kick Ass Torrents</legend>
|
||||
<div class="row checkbox">
|
||||
<input id="usekat" type="checkbox" name="use_kat" value="1" ${config['use_kat']} /><label>Use Kick Ass Torrents</label>
|
||||
<input id="use_kat" type="checkbox" name="use_kat" value="1" ${config['use_kat']} /><label>Use Kick Ass Torrents</label>
|
||||
</div>
|
||||
<div class="config">
|
||||
<div class="row">
|
||||
@@ -555,7 +556,7 @@
|
||||
<fieldset>
|
||||
<legend>Waffles.fm</legend>
|
||||
<div class="row checkbox">
|
||||
<input id="usewaffles" type="checkbox" name="waffles" onclick="initConfigCheckbox($(this));" value="1" ${config['use_waffles']} /><label>Use Waffles.fm</label>
|
||||
<input id="use_waffles" type="checkbox" name="use_waffles" onclick="initConfigCheckbox($(this));" value="1" ${config['use_waffles']} /><label>Use Waffles.fm</label>
|
||||
</div>
|
||||
<div class="config">
|
||||
<div class="row">
|
||||
@@ -576,7 +577,7 @@
|
||||
<fieldset>
|
||||
<legend>rutracker.org</legend>
|
||||
<div class="row checkbox">
|
||||
<input id="userutracker" type="checkbox" name="rutracker" onclick="initConfigCheckbox($(this));" value="1" ${config['use_rutracker']} /><label>Use rutracker.org</label>
|
||||
<input id="use_rutracker" type="checkbox" name="use_rutracker" onclick="initConfigCheckbox($(this));" value="1" ${config['use_rutracker']} /><label>Use rutracker.org</label>
|
||||
</div>
|
||||
<div class="config">
|
||||
<div class="row">
|
||||
@@ -597,7 +598,7 @@
|
||||
<fieldset>
|
||||
<legend>What.cd</legend>
|
||||
<div class="row checkbox">
|
||||
<input id="usewhatcd" type="checkbox" name="whatcd" onclick="initConfigCheckbox($(this));" value="1" ${config['use_whatcd']} /><label>Use What.cd</label>
|
||||
<input id="use_whatcd" type="checkbox" name="use_whatcd" onclick="initConfigCheckbox($(this));" value="1" ${config['use_whatcd']} /><label>Use What.cd</label>
|
||||
</div>
|
||||
<div class="config">
|
||||
<div class="row">
|
||||
@@ -618,7 +619,7 @@
|
||||
<fieldset>
|
||||
<legend>Mininova</legend>
|
||||
<div class="row checkbox">
|
||||
<input id="usemininova" type="checkbox" name="use_mininova" value="1" ${config['use_mininova']} /><label>Use Mininova</label>
|
||||
<input id="use_mininova" type="checkbox" name="use_mininova" value="1" ${config['use_mininova']} /><label>Use Mininova</label>
|
||||
</div>
|
||||
<div class="config">
|
||||
<div class="row">
|
||||
@@ -2031,16 +2032,16 @@
|
||||
});
|
||||
initActions();
|
||||
initConfigCheckbox("#headphones_indexer");
|
||||
initConfigCheckbox("#usenewznab");
|
||||
initConfigCheckbox("#usenzbsorg");
|
||||
initConfigCheckbox("#useomgwtfnzbs");
|
||||
initConfigCheckbox("#usekat");
|
||||
initConfigCheckbox("#usepiratebay");
|
||||
initConfigCheckbox("#usemininova");
|
||||
initConfigCheckbox("#usewaffles");
|
||||
initConfigCheckbox("#userutracker");
|
||||
initConfigCheckbox("#usewhatcd");
|
||||
initConfigCheckbox("#useapi");
|
||||
initConfigCheckbox("#use_newznab");
|
||||
initConfigCheckbox("#use_nzbsorg");
|
||||
initConfigCheckbox("#use_omgwtfnzbs");
|
||||
initConfigCheckbox("#use_kat");
|
||||
initConfigCheckbox("#use_piratebay");
|
||||
initConfigCheckbox("#use_mininova");
|
||||
initConfigCheckbox("#use_waffles");
|
||||
initConfigCheckbox("#use_rutracker");
|
||||
initConfigCheckbox("#use_whatcd");
|
||||
initConfigCheckbox("#api_enabled");
|
||||
initConfigCheckbox("#enable_https");
|
||||
|
||||
|
||||
|
||||
+106
-52
@@ -13,7 +13,8 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# NZBGet support added by CurlyMo <curlymoo1@gmail.com> as a part of XBian - XBMC on the Raspberry Pi
|
||||
# NZBGet support added by CurlyMo <curlymoo1@gmail.com> as a part of
|
||||
# XBian - XBMC on the Raspberry Pi
|
||||
|
||||
import os
|
||||
import sys
|
||||
@@ -62,7 +63,7 @@ QUIET = False
|
||||
VERBOSE = False
|
||||
DAEMON = False
|
||||
CREATEPID = False
|
||||
PIDFILE= None
|
||||
PIDFILE = None
|
||||
|
||||
SCHED = BackgroundScheduler()
|
||||
|
||||
@@ -87,10 +88,11 @@ LOSSY_MEDIA_FORMATS = ["mp3", "aac", "ogg", "ape", "m4a", "asf", "wma"]
|
||||
LOSSLESS_MEDIA_FORMATS = ["flac"]
|
||||
MEDIA_FORMATS = LOSSY_MEDIA_FORMATS + LOSSLESS_MEDIA_FORMATS
|
||||
|
||||
MIRRORLIST = ["musicbrainz.org","headphones","custom"]
|
||||
MIRRORLIST = ["musicbrainz.org", "headphones", "custom"]
|
||||
|
||||
UMASK = None
|
||||
|
||||
|
||||
def initialize(config_file):
|
||||
|
||||
with INIT_LOCK:
|
||||
@@ -109,7 +111,8 @@ def initialize(config_file):
|
||||
return False
|
||||
|
||||
if CONFIG.HTTP_PORT < 21 or CONFIG.HTTP_PORT > 65535:
|
||||
headphones.logger.warn('HTTP_PORT out of bounds: 21 < %s < 65535', CONFIG.HTTP_PORT)
|
||||
headphones.logger.warn(
|
||||
'HTTP_PORT out of bounds: 21 < %s < 65535', CONFIG.HTTP_PORT)
|
||||
CONFIG.HTTP_PORT = 8181
|
||||
|
||||
if CONFIG.HTTPS_CERT == '':
|
||||
@@ -125,7 +128,8 @@ def initialize(config_file):
|
||||
os.makedirs(CONFIG.LOG_DIR)
|
||||
except OSError:
|
||||
if VERBOSE:
|
||||
sys.stderr.write('Unable to create the log directory. Logging to screen only.\n')
|
||||
sys.stderr.write(
|
||||
'Unable to create the log directory. Logging to screen only.\n')
|
||||
|
||||
# Start the logger, disable console if needed
|
||||
logger.initLogger(console=not QUIET, verbose=VERBOSE)
|
||||
@@ -137,7 +141,8 @@ def initialize(config_file):
|
||||
try:
|
||||
os.makedirs(CONFIG.CACHE_DIR)
|
||||
except OSError:
|
||||
logger.error('Could not create cache dir. Check permissions of datadir: %s', DATA_DIR)
|
||||
logger.error(
|
||||
'Could not create cache dir. Check permissions of datadir: %s', DATA_DIR)
|
||||
|
||||
# Sanity check for search interval. Set it to at least 6 hours
|
||||
if CONFIG.SEARCH_INTERVAL < 360:
|
||||
@@ -172,6 +177,7 @@ def initialize(config_file):
|
||||
_INITIALIZED = True
|
||||
return True
|
||||
|
||||
|
||||
def daemonize():
|
||||
if threading.activeCount() != 1:
|
||||
logger.warn(
|
||||
@@ -223,6 +229,7 @@ def daemonize():
|
||||
with file(PIDFILE, 'w') as fp:
|
||||
fp.write("%s\n" % pid)
|
||||
|
||||
|
||||
def launch_browser(host, port, root):
|
||||
|
||||
if host == '0.0.0.0':
|
||||
@@ -247,65 +254,102 @@ def start():
|
||||
|
||||
# Start our scheduled background tasks
|
||||
from headphones import updater, searcher, librarysync, postprocessor, torrentfinished
|
||||
SCHED.add_job(updater.dbUpdate, trigger=IntervalTrigger(hours=CONFIG.UPDATE_DB_INTERVAL))
|
||||
SCHED.add_job(searcher.searchforalbum, trigger=IntervalTrigger(minutes=CONFIG.SEARCH_INTERVAL))
|
||||
SCHED.add_job(librarysync.libraryScan, trigger=IntervalTrigger(hours=CONFIG.LIBRARYSCAN_INTERVAL))
|
||||
SCHED.add_job(updater.dbUpdate, trigger=IntervalTrigger(
|
||||
hours=CONFIG.UPDATE_DB_INTERVAL))
|
||||
SCHED.add_job(searcher.searchforalbum, trigger=IntervalTrigger(
|
||||
minutes=CONFIG.SEARCH_INTERVAL))
|
||||
SCHED.add_job(librarysync.libraryScan, trigger=IntervalTrigger(
|
||||
hours=CONFIG.LIBRARYSCAN_INTERVAL))
|
||||
|
||||
if CONFIG.CHECK_GITHUB:
|
||||
SCHED.add_job(versioncheck.checkGithub, trigger=IntervalTrigger(minutes=CONFIG.CHECK_GITHUB_INTERVAL))
|
||||
SCHED.add_job(versioncheck.checkGithub, trigger=IntervalTrigger(
|
||||
minutes=CONFIG.CHECK_GITHUB_INTERVAL))
|
||||
|
||||
if CONFIG.DOWNLOAD_SCAN_INTERVAL > 0:
|
||||
SCHED.add_job(postprocessor.checkFolder, trigger=IntervalTrigger(minutes=CONFIG.DOWNLOAD_SCAN_INTERVAL))
|
||||
SCHED.add_job(postprocessor.checkFolder, trigger=IntervalTrigger(
|
||||
minutes=CONFIG.DOWNLOAD_SCAN_INTERVAL))
|
||||
|
||||
# Remove Torrent + data if Post Processed and finished Seeding
|
||||
if CONFIG.TORRENT_REMOVAL_INTERVAL > 0:
|
||||
SCHED.add_job(torrentfinished.checkTorrentFinished, trigger=IntervalTrigger(minutes=CONFIG.TORRENT_REMOVAL_INTERVAL))
|
||||
SCHED.add_job(torrentfinished.checkTorrentFinished, trigger=IntervalTrigger(
|
||||
minutes=CONFIG.TORRENT_REMOVAL_INTERVAL))
|
||||
|
||||
SCHED.start()
|
||||
|
||||
started = True
|
||||
|
||||
|
||||
def sig_handler(signum=None, frame=None):
|
||||
if signum is not None:
|
||||
logger.info("Signal %i caught, saving and exiting...", signum)
|
||||
shutdown()
|
||||
|
||||
|
||||
def dbcheck():
|
||||
|
||||
conn=sqlite3.connect(DB_FILE)
|
||||
c=conn.cursor()
|
||||
c.execute('CREATE TABLE IF NOT EXISTS artists (ArtistID TEXT UNIQUE, ArtistName TEXT, ArtistSortName TEXT, DateAdded TEXT, Status TEXT, IncludeExtras INTEGER, LatestAlbum TEXT, ReleaseDate TEXT, AlbumID TEXT, HaveTracks INTEGER, TotalTracks INTEGER, LastUpdated TEXT, ArtworkURL TEXT, ThumbURL TEXT, Extras TEXT)')
|
||||
c.execute('CREATE TABLE IF NOT EXISTS albums (ArtistID TEXT, ArtistName TEXT, AlbumTitle TEXT, AlbumASIN TEXT, ReleaseDate TEXT, DateAdded TEXT, AlbumID TEXT UNIQUE, Status TEXT, Type TEXT, ArtworkURL TEXT, ThumbURL TEXT, ReleaseID TEXT, ReleaseCountry TEXT, ReleaseFormat TEXT, SearchTerm TEXT)') # ReleaseFormat here means CD,Digital,Vinyl, etc. If using the default Headphones hybrid release, ReleaseID will equal AlbumID (AlbumID is releasegroup id)
|
||||
c.execute('CREATE TABLE IF NOT EXISTS tracks (ArtistID TEXT, ArtistName TEXT, AlbumTitle TEXT, AlbumASIN TEXT, AlbumID TEXT, TrackTitle TEXT, TrackDuration, TrackID TEXT, TrackNumber INTEGER, Location TEXT, BitRate INTEGER, CleanName TEXT, Format TEXT, ReleaseID TEXT)') # Format here means mp3, flac, etc.
|
||||
c.execute('CREATE TABLE IF NOT EXISTS allalbums (ArtistID TEXT, ArtistName TEXT, AlbumTitle TEXT, AlbumASIN TEXT, ReleaseDate TEXT, AlbumID TEXT, Type TEXT, ReleaseID TEXT, ReleaseCountry TEXT, ReleaseFormat TEXT)')
|
||||
c.execute('CREATE TABLE IF NOT EXISTS alltracks (ArtistID TEXT, ArtistName TEXT, AlbumTitle TEXT, AlbumASIN TEXT, AlbumID TEXT, TrackTitle TEXT, TrackDuration, TrackID TEXT, TrackNumber INTEGER, Location TEXT, BitRate INTEGER, CleanName TEXT, Format TEXT, ReleaseID TEXT)')
|
||||
c.execute('CREATE TABLE IF NOT EXISTS snatched (AlbumID TEXT, Title TEXT, Size INTEGER, URL TEXT, DateAdded TEXT, Status TEXT, FolderName TEXT, Kind TEXT)')
|
||||
c.execute('CREATE TABLE IF NOT EXISTS have (ArtistName TEXT, AlbumTitle TEXT, TrackNumber TEXT, TrackTitle TEXT, TrackLength TEXT, BitRate TEXT, Genre TEXT, Date TEXT, TrackID TEXT, Location TEXT, CleanName TEXT, Format TEXT, Matched TEXT)') # Matched is a temporary value used to see if there was a match found in alltracks
|
||||
c.execute('CREATE TABLE IF NOT EXISTS lastfmcloud (ArtistName TEXT, ArtistID TEXT, Count INTEGER)')
|
||||
c.execute('CREATE TABLE IF NOT EXISTS descriptions (ArtistID TEXT, ReleaseGroupID TEXT, ReleaseID TEXT, Summary TEXT, Content TEXT, LastUpdated TEXT)')
|
||||
conn = sqlite3.connect(DB_FILE)
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
'CREATE TABLE IF NOT EXISTS artists (ArtistID TEXT UNIQUE, ArtistName TEXT, ArtistSortName TEXT, DateAdded TEXT, Status TEXT, IncludeExtras INTEGER, LatestAlbum TEXT, ReleaseDate TEXT, AlbumID TEXT, HaveTracks INTEGER, TotalTracks INTEGER, LastUpdated TEXT, ArtworkURL TEXT, ThumbURL TEXT, Extras TEXT)')
|
||||
# ReleaseFormat here means CD,Digital,Vinyl, etc. If using the default
|
||||
# Headphones hybrid release, ReleaseID will equal AlbumID (AlbumID is
|
||||
# releasegroup id)
|
||||
c.execute(
|
||||
'CREATE TABLE IF NOT EXISTS albums (ArtistID TEXT, ArtistName TEXT, AlbumTitle TEXT, AlbumASIN TEXT, ReleaseDate TEXT, DateAdded TEXT, AlbumID TEXT UNIQUE, Status TEXT, Type TEXT, ArtworkURL TEXT, ThumbURL TEXT, ReleaseID TEXT, ReleaseCountry TEXT, ReleaseFormat TEXT, SearchTerm TEXT)')
|
||||
# Format here means mp3, flac, etc.
|
||||
c.execute(
|
||||
'CREATE TABLE IF NOT EXISTS tracks (ArtistID TEXT, ArtistName TEXT, AlbumTitle TEXT, AlbumASIN TEXT, AlbumID TEXT, TrackTitle TEXT, TrackDuration, TrackID TEXT, TrackNumber INTEGER, Location TEXT, BitRate INTEGER, CleanName TEXT, Format TEXT, ReleaseID TEXT)')
|
||||
c.execute(
|
||||
'CREATE TABLE IF NOT EXISTS allalbums (ArtistID TEXT, ArtistName TEXT, AlbumTitle TEXT, AlbumASIN TEXT, ReleaseDate TEXT, AlbumID TEXT, Type TEXT, ReleaseID TEXT, ReleaseCountry TEXT, ReleaseFormat TEXT)')
|
||||
c.execute(
|
||||
'CREATE TABLE IF NOT EXISTS alltracks (ArtistID TEXT, ArtistName TEXT, AlbumTitle TEXT, AlbumASIN TEXT, AlbumID TEXT, TrackTitle TEXT, TrackDuration, TrackID TEXT, TrackNumber INTEGER, Location TEXT, BitRate INTEGER, CleanName TEXT, Format TEXT, ReleaseID TEXT)')
|
||||
c.execute(
|
||||
'CREATE TABLE IF NOT EXISTS snatched (AlbumID TEXT, Title TEXT, Size INTEGER, URL TEXT, DateAdded TEXT, Status TEXT, FolderName TEXT, Kind TEXT)')
|
||||
# Matched is a temporary value used to see if there was a match found in
|
||||
# alltracks
|
||||
c.execute(
|
||||
'CREATE TABLE IF NOT EXISTS have (ArtistName TEXT, AlbumTitle TEXT, TrackNumber TEXT, TrackTitle TEXT, TrackLength TEXT, BitRate TEXT, Genre TEXT, Date TEXT, TrackID TEXT, Location TEXT, CleanName TEXT, Format TEXT, Matched TEXT)')
|
||||
c.execute(
|
||||
'CREATE TABLE IF NOT EXISTS lastfmcloud (ArtistName TEXT, ArtistID TEXT, Count INTEGER)')
|
||||
c.execute(
|
||||
'CREATE TABLE IF NOT EXISTS descriptions (ArtistID TEXT, ReleaseGroupID TEXT, ReleaseID TEXT, Summary TEXT, Content TEXT, LastUpdated TEXT)')
|
||||
c.execute('CREATE TABLE IF NOT EXISTS blacklist (ArtistID TEXT UNIQUE)')
|
||||
c.execute('CREATE TABLE IF NOT EXISTS newartists (ArtistName TEXT UNIQUE)')
|
||||
c.execute('CREATE TABLE IF NOT EXISTS releases (ReleaseID TEXT, ReleaseGroupID TEXT, UNIQUE(ReleaseID, ReleaseGroupID))')
|
||||
c.execute('CREATE INDEX IF NOT EXISTS tracks_albumid ON tracks(AlbumID ASC)')
|
||||
c.execute('CREATE INDEX IF NOT EXISTS album_artistid_reldate ON albums(ArtistID ASC, ReleaseDate DESC)')
|
||||
#Below creates indices to speed up Active Artist updating
|
||||
c.execute('CREATE INDEX IF NOT EXISTS alltracks_relid ON alltracks(ReleaseID ASC, TrackID ASC)')
|
||||
c.execute('CREATE INDEX IF NOT EXISTS allalbums_relid ON allalbums(ReleaseID ASC)')
|
||||
c.execute(
|
||||
'CREATE TABLE IF NOT EXISTS releases (ReleaseID TEXT, ReleaseGroupID TEXT, UNIQUE(ReleaseID, ReleaseGroupID))')
|
||||
c.execute(
|
||||
'CREATE INDEX IF NOT EXISTS tracks_albumid ON tracks(AlbumID ASC)')
|
||||
c.execute(
|
||||
'CREATE INDEX IF NOT EXISTS album_artistid_reldate ON albums(ArtistID ASC, ReleaseDate DESC)')
|
||||
# Below creates indices to speed up Active Artist updating
|
||||
c.execute(
|
||||
'CREATE INDEX IF NOT EXISTS alltracks_relid ON alltracks(ReleaseID ASC, TrackID ASC)')
|
||||
c.execute(
|
||||
'CREATE INDEX IF NOT EXISTS allalbums_relid ON allalbums(ReleaseID ASC)')
|
||||
c.execute('CREATE INDEX IF NOT EXISTS have_location ON have(Location ASC)')
|
||||
#Below creates indices to speed up library scanning & matching
|
||||
c.execute('CREATE INDEX IF NOT EXISTS have_Metadata ON have(ArtistName ASC, AlbumTitle ASC, TrackTitle ASC)')
|
||||
c.execute('CREATE INDEX IF NOT EXISTS have_CleanName ON have(CleanName ASC)')
|
||||
c.execute('CREATE INDEX IF NOT EXISTS tracks_Metadata ON tracks(ArtistName ASC, AlbumTitle ASC, TrackTitle ASC)')
|
||||
c.execute('CREATE INDEX IF NOT EXISTS tracks_CleanName ON tracks(CleanName ASC)')
|
||||
c.execute('CREATE INDEX IF NOT EXISTS alltracks_Metadata ON alltracks(ArtistName ASC, AlbumTitle ASC, TrackTitle ASC)')
|
||||
c.execute('CREATE INDEX IF NOT EXISTS alltracks_CleanName ON alltracks(CleanName ASC)')
|
||||
c.execute('CREATE INDEX IF NOT EXISTS tracks_Location ON tracks(Location ASC)')
|
||||
c.execute('CREATE INDEX IF NOT EXISTS alltracks_Location ON alltracks(Location ASC)')
|
||||
# Below creates indices to speed up library scanning & matching
|
||||
c.execute(
|
||||
'CREATE INDEX IF NOT EXISTS have_Metadata ON have(ArtistName ASC, AlbumTitle ASC, TrackTitle ASC)')
|
||||
c.execute(
|
||||
'CREATE INDEX IF NOT EXISTS have_CleanName ON have(CleanName ASC)')
|
||||
c.execute(
|
||||
'CREATE INDEX IF NOT EXISTS tracks_Metadata ON tracks(ArtistName ASC, AlbumTitle ASC, TrackTitle ASC)')
|
||||
c.execute(
|
||||
'CREATE INDEX IF NOT EXISTS tracks_CleanName ON tracks(CleanName ASC)')
|
||||
c.execute(
|
||||
'CREATE INDEX IF NOT EXISTS alltracks_Metadata ON alltracks(ArtistName ASC, AlbumTitle ASC, TrackTitle ASC)')
|
||||
c.execute(
|
||||
'CREATE INDEX IF NOT EXISTS alltracks_CleanName ON alltracks(CleanName ASC)')
|
||||
c.execute(
|
||||
'CREATE INDEX IF NOT EXISTS tracks_Location ON tracks(Location ASC)')
|
||||
c.execute(
|
||||
'CREATE INDEX IF NOT EXISTS alltracks_Location ON alltracks(Location ASC)')
|
||||
|
||||
try:
|
||||
c.execute('SELECT IncludeExtras from artists')
|
||||
except sqlite3.OperationalError:
|
||||
c.execute('ALTER TABLE artists ADD COLUMN IncludeExtras INTEGER DEFAULT 0')
|
||||
c.execute(
|
||||
'ALTER TABLE artists ADD COLUMN IncludeExtras INTEGER DEFAULT 0')
|
||||
|
||||
try:
|
||||
c.execute('SELECT LatestAlbum from artists')
|
||||
@@ -325,12 +369,14 @@ def dbcheck():
|
||||
try:
|
||||
c.execute('SELECT HaveTracks from artists')
|
||||
except sqlite3.OperationalError:
|
||||
c.execute('ALTER TABLE artists ADD COLUMN HaveTracks INTEGER DEFAULT 0')
|
||||
c.execute(
|
||||
'ALTER TABLE artists ADD COLUMN HaveTracks INTEGER DEFAULT 0')
|
||||
|
||||
try:
|
||||
c.execute('SELECT TotalTracks from artists')
|
||||
except sqlite3.OperationalError:
|
||||
c.execute('ALTER TABLE artists ADD COLUMN TotalTracks INTEGER DEFAULT 0')
|
||||
c.execute(
|
||||
'ALTER TABLE artists ADD COLUMN TotalTracks INTEGER DEFAULT 0')
|
||||
|
||||
try:
|
||||
c.execute('SELECT Type from albums')
|
||||
@@ -386,12 +432,14 @@ def dbcheck():
|
||||
try:
|
||||
c.execute('SELECT LastUpdated from artists')
|
||||
except sqlite3.OperationalError:
|
||||
c.execute('ALTER TABLE artists ADD COLUMN LastUpdated TEXT DEFAULT NULL')
|
||||
c.execute(
|
||||
'ALTER TABLE artists ADD COLUMN LastUpdated TEXT DEFAULT NULL')
|
||||
|
||||
try:
|
||||
c.execute('SELECT ArtworkURL from artists')
|
||||
except sqlite3.OperationalError:
|
||||
c.execute('ALTER TABLE artists ADD COLUMN ArtworkURL TEXT DEFAULT NULL')
|
||||
c.execute(
|
||||
'ALTER TABLE artists ADD COLUMN ArtworkURL TEXT DEFAULT NULL')
|
||||
|
||||
try:
|
||||
c.execute('SELECT ArtworkURL from albums')
|
||||
@@ -411,12 +459,14 @@ def dbcheck():
|
||||
try:
|
||||
c.execute('SELECT ArtistID from descriptions')
|
||||
except sqlite3.OperationalError:
|
||||
c.execute('ALTER TABLE descriptions ADD COLUMN ArtistID TEXT DEFAULT NULL')
|
||||
c.execute(
|
||||
'ALTER TABLE descriptions ADD COLUMN ArtistID TEXT DEFAULT NULL')
|
||||
|
||||
try:
|
||||
c.execute('SELECT LastUpdated from descriptions')
|
||||
except sqlite3.OperationalError:
|
||||
c.execute('ALTER TABLE descriptions ADD COLUMN LastUpdated TEXT DEFAULT NULL')
|
||||
c.execute(
|
||||
'ALTER TABLE descriptions ADD COLUMN LastUpdated TEXT DEFAULT NULL')
|
||||
|
||||
try:
|
||||
c.execute('SELECT ReleaseID from albums')
|
||||
@@ -426,12 +476,14 @@ def dbcheck():
|
||||
try:
|
||||
c.execute('SELECT ReleaseFormat from albums')
|
||||
except sqlite3.OperationalError:
|
||||
c.execute('ALTER TABLE albums ADD COLUMN ReleaseFormat TEXT DEFAULT NULL')
|
||||
c.execute(
|
||||
'ALTER TABLE albums ADD COLUMN ReleaseFormat TEXT DEFAULT NULL')
|
||||
|
||||
try:
|
||||
c.execute('SELECT ReleaseCountry from albums')
|
||||
except sqlite3.OperationalError:
|
||||
c.execute('ALTER TABLE albums ADD COLUMN ReleaseCountry TEXT DEFAULT NULL')
|
||||
c.execute(
|
||||
'ALTER TABLE albums ADD COLUMN ReleaseCountry TEXT DEFAULT NULL')
|
||||
|
||||
try:
|
||||
c.execute('SELECT ReleaseID from tracks')
|
||||
@@ -447,14 +499,17 @@ def dbcheck():
|
||||
c.execute('SELECT Extras from artists')
|
||||
except sqlite3.OperationalError:
|
||||
c.execute('ALTER TABLE artists ADD COLUMN Extras TEXT DEFAULT NULL')
|
||||
# Need to update some stuff when people are upgrading and have 'include extras' set globally/for an artist
|
||||
# Need to update some stuff when people are upgrading and have 'include
|
||||
# extras' set globally/for an artist
|
||||
if CONFIG.INCLUDE_EXTRAS:
|
||||
CONFIG.EXTRAS = "1,2,3,4,5,6,7,8"
|
||||
logger.info("Copying over current artist IncludeExtras information")
|
||||
artists = c.execute('SELECT ArtistID, IncludeExtras from artists').fetchall()
|
||||
artists = c.execute(
|
||||
'SELECT ArtistID, IncludeExtras from artists').fetchall()
|
||||
for artist in artists:
|
||||
if artist[1]:
|
||||
c.execute('UPDATE artists SET Extras=? WHERE ArtistID=?', ("1,2,3,4,5,6,7,8", artist[0]))
|
||||
c.execute(
|
||||
'UPDATE artists SET Extras=? WHERE ArtistID=?', ("1,2,3,4,5,6,7,8", artist[0]))
|
||||
|
||||
try:
|
||||
c.execute('SELECT Kind from snatched')
|
||||
@@ -466,7 +521,6 @@ def dbcheck():
|
||||
except sqlite3.OperationalError:
|
||||
c.execute('ALTER TABLE albums ADD COLUMN SearchTerm TEXT DEFAULT NULL')
|
||||
|
||||
|
||||
conn.commit()
|
||||
c.close()
|
||||
|
||||
@@ -489,7 +543,7 @@ def shutdown(restart=False, update=False):
|
||||
logger.warn('Headphones failed to update: %s. Restarting.', e)
|
||||
|
||||
if CREATEPID:
|
||||
logger.info ('Removing pidfile %s', PIDFILE)
|
||||
logger.info('Removing pidfile %s', PIDFILE)
|
||||
os.remove(PIDFILE)
|
||||
|
||||
if restart:
|
||||
|
||||
@@ -15,13 +15,16 @@
|
||||
|
||||
from headphones import request, db
|
||||
|
||||
|
||||
def getAlbumArt(albumid):
|
||||
myDB = db.DBConnection()
|
||||
asin = myDB.action('SELECT AlbumASIN from albums WHERE AlbumID=?', [albumid]).fetchone()[0]
|
||||
asin = myDB.action(
|
||||
'SELECT AlbumASIN from albums WHERE AlbumID=?', [albumid]).fetchone()[0]
|
||||
|
||||
if asin:
|
||||
return 'http://ec1.images-amazon.com/images/P/%s.01.LZZZZZZZ.jpg' % asin
|
||||
|
||||
|
||||
def getCachedArt(albumid):
|
||||
from headphones import cache
|
||||
|
||||
|
||||
+33
-24
@@ -16,15 +16,19 @@
|
||||
import headphones
|
||||
from headphones import db, logger, cache
|
||||
|
||||
|
||||
def switch(AlbumID, ReleaseID):
|
||||
'''
|
||||
Takes the contents from allalbums & alltracks (based on ReleaseID) and switches them into
|
||||
the albums & tracks table.
|
||||
'''
|
||||
myDB = db.DBConnection()
|
||||
oldalbumdata = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone()
|
||||
newalbumdata = myDB.action('SELECT * from allalbums WHERE ReleaseID=?', [ReleaseID]).fetchone()
|
||||
newtrackdata = myDB.action('SELECT * from alltracks WHERE ReleaseID=?', [ReleaseID]).fetchall()
|
||||
oldalbumdata = myDB.action(
|
||||
'SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone()
|
||||
newalbumdata = myDB.action(
|
||||
'SELECT * from allalbums WHERE ReleaseID=?', [ReleaseID]).fetchone()
|
||||
newtrackdata = myDB.action(
|
||||
'SELECT * from alltracks WHERE ReleaseID=?', [ReleaseID]).fetchall()
|
||||
myDB.action('DELETE from tracks WHERE AlbumID=?', [AlbumID])
|
||||
|
||||
controlValueDict = {"AlbumID": AlbumID}
|
||||
@@ -38,7 +42,7 @@ def switch(AlbumID, ReleaseID):
|
||||
"Type": newalbumdata['Type'],
|
||||
"ReleaseCountry": newalbumdata['ReleaseCountry'],
|
||||
"ReleaseFormat": newalbumdata['ReleaseFormat']
|
||||
}
|
||||
}
|
||||
|
||||
myDB.upsert("albums", newValueDict, controlValueDict)
|
||||
|
||||
@@ -53,35 +57,40 @@ def switch(AlbumID, ReleaseID):
|
||||
"AlbumID": AlbumID}
|
||||
|
||||
newValueDict = {"ArtistID": track['ArtistID'],
|
||||
"ArtistName": track['ArtistName'],
|
||||
"AlbumTitle": track['AlbumTitle'],
|
||||
"AlbumASIN": track['AlbumASIN'],
|
||||
"ReleaseID": track['ReleaseID'],
|
||||
"TrackTitle": track['TrackTitle'],
|
||||
"TrackDuration": track['TrackDuration'],
|
||||
"TrackNumber": track['TrackNumber'],
|
||||
"CleanName": track['CleanName'],
|
||||
"Location": track['Location'],
|
||||
"Format": track['Format'],
|
||||
"BitRate": track['BitRate']
|
||||
}
|
||||
"ArtistName": track['ArtistName'],
|
||||
"AlbumTitle": track['AlbumTitle'],
|
||||
"AlbumASIN": track['AlbumASIN'],
|
||||
"ReleaseID": track['ReleaseID'],
|
||||
"TrackTitle": track['TrackTitle'],
|
||||
"TrackDuration": track['TrackDuration'],
|
||||
"TrackNumber": track['TrackNumber'],
|
||||
"CleanName": track['CleanName'],
|
||||
"Location": track['Location'],
|
||||
"Format": track['Format'],
|
||||
"BitRate": track['BitRate']
|
||||
}
|
||||
|
||||
myDB.upsert("tracks", newValueDict, controlValueDict)
|
||||
|
||||
# Mark albums as downloaded if they have at least 80% (by default, configurable) of the album
|
||||
# Mark albums as downloaded if they have at least 80% (by default,
|
||||
# configurable) of the album
|
||||
total_track_count = len(newtrackdata)
|
||||
have_track_count = len(myDB.select('SELECT * from tracks WHERE AlbumID=? AND Location IS NOT NULL', [AlbumID]))
|
||||
have_track_count = len(myDB.select(
|
||||
'SELECT * from tracks WHERE AlbumID=? AND Location IS NOT NULL', [AlbumID]))
|
||||
|
||||
if oldalbumdata['Status'] == 'Skipped' and ((have_track_count/float(total_track_count)) >= (headphones.CONFIG.ALBUM_COMPLETION_PCT/100.0)):
|
||||
myDB.action('UPDATE albums SET Status=? WHERE AlbumID=?', ['Downloaded', AlbumID])
|
||||
if oldalbumdata['Status'] == 'Skipped' and ((have_track_count / float(total_track_count)) >= (headphones.CONFIG.ALBUM_COMPLETION_PCT / 100.0)):
|
||||
myDB.action(
|
||||
'UPDATE albums SET Status=? WHERE AlbumID=?', ['Downloaded', AlbumID])
|
||||
|
||||
# Update have track counts on index
|
||||
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND AlbumID IN (SELECT AlbumID FROM albums WHERE Status != "Ignored")', [newalbumdata['ArtistID']]))
|
||||
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [newalbumdata['ArtistID']]))
|
||||
totaltracks = len(myDB.select(
|
||||
'SELECT TrackTitle from tracks WHERE ArtistID=? AND AlbumID IN (SELECT AlbumID FROM albums WHERE Status != "Ignored")', [newalbumdata['ArtistID']]))
|
||||
havetracks = len(myDB.select(
|
||||
'SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [newalbumdata['ArtistID']]))
|
||||
|
||||
controlValueDict = {"ArtistID": newalbumdata['ArtistID']}
|
||||
|
||||
newValueDict = { "TotalTracks": totaltracks,
|
||||
"HaveTracks": havetracks}
|
||||
newValueDict = {"TotalTracks": totaltracks,
|
||||
"HaveTracks": havetracks}
|
||||
|
||||
myDB.upsert("artists", newValueDict, controlValueDict)
|
||||
|
||||
+55
-38
@@ -21,12 +21,13 @@ import headphones
|
||||
import copy
|
||||
import json
|
||||
|
||||
cmd_list = [ 'getIndex', 'getArtist', 'getAlbum', 'getUpcoming', 'getWanted', 'getSimilar', 'getHistory', 'getLogs',
|
||||
cmd_list = ['getIndex', 'getArtist', 'getAlbum', 'getUpcoming', 'getWanted', 'getSimilar', 'getHistory', 'getLogs',
|
||||
'findArtist', 'findAlbum', 'addArtist', 'delArtist', 'pauseArtist', 'resumeArtist', 'refreshArtist',
|
||||
'addAlbum', 'queueAlbum', 'unqueueAlbum', 'forceSearch', 'forceProcess', 'getVersion', 'checkGithub',
|
||||
'shutdown', 'restart', 'update', 'getArtistArt', 'getAlbumArt', 'getArtistInfo', 'getAlbumInfo',
|
||||
'getArtistThumb', 'getAlbumThumb', 'choose_specific_download', 'download_specific_release']
|
||||
|
||||
|
||||
class Api(object):
|
||||
|
||||
def __init__(self):
|
||||
@@ -41,8 +42,7 @@ class Api(object):
|
||||
|
||||
self.callback = None
|
||||
|
||||
|
||||
def checkParams(self,*args,**kwargs):
|
||||
def checkParams(self, *args, **kwargs):
|
||||
|
||||
if not headphones.CONFIG.API_ENABLED:
|
||||
self.data = 'API not enabled'
|
||||
@@ -96,7 +96,7 @@ class Api(object):
|
||||
else:
|
||||
return self.data
|
||||
|
||||
def _dic_from_query(self,query):
|
||||
def _dic_from_query(self, query):
|
||||
|
||||
myDB = db.DBConnection()
|
||||
rows = myDB.select(query)
|
||||
@@ -111,7 +111,8 @@ class Api(object):
|
||||
|
||||
def _getIndex(self, **kwargs):
|
||||
|
||||
self.data = self._dic_from_query('SELECT * from artists order by ArtistSortName COLLATE NOCASE')
|
||||
self.data = self._dic_from_query(
|
||||
'SELECT * from artists order by ArtistSortName COLLATE NOCASE')
|
||||
return
|
||||
|
||||
def _getArtist(self, **kwargs):
|
||||
@@ -122,11 +123,15 @@ class Api(object):
|
||||
else:
|
||||
self.id = kwargs['id']
|
||||
|
||||
artist = self._dic_from_query('SELECT * from artists WHERE ArtistID="' + self.id + '"')
|
||||
albums = self._dic_from_query('SELECT * from albums WHERE ArtistID="' + self.id + '" order by ReleaseDate DESC')
|
||||
description = self._dic_from_query('SELECT * from descriptions WHERE ArtistID="' + self.id + '"')
|
||||
artist = self._dic_from_query(
|
||||
'SELECT * from artists WHERE ArtistID="' + self.id + '"')
|
||||
albums = self._dic_from_query(
|
||||
'SELECT * from albums WHERE ArtistID="' + self.id + '" order by ReleaseDate DESC')
|
||||
description = self._dic_from_query(
|
||||
'SELECT * from descriptions WHERE ArtistID="' + self.id + '"')
|
||||
|
||||
self.data = { 'artist': artist, 'albums': albums, 'description' : description }
|
||||
self.data = {
|
||||
'artist': artist, 'albums': albums, 'description': description}
|
||||
return
|
||||
|
||||
def _getAlbum(self, **kwargs):
|
||||
@@ -137,23 +142,30 @@ class Api(object):
|
||||
else:
|
||||
self.id = kwargs['id']
|
||||
|
||||
album = self._dic_from_query('SELECT * from albums WHERE AlbumID="' + self.id + '"')
|
||||
tracks = self._dic_from_query('SELECT * from tracks WHERE AlbumID="' + self.id + '"')
|
||||
description = self._dic_from_query('SELECT * from descriptions WHERE ReleaseGroupID="' + self.id + '"')
|
||||
album = self._dic_from_query(
|
||||
'SELECT * from albums WHERE AlbumID="' + self.id + '"')
|
||||
tracks = self._dic_from_query(
|
||||
'SELECT * from tracks WHERE AlbumID="' + self.id + '"')
|
||||
description = self._dic_from_query(
|
||||
'SELECT * from descriptions WHERE ReleaseGroupID="' + self.id + '"')
|
||||
|
||||
self.data = { 'album' : album, 'tracks' : tracks, 'description' : description }
|
||||
self.data = {
|
||||
'album': album, 'tracks': tracks, 'description': description}
|
||||
return
|
||||
|
||||
def _getHistory(self, **kwargs):
|
||||
self.data = self._dic_from_query('SELECT * from snatched WHERE status NOT LIKE "Seed%" order by DateAdded DESC')
|
||||
self.data = self._dic_from_query(
|
||||
'SELECT * from snatched WHERE status NOT LIKE "Seed%" order by DateAdded DESC')
|
||||
return
|
||||
|
||||
def _getUpcoming(self, **kwargs):
|
||||
self.data = self._dic_from_query("SELECT * from albums WHERE ReleaseDate > date('now') order by ReleaseDate DESC")
|
||||
self.data = self._dic_from_query(
|
||||
"SELECT * from albums WHERE ReleaseDate > date('now') order by ReleaseDate DESC")
|
||||
return
|
||||
|
||||
def _getWanted(self, **kwargs):
|
||||
self.data = self._dic_from_query("SELECT * from albums WHERE Status='Wanted'")
|
||||
self.data = self._dic_from_query(
|
||||
"SELECT * from albums WHERE Status='Wanted'")
|
||||
return
|
||||
|
||||
def _getSimilar(self, **kwargs):
|
||||
@@ -170,7 +182,7 @@ class Api(object):
|
||||
if 'limit' in kwargs:
|
||||
limit = kwargs['limit']
|
||||
else:
|
||||
limit=50
|
||||
limit = 50
|
||||
|
||||
self.data = mb.findArtist(kwargs['name'], limit)
|
||||
|
||||
@@ -181,7 +193,7 @@ class Api(object):
|
||||
if 'limit' in kwargs:
|
||||
limit = kwargs['limit']
|
||||
else:
|
||||
limit=50
|
||||
limit = 50
|
||||
|
||||
self.data = mb.findRelease(kwargs['name'], limit)
|
||||
|
||||
@@ -314,11 +326,11 @@ class Api(object):
|
||||
|
||||
def _getVersion(self, **kwargs):
|
||||
self.data = {
|
||||
'git_path' : headphones.CONFIG.GIT_PATH,
|
||||
'install_type' : headphones.INSTALL_TYPE,
|
||||
'current_version' : headphones.CURRENT_VERSION,
|
||||
'latest_version' : headphones.LATEST_VERSION,
|
||||
'commits_behind' : headphones.COMMITS_BEHIND,
|
||||
'git_path': headphones.CONFIG.GIT_PATH,
|
||||
'install_type': headphones.INSTALL_TYPE,
|
||||
'current_version': headphones.CURRENT_VERSION,
|
||||
'latest_version': headphones.LATEST_VERSION,
|
||||
'commits_behind': headphones.COMMITS_BEHIND,
|
||||
}
|
||||
|
||||
def _checkGithub(self, **kwargs):
|
||||
@@ -402,18 +414,19 @@ class Api(object):
|
||||
else:
|
||||
self.id = kwargs['id']
|
||||
|
||||
results = searcher.searchforalbum(self.id, choose_specific_download=True)
|
||||
results = searcher.searchforalbum(
|
||||
self.id, choose_specific_download=True)
|
||||
|
||||
results_as_dicts = []
|
||||
|
||||
for result in results:
|
||||
|
||||
result_dict = {
|
||||
'title':result[0],
|
||||
'size':result[1],
|
||||
'url':result[2],
|
||||
'provider':result[3],
|
||||
'kind':result[4]
|
||||
'title': result[0],
|
||||
'size': result[1],
|
||||
'url': result[2],
|
||||
'provider': result[3],
|
||||
'kind': result[4]
|
||||
}
|
||||
results_as_dicts.append(result_dict)
|
||||
|
||||
@@ -421,7 +434,7 @@ class Api(object):
|
||||
|
||||
def _download_specific_release(self, **kwargs):
|
||||
|
||||
expected_kwargs =['id', 'title','size','url','provider','kind']
|
||||
expected_kwargs = ['id', 'title', 'size', 'url', 'provider', 'kind']
|
||||
|
||||
for kwarg in expected_kwargs:
|
||||
if kwarg not in kwargs:
|
||||
@@ -438,20 +451,24 @@ class Api(object):
|
||||
for kwarg in expected_kwargs:
|
||||
del kwargs[kwarg]
|
||||
|
||||
# Handle situations where the torrent url contains arguments that are parsed
|
||||
# Handle situations where the torrent url contains arguments that are
|
||||
# parsed
|
||||
if kwargs:
|
||||
import urllib, urllib2
|
||||
url = urllib2.quote(url, safe=":?/=&") + '&' + urllib.urlencode(kwargs)
|
||||
import urllib
|
||||
import urllib2
|
||||
url = urllib2.quote(
|
||||
url, safe=":?/=&") + '&' + urllib.urlencode(kwargs)
|
||||
|
||||
try:
|
||||
result = [(title,int(size),url,provider,kind)]
|
||||
result = [(title, int(size), url, provider, kind)]
|
||||
except ValueError:
|
||||
result = [(title,float(size),url,provider,kind)]
|
||||
result = [(title, float(size), url, provider, kind)]
|
||||
|
||||
logger.info(u"Making sure we can download the chosen result")
|
||||
(data, bestqual) = searcher.preprocess(result)
|
||||
|
||||
if data and bestqual:
|
||||
myDB = db.DBConnection()
|
||||
album = myDB.action('SELECT * from albums WHERE AlbumID=?', [id]).fetchone()
|
||||
searcher.send_to_downloader(data, bestqual, album)
|
||||
myDB = db.DBConnection()
|
||||
album = myDB.action(
|
||||
'SELECT * from albums WHERE AlbumID=?', [id]).fetchone()
|
||||
searcher.send_to_downloader(data, bestqual, album)
|
||||
|
||||
+13
-9
@@ -22,6 +22,7 @@ from headphones import db, helpers, logger, lastfm, request
|
||||
|
||||
LASTFM_API_KEY = "690e1ed3bc00bc91804cd8f7fe5ed6d4"
|
||||
|
||||
|
||||
class Cache(object):
|
||||
"""
|
||||
This class deals with getting, storing and serving up artwork (album
|
||||
@@ -59,12 +60,12 @@ class Cache(object):
|
||||
self.info_summary = None
|
||||
self.info_content = None
|
||||
|
||||
def _findfilesstartingwith(self,pattern,folder):
|
||||
def _findfilesstartingwith(self, pattern, folder):
|
||||
files = []
|
||||
if os.path.exists(folder):
|
||||
for fname in os.listdir(folder):
|
||||
if fname.startswith(pattern):
|
||||
files.append(os.path.join(folder,fname))
|
||||
files.append(os.path.join(folder, fname))
|
||||
return files
|
||||
|
||||
def _exists(self, type):
|
||||
@@ -72,14 +73,14 @@ class Cache(object):
|
||||
self.thumb_files = []
|
||||
|
||||
if type == 'artwork':
|
||||
self.artwork_files = self._findfilesstartingwith(self.id,self.path_to_art_cache)
|
||||
self.artwork_files = self._findfilesstartingwith(self.id, self.path_to_art_cache)
|
||||
if self.artwork_files:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
elif type == 'thumb':
|
||||
self.thumb_files = self._findfilesstartingwith("T_" + self.id,self.path_to_art_cache)
|
||||
self.thumb_files = self._findfilesstartingwith("T_" + self.id, self.path_to_art_cache)
|
||||
if self.thumb_files:
|
||||
return True
|
||||
else:
|
||||
@@ -92,7 +93,6 @@ class Cache(object):
|
||||
|
||||
return days_old
|
||||
|
||||
|
||||
def _is_current(self, filename=None, date=None):
|
||||
|
||||
if filename:
|
||||
@@ -191,11 +191,11 @@ class Cache(object):
|
||||
if not db_info or not db_info['LastUpdated'] or not self._is_current(date=db_info['LastUpdated']):
|
||||
|
||||
self._update_cache()
|
||||
info_dict = { 'Summary' : self.info_summary, 'Content' : self.info_content }
|
||||
info_dict = { 'Summary': self.info_summary, 'Content': self.info_content }
|
||||
return info_dict
|
||||
|
||||
else:
|
||||
info_dict = { 'Summary' : db_info['Summary'], 'Content' : db_info['Content'] }
|
||||
info_dict = { 'Summary': db_info['Summary'], 'Content': db_info['Content'] }
|
||||
return info_dict
|
||||
|
||||
def get_image_links(self, ArtistID=None, AlbumID=None):
|
||||
@@ -240,7 +240,7 @@ class Cache(object):
|
||||
if not thumb_url:
|
||||
logger.debug('No album thumbnail image found on last.fm')
|
||||
|
||||
return {'artwork' : image_url, 'thumbnail' : thumb_url }
|
||||
return {'artwork': image_url, 'thumbnail': thumb_url }
|
||||
|
||||
def remove_from_cache(self, ArtistID=None, AlbumID=None):
|
||||
"""
|
||||
@@ -400,7 +400,7 @@ class Cache(object):
|
||||
self.artwork_url = image_url
|
||||
|
||||
# Grab the thumbnail as well if we're getting the full artwork (as long as it's missing/outdated
|
||||
if thumb_url and self.query_type in ['thumb','artwork'] and not (self.thumb_files and self._is_current(self.thumb_files[0])):
|
||||
if thumb_url and self.query_type in ['thumb', 'artwork'] and not (self.thumb_files and self._is_current(self.thumb_files[0])):
|
||||
artwork = request.request_content(thumb_url, timeout=20)
|
||||
|
||||
if artwork:
|
||||
@@ -431,6 +431,7 @@ class Cache(object):
|
||||
self.thumb_errors = True
|
||||
self.thumb_url = image_url
|
||||
|
||||
|
||||
def getArtwork(ArtistID=None, AlbumID=None):
|
||||
|
||||
c = Cache()
|
||||
@@ -445,6 +446,7 @@ def getArtwork(ArtistID=None, AlbumID=None):
|
||||
artwork_file = os.path.basename(artwork_path)
|
||||
return "cache/artwork/" + artwork_file
|
||||
|
||||
|
||||
def getThumb(ArtistID=None, AlbumID=None):
|
||||
|
||||
c = Cache()
|
||||
@@ -459,6 +461,7 @@ def getThumb(ArtistID=None, AlbumID=None):
|
||||
thumbnail_file = os.path.basename(artwork_path)
|
||||
return "cache/artwork/" + thumbnail_file
|
||||
|
||||
|
||||
def getInfo(ArtistID=None, AlbumID=None):
|
||||
|
||||
c = Cache()
|
||||
@@ -467,6 +470,7 @@ def getInfo(ArtistID=None, AlbumID=None):
|
||||
|
||||
return info_dict
|
||||
|
||||
|
||||
def getImageLinks(ArtistID=None, AlbumID=None):
|
||||
|
||||
c = Cache()
|
||||
|
||||
+11
-3
@@ -24,24 +24,27 @@ import datetime
|
||||
|
||||
from common import USER_AGENT
|
||||
|
||||
|
||||
class HeadphonesURLopener(urllib.FancyURLopener):
|
||||
version = USER_AGENT
|
||||
|
||||
|
||||
class AuthURLOpener(HeadphonesURLopener):
|
||||
"""
|
||||
URLOpener class that supports http auth without needing interactive password entry.
|
||||
If the provided username/password don't work it simply fails.
|
||||
|
||||
|
||||
user: username to use for HTTP auth
|
||||
pw: password to use for HTTP auth
|
||||
"""
|
||||
|
||||
def __init__(self, user, pw):
|
||||
self.username = user
|
||||
self.password = pw
|
||||
|
||||
# remember if we've tried the username/password before
|
||||
self.numTries = 0
|
||||
|
||||
|
||||
# call the base class
|
||||
urllib.FancyURLopener.__init__(self)
|
||||
|
||||
@@ -55,7 +58,7 @@ class AuthURLOpener(HeadphonesURLopener):
|
||||
if self.numTries == 0:
|
||||
self.numTries = 1
|
||||
return (self.username, self.password)
|
||||
|
||||
|
||||
# if we've tried before then return blank which cancels the request
|
||||
else:
|
||||
return ('', '')
|
||||
@@ -65,6 +68,7 @@ class AuthURLOpener(HeadphonesURLopener):
|
||||
self.numTries = 0
|
||||
return HeadphonesURLopener.open(self, url)
|
||||
|
||||
|
||||
class SearchResult:
|
||||
"""
|
||||
Represents a search result from an indexer.
|
||||
@@ -96,24 +100,28 @@ class SearchResult:
|
||||
myString += " " + extra + "\n"
|
||||
return myString
|
||||
|
||||
|
||||
class NZBSearchResult(SearchResult):
|
||||
"""
|
||||
Regular NZB result with an URL to the NZB
|
||||
"""
|
||||
resultType = "nzb"
|
||||
|
||||
|
||||
class NZBDataSearchResult(SearchResult):
|
||||
"""
|
||||
NZB result where the actual NZB XML data is stored in the extraInfo
|
||||
"""
|
||||
resultType = "nzbdata"
|
||||
|
||||
|
||||
class TorrentSearchResult(SearchResult):
|
||||
"""
|
||||
Torrent result with an URL to the torrent
|
||||
"""
|
||||
resultType = "torrent"
|
||||
|
||||
|
||||
class Proper:
|
||||
def __init__(self, name, url, date):
|
||||
self.name = name
|
||||
|
||||
+10
-9
@@ -44,17 +44,18 @@ ARCHIVED = 6 # releases that you don't have locally (counts toward download comp
|
||||
IGNORED = 7 # releases that you don't want included in your download stats
|
||||
SNATCHED_PROPER = 9 # qualified with quality
|
||||
|
||||
|
||||
class Quality:
|
||||
|
||||
NONE = 0
|
||||
B192 = 1<<1 # 2
|
||||
VBR = 1<<2 # 4
|
||||
B256 = 1<<3 # 8
|
||||
B320 = 1<<4 #16
|
||||
FLAC = 1<<5 #32
|
||||
B192 = 1 << 1 # 2
|
||||
VBR = 1 << 2 # 4
|
||||
B256 = 1 << 3 # 8
|
||||
B320 = 1 << 4 #16
|
||||
FLAC = 1 << 5 #32
|
||||
|
||||
# put these bits at the other end of the spectrum, far enough out that they shouldn't interfere
|
||||
UNKNOWN = 1<<15
|
||||
UNKNOWN = 1 << 15
|
||||
|
||||
qualityStrings = {NONE: "N/A",
|
||||
UNKNOWN: "Unknown",
|
||||
@@ -82,7 +83,7 @@ class Quality:
|
||||
anyQuality = reduce(operator.or_, anyQualities)
|
||||
if bestQualities:
|
||||
bestQuality = reduce(operator.or_, bestQualities)
|
||||
return anyQuality | (bestQuality<<16)
|
||||
return anyQuality | (bestQuality << 16)
|
||||
|
||||
@staticmethod
|
||||
def splitQuality(quality):
|
||||
@@ -91,7 +92,7 @@ class Quality:
|
||||
for curQual in Quality.qualityStrings.keys():
|
||||
if curQual & quality:
|
||||
anyQualities.append(curQual)
|
||||
if curQual<<16 & quality:
|
||||
if curQual << 16 & quality:
|
||||
bestQualities.append(curQual)
|
||||
|
||||
return (anyQualities, bestQualities)
|
||||
@@ -106,7 +107,7 @@ class Quality:
|
||||
if x == Quality.UNKNOWN:
|
||||
continue
|
||||
|
||||
regex = '\W'+Quality.qualityStrings[x].replace(' ','\W')+'\W'
|
||||
regex = '\W'+Quality.qualityStrings[x].replace(' ', '\W')+'\W'
|
||||
regex_match = re.search(regex, name, re.I)
|
||||
if regex_match:
|
||||
return x
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
import re
|
||||
from configobj import ConfigObj
|
||||
|
||||
|
||||
def bool_int(value):
|
||||
"""
|
||||
Casts a config value into a 0 or 1
|
||||
@@ -232,6 +233,7 @@ _CONFIG_DEFINITIONS = {
|
||||
'XLDPROFILE': (str, 'General', '')
|
||||
}
|
||||
|
||||
|
||||
class Config(object):
|
||||
""" Wraps access to particular values in a config file """
|
||||
|
||||
@@ -316,8 +318,10 @@ class Config(object):
|
||||
|
||||
def add_extra_newznab(self, newznab):
|
||||
""" Add a new extra newznab """
|
||||
extra_newznabs = self.EXTRA_NEWZNABS
|
||||
for item in newznab:
|
||||
self.EXTRA_NEWZNABS.append(item)
|
||||
extra_newznabs.append(item)
|
||||
self.EXTRA_NEWZNABS = extra_newznabs
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
|
||||
+28
-19
@@ -69,6 +69,7 @@ WAVE_FILE_TYPE_BY_EXTENSION = {
|
||||
#SHNTOOL_COMPATIBLE = ('Waveform Audio', 'WavPack', 'Free Lossless Audio Codec')
|
||||
SHNTOOL_COMPATIBLE = ('Free Lossless Audio Codec')
|
||||
|
||||
|
||||
def check_splitter(command):
|
||||
'''Check xld or shntools installed'''
|
||||
try:
|
||||
@@ -82,6 +83,7 @@ def check_splitter(command):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def split_baby(split_file, split_cmd):
|
||||
'''Let's split baby'''
|
||||
logger.info('Splitting %s...', split_file.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
@@ -115,6 +117,7 @@ def split_baby(split_file, split_cmd):
|
||||
logger.info('Split success %s', split_file.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
return True
|
||||
|
||||
|
||||
def check_list(list, ignore=0):
|
||||
'''Checks a list for None elements. If list have None (after ignore index) then it should pass only if all elements
|
||||
are None threreafter. Returns a tuple without the None entries.'''
|
||||
@@ -146,12 +149,14 @@ def check_list(list, ignore=0):
|
||||
|
||||
return tuple(list1+list2)
|
||||
|
||||
|
||||
def trim_cue_entry(string):
|
||||
'''Removes leading and trailing "s.'''
|
||||
if string[0] == '"' and string[-1] == '"':
|
||||
string = string[1:-1]
|
||||
return string
|
||||
|
||||
|
||||
def int_to_str(value, length=2):
|
||||
'''Converts integer to string eg 3 to "03"'''
|
||||
try:
|
||||
@@ -164,6 +169,7 @@ def int_to_str(value, length=2):
|
||||
content = '0' + content
|
||||
return content
|
||||
|
||||
|
||||
def split_file_list(ext=None):
|
||||
file_list = [None for m in range(100)]
|
||||
if ext and ext[0] != '.':
|
||||
@@ -192,7 +198,7 @@ class Directory:
|
||||
if c.__class__.__name__ == classname:
|
||||
content.append(c)
|
||||
return content
|
||||
|
||||
|
||||
def tracks(self, ext=None, split=False):
|
||||
content = []
|
||||
for c in self.content:
|
||||
@@ -204,14 +210,14 @@ class Directory:
|
||||
if not split or (split and c.split_file):
|
||||
content.append(c)
|
||||
return content
|
||||
|
||||
|
||||
def update(self):
|
||||
def check_match(filename):
|
||||
for i in self.content:
|
||||
if i.name == filename:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def identify_track_number(filename):
|
||||
if 'split-track' in filename:
|
||||
search = re.search('split-track(\d\d)', filename)
|
||||
@@ -219,14 +225,14 @@ class Directory:
|
||||
n = int(search.group(1))
|
||||
if n:
|
||||
return n
|
||||
for n in range(0,100):
|
||||
for n in range(0, 100):
|
||||
search = re.search(int_to_str(n), filename)
|
||||
if search:
|
||||
# TODO: not part of other value such as year
|
||||
return n
|
||||
|
||||
list_dir = glob.glob(os.path.join(self.path, '*'))
|
||||
|
||||
|
||||
# TODO: for some reason removes only one file
|
||||
rem_list = []
|
||||
for i in self.content:
|
||||
@@ -234,7 +240,7 @@ class Directory:
|
||||
rem_list.append(i)
|
||||
for i in rem_list:
|
||||
self.content.remove(i)
|
||||
|
||||
|
||||
for i in list_dir:
|
||||
if not check_match(i):
|
||||
# music file
|
||||
@@ -244,7 +250,7 @@ class Directory:
|
||||
self.content.append(WaveFile(self.path + os.sep + i, track_nr=track_nr))
|
||||
else:
|
||||
self.content.append(WaveFile(self.path + os.sep + i))
|
||||
|
||||
|
||||
# cue file
|
||||
elif os.path.splitext(i)[-1] == '.cue':
|
||||
self.content.append(CueFile(self.path + os.sep + i))
|
||||
@@ -252,14 +258,15 @@ class Directory:
|
||||
# meta file
|
||||
elif i == ALBUM_META_FILE_NAME:
|
||||
self.content.append(MetaFile(self.path + os.sep + i))
|
||||
|
||||
|
||||
# directory
|
||||
elif os.path.isdir(i):
|
||||
self.content.append(Directory(self.path + os.sep + i))
|
||||
|
||||
|
||||
else:
|
||||
self.content.append(File(self.path + os.sep + i))
|
||||
|
||||
|
||||
class File:
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
@@ -285,6 +292,7 @@ class File:
|
||||
|
||||
return content
|
||||
|
||||
|
||||
class CueFile(File):
|
||||
def __init__(self, path):
|
||||
|
||||
@@ -434,6 +442,7 @@ class CueFile(File):
|
||||
content += '\n'
|
||||
return content
|
||||
|
||||
|
||||
class MetaFile(File):
|
||||
def __init__(self, path):
|
||||
File.__init__(self, path)
|
||||
@@ -442,7 +451,7 @@ class MetaFile(File):
|
||||
|
||||
content = {}
|
||||
content['tracks'] = [None for m in range(100)]
|
||||
|
||||
|
||||
for l in self.rawcontent.splitlines():
|
||||
parsed_line = re.search('^(.+?)\t(.+?)$', l)
|
||||
if parsed_line:
|
||||
@@ -455,11 +464,11 @@ class MetaFile(File):
|
||||
content['tracks'][int(parsed_track.group(1))][parsed_track.group(2)] = parsed_line.group(2)
|
||||
else:
|
||||
content[parsed_line.group(1)] = parsed_line.group(2)
|
||||
|
||||
|
||||
content['tracks'] = check_list(content['tracks'], ignore=1)
|
||||
|
||||
|
||||
self.content = content
|
||||
|
||||
|
||||
def flac_tags(self, track_nr):
|
||||
common_tags = dict()
|
||||
freeform_tags = dict()
|
||||
@@ -483,9 +492,9 @@ class MetaFile(File):
|
||||
|
||||
def folders(self):
|
||||
artist = self.content['artist']
|
||||
album = self.content['date'] + ' - ' + self.content['title'] + ' (' + self.content['label'] + ' - ' + self.content['catalog'] + ')'
|
||||
album = self.content['date'] + ' - ' + self.content['title'] + ' (' + self.content['label'] + ' - ' + self.content['catalog'] + ')'
|
||||
return artist, album
|
||||
|
||||
|
||||
def complete(self):
|
||||
'''Check MetaFile for containing all data'''
|
||||
self.__init__(self.path)
|
||||
@@ -493,11 +502,12 @@ class MetaFile(File):
|
||||
if re.search('^[0-9A-Za-z]+?\t$', l):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def count_tracks(self):
|
||||
'''Returns tracks count'''
|
||||
return len(self.content['tracks']) - self.content['tracks'].count(None)
|
||||
|
||||
|
||||
class WaveFile(File):
|
||||
def __init__(self, path, track_nr=None):
|
||||
File.__init__(self, path)
|
||||
@@ -508,7 +518,7 @@ class WaveFile(File):
|
||||
def filename(self, ext=None, cmd=False):
|
||||
title = meta.content['tracks'][self.track_nr]['title']
|
||||
|
||||
if ext:
|
||||
if ext:
|
||||
if ext[0] != '.':
|
||||
ext = '.' + ext
|
||||
else:
|
||||
@@ -537,6 +547,7 @@ class WaveFile(File):
|
||||
if self.type == 'Free Lossless Audio Codec':
|
||||
return FLAC(self.name)
|
||||
|
||||
|
||||
def split(albumpath):
|
||||
|
||||
os.chdir(albumpath)
|
||||
@@ -662,5 +673,3 @@ def split(albumpath):
|
||||
# Rename original file
|
||||
os.rename(wave.name, wave.name + '.original')
|
||||
return True
|
||||
|
||||
|
||||
|
||||
+8
-5
@@ -28,10 +28,12 @@ import headphones
|
||||
|
||||
from headphones import logger
|
||||
|
||||
|
||||
def dbFilename(filename="headphones.db"):
|
||||
|
||||
return os.path.join(headphones.DATA_DIR, filename)
|
||||
|
||||
|
||||
def getCacheSize():
|
||||
#this will protect against typecasting problems produced by empty string and None settings
|
||||
if not headphones.CONFIG.CACHE_SIZEMB:
|
||||
@@ -39,6 +41,7 @@ def getCacheSize():
|
||||
return 0
|
||||
return int(headphones.CONFIG.CACHE_SIZEMB)
|
||||
|
||||
|
||||
class DBConnection:
|
||||
|
||||
def __init__(self, filename="headphones.db"):
|
||||
@@ -59,14 +62,14 @@ class DBConnection:
|
||||
return
|
||||
|
||||
sqlResult = None
|
||||
|
||||
|
||||
try:
|
||||
with self.connection as c:
|
||||
if args == None:
|
||||
sqlResult = c.execute(query)
|
||||
else:
|
||||
sqlResult = c.execute(query, args)
|
||||
|
||||
|
||||
except sqlite3.OperationalError, e:
|
||||
if "unable to open database file" in e.message or "database is locked" in e.message:
|
||||
logger.warn('Database Error: %s', e)
|
||||
@@ -77,13 +80,13 @@ class DBConnection:
|
||||
except sqlite3.DatabaseError, e:
|
||||
logger.error('Fatal Error executing %s :: %s', query, e)
|
||||
raise
|
||||
|
||||
|
||||
return sqlResult
|
||||
|
||||
def select(self, query, args=None):
|
||||
|
||||
sqlResults = self.action(query, args).fetchall()
|
||||
|
||||
|
||||
if sqlResults == None or sqlResults == [None]:
|
||||
return []
|
||||
|
||||
@@ -93,7 +96,7 @@ class DBConnection:
|
||||
|
||||
changesBefore = self.connection.total_changes
|
||||
|
||||
genParams = lambda myDict : [x + " = ?" for x in myDict.keys()]
|
||||
genParams = lambda myDict: [x + " = ?" for x in myDict.keys()]
|
||||
|
||||
query = "UPDATE "+tableName+" SET " + ", ".join(genParams(valueDict)) + " WHERE " + " AND ".join(genParams(keyDict))
|
||||
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class HeadphonesException(Exception):
|
||||
"""
|
||||
Generic Headphones Exception - should never be thrown, only subclassed
|
||||
"""
|
||||
|
||||
|
||||
class NewzbinAPIThrottled(HeadphonesException):
|
||||
"""
|
||||
Newzbin has throttled us, deal with it
|
||||
|
||||
@@ -5,6 +5,7 @@ import xml.parsers.expat as expat
|
||||
import commands
|
||||
from headphones import logger
|
||||
|
||||
|
||||
def getXldProfile(xldProfile):
|
||||
xldProfileNotFound = xldProfile
|
||||
expandedPath = os.path.expanduser('~/Library/Preferences/jp.tmkk.XLD.plist')
|
||||
@@ -178,4 +179,4 @@ def getXldProfile(xldProfile):
|
||||
|
||||
return(xldProfileForCmd, xldFormat, xldBitrate)
|
||||
|
||||
return(xldProfileNotFound, None, None)
|
||||
return(xldProfileNotFound, None, None)
|
||||
|
||||
+64
-32
@@ -31,6 +31,7 @@ RE_FEATURING = re.compile(r"[fF]t\.|[fF]eaturing|[fF]eat\.|\b[wW]ith\b|&|vs\.")
|
||||
RE_CD_ALBUM = re.compile(r"\(?((CD|disc)\s*[0-9]+)\)?", re.I)
|
||||
RE_CD = re.compile(r"^(CD|dics)\s*[0-9]+$", re.I)
|
||||
|
||||
|
||||
def multikeysort(items, columns):
|
||||
comparers = [ ((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1)) for col in columns]
|
||||
|
||||
@@ -44,12 +45,14 @@ def multikeysort(items, columns):
|
||||
|
||||
return sorted(items, cmp=comparer)
|
||||
|
||||
|
||||
def checked(variable):
|
||||
if variable:
|
||||
return 'Checked'
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
def radio(variable, pos):
|
||||
|
||||
if variable == pos:
|
||||
@@ -57,35 +60,36 @@ def radio(variable, pos):
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
def latinToAscii(unicrap):
|
||||
"""
|
||||
From couch potato
|
||||
"""
|
||||
xlate = {0xc0:'A', 0xc1:'A', 0xc2:'A', 0xc3:'A', 0xc4:'A', 0xc5:'A',
|
||||
0xc6:'Ae', 0xc7:'C',
|
||||
0xc8:'E', 0xc9:'E', 0xca:'E', 0xcb:'E', 0x86:'e',
|
||||
0xcc:'I', 0xcd:'I', 0xce:'I', 0xcf:'I',
|
||||
0xd0:'Th', 0xd1:'N',
|
||||
0xd2:'O', 0xd3:'O', 0xd4:'O', 0xd5:'O', 0xd6:'O', 0xd8:'O',
|
||||
0xd9:'U', 0xda:'U', 0xdb:'U', 0xdc:'U',
|
||||
0xdd:'Y', 0xde:'th', 0xdf:'ss',
|
||||
0xe0:'a', 0xe1:'a', 0xe2:'a', 0xe3:'a', 0xe4:'a', 0xe5:'a',
|
||||
0xe6:'ae', 0xe7:'c',
|
||||
0xe8:'e', 0xe9:'e', 0xea:'e', 0xeb:'e', 0x0259:'e',
|
||||
0xec:'i', 0xed:'i', 0xee:'i', 0xef:'i',
|
||||
0xf0:'th', 0xf1:'n',
|
||||
0xf2:'o', 0xf3:'o', 0xf4:'o', 0xf5:'o', 0xf6:'o', 0xf8:'o',
|
||||
0xf9:'u', 0xfa:'u', 0xfb:'u', 0xfc:'u',
|
||||
0xfd:'y', 0xfe:'th', 0xff:'y',
|
||||
0xa1:'!', 0xa2:'{cent}', 0xa3:'{pound}', 0xa4:'{currency}',
|
||||
0xa5:'{yen}', 0xa6:'|', 0xa7:'{section}', 0xa8:'{umlaut}',
|
||||
0xa9:'{C}', 0xaa:'{^a}', 0xab:'<<', 0xac:'{not}',
|
||||
0xad:'-', 0xae:'{R}', 0xaf:'_', 0xb0:'{degrees}',
|
||||
0xb1:'{+/-}', 0xb2:'{^2}', 0xb3:'{^3}', 0xb4:"'",
|
||||
0xb5:'{micro}', 0xb6:'{paragraph}', 0xb7:'*', 0xb8:'{cedilla}',
|
||||
0xb9:'{^1}', 0xba:'{^o}', 0xbb:'>>',
|
||||
0xbc:'{1/4}', 0xbd:'{1/2}', 0xbe:'{3/4}', 0xbf:'?',
|
||||
0xd7:'*', 0xf7:'/'
|
||||
xlate = {0xc0: 'A', 0xc1: 'A', 0xc2: 'A', 0xc3: 'A', 0xc4: 'A', 0xc5: 'A',
|
||||
0xc6: 'Ae', 0xc7: 'C',
|
||||
0xc8: 'E', 0xc9: 'E', 0xca: 'E', 0xcb: 'E', 0x86: 'e',
|
||||
0xcc: 'I', 0xcd: 'I', 0xce: 'I', 0xcf: 'I',
|
||||
0xd0: 'Th', 0xd1: 'N',
|
||||
0xd2: 'O', 0xd3: 'O', 0xd4: 'O', 0xd5: 'O', 0xd6: 'O', 0xd8: 'O',
|
||||
0xd9: 'U', 0xda: 'U', 0xdb: 'U', 0xdc: 'U',
|
||||
0xdd: 'Y', 0xde: 'th', 0xdf: 'ss',
|
||||
0xe0: 'a', 0xe1: 'a', 0xe2: 'a', 0xe3: 'a', 0xe4: 'a', 0xe5: 'a',
|
||||
0xe6: 'ae', 0xe7: 'c',
|
||||
0xe8: 'e', 0xe9: 'e', 0xea: 'e', 0xeb: 'e', 0x0259: 'e',
|
||||
0xec: 'i', 0xed: 'i', 0xee: 'i', 0xef: 'i',
|
||||
0xf0: 'th', 0xf1: 'n',
|
||||
0xf2: 'o', 0xf3: 'o', 0xf4: 'o', 0xf5: 'o', 0xf6: 'o', 0xf8: 'o',
|
||||
0xf9: 'u', 0xfa: 'u', 0xfb: 'u', 0xfc: 'u',
|
||||
0xfd: 'y', 0xfe: 'th', 0xff: 'y',
|
||||
0xa1: '!', 0xa2: '{cent}', 0xa3: '{pound}', 0xa4: '{currency}',
|
||||
0xa5: '{yen}', 0xa6: '|', 0xa7: '{section}', 0xa8: '{umlaut}',
|
||||
0xa9: '{C}', 0xaa: '{^a}', 0xab: '<<', 0xac: '{not}',
|
||||
0xad: '-', 0xae: '{R}', 0xaf: '_', 0xb0: '{degrees}',
|
||||
0xb1: '{+/-}', 0xb2: '{^2}', 0xb3: '{^3}', 0xb4: "'",
|
||||
0xb5: '{micro}', 0xb6: '{paragraph}', 0xb7: '*', 0xb8: '{cedilla}',
|
||||
0xb9: '{^1}', 0xba: '{^o}', 0xbb: '>>',
|
||||
0xbc: '{1/4}', 0xbd: '{1/2}', 0xbe: '{3/4}', 0xbf: '?',
|
||||
0xd7: '*', 0xf7: '/'
|
||||
}
|
||||
|
||||
r = ''
|
||||
@@ -98,6 +102,7 @@ def latinToAscii(unicrap):
|
||||
r += str(i)
|
||||
return r
|
||||
|
||||
|
||||
def convert_milliseconds(ms):
|
||||
|
||||
seconds = ms/1000
|
||||
@@ -109,6 +114,7 @@ def convert_milliseconds(ms):
|
||||
|
||||
return minutes
|
||||
|
||||
|
||||
def convert_seconds(s):
|
||||
|
||||
gmtime = time.gmtime(s)
|
||||
@@ -119,15 +125,18 @@ def convert_seconds(s):
|
||||
|
||||
return minutes
|
||||
|
||||
|
||||
def today():
|
||||
today = datetime.date.today()
|
||||
yyyymmdd = datetime.date.isoformat(today)
|
||||
return yyyymmdd
|
||||
|
||||
|
||||
def now():
|
||||
now = datetime.datetime.now()
|
||||
return now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def get_age(date):
|
||||
|
||||
try:
|
||||
@@ -142,17 +151,20 @@ def get_age(date):
|
||||
|
||||
return days_old
|
||||
|
||||
|
||||
def bytes_to_mb(bytes):
|
||||
|
||||
mb = int(bytes)/1048576
|
||||
size = '%.1f MB' % mb
|
||||
return size
|
||||
|
||||
|
||||
def mb_to_bytes(mb_str):
|
||||
result = re.search('^(\d+(?:\.\d+)?)\s?(?:mb)?', mb_str, flags=re.I)
|
||||
if result:
|
||||
return int(float(result.group(1))*1048576)
|
||||
|
||||
|
||||
def piratesize(size):
|
||||
split = size.split(" ")
|
||||
factor = float(split[0])
|
||||
@@ -170,6 +182,7 @@ def piratesize(size):
|
||||
|
||||
return size
|
||||
|
||||
|
||||
def replace_all(text, dic, normalize=False):
|
||||
|
||||
if not text:
|
||||
@@ -187,6 +200,7 @@ def replace_all(text, dic, normalize=False):
|
||||
text = text.replace(i, j)
|
||||
return text
|
||||
|
||||
|
||||
def replace_illegal_chars(string, type="file"):
|
||||
if type == "file":
|
||||
string = re.sub('[\?"*:|<>/]', '_', string)
|
||||
@@ -195,6 +209,7 @@ def replace_illegal_chars(string, type="file"):
|
||||
|
||||
return string
|
||||
|
||||
|
||||
def cleanName(string):
|
||||
|
||||
pass1 = latinToAscii(string).lower()
|
||||
@@ -202,6 +217,7 @@ def cleanName(string):
|
||||
|
||||
return out_string
|
||||
|
||||
|
||||
def cleanTitle(title):
|
||||
|
||||
title = re.sub('[\.\-\/\_]', ' ', title).lower()
|
||||
@@ -213,6 +229,7 @@ def cleanTitle(title):
|
||||
|
||||
return title
|
||||
|
||||
|
||||
def split_path(f):
|
||||
"""
|
||||
Split a path into components, starting with the drive letter (if any). Given
|
||||
@@ -244,6 +261,7 @@ def split_path(f):
|
||||
# Done
|
||||
return components
|
||||
|
||||
|
||||
def expand_subfolders(f):
|
||||
"""
|
||||
Try to expand a given folder and search for subfolders containing media
|
||||
@@ -310,6 +328,7 @@ def expand_subfolders(f):
|
||||
logger.debug("Expanded subfolders in folder: %s", media_folders)
|
||||
return media_folders
|
||||
|
||||
|
||||
def extract_data(s):
|
||||
|
||||
s = s.replace('_', ' ')
|
||||
@@ -337,6 +356,7 @@ def extract_data(s):
|
||||
else:
|
||||
return (None, None, None)
|
||||
|
||||
|
||||
def extract_metadata(f):
|
||||
"""
|
||||
Scan all files in the given directory and decide on an artist, album and
|
||||
@@ -435,6 +455,7 @@ def extract_metadata(f):
|
||||
|
||||
return (None, None, None)
|
||||
|
||||
|
||||
def get_downloaded_track_list(albumpath):
|
||||
"""
|
||||
Return a list of audio files for the given directory.
|
||||
@@ -449,6 +470,7 @@ def get_downloaded_track_list(albumpath):
|
||||
|
||||
return downloaded_track_list
|
||||
|
||||
|
||||
def preserve_torrent_direcory(albumpath):
|
||||
"""
|
||||
Copy torrent directory to headphones-modified to keep files for seeding.
|
||||
@@ -465,6 +487,7 @@ def preserve_torrent_direcory(albumpath):
|
||||
". Not continuing. Error: " + str(e))
|
||||
return None
|
||||
|
||||
|
||||
def cue_split(albumpath):
|
||||
"""
|
||||
Attempts to check and split audio files by a cue for the given directory.
|
||||
@@ -504,6 +527,7 @@ def cue_split(albumpath):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def extract_logline(s):
|
||||
# Default log format
|
||||
pattern = re.compile(r'(?P<timestamp>.*?)\s\-\s(?P<level>.*?)\s*\:\:\s(?P<thread>.*?)\s\:\s(?P<message>.*)', re.VERBOSE)
|
||||
@@ -517,6 +541,7 @@ def extract_logline(s):
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def extract_song_data(s):
|
||||
|
||||
#headphones default format
|
||||
@@ -548,6 +573,7 @@ def extract_song_data(s):
|
||||
logger.info("Couldn't parse %s into a valid Newbin format", s)
|
||||
return (name, album, year)
|
||||
|
||||
|
||||
def smartMove(src, dest, delete=True):
|
||||
|
||||
from headphones import logger
|
||||
@@ -588,32 +614,36 @@ def smartMove(src, dest, delete=True):
|
||||
|
||||
# TODO: Grab config values from sab to know when these options are checked. For now we'll just iterate through all combinations
|
||||
|
||||
|
||||
def sab_replace_dots(name):
|
||||
return name.replace('.',' ')
|
||||
return name.replace('.', ' ')
|
||||
|
||||
|
||||
def sab_replace_spaces(name):
|
||||
return name.replace(' ','_')
|
||||
return name.replace(' ', '_')
|
||||
|
||||
|
||||
def sab_sanitize_foldername(name):
|
||||
""" Return foldername with dodgy chars converted to safe ones
|
||||
Remove any leading and trailing dot and space characters
|
||||
"""
|
||||
CH_ILLEGAL = r'\/<>?*|"'
|
||||
CH_LEGAL = r'++{}!@#`'
|
||||
CH_LEGAL = r'++{}!@#`'
|
||||
|
||||
FL_ILLEGAL = CH_ILLEGAL + ':\x92"'
|
||||
FL_LEGAL = CH_LEGAL + "-''"
|
||||
FL_LEGAL = CH_LEGAL + "-''"
|
||||
|
||||
uFL_ILLEGAL = FL_ILLEGAL.decode('latin-1')
|
||||
uFL_LEGAL = FL_LEGAL.decode('latin-1')
|
||||
uFL_LEGAL = FL_LEGAL.decode('latin-1')
|
||||
|
||||
if not name:
|
||||
return name
|
||||
if isinstance(name, unicode):
|
||||
illegal = uFL_ILLEGAL
|
||||
legal = uFL_LEGAL
|
||||
legal = uFL_LEGAL
|
||||
else:
|
||||
illegal = FL_ILLEGAL
|
||||
legal = FL_LEGAL
|
||||
legal = FL_LEGAL
|
||||
|
||||
lst = []
|
||||
for ch in name.strip():
|
||||
@@ -634,12 +664,14 @@ def sab_sanitize_foldername(name):
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def split_string(mystring, splitvar=','):
|
||||
mylist = []
|
||||
for each_word in mystring.split(splitvar):
|
||||
mylist.append(each_word.strip())
|
||||
return mylist
|
||||
|
||||
|
||||
def create_https_certificates(ssl_cert, ssl_key):
|
||||
"""
|
||||
Stolen from SickBeard (http://github.com/midgetspy/Sick-Beard):
|
||||
|
||||
+18
-14
@@ -23,7 +23,7 @@ import threading
|
||||
import headphones
|
||||
|
||||
blacklisted_special_artist_names = ['[anonymous]', '[data]', '[no artist]',
|
||||
'[traditional]','[unknown]','Various Artists']
|
||||
'[traditional]', '[unknown]', 'Various Artists']
|
||||
blacklisted_special_artists = ['f731ccc4-e22a-43af-a747-64213329e088',
|
||||
'33cf029c-63b0-41a0-9855-be2a3665fb3b',
|
||||
'314e1c25-dde7-4e4d-b2f4-0a7b9f7c56dc',
|
||||
@@ -32,6 +32,7 @@ blacklisted_special_artists = ['f731ccc4-e22a-43af-a747-64213329e088',
|
||||
'125ec42a-7229-4250-afc5-e057484327fe',
|
||||
'89ad4ac3-39f7-470e-963a-56509c546377']
|
||||
|
||||
|
||||
def is_exists(artistid):
|
||||
myDB = db.DBConnection()
|
||||
|
||||
@@ -52,7 +53,6 @@ def artistlist_to_mbids(artistlist, forced=False):
|
||||
if not artist and not (artist == ' '):
|
||||
continue
|
||||
|
||||
|
||||
# If adding artists through Manage New Artists, they're coming through as non-unicode (utf-8?)
|
||||
# and screwing everything up
|
||||
if not isinstance(artist, unicode):
|
||||
@@ -105,12 +105,14 @@ def artistlist_to_mbids(artistlist, forced=False):
|
||||
except Exception as e:
|
||||
logger.warn('Failed to update arist information from Last.fm: %s' % e)
|
||||
|
||||
|
||||
def addArtistIDListToDB(artistidlist):
|
||||
# Used to add a list of artist IDs to the database in a single thread
|
||||
logger.debug("Importer: Adding artist ids %s" % artistidlist)
|
||||
for artistid in artistidlist:
|
||||
addArtisttoDB(artistid)
|
||||
|
||||
|
||||
def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
|
||||
# Putting this here to get around the circular import. We're using this to update thumbnails for artist/albums
|
||||
@@ -172,7 +174,6 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
else:
|
||||
sortname = artist['artist_name']
|
||||
|
||||
|
||||
logger.info(u"Now adding/updating: " + artist['artist_name'])
|
||||
controlValueDict = {"ArtistID": artistid}
|
||||
newValueDict = {"ArtistName": artist['artist_name'],
|
||||
@@ -240,15 +241,14 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
check_release_date = None
|
||||
new_release_group = True
|
||||
|
||||
|
||||
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)
|
||||
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)
|
||||
new_releases = mb.get_new_releases(rgid, includeExtras, True)
|
||||
else:
|
||||
if len(check_release_date) == 10:
|
||||
release_date = check_release_date
|
||||
@@ -260,7 +260,7 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
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)
|
||||
new_releases = mb.get_new_releases(rgid,includeExtras,True)
|
||||
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)
|
||||
skip_log = 1
|
||||
@@ -273,7 +273,7 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
new_releases = new_releases
|
||||
else:
|
||||
logger.info("[%s] Now adding/updating: %s (Comprehensive Force)", artist['artist_name'], rg['title'])
|
||||
new_releases = mb.get_new_releases(rgid,includeExtras,forcefull)
|
||||
new_releases = mb.get_new_releases(rgid, includeExtras, forcefull)
|
||||
|
||||
if new_releases != 0:
|
||||
# Dump existing hybrid release since we're repackaging/replacing it
|
||||
@@ -325,7 +325,7 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
logger.info('[%s] Packaging %s releases into hybrid title' % (artist['artist_name'], rg['title']))
|
||||
except Exception as e:
|
||||
errors = True
|
||||
logger.warn('[%s] Unable to get hybrid release information for %s: %s' % (artist['artist_name'],rg['title'],e))
|
||||
logger.warn('[%s] Unable to get hybrid release information for %s: %s' % (artist['artist_name'], rg['title'], e))
|
||||
continue
|
||||
|
||||
# Use the ReleaseGroupID as the ReleaseID for the hybrid release to differentiate it
|
||||
@@ -407,7 +407,7 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
|
||||
if rg_exists:
|
||||
newValueDict['DateAdded'] = rg_exists['DateAdded']
|
||||
newValueDict['Status'] = rg_exists['Status']
|
||||
newValueDict['Status'] = rg_exists['Status']
|
||||
|
||||
else:
|
||||
today = helpers.today()
|
||||
@@ -504,6 +504,7 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
for album_search in album_searches:
|
||||
searcher.searchforalbum(albumid=album_search)
|
||||
|
||||
|
||||
def finalize_update(artistid, artistname, errors=False):
|
||||
# Moving this little bit to it's own function so we can update have tracks & latest album when deleting extras
|
||||
|
||||
@@ -533,6 +534,7 @@ def finalize_update(artistid, artistname, errors=False):
|
||||
|
||||
myDB.upsert("artists", newValueDict, controlValueDict)
|
||||
|
||||
|
||||
def addReleaseById(rid, rgid=None):
|
||||
|
||||
myDB = db.DBConnection()
|
||||
@@ -689,6 +691,7 @@ def addReleaseById(rid, rgid=None):
|
||||
else:
|
||||
logger.info('Release ' + str(rid) + " already exists in the database!")
|
||||
|
||||
|
||||
def updateFormat():
|
||||
myDB = db.DBConnection()
|
||||
tracks = myDB.select('SELECT * from tracks WHERE Location IS NOT NULL and Format IS NULL')
|
||||
@@ -718,6 +721,7 @@ def updateFormat():
|
||||
myDB.upsert("have", newValueDict, controlValueDict)
|
||||
logger.info('Finished finding media format for %s files' % len(havetracks))
|
||||
|
||||
|
||||
def getHybridRelease(fullreleaselist):
|
||||
"""
|
||||
Returns a dictionary of best group of tracks from the list of releases and
|
||||
@@ -786,7 +790,7 @@ def getHybridRelease(fullreleaselist):
|
||||
else:
|
||||
return releaseDate + '13-32'
|
||||
|
||||
sortable_release_list.sort(key=lambda x:getSortableReleaseDate(x['releasedate']))
|
||||
sortable_release_list.sort(key=lambda x: getSortableReleaseDate(x['releasedate']))
|
||||
|
||||
average_tracks = sum(x['trackscount'] for x in sortable_release_list) / float(len(sortable_release_list))
|
||||
for item in sortable_release_list:
|
||||
@@ -794,9 +798,9 @@ def getHybridRelease(fullreleaselist):
|
||||
|
||||
a = helpers.multikeysort(sortable_release_list, ['-hasasin', 'country', 'format', 'trackscount_delta'])
|
||||
|
||||
release_dict = {'ReleaseDate' : sortable_release_list[0]['releasedate'],
|
||||
'Tracks' : a[0]['tracks'],
|
||||
'AlbumASIN' : a[0]['asin']
|
||||
release_dict = {'ReleaseDate': sortable_release_list[0]['releasedate'],
|
||||
'Tracks': a[0]['tracks'],
|
||||
'AlbumASIN': a[0]['asin']
|
||||
}
|
||||
|
||||
return release_dict
|
||||
|
||||
@@ -30,6 +30,7 @@ API_KEY = "395e6ec6bb557382fc41fde867bce66f"
|
||||
# Required for API request limit
|
||||
lock = threading.Lock()
|
||||
|
||||
|
||||
def request_lastfm(method, **kwargs):
|
||||
"""
|
||||
Call a Last.FM API method. Automatically sets the method and API key. Method
|
||||
@@ -62,6 +63,7 @@ def request_lastfm(method, **kwargs):
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def getSimilar():
|
||||
myDB = db.DBConnection()
|
||||
results = myDB.select("SELECT ArtistID from artists ORDER BY HaveTracks DESC")
|
||||
@@ -107,6 +109,7 @@ def getSimilar():
|
||||
|
||||
logger.debug("Inserted %d artists into Last.FM tag cloud", len(top_list))
|
||||
|
||||
|
||||
def getArtists():
|
||||
myDB = db.DBConnection()
|
||||
results = myDB.select("SELECT ArtistID from artists")
|
||||
@@ -136,6 +139,7 @@ def getArtists():
|
||||
|
||||
logger.info("Imported %d new artists from Last.FM", len(artistlist))
|
||||
|
||||
|
||||
def getTagTopArtists(tag, limit=50):
|
||||
myDB = db.DBConnection()
|
||||
results = myDB.select("SELECT ArtistID from artists")
|
||||
@@ -159,4 +163,4 @@ def getTagTopArtists(tag, limit=50):
|
||||
for artistid in artistlist:
|
||||
importer.addArtisttoDB(artistid)
|
||||
|
||||
logger.debug("Added %d new artists from Last.FM", len(artistlist))
|
||||
logger.debug("Added %d new artists from Last.FM", len(artistlist))
|
||||
|
||||
+61
-60
@@ -22,9 +22,10 @@ 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
|
||||
def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=False):
|
||||
|
||||
|
||||
def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=False):
|
||||
|
||||
if cron and not headphones.CONFIG.LIBRARYSCAN:
|
||||
return
|
||||
|
||||
@@ -78,7 +79,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
|
||||
latest_subdirectory = []
|
||||
|
||||
for r,d,f in os.walk(dir, followlinks=True):
|
||||
for r, d, f in os.walk(dir, followlinks=True):
|
||||
# Need to abuse slicing to get a copy of the list, doing it directly
|
||||
# will skip the element after a deleted one using a list comprehension
|
||||
# will not work correctly for nested subdirectories (os.walk keeps its
|
||||
@@ -91,11 +92,11 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
# MEDIA_FORMATS = music file extensions, e.g. mp3, flac, etc
|
||||
if any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
||||
|
||||
subdirectory = r.replace(dir,'')
|
||||
subdirectory = r.replace(dir, '')
|
||||
latest_subdirectory.append(subdirectory)
|
||||
if file_count == 0 and r.replace(dir,'') !='':
|
||||
if file_count == 0 and r.replace(dir, '') != '':
|
||||
logger.info("[%s] Now scanning subdirectory %s" % (dir.decode(headphones.SYS_ENCODING, 'replace'), subdirectory.decode(headphones.SYS_ENCODING, 'replace')))
|
||||
elif latest_subdirectory[file_count] != latest_subdirectory[file_count-1] and file_count !=0:
|
||||
elif latest_subdirectory[file_count] != latest_subdirectory[file_count-1] and file_count != 0:
|
||||
logger.info("[%s] Now scanning subdirectory %s" % (dir.decode(headphones.SYS_ENCODING, 'replace'), subdirectory.decode(headphones.SYS_ENCODING, 'replace')))
|
||||
|
||||
song = os.path.join(r, files)
|
||||
@@ -129,24 +130,24 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
# TODO: skip adding songs without the minimum requisite information (just a matter of putting together the right if statements)
|
||||
|
||||
if f_artist and f.album and f.title:
|
||||
CleanName = helpers.cleanName(f_artist +' '+ f.album +' '+ f.title)
|
||||
CleanName = helpers.cleanName(f_artist + ' ' + f.album + ' ' + f.title)
|
||||
else:
|
||||
CleanName = None
|
||||
|
||||
controlValueDict = {'Location' : unicode_song_path}
|
||||
controlValueDict = {'Location': unicode_song_path}
|
||||
|
||||
newValueDict = { 'TrackID' : f.mb_trackid,
|
||||
newValueDict = { 'TrackID': f.mb_trackid,
|
||||
#'ReleaseID' : f.mb_albumid,
|
||||
'ArtistName' : f_artist,
|
||||
'AlbumTitle' : f.album,
|
||||
'ArtistName': f_artist,
|
||||
'AlbumTitle': f.album,
|
||||
'TrackNumber': f.track,
|
||||
'TrackLength': f.length,
|
||||
'Genre' : f.genre,
|
||||
'Date' : f.date,
|
||||
'TrackTitle' : f.title,
|
||||
'BitRate' : f.bitrate,
|
||||
'Format' : f.format,
|
||||
'CleanName' : CleanName
|
||||
'Genre': f.genre,
|
||||
'Date': f.date,
|
||||
'TrackTitle': f.title,
|
||||
'BitRate': f.bitrate,
|
||||
'Format': f.format,
|
||||
'CleanName': CleanName
|
||||
}
|
||||
|
||||
#song_list.append(song_dict)
|
||||
@@ -157,7 +158,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
if f_artist:
|
||||
new_artists.append(f_artist)
|
||||
myDB.upsert("have", newValueDict, controlValueDict)
|
||||
new_song_count+=1
|
||||
new_song_count += 1
|
||||
else:
|
||||
if check_exist_song['ArtistName'] != f_artist or check_exist_song['AlbumTitle'] != f.album or check_exist_song['TrackTitle'] != f.title:
|
||||
#Important track metadata has been modified, need to run matcher again
|
||||
@@ -172,14 +173,13 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
myDB.upsert("have", newValueDict, controlValueDict)
|
||||
myDB.action('UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE Location=?', [None, None, None, unicode_song_path])
|
||||
myDB.action('UPDATE alltracks SET Location=?, BitRate=?, Format=? WHERE Location=?', [None, None, None, unicode_song_path])
|
||||
new_song_count+=1
|
||||
new_song_count += 1
|
||||
else:
|
||||
#This track information hasn't changed
|
||||
if f_artist and check_exist_song['Matched'] != "Ignored":
|
||||
new_artists.append(f_artist)
|
||||
|
||||
file_count+=1
|
||||
|
||||
file_count += 1
|
||||
|
||||
# Now we start track matching
|
||||
logger.info("%s new/modified songs found and added to the database" % new_song_count)
|
||||
@@ -202,13 +202,13 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
latest_artist.append(song['ArtistName'])
|
||||
if song_count == 0:
|
||||
logger.info("Now matching songs by %s" % song['ArtistName'])
|
||||
elif latest_artist[song_count] != latest_artist[song_count-1] and song_count !=0:
|
||||
elif latest_artist[song_count] != latest_artist[song_count-1] and song_count != 0:
|
||||
logger.info("Now matching songs by %s" % song['ArtistName'])
|
||||
|
||||
song_count += 1
|
||||
completion_percentage = float(song_count)/total_number_of_songs * 100
|
||||
|
||||
if completion_percentage%10 == 0:
|
||||
if completion_percentage % 10 == 0:
|
||||
logger.info("Track matching is " + str(completion_percentage) + "% complete")
|
||||
|
||||
#THE "MORE-SPECIFIC" CLAUSES HERE HAVE ALL BEEN REMOVED. WHEN RUNNING A LIBRARY SCAN, THE ONLY CLAUSES THAT
|
||||
@@ -221,79 +221,78 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
track = myDB.action('SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from tracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']]).fetchone()
|
||||
have_updated = False
|
||||
if track:
|
||||
controlValueDict = { 'ArtistName' : track['ArtistName'],
|
||||
'AlbumTitle' : track['AlbumTitle'],
|
||||
'TrackTitle' : track['TrackTitle'] }
|
||||
newValueDict = { 'Location' : song['Location'],
|
||||
'BitRate' : song['BitRate'],
|
||||
'Format' : song['Format'] }
|
||||
controlValueDict = { 'ArtistName': track['ArtistName'],
|
||||
'AlbumTitle': track['AlbumTitle'],
|
||||
'TrackTitle': track['TrackTitle'] }
|
||||
newValueDict = { 'Location': song['Location'],
|
||||
'BitRate': song['BitRate'],
|
||||
'Format': song['Format'] }
|
||||
myDB.upsert("tracks", newValueDict, controlValueDict)
|
||||
|
||||
controlValueDict2 = { 'Location' : song['Location']}
|
||||
newValueDict2 = { 'Matched' : track['AlbumID']}
|
||||
controlValueDict2 = { 'Location': song['Location']}
|
||||
newValueDict2 = { 'Matched': track['AlbumID']}
|
||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||
have_updated = True
|
||||
else:
|
||||
track = myDB.action('SELECT CleanName, AlbumID from tracks WHERE CleanName LIKE ?', [song['CleanName']]).fetchone()
|
||||
if track:
|
||||
controlValueDict = { 'CleanName' : track['CleanName']}
|
||||
newValueDict = { 'Location' : song['Location'],
|
||||
'BitRate' : song['BitRate'],
|
||||
'Format' : song['Format'] }
|
||||
controlValueDict = { 'CleanName': track['CleanName']}
|
||||
newValueDict = { 'Location': song['Location'],
|
||||
'BitRate': song['BitRate'],
|
||||
'Format': song['Format'] }
|
||||
myDB.upsert("tracks", newValueDict, controlValueDict)
|
||||
|
||||
controlValueDict2 = { 'Location' : song['Location']}
|
||||
newValueDict2 = { 'Matched' : track['AlbumID']}
|
||||
controlValueDict2 = { 'Location': song['Location']}
|
||||
newValueDict2 = { 'Matched': track['AlbumID']}
|
||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||
have_updated = True
|
||||
else:
|
||||
controlValueDict2 = { 'Location' : song['Location']}
|
||||
newValueDict2 = { 'Matched' : "Failed"}
|
||||
controlValueDict2 = { 'Location': song['Location']}
|
||||
newValueDict2 = { 'Matched': "Failed"}
|
||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||
have_updated = True
|
||||
|
||||
alltrack = myDB.action('SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from alltracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']]).fetchone()
|
||||
if alltrack:
|
||||
controlValueDict = { 'ArtistName' : alltrack['ArtistName'],
|
||||
'AlbumTitle' : alltrack['AlbumTitle'],
|
||||
'TrackTitle' : alltrack['TrackTitle'] }
|
||||
newValueDict = { 'Location' : song['Location'],
|
||||
'BitRate' : song['BitRate'],
|
||||
'Format' : song['Format'] }
|
||||
controlValueDict = { 'ArtistName': alltrack['ArtistName'],
|
||||
'AlbumTitle': alltrack['AlbumTitle'],
|
||||
'TrackTitle': alltrack['TrackTitle'] }
|
||||
newValueDict = { 'Location': song['Location'],
|
||||
'BitRate': song['BitRate'],
|
||||
'Format': song['Format'] }
|
||||
myDB.upsert("alltracks", newValueDict, controlValueDict)
|
||||
|
||||
controlValueDict2 = { 'Location' : song['Location']}
|
||||
newValueDict2 = { 'Matched' : alltrack['AlbumID']}
|
||||
controlValueDict2 = { 'Location': song['Location']}
|
||||
newValueDict2 = { 'Matched': alltrack['AlbumID']}
|
||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||
else:
|
||||
alltrack = myDB.action('SELECT CleanName, AlbumID from alltracks WHERE CleanName LIKE ?', [song['CleanName']]).fetchone()
|
||||
if alltrack:
|
||||
controlValueDict = { 'CleanName' : alltrack['CleanName']}
|
||||
newValueDict = { 'Location' : song['Location'],
|
||||
'BitRate' : song['BitRate'],
|
||||
'Format' : song['Format'] }
|
||||
controlValueDict = { 'CleanName': alltrack['CleanName']}
|
||||
newValueDict = { 'Location': song['Location'],
|
||||
'BitRate': song['BitRate'],
|
||||
'Format': song['Format'] }
|
||||
myDB.upsert("alltracks", newValueDict, controlValueDict)
|
||||
|
||||
controlValueDict2 = { 'Location' : song['Location']}
|
||||
newValueDict2 = { 'Matched' : alltrack['AlbumID']}
|
||||
controlValueDict2 = { 'Location': song['Location']}
|
||||
newValueDict2 = { 'Matched': alltrack['AlbumID']}
|
||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||
else:
|
||||
# alltracks may not exist if adding album manually, have should only be set to failed if not already updated in tracks
|
||||
if not have_updated:
|
||||
controlValueDict2 = { 'Location' : song['Location']}
|
||||
newValueDict2 = { 'Matched' : "Failed"}
|
||||
controlValueDict2 = { 'Location': song['Location']}
|
||||
newValueDict2 = { 'Matched': "Failed"}
|
||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||
|
||||
else:
|
||||
controlValueDict2 = { 'Location' : song['Location']}
|
||||
newValueDict2 = { 'Matched' : "Failed"}
|
||||
controlValueDict2 = { 'Location': song['Location']}
|
||||
newValueDict2 = { 'Matched': "Failed"}
|
||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||
|
||||
#######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.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
|
||||
|
||||
if not append:
|
||||
logger.info('Updating scanned artist track counts')
|
||||
|
||||
@@ -343,6 +342,8 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
logger.info('Library scan complete')
|
||||
|
||||
#ADDED THIS SECTION TO MARK ALBUMS AS DOWNLOADED IF ARTISTS ARE ADDED EN MASSE BEFORE LIBRARY IS SCANNED
|
||||
|
||||
|
||||
def update_album_status(AlbumID=None):
|
||||
myDB = db.DBConnection()
|
||||
logger.info('Counting matched tracks to mark albums as skipped/downloaded')
|
||||
@@ -355,9 +356,9 @@ def update_album_status(AlbumID=None):
|
||||
total_tracks = 0
|
||||
have_tracks = 0
|
||||
for track in track_counter:
|
||||
total_tracks+=1
|
||||
total_tracks += 1
|
||||
if track['Location']:
|
||||
have_tracks+=1
|
||||
have_tracks += 1
|
||||
if total_tracks != 0:
|
||||
album_completion = float(float(have_tracks) / float(total_tracks)) * 100
|
||||
else:
|
||||
@@ -379,7 +380,7 @@ def update_album_status(AlbumID=None):
|
||||
else:
|
||||
new_album_status = album['Status']
|
||||
|
||||
myDB.upsert("albums", {'Status' : new_album_status}, {'AlbumID' : album['AlbumID']})
|
||||
myDB.upsert("albums", {'Status': new_album_status}, {'AlbumID': album['AlbumID']})
|
||||
if new_album_status != album['Status']:
|
||||
logger.info('Album %s changed to %s' % (album['AlbumTitle'], new_album_status))
|
||||
logger.info('Album status update complete')
|
||||
|
||||
@@ -39,6 +39,7 @@ logger = logging.getLogger("headphones")
|
||||
# Global queue for multiprocessing logging
|
||||
queue = None
|
||||
|
||||
|
||||
class LogListHandler(logging.Handler):
|
||||
"""
|
||||
Log handler for Web UI.
|
||||
@@ -50,6 +51,7 @@ class LogListHandler(logging.Handler):
|
||||
|
||||
headphones.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName))
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def listener():
|
||||
"""
|
||||
@@ -85,6 +87,7 @@ def listener():
|
||||
finally:
|
||||
queue_listener.stop()
|
||||
|
||||
|
||||
def initMultiprocessing():
|
||||
"""
|
||||
Remove all handlers and add QueueHandler on top. This should only be called
|
||||
@@ -108,6 +111,7 @@ def initMultiprocessing():
|
||||
# Change current thread name for log record
|
||||
threading.current_thread().name = multiprocessing.current_process().name
|
||||
|
||||
|
||||
def initLogger(console=False, verbose=False):
|
||||
"""
|
||||
Setup logging for Headphones. It uses the logger instance with the name
|
||||
@@ -163,6 +167,7 @@ def initLogger(console=False, verbose=False):
|
||||
# Install exception hooks
|
||||
initHooks()
|
||||
|
||||
|
||||
def initHooks(global_exceptions=True, thread_exceptions=True, pass_original=True):
|
||||
"""
|
||||
This method installs exception catching mechanisms. Any exception caught
|
||||
@@ -217,4 +222,4 @@ warn = logger.warn
|
||||
error = logger.error
|
||||
debug = logger.debug
|
||||
warning = logger.warning
|
||||
exception = logger.exception
|
||||
exception = logger.exception
|
||||
|
||||
@@ -18,6 +18,7 @@ import htmlentitydefs
|
||||
|
||||
from headphones import logger, request
|
||||
|
||||
|
||||
def getLyrics(artist, song):
|
||||
|
||||
params = { "artist": artist.encode('utf-8'),
|
||||
@@ -60,6 +61,7 @@ def getLyrics(artist, song):
|
||||
|
||||
return lyrics
|
||||
|
||||
|
||||
def convert_html_entities(s):
|
||||
matches = re.findall("&#\d+;", s)
|
||||
if len(matches) > 0:
|
||||
|
||||
+26
-20
@@ -32,6 +32,8 @@ mb_lock = threading.Lock()
|
||||
|
||||
# Quick fix to add mirror switching on the fly. Need to probably return the mbhost & mbport that's
|
||||
# being used, so we can send those values to the log
|
||||
|
||||
|
||||
def startmb():
|
||||
|
||||
mbuser = None
|
||||
@@ -54,7 +56,7 @@ def startmb():
|
||||
else:
|
||||
return False
|
||||
|
||||
musicbrainzngs.set_useragent("headphones","0.0","https://github.com/rembo10/headphones")
|
||||
musicbrainzngs.set_useragent("headphones", "0.0", "https://github.com/rembo10/headphones")
|
||||
musicbrainzngs.set_hostname(mbhost + ":" + str(mbport))
|
||||
if sleepytime == 0:
|
||||
musicbrainzngs.set_rate_limit(False)
|
||||
@@ -67,12 +69,13 @@ def startmb():
|
||||
if not mbuser and mbpass:
|
||||
logger.warn("No username or password set for VIP server")
|
||||
else:
|
||||
musicbrainzngs.hpauth(mbuser,mbpass)
|
||||
musicbrainzngs.hpauth(mbuser, mbpass)
|
||||
|
||||
logger.debug('Using the following server values: MBHost: %s, MBPort: %i, Sleep Interval: %i', mbhost, mbport, sleepytime)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def findArtist(name, limit=1):
|
||||
|
||||
with mb_lock:
|
||||
@@ -123,6 +126,7 @@ def findArtist(name, limit=1):
|
||||
})
|
||||
return artistlist
|
||||
|
||||
|
||||
def findRelease(name, limit=1, artist=None):
|
||||
|
||||
with mb_lock:
|
||||
@@ -131,7 +135,7 @@ def findRelease(name, limit=1, artist=None):
|
||||
|
||||
# additional artist search
|
||||
if not artist and ':' in name:
|
||||
name, artist = name.rsplit(":",1)
|
||||
name, artist = name.rsplit(":", 1)
|
||||
|
||||
chars = set('!?*-')
|
||||
if any((c in chars) for c in name):
|
||||
@@ -140,7 +144,7 @@ def findRelease(name, limit=1, artist=None):
|
||||
artist = '"'+artist+'"'
|
||||
|
||||
try:
|
||||
releaseResults = musicbrainzngs.search_releases(query=name,limit=limit,artist=artist)['release-list']
|
||||
releaseResults = musicbrainzngs.search_releases(query=name, limit=limit, artist=artist)['release-list']
|
||||
except musicbrainzngs.WebServiceError as e: #need to update exceptions
|
||||
logger.warn('Attempt to query MusicBrainz for "%s" failed: %s' % (name, str(e)))
|
||||
time.sleep(5)
|
||||
@@ -201,6 +205,7 @@ def findRelease(name, limit=1, artist=None):
|
||||
})
|
||||
return releaselist
|
||||
|
||||
|
||||
def getArtist(artistid, extrasonly=False):
|
||||
|
||||
with mb_lock:
|
||||
@@ -214,12 +219,12 @@ def getArtist(artistid, extrasonly=False):
|
||||
newRgs = None
|
||||
artist['release-group-list'] = []
|
||||
while newRgs == None or len(newRgs) >= limit:
|
||||
newRgs = musicbrainzngs.browse_release_groups(artistid,release_type="album",offset=len(artist['release-group-list']),limit=limit)['release-group-list']
|
||||
newRgs = musicbrainzngs.browse_release_groups(artistid, release_type="album", offset=len(artist['release-group-list']), limit=limit)['release-group-list']
|
||||
artist['release-group-list'] += newRgs
|
||||
except musicbrainzngs.WebServiceError as e:
|
||||
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s (%s)' % (artistid, str(e)))
|
||||
time.sleep(5)
|
||||
except Exception,e:
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
if not artist:
|
||||
@@ -247,7 +252,6 @@ def getArtist(artistid, extrasonly=False):
|
||||
# if 'end' in artist['life-span']:
|
||||
# artist_dict['artist_enddate'] = unicode(artist['life-span']['end'])
|
||||
|
||||
|
||||
releasegroups = []
|
||||
|
||||
if not extrasonly:
|
||||
@@ -296,7 +300,7 @@ def getArtist(artistid, extrasonly=False):
|
||||
limit = 200
|
||||
newRgs = None
|
||||
while newRgs == None or len(newRgs) >= limit:
|
||||
newRgs = musicbrainzngs.browse_release_groups(artistid,release_type=include,offset=len(mb_extras_list),limit=limit)['release-group-list']
|
||||
newRgs = musicbrainzngs.browse_release_groups(artistid, release_type=include, offset=len(mb_extras_list), limit=limit)['release-group-list']
|
||||
mb_extras_list += newRgs
|
||||
except musicbrainzngs.WebServiceError as e:
|
||||
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s (%s)' % (artistid, str(e)))
|
||||
@@ -321,6 +325,7 @@ def getArtist(artistid, extrasonly=False):
|
||||
|
||||
return artist_dict
|
||||
|
||||
|
||||
def getReleaseGroup(rgid):
|
||||
"""
|
||||
Returns a list of releases in a release group
|
||||
@@ -332,7 +337,7 @@ def getReleaseGroup(rgid):
|
||||
releaseGroup = None
|
||||
|
||||
try:
|
||||
releaseGroup = musicbrainzngs.get_release_group_by_id(rgid,["artists","releases","media","discids",])['release-group']
|
||||
releaseGroup = musicbrainzngs.get_release_group_by_id(rgid, ["artists", "releases", "media", "discids", ])['release-group']
|
||||
except musicbrainzngs.WebServiceError as e:
|
||||
logger.warn('Attempt to retrieve information from MusicBrainz for release group "%s" failed (%s)' % (rgid, str(e)))
|
||||
time.sleep(5)
|
||||
@@ -342,6 +347,7 @@ def getReleaseGroup(rgid):
|
||||
else:
|
||||
return releaseGroup['release-list']
|
||||
|
||||
|
||||
def getRelease(releaseid, include_artist_info=True):
|
||||
"""
|
||||
Deep release search to get track info
|
||||
@@ -353,9 +359,9 @@ def getRelease(releaseid, include_artist_info=True):
|
||||
|
||||
try:
|
||||
if include_artist_info:
|
||||
results = musicbrainzngs.get_release_by_id(releaseid,["artists","release-groups","media","recordings"]).get('release')
|
||||
results = musicbrainzngs.get_release_by_id(releaseid, ["artists", "release-groups", "media", "recordings"]).get('release')
|
||||
else:
|
||||
results = musicbrainzngs.get_release_by_id(releaseid,["media","recordings"]).get('release')
|
||||
results = musicbrainzngs.get_release_by_id(releaseid, ["media", "recordings"]).get('release')
|
||||
except musicbrainzngs.WebServiceError as e:
|
||||
logger.warn('Attempt to retrieve information from MusicBrainz for release "%s" failed (%s)' % (releaseid, str(e)))
|
||||
time.sleep(5)
|
||||
@@ -377,7 +383,6 @@ def getRelease(releaseid, include_artist_info=True):
|
||||
except:
|
||||
release['country'] = u'Unknown'
|
||||
|
||||
|
||||
if include_artist_info:
|
||||
|
||||
if 'release-group' in results:
|
||||
@@ -404,7 +409,8 @@ def getRelease(releaseid, include_artist_info=True):
|
||||
|
||||
return release
|
||||
|
||||
def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
||||
|
||||
def get_new_releases(rgid, includeExtras=False, forcefull=False):
|
||||
|
||||
myDB = db.DBConnection()
|
||||
results = []
|
||||
@@ -412,7 +418,7 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
||||
limit = 100
|
||||
newResults = None
|
||||
while newResults == None or len(newResults) >= limit:
|
||||
newResults = musicbrainzngs.browse_releases(release_group=rgid,includes=['artist-credits','labels','recordings','release-groups','media'],limit=limit,offset=len(results))
|
||||
newResults = musicbrainzngs.browse_releases(release_group=rgid, includes=['artist-credits', 'labels', 'recordings', 'release-groups', 'media'], limit=limit, offset=len(results))
|
||||
if 'release-list' not in newResults:
|
||||
break #may want to raise an exception here instead ?
|
||||
newResults = newResults['release-list']
|
||||
@@ -486,11 +492,10 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
||||
logger.warn('Release ' + releasedata['id'] + ' has no Artists associated.')
|
||||
return False
|
||||
|
||||
|
||||
release['ReleaseCountry'] = unicode(releasedata['country']) if 'country' in releasedata else u'Unknown'
|
||||
#assuming that the list will contain media and that the format will be consistent
|
||||
try:
|
||||
additional_medium=''
|
||||
additional_medium = ''
|
||||
for position in releasedata['medium-list']:
|
||||
if position['format'] == releasedata['medium-list'][0]['format']:
|
||||
medium_count = int(position['position'])
|
||||
@@ -510,7 +515,7 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
||||
# What we're doing here now is first updating the allalbums & alltracks table to the most
|
||||
# current info, then moving the appropriate release into the album table and its associated
|
||||
# tracks into the tracks table
|
||||
controlValueDict = {"ReleaseID" : release['ReleaseID']}
|
||||
controlValueDict = {"ReleaseID": release['ReleaseID']}
|
||||
|
||||
newValueDict = {"ArtistID": release['ArtistID'],
|
||||
"ArtistName": release['ArtistName'],
|
||||
@@ -570,6 +575,7 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
||||
|
||||
return num_new_releases
|
||||
|
||||
|
||||
def getTracksFromRelease(release):
|
||||
totalTracks = 1
|
||||
tracks = []
|
||||
@@ -590,6 +596,8 @@ def getTracksFromRelease(release):
|
||||
return tracks
|
||||
|
||||
# Used when there is a disambiguation
|
||||
|
||||
|
||||
def findArtistbyAlbum(name):
|
||||
|
||||
myDB = db.DBConnection()
|
||||
@@ -613,7 +621,6 @@ def findArtistbyAlbum(name):
|
||||
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
if not results:
|
||||
return False
|
||||
|
||||
@@ -631,10 +638,9 @@ def findArtistbyAlbum(name):
|
||||
#artist_dict['url'] = u'http://musicbrainz.org/artist/' + newArtist['id']
|
||||
#artist_dict['score'] = int(releaseGroup['ext:score'])
|
||||
|
||||
|
||||
|
||||
return artist_dict
|
||||
|
||||
|
||||
def findAlbumID(artist=None, album=None):
|
||||
|
||||
results = None
|
||||
|
||||
+31
-27
@@ -30,6 +30,7 @@ if headphones.CONFIG.ENCODER == 'xld':
|
||||
else:
|
||||
XLD = False
|
||||
|
||||
|
||||
def encode(albumPath):
|
||||
|
||||
# Return if xld details not found
|
||||
@@ -40,10 +41,10 @@ def encode(albumPath):
|
||||
logger.error('Details for xld profile \'%s\' not found, files will not be re-encoded', xldProfile)
|
||||
return None
|
||||
|
||||
tempDirEncode=os.path.join(albumPath,"temp")
|
||||
musicFiles=[]
|
||||
musicFinalFiles=[]
|
||||
musicTempFiles=[]
|
||||
tempDirEncode = os.path.join(albumPath, "temp")
|
||||
musicFiles = []
|
||||
musicFinalFiles = []
|
||||
musicTempFiles = []
|
||||
encoder = ""
|
||||
|
||||
# Create temporary directory, but remove the old one first.
|
||||
@@ -57,7 +58,7 @@ def encode(albumPath):
|
||||
logger.exception("Unable to create temporary directory")
|
||||
return None
|
||||
|
||||
for r,d,f in os.walk(albumPath):
|
||||
for r, d, f in os.walk(albumPath):
|
||||
for music in f:
|
||||
if any(music.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
||||
if not XLD:
|
||||
@@ -85,24 +86,24 @@ def encode(albumPath):
|
||||
else:
|
||||
if XLD:
|
||||
encoder = os.path.join('/Applications', 'xld')
|
||||
elif headphones.CONFIG.ENCODER =='lame':
|
||||
elif headphones.CONFIG.ENCODER == 'lame':
|
||||
if headphones.SYS_PLATFORM == "win32":
|
||||
## NEED THE DEFAULT LAME INSTALL ON WIN!
|
||||
encoder = "C:/Program Files/lame/lame.exe"
|
||||
else:
|
||||
encoder="lame"
|
||||
elif headphones.CONFIG.ENCODER =='ffmpeg':
|
||||
encoder = "lame"
|
||||
elif headphones.CONFIG.ENCODER == 'ffmpeg':
|
||||
if headphones.SYS_PLATFORM == "win32":
|
||||
encoder = "C:/Program Files/ffmpeg/bin/ffmpeg.exe"
|
||||
else:
|
||||
encoder="ffmpeg"
|
||||
encoder = "ffmpeg"
|
||||
elif headphones.CONFIG.ENCODER == 'libav':
|
||||
if headphones.SYS_PLATFORM == "win32":
|
||||
encoder = "C:/Program Files/libav/bin/avconv.exe"
|
||||
else:
|
||||
encoder="avconv"
|
||||
encoder = "avconv"
|
||||
|
||||
i=0
|
||||
i = 0
|
||||
encoder_failed = False
|
||||
jobs = []
|
||||
|
||||
@@ -124,12 +125,12 @@ def encode(albumPath):
|
||||
else:
|
||||
encode = True
|
||||
else:
|
||||
if headphones.CONFIG.ENCODEROUTPUTFORMAT=='ogg':
|
||||
if headphones.CONFIG.ENCODEROUTPUTFORMAT == 'ogg':
|
||||
if music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.ogg'):
|
||||
logger.warn('Cannot re-encode .ogg %s', music.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
else:
|
||||
encode = True
|
||||
elif (headphones.CONFIG.ENCODEROUTPUTFORMAT=='mp3' or headphones.CONFIG.ENCODEROUTPUTFORMAT=='m4a'):
|
||||
elif (headphones.CONFIG.ENCODEROUTPUTFORMAT == 'mp3' or headphones.CONFIG.ENCODEROUTPUTFORMAT == 'm4a'):
|
||||
if (music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.'+headphones.CONFIG.ENCODEROUTPUTFORMAT) and (int(infoMusic.bitrate / 1000 ) <= headphones.CONFIG.BITRATE)):
|
||||
logger.info('%s has bitrate <= %skb, will not be re-encoded', music, headphones.CONFIG.BITRATE)
|
||||
else:
|
||||
@@ -142,7 +143,7 @@ def encode(albumPath):
|
||||
musicFiles[i] = None
|
||||
musicTempFiles[i] = None
|
||||
|
||||
i=i+1
|
||||
i = i+1
|
||||
|
||||
# Encode music files
|
||||
if len(jobs) > 0:
|
||||
@@ -216,7 +217,7 @@ def encode(albumPath):
|
||||
return None
|
||||
|
||||
time.sleep(1)
|
||||
for r,d,f in os.walk(albumPath):
|
||||
for r, d, f in os.walk(albumPath):
|
||||
for music in f:
|
||||
if any(music.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
||||
musicFinalFiles.append(os.path.join(r, music))
|
||||
@@ -226,6 +227,7 @@ def encode(albumPath):
|
||||
|
||||
return musicFinalFiles
|
||||
|
||||
|
||||
def command_map(args):
|
||||
"""
|
||||
Wrapper for the '[multiprocessing.]map()' method, to unpack the arguments
|
||||
@@ -243,6 +245,7 @@ def command_map(args):
|
||||
logger.exception("Encoder raised an exception.")
|
||||
return False
|
||||
|
||||
|
||||
def command(encoder, musicSource, musicDest, albumPath):
|
||||
"""
|
||||
Encode a given music file with a certain encoder. Returns True on success,
|
||||
@@ -268,9 +271,9 @@ def command(encoder, musicSource, musicDest, albumPath):
|
||||
opts = []
|
||||
if not headphones.CONFIG.ADVANCEDENCODER:
|
||||
opts.extend(['-h'])
|
||||
if headphones.CONFIG.ENCODERVBRCBR=='cbr':
|
||||
if headphones.CONFIG.ENCODERVBRCBR == 'cbr':
|
||||
opts.extend(['--resample', str(headphones.CONFIG.SAMPLINGFREQUENCY), '-b', str(headphones.CONFIG.BITRATE)])
|
||||
elif headphones.CONFIG.ENCODERVBRCBR=='vbr':
|
||||
elif headphones.CONFIG.ENCODERVBRCBR == 'vbr':
|
||||
opts.extend(['-v', str(headphones.CONFIG.ENCODERQUALITY)])
|
||||
else:
|
||||
advanced = (headphones.CONFIG.ADVANCEDENCODER.split())
|
||||
@@ -285,13 +288,13 @@ def command(encoder, musicSource, musicDest, albumPath):
|
||||
cmd = [encoder, '-i', musicSource]
|
||||
opts = []
|
||||
if not headphones.CONFIG.ADVANCEDENCODER:
|
||||
if headphones.CONFIG.ENCODEROUTPUTFORMAT=='ogg':
|
||||
if headphones.CONFIG.ENCODEROUTPUTFORMAT == 'ogg':
|
||||
opts.extend(['-acodec', 'libvorbis'])
|
||||
if headphones.CONFIG.ENCODEROUTPUTFORMAT=='m4a':
|
||||
if headphones.CONFIG.ENCODEROUTPUTFORMAT == 'm4a':
|
||||
opts.extend(['-strict', 'experimental'])
|
||||
if headphones.CONFIG.ENCODERVBRCBR=='cbr':
|
||||
if headphones.CONFIG.ENCODERVBRCBR == 'cbr':
|
||||
opts.extend(['-ar', str(headphones.CONFIG.SAMPLINGFREQUENCY), '-ab', str(headphones.CONFIG.BITRATE) + 'k'])
|
||||
elif headphones.CONFIG.ENCODERVBRCBR=='vbr':
|
||||
elif headphones.CONFIG.ENCODERVBRCBR == 'vbr':
|
||||
opts.extend(['-aq', str(headphones.CONFIG.ENCODERQUALITY)])
|
||||
opts.extend(['-y', '-ac', '2', '-vn'])
|
||||
else:
|
||||
@@ -306,13 +309,13 @@ def command(encoder, musicSource, musicDest, albumPath):
|
||||
cmd = [encoder, '-i', musicSource]
|
||||
opts = []
|
||||
if not headphones.CONFIG.ADVANCEDENCODER:
|
||||
if headphones.CONFIG.ENCODEROUTPUTFORMAT=='ogg':
|
||||
if headphones.CONFIG.ENCODEROUTPUTFORMAT == 'ogg':
|
||||
opts.extend(['-acodec', 'libvorbis'])
|
||||
if headphones.CONFIG.ENCODEROUTPUTFORMAT=='m4a':
|
||||
if headphones.CONFIG.ENCODEROUTPUTFORMAT == 'm4a':
|
||||
opts.extend(['-strict', 'experimental'])
|
||||
if headphones.CONFIG.ENCODERVBRCBR=='cbr':
|
||||
if headphones.CONFIG.ENCODERVBRCBR == 'cbr':
|
||||
opts.extend(['-ar', str(headphones.CONFIG.SAMPLINGFREQUENCY), '-ab', str(headphones.CONFIG.BITRATE) + 'k'])
|
||||
elif headphones.CONFIG.ENCODERVBRCBR=='vbr':
|
||||
elif headphones.CONFIG.ENCODERVBRCBR == 'vbr':
|
||||
opts.extend(['-aq', str(headphones.CONFIG.ENCODERQUALITY)])
|
||||
opts.extend(['-y', '-ac', '2', '-vn'])
|
||||
else:
|
||||
@@ -357,10 +360,11 @@ def command(encoder, musicSource, musicDest, albumPath):
|
||||
|
||||
return encoded
|
||||
|
||||
|
||||
def getTimeEncode(start):
|
||||
seconds =int(time.time()-start)
|
||||
seconds = int(time.time()-start)
|
||||
hours = seconds / 3600
|
||||
seconds -= 3600*hours
|
||||
minutes = seconds / 60
|
||||
seconds -= 60*minutes
|
||||
return "%02d:%02d:%02d" % (hours, minutes, seconds)
|
||||
return "%02d:%02d:%02d" % (hours, minutes, seconds)
|
||||
|
||||
+39
-25
@@ -39,6 +39,7 @@ try:
|
||||
except ImportError:
|
||||
from cgi import parse_qsl
|
||||
|
||||
|
||||
class GROWL(object):
|
||||
"""
|
||||
Growl notifications, for OS X.
|
||||
@@ -124,6 +125,7 @@ class GROWL(object):
|
||||
|
||||
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
||||
|
||||
|
||||
class PROWL(object):
|
||||
"""
|
||||
Prowl notifications.
|
||||
@@ -151,8 +153,8 @@ class PROWL(object):
|
||||
|
||||
http_handler.request("POST",
|
||||
"/publicapi/add",
|
||||
headers = {'Content-type': "application/x-www-form-urlencoded"},
|
||||
body = urlencode(data))
|
||||
headers={'Content-type': "application/x-www-form-urlencoded"},
|
||||
body=urlencode(data))
|
||||
response = http_handler.getresponse()
|
||||
request_status = response.status
|
||||
|
||||
@@ -177,6 +179,7 @@ class PROWL(object):
|
||||
|
||||
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
||||
|
||||
|
||||
class MPC(object):
|
||||
"""
|
||||
MPC library update
|
||||
@@ -255,7 +258,7 @@ class XBMC(object):
|
||||
request = self._sendhttp(host, notifycommand)
|
||||
|
||||
else: #Frodo
|
||||
params = {'title':header, 'message': message, 'displaytime': int(time), 'image': albumartpath}
|
||||
params = {'title': header, 'message': message, 'displaytime': int(time), 'image': albumartpath}
|
||||
request = self._sendjson(host, 'GUI.ShowNotification', params)
|
||||
|
||||
if not request:
|
||||
@@ -264,6 +267,7 @@ class XBMC(object):
|
||||
except Exception:
|
||||
logger.error('Error sending notification request to XBMC')
|
||||
|
||||
|
||||
class LMS(object):
|
||||
"""
|
||||
Class for updating a Logitech Media Server
|
||||
@@ -273,7 +277,7 @@ class LMS(object):
|
||||
self.hosts = headphones.CONFIG.LMS_HOST
|
||||
|
||||
def _sendjson(self, host):
|
||||
data = {'id': 1, 'method': 'slim.request', 'params': ["",["rescan"]]}
|
||||
data = {'id': 1, 'method': 'slim.request', 'params': ["", ["rescan"]]}
|
||||
data = json.JSONEncoder().encode(data)
|
||||
|
||||
content = {'Content-Type': 'application/json'}
|
||||
@@ -305,6 +309,7 @@ class LMS(object):
|
||||
if not request:
|
||||
logger.warn('Error sending rescan request to LMS')
|
||||
|
||||
|
||||
class Plex(object):
|
||||
def __init__(self):
|
||||
|
||||
@@ -391,6 +396,7 @@ class Plex(object):
|
||||
except:
|
||||
logger.warn('Error sending notification request to Plex Media Server')
|
||||
|
||||
|
||||
class NMA(object):
|
||||
def notify(self, artist=None, album=None, snatched=None):
|
||||
title = 'Headphones'
|
||||
@@ -427,6 +433,7 @@ class NMA(object):
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
class PUSHBULLET(object):
|
||||
|
||||
def __init__(self):
|
||||
@@ -449,9 +456,9 @@ class PUSHBULLET(object):
|
||||
|
||||
http_handler.request("POST",
|
||||
"/api/pushes",
|
||||
headers = {'Content-type': "application/x-www-form-urlencoded",
|
||||
'Authorization' : 'Basic %s' % base64.b64encode(headphones.CONFIG.PUSHBULLET_APIKEY + ":") },
|
||||
body = urlencode(data))
|
||||
headers={'Content-type': "application/x-www-form-urlencoded",
|
||||
'Authorization': 'Basic %s' % base64.b64encode(headphones.CONFIG.PUSHBULLET_APIKEY + ":") },
|
||||
body=urlencode(data))
|
||||
response = http_handler.getresponse()
|
||||
request_status = response.status
|
||||
logger.debug(u"PushBullet response status: %r" % request_status)
|
||||
@@ -480,6 +487,7 @@ class PUSHBULLET(object):
|
||||
|
||||
self.notify('Main Screen Activate', 'Test Message')
|
||||
|
||||
|
||||
class PUSHALOT(object):
|
||||
|
||||
def notify(self, message, event):
|
||||
@@ -500,8 +508,8 @@ class PUSHALOT(object):
|
||||
|
||||
http_handler.request("POST",
|
||||
"/api/sendmessage",
|
||||
headers = {'Content-type': "application/x-www-form-urlencoded"},
|
||||
body = urlencode(data))
|
||||
headers={'Content-type': "application/x-www-form-urlencoded"},
|
||||
body=urlencode(data))
|
||||
response = http_handler.getresponse()
|
||||
request_status = response.status
|
||||
|
||||
@@ -519,6 +527,7 @@ class PUSHALOT(object):
|
||||
logger.info(u"Pushalot notification failed.")
|
||||
return False
|
||||
|
||||
|
||||
class Synoindex(object):
|
||||
def __init__(self, util_loc='/usr/syno/bin/synoindex'):
|
||||
self.util_loc = util_loc
|
||||
@@ -555,6 +564,7 @@ class Synoindex(object):
|
||||
for path in path_list:
|
||||
self.notify(path)
|
||||
|
||||
|
||||
class PUSHOVER(object):
|
||||
|
||||
def __init__(self):
|
||||
@@ -584,8 +594,8 @@ class PUSHOVER(object):
|
||||
|
||||
http_handler.request("POST",
|
||||
"/1/messages.json",
|
||||
headers = {'Content-type': "application/x-www-form-urlencoded"},
|
||||
body = urlencode(data))
|
||||
headers={'Content-type': "application/x-www-form-urlencoded"},
|
||||
body=urlencode(data))
|
||||
response = http_handler.getresponse()
|
||||
request_status = response.status
|
||||
logger.debug(u"Pushover response status: %r" % request_status)
|
||||
@@ -613,12 +623,13 @@ class PUSHOVER(object):
|
||||
|
||||
self.notify('Main Screen Activate', 'Test Message')
|
||||
|
||||
|
||||
class TwitterNotifier(object):
|
||||
|
||||
REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
|
||||
ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_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'
|
||||
SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
|
||||
|
||||
def __init__(self):
|
||||
self.consumer_key = "oYKnp2ddX5gbARjqX8ZAAg"
|
||||
@@ -638,8 +649,8 @@ class TwitterNotifier(object):
|
||||
def _get_authorization(self):
|
||||
|
||||
signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable
|
||||
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
|
||||
oauth_client = oauth.Client(oauth_consumer)
|
||||
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
|
||||
oauth_client = oauth.Client(oauth_consumer)
|
||||
|
||||
logger.info('Requesting temp token from Twitter')
|
||||
|
||||
@@ -653,7 +664,7 @@ class TwitterNotifier(object):
|
||||
headphones.CONFIG.TWITTER_USERNAME = request_token['oauth_token']
|
||||
headphones.CONFIG.TWITTER_PASSWORD = request_token['oauth_token_secret']
|
||||
|
||||
return self.AUTHORIZATION_URL+"?oauth_token="+ request_token['oauth_token']
|
||||
return self.AUTHORIZATION_URL+"?oauth_token=" + request_token['oauth_token']
|
||||
|
||||
def _get_credentials(self, key):
|
||||
request_token = {}
|
||||
@@ -668,14 +679,14 @@ class TwitterNotifier(object):
|
||||
logger.info('Generating and signing request for an access token using key '+key)
|
||||
|
||||
signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable
|
||||
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
|
||||
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
|
||||
logger.info('oauth_consumer: '+str(oauth_consumer))
|
||||
oauth_client = oauth.Client(oauth_consumer, token)
|
||||
oauth_client = oauth.Client(oauth_consumer, token)
|
||||
logger.info('oauth_client: '+str(oauth_client))
|
||||
resp, content = oauth_client.request(self.ACCESS_TOKEN_URL, method='POST', body='oauth_verifier=%s' % key)
|
||||
logger.info('resp, content: '+str(resp)+','+str(content))
|
||||
|
||||
access_token = dict(parse_qsl(content))
|
||||
access_token = dict(parse_qsl(content))
|
||||
logger.info('access_token: '+str(access_token))
|
||||
|
||||
logger.info('resp[status] = '+str(resp['status']))
|
||||
@@ -689,13 +700,12 @@ class TwitterNotifier(object):
|
||||
headphones.CONFIG.TWITTER_PASSWORD = access_token['oauth_token_secret']
|
||||
return True
|
||||
|
||||
|
||||
def _send_tweet(self, message=None):
|
||||
|
||||
username=self.consumer_key
|
||||
password=self.consumer_secret
|
||||
access_token_key=headphones.CONFIG.TWITTER_USERNAME
|
||||
access_token_secret=headphones.CONFIG.TWITTER_PASSWORD
|
||||
username = self.consumer_key
|
||||
password = self.consumer_secret
|
||||
access_token_key = headphones.CONFIG.TWITTER_USERNAME
|
||||
access_token_secret = headphones.CONFIG.TWITTER_PASSWORD
|
||||
|
||||
logger.info(u"Sending tweet: "+message)
|
||||
|
||||
@@ -717,6 +727,7 @@ class TwitterNotifier(object):
|
||||
|
||||
return self._send_tweet(prefix+": "+message)
|
||||
|
||||
|
||||
class OSX_NOTIFY(object):
|
||||
|
||||
def __init__(self):
|
||||
@@ -727,6 +738,7 @@ class OSX_NOTIFY(object):
|
||||
|
||||
def swizzle(self, cls, SEL, func):
|
||||
old_IMP = cls.instanceMethodForSelector_(SEL)
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
return func(self, old_IMP, *args, **kwargs)
|
||||
new_IMP = self.objc.selector(wrapper, selector=old_IMP.selector,
|
||||
@@ -772,6 +784,7 @@ class OSX_NOTIFY(object):
|
||||
def swizzled_bundleIdentifier(self, original, swizzled):
|
||||
return 'ade.headphones.osxnotify'
|
||||
|
||||
|
||||
class BOXCAR(object):
|
||||
|
||||
def __init__(self):
|
||||
@@ -798,6 +811,7 @@ class BOXCAR(object):
|
||||
logger.warn('Error sending Boxcar2 Notification: %s' % e)
|
||||
return False
|
||||
|
||||
|
||||
class SubSonicNotifier(object):
|
||||
|
||||
def __init__(self):
|
||||
@@ -815,4 +829,4 @@ class SubSonicNotifier(object):
|
||||
|
||||
# Invoke request
|
||||
request.request_response(self.host + "musicFolderSettings.view?scanNow",
|
||||
auth=(self.username, self.password))
|
||||
auth=(self.username, self.password))
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
|
||||
import httplib
|
||||
import datetime
|
||||
|
||||
@@ -32,6 +31,7 @@ import xmlrpclib
|
||||
|
||||
from headphones import logger
|
||||
|
||||
|
||||
def sendNZB(nzb):
|
||||
|
||||
addToTop = False
|
||||
@@ -43,11 +43,10 @@ def sendNZB(nzb):
|
||||
|
||||
if headphones.CONFIG.NZBGET_HOST.startswith('https://'):
|
||||
nzbgetXMLrpc = 'https://' + nzbgetXMLrpc
|
||||
headphones.CONFIG.NZBGET_HOST.replace('https://','',1)
|
||||
headphones.CONFIG.NZBGET_HOST.replace('https://', '', 1)
|
||||
else:
|
||||
nzbgetXMLrpc = 'http://' + nzbgetXMLrpc
|
||||
headphones.CONFIG.NZBGET_HOST.replace('http://','',1)
|
||||
|
||||
headphones.CONFIG.NZBGET_HOST.replace('http://', '', 1)
|
||||
|
||||
url = nzbgetXMLrpc % {"host": headphones.CONFIG.NZBGET_HOST, "username": headphones.CONFIG.NZBGET_USERNAME, "password": headphones.CONFIG.NZBGET_PASSWORD}
|
||||
|
||||
|
||||
+28
-15
@@ -32,6 +32,7 @@ from headphones import logger, helpers, request, mb, music_encoder
|
||||
|
||||
postprocessor_lock = threading.Lock()
|
||||
|
||||
|
||||
def checkFolder():
|
||||
|
||||
with postprocessor_lock:
|
||||
@@ -48,7 +49,7 @@ def checkFolder():
|
||||
else:
|
||||
download_dir = headphones.CONFIG.DOWNLOAD_TORRENT_DIR
|
||||
|
||||
album_path = os.path.join(download_dir, album['FolderName']).encode(headphones.SYS_ENCODING,'replace')
|
||||
album_path = os.path.join(download_dir, album['FolderName']).encode(headphones.SYS_ENCODING, 'replace')
|
||||
logger.info("Checking if %s exists" % album_path)
|
||||
if os.path.exists(album_path):
|
||||
logger.info('Found "' + album['FolderName'] + '" in ' + album['Kind'] + ' download folder. Verifying....')
|
||||
@@ -57,6 +58,7 @@ def checkFolder():
|
||||
else:
|
||||
logger.info("No folder name found for " + album['Title'])
|
||||
|
||||
|
||||
def verify(albumid, albumpath, Kind=None, forced=False):
|
||||
|
||||
myDB = db.DBConnection()
|
||||
@@ -167,7 +169,7 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
||||
downloaded_track_list = []
|
||||
downloaded_cuecount = 0
|
||||
|
||||
for r,d,f in os.walk(albumpath):
|
||||
for r, d, f in os.walk(albumpath):
|
||||
for files in f:
|
||||
if any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
||||
downloaded_track_list.append(os.path.join(r, files))
|
||||
@@ -180,7 +182,7 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
||||
|
||||
# Split cue
|
||||
if downloaded_cuecount and downloaded_cuecount >= len(downloaded_track_list):
|
||||
if headphones.CONFIG.KEEP_TORRENT_FILES and Kind=="torrent":
|
||||
if headphones.CONFIG.KEEP_TORRENT_FILES and Kind == "torrent":
|
||||
albumpath = helpers.preserve_torrent_direcory(albumpath)
|
||||
if albumpath and helpers.cue_split(albumpath):
|
||||
downloaded_track_list = helpers.get_downloaded_track_list(albumpath)
|
||||
@@ -276,11 +278,12 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
||||
else:
|
||||
logger.info(u"Already marked as unprocessed: " + albumpath.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
|
||||
|
||||
def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, Kind=None):
|
||||
|
||||
logger.info('Starting post-processing for: %s - %s' % (release['ArtistName'], release['AlbumTitle']))
|
||||
# Check to see if we're preserving the torrent dir
|
||||
if headphones.CONFIG.KEEP_TORRENT_FILES and Kind=="torrent" and 'headphones-modified' not in albumpath:
|
||||
if headphones.CONFIG.KEEP_TORRENT_FILES and Kind == "torrent" and 'headphones-modified' not in albumpath:
|
||||
new_folder = os.path.join(albumpath, 'headphones-modified'.encode(headphones.SYS_ENCODING, 'replace'))
|
||||
logger.info("Copying files to 'headphones-modified' subfolder to preserve downloaded files for seeding")
|
||||
try:
|
||||
@@ -296,7 +299,7 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
# but this is good to make sure we're not counting files that may have failed to move
|
||||
downloaded_track_list = []
|
||||
|
||||
for r,d,f in os.walk(albumpath):
|
||||
for r, d, f in os.walk(albumpath):
|
||||
for files in f:
|
||||
if any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
||||
downloaded_track_list.append(os.path.join(r, files))
|
||||
@@ -334,7 +337,7 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
|
||||
#start encoding
|
||||
if headphones.CONFIG.MUSIC_ENCODER:
|
||||
downloaded_track_list=music_encoder.encode(albumpath)
|
||||
downloaded_track_list = music_encoder.encode(albumpath)
|
||||
|
||||
if not downloaded_track_list:
|
||||
return
|
||||
@@ -500,6 +503,7 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
mpc = notifiers.MPC()
|
||||
mpc.notify()
|
||||
|
||||
|
||||
def embedAlbumArt(artwork, downloaded_track_list):
|
||||
logger.info('Embedding album art')
|
||||
|
||||
@@ -519,6 +523,7 @@ def embedAlbumArt(artwork, downloaded_track_list):
|
||||
logger.error(u'Error embedding album art to: %s. Error: %s' % (downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), str(e)))
|
||||
continue
|
||||
|
||||
|
||||
def addAlbumArt(artwork, albumpath, release):
|
||||
logger.info('Adding album art to folder')
|
||||
|
||||
@@ -552,10 +557,11 @@ def addAlbumArt(artwork, albumpath, release):
|
||||
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 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)
|
||||
@@ -564,10 +570,11 @@ def cleanupFiles(albumpath):
|
||||
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 r, d, f in os.walk(albumpath):
|
||||
for file in f:
|
||||
if file.lower().endswith('.nfo'):
|
||||
logger.debug('Renaming: "%s" to "%s"' % (file.decode(headphones.SYS_ENCODING, 'replace'), file.decode(headphones.SYS_ENCODING, 'replace') + '-orig'))
|
||||
@@ -577,6 +584,7 @@ def renameNFO(albumpath):
|
||||
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):
|
||||
logger.info("Moving files: %s" % albumpath)
|
||||
try:
|
||||
@@ -602,7 +610,7 @@ def moveFiles(albumpath, release, tracks):
|
||||
else:
|
||||
firstchar = sortname[0]
|
||||
|
||||
for r,d,f in os.walk(albumpath):
|
||||
for r, d, f in os.walk(albumpath):
|
||||
try:
|
||||
origfolder = os.path.basename(os.path.normpath(r).decode(headphones.SYS_ENCODING, 'replace'))
|
||||
except:
|
||||
@@ -627,7 +635,7 @@ def moveFiles(albumpath, release, tracks):
|
||||
folder = helpers.replace_all(headphones.CONFIG.FOLDER_FORMAT.strip(), values, normalize=True)
|
||||
|
||||
folder = helpers.replace_illegal_chars(folder, type="folder")
|
||||
folder = folder.replace('./', '_/').replace('/.','/_')
|
||||
folder = folder.replace('./', '_/').replace('/.', '/_')
|
||||
|
||||
if folder.endswith('.'):
|
||||
folder = folder[:-1] + '_'
|
||||
@@ -641,7 +649,7 @@ def moveFiles(albumpath, release, tracks):
|
||||
lossy_media = False
|
||||
lossless_media = False
|
||||
|
||||
for r,d,f in os.walk(albumpath):
|
||||
for r, d, f in os.walk(albumpath):
|
||||
for files in f:
|
||||
files_to_move.append(os.path.join(r, files))
|
||||
if any(files.lower().endswith('.' + x.lower()) for x in headphones.LOSSY_MEDIA_FORMATS):
|
||||
@@ -809,6 +817,7 @@ def moveFiles(albumpath, release, tracks):
|
||||
|
||||
return destination_paths
|
||||
|
||||
|
||||
def correctMetadata(albumid, release, downloaded_track_list):
|
||||
|
||||
logger.info('Preparing to write metadata to tracks....')
|
||||
@@ -862,6 +871,7 @@ def correctMetadata(albumid, release, downloaded_track_list):
|
||||
except Exception, e:
|
||||
logger.warn("Error writing metadata to '%s': %s", item.path.decode(headphones.SYS_ENCODING, 'replace'), str(e))
|
||||
|
||||
|
||||
def embedLyrics(downloaded_track_list):
|
||||
logger.info('Adding lyrics')
|
||||
|
||||
@@ -909,6 +919,7 @@ def embedLyrics(downloaded_track_list):
|
||||
else:
|
||||
logger.debug('No lyrics found for track: %s', item.title)
|
||||
|
||||
|
||||
def renameFiles(albumpath, downloaded_track_list, release):
|
||||
logger.info('Renaming files')
|
||||
try:
|
||||
@@ -973,8 +984,7 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
||||
|
||||
ext = os.path.splitext(downloaded_track)[1]
|
||||
|
||||
new_file_name = helpers.replace_all(headphones.CONFIG.FILE_FORMAT.strip(), values).replace('/','_') + ext
|
||||
|
||||
new_file_name = helpers.replace_all(headphones.CONFIG.FILE_FORMAT.strip(), values).replace('/', '_') + ext
|
||||
|
||||
new_file_name = helpers.replace_illegal_chars(new_file_name).encode(headphones.SYS_ENCODING, 'replace')
|
||||
|
||||
@@ -990,18 +1000,19 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
||||
logger.debug("Renaming for: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + " is not neccessary")
|
||||
continue
|
||||
|
||||
logger.debug('Renaming %s ---> %s', downloaded_track.decode(headphones.SYS_ENCODING,'replace'), new_file_name.decode(headphones.SYS_ENCODING,'replace'))
|
||||
logger.debug('Renaming %s ---> %s', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), new_file_name.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
try:
|
||||
os.rename(downloaded_track, new_file)
|
||||
except Exception, e:
|
||||
logger.error('Error renaming file: %s. Error: %s', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), e)
|
||||
continue
|
||||
|
||||
|
||||
def updateFilePermissions(albumpaths):
|
||||
|
||||
for folder in albumpaths:
|
||||
logger.info("Updating file permissions in %s", folder)
|
||||
for r,d,f in os.walk(folder):
|
||||
for r, d, f in os.walk(folder):
|
||||
for files in f:
|
||||
full_path = os.path.join(r, files)
|
||||
try:
|
||||
@@ -1010,6 +1021,7 @@ def updateFilePermissions(albumpaths):
|
||||
logger.error("Could not change permissions for file: %s", full_path)
|
||||
continue
|
||||
|
||||
|
||||
def renameUnprocessedFolder(albumpath):
|
||||
|
||||
i = 0
|
||||
@@ -1026,6 +1038,7 @@ def renameUnprocessedFolder(albumpath):
|
||||
os.rename(albumpath, new_folder_name)
|
||||
return
|
||||
|
||||
|
||||
def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None):
|
||||
|
||||
if album_dir:
|
||||
|
||||
@@ -27,6 +27,7 @@ import collections
|
||||
# Dictionary with last request times, for rate limiting.
|
||||
last_requests = collections.defaultdict(int)
|
||||
|
||||
|
||||
def request_response(url, method="get", auto_raise=True,
|
||||
whitelist_status_code=None, rate_limit=None, **kwargs):
|
||||
"""
|
||||
@@ -125,6 +126,7 @@ def request_response(url, method="get", auto_raise=True,
|
||||
except requests.RequestException as e:
|
||||
logger.error("Request raised exception: %s", e)
|
||||
|
||||
|
||||
def request_soup(url, **kwargs):
|
||||
"""
|
||||
Wrapper for `request_response', which will return a BeatifulSoup object if
|
||||
@@ -137,6 +139,7 @@ def request_soup(url, **kwargs):
|
||||
if response is not None:
|
||||
return BeautifulSoup(response.content, parser)
|
||||
|
||||
|
||||
def request_minidom(url, **kwargs):
|
||||
"""
|
||||
Wrapper for `request_response', which will return a Minidom object if no
|
||||
@@ -148,6 +151,7 @@ def request_minidom(url, **kwargs):
|
||||
if response is not None:
|
||||
return minidom.parseString(response.content)
|
||||
|
||||
|
||||
def request_json(url, **kwargs):
|
||||
"""
|
||||
Wrapper for `request_response', which will decode the response as JSON
|
||||
@@ -175,6 +179,7 @@ def request_json(url, **kwargs):
|
||||
if headphones.VERBOSE:
|
||||
server_message(response)
|
||||
|
||||
|
||||
def request_content(url, **kwargs):
|
||||
"""
|
||||
Wrapper for `request_response', which will return the raw content.
|
||||
@@ -185,6 +190,7 @@ def request_content(url, **kwargs):
|
||||
if response is not None:
|
||||
return response.content
|
||||
|
||||
|
||||
def request_feed(url, **kwargs):
|
||||
"""
|
||||
Wrapper for `request_response', which will return a feed object.
|
||||
@@ -195,6 +201,7 @@ def request_feed(url, **kwargs):
|
||||
if response is not None:
|
||||
return feedparser.parse(response.content)
|
||||
|
||||
|
||||
def server_message(response):
|
||||
"""
|
||||
Extract server message from response and log in to logger with DEBUG level.
|
||||
|
||||
+12
-10
@@ -30,6 +30,7 @@ from headphones.common import USER_AGENT
|
||||
from headphones import logger
|
||||
from headphones import notifiers, helpers
|
||||
|
||||
|
||||
def sendNZB(nzb):
|
||||
|
||||
params = {}
|
||||
@@ -94,11 +95,11 @@ def sendNZB(nzb):
|
||||
except httplib.InvalidURL, e:
|
||||
logger.error(u"Invalid SAB host, check your config. Current host: %s" % headphones.CONFIG.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
|
||||
@@ -126,11 +127,12 @@ def sendNZB(nzb):
|
||||
else:
|
||||
logger.info(u"Unknown failure sending NZB to sab. Return text is: " + sabText)
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def checkConfig():
|
||||
|
||||
params = { 'mode' : 'get_config',
|
||||
'section' : 'misc'
|
||||
params = { 'mode': 'get_config',
|
||||
'section': 'misc'
|
||||
}
|
||||
|
||||
if headphones.CONFIG.SAB_USERNAME:
|
||||
@@ -145,18 +147,18 @@ def checkConfig():
|
||||
|
||||
if headphones.CONFIG.SAB_HOST.endswith('/'):
|
||||
headphones.CONFIG.SAB_HOST = headphones.CONFIG.SAB_HOST[0:len(headphones.CONFIG.SAB_HOST)-1]
|
||||
|
||||
|
||||
url = headphones.CONFIG.SAB_HOST + "/" + "api?" + urllib.urlencode(params)
|
||||
|
||||
|
||||
try:
|
||||
f = urllib.urlopen(url).read()
|
||||
except Exception, e:
|
||||
logger.warn("Unable to read SABnzbd config file - cannot determine renaming options (might affect auto & forced post processing)")
|
||||
return (0, 0)
|
||||
|
||||
|
||||
config_options = ast.literal_eval(f)
|
||||
|
||||
|
||||
replace_spaces = config_options['misc']['replace_spaces']
|
||||
replace_dots = config_options['misc']['replace_dots']
|
||||
|
||||
|
||||
return (replace_spaces, replace_dots)
|
||||
|
||||
+37
-22
@@ -54,6 +54,7 @@ gazelle = None
|
||||
# RUtracker search object
|
||||
rutracker = rutrackersearch.Rutracker()
|
||||
|
||||
|
||||
def fix_url(s, charset="utf-8"):
|
||||
"""
|
||||
Fix the URL so it is proper formatted and encoded.
|
||||
@@ -68,6 +69,7 @@ def fix_url(s, charset="utf-8"):
|
||||
|
||||
return urlparse.urlunsplit((scheme, netloc, path, qs, anchor))
|
||||
|
||||
|
||||
def torrent_to_file(target_file, data):
|
||||
"""
|
||||
Write torrent data to file, and change permissions accordingly. Will return
|
||||
@@ -94,6 +96,7 @@ def torrent_to_file(target_file, data):
|
||||
# Done
|
||||
return True
|
||||
|
||||
|
||||
def read_torrent_name(torrent_file, default_name=None):
|
||||
"""
|
||||
Read the torrent file and return the torrent name. If the torrent name
|
||||
@@ -123,6 +126,7 @@ def read_torrent_name(torrent_file, default_name=None):
|
||||
# Return default
|
||||
return default_name
|
||||
|
||||
|
||||
def calculate_torrent_hash(link, data=None):
|
||||
"""
|
||||
Calculate the torrent hash from a magnet link or data.
|
||||
@@ -141,6 +145,7 @@ def calculate_torrent_hash(link, data=None):
|
||||
|
||||
return torrent_hash
|
||||
|
||||
|
||||
def get_seed_ratio(provider):
|
||||
"""
|
||||
Return the seed ratio for the specified provider, if applicable. Defaults to
|
||||
@@ -148,17 +153,17 @@ def get_seed_ratio(provider):
|
||||
"""
|
||||
|
||||
if provider == 'rutracker.org':
|
||||
seed_ratio = headphones.RUTRACKER_RATIO
|
||||
seed_ratio = headphones.CONFIG.RUTRACKER_RATIO
|
||||
elif provider == 'Kick Ass Torrents':
|
||||
seed_ratio = headphones.KAT_RATIO
|
||||
seed_ratio = headphones.CONFIG.KAT_RATIO
|
||||
elif provider == 'What.cd':
|
||||
seed_ratio = headphones.WHATCD_RATIO
|
||||
seed_ratio = headphones.CONFIG.WHATCD_RATIO
|
||||
elif provider == 'The Pirate Bay':
|
||||
seed_ratio = headphones.PIRATEBAY_RATIO
|
||||
seed_ratio = headphones.CONFIG.PIRATEBAY_RATIO
|
||||
elif provider == 'Waffles.fm':
|
||||
seed_ratio = headphones.WAFFLES_RATIO
|
||||
seed_ratio = headphones.CONFIG.WAFFLES_RATIO
|
||||
elif provider == 'Mininova':
|
||||
seed_ratio = headphones.MININOVA_RATIO
|
||||
seed_ratio = headphones.CONFIG.MININOVA_RATIO
|
||||
else:
|
||||
seed_ratio = None
|
||||
|
||||
@@ -170,6 +175,7 @@ def get_seed_ratio(provider):
|
||||
|
||||
return seed_ratio
|
||||
|
||||
|
||||
def searchforalbum(albumid=None, new=False, losslessOnly=False, choose_specific_download=False):
|
||||
myDB = db.DBConnection()
|
||||
|
||||
@@ -204,6 +210,7 @@ def searchforalbum(albumid=None, new=False, losslessOnly=False, choose_specific_
|
||||
|
||||
logger.info('Search for Wanted albums complete')
|
||||
|
||||
|
||||
def do_sorted_search(album, new, losslessOnly, choose_specific_download=False):
|
||||
|
||||
NZB_PROVIDERS = (headphones.CONFIG.HEADPHONES_INDEXER or headphones.CONFIG.NEWZNAB or headphones.CONFIG.NZBSORG or headphones.CONFIG.OMGWTFNZBS)
|
||||
@@ -249,7 +256,6 @@ def do_sorted_search(album, new, losslessOnly, choose_specific_download=False):
|
||||
|
||||
results = nzb_results + torrent_results
|
||||
|
||||
|
||||
if choose_specific_download:
|
||||
return results
|
||||
|
||||
@@ -264,11 +270,13 @@ def do_sorted_search(album, new, losslessOnly, choose_specific_download=False):
|
||||
if data and bestqual:
|
||||
send_to_downloader(data, bestqual, album)
|
||||
|
||||
|
||||
def removeDisallowedFilenameChars(filename):
|
||||
validFilenameChars = "-_.() %s%s" % (string.ascii_letters, string.digits)
|
||||
cleanedFilename = unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore').lower()
|
||||
return ''.join(c for c in cleanedFilename if c in validFilenameChars)
|
||||
|
||||
|
||||
def more_filtering(results, album, albumlength, new):
|
||||
|
||||
low_size_limit = None
|
||||
@@ -332,6 +340,7 @@ def more_filtering(results, album, albumlength, new):
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def sort_search_results(resultlist, album, new, albumlength):
|
||||
|
||||
if new and not len(resultlist):
|
||||
@@ -351,9 +360,9 @@ def sort_search_results(resultlist, album, new, albumlength):
|
||||
# add a search provider priority (weighted based on position)
|
||||
i = next((i for i, word in enumerate(preferred_words) if word in result[3].lower()), None)
|
||||
if i is not None:
|
||||
priority += round((len(preferred_words) - i) / float(len(preferred_words)),2)
|
||||
priority += round((len(preferred_words) - i) / float(len(preferred_words)), 2)
|
||||
|
||||
temp_list.append((result[0],result[1],result[2],result[3],result[4],priority))
|
||||
temp_list.append((result[0], result[1], result[2], result[3], result[4], priority))
|
||||
|
||||
resultlist = temp_list
|
||||
|
||||
@@ -401,6 +410,7 @@ def sort_search_results(resultlist, album, new, albumlength):
|
||||
|
||||
return finallist
|
||||
|
||||
|
||||
def get_year_from_release_date(release_date):
|
||||
|
||||
try:
|
||||
@@ -410,13 +420,14 @@ def get_year_from_release_date(release_date):
|
||||
|
||||
return year
|
||||
|
||||
|
||||
def searchNZB(album, new=False, losslessOnly=False, albumlength=None):
|
||||
|
||||
albumid = album['AlbumID']
|
||||
reldate = album['ReleaseDate']
|
||||
year = get_year_from_release_date(reldate)
|
||||
|
||||
dic = {'...':'', ' & ':' ', ' = ': ' ', '?':'', '$':'s', ' + ':' ', '"':'', ',':'', '*':'', '.':'', ':':''}
|
||||
dic = {'...': '', ' & ': ' ', ' = ': ' ', '?': '', '$': 's', ' + ': ' ', '"': '', ',': '', '*': '', '.': '', ':': ''}
|
||||
|
||||
cleanalbum = helpers.latinToAscii(helpers.replace_all(album['AlbumTitle'], dic)).strip()
|
||||
cleanartist = helpers.latinToAscii(helpers.replace_all(album['ArtistName'], dic)).strip()
|
||||
@@ -678,6 +689,7 @@ def searchNZB(album, new=False, losslessOnly=False, albumlength=None):
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def send_to_downloader(data, bestqual, album):
|
||||
|
||||
logger.info(u'Found best result from %s: <a href="%s">%s</a> - %s', bestqual[3], bestqual[2], bestqual[0], helpers.bytes_to_mb(bestqual[1]))
|
||||
@@ -885,15 +897,15 @@ def send_to_downloader(data, bestqual, album):
|
||||
if headphones.CONFIG.GROWL_ENABLED and headphones.CONFIG.GROWL_ONSNATCH:
|
||||
logger.info(u"Sending Growl notification")
|
||||
growl = notifiers.GROWL()
|
||||
growl.notify(name,"Download started")
|
||||
growl.notify(name, "Download started")
|
||||
if headphones.CONFIG.PROWL_ENABLED and headphones.CONFIG.PROWL_ONSNATCH:
|
||||
logger.info(u"Sending Prowl notification")
|
||||
prowl = notifiers.PROWL()
|
||||
prowl.notify(name,"Download started")
|
||||
prowl.notify(name, "Download started")
|
||||
if headphones.CONFIG.PUSHOVER_ENABLED and headphones.CONFIG.PUSHOVER_ONSNATCH:
|
||||
logger.info(u"Sending Pushover notification")
|
||||
prowl = notifiers.PUSHOVER()
|
||||
prowl.notify(name,"Download started")
|
||||
prowl.notify(name, "Download started")
|
||||
if headphones.CONFIG.PUSHBULLET_ENABLED and headphones.CONFIG.PUSHBULLET_ONSNATCH:
|
||||
logger.info(u"Sending PushBullet notification")
|
||||
pushbullet = notifiers.PUSHBULLET()
|
||||
@@ -909,7 +921,7 @@ def send_to_downloader(data, bestqual, album):
|
||||
if headphones.CONFIG.PUSHALOT_ENABLED and headphones.CONFIG.PUSHALOT_ONSNATCH:
|
||||
logger.info(u"Sending Pushalot notification")
|
||||
pushalot = notifiers.PUSHALOT()
|
||||
pushalot.notify(name,"Download started")
|
||||
pushalot.notify(name, "Download started")
|
||||
if headphones.CONFIG.OSX_NOTIFY_ENABLED and headphones.CONFIG.OSX_NOTIFY_ONSNATCH:
|
||||
logger.info(u"Sending OS X notification")
|
||||
osx_notify = notifiers.OSX_NOTIFY()
|
||||
@@ -920,6 +932,7 @@ def send_to_downloader(data, bestqual, album):
|
||||
boxcar = notifiers.BOXCAR()
|
||||
boxcar.notify('Headphones snatched: ' + title, b2msg, rgid)
|
||||
|
||||
|
||||
def verifyresult(title, artistterm, term, lossless):
|
||||
|
||||
title = re.sub('[\.\-\/\_]', ' ', title)
|
||||
@@ -977,7 +990,7 @@ def verifyresult(title, artistterm, term, lossless):
|
||||
if not re.search('(?:\W|^)+' + token + '(?:\W|$)+', title, re.IGNORECASE | re.UNICODE):
|
||||
cleantoken = ''.join(c for c in token if c not in string.punctuation)
|
||||
if not not re.search('(?:\W|^)+' + cleantoken + '(?:\W|$)+', title, re.IGNORECASE | re.UNICODE):
|
||||
dic = {'!':'i', '$':'s'}
|
||||
dic = {'!': 'i', '$': 's'}
|
||||
dumbtoken = helpers.replace_all(token, dic)
|
||||
if not not re.search('(?:\W|^)+' + dumbtoken + '(?:\W|$)+', title, re.IGNORECASE | re.UNICODE):
|
||||
logger.info("Removed from results: %s (missing tokens: %s and %s)", title, token, cleantoken)
|
||||
@@ -985,6 +998,7 @@ def verifyresult(title, artistterm, term, lossless):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
|
||||
global gazelle # persistent what.cd api object to reduce number of login attempts
|
||||
|
||||
@@ -1001,7 +1015,7 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
|
||||
year = get_year_from_release_date(reldate)
|
||||
|
||||
# MERGE THIS WITH THE TERM CLEANUP FROM searchNZB
|
||||
dic = {'...':'', ' & ':' ', ' = ': ' ', '?':'', '$':'s', ' + ':' ', '"':'', ',':' ', '*':''}
|
||||
dic = {'...': '', ' & ': ' ', ' = ': ' ', '?': '', '$': 's', ' + ': ' ', '"': '', ',': ' ', '*': ''}
|
||||
|
||||
semi_cleanalbum = helpers.replace_all(album['AlbumTitle'], dic)
|
||||
cleanalbum = helpers.latinToAscii(semi_cleanalbum)
|
||||
@@ -1035,7 +1049,7 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
|
||||
# Replace bad characters in the term and unicode it
|
||||
term = re.sub('[\.\-\/]', ' ', term).encode('utf-8')
|
||||
artistterm = re.sub('[\.\-\/]', ' ', cleanartist).encode('utf-8', 'replace')
|
||||
albumterm = re.sub('[\.\-\/]', ' ', cleanalbum).encode('utf-8', 'replace')
|
||||
albumterm = re.sub('[\.\-\/]', ' ', cleanalbum).encode('utf-8', 'replace')
|
||||
|
||||
# If Preferred Bitrate and High Limit and Allow Lossless then get both lossy and lossless
|
||||
if headphones.CONFIG.PREFERRED_QUALITY == 2 and headphones.CONFIG.PREFERRED_BITRATE and headphones.CONFIG.PREFERRED_BITRATE_HIGH_BUFFER and headphones.CONFIG.PREFERRED_BITRATE_ALLOW_LOSSLESS:
|
||||
@@ -1057,7 +1071,6 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
|
||||
|
||||
return proxy_url
|
||||
|
||||
|
||||
if headphones.CONFIG.KAT:
|
||||
provider = "Kick Ass Torrents"
|
||||
ka_term = term.replace("!", "")
|
||||
@@ -1336,7 +1349,7 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
|
||||
# Request content
|
||||
logger.info("Searching The Pirate Bay using term: %s", tpb_term)
|
||||
|
||||
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36'}
|
||||
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36'}
|
||||
data = request.request_soup(url=providerurl + category, headers=headers)
|
||||
|
||||
# Process content
|
||||
@@ -1350,12 +1363,12 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
|
||||
try:
|
||||
url = None
|
||||
rightformat = True
|
||||
title = ''.join(item.find("a", {"class" : "detLink"}))
|
||||
seeds = int(''.join(item.find("td", {"align" : "right"})))
|
||||
title = ''.join(item.find("a", {"class": "detLink"}))
|
||||
seeds = int(''.join(item.find("td", {"align": "right"})))
|
||||
|
||||
if headphones.CONFIG.TORRENT_DOWNLOADER == 0:
|
||||
try:
|
||||
url = item.find("a", {"title":"Download this torrent"})['href']
|
||||
url = item.find("a", {"title": "Download this torrent"})['href']
|
||||
except TypeError:
|
||||
if headphones.MAGNET_LINKS != 0:
|
||||
url = item.findAll("a")[3]['href']
|
||||
@@ -1446,6 +1459,8 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
|
||||
return results
|
||||
|
||||
# THIS IS KIND OF A MESS AND PROBABLY NEEDS TO BE CLEANED UP
|
||||
|
||||
|
||||
def preprocess(resultlist):
|
||||
|
||||
for result in resultlist:
|
||||
|
||||
@@ -20,6 +20,7 @@ import urllib
|
||||
import re
|
||||
import os
|
||||
|
||||
|
||||
class Rutracker():
|
||||
|
||||
logged_in = False
|
||||
@@ -47,9 +48,9 @@ class Rutracker():
|
||||
#if self.login_counter > 1:
|
||||
# return False
|
||||
|
||||
params = urllib.urlencode({"login_username" : login,
|
||||
"login_password" : password,
|
||||
"login" : "Вход"})
|
||||
params = urllib.urlencode({"login_username": login,
|
||||
"login_password": password,
|
||||
"login": "Вход"})
|
||||
|
||||
try:
|
||||
self.opener.open("http://login.rutracker.org/forum/login.php", params)
|
||||
@@ -114,26 +115,26 @@ class Rutracker():
|
||||
#logger.debug (soup.prettify())
|
||||
|
||||
# Title
|
||||
for link in soup.find_all('a', attrs={'class' : 'med tLink hl-tags bold'}):
|
||||
for link in soup.find_all('a', attrs={'class': 'med tLink hl-tags bold'}):
|
||||
title = link.get_text()
|
||||
titles.append(title)
|
||||
|
||||
# Download URL
|
||||
for link in soup.find_all('a', attrs={'class' : 'small tr-dl dl-stub'}):
|
||||
for link in soup.find_all('a', attrs={'class': 'small tr-dl dl-stub'}):
|
||||
url = link.get('href')
|
||||
urls.append(url)
|
||||
|
||||
# Seeders
|
||||
for link in soup.find_all('b', attrs={'class' : 'seedmed'}):
|
||||
for link in soup.find_all('b', attrs={'class': 'seedmed'}):
|
||||
seeder = link.get_text()
|
||||
seeders.append(seeder)
|
||||
|
||||
# Size
|
||||
for link in soup.find_all('td', attrs={'class' : 'row4 small nowrap tor-size'}):
|
||||
for link in soup.find_all('td', attrs={'class': 'row4 small nowrap tor-size'}):
|
||||
size = link.u.string
|
||||
sizes.append(size)
|
||||
|
||||
except :
|
||||
except:
|
||||
pass
|
||||
|
||||
# Combine lists
|
||||
@@ -346,4 +347,3 @@ class Rutracker():
|
||||
except Exception:
|
||||
logger.exception('Error adding file to utorrent')
|
||||
return
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ from headphones import db, utorrent, transmission, logger
|
||||
postprocessor_lock = threading.Lock()
|
||||
|
||||
# Remove Torrent + data if Post Processed and finished Seeding
|
||||
|
||||
|
||||
def checkTorrentFinished():
|
||||
|
||||
logger.info("Checking if any torrents have finished seeding and can be removed")
|
||||
|
||||
@@ -27,17 +27,18 @@ import headphones
|
||||
# TODO: Store the session id so we don't need to make 2 calls
|
||||
# Store torrent id so we can check up on it
|
||||
|
||||
|
||||
def addTorrent(link):
|
||||
method = 'torrent-add'
|
||||
|
||||
if link.endswith('.torrent'):
|
||||
with open(link, 'rb') as f:
|
||||
metainfo = str(base64.b64encode(f.read()))
|
||||
arguments = {'metainfo': metainfo, 'download-dir':headphones.CONFIG.DOWNLOAD_TORRENT_DIR}
|
||||
arguments = {'metainfo': metainfo, 'download-dir': headphones.CONFIG.DOWNLOAD_TORRENT_DIR}
|
||||
else:
|
||||
arguments = {'filename': link, 'download-dir': headphones.CONFIG.DOWNLOAD_TORRENT_DIR}
|
||||
|
||||
response = torrentAction(method,arguments)
|
||||
response = torrentAction(method, arguments)
|
||||
|
||||
if not response:
|
||||
return False
|
||||
@@ -60,9 +61,10 @@ def addTorrent(link):
|
||||
logger.info('Transmission returned status %s' % response['result'])
|
||||
return False
|
||||
|
||||
|
||||
def getTorrentFolder(torrentid):
|
||||
method = 'torrent-get'
|
||||
arguments = { 'ids': torrentid, 'fields': ['name','percentDone']}
|
||||
arguments = { 'ids': torrentid, 'fields': ['name', 'percentDone']}
|
||||
|
||||
response = torrentAction(method, arguments)
|
||||
percentdone = response['arguments']['torrents'][0]['percentDone']
|
||||
@@ -70,8 +72,8 @@ def getTorrentFolder(torrentid):
|
||||
|
||||
tries = 1
|
||||
|
||||
while percentdone == 0 and tries <10:
|
||||
tries+=1
|
||||
while percentdone == 0 and tries < 10:
|
||||
tries += 1
|
||||
time.sleep(5)
|
||||
response = torrentAction(method, arguments)
|
||||
percentdone = response['arguments']['torrents'][0]['percentDone']
|
||||
@@ -80,6 +82,7 @@ def getTorrentFolder(torrentid):
|
||||
|
||||
return torrent_folder_name
|
||||
|
||||
|
||||
def setSeedRatio(torrentid, ratio):
|
||||
method = 'torrent-set'
|
||||
if ratio != 0:
|
||||
@@ -91,7 +94,8 @@ def setSeedRatio(torrentid, ratio):
|
||||
if not response:
|
||||
return False
|
||||
|
||||
def removeTorrent(torrentid, remove_data = False):
|
||||
|
||||
def removeTorrent(torrentid, remove_data=False):
|
||||
|
||||
method = 'torrent-get'
|
||||
arguments = { 'ids': torrentid, 'fields': ['isFinished', 'name']}
|
||||
@@ -120,6 +124,7 @@ def removeTorrent(torrentid, remove_data = False):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def torrentAction(method, arguments):
|
||||
|
||||
host = headphones.CONFIG.TRANSMISSION_HOST
|
||||
|
||||
@@ -17,6 +17,7 @@ import headphones
|
||||
|
||||
from headphones import logger, db, importer
|
||||
|
||||
|
||||
def dbUpdate(forcefull=False):
|
||||
|
||||
myDB = db.DBConnection()
|
||||
|
||||
+14
-8
@@ -21,12 +21,13 @@ import headphones
|
||||
from headphones import logger
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
class utorrentclient(object):
|
||||
|
||||
TOKEN_REGEX = "<div id='token' style='display:none;'>([^<>]+)</div>"
|
||||
UTSetting = namedtuple("UTSetting", ["name", "int", "str", "access"])
|
||||
|
||||
def __init__(self, base_url = None, username = None, password = None,):
|
||||
def __init__(self, base_url=None, username=None, password=None,):
|
||||
|
||||
host = headphones.CONFIG.UTORRENT_HOST
|
||||
if not host.startswith('http'):
|
||||
@@ -48,7 +49,7 @@ class utorrentclient(object):
|
||||
def _make_opener(self, realm, base_url, username, password):
|
||||
"""uTorrent API need HTTP Basic Auth and cookie support for token verify."""
|
||||
auth = urllib2.HTTPBasicAuthHandler()
|
||||
auth.add_password(realm=realm,uri=base_url,user=username,passwd=password)
|
||||
auth.add_password(realm=realm, uri=base_url, user=username, passwd=password)
|
||||
opener = urllib2.build_opener(auth)
|
||||
urllib2.install_opener(opener)
|
||||
|
||||
@@ -132,7 +133,7 @@ class utorrentclient(object):
|
||||
return settings[key]
|
||||
return settings
|
||||
|
||||
def remove(self, hash, remove_data = False):
|
||||
def remove(self, hash, remove_data=False):
|
||||
if remove_data:
|
||||
params = [('action', 'removedata'), ('hash', hash)]
|
||||
else:
|
||||
@@ -156,13 +157,15 @@ class utorrentclient(object):
|
||||
logger.debug('URL: ' + str(url))
|
||||
logger.debug('uTorrent webUI raised the following error: ' + str(err))
|
||||
|
||||
|
||||
def labelTorrent(hash):
|
||||
label = headphones.CONFIG.UTORRENT_LABEL
|
||||
uTorrentClient = utorrentclient()
|
||||
if label:
|
||||
uTorrentClient.setprops(hash,'label',label)
|
||||
uTorrentClient.setprops(hash, 'label', label)
|
||||
|
||||
def removeTorrent(hash, remove_data = False):
|
||||
|
||||
def removeTorrent(hash, remove_data=False):
|
||||
uTorrentClient = utorrentclient()
|
||||
status, torrentList = uTorrentClient.list()
|
||||
torrents = torrentList['torrents']
|
||||
@@ -177,14 +180,16 @@ def removeTorrent(hash, remove_data = False):
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def setSeedRatio(hash, ratio):
|
||||
uTorrentClient = utorrentclient()
|
||||
uTorrentClient.setprops(hash, 'seed_override', '1')
|
||||
if ratio != 0:
|
||||
uTorrentClient.setprops(hash,'seed_ratio', ratio * 10)
|
||||
uTorrentClient.setprops(hash, 'seed_ratio', ratio * 10)
|
||||
else:
|
||||
# TODO passing -1 should be unlimited
|
||||
uTorrentClient.setprops(hash,'seed_ratio', -10)
|
||||
uTorrentClient.setprops(hash, 'seed_ratio', -10)
|
||||
|
||||
|
||||
def dirTorrent(hash, cacheid=None, return_name=None):
|
||||
|
||||
@@ -212,6 +217,7 @@ def dirTorrent(hash, cacheid=None, return_name=None):
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def addTorrent(link, hash):
|
||||
uTorrentClient = utorrentclient()
|
||||
|
||||
@@ -243,6 +249,7 @@ def addTorrent(link, hash):
|
||||
labelTorrent(hash)
|
||||
return os.path.basename(os.path.normpath(torrent_folder))
|
||||
|
||||
|
||||
def getSettingsDirectories():
|
||||
uTorrentClient = utorrentclient()
|
||||
settings = uTorrentClient.get_settings()
|
||||
@@ -253,4 +260,3 @@ def getSettingsDirectories():
|
||||
if 'dir_completed_download' in settings:
|
||||
completed = settings['dir_completed_download'][2]
|
||||
return active, completed
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
HEADPHONES_VERSION = "master"
|
||||
HEADPHONES_VERSION = "master"
|
||||
|
||||
@@ -22,6 +22,7 @@ import subprocess
|
||||
|
||||
from headphones import logger, version, request
|
||||
|
||||
|
||||
def runGit(args):
|
||||
|
||||
if headphones.CONFIG.GIT_PATH:
|
||||
@@ -59,6 +60,7 @@ def runGit(args):
|
||||
|
||||
return (output, err)
|
||||
|
||||
|
||||
def getVersion():
|
||||
|
||||
if version.HEADPHONES_VERSION.startswith('win32build'):
|
||||
@@ -115,6 +117,7 @@ def getVersion():
|
||||
else:
|
||||
return None, 'master'
|
||||
|
||||
|
||||
def checkGithub():
|
||||
headphones.COMMITS_BEHIND = 0
|
||||
|
||||
@@ -161,6 +164,7 @@ def checkGithub():
|
||||
|
||||
return headphones.LATEST_VERSION
|
||||
|
||||
|
||||
def update():
|
||||
if headphones.INSTALL_TYPE == 'win':
|
||||
logger.info('Windows .exe updating not supported yet.')
|
||||
@@ -185,7 +189,7 @@ def update():
|
||||
update_dir = os.path.join(headphones.PROG_DIR, 'update')
|
||||
version_path = os.path.join(headphones.PROG_DIR, 'version.txt')
|
||||
|
||||
logger.info('Downloading update from: '+ tar_download_url)
|
||||
logger.info('Downloading update from: ' + tar_download_url)
|
||||
data = request.request_content(tar_download_url)
|
||||
|
||||
if not data:
|
||||
|
||||
+227
-200
@@ -37,6 +37,7 @@ except ImportError:
|
||||
# Python 2.6.x fallback, from libs
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
|
||||
def serve_template(templatename, **kwargs):
|
||||
|
||||
interface_dir = os.path.join(str(headphones.PROG_DIR), 'data/interfaces/')
|
||||
@@ -50,11 +51,12 @@ def serve_template(templatename, **kwargs):
|
||||
except:
|
||||
return exceptions.html_error_template().render()
|
||||
|
||||
|
||||
class WebInterface(object):
|
||||
|
||||
def index(self):
|
||||
raise cherrypy.HTTPRedirect("home")
|
||||
index.exposed=True
|
||||
index.exposed = True
|
||||
|
||||
def home(self):
|
||||
myDB = db.DBConnection()
|
||||
@@ -97,12 +99,11 @@ class WebInterface(object):
|
||||
extras_dict[extra] = "checked"
|
||||
else:
|
||||
extras_dict[extra] = ""
|
||||
i+=1
|
||||
i += 1
|
||||
|
||||
return serve_template(templatename="artist.html", title=artist['ArtistName'], artist=artist, albums=albums, extras=extras_dict)
|
||||
artistPage.exposed = True
|
||||
|
||||
|
||||
def albumPage(self, AlbumID):
|
||||
myDB = db.DBConnection()
|
||||
album = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone()
|
||||
@@ -122,7 +123,7 @@ class WebInterface(object):
|
||||
raise cherrypy.HTTPRedirect("home")
|
||||
|
||||
if not album['ArtistName']:
|
||||
title = ' - '
|
||||
title = ' - '
|
||||
else:
|
||||
title = album['ArtistName'] + ' - '
|
||||
if not album['AlbumTitle']:
|
||||
@@ -132,7 +133,6 @@ class WebInterface(object):
|
||||
return serve_template(templatename="album.html", title=title, album=album, tracks=tracks, description=description)
|
||||
albumPage.exposed = True
|
||||
|
||||
|
||||
def search(self, name, type):
|
||||
if len(name) == 0:
|
||||
raise cherrypy.HTTPRedirect("home")
|
||||
@@ -215,7 +215,7 @@ class WebInterface(object):
|
||||
myDB = db.DBConnection()
|
||||
namecheck = myDB.select('SELECT ArtistName from artists where ArtistID=?', [ArtistID])
|
||||
for name in namecheck:
|
||||
artistname=name['ArtistName']
|
||||
artistname = name['ArtistName']
|
||||
myDB.action('DELETE from artists WHERE ArtistID=?', [ArtistID])
|
||||
|
||||
from headphones import cache
|
||||
@@ -255,7 +255,7 @@ class WebInterface(object):
|
||||
def refreshArtist(self, ArtistID):
|
||||
threading.Thread(target=importer.addArtisttoDB, args=[ArtistID, False, True]).start()
|
||||
raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID)
|
||||
refreshArtist.exposed=True
|
||||
refreshArtist.exposed = True
|
||||
|
||||
def markAlbums(self, ArtistID=None, action=None, **args):
|
||||
myDB = db.DBConnection()
|
||||
@@ -322,11 +322,11 @@ class WebInterface(object):
|
||||
for result in results:
|
||||
|
||||
result_dict = {
|
||||
'title':result[0],
|
||||
'size':result[1],
|
||||
'url':result[2],
|
||||
'provider':result[3],
|
||||
'kind':result[4]
|
||||
'title': result[0],
|
||||
'size': result[1],
|
||||
'url': result[2],
|
||||
'provider': result[3],
|
||||
'kind': result[4]
|
||||
}
|
||||
results_as_dicts.append(result_dict)
|
||||
|
||||
@@ -344,9 +344,9 @@ class WebInterface(object):
|
||||
url = urllib2.quote(url, safe=":?/=&") + '&' + urllib.urlencode(kwargs)
|
||||
|
||||
try:
|
||||
result = [(title,int(size),url,provider,kind)]
|
||||
result = [(title, int(size), url, provider, kind)]
|
||||
except ValueError:
|
||||
result = [(title,float(size),url,provider,kind)]
|
||||
result = [(title, float(size), url, provider, kind)]
|
||||
|
||||
logger.info(u"Making sure we can download the chosen result")
|
||||
(data, bestqual) = searcher.preprocess(result)
|
||||
@@ -460,19 +460,18 @@ class WebInterface(object):
|
||||
# else:
|
||||
# original_clean = None
|
||||
if original_clean == albums['CleanName']:
|
||||
have_dict = { 'ArtistName' : albums['ArtistName'], 'AlbumTitle' : albums['AlbumTitle'] }
|
||||
have_dict = { 'ArtistName': albums['ArtistName'], 'AlbumTitle': albums['AlbumTitle'] }
|
||||
have_album_dictionary.append(have_dict)
|
||||
headphones_albums = myDB.select('SELECT ArtistName, AlbumTitle from albums ORDER BY ArtistName')
|
||||
for albums in headphones_albums:
|
||||
if albums['ArtistName'] and albums['AlbumTitle']:
|
||||
headphones_dict = { 'ArtistName' : albums['ArtistName'], 'AlbumTitle' : albums['AlbumTitle'] }
|
||||
headphones_dict = { 'ArtistName': albums['ArtistName'], 'AlbumTitle': albums['AlbumTitle'] }
|
||||
headphones_album_dictionary.append(headphones_dict)
|
||||
#unmatchedalbums = [f for f in have_album_dictionary if f not in [x for x in headphones_album_dictionary]]
|
||||
|
||||
check = set([(cleanName(d['ArtistName']).lower(), cleanName(d['AlbumTitle']).lower()) for d in headphones_album_dictionary])
|
||||
unmatchedalbums = [d for d in have_album_dictionary if (cleanName(d['ArtistName']).lower(), cleanName(d['AlbumTitle']).lower()) not in check]
|
||||
|
||||
|
||||
return serve_template(templatename="manageunmatched.html", title="Manage Unmatched Items", unmatchedalbums=unmatchedalbums)
|
||||
manageUnmatched.exposed = True
|
||||
|
||||
@@ -500,9 +499,9 @@ class WebInterface(object):
|
||||
new_clean_filename = old_clean_filename.replace(existing_artist_clean, new_artist_clean, 1)
|
||||
myDB.action('UPDATE have SET CleanName=? WHERE ArtistName=? AND CleanName=?', [new_clean_filename, existing_artist, old_clean_filename])
|
||||
controlValueDict = {"CleanName": new_clean_filename}
|
||||
newValueDict = {"Location" : entry['Location'],
|
||||
"BitRate" : entry['BitRate'],
|
||||
"Format" : entry['Format']
|
||||
newValueDict = {"Location": entry['Location'],
|
||||
"BitRate": entry['BitRate'],
|
||||
"Format": entry['Format']
|
||||
}
|
||||
#Attempt to match tracks with new CleanName
|
||||
match_alltracks = myDB.action('SELECT CleanName from alltracks WHERE CleanName=?', [new_clean_filename]).fetchone()
|
||||
@@ -512,7 +511,7 @@ class WebInterface(object):
|
||||
if match_tracks:
|
||||
myDB.upsert("tracks", newValueDict, controlValueDict)
|
||||
myDB.action('UPDATE have SET Matched="Manual" WHERE CleanName=?', [new_clean_filename])
|
||||
update_count+=1
|
||||
update_count += 1
|
||||
#This was throwing errors and I don't know why, but it seems to be working fine.
|
||||
#else:
|
||||
#logger.info("There was an error modifying Artist %s. This should not have happened" % existing_artist)
|
||||
@@ -538,9 +537,9 @@ class WebInterface(object):
|
||||
new_clean_filename = old_clean_filename.replace(existing_clean_string, new_clean_string, 1)
|
||||
myDB.action('UPDATE have SET CleanName=? WHERE ArtistName=? AND AlbumTitle=? AND CleanName=?', [new_clean_filename, existing_artist, existing_album, old_clean_filename])
|
||||
controlValueDict = {"CleanName": new_clean_filename}
|
||||
newValueDict = {"Location" : entry['Location'],
|
||||
"BitRate" : entry['BitRate'],
|
||||
"Format" : entry['Format']
|
||||
newValueDict = {"Location": entry['Location'],
|
||||
"BitRate": entry['BitRate'],
|
||||
"Format": entry['Format']
|
||||
}
|
||||
#Attempt to match tracks with new CleanName
|
||||
match_alltracks = myDB.action('SELECT CleanName from alltracks WHERE CleanName=?', [new_clean_filename]).fetchone()
|
||||
@@ -551,7 +550,7 @@ class WebInterface(object):
|
||||
myDB.upsert("tracks", newValueDict, controlValueDict)
|
||||
myDB.action('UPDATE have SET Matched="Manual" WHERE CleanName=?', [new_clean_filename])
|
||||
album_id = match_tracks['AlbumID']
|
||||
update_count+=1
|
||||
update_count += 1
|
||||
#This was throwing errors and I don't know why, but it seems to be working fine.
|
||||
#else:
|
||||
#logger.info("There was an error modifying Artist %s / Album %s with clean name %s" % (existing_artist, existing_album, existing_clean_string))
|
||||
@@ -575,7 +574,7 @@ class WebInterface(object):
|
||||
album_status = "Ignored"
|
||||
elif albums['Matched'] == "Manual" or albums['CleanName'] != original_clean:
|
||||
album_status = "Matched"
|
||||
manual_dict = { 'ArtistName' : albums['ArtistName'], 'AlbumTitle' : albums['AlbumTitle'], 'AlbumStatus' : album_status }
|
||||
manual_dict = { 'ArtistName': albums['ArtistName'], 'AlbumTitle': albums['AlbumTitle'], 'AlbumStatus': album_status }
|
||||
if manual_dict not in manual_albums:
|
||||
manual_albums.append(manual_dict)
|
||||
manual_albums_sorted = sorted(manual_albums, key=itemgetter('ArtistName', 'AlbumTitle'))
|
||||
@@ -608,7 +607,7 @@ class WebInterface(object):
|
||||
myDB.action('UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE CleanName=?', [None, None, None, tracks['CleanName']])
|
||||
myDB.action('UPDATE alltracks SET Location=?, BitRate=?, Format=? WHERE CleanName=?', [None, None, None, tracks['CleanName']])
|
||||
myDB.action('UPDATE have SET CleanName=?, Matched="Failed" WHERE ArtistName=? AND AlbumTitle=? AND TrackTitle=?', (original_clean, artist, album, track_title))
|
||||
update_count+=1
|
||||
update_count += 1
|
||||
if update_count > 0:
|
||||
librarysync.update_album_status()
|
||||
logger.info("Artist: %s successfully restored to unmatched list" % artist)
|
||||
@@ -628,7 +627,7 @@ class WebInterface(object):
|
||||
myDB.action('UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE CleanName=?', [None, None, None, tracks['CleanName']])
|
||||
myDB.action('UPDATE alltracks SET Location=?, BitRate=?, Format=? WHERE CleanName=?', [None, None, None, tracks['CleanName']])
|
||||
myDB.action('UPDATE have SET CleanName=?, Matched="Failed" WHERE ArtistName=? AND AlbumTitle=? AND TrackTitle=?', (original_clean, artist, album, track_title))
|
||||
update_count+=1
|
||||
update_count += 1
|
||||
if update_count > 0:
|
||||
librarysync.update_album_status(album_id)
|
||||
logger.info("Album: %s successfully restored to unmatched list" % album)
|
||||
@@ -713,7 +712,7 @@ class WebInterface(object):
|
||||
|
||||
def forcePostProcess(self, dir=None, album_dir=None):
|
||||
from headphones import postprocessor
|
||||
threading.Thread(target=postprocessor.forcePostProcess, kwargs={'dir':dir,'album_dir':album_dir}).start()
|
||||
threading.Thread(target=postprocessor.forcePostProcess, kwargs={'dir': dir, 'album_dir': album_dir}).start()
|
||||
raise cherrypy.HTTPRedirect("home")
|
||||
forcePostProcess.exposed = True
|
||||
|
||||
@@ -747,7 +746,7 @@ class WebInterface(object):
|
||||
raise cherrypy.HTTPRedirect("logs")
|
||||
toggleVerbose.exposed = True
|
||||
|
||||
def getLog(self,iDisplayStart=0,iDisplayLength=100,iSortCol_0=0,sSortDir_0="desc",sSearch="",**kwargs):
|
||||
def getLog(self, iDisplayStart=0, iDisplayLength=100, iSortCol_0=0, sSortDir_0="desc", sSearch="", **kwargs):
|
||||
|
||||
iDisplayStart = int(iDisplayStart)
|
||||
iDisplayLength = int(iDisplayLength)
|
||||
@@ -763,26 +762,25 @@ class WebInterface(object):
|
||||
sortcolumn = 2
|
||||
elif iSortCol_0 == '2':
|
||||
sortcolumn = 1
|
||||
filtered.sort(key=lambda x:x[sortcolumn],reverse=sSortDir_0 == "desc")
|
||||
filtered.sort(key=lambda x: x[sortcolumn], reverse=sSortDir_0 == "desc")
|
||||
|
||||
rows = filtered[iDisplayStart:(iDisplayStart+iDisplayLength)]
|
||||
rows = [[row[0],row[2],row[1]] for row in rows]
|
||||
rows = [[row[0], row[2], row[1]] for row in rows]
|
||||
|
||||
return json.dumps({
|
||||
'iTotalDisplayRecords':len(filtered),
|
||||
'iTotalRecords':len(headphones.LOG_LIST),
|
||||
'aaData':rows,
|
||||
'iTotalDisplayRecords': len(filtered),
|
||||
'iTotalRecords': len(headphones.LOG_LIST),
|
||||
'aaData': rows,
|
||||
})
|
||||
getLog.exposed = True
|
||||
|
||||
def getArtists_json(self,iDisplayStart=0,iDisplayLength=100,sSearch="",iSortCol_0='0',sSortDir_0='asc',**kwargs):
|
||||
def getArtists_json(self, iDisplayStart=0, iDisplayLength=100, sSearch="", iSortCol_0='0', sSortDir_0='asc', **kwargs):
|
||||
iDisplayStart = int(iDisplayStart)
|
||||
iDisplayLength = int(iDisplayLength)
|
||||
filtered = []
|
||||
totalcount = 0
|
||||
myDB = db.DBConnection()
|
||||
|
||||
|
||||
sortcolumn = 'ArtistSortName'
|
||||
sortbyhavepercent = False
|
||||
if iSortCol_0 == '2':
|
||||
@@ -793,36 +791,35 @@ class WebInterface(object):
|
||||
sortbyhavepercent = True
|
||||
|
||||
if sSearch == "":
|
||||
query = 'SELECT * from artists order by %s COLLATE NOCASE %s' % (sortcolumn,sSortDir_0)
|
||||
query = 'SELECT * from artists order by %s COLLATE NOCASE %s' % (sortcolumn, sSortDir_0)
|
||||
filtered = myDB.select(query)
|
||||
totalcount = len(filtered)
|
||||
else:
|
||||
query = 'SELECT * from artists WHERE ArtistSortName LIKE "%' + sSearch + '%" OR LatestAlbum LIKE "%' + sSearch +'%"' + 'ORDER BY %s COLLATE NOCASE %s' % (sortcolumn,sSortDir_0)
|
||||
query = 'SELECT * from artists WHERE ArtistSortName LIKE "%' + sSearch + '%" OR LatestAlbum LIKE "%' + sSearch + '%"' + 'ORDER BY %s COLLATE NOCASE %s' % (sortcolumn, sSortDir_0)
|
||||
filtered = myDB.select(query)
|
||||
totalcount = myDB.select('SELECT COUNT(*) from artists')[0][0]
|
||||
|
||||
if sortbyhavepercent:
|
||||
filtered.sort(key=lambda x:(float(x['HaveTracks'])/x['TotalTracks'] if x['TotalTracks'] > 0 else 0.0,x['HaveTracks'] if x['HaveTracks'] else 0.0),reverse=sSortDir_0 == "asc")
|
||||
filtered.sort(key=lambda x: (float(x['HaveTracks'])/x['TotalTracks'] if x['TotalTracks'] > 0 else 0.0, x['HaveTracks'] if x['HaveTracks'] else 0.0), reverse=sSortDir_0 == "asc")
|
||||
|
||||
#can't figure out how to change the datatables default sorting order when its using an ajax datasource so ill
|
||||
#just reverse it here and the first click on the "Latest Album" header will sort by descending release date
|
||||
if sortcolumn == 'ReleaseDate':
|
||||
filtered.reverse()
|
||||
|
||||
|
||||
artists = filtered[iDisplayStart:(iDisplayStart+iDisplayLength)]
|
||||
rows = []
|
||||
for artist in artists:
|
||||
row = {"ArtistID":artist['ArtistID'],
|
||||
"ArtistName":artist["ArtistName"],
|
||||
"ArtistSortName":artist["ArtistSortName"],
|
||||
"Status":artist["Status"],
|
||||
"TotalTracks":artist["TotalTracks"],
|
||||
"HaveTracks":artist["HaveTracks"],
|
||||
"LatestAlbum":"",
|
||||
"ReleaseDate":"",
|
||||
"ReleaseInFuture":"False",
|
||||
"AlbumID":"",
|
||||
row = {"ArtistID": artist['ArtistID'],
|
||||
"ArtistName": artist["ArtistName"],
|
||||
"ArtistSortName": artist["ArtistSortName"],
|
||||
"Status": artist["Status"],
|
||||
"TotalTracks": artist["TotalTracks"],
|
||||
"HaveTracks": artist["HaveTracks"],
|
||||
"LatestAlbum": "",
|
||||
"ReleaseDate": "",
|
||||
"ReleaseInFuture": "False",
|
||||
"AlbumID": "",
|
||||
}
|
||||
|
||||
if not row['HaveTracks']:
|
||||
@@ -840,15 +837,14 @@ class WebInterface(object):
|
||||
|
||||
rows.append(row)
|
||||
|
||||
|
||||
dict = {'iTotalDisplayRecords':len(filtered),
|
||||
'iTotalRecords':totalcount,
|
||||
'aaData':rows,
|
||||
dict = {'iTotalDisplayRecords': len(filtered),
|
||||
'iTotalRecords': totalcount,
|
||||
'aaData': rows,
|
||||
}
|
||||
s = json.dumps(dict)
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return s
|
||||
getArtists_json.exposed=True
|
||||
getArtists_json.exposed = True
|
||||
|
||||
def getAlbumsByArtist_json(self, artist=None):
|
||||
myDB = db.DBConnection()
|
||||
@@ -857,12 +853,12 @@ class WebInterface(object):
|
||||
album_list = myDB.select("SELECT AlbumTitle from albums WHERE ArtistName=?", [artist])
|
||||
for album in album_list:
|
||||
album_json[counter] = album['AlbumTitle']
|
||||
counter+=1
|
||||
counter += 1
|
||||
json_albums = json.dumps(album_json)
|
||||
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json_albums
|
||||
getAlbumsByArtist_json.exposed=True
|
||||
getAlbumsByArtist_json.exposed = True
|
||||
|
||||
def getArtistjson(self, ArtistID, **kwargs):
|
||||
myDB = db.DBConnection()
|
||||
@@ -872,7 +868,7 @@ class WebInterface(object):
|
||||
'Status': artist['Status']
|
||||
})
|
||||
return artist_json
|
||||
getArtistjson.exposed=True
|
||||
getArtistjson.exposed = True
|
||||
|
||||
def getAlbumjson(self, AlbumID, **kwargs):
|
||||
myDB = db.DBConnection()
|
||||
@@ -883,7 +879,7 @@ class WebInterface(object):
|
||||
'Status': album['Status']
|
||||
})
|
||||
return album_json
|
||||
getAlbumjson.exposed=True
|
||||
getAlbumjson.exposed = True
|
||||
|
||||
def clearhistory(self, type=None, date_added=None, title=None):
|
||||
myDB = db.DBConnection()
|
||||
@@ -936,127 +932,127 @@ class WebInterface(object):
|
||||
interface_list = [ name for name in os.listdir(interface_dir) if os.path.isdir(os.path.join(interface_dir, name)) ]
|
||||
|
||||
config = {
|
||||
"http_host" : headphones.CONFIG.HTTP_HOST,
|
||||
"http_username" : headphones.CONFIG.HTTP_USERNAME,
|
||||
"http_port" : headphones.CONFIG.HTTP_PORT,
|
||||
"http_password" : headphones.CONFIG.HTTP_PASSWORD,
|
||||
"launch_browser" : checked(headphones.CONFIG.LAUNCH_BROWSER),
|
||||
"enable_https" : checked(headphones.CONFIG.ENABLE_HTTPS),
|
||||
"https_cert" : headphones.CONFIG.HTTPS_CERT,
|
||||
"https_key" : headphones.CONFIG.HTTPS_KEY,
|
||||
"api_enabled" : checked(headphones.CONFIG.API_ENABLED),
|
||||
"api_key" : headphones.CONFIG.API_KEY,
|
||||
"download_scan_interval" : headphones.CONFIG.DOWNLOAD_SCAN_INTERVAL,
|
||||
"update_db_interval" : headphones.CONFIG.UPDATE_DB_INTERVAL,
|
||||
"mb_ignore_age" : headphones.CONFIG.MB_IGNORE_AGE,
|
||||
"search_interval" : headphones.CONFIG.SEARCH_INTERVAL,
|
||||
"libraryscan_interval" : headphones.CONFIG.LIBRARYSCAN_INTERVAL,
|
||||
"sab_host" : headphones.CONFIG.SAB_HOST,
|
||||
"sab_username" : headphones.CONFIG.SAB_USERNAME,
|
||||
"sab_apikey" : headphones.CONFIG.SAB_APIKEY,
|
||||
"sab_password" : headphones.CONFIG.SAB_PASSWORD,
|
||||
"sab_category" : headphones.CONFIG.SAB_CATEGORY,
|
||||
"nzbget_host" : headphones.CONFIG.NZBGET_HOST,
|
||||
"nzbget_username" : headphones.CONFIG.NZBGET_USERNAME,
|
||||
"nzbget_password" : headphones.CONFIG.NZBGET_PASSWORD,
|
||||
"nzbget_category" : headphones.CONFIG.NZBGET_CATEGORY,
|
||||
"nzbget_priority" : headphones.CONFIG.NZBGET_PRIORITY,
|
||||
"transmission_host" : headphones.CONFIG.TRANSMISSION_HOST,
|
||||
"transmission_username" : headphones.CONFIG.TRANSMISSION_USERNAME,
|
||||
"transmission_password" : headphones.CONFIG.TRANSMISSION_PASSWORD,
|
||||
"utorrent_host" : headphones.CONFIG.UTORRENT_HOST,
|
||||
"utorrent_username" : headphones.CONFIG.UTORRENT_USERNAME,
|
||||
"utorrent_password" : headphones.CONFIG.UTORRENT_PASSWORD,
|
||||
"utorrent_label" : headphones.CONFIG.UTORRENT_LABEL,
|
||||
"nzb_downloader_sabnzbd" : radio(headphones.CONFIG.NZB_DOWNLOADER, 0),
|
||||
"nzb_downloader_nzbget" : radio(headphones.CONFIG.NZB_DOWNLOADER, 1),
|
||||
"nzb_downloader_blackhole" : radio(headphones.CONFIG.NZB_DOWNLOADER, 2),
|
||||
"torrent_downloader_blackhole" : radio(headphones.CONFIG.TORRENT_DOWNLOADER, 0),
|
||||
"torrent_downloader_transmission" : radio(headphones.CONFIG.TORRENT_DOWNLOADER, 1),
|
||||
"torrent_downloader_utorrent" : radio(headphones.CONFIG.TORRENT_DOWNLOADER, 2),
|
||||
"download_dir" : headphones.CONFIG.DOWNLOAD_DIR,
|
||||
"use_blackhole" : checked(headphones.CONFIG.BLACKHOLE),
|
||||
"blackhole_dir" : headphones.CONFIG.BLACKHOLE_DIR,
|
||||
"usenet_retention" : headphones.CONFIG.USENET_RETENTION,
|
||||
"headphones_indexer" : checked(headphones.CONFIG.HEADPHONES_INDEXER),
|
||||
"use_newznab" : checked(headphones.CONFIG.NEWZNAB),
|
||||
"newznab_host" : headphones.CONFIG.NEWZNAB_HOST,
|
||||
"newznab_apikey" : headphones.CONFIG.NEWZNAB_APIKEY,
|
||||
"newznab_enabled" : checked(headphones.CONFIG.NEWZNAB_ENABLED),
|
||||
"extra_newznabs" : headphones.CONFIG.get_extra_newznabs(),
|
||||
"use_nzbsorg" : checked(headphones.CONFIG.NZBSORG),
|
||||
"nzbsorg_uid" : headphones.CONFIG.NZBSORG_UID,
|
||||
"nzbsorg_hash" : headphones.CONFIG.NZBSORG_HASH,
|
||||
"use_omgwtfnzbs" : checked(headphones.CONFIG.OMGWTFNZBS),
|
||||
"omgwtfnzbs_uid" : headphones.CONFIG.OMGWTFNZBS_UID,
|
||||
"omgwtfnzbs_apikey" : headphones.CONFIG.OMGWTFNZBS_APIKEY,
|
||||
"preferred_words" : headphones.CONFIG.PREFERRED_WORDS,
|
||||
"ignored_words" : headphones.CONFIG.IGNORED_WORDS,
|
||||
"required_words" : headphones.CONFIG.REQUIRED_WORDS,
|
||||
"torrentblackhole_dir" : headphones.CONFIG.TORRENTBLACKHOLE_DIR,
|
||||
"download_torrent_dir" : headphones.CONFIG.DOWNLOAD_TORRENT_DIR,
|
||||
"numberofseeders" : headphones.CONFIG.NUMBEROFSEEDERS,
|
||||
"use_kat" : checked(headphones.CONFIG.KAT),
|
||||
"kat_proxy_url" : headphones.CONFIG.KAT_PROXY_URL,
|
||||
"http_host": headphones.CONFIG.HTTP_HOST,
|
||||
"http_username": headphones.CONFIG.HTTP_USERNAME,
|
||||
"http_port": headphones.CONFIG.HTTP_PORT,
|
||||
"http_password": headphones.CONFIG.HTTP_PASSWORD,
|
||||
"launch_browser": checked(headphones.CONFIG.LAUNCH_BROWSER),
|
||||
"enable_https": checked(headphones.CONFIG.ENABLE_HTTPS),
|
||||
"https_cert": headphones.CONFIG.HTTPS_CERT,
|
||||
"https_key": headphones.CONFIG.HTTPS_KEY,
|
||||
"api_enabled": checked(headphones.CONFIG.API_ENABLED),
|
||||
"api_key": headphones.CONFIG.API_KEY,
|
||||
"download_scan_interval": headphones.CONFIG.DOWNLOAD_SCAN_INTERVAL,
|
||||
"update_db_interval": headphones.CONFIG.UPDATE_DB_INTERVAL,
|
||||
"mb_ignore_age": headphones.CONFIG.MB_IGNORE_AGE,
|
||||
"search_interval": headphones.CONFIG.SEARCH_INTERVAL,
|
||||
"libraryscan_interval": headphones.CONFIG.LIBRARYSCAN_INTERVAL,
|
||||
"sab_host": headphones.CONFIG.SAB_HOST,
|
||||
"sab_username": headphones.CONFIG.SAB_USERNAME,
|
||||
"sab_apikey": headphones.CONFIG.SAB_APIKEY,
|
||||
"sab_password": headphones.CONFIG.SAB_PASSWORD,
|
||||
"sab_category": headphones.CONFIG.SAB_CATEGORY,
|
||||
"nzbget_host": headphones.CONFIG.NZBGET_HOST,
|
||||
"nzbget_username": headphones.CONFIG.NZBGET_USERNAME,
|
||||
"nzbget_password": headphones.CONFIG.NZBGET_PASSWORD,
|
||||
"nzbget_category": headphones.CONFIG.NZBGET_CATEGORY,
|
||||
"nzbget_priority": headphones.CONFIG.NZBGET_PRIORITY,
|
||||
"transmission_host": headphones.CONFIG.TRANSMISSION_HOST,
|
||||
"transmission_username": headphones.CONFIG.TRANSMISSION_USERNAME,
|
||||
"transmission_password": headphones.CONFIG.TRANSMISSION_PASSWORD,
|
||||
"utorrent_host": headphones.CONFIG.UTORRENT_HOST,
|
||||
"utorrent_username": headphones.CONFIG.UTORRENT_USERNAME,
|
||||
"utorrent_password": headphones.CONFIG.UTORRENT_PASSWORD,
|
||||
"utorrent_label": headphones.CONFIG.UTORRENT_LABEL,
|
||||
"nzb_downloader_sabnzbd": radio(headphones.CONFIG.NZB_DOWNLOADER, 0),
|
||||
"nzb_downloader_nzbget": radio(headphones.CONFIG.NZB_DOWNLOADER, 1),
|
||||
"nzb_downloader_blackhole": radio(headphones.CONFIG.NZB_DOWNLOADER, 2),
|
||||
"torrent_downloader_blackhole": radio(headphones.CONFIG.TORRENT_DOWNLOADER, 0),
|
||||
"torrent_downloader_transmission": radio(headphones.CONFIG.TORRENT_DOWNLOADER, 1),
|
||||
"torrent_downloader_utorrent": radio(headphones.CONFIG.TORRENT_DOWNLOADER, 2),
|
||||
"download_dir": headphones.CONFIG.DOWNLOAD_DIR,
|
||||
"use_blackhole": checked(headphones.CONFIG.BLACKHOLE),
|
||||
"blackhole_dir": headphones.CONFIG.BLACKHOLE_DIR,
|
||||
"usenet_retention": headphones.CONFIG.USENET_RETENTION,
|
||||
"headphones_indexer": checked(headphones.CONFIG.HEADPHONES_INDEXER),
|
||||
"use_newznab": checked(headphones.CONFIG.NEWZNAB),
|
||||
"newznab_host": headphones.CONFIG.NEWZNAB_HOST,
|
||||
"newznab_apikey": headphones.CONFIG.NEWZNAB_APIKEY,
|
||||
"newznab_enabled": checked(headphones.CONFIG.NEWZNAB_ENABLED),
|
||||
"extra_newznabs": headphones.CONFIG.get_extra_newznabs(),
|
||||
"use_nzbsorg": checked(headphones.CONFIG.NZBSORG),
|
||||
"nzbsorg_uid": headphones.CONFIG.NZBSORG_UID,
|
||||
"nzbsorg_hash": headphones.CONFIG.NZBSORG_HASH,
|
||||
"use_omgwtfnzbs": checked(headphones.CONFIG.OMGWTFNZBS),
|
||||
"omgwtfnzbs_uid": headphones.CONFIG.OMGWTFNZBS_UID,
|
||||
"omgwtfnzbs_apikey": headphones.CONFIG.OMGWTFNZBS_APIKEY,
|
||||
"preferred_words": headphones.CONFIG.PREFERRED_WORDS,
|
||||
"ignored_words": headphones.CONFIG.IGNORED_WORDS,
|
||||
"required_words": headphones.CONFIG.REQUIRED_WORDS,
|
||||
"torrentblackhole_dir": headphones.CONFIG.TORRENTBLACKHOLE_DIR,
|
||||
"download_torrent_dir": headphones.CONFIG.DOWNLOAD_TORRENT_DIR,
|
||||
"numberofseeders": headphones.CONFIG.NUMBEROFSEEDERS,
|
||||
"use_kat": checked(headphones.CONFIG.KAT),
|
||||
"kat_proxy_url": headphones.CONFIG.KAT_PROXY_URL,
|
||||
"kat_ratio": headphones.CONFIG.KAT_RATIO,
|
||||
"use_piratebay" : checked(headphones.CONFIG.PIRATEBAY),
|
||||
"piratebay_proxy_url" : headphones.CONFIG.PIRATEBAY_PROXY_URL,
|
||||
"use_piratebay": checked(headphones.CONFIG.PIRATEBAY),
|
||||
"piratebay_proxy_url": headphones.CONFIG.PIRATEBAY_PROXY_URL,
|
||||
"piratebay_ratio": headphones.CONFIG.PIRATEBAY_RATIO,
|
||||
"use_mininova" : checked(headphones.CONFIG.MININOVA),
|
||||
"use_mininova": checked(headphones.CONFIG.MININOVA),
|
||||
"mininova_ratio": headphones.CONFIG.MININOVA_RATIO,
|
||||
"use_waffles" : checked(headphones.CONFIG.WAFFLES),
|
||||
"waffles_uid" : headphones.CONFIG.WAFFLES_UID,
|
||||
"use_waffles": checked(headphones.CONFIG.WAFFLES),
|
||||
"waffles_uid": headphones.CONFIG.WAFFLES_UID,
|
||||
"waffles_passkey": headphones.CONFIG.WAFFLES_PASSKEY,
|
||||
"waffles_ratio": headphones.CONFIG.WAFFLES_RATIO,
|
||||
"use_rutracker" : checked(headphones.CONFIG.RUTRACKER),
|
||||
"rutracker_user" : headphones.CONFIG.RUTRACKER_USER,
|
||||
"use_rutracker": checked(headphones.CONFIG.RUTRACKER),
|
||||
"rutracker_user": headphones.CONFIG.RUTRACKER_USER,
|
||||
"rutracker_password": headphones.CONFIG.RUTRACKER_PASSWORD,
|
||||
"rutracker_ratio": headphones.CONFIG.RUTRACKER_RATIO,
|
||||
"use_whatcd" : checked(headphones.CONFIG.WHATCD),
|
||||
"whatcd_username" : headphones.CONFIG.WHATCD_USERNAME,
|
||||
"use_whatcd": checked(headphones.CONFIG.WHATCD),
|
||||
"whatcd_username": headphones.CONFIG.WHATCD_USERNAME,
|
||||
"whatcd_password": headphones.CONFIG.WHATCD_PASSWORD,
|
||||
"whatcd_ratio": headphones.CONFIG.WHATCD_RATIO,
|
||||
"pref_qual_0" : radio(headphones.CONFIG.PREFERRED_QUALITY, 0),
|
||||
"pref_qual_1" : radio(headphones.CONFIG.PREFERRED_QUALITY, 1),
|
||||
"pref_qual_2" : radio(headphones.CONFIG.PREFERRED_QUALITY, 2),
|
||||
"pref_qual_3" : radio(headphones.CONFIG.PREFERRED_QUALITY, 3),
|
||||
"preferred_bitrate" : headphones.CONFIG.PREFERRED_BITRATE,
|
||||
"preferred_bitrate_high" : headphones.CONFIG.PREFERRED_BITRATE_HIGH_BUFFER,
|
||||
"preferred_bitrate_low" : headphones.CONFIG.PREFERRED_BITRATE_LOW_BUFFER,
|
||||
"preferred_bitrate_allow_lossless" : checked(headphones.CONFIG.PREFERRED_BITRATE_ALLOW_LOSSLESS),
|
||||
"detect_bitrate" : checked(headphones.CONFIG.DETECT_BITRATE),
|
||||
"lossless_bitrate_from" : headphones.CONFIG.LOSSLESS_BITRATE_FROM,
|
||||
"lossless_bitrate_to" : headphones.CONFIG.LOSSLESS_BITRATE_TO,
|
||||
"freeze_db" : checked(headphones.CONFIG.FREEZE_DB),
|
||||
"move_files" : checked(headphones.CONFIG.MOVE_FILES),
|
||||
"rename_files" : checked(headphones.CONFIG.RENAME_FILES),
|
||||
"correct_metadata" : checked(headphones.CONFIG.CORRECT_METADATA),
|
||||
"cleanup_files" : checked(headphones.CONFIG.CLEANUP_FILES),
|
||||
"keep_nfo" : checked(headphones.CONFIG.KEEP_NFO),
|
||||
"add_album_art" : checked(headphones.CONFIG.ADD_ALBUM_ART),
|
||||
"album_art_format" : headphones.CONFIG.ALBUM_ART_FORMAT,
|
||||
"embed_album_art" : checked(headphones.CONFIG.EMBED_ALBUM_ART),
|
||||
"embed_lyrics" : checked(headphones.CONFIG.EMBED_LYRICS),
|
||||
"replace_existing_folders" : checked(headphones.CONFIG.REPLACE_EXISTING_FOLDERS),
|
||||
"destination_dir" : headphones.CONFIG.DESTINATION_DIR,
|
||||
"lossless_destination_dir" : headphones.CONFIG.LOSSLESS_DESTINATION_DIR,
|
||||
"folder_format" : headphones.CONFIG.FOLDER_FORMAT,
|
||||
"file_format" : headphones.CONFIG.FILE_FORMAT,
|
||||
"file_underscores" : checked(headphones.CONFIG.FILE_UNDERSCORES),
|
||||
"include_extras" : checked(headphones.CONFIG.INCLUDE_EXTRAS),
|
||||
"autowant_upcoming" : checked(headphones.CONFIG.AUTOWANT_UPCOMING),
|
||||
"autowant_all" : checked(headphones.CONFIG.AUTOWANT_ALL),
|
||||
"autowant_manually_added" : checked(headphones.CONFIG.AUTOWANT_MANUALLY_ADDED),
|
||||
"keep_torrent_files" : checked(headphones.CONFIG.KEEP_TORRENT_FILES),
|
||||
"prefer_torrents_0" : radio(headphones.CONFIG.PREFER_TORRENTS, 0),
|
||||
"prefer_torrents_1" : radio(headphones.CONFIG.PREFER_TORRENTS, 1),
|
||||
"prefer_torrents_2" : radio(headphones.CONFIG.PREFER_TORRENTS, 2),
|
||||
"magnet_links_0" : radio(headphones.CONFIG.MAGNET_LINKS, 0),
|
||||
"magnet_links_1" : radio(headphones.CONFIG.MAGNET_LINKS, 1),
|
||||
"magnet_links_2" : radio(headphones.CONFIG.MAGNET_LINKS, 2),
|
||||
"log_dir" : headphones.CONFIG.LOG_DIR,
|
||||
"cache_dir" : headphones.CONFIG.CACHE_DIR,
|
||||
"interface_list" : interface_list,
|
||||
"pref_qual_0": radio(headphones.CONFIG.PREFERRED_QUALITY, 0),
|
||||
"pref_qual_1": radio(headphones.CONFIG.PREFERRED_QUALITY, 1),
|
||||
"pref_qual_2": radio(headphones.CONFIG.PREFERRED_QUALITY, 2),
|
||||
"pref_qual_3": radio(headphones.CONFIG.PREFERRED_QUALITY, 3),
|
||||
"preferred_bitrate": headphones.CONFIG.PREFERRED_BITRATE,
|
||||
"preferred_bitrate_high": headphones.CONFIG.PREFERRED_BITRATE_HIGH_BUFFER,
|
||||
"preferred_bitrate_low": headphones.CONFIG.PREFERRED_BITRATE_LOW_BUFFER,
|
||||
"preferred_bitrate_allow_lossless": checked(headphones.CONFIG.PREFERRED_BITRATE_ALLOW_LOSSLESS),
|
||||
"detect_bitrate": checked(headphones.CONFIG.DETECT_BITRATE),
|
||||
"lossless_bitrate_from": headphones.CONFIG.LOSSLESS_BITRATE_FROM,
|
||||
"lossless_bitrate_to": headphones.CONFIG.LOSSLESS_BITRATE_TO,
|
||||
"freeze_db": checked(headphones.CONFIG.FREEZE_DB),
|
||||
"move_files": checked(headphones.CONFIG.MOVE_FILES),
|
||||
"rename_files": checked(headphones.CONFIG.RENAME_FILES),
|
||||
"correct_metadata": checked(headphones.CONFIG.CORRECT_METADATA),
|
||||
"cleanup_files": checked(headphones.CONFIG.CLEANUP_FILES),
|
||||
"keep_nfo": checked(headphones.CONFIG.KEEP_NFO),
|
||||
"add_album_art": checked(headphones.CONFIG.ADD_ALBUM_ART),
|
||||
"album_art_format": headphones.CONFIG.ALBUM_ART_FORMAT,
|
||||
"embed_album_art": checked(headphones.CONFIG.EMBED_ALBUM_ART),
|
||||
"embed_lyrics": checked(headphones.CONFIG.EMBED_LYRICS),
|
||||
"replace_existing_folders": checked(headphones.CONFIG.REPLACE_EXISTING_FOLDERS),
|
||||
"destination_dir": headphones.CONFIG.DESTINATION_DIR,
|
||||
"lossless_destination_dir": headphones.CONFIG.LOSSLESS_DESTINATION_DIR,
|
||||
"folder_format": headphones.CONFIG.FOLDER_FORMAT,
|
||||
"file_format": headphones.CONFIG.FILE_FORMAT,
|
||||
"file_underscores": checked(headphones.CONFIG.FILE_UNDERSCORES),
|
||||
"include_extras": checked(headphones.CONFIG.INCLUDE_EXTRAS),
|
||||
"autowant_upcoming": checked(headphones.CONFIG.AUTOWANT_UPCOMING),
|
||||
"autowant_all": checked(headphones.CONFIG.AUTOWANT_ALL),
|
||||
"autowant_manually_added": checked(headphones.CONFIG.AUTOWANT_MANUALLY_ADDED),
|
||||
"keep_torrent_files": checked(headphones.CONFIG.KEEP_TORRENT_FILES),
|
||||
"prefer_torrents_0": radio(headphones.CONFIG.PREFER_TORRENTS, 0),
|
||||
"prefer_torrents_1": radio(headphones.CONFIG.PREFER_TORRENTS, 1),
|
||||
"prefer_torrents_2": radio(headphones.CONFIG.PREFER_TORRENTS, 2),
|
||||
"magnet_links_0": radio(headphones.CONFIG.MAGNET_LINKS, 0),
|
||||
"magnet_links_1": radio(headphones.CONFIG.MAGNET_LINKS, 1),
|
||||
"magnet_links_2": radio(headphones.CONFIG.MAGNET_LINKS, 2),
|
||||
"log_dir": headphones.CONFIG.LOG_DIR,
|
||||
"cache_dir": headphones.CONFIG.CACHE_DIR,
|
||||
"interface_list": interface_list,
|
||||
"music_encoder": checked(headphones.CONFIG.MUSIC_ENCODER),
|
||||
"encoder": headphones.CONFIG.ENCODER,
|
||||
"xldprofile": headphones.CONFIG.XLDPROFILE,
|
||||
@@ -1161,7 +1157,7 @@ class WebInterface(object):
|
||||
extras_dict[extra] = "checked"
|
||||
else:
|
||||
extras_dict[extra] = ""
|
||||
i+=1
|
||||
i += 1
|
||||
|
||||
config["extras"] = extras_dict
|
||||
|
||||
@@ -1170,18 +1166,45 @@ class WebInterface(object):
|
||||
|
||||
def configUpdate(self, **kwargs):
|
||||
# Handle the variable config options. Note - keys with False values aren't getting passed
|
||||
headphones.CONFIG.clear_extra_newznabs()
|
||||
for kwarg in kwargs:
|
||||
if kwarg.startswith('newznab_host'):
|
||||
newznab_number = kwarg[12:]
|
||||
if len(newznab_number):
|
||||
newznab_host = kwargs.get('newznab_host' + newznab_number)
|
||||
newznab_api = kwargs.get('newznab_api' + newznab_number)
|
||||
try:
|
||||
newznab_enabled = int(kwargs.get('newznab_enabled' + newznab_number))
|
||||
except KeyError:
|
||||
newznab_enabled = 0
|
||||
headphones.CONFIG.add_extra_newznab((newznab_host, newznab_api, newznab_enabled))
|
||||
|
||||
checked_configs = [
|
||||
"launch_browser", "enable_https", "api_enabled", "use_blackhole", "headphones_indexer", "use_newznab", "newznab_enabled",
|
||||
"use_nzbsorg", "use_omgwtfnzbs", "use_kat", "use_piratebay", "use_mininova", "use_waffles", "use_rutracker", "use_whatcd",
|
||||
"preferred_bitrate_allow_lossless", "detect_bitrate", "freeze_db", "move_files", "rename_files", "correct_metadata",
|
||||
"cleanup_files", "keep_nfo", "add_album_art", "embed_album_art", "embed_lyrics", "replace_existing_folders", "file_underscores",
|
||||
"include_extras", "autowant_upcoming", "autowant_all", "autowant_manually_added", "keep_torrent_files", "music_encoder",
|
||||
"encoderlossless", "encoder_multicore", "delete_lossless_files", "growl_enabled", "growl_onsnatch", "prowl_enabled",
|
||||
"prowl_onsnatch", "xbmc_enabled", "xbmc_update", "xbmc_notify", "lms_enabled", "plex_enabled", "plex_update", "plex_notify",
|
||||
"nma_enabled", "nma_onsnatch", "pushalot_enabled", "pushalot_onsnatch", "synoindex_enabled", "pushover_enabled",
|
||||
"pushover_onsnatch", "pushbullet_enabled", "pushbullet_onsnatch", "subsonic_enabled", "twitter_enabled", "twitter_onsnatch",
|
||||
"osx_notify_enabled", "osx_notify_onsnatch", "boxcar_enabled", "boxcar_onsnatch", "songkick_enabled", "songkick_filter_enabled",
|
||||
"mpc_enabled"
|
||||
]
|
||||
for checked_config in checked_configs:
|
||||
if checked_config not in kwargs:
|
||||
# checked items should be zero or one. if they were not sent then the item was not checked
|
||||
kwargs[checked_config] = 0
|
||||
|
||||
for plain_config, use_config in [(x[4:], x) for x in kwargs if x.startswith('use_')]:
|
||||
# the use prefix is fairly nice in the html, but does not match the actual config
|
||||
kwargs[plain_config] = kwargs[use_config]
|
||||
del kwargs[use_config]
|
||||
|
||||
extra_newznabs = []
|
||||
for kwarg in [x for x in kwargs if x.startswith('newznab_host')]:
|
||||
newznab_host_key = kwarg
|
||||
newznab_number = kwarg[12:]
|
||||
if len(newznab_number):
|
||||
newznab_api_key = 'newznab_api' + newznab_number
|
||||
newznab_enabled_key = 'newznab_enabled' + newznab_number
|
||||
newznab_host = kwargs.get(newznab_host_key, '')
|
||||
newznab_api = kwargs.get(newznab_api_key, '')
|
||||
newznab_enabled = int(kwargs.get(newznab_enabled_key, 0))
|
||||
for key in [newznab_host_key, newznab_api_key, newznab_enabled_key]:
|
||||
if key in kwargs:
|
||||
del kwargs[key]
|
||||
extra_newznabs.append((newznab_host, newznab_api, newznab_enabled))
|
||||
|
||||
|
||||
# Convert the extras to list then string. Coming in as 0 or 1 (append new extras to the end)
|
||||
temp_extras_list = []
|
||||
@@ -1198,7 +1221,7 @@ class WebInterface(object):
|
||||
for extra in extras_list:
|
||||
if extra:
|
||||
temp_extras_list.append(i)
|
||||
i+=1
|
||||
i += 1
|
||||
|
||||
for extra in expected_extras:
|
||||
temp = '%s_temp' % extra
|
||||
@@ -1208,8 +1231,10 @@ class WebInterface(object):
|
||||
del kwargs[extra]
|
||||
|
||||
headphones.CONFIG.EXTRAS = ','.join(str(n) for n in temp_extras_list)
|
||||
|
||||
headphones.CONFIG.clear_extra_newznabs()
|
||||
headphones.CONFIG.process_kwargs(kwargs)
|
||||
for extra_newznab in extra_newznabs:
|
||||
headphones.CONFIG.add_extra_newznab(extra_newznab)
|
||||
|
||||
# Sanity checking
|
||||
if headphones.CONFIG.SEARCH_INTERVAL < 360:
|
||||
@@ -1312,7 +1337,7 @@ class WebInterface(object):
|
||||
if AlbumID and not image_dict:
|
||||
image_url = "http://coverartarchive.org/release/%s/front-500.jpg" % AlbumID
|
||||
thumb_url = "http://coverartarchive.org/release/%s/front-250.jpg" % AlbumID
|
||||
image_dict = {'artwork' : image_url, 'thumbnail' : thumb_url}
|
||||
image_dict = {'artwork': image_url, 'thumbnail': thumb_url}
|
||||
elif AlbumID and (not image_dict['artwork'] or not image_dict['thumbnail']):
|
||||
if not image_dict['artwork']:
|
||||
image_dict['artwork'] = "http://coverartarchive.org/release/%s/front-500.jpg" % AlbumID
|
||||
@@ -1363,12 +1388,13 @@ class WebInterface(object):
|
||||
return msg
|
||||
osxnotifyregister.exposed = True
|
||||
|
||||
|
||||
class Artwork(object):
|
||||
def index(self):
|
||||
return "Artwork"
|
||||
index.exposed = True
|
||||
|
||||
def default(self,ArtistOrAlbum="",ID=None):
|
||||
def default(self, ArtistOrAlbum="", ID=None):
|
||||
from headphones import cache
|
||||
ArtistID = None
|
||||
AlbumID = None
|
||||
@@ -1377,23 +1403,23 @@ class Artwork(object):
|
||||
elif ArtistOrAlbum == "album":
|
||||
AlbumID = ID
|
||||
|
||||
relpath = cache.getArtwork(ArtistID,AlbumID)
|
||||
relpath = cache.getArtwork(ArtistID, AlbumID)
|
||||
|
||||
if not relpath:
|
||||
relpath = "data/interfaces/default/images/no-cover-art.png"
|
||||
basedir = os.path.dirname(sys.argv[0])
|
||||
path = os.path.join(basedir,relpath)
|
||||
path = os.path.join(basedir, relpath)
|
||||
cherrypy.response.headers['Content-type'] = 'image/png'
|
||||
cherrypy.response.headers['Cache-Control'] = 'no-cache'
|
||||
else:
|
||||
relpath = relpath.replace('cache/','',1)
|
||||
path = os.path.join(headphones.CONFIG.CACHE_DIR,relpath)
|
||||
relpath = relpath.replace('cache/', '', 1)
|
||||
path = os.path.join(headphones.CONFIG.CACHE_DIR, relpath)
|
||||
fileext = os.path.splitext(relpath)[1][1::]
|
||||
cherrypy.response.headers['Content-type'] = 'image/' + fileext
|
||||
cherrypy.response.headers['Cache-Control'] = 'max-age=31556926'
|
||||
|
||||
path = os.path.normpath(path)
|
||||
f = open(path,'rb')
|
||||
f = open(path, 'rb')
|
||||
return f.read()
|
||||
default.exposed = True
|
||||
|
||||
@@ -1401,7 +1427,8 @@ class Artwork(object):
|
||||
def index(self):
|
||||
return "Here be thumbs"
|
||||
index.exposed = True
|
||||
def default(self,ArtistOrAlbum="",ID=None):
|
||||
|
||||
def default(self, ArtistOrAlbum="", ID=None):
|
||||
from headphones import cache
|
||||
ArtistID = None
|
||||
AlbumID = None
|
||||
@@ -1410,23 +1437,23 @@ class Artwork(object):
|
||||
elif ArtistOrAlbum == "album":
|
||||
AlbumID = ID
|
||||
|
||||
relpath = cache.getThumb(ArtistID,AlbumID)
|
||||
relpath = cache.getThumb(ArtistID, AlbumID)
|
||||
|
||||
if not relpath:
|
||||
relpath = "data/interfaces/default/images/no-cover-artist.png"
|
||||
basedir = os.path.dirname(sys.argv[0])
|
||||
path = os.path.join(basedir,relpath)
|
||||
path = os.path.join(basedir, relpath)
|
||||
cherrypy.response.headers['Content-type'] = 'image/png'
|
||||
cherrypy.response.headers['Cache-Control'] = 'no-cache'
|
||||
else:
|
||||
relpath = relpath.replace('cache/','',1)
|
||||
path = os.path.join(headphones.CONFIG.CACHE_DIR,relpath)
|
||||
relpath = relpath.replace('cache/', '', 1)
|
||||
path = os.path.join(headphones.CONFIG.CACHE_DIR, relpath)
|
||||
fileext = os.path.splitext(relpath)[1][1::]
|
||||
cherrypy.response.headers['Content-type'] = 'image/' + fileext
|
||||
cherrypy.response.headers['Cache-Control'] = 'max-age=31556926'
|
||||
|
||||
path = os.path.normpath(path)
|
||||
f = open(path,'rb')
|
||||
f = open(path, 'rb')
|
||||
return f.read()
|
||||
default.exposed = True
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ from headphones import logger
|
||||
from headphones.webserve import WebInterface
|
||||
from headphones.helpers import create_https_certificates
|
||||
|
||||
|
||||
def initialize(options=None):
|
||||
if options is None:
|
||||
options = {}
|
||||
@@ -71,28 +72,28 @@ def initialize(options=None):
|
||||
'tools.staticdir.root': os.path.join(headphones.PROG_DIR, 'data'),
|
||||
'tools.proxy.on': options['http_proxy'] # pay attention to X-Forwarded-Proto header
|
||||
},
|
||||
'/interfaces':{
|
||||
'/interfaces': {
|
||||
'tools.staticdir.on': True,
|
||||
'tools.staticdir.dir': "interfaces"
|
||||
},
|
||||
'/images':{
|
||||
'/images': {
|
||||
'tools.staticdir.on': True,
|
||||
'tools.staticdir.dir': "images"
|
||||
},
|
||||
'/css':{
|
||||
'/css': {
|
||||
'tools.staticdir.on': True,
|
||||
'tools.staticdir.dir': "css"
|
||||
},
|
||||
'/js':{
|
||||
'/js': {
|
||||
'tools.staticdir.on': True,
|
||||
'tools.staticdir.dir': "js"
|
||||
},
|
||||
'/favicon.ico':{
|
||||
'/favicon.ico': {
|
||||
'tools.staticfile.on': True,
|
||||
'tools.staticfile.filename': os.path.join(os.path.abspath(
|
||||
os.curdir), "images" + os.sep + "favicon.ico")
|
||||
},
|
||||
'/cache':{
|
||||
'/cache': {
|
||||
'tools.staticdir.on': True,
|
||||
'tools.staticdir.dir': headphones.CONFIG.CACHE_DIR
|
||||
}
|
||||
@@ -111,7 +112,6 @@ def initialize(options=None):
|
||||
})
|
||||
conf['/api'] = { 'tools.auth_basic.on': False }
|
||||
|
||||
|
||||
# Prevent time-outs
|
||||
cherrypy.engine.timeout_monitor.unsubscribe()
|
||||
cherrypy.tree.mount(WebInterface(), str(options['http_root']), config=conf)
|
||||
|
||||
Reference in New Issue
Block a user