mirror of
https://github.com/rembo10/headphones.git
synced 2026-05-20 18:45:32 +01:00
autopep8 E301,E302,E303 too many / too few blank lines
This commit is contained in:
@@ -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
|
||||
@@ -92,7 +93,6 @@ class Cache(object):
|
||||
|
||||
return days_old
|
||||
|
||||
|
||||
def _is_current(self, filename=None, date=None):
|
||||
|
||||
if filename:
|
||||
@@ -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()
|
||||
|
||||
@@ -24,9 +24,11 @@ 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.
|
||||
@@ -35,6 +37,7 @@ class AuthURLOpener(HeadphonesURLopener):
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -44,6 +44,7 @@ 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
|
||||
|
||||
@@ -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
|
||||
@@ -230,6 +231,7 @@ _config_definitions = {
|
||||
'XLDPROFILE': (str, 'General', '')
|
||||
}
|
||||
|
||||
|
||||
class Config(object):
|
||||
""" Wraps access to particular values in a config file """
|
||||
|
||||
|
||||
@@ -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] != '.':
|
||||
@@ -260,6 +266,7 @@ class Directory:
|
||||
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)
|
||||
@@ -498,6 +507,7 @@ class MetaFile(File):
|
||||
'''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)
|
||||
@@ -537,6 +547,7 @@ class WaveFile(File):
|
||||
if self.type == 'Free Lossless Audio Codec':
|
||||
return FLAC(self.name)
|
||||
|
||||
|
||||
def split(albumpath):
|
||||
|
||||
os.chdir(albumpath)
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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,6 +60,7 @@ def radio(variable, pos):
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
def latinToAscii(unicrap):
|
||||
"""
|
||||
From couch potato
|
||||
@@ -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,11 +614,15 @@ 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('.', ' ')
|
||||
|
||||
|
||||
def sab_replace_spaces(name):
|
||||
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
|
||||
@@ -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):
|
||||
|
||||
@@ -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,7 +241,6 @@ 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -180,7 +181,6 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
|
||||
file_count+=1
|
||||
|
||||
|
||||
# Now we start track matching
|
||||
logger.info("%s new/modified songs found and added to the database" % new_song_count)
|
||||
song_list = myDB.action("SELECT * FROM have WHERE Matched IS NULL AND LOCATION LIKE ?", [dir.decode(headphones.SYS_ENCODING, 'replace')+"%"])
|
||||
@@ -293,7 +293,6 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
|
||||
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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -73,6 +75,7 @@ def startmb():
|
||||
|
||||
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:
|
||||
@@ -201,6 +205,7 @@ def findRelease(name, limit=1, artist=None):
|
||||
})
|
||||
return releaselist
|
||||
|
||||
|
||||
def getArtist(artistid, extrasonly=False):
|
||||
|
||||
with mb_lock:
|
||||
@@ -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:
|
||||
@@ -321,6 +325,7 @@ def getArtist(artistid, extrasonly=False):
|
||||
|
||||
return artist_dict
|
||||
|
||||
|
||||
def getReleaseGroup(rgid):
|
||||
"""
|
||||
Returns a list of releases in a release group
|
||||
@@ -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
|
||||
@@ -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,6 +409,7 @@ def getRelease(releaseid, include_artist_info=True):
|
||||
|
||||
return release
|
||||
|
||||
|
||||
def get_new_releases(rgid, includeExtras=False, forcefull=False):
|
||||
|
||||
myDB = db.DBConnection()
|
||||
@@ -486,7 +492,6 @@ 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:
|
||||
@@ -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
|
||||
|
||||
@@ -30,6 +30,7 @@ if headphones.CONFIG.ENCODER == 'xld':
|
||||
else:
|
||||
XLD = False
|
||||
|
||||
|
||||
def encode(albumPath):
|
||||
|
||||
# Return if xld details not found
|
||||
@@ -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,
|
||||
@@ -357,6 +360,7 @@ def command(encoder, musicSource, musicDest, albumPath):
|
||||
|
||||
return encoded
|
||||
|
||||
|
||||
def getTimeEncode(start):
|
||||
seconds =int(time.time()-start)
|
||||
hours = seconds / 3600
|
||||
|
||||
@@ -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.
|
||||
@@ -177,6 +179,7 @@ class PROWL(object):
|
||||
|
||||
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
||||
|
||||
|
||||
class MPC(object):
|
||||
"""
|
||||
MPC library update
|
||||
@@ -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
|
||||
@@ -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):
|
||||
@@ -480,6 +487,7 @@ class PUSHBULLET(object):
|
||||
|
||||
self.notify('Main Screen Activate', 'Test Message')
|
||||
|
||||
|
||||
class PUSHALOT(object):
|
||||
|
||||
def notify(self, message, event):
|
||||
@@ -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):
|
||||
@@ -613,6 +623,7 @@ class PUSHOVER(object):
|
||||
|
||||
self.notify('Main Screen Activate', 'Test Message')
|
||||
|
||||
|
||||
class TwitterNotifier(object):
|
||||
|
||||
REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
|
||||
@@ -689,7 +700,6 @@ class TwitterNotifier(object):
|
||||
headphones.CONFIG.TWITTER_PASSWORD = access_token['oauth_token_secret']
|
||||
return True
|
||||
|
||||
|
||||
def _send_tweet(self, message=None):
|
||||
|
||||
username=self.consumer_key
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
@@ -48,7 +48,6 @@ def sendNZB(nzb):
|
||||
nzbgetXMLrpc = 'http://' + nzbgetXMLrpc
|
||||
headphones.CONFIG.NZBGET_HOST.replace('http://', '', 1)
|
||||
|
||||
|
||||
url = nzbgetXMLrpc % {"host": headphones.CONFIG.NZBGET_HOST, "username": headphones.CONFIG.NZBGET_USERNAME, "password": headphones.CONFIG.NZBGET_PASSWORD}
|
||||
|
||||
nzbGetRPC = xmlrpclib.ServerProxy(url)
|
||||
|
||||
@@ -32,6 +32,7 @@ from headphones import logger, helpers, request, mb, music_encoder
|
||||
|
||||
postprocessor_lock = threading.Lock()
|
||||
|
||||
|
||||
def checkFolder():
|
||||
|
||||
with postprocessor_lock:
|
||||
@@ -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()
|
||||
@@ -276,6 +278,7 @@ 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']))
|
||||
@@ -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,6 +557,7 @@ def addAlbumArt(artwork, albumpath, release):
|
||||
logger.error('Error saving album art: %s', e)
|
||||
return
|
||||
|
||||
|
||||
def cleanupFiles(albumpath):
|
||||
logger.info('Cleaning up files')
|
||||
|
||||
@@ -564,6 +570,7 @@ 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')
|
||||
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -975,7 +986,6 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
||||
|
||||
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')
|
||||
|
||||
if headphones.CONFIG.FILE_UNDERSCORES:
|
||||
@@ -997,6 +1007,7 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
||||
logger.error('Error renaming file: %s. Error: %s', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), e)
|
||||
continue
|
||||
|
||||
|
||||
def updateFilePermissions(albumpaths):
|
||||
|
||||
for folder in albumpaths:
|
||||
@@ -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.
|
||||
|
||||
@@ -30,6 +30,7 @@ from headphones.common import USER_AGENT
|
||||
from headphones import logger
|
||||
from headphones import notifiers, helpers
|
||||
|
||||
|
||||
def sendNZB(nzb):
|
||||
|
||||
params = {}
|
||||
@@ -127,6 +128,7 @@ def sendNZB(nzb):
|
||||
logger.info(u"Unknown failure sending NZB to sab. Return text is: " + sabText)
|
||||
return False
|
||||
|
||||
|
||||
def checkConfig():
|
||||
|
||||
params = { 'mode': 'get_config',
|
||||
|
||||
@@ -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
|
||||
@@ -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):
|
||||
@@ -401,6 +410,7 @@ def sort_search_results(resultlist, album, new, albumlength):
|
||||
|
||||
return finallist
|
||||
|
||||
|
||||
def get_year_from_release_date(release_date):
|
||||
|
||||
try:
|
||||
@@ -410,6 +420,7 @@ def get_year_from_release_date(release_date):
|
||||
|
||||
return year
|
||||
|
||||
|
||||
def searchNZB(album, new=False, losslessOnly=False, albumlength=None):
|
||||
|
||||
albumid = album['AlbumID']
|
||||
@@ -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]))
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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("!", "")
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +27,7 @@ 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'
|
||||
|
||||
@@ -60,6 +61,7 @@ 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']}
|
||||
@@ -80,6 +82,7 @@ def getTorrentFolder(torrentid):
|
||||
|
||||
return torrent_folder_name
|
||||
|
||||
|
||||
def setSeedRatio(torrentid, ratio):
|
||||
method = 'torrent-set'
|
||||
if ratio != 0:
|
||||
@@ -91,6 +94,7 @@ def setSeedRatio(torrentid, ratio):
|
||||
if not response:
|
||||
return False
|
||||
|
||||
|
||||
def removeTorrent(torrentid, remove_data = False):
|
||||
|
||||
method = 'torrent-get'
|
||||
@@ -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()
|
||||
|
||||
@@ -21,6 +21,7 @@ import headphones
|
||||
from headphones import logger
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
class utorrentclient(object):
|
||||
|
||||
TOKEN_REGEX = "<div id='token' style='display:none;'>([^<>]+)</div>"
|
||||
@@ -156,12 +157,14 @@ 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)
|
||||
|
||||
|
||||
def removeTorrent(hash, remove_data = False):
|
||||
uTorrentClient = utorrentclient()
|
||||
status, torrentList = uTorrentClient.list()
|
||||
@@ -177,6 +180,7 @@ def removeTorrent(hash, remove_data = False):
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def setSeedRatio(hash, ratio):
|
||||
uTorrentClient = utorrentclient()
|
||||
uTorrentClient.setprops(hash, 'seed_override', '1')
|
||||
@@ -186,6 +190,7 @@ def setSeedRatio(hash, ratio):
|
||||
# TODO passing -1 should be unlimited
|
||||
uTorrentClient.setprops(hash, 'seed_ratio', -10)
|
||||
|
||||
|
||||
def dirTorrent(hash, cacheid=None, return_name=None):
|
||||
|
||||
uTorrentClient = utorrentclient()
|
||||
@@ -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()
|
||||
|
||||
@@ -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.')
|
||||
|
||||
@@ -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,6 +51,7 @@ def serve_template(templatename, **kwargs):
|
||||
except:
|
||||
return exceptions.html_error_template().render()
|
||||
|
||||
|
||||
class WebInterface(object):
|
||||
|
||||
def index(self):
|
||||
@@ -102,7 +104,6 @@ class WebInterface(object):
|
||||
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()
|
||||
@@ -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")
|
||||
@@ -472,7 +472,6 @@ class WebInterface(object):
|
||||
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
|
||||
|
||||
@@ -782,7 +781,6 @@ class WebInterface(object):
|
||||
totalcount = 0
|
||||
myDB = db.DBConnection()
|
||||
|
||||
|
||||
sortcolumn = 'ArtistSortName'
|
||||
sortbyhavepercent = False
|
||||
if iSortCol_0 == '2':
|
||||
@@ -809,7 +807,6 @@ class WebInterface(object):
|
||||
if sortcolumn == 'ReleaseDate':
|
||||
filtered.reverse()
|
||||
|
||||
|
||||
artists = filtered[iDisplayStart:(iDisplayStart+iDisplayLength)]
|
||||
rows = []
|
||||
for artist in artists:
|
||||
@@ -840,7 +837,6 @@ class WebInterface(object):
|
||||
|
||||
rows.append(row)
|
||||
|
||||
|
||||
dict = {'iTotalDisplayRecords': len(filtered),
|
||||
'iTotalRecords': totalcount,
|
||||
'aaData': rows,
|
||||
@@ -1362,6 +1358,7 @@ class WebInterface(object):
|
||||
return msg
|
||||
osxnotifyregister.exposed = True
|
||||
|
||||
|
||||
class Artwork(object):
|
||||
def index(self):
|
||||
return "Artwork"
|
||||
@@ -1400,6 +1397,7 @@ class Artwork(object):
|
||||
def index(self):
|
||||
return "Here be thumbs"
|
||||
index.exposed = True
|
||||
|
||||
def default(self, ArtistOrAlbum="", ID=None):
|
||||
from headphones import cache
|
||||
ArtistID = None
|
||||
|
||||
@@ -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 = {}
|
||||
@@ -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