From e88049dd960961942f561418f35349da36a5cc72 Mon Sep 17 00:00:00 2001 From: sbuser Date: Mon, 8 Aug 2011 13:46:52 -0500 Subject: [PATCH 01/10] bugfix --- headphones/searcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headphones/searcher.py b/headphones/searcher.py index 824bb41d..1459481b 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -407,7 +407,7 @@ def searchNZB(albumid=None, new=False): if headphones.SAB_HOST and not headphones.BLACKHOLE: nzb = classes.NZBDataSearchResult() - nzb.extraInfo.append(data) + nzb.extraInfo.append(data) nzb.name = nzb_folder_name sab.sendNZB(nzb) From c07bef0db1e630d8a8203d78d69ce87cc9985af1 Mon Sep 17 00:00:00 2001 From: sbuser Date: Mon, 8 Aug 2011 18:26:10 -0500 Subject: [PATCH 02/10] Keep a local cache of releaseID->releaseGroupID relationships so we don't hammer MB unnecessarily when external programs addReleaseByID --- headphones/__init__.py | 1 + headphones/importer.py | 33 +++++++++++++++++++-------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/headphones/__init__.py b/headphones/__init__.py index 6939f8b8..47e8ed29 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -431,6 +431,7 @@ def dbcheck(): c.execute('CREATE TABLE IF NOT EXISTS have (ArtistName TEXT, AlbumTitle TEXT, TrackNumber TEXT, TrackTitle TEXT, TrackLength TEXT, BitRate TEXT, Genre TEXT, Date TEXT, TrackID TEXT)') c.execute('CREATE TABLE IF NOT EXISTS lastfmcloud (ArtistName TEXT, ArtistID TEXT, Count INTEGER)') c.execute('CREATE TABLE IF NOT EXISTS descriptions (ReleaseGroupID TEXT, ReleaseID TEXT, Summary TEXT, Content TEXT)') + c.execute('CREATE TABLE IF NOT EXISTS releases (ReleaseID TEXT, ReleaseGroupID TEXT, UNIQUE(ReleaseID, ReleaseGroupID))') try: c.execute('SELECT IncludeExtras from artists') diff --git a/headphones/importer.py b/headphones/importer.py index 4f8dae3b..cfcd22ee 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -256,21 +256,26 @@ def addArtisttoDB(artistid, extrasonly=False): logger.info(u"Updating complete for: " + artist['artist_name']) def addReleaseById(rid): - + myDB = db.DBConnection() + + rgid = myDB.select("SELECT * from releases WHERE ReleaseID=?", [rid]) + if not rgid: + #we have to make a call to get the release no matter what so we can get the RGID + #need a way around this - a local cache maybe in the future maybe? + try: + release_dict = mb.getRelease(rid) + #keep a local cache of these so that external programs that are adding releasesByID don't hammer MB + myDB.action('INSERT INTO releases VALUES( ?, ?)', [rid, release_dict['rgid']]) + except Exception, e: + logger.info('Unable to get release information for Release: ' + str(rid) + " " + str(e)) + return + if not release_dict: + logger.info('Unable to get release information for Release: ' + str(rid) + " no dict") + return + + rgid = release_dict['rgid'] - #we have to make a call to get the release no matter what so we can get the RGID - #need a way around this - a local cache maybe in the future maybe? - try: - release_dict = mb.getRelease(rid) - except Exception, e: - logger.info('Unable to get release information for Release: ' + str(rid) + " " + str(e)) - return - if not release_dict: - logger.info('Unable to get release information for Release: ' + str(rid) + " no dict") - return - - rgid = release_dict['rgid'] #we don't want to make more calls to MB here unless we have to, could be happening quite a lot #TODO: why do I have to str() this here? I don't get it. @@ -316,7 +321,7 @@ def addReleaseById(rid): for track in release_dict['tracks']: controlValueDict = {"TrackID": track['id'], - "AlbumID": release_dict['rgid']} + "AlbumID": rgid} newValueDict = {"ArtistID": release_dict['artist_id'], "ArtistName": release_dict['artist_name'], "AlbumTitle": release_dict['rg_title'], From 12406b67571268ef35e1d66a0397db1b590a1030 Mon Sep 17 00:00:00 2001 From: sbuser Date: Mon, 8 Aug 2011 18:33:51 -0500 Subject: [PATCH 03/10] Bugfix for local ReleaseID->ReleaseGroupID cache --- headphones/importer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/headphones/importer.py b/headphones/importer.py index cfcd22ee..b7231cc3 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -259,7 +259,9 @@ def addReleaseById(rid): myDB = db.DBConnection() - rgid = myDB.select("SELECT * from releases WHERE ReleaseID=?", [rid]) + results = myDB.select("SELECT ReleaseGroupID from releases WHERE ReleaseID=?", [rid]) + for result in results: + rgid = result['ReleaseGroupID'] if not rgid: #we have to make a call to get the release no matter what so we can get the RGID #need a way around this - a local cache maybe in the future maybe? From 089af04098dfdcabd87e2a02c8215da5016135ab Mon Sep 17 00:00:00 2001 From: sbuser Date: Mon, 8 Aug 2011 19:29:27 -0500 Subject: [PATCH 04/10] More bug fixes for ReleaseID->ReleaseGroupID cache. --- headphones/importer.py | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/headphones/importer.py b/headphones/importer.py index b7231cc3..4b14e43c 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -259,16 +259,19 @@ def addReleaseById(rid): myDB = db.DBConnection() - results = myDB.select("SELECT ReleaseGroupID from releases WHERE ReleaseID=?", [rid]) + rgid = None + artistid = None + release_dict = None + results = myDB.select("SELECT albums.ArtistID, releases.ReleaseGroupID from releases, albums WHERE releases.ReleaseID=? and releases.ReleaseGroupID=albums.AlbumID LIMIT 1", [rid]) for result in results: rgid = result['ReleaseGroupID'] + artistid = result['ArtistID'] + logger.debug("Found a cached releaseid : releasegroupid relationship: " + rid + " : " + rgid) if not rgid: - #we have to make a call to get the release no matter what so we can get the RGID - #need a way around this - a local cache maybe in the future maybe? + #didn't find it in the cache, get the information from MB + logger.debug("Didn't find releaseID %s in the cache. Looking up its ReleaseGroupID", [rid]) try: release_dict = mb.getRelease(rid) - #keep a local cache of these so that external programs that are adding releasesByID don't hammer MB - myDB.action('INSERT INTO releases VALUES( ?, ?)', [rid, release_dict['rgid']]) except Exception, e: logger.info('Unable to get release information for Release: ' + str(rid) + " " + str(e)) return @@ -277,15 +280,15 @@ def addReleaseById(rid): return rgid = release_dict['rgid'] - + artistid = release_dict['artist_id'] #we don't want to make more calls to MB here unless we have to, could be happening quite a lot - #TODO: why do I have to str() this here? I don't get it. rg_exists = myDB.select("SELECT * from albums WHERE AlbumID=?", [rgid]) #make sure the artist exists since I don't know what happens later if it doesn't - artist_exists = myDB.select("SELECT * from artists WHERE ArtistID=?", [release_dict['artist_id']]) - if not artist_exists: + artist_exists = myDB.select("SELECT * from artists WHERE ArtistID=?", [artistid]) + + if not artist_exists and release_dict: if release_dict['artist_name'].startswith('The '): sortname = release_dict['artist_name'][4:] else: @@ -303,8 +306,13 @@ def addReleaseById(rid): newValueDict['IncludeExtras'] = 1 myDB.upsert("artists", newValueDict, controlValueDict) - - if not rg_exists: + + elif not artist_exists and not release_dict: + logger.error("Artist does not exist in the database and did not get a valid response from MB. Skipping release.") + return + + if not rg_exists and release_dict: #it should never be the case that we have an rg and not the artist + #but if it is this will fail logger.info(u"Now adding-by-id album (" + release_dict['title'] + ") from id: " + rgid) controlValueDict = {"AlbumID": rgid} @@ -319,6 +327,9 @@ def addReleaseById(rid): } myDB.upsert("albums", newValueDict, controlValueDict) + + #keep a local cache of these so that external programs that are adding releasesByID don't hammer MB + myDB.action('INSERT INTO releases VALUES( ?, ?)', [rid, release_dict['rgid']]) for track in release_dict['tracks']: @@ -334,10 +345,12 @@ def addReleaseById(rid): } myDB.upsert("tracks", newValueDict, controlValueDict) - - + #start a search for the album import searcher searcher.searchNZB(rgid, False) + elif not rg_exists and not release_dict: + logger.error("ReleaseGroup does not exist in the database and did not get a valid response from MB. Skipping release.") + return else: logger.info('Release ' + str(rid) + " already exists in the database!") \ No newline at end of file From 696647b9f093ba5706c34d40812d000bcbc3620d Mon Sep 17 00:00:00 2001 From: sbuser Date: Mon, 8 Aug 2011 20:19:53 -0500 Subject: [PATCH 05/10] Still bug fixing ReleaseID lookups. --- headphones/importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headphones/importer.py b/headphones/importer.py index 4b14e43c..5ddbb20e 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -269,7 +269,7 @@ def addReleaseById(rid): logger.debug("Found a cached releaseid : releasegroupid relationship: " + rid + " : " + rgid) if not rgid: #didn't find it in the cache, get the information from MB - logger.debug("Didn't find releaseID %s in the cache. Looking up its ReleaseGroupID", [rid]) + logger.debug("Didn't find releaseID " + rid + " in the cache. Looking up its ReleaseGroupID") try: release_dict = mb.getRelease(rid) except Exception, e: From c2092211f8e90cfc7348e59150ad0efb4dfc71a0 Mon Sep 17 00:00:00 2001 From: sbuser Date: Tue, 9 Aug 2011 18:27:35 -0500 Subject: [PATCH 06/10] Handle an error in searcher. --- headphones/searcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headphones/searcher.py b/headphones/searcher.py index 28f717a7..35def60f 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -453,7 +453,7 @@ def getresultNZB(result): else: try: nzb = urllib2.urlopen(result[2], timeout=20).read() - except: + except urllib2.URLError, e: logger.warn('Error fetching nzb from url: ' + result[2] + ' %s' % e) return nzb From 63b48a06639b3279f1b762d668f38f4fa795095d Mon Sep 17 00:00:00 2001 From: sbuser Date: Tue, 9 Aug 2011 18:33:11 -0500 Subject: [PATCH 07/10] Fixed Newzbin downloads in new searcher with preprocessing. Upped timeout as my newznab provider lags. Make a config option for this? --- headphones/searcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/headphones/searcher.py b/headphones/searcher.py index 35def60f..66216222 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -442,7 +442,7 @@ def verifyresult(title, term): return True def getresultNZB(result): - if result[3] == 'Newzbin': + if result[3] == 'newzbin': params = urllib.urlencode({"username": headphones.NEWZBIN_UID, "password": headphones.NEWZBIN_PASSWORD, "reportid": result[2]}) url = "https://www.newzbin.com" + "/api/dnzb/" urllib._urlopener = NewzbinDownloader() @@ -452,7 +452,7 @@ def getresultNZB(result): logger.warn('Error fetching nzb from url: ' + url + ' %s' % e) else: try: - nzb = urllib2.urlopen(result[2], timeout=20).read() + nzb = urllib2.urlopen(result[2], timeout=30).read() except urllib2.URLError, e: logger.warn('Error fetching nzb from url: ' + result[2] + ' %s' % e) return nzb From ff5776659fa5dfbf248763e641040e8054792642 Mon Sep 17 00:00:00 2001 From: Remy Date: Tue, 9 Aug 2011 17:43:36 -0700 Subject: [PATCH 08/10] Added customizable interfaces option to config --- data/interfaces/default/config.html | 20 ++++++++++++++++++-- headphones/__init__.py | 7 ++++++- headphones/webserve.py | 14 +++++++++++--- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index fe464f75..5f4aa2da 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -1,4 +1,8 @@ <%inherit file="base.html"/> +<%! + import headphones +%> + <%def name="headerIncludes()">
    @@ -234,8 +238,20 @@

    Miscellaneous:


    -

    Automatically Include Extras When Adding an Artist


    - Extras includes: EPs, Compilations, Live Albums, Remix Albums and Singles +

    Automatically Include Extras When Adding an Artist

    + (EPs, Compilations, Live Albums, Remix Albums and Singles) +

    +

    Interface:

    Log Directory:

    diff --git a/headphones/__init__.py b/headphones/__init__.py index 453266cc..5c128dda 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -104,6 +104,8 @@ LASTFM_USERNAME = None MEDIA_FORMATS = ["mp3", "flac", "aac", "ogg", "ape", "m4a"] +INTERFACE = None + def CheckSection(sec): """ Check if INI section exists, if not create it """ try: @@ -161,7 +163,7 @@ def initialize(): ADD_ALBUM_ART, EMBED_ALBUM_ART, DOWNLOAD_DIR, BLACKHOLE, BLACKHOLE_DIR, USENET_RETENTION, NZB_SEARCH_INTERVAL, \ LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \ NZBMATRIX, NZBMATRIX_USERNAME, NZBMATRIX_APIKEY, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, \ - NZBSORG, NZBSORG_UID, NZBSORG_HASH, NEWZBIN, NEWZBIN_UID, NEWZBIN_PASSWORD, LASTFM_USERNAME + NZBSORG, NZBSORG_UID, NZBSORG_HASH, NEWZBIN, NEWZBIN_UID, NEWZBIN_PASSWORD, LASTFM_USERNAME, INTERFACE if __INITIALIZED__: return False @@ -237,6 +239,8 @@ def initialize(): NEWZBIN_PASSWORD = check_setting_str(CFG, 'Newzbin', 'newzbin_password', '') LASTFM_USERNAME = check_setting_str(CFG, 'General', 'lastfm_username', '') + + INTERFACE = check_setting_str(CFG, 'General', 'interface', 'default') if not LOG_DIR: LOG_DIR = os.path.join(DATA_DIR, 'logs') @@ -403,6 +407,7 @@ def config_write(): new_config['Newzbin']['newzbin_password'] = NEWZBIN_PASSWORD new_config['General']['lastfm_username'] = LASTFM_USERNAME + new_config['General']['interface'] = INTERFACE new_config.write() diff --git a/headphones/webserve.py b/headphones/webserve.py index 779f6da0..707c2436 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -17,7 +17,9 @@ from headphones.helpers import checked, radio def serve_template(templatename, **kwargs): - template_dir = os.path.join(str(headphones.PROG_DIR), 'data/interfaces/default/') + interface_dir = os.path.join(str(headphones.PROG_DIR), 'data/interfaces/') + template_dir = os.path.join(str(interface_dir), headphones.INTERFACE) + _hplookup = TemplateLookup(directories=[template_dir]) try: @@ -246,6 +248,10 @@ class WebInterface(object): clearhistory.exposed = True def config(self): + + interface_dir = os.path.join(headphones.PROG_DIR, 'data/interfaces/') + interface_list = [ name for name in os.listdir(interface_dir) if os.path.isdir(os.path.join(interface_dir, name)) ] + config = { "http_host" : headphones.HTTP_HOST, "http_user" : headphones.HTTP_USERNAME, @@ -289,7 +295,8 @@ class WebInterface(object): "folder_format" : headphones.FOLDER_FORMAT, "file_format" : headphones.FILE_FORMAT, "include_extras" : checked(headphones.INCLUDE_EXTRAS), - "log_dir" : headphones.LOG_DIR + "log_dir" : headphones.LOG_DIR, + "interface_list" : interface_list } return serve_template(templatename="config.html", title="Settings", config=config) config.exposed = True @@ -299,7 +306,7 @@ class WebInterface(object): sab_host=None, sab_username=None, sab_apikey=None, sab_password=None, sab_category=None, download_dir=None, blackhole=0, blackhole_dir=None, usenet_retention=None, nzbmatrix=0, nzbmatrix_username=None, nzbmatrix_apikey=None, newznab=0, newznab_host=None, newznab_apikey=None, nzbsorg=0, nzbsorg_uid=None, nzbsorg_hash=None, newzbin=0, newzbin_uid=None, newzbin_password=None, preferred_quality=0, preferred_bitrate=None, detect_bitrate=0, move_files=0, - rename_files=0, correct_metadata=0, cleanup_files=0, add_album_art=0, embed_album_art=0, destination_dir=None, folder_format=None, file_format=None, include_extras=0, log_dir=None): + rename_files=0, correct_metadata=0, cleanup_files=0, add_album_art=0, embed_album_art=0, destination_dir=None, folder_format=None, file_format=None, include_extras=0, interface=None, log_dir=None): headphones.HTTP_HOST = http_host headphones.HTTP_PORT = http_port @@ -340,6 +347,7 @@ class WebInterface(object): headphones.FOLDER_FORMAT = folder_format headphones.FILE_FORMAT = file_format headphones.INCLUDE_EXTRAS = include_extras + headphones.INTERFACE = interface headphones.LOG_DIR = log_dir headphones.config_write() From 07ffe8691cfe6b2407904cfb5c054bb925a60bff Mon Sep 17 00:00:00 2001 From: Remy Date: Tue, 9 Aug 2011 21:02:47 -0700 Subject: [PATCH 09/10] Added interfaces to staticdir in webstart, searcher excludes Various Artists from search (also cleaned up term encoding), default usenet retention in postprocessor/preprocess --- headphones/postprocessor.py | 6 ++++++ headphones/searcher.py | 27 +++++++++++++++++++-------- headphones/webstart.py | 4 ++++ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index e28033c3..d8b8cd14 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -111,6 +111,12 @@ def verify(albumid, albumpath): tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid]) downloaded_track_list = [] + + try: + albumpath = str(albumpath) + except UnicodeEncodeError: + albumpath = unicode(albumpath).encode('unicode_escape') + for r,d,f in os.walk(albumpath): for files in f: if any(files.endswith('.' + x) for x in headphones.MEDIA_FORMATS): diff --git a/headphones/searcher.py b/headphones/searcher.py index 763e6326..cf25b050 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -74,17 +74,22 @@ def searchNZB(albumid=None, new=False): dic = {'...':'', ' & ':' ', ' = ': ' ', '?':'', '$':'s', ' + ':' ', '"':'', ',':''} - cleanartistalbum = helpers.latinToAscii(helpers.replace_all(albums[0]+' '+albums[1], dic)) + cleanalbum = helpers.latinToAscii(helpers.replace_all(albums[1], dic)) cleanartist = helpers.latinToAscii(helpers.replace_all(albums[0], dic)) - # FLAC usually doesn't have a year for some reason so I'll leave it out: - term = re.sub('[\.\-\/]', ' ', '%s' % (cleanartistalbum)).encode('utf-8') - altterm = re.sub('[\.\-\/]', ' ', '%s %s' % (cleanartistalbum, year)).encode('utf-8') - artistterm = re.sub('[\.\-\/]', ' ', '%s' % (cleanartist)).encode('utf-8') - + # FLAC usually doesn't have a year for some reason so I'll leave it out + # Various Artist albums might be listed as VA, so I'll leave that out too # Only use the year if the term could return a bunch of different albums, i.e. self-titled albums if albums[0] in albums[1] or len(albums[0]) < 4 or len(albums[1]) < 4: - term = altterm + term = cleanartist + ' ' + cleanalbum + ' ' + year + elif albums[0] == 'Various Artists': + term = cleanalbum + ' ' + year + else: + term = cleanartist + ' ' + cleanalbum + + # Replace bad characters in the term and unicode it + term = re.sub('[\.\-\/]', ' ', term).encode('utf-8') + artistterm = re.sub('[\.\-\/]', ' ', cleanartist).encode('utf-8') logger.info("Searching for %s since it was marked as wanted" % term) @@ -459,6 +464,12 @@ def getresultNZB(result): return nzb def preprocess(resultlist): + + if not headphones.USENET_RETENTION: + usenet_retention = 1000 + else: + usenet_retention = int(headphones.USENET_RETENTION) + for result in resultlist: nzb = getresultNZB(result) if nzb: @@ -467,7 +478,7 @@ def preprocess(resultlist): node = d.documentElement nzbfiles = d.getElementsByTagName("file") for nzbfile in nzbfiles: - if nzbfile.getAttribute("date") < (time.time() - int(headphones.USENET_RETENTION) * 86400): + if nzbfile.getAttribute("date") < (time.time() - usenet_retention * 86400): logger.error('NZB contains a file out of your retention. Skipping.') continue #TODO: Do we want rar checking in here to try to keep unknowns out? diff --git a/headphones/webstart.py b/headphones/webstart.py index 474676b2..e5c14cd7 100644 --- a/headphones/webstart.py +++ b/headphones/webstart.py @@ -22,6 +22,10 @@ def initialize(options={}): '/': { 'tools.staticdir.root': os.path.join(headphones.PROG_DIR, 'data') }, + '/interfaces':{ + 'tools.staticdir.on': True, + 'tools.staticdir.dir': "interfaces" + }, '/images':{ 'tools.staticdir.on': True, 'tools.staticdir.dir': "images" From 4b701f5a27f5fe8fc388f2a93b29894866d1a8aa Mon Sep 17 00:00:00 2001 From: Remy Date: Tue, 9 Aug 2011 21:03:23 -0700 Subject: [PATCH 10/10] Added a sample template --- data/interfaces/remix/album.html | 105 ++++++++ data/interfaces/remix/artist.html | 127 ++++++++++ data/interfaces/remix/base.html | 101 ++++++++ data/interfaces/remix/config.html | 265 ++++++++++++++++++++ data/interfaces/remix/extras.html | 13 + data/interfaces/remix/history.html | 79 ++++++ data/interfaces/remix/index.html | 86 +++++++ data/interfaces/remix/logs.html | 60 +++++ data/interfaces/remix/manage.html | 57 +++++ data/interfaces/remix/searchresults.html | 70 ++++++ data/interfaces/remix/shutdown.html | 13 + data/interfaces/remix/style.css | 298 +++++++++++++++++++++++ data/interfaces/remix/upcoming.html | 86 +++++++ 13 files changed, 1360 insertions(+) create mode 100644 data/interfaces/remix/album.html create mode 100644 data/interfaces/remix/artist.html create mode 100755 data/interfaces/remix/base.html create mode 100644 data/interfaces/remix/config.html create mode 100644 data/interfaces/remix/extras.html create mode 100644 data/interfaces/remix/history.html create mode 100644 data/interfaces/remix/index.html create mode 100644 data/interfaces/remix/logs.html create mode 100644 data/interfaces/remix/manage.html create mode 100644 data/interfaces/remix/searchresults.html create mode 100644 data/interfaces/remix/shutdown.html create mode 100755 data/interfaces/remix/style.css create mode 100644 data/interfaces/remix/upcoming.html diff --git a/data/interfaces/remix/album.html b/data/interfaces/remix/album.html new file mode 100644 index 00000000..2308f32b --- /dev/null +++ b/data/interfaces/remix/album.html @@ -0,0 +1,105 @@ +<%inherit file="base.html" /> +<%! + from headphones import db, helpers + myDB = db.DBConnection() +%> + +<%def name="headerIncludes()"> +
    + +
    + + +<%def name="body()"> +
    +

    <- Back to ${album['ArtistName']}

    +
    + albumart +

    ${album['AlbumTitle']}

    +

    ${album['ArtistName']}

    +
    + <% + totalduration = myDB.action("SELECT SUM(TrackDuration) FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0] + totaltracks = len(myDB.select("SELECT TrackTitle from tracks WHERE AlbumID=?", [album['AlbumID']])) + try: + albumduration = helpers.convert_milliseconds(totalduration) + except: + albumduration = 'n/a' + + %> +

    Tracks: ${totaltracks}

    +

    Duration: ${albumduration}

    + %if description: +

    Description:

    + ${description['Summary']} + %endif +
    +
    + + + + + + + + + + + <% + i = 0 + %> + %for track in tracks: + <% + i += 1 + have = myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND AlbumTitle like ? AND TrackTitle like ?', [track['ArtistName'], track['AlbumTitle'], track['TrackTitle']]) + if len(have): + grade = 'A' + check = 'checkmark' + else: + grade = 'Z' + check = '' + try: + trackduration = helpers.convert_milliseconds(track['TrackDuration']) + except: + trackduration = 'n/a' + %> + + + + + + + %endfor + +
    #Track TitleDuration
    ${i}${track['TrackTitle']}${trackduration}${check}
    +
    +
    + + +<%def name="headIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file diff --git a/data/interfaces/remix/artist.html b/data/interfaces/remix/artist.html new file mode 100644 index 00000000..9ded40dc --- /dev/null +++ b/data/interfaces/remix/artist.html @@ -0,0 +1,127 @@ +<%inherit file="base.html"/> +<%! + from headphones import db +%> + +<%def name="headerIncludes()"> +
    + +
    + + +<%def name="body()"> +
    +

    ${artist['ArtistName']}

    +

    +
    +

    Mark selected albums as + + +

    + + + + + + + + + + + + + + %for album in albums: + <% + if album['Status'] == 'Skipped': + grade = 'Z' + elif album['Status'] == 'Wanted': + grade = 'X' + elif album['Status'] == 'Snatched': + grade = 'C' + else: + grade = 'A' + + myDB = db.DBConnection() + totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE AlbumID=?', [album['AlbumID']])) + havetracks = len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND AlbumTitle like ?', [album['ArtistName'], album['AlbumTitle']])) + + try: + percent = (havetracks*100.0)/totaltracks + if percent > 100: + percent = 100 + except (ZeroDivisionError, TypeError): + percent = 0 + totaltracks = '?' + + %> + + + + + + + + + %endfor + +
    Album NameRelease DateRelease TypeStatusHave
    + ${album['AlbumTitle']}${album['ReleaseDate']}${album['Type']}${album['Status']} + %if album['Status'] == 'Skipped': + [want] + %elif album['Status'] == 'Wanted': + [skip] + %else: + [retry][new] + %endif +
    ${havetracks}/${totaltracks}
    +
    + + +<%def name="headIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file diff --git a/data/interfaces/remix/base.html b/data/interfaces/remix/base.html new file mode 100755 index 00000000..6a218b97 --- /dev/null +++ b/data/interfaces/remix/base.html @@ -0,0 +1,101 @@ +<% + import headphones +%> + + + + + + + + + + + Headphones::Remix - ${title} + + + + + + + + + ${next.headIncludes()} + + + + +
    +
    + % if not headphones.CURRENT_VERSION: +
    + You're running an unknown version of Headphones. Click here to update +
    + % elif headphones.CURRENT_VERSION != headphones.LATEST_VERSION and headphones.INSTALL_TYPE != 'win': +
    + A newer version is available. You're ${headphones.COMMITS_BEHIND} commits behind. Click here to update +
    + % endif + + + +
    + ${next.headerIncludes()} +
    +
    + +
    + ${next.body()} +
    + +
    +
    + Version: ${headphones.CURRENT_VERSION} +
    + +
    +
    + + + + ${next.javascriptIncludes()} + + + + + + + + +<%def name="javascriptIncludes()"> +<%def name="headIncludes()"> +<%def name="headerIncludes()"> \ No newline at end of file diff --git a/data/interfaces/remix/config.html b/data/interfaces/remix/config.html new file mode 100644 index 00000000..5f4aa2da --- /dev/null +++ b/data/interfaces/remix/config.html @@ -0,0 +1,265 @@ +<%inherit file="base.html"/> +<%! + import headphones +%> + +<%def name="headerIncludes()"> +
    + +
    + +<%def name="body()"> +
    +

    +

    +
    +
    +

    Web Interface

    + + + + + + + + + + + + + + + + +
    +

    HTTP Host:

    +
    + i.e. localhost or 0.0.0.0 +
    +

    HTTP Username:

    + +
    +

    HTTP Port:

    + +
    +

    HTTP Password:

    + +
    +

    Launch Browser on Startup:

    +
    +
    +
    +

    Download Settings

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    SABnzbd Host:


    + + usually localhost:8080 +
    +

    SABnzbd Username:

    +
    +

    SABnzbd API:

    +
    +

    SABnzbd Password:

    +
    +

    SABnzbd Category:

    +
    +

    Music Download Directory:


    + + Full path to the directory where SAB downloads your music
    + i.e. /Users/name/Downloads/music
    +
    +

    Use Black Hole:

    +
    +

    Black Hole Directory:


    + + Folder your Download program watches for NZBs +
    +

    Usenet Retention:

    +
    +
    +
    +

    Search Providers

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    NZBMatrix:

    +
    +

    NZBMatrix Username:

    + +
    +

    NZBMatrix API:

    + +
    +

    Newznab:

    +
    +

    Newznab Host:

    +
    + i.e. http://nzb.su +
    +

    Newznab API:

    + +
    +

    NZBs.org:

    +
    + +

    NZBs.org UID:

    + + +
    + +

    NZBs.org Hash:

    + + +
    +

    Newzbin:

    +
    +

    Newzbin UID:

    + + +
    +

    Newzbin Password:

    + +
    +
    +
    +

    Quality & Post Processing

    + + + + + + + + + + +
    +

    Album Quality:


    + Highest Quality excluding Lossless
    + Highest Quality including Lossless
    + Lossless Only
    + Preferred Bitrate: + kbps
    + Auto-Detect Preferred Bitrate +
    +

    Post-Processing:

    + Move downloads to Destination Folder
    + Rename files
    + Correct metadata
    + Delete leftover files (.m3u, .nfo, .sfv, .nzb, etc.)
    + Add album art as 'folder.jpg' to album folder
    + Embed album art in each file +
    +
    + +

    Path to Destination folder:

    +
    + i.e. /Users/name/Music/iTunes or /Volumes/share/music +
    +
    +
    +

    Advanced Settings

    + + + + + + +
    +

    Renaming Options:

    +
    +

    Folder Format:

    + Use: artist, album, year and first (first letter in artist name)
    + E.g.: first/artist/album [year] = G/Girl Talk/All Day [2010]
    +

    +

    File Format:

    +
    + Use: tracknumber, title, artist, album and year +
    +

    Miscellaneous:

    +
    +

    Automatically Include Extras When Adding an Artist

    + (EPs, Compilations, Live Albums, Remix Albums and Singles) +

    +

    Interface: +

    +

    Log Directory:

    +
    +
    + +


    + (Web Interface changes require a restart to take effect) + + diff --git a/data/interfaces/remix/extras.html b/data/interfaces/remix/extras.html new file mode 100644 index 00000000..d9dbbe2f --- /dev/null +++ b/data/interfaces/remix/extras.html @@ -0,0 +1,13 @@ +<%inherit file="base.html" /> +<%def name="body()"> +

    +

    Artists You Might Like

    +
    + +
    +
    + diff --git a/data/interfaces/remix/history.html b/data/interfaces/remix/history.html new file mode 100644 index 00000000..77d80d87 --- /dev/null +++ b/data/interfaces/remix/history.html @@ -0,0 +1,79 @@ +<%inherit file="base.html"/> +<%! + from headphones import helpers +%> + +<%def name="headerIncludes()"> + + + +<%def name="body()"> +
    + History +
    + + + + + + + + + + + + %for item in history: + <% + if item['Status'] == 'Processed': + grade = 'A' + elif item['Status'] == 'Snatched': + grade = 'C' + elif item['Status'] == 'Unprocessed': + grade = 'X' + else: + grade = 'U' + %> + + + + + + + + %endfor + +
    Date AddedFile NameSizeStatus
    ${item['DateAdded']}${item['Title']}${helpers.bytes_to_mb(item['Size'])}${item['Status']}[retry][new]
    + + +<%def name="headIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file diff --git a/data/interfaces/remix/index.html b/data/interfaces/remix/index.html new file mode 100644 index 00000000..83a594b6 --- /dev/null +++ b/data/interfaces/remix/index.html @@ -0,0 +1,86 @@ +<%inherit file="base.html"/> +<%! + from headphones import helpers +%> + +<%def name="body()"> + + + + + + + + + + + %for artist in artists: + <% + totaltracks = artist['TotalTracks'] + havetracks = artist['HaveTracks'] + if not havetracks: + havetracks = 0 + try: + percent = (havetracks*100.0)/totaltracks + if percent > 100: + percent = 100 + except (ZeroDivisionError, TypeError): + percent = 0 + totaltracks = '?' + + if artist['ReleaseDate'] and artist['LatestAlbum']: + releasedate = artist['ReleaseDate'] + albumdisplay = '%s (%s)' % (artist['LatestAlbum'], artist['ReleaseDate']) + if releasedate > helpers.today(): + grade = 'A' + else: + grade = 'Z' + elif artist['LatestAlbum']: + releasedate = '' + grade = 'Z' + albumdisplay = '%s' % artist['LatestAlbum'] + else: + releasedate = '' + grade = 'Z' + albumdisplay = 'None' + + if artist['Status'] == 'Paused': + grade = 'X' + + %> + + + + + + + %endfor + +
    Artist NameStatusLatest AlbumHave
    ${artist['ArtistName']}${artist['Status']}${albumdisplay}
    ${havetracks}/${totaltracks}
    + + +<%def name="headIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file diff --git a/data/interfaces/remix/logs.html b/data/interfaces/remix/logs.html new file mode 100644 index 00000000..98004034 --- /dev/null +++ b/data/interfaces/remix/logs.html @@ -0,0 +1,60 @@ +<%inherit file="base.html"/> +<%! + from headphones import helpers +%> + +<%def name="body()"> + + + + + + + + + + %for line in lineList: + <% + timestamp, message, level, threadname = line + + if level == 'WARNING' or level == 'ERROR': + grade = 'X' + else: + grade = 'Z' + %> + + + + + + %endfor + +
    TimestampLevelMessage
    ${timestamp}${level}${message}
    + + +<%def name="headIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file diff --git a/data/interfaces/remix/manage.html b/data/interfaces/remix/manage.html new file mode 100644 index 00000000..8b891317 --- /dev/null +++ b/data/interfaces/remix/manage.html @@ -0,0 +1,57 @@ +<%inherit file="base.html" /> +<%! + import headphones +%> + +<%def name="body()"> +
    +

    Scan Music Library


    + Where do you keep your music?

    + You can put in any directory, and it will scan for audio files in that folder + (including all subdirectories)

    For example: '/Users/name/Music' +

    + It may take a while depending on how many files you have. You can navigate away from the page
    + as soon as you click 'Submit' +

    +
    + %if headphones.MUSIC_DIR: + + %else: + + %endif +
    +
    + +
    +

    Import Last.FM Artists


    + Enter the username whose artists you want to import:

    +
    + <% + if headphones.LASTFM_USERNAME: + lastfmvalue = headphones.LASTFM_USERNAME + else: + lastfmvalue = 'Last.fm Username' + %> + +


    +
    + +
    +

    Placeholder :-)


    +

    +
    + +


    +
    + + + \ No newline at end of file diff --git a/data/interfaces/remix/searchresults.html b/data/interfaces/remix/searchresults.html new file mode 100644 index 00000000..5109d10b --- /dev/null +++ b/data/interfaces/remix/searchresults.html @@ -0,0 +1,70 @@ +<%inherit file="base.html" /> + +<%def name="body()"> + +
    +

    Search Results

    +

    + + + + %if type == 'album': + + %endif + + + + + + + %if searchresults: + %for result in searchresults: + <% + if result['score'] == 100: + grade = 'A' + else: + grade = 'Z' + %> + + %if type == 'album': + + %endif + + + %if type == 'album': + + %else: + + %endif + + %endfor + %endif + +
    Album NameArtist NameScore
    ${result['title']}${result['uniquename']}${result['score']}Add this albumAdd this artist
    + + +<%def name="headIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file diff --git a/data/interfaces/remix/shutdown.html b/data/interfaces/remix/shutdown.html new file mode 100644 index 00000000..be7ca21d --- /dev/null +++ b/data/interfaces/remix/shutdown.html @@ -0,0 +1,13 @@ +<%inherit file="base.html"/> + +<%def name="headIncludes()"> + + + +<%def name="body()"> +
    +
    +

    Headphones is ${message}

    +
    +
    + \ No newline at end of file diff --git a/data/interfaces/remix/style.css b/data/interfaces/remix/style.css new file mode 100755 index 00000000..28fc9c97 --- /dev/null +++ b/data/interfaces/remix/style.css @@ -0,0 +1,298 @@ +/* HTML5 ✰ Boilerplate */ + +html, body, div, span, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +blockquote, q { quotes: none; } +blockquote:before, blockquote:after, +q:before, q:after { content: ''; content: none; } +ins { background-color: #ff9; color: #000; text-decoration: none; } +mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } +del { text-decoration: line-through; } +abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } +table { border-collapse: collapse; border-spacing: 0; } +hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } +input, select { vertical-align: middle; } + +body { font:13px/1.231 sans-serif; *font-size:small; } +select, input, textarea, button { font:99% sans-serif; } +pre, code, kbd, samp { font-family: monospace, sans-serif; } + +html { overflow-y: scroll; } +a:hover, a:active { outline: none; } +ul, ol { margin-left: 2em; } +ol { list-style-type: decimal; } +nav ul, nav li { margin: 0; list-style:none; list-style-image: none; } +small { font-size: 85%; } +strong, th { font-weight: bold; } +td { vertical-align: top; } + +sub, sup { font-size: 75%; line-height: 0; position: relative; } +sup { top: -0.5em; } +sub { bottom: -0.25em; } + +pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; padding: 15px; } +textarea { overflow: auto; } +.ie6 legend, .ie7 legend { margin-left: -7px; } +input[type="radio"] { vertical-align: text-bottom; } +input[type="checkbox"] { vertical-align: bottom; } +.ie7 input[type="checkbox"] { vertical-align: baseline; } +.ie6 input { vertical-align: text-bottom; } +label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; } +button, input, select, textarea { margin: 0; } +input:valid, textarea:valid { } +input:invalid, textarea:invalid { border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; } +.no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; } + +::-moz-selection{ background: grey; color:#fff; text-shadow: none; } +::selection { background: grey; color:#fff; text-shadow: none; } + +button { width: auto; overflow: visible; } +.ie7 img { -ms-interpolation-mode: bicubic; } + +body, select, input, textarea { color: #444; } +h1, h2, h3, h4, h5, h6 { font-weight: bold; } + +/* + // ========================================== \\ + || || + || Custom Styles || + || || + \\ ========================================== // +*/ + +a:link { + color: orange; + text-decoration: none; + } +a:visited { + color: orange; + text-decoration: none; + } +a:hover { /*this effect is not shown in NN4.xx*/ + color: #999999; + text-decoration: underline; + } +a:active {/*colour in NN4.xx is red*/ + color: #5E2612; + text-decoration: underline; + } +a.blue { + color: blue; + } + +container { } + +body { background-color: black; min-width: 935px; } + +header { min-height: 68px; width: 100%; min-width: 935px; padding-left: 0px; padding-right: 10px; background-color: #black; position: fixed; z-index: 998; } + +h1 { font-size: 24px; } +h2 { font-size: 20px; } +h3 { font-size: 16px; } + +p.indented { padding-top: 20px; margin-left: 20px; font-size: 14px; } +p.center { text-align: center; font-size: 18px; } +.smalltext2 { font-size: 11px; margin-left: 45px; } + +div#updatebar { text-align: center; min-width: 970px; width: 100%; background-color: light-blue; float: left; } +div#logo { float: left; padding-left: 10px; } + +ul#nav { margin: 25px 0 0 0; float: left; list-style-type: none; } +ul#nav li { margin: 40px 0px auto 10px; display: inline; } +ul#nav li a { padding: 5px; font-size: 16px; font-weight: bold; color: white; text-decoration: none; } +ul#nav li a:hover { background-color: #a3e532; } + +div#subhead_container { height: 30px; width:100%; min-width: 1000px; background-color: blue; float: left; list-style-type: none; z-index: 998; overflow: hidden; } +ul#subhead_menu { margin-top: 5px; } +ul#subhead_menu li { width: 100%; height: 100%; display: inline; } +ul#subhead_menu li a { padding: 5px 15px 10px 15px; vertical-align: middle; color: white; font-size: 16px; text-decoration: none; } +ul#subhead_menu li a:hover { width: 100%; height: 100%; background-color: #grey; } + +div#searchbar { margin: 24px 30px auto auto; float: right; } + +div#main { margin: 0; padding: 80px 0 0 0; } + +.table_wrapper { border-radius: 20px; -webkit-border-radius: 20px; -moz-border-radius: 20px; width: 88%; margin: 20px auto 0 auto; padding: 25px; background-color: white; position: relative; min-height: 200px; clear: both; _height: 302px; zoom: 1; } +.manage_wrapper { width: 88%; margin: 20px auto 0 auto; padding: 25px; min-height: 150px; clear: both; _height: 302px; zoom: 1; } +.table_wrapper_left { padding: 25px; background-color: #ffffff; float: left; width: 40%; min-height: 100px; margin-top: 25px; margin-left: 30px; margin-right: auto; -moz-border-radius: 20px; border-radius: 20px; } +.table_wrapper_right{ padding: 25px; background-color: #ffffff; width: 40%; min-height: 100px; margin-top: 25px; margin-left: auto; margin-right: 30px; -moz-border-radius: 20px; border-radius: 20px; } + +.configtable { font-size: 14px; line-height:18px; } +.configtable td { width: 350px; padding: 10px; } +.configtable td#middle { vertical-align: middle; } + +table#artist_table { background-color: white; width: 100%; padding: 20px; } + +table#artist_table th#name { text-align: left; min-width: 200px; } +table#artist_table th#status { text-align: left; min-width: 50px; } +table#artist_table th#album { text-align: left; min-width: 300px; } +table#artist_table th#have { text-align: center; } +table#artist_table td#name { vertical-align: middle; text-align: left; min-width:200px; } +table#artist_table td#status { vertical-align: middle; text-align: left; min-width: 50px; } +table#artist_table td#album { vertical-align: middle; text-align: left; min-width: 300px; } +table#artist_table td#have { vertical-align: middle; } + +div#paddingheader { padding-top: 48px; font-size: 24px; font-weight: bold; text-align: center; } +div#nopaddingheader { font-size: 24px; font-weight: bold; text-align: center; } +table#album_table { background-color: white; } + +table#album_table th#select { vertical-align: middle; text-align: left; min-width: 25px; } +table#album_table th#albumart { text-align: left; min-width: 50px; } +table#album_table th#albumname { text-align: center; min-width: 150px; } +table#album_table th#reldate { width: 175px; text-align: center; min-width: 100px; } +table#album_table th#status { width: 175px; text-align: center; min-width: 100px; } +table#album_table th#type { width: 175px; text-align: center; min-width: 100px; } +table#album_table td#select { vertical-align: middle; text-align: left; } +table#album_table td#albumart { vertical-align: middle; text-align: left; } +table#album_table td#albumname { vertical-align: middle; text-align: center; } +table#album_table td#reldate { vertical-align: middle; text-align: center; } +table#album_table td#status { vertical-align: middle; text-align: center; } +table#album_table td#type { vertical-align: middle; text-align: center; } +table#album_table td#have { vertical-align: middle; } + +img.albumArt { float: left; padding-right: 5px; } +div#albumheader { padding-top: 48px; height: 200px; } +div#track_wrapper { padding-top: 20px; text-align: center; font-size: 16px; } + +table#track_table th#number { text-align: right; min-width: 20px; } +table#track_table th#name { text-align: center; min-width: 350px; } +table#track_table th#duration { width: 175px; text-align: center; min-width: 100px; } +table#track_table th#have { width: 175px; text-align: center; min-width: 100px; } + +table#track_table td#number { vertical-align: middle; text-align: right; } +table#track_table td#name { vertical-align: middle; text-align: center; } +table#track_table td#duration { vertical-align: middle; text-align: center; } +table#track_table td#have { vertical-align: middle; text-align: center; } + +table#history_table { background-color: white; width: 100%; } + +table#log_table { background-color: white; } + +table#log_table th#timestamp { text-align: left; min-width: 150px; } +table#log_table th#level { text-align: left; min-width: 60px; } +table#log_table th#message { text-align: left; min-width: 500px; } + +table#upcoming_table th#albumart { text-align: center; min-width: 50px; } +table#upcoming_table th#albumname { text-align: center; min-width: 200px; } +table#upcoming_table th#artistname { text-align: center; min-width: 150px; } +table#upcoming_table th#reldate { text-align: center; min-width: 100px; } +table#upcoming_table th#type { text-align: center; min-width: 75px; } + +table#upcoming_table td#select { vertical-align: middle; text-align: center; } +table#upcoming_table td#albumart { vertical-align: middle; text-align: center; min-width: 50px; } +table#upcoming_table td#albumname { vertical-align: middle; text-align: center; min-width: 200px; } +table#upcoming_table td#artistname { vertical-align: middle; text-align: center; min-width: 150px; } +table#upcoming_table td#reldate { vertical-align: middle; text-align: center; min-width: 100px; } +table#upcoming_table td#type { vertical-align: middle; text-align: center; min-width: 75px; } +table#upcoming_table td#status { vertical-align: middle; text-align: center; } + +table#wanted_table th#albumart { text-align: center; min-width: 50px; } +table#wanted_table th#albumname { text-align: center; min-width: 200px; } +table#wanted_table th#artistname { text-align: center; min-width: 150px; } +table#wanted_table th#reldate { text-align: center; min-width: 100px; } +table#wanted_table th#type { text-align: center; min-width: 75px; } + +table#wanted_table td#select { vertical-align: middle; text-align: center; } +table#wanted_table td#albumart { vertical-align: middle; text-align: center; min-width: 50px; } +table#wanted_table td#albumname { vertical-align: middle; text-align: center; min-width: 200px; } +table#wanted_table td#artistname { vertical-align: middle; text-align: center; min-width: 150px; } +table#wanted_table td#reldate { vertical-align: middle; text-align: center; min-width: 100px; } +table#wanted_table td#type { vertical-align: middle; text-align: center; min-width: 75px; } +table#wanted_table td#status { vertical-align: middle; text-align: center; } + +table#searchresults_table th#albumname { text-align: left; min-width: 225px; } +table#searchresults_table th#artistname { text-align: center; min-width: 325px; } +table#searchresults_table th#score { text-align: center; min-width: 75px; } + +table#searchresults_table td#albumname { vertical-align: middle; text-align: left; min-width: 200px; } +table#searchresults_table td#artistname { vertical-align: middle; text-align: left; min-width: 300px; } +table#searchresults_table td#score { vertical-align: middle; text-align: center; min-width: 75px; } + +div.progress-container { border: 1px solid #ccc; width: 100px; height: 14px; margin: 2px 5px 2px 0; padding: 1px; float: left; background: white; } +div.progress-container > div { background-color: #a3e532; height: 14px; } +.havetracks { font-size: 13px; margin-left: 36px; padding-bottom: 3px; vertical-align: middle; } + +footer { margin: 20px auto 20px auto; } +div#version { text-align: center; font-weight: bold; } +div#donate { text-align: center; margin: 20px auto 20px auto; } + +div#shutdown{ text-align: center; vertical-align: middle; } + +.cloudtag { padding-top: 30px; font-size:16px; } +#cloud a.tag1 { font-size: 0.7em; font-weight: 100; } +#cloud a.tag2 { font-size: 0.8em; font-weight: 200; } +#cloud a.tag3 { font-size: 0.9em; font-weight: 300; } +#cloud a.tag4 { font-size: 1.0em; font-weight: 400; } +#cloud a.tag5 { font-size: 1.2em; font-weight: 500; } +#cloud a.tag6 { font-size: 1.4em; font-weight: 600; } +#cloud a.tag7 { font-size: 1.6em; font-weight: 700; } +#cloud a.tag8 { font-size: 1.8em; font-weight: 800; } +#cloud a.tag9 { font-size: 2.2em; font-weight: 900; } +#cloud a.tag10 { font-size: 2.5em; font-weight: 900; } + +#cloud { padding: 2px; line-height: 1.5em; text-align: center; } +#cloud a { padding: 0px; } +#cloud { margin: 0; } +#cloud li { display: inline; } + + + +.ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; } +.hidden { display: none; visibility: hidden; } +.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } +.visuallyhidden.focusable:active, +.visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } +.invisible { visibility: hidden; } +.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } +.clearfix:after { clear: both; } +.clearfix { zoom: 1; } + + +@media all and (orientation:portrait) { + +} + +@media all and (orientation:landscape) { + +} + +@media screen and (max-device-width: 480px) { + + html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } +} + + +@media print { + * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; + -ms-filter: none !important; } + a, a:visited { color: #444 !important; text-decoration: underline; } + a[href]:after { content: " (" attr(href) ")"; } + abbr[title]:after { content: " (" attr(title) ")"; } + .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } + pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } + thead { display: table-header-group; } + tr, img { page-break-inside: avoid; } + @page { margin: 0.5cm; } + p, h2, h3 { orphans: 3; widows: 3; } + h2, h3{ page-break-after: avoid; } +} \ No newline at end of file diff --git a/data/interfaces/remix/upcoming.html b/data/interfaces/remix/upcoming.html new file mode 100644 index 00000000..5c209056 --- /dev/null +++ b/data/interfaces/remix/upcoming.html @@ -0,0 +1,86 @@ +<%inherit file="base.html" /> +<%def name="body()"> +
    +

    Upcoming Albums

    + + + + + + + + + + + + + %for album in upcoming: + + + + + + + + + %endfor + +
    ArtistAlbum NameRelease DateTypeStatus
    ${album['ArtistName']}${album['AlbumTitle']}${album['ReleaseDate']}${album['Type']}${album['Status']}
    +
    + +
    +

    Mark selected albums as + + +

    +
    +

    Wanted Albums

    + + + + + + + + + + + + + %for album in wanted: + + + + + + + + %endfor + +
    ArtistAlbum NameRelease DateType
    + ${album['ArtistName']}${album['AlbumTitle']}${album['ReleaseDate']}${album['Type']}
    + +
    + + +<%def name="headIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file