diff --git a/Headphones.py b/Headphones.py
index 18bb7970..a3896a0f 100644
--- a/Headphones.py
+++ b/Headphones.py
@@ -41,6 +41,7 @@ def main():
headphones.ARGS = sys.argv[1:]
# From sickbeard
+ headphones.SYS_PLATFORM = sys.platform
headphones.SYS_ENCODING = None
try:
@@ -116,7 +117,7 @@ def main():
# Force the http port if neccessary
if args.port:
http_port = args.port
- logger.info('Starting Headphones on foced port: %i' % http_port)
+ logger.info('Starting Headphones on forced port: %i' % http_port)
else:
http_port = int(headphones.HTTP_PORT)
@@ -140,7 +141,10 @@ def main():
while True:
if not headphones.SIGNAL:
- time.sleep(1)
+ try:
+ time.sleep(1)
+ except KeyboardInterrupt:
+ headphones.SIGNAL = 'shutdown'
else:
logger.info('Received signal: ' + headphones.SIGNAL)
if headphones.SIGNAL == 'shutdown':
diff --git a/data/interfaces/brink/manage.html b/data/interfaces/brink/manage.html
index 3434e363..1b31bdf8 100644
--- a/data/interfaces/brink/manage.html
+++ b/data/interfaces/brink/manage.html
@@ -43,6 +43,11 @@
|
@@ -97,4 +102,4 @@
-->
-%def>
\ No newline at end of file
+%def>
diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html
index 532e02f2..a3f994cb 100644
--- a/data/interfaces/default/config.html
+++ b/data/interfaces/default/config.html
@@ -85,7 +85,6 @@
mins
-
|
@@ -760,7 +772,12 @@
$("#hpserveroptions").slideUp();
};
- handleNewSelection = function () {
+ hideEncoderDivs = function () {
+ $("#lameffmpegproperties").slideUp();
+ $("#xldproperties").slideUp();
+ };
+
+ handleNewServerSelection = function () {
hideServerDivs();
@@ -774,6 +791,23 @@
}
};
+ handleNewEncoderSelection = function () {
+
+ hideEncoderDivs();
+
+ switch ($(this).val()) {
+ case 'xld':
+ $("#xldproperties").slideDown();
+ break;
+ case 'ffmpeg':
+ $("#lameffmpegproperties").slideDown();
+ break;
+ case 'lame':
+ $("#lameffmpegproperties").slideDown();
+ break;
+ }
+ };
+
function openExtrasDialog() {
$("#dialog").dialog({ close: function(){
var elem = '';
@@ -914,8 +948,11 @@
}
});
- $("#mirror").change(handleNewSelection);
- handleNewSelection.apply($("#mirror"));
+ $("#mirror").change(handleNewServerSelection);
+ handleNewServerSelection.apply($("#mirror"));
+
+ $("#encoder").change(handleNewEncoderSelection);
+ handleNewEncoderSelection.apply($("#encoder"));
var deletedNewznabs = 0;
diff --git a/data/interfaces/default/manage.html b/data/interfaces/default/manage.html
index 1b833dfe..66405aab 100644
--- a/data/interfaces/default/manage.html
+++ b/data/interfaces/default/manage.html
@@ -58,10 +58,14 @@
%endif
-
+
+
+
+
+
diff --git a/headphones/__init__.py b/headphones/__init__.py
index 30fa5902..3f27c709 100644
--- a/headphones/__init__.py
+++ b/headphones/__init__.py
@@ -36,6 +36,7 @@ PROG_DIR = None
ARGS = None
SIGNAL = None
+SYS_PLATFORM = None
SYS_ENCODING = None
VERBOSE = 1
@@ -111,6 +112,7 @@ AUTOWANT_UPCOMING = False
AUTOWANT_ALL = False
SEARCH_INTERVAL = 360
+LIBRARYSCAN = False
LIBRARYSCAN_INTERVAL = 300
DOWNLOAD_SCAN_INTERVAL = 5
@@ -168,6 +170,7 @@ FOLDER_PERMISSIONS = None
MUSIC_ENCODER = False
ENCODERFOLDER = None
ENCODER = None
+XLDPROFILE = None
BITRATE = None
SAMPLINGFREQUENCY = None
ADVANCEDENCODER = None
@@ -250,7 +253,7 @@ def initialize():
with INIT_LOCK:
- global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \
+ global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, SYS_PLATFORM, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \
HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, HTTP_PROXY, LAUNCH_BROWSER, API_ENABLED, API_KEY, GIT_PATH, \
CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, MUSIC_DIR, DESTINATION_DIR, \
LOSSLESS_DESTINATION_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, ADD_ARTISTS, CORRECT_METADATA, MOVE_FILES, \
@@ -258,10 +261,10 @@ def initialize():
ADD_ALBUM_ART, EMBED_ALBUM_ART, EMBED_LYRICS, DOWNLOAD_DIR, BLACKHOLE, BLACKHOLE_DIR, USENET_RETENTION, SEARCH_INTERVAL, \
TORRENTBLACKHOLE_DIR, NUMBEROFSEEDERS, ISOHUNT, KAT, MININOVA, WAFFLES, WAFFLES_UID, WAFFLES_PASSKEY, \
RUTRACKER, RUTRACKER_USER, RUTRACKER_PASSWORD, WHATCD, WHATCD_USERNAME, WHATCD_PASSWORD, DOWNLOAD_TORRENT_DIR, \
- LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \
+ LIBRARYSCAN, 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, NEWZNAB_ENABLED, EXTRA_NEWZNABS,\
NZBSORG, NZBSORG_UID, NZBSORG_HASH, NEWZBIN, NEWZBIN_UID, NEWZBIN_PASSWORD, LASTFM_USERNAME, INTERFACE, FOLDER_PERMISSIONS, \
- ENCODERFOLDER, ENCODER, BITRATE, SAMPLINGFREQUENCY, MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, \
+ ENCODERFOLDER, ENCODER, XLDPROFILE, BITRATE, SAMPLINGFREQUENCY, MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, \
ENCODERVBRCBR, ENCODERLOSSLESS, DELETE_LOSSLESS_FILES, PROWL_ENABLED, PROWL_PRIORITY, PROWL_KEYS, PROWL_ONSNATCH, MIRRORLIST, \
MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \
XBMC_NOTIFY, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
@@ -341,6 +344,7 @@ def initialize():
AUTOWANT_ALL = bool(check_setting_int(CFG, 'General', 'autowant_all', 0))
SEARCH_INTERVAL = check_setting_int(CFG, 'General', 'search_interval', 360)
+ LIBRARYSCAN = bool(check_setting_int(CFG, 'General', 'libraryscan', 1))
LIBRARYSCAN_INTERVAL = check_setting_int(CFG, 'General', 'libraryscan_interval', 300)
DOWNLOAD_SCAN_INTERVAL = check_setting_int(CFG, 'General', 'download_scan_interval', 5)
@@ -397,6 +401,7 @@ def initialize():
ENCODERFOLDER = check_setting_str(CFG, 'General', 'encoderfolder', '')
ENCODER = check_setting_str(CFG, 'General', 'encoder', 'ffmpeg')
+ XLDPROFILE = check_setting_str(CFG, 'General', 'xldprofile', '')
BITRATE = check_setting_int(CFG, 'General', 'bitrate', 192)
SAMPLINGFREQUENCY= check_setting_int(CFG, 'General', 'samplingfrequency', 44100)
MUSIC_ENCODER = bool(check_setting_int(CFG, 'General', 'music_encoder', 0))
@@ -656,6 +661,7 @@ def config_write():
new_config['What.cd']['whatcd_password'] = WHATCD_PASSWORD
new_config['General']['search_interval'] = SEARCH_INTERVAL
+ new_config['General']['libraryscan'] = int(LIBRARYSCAN)
new_config['General']['libraryscan_interval'] = LIBRARYSCAN_INTERVAL
new_config['General']['download_scan_interval'] = DOWNLOAD_SCAN_INTERVAL
@@ -723,6 +729,7 @@ def config_write():
new_config['General']['music_encoder'] = int(MUSIC_ENCODER)
new_config['General']['encoder'] = ENCODER
+ new_config['General']['xldprofile'] = XLDPROFILE
new_config['General']['bitrate'] = int(BITRATE)
new_config['General']['samplingfrequency'] = int(SAMPLINGFREQUENCY)
new_config['General']['encoderfolder'] = ENCODERFOLDER
@@ -756,9 +763,9 @@ def start():
# Start our scheduled background tasks
from headphones import updater, searcher, librarysync, postprocessor
- SCHED.add_interval_job(updater.dbUpdate, hours=48)
+ SCHED.add_interval_job(updater.dbUpdate, hours=24)
SCHED.add_interval_job(searcher.searchforalbum, minutes=SEARCH_INTERVAL)
- SCHED.add_interval_job(librarysync.libraryScan, minutes=LIBRARYSCAN_INTERVAL)
+ SCHED.add_interval_job(librarysync.libraryScan, minutes=LIBRARYSCAN_INTERVAL, kwargs={'cron':True})
if CHECK_GITHUB:
SCHED.add_interval_job(versioncheck.checkGithub, minutes=CHECK_GITHUB_INTERVAL)
diff --git a/headphones/getXldProfile.py b/headphones/getXldProfile.py
new file mode 100755
index 00000000..e9d27015
--- /dev/null
+++ b/headphones/getXldProfile.py
@@ -0,0 +1,181 @@
+import os.path
+import plistlib
+import sys
+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')
+ try:
+ preferences = plistlib.Plist.fromFile(expandedPath)
+ except (expat.ExpatError):
+ os.system("/usr/bin/plutil -convert xml1 %s" % expandedPath )
+ try:
+ preferences = plistlib.Plist.fromFile(expandedPath)
+ except (ImportError):
+ os.system("/usr/bin/plutil -convert binary1 %s" % expandedPath )
+ logger.info('The plist at "%s" has a date in it, and therefore is not useable.' % expandedPath)
+ return(xldProfileNotFound, None, None)
+ except (ImportError):
+ logger.info('The plist at "%s" has a date in it, and therefore is not useable.' % expandedPath)
+ except:
+ logger.info('Unexpected error:', sys.exc_info()[0])
+ return(xldProfileNotFound, None, None)
+
+ xldProfile = xldProfile.lower()
+ profiles = preferences.get('Profiles')
+
+ for profile in profiles:
+
+ profilename = profile.get('XLDProfileManager_ProfileName')
+ xldProfileForCmd = profilename
+ profilename = profilename.lower()
+ xldFormat = None
+ xldBitrate = None
+
+ if profilename == xldProfile:
+
+ OutputFormatName = profile.get('OutputFormatName')
+ ShortDesc = profile.get('ShortDesc')
+
+ # Determine format and bitrate
+
+ if OutputFormatName == 'WAV':
+ xldFormat = 'wav'
+
+ elif OutputFormatName == 'AIFF':
+ xldFormat = 'aiff'
+
+ elif 'PCM' in OutputFormatName:
+ xldFormat = 'pcm'
+
+ elif OutputFormatName == 'Wave64':
+ xldFormat = 'w64'
+
+ elif OutputFormatName == 'MPEG-4 AAC':
+ xldFormat = 'm4a'
+ if 'CBR' in ShortDesc or 'ABR' in ShortDesc or 'CVBR' in ShortDesc:
+ xldBitrate = int(profile.get('XLDAacOutput2_Bitrate'))
+ elif 'TVBR' in ShortDesc:
+ XLDAacOutput2_VBRQuality = int(profile.get('XLDAacOutput2_VBRQuality'))
+ if XLDAacOutput2_VBRQuality > 122:
+ xldBitrate = 320
+ elif XLDAacOutput2_VBRQuality > 113 and XLDAacOutput2_VBRQuality <= 122:
+ xldBitrate = 285
+ elif XLDAacOutput2_VBRQuality > 104 and XLDAacOutput2_VBRQuality <= 113:
+ xldBitrate = 255
+ elif XLDAacOutput2_VBRQuality > 95 and XLDAacOutput2_VBRQuality <= 104:
+ xldBitrate = 225
+ elif XLDAacOutput2_VBRQuality > 86 and XLDAacOutput2_VBRQuality <= 95:
+ xldBitrate = 195
+ elif XLDAacOutput2_VBRQuality > 77 and XLDAacOutput2_VBRQuality <= 86:
+ xldBitrate = 165
+ elif XLDAacOutput2_VBRQuality > 68 and XLDAacOutput2_VBRQuality <= 77:
+ xldBitrate = 150
+ elif XLDAacOutput2_VBRQuality > 58 and XLDAacOutput2_VBRQuality <= 68:
+ xldBitrate = 135
+ elif XLDAacOutput2_VBRQuality > 49 and XLDAacOutput2_VBRQuality <= 58:
+ xldBitrate = 115
+ elif XLDAacOutput2_VBRQuality > 40 and XLDAacOutput2_VBRQuality <= 49:
+ xldBitrate = 105
+ elif XLDAacOutput2_VBRQuality > 31 and XLDAacOutput2_VBRQuality <= 40:
+ xldBitrate = 95
+ elif XLDAacOutput2_VBRQuality > 22 and XLDAacOutput2_VBRQuality <= 31:
+ xldBitrate = 80
+ elif XLDAacOutput2_VBRQuality > 13 and XLDAacOutput2_VBRQuality <= 22:
+ xldBitrate = 75
+ elif XLDAacOutput2_VBRQuality > 4 and XLDAacOutput2_VBRQuality <= 13:
+ xldBitrate = 45
+ elif XLDAacOutput2_VBRQuality >= 0 and XLDAacOutput2_VBRQuality <= 4:
+ xldBitrate = 40
+
+ elif OutputFormatName == 'Apple Lossless':
+ xldFormat = 'm4a'
+
+ elif OutputFormatName == 'FLAC':
+ if 'ogg' in ShortDesc:
+ xldFormat = 'oga'
+ else:
+ xldFormat = 'flac'
+
+ elif OutputFormatName == 'MPEG-4 HE-AAC':
+ xldFormat = 'm4a'
+ xldBitrate = int(profile.get('Bitrate'))
+
+ elif OutputFormatName == 'LAME MP3':
+ xldFormat = 'mp3'
+ if 'VBR' in ShortDesc:
+ VbrQuality = float(profile.get('VbrQuality'))
+ if VbrQuality < 1:
+ xldBitrate = 260
+ elif VbrQuality >= 1 and VbrQuality < 2:
+ xldBitrate = 250
+ elif VbrQuality >= 2 and VbrQuality < 3:
+ xldBitrate = 210
+ elif VbrQuality >= 3 and VbrQuality < 4:
+ xldBitrate = 195
+ elif VbrQuality >= 4 and VbrQuality < 5:
+ xldBitrate = 185
+ elif VbrQuality >= 5 and VbrQuality < 6:
+ xldBitrate = 150
+ elif VbrQuality >= 6 and VbrQuality < 7:
+ xldBitrate = 130
+ elif VbrQuality >= 7 and VbrQuality < 8:
+ xldBitrate = 120
+ elif VbrQuality >= 8 and VbrQuality < 9:
+ xldBitrate = 105
+ elif VbrQuality >= 9:
+ xldBitrate = 85
+ elif 'CBR' in ShortDesc:
+ xldBitrate = int(profile.get('Bitrate'))
+ elif 'ABR' in ShortDesc:
+ xldBitrate = int(profile.get('AbrBitrate'))
+
+ elif OutputFormatName == 'Opus':
+ xldFormat = 'opus'
+ xldBitrate = int(profile.get('XLDOpusOutput_Bitrate'))
+
+ elif OutputFormatName == 'Ogg Vorbis':
+ xldFormat = 'ogg'
+ XLDVorbisOutput_Quality = float(profile.get('XLDVorbisOutput_Quality'))
+ if XLDVorbisOutput_Quality <= -2:
+ xldBitrate = 32
+ elif XLDVorbisOutput_Quality > -2 and XLDVorbisOutput_Quality <= -1:
+ xldBitrate = 48
+ elif XLDVorbisOutput_Quality > -1 and XLDVorbisOutput_Quality <= 0:
+ xldBitrate = 64
+ elif XLDVorbisOutput_Quality > 0 and XLDVorbisOutput_Quality <= 1:
+ xldBitrate = 80
+ elif XLDVorbisOutput_Quality > 1 and XLDVorbisOutput_Quality <= 2:
+ xldBitrate = 96
+ elif XLDVorbisOutput_Quality > 2 and XLDVorbisOutput_Quality <= 3:
+ xldBitrate = 112
+ elif XLDVorbisOutput_Quality > 3 and XLDVorbisOutput_Quality <= 4:
+ xldBitrate = 128
+ elif XLDVorbisOutput_Quality > 4 and XLDVorbisOutput_Quality <= 5:
+ xldBitrate = 160
+ elif XLDVorbisOutput_Quality > 5 and XLDVorbisOutput_Quality <= 6:
+ xldBitrate = 192
+ elif XLDVorbisOutput_Quality > 6 and XLDVorbisOutput_Quality <= 7:
+ xldBitrate = 224
+ elif XLDVorbisOutput_Quality > 7 and XLDVorbisOutput_Quality <= 8:
+ xldBitrate = 256
+ elif XLDVorbisOutput_Quality > 8 and XLDVorbisOutput_Quality <= 9:
+ xldBitrate = 320
+ elif XLDVorbisOutput_Quality > 9:
+ xldBitrate = 500
+
+ elif OutputFormatName == 'WavPack':
+ xldFormat = 'wv'
+ if ShortDesc != 'normal':
+ xldBitrate = int(profile.get('XLDWavpackOutput_BitRate'))
+
+ # Lossless
+ if xldFormat and not xldBitrate:
+ xldBitrate = 500
+
+ return(xldProfileForCmd, xldFormat, xldBitrate)
+
+ return(xldProfileNotFound, None, None)
\ No newline at end of file
diff --git a/headphones/helpers.py b/headphones/helpers.py
index 89fb20cc..298062ee 100644
--- a/headphones/helpers.py
+++ b/headphones/helpers.py
@@ -118,6 +118,20 @@ def now():
now = datetime.datetime.now()
return now.strftime("%Y-%m-%d %H:%M:%S")
+def get_age(date):
+
+ try:
+ split_date = date.split('-')
+ except:
+ return False
+
+ try:
+ days_old = int(split_date[0])*365 + int(split_date[1])*30 + int(split_date[2])
+ except IndexError:
+ days_old = False
+
+ return days_old
+
def bytes_to_mb(bytes):
mb = int(bytes)/1048576
@@ -130,6 +144,10 @@ def mb_to_bytes(mb_str):
return int(float(result.group(1))*1048576)
def replace_all(text, dic):
+
+ if not text:
+ return ''
+
for i, j in dic.iteritems():
text = text.replace(i, j)
return text
@@ -153,8 +171,6 @@ def cleanTitle(title):
return title
def extract_data(s):
-
- from headphones import logger
#headphones default format
pattern = re.compile(r'(?P.*?)\s\-\s(?P.*?)\s\[(?P.*?)\]', re.VERBOSE)
@@ -165,8 +181,6 @@ def extract_data(s):
album = match.group("album")
year = match.group("year")
return (name, album, year)
- else:
- logger.info("Couldn't parse " + s + " into a valid default format")
#newzbin default format
pattern = re.compile(r'(?P.*?)\s\-\s(?P.*?)\s\((?P\d+?\))', re.VERBOSE)
@@ -177,8 +191,7 @@ def extract_data(s):
year = match.group("year")
return (name, album, year)
else:
- logger.info("Couldn't parse " + s + " into a valid Newbin format")
- return (name, album, year)
+ return (None, None, None)
def extract_logline(s):
# Default log format
@@ -257,3 +270,55 @@ def smartMove(src, dest, delete=True):
return True
except Exception, e:
logger.warn('Error moving file %s: %s' % (filename.decode(headphones.SYS_ENCODING, 'replace'), str(e).decode(headphones.SYS_ENCODING, 'replace')))
+
+#########################
+#Sab renaming functions #
+#########################
+
+# 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
+ """
+ CH_ILLEGAL = r'\/<>?*|"'
+ CH_LEGAL = r'++{}!@#`'
+
+ FL_ILLEGAL = CH_ILLEGAL + ':\x92"'
+ FL_LEGAL = CH_LEGAL + "-''"
+
+ uFL_ILLEGAL = FL_ILLEGAL.decode('latin-1')
+ uFL_LEGAL = FL_LEGAL.decode('latin-1')
+
+ if not name:
+ return name
+ if isinstance(name, unicode):
+ illegal = uFL_ILLEGAL
+ legal = uFL_LEGAL
+ else:
+ illegal = FL_ILLEGAL
+ legal = FL_LEGAL
+
+ lst = []
+ for ch in name.strip():
+ if ch in illegal:
+ ch = legal[illegal.find(ch)]
+ lst.append(ch)
+ else:
+ lst.append(ch)
+ name = ''.join(lst)
+
+ name = name.strip('. ')
+ if not name:
+ name = 'unknown'
+
+ #maxlen = cfg.folder_max_length()
+ #if len(name) > maxlen:
+ # name = name[:maxlen]
+
+ return name
diff --git a/headphones/importer.py b/headphones/importer.py
index 158d703f..447ff908 100644
--- a/headphones/importer.py
+++ b/headphones/importer.py
@@ -358,23 +358,22 @@ def addArtisttoDB(artistid, extrasonly=False):
if not rg_exists:
- newValueDict['DateAdded']= helpers.today()
+ today = helpers.today()
+
+ newValueDict['DateAdded']= today
if headphones.AUTOWANT_ALL:
newValueDict['Status'] = "Wanted"
- elif album['ReleaseDate'] > helpers.today() and headphones.AUTOWANT_UPCOMING:
+ elif album['ReleaseDate'] > today and headphones.AUTOWANT_UPCOMING:
+ newValueDict['Status'] = "Wanted"
+ # Sometimes "new" albums are added to musicbrainz after their release date, so let's try to catch these
+ # The first test just makes sure we have year-month-day
+ elif helpers.get_age(album['ReleaseDate']) and helpers.get_age(today) - helpers.get_age(album['ReleaseDate']) < 21 and headphones.AUTOWANT_UPCOMING:
newValueDict['Status'] = "Wanted"
else:
newValueDict['Status'] = "Skipped"
myDB.upsert("albums", newValueDict, controlValueDict)
-
- #start a search for the album if it's new and autowant_all is selected:
- # Should this run in a background thread? Don't know if we want to have a bunch of
- # simultaneous threads running
- if not rg_exists and headphones.AUTOWANT_ALL:
- from headphones import searcher
- searcher.searchforalbum(albumid=rg['id'])
myDB.action('DELETE from tracks WHERE AlbumID=?', [rg['id']])
tracks = myDB.action('SELECT * from alltracks WHERE ReleaseID=?', [releaseid]).fetchall()
@@ -405,16 +404,24 @@ def addArtisttoDB(artistid, extrasonly=False):
# Mark albums as downloaded if they have at least 80% (by default, configurable) of the album
have_track_count = len(myDB.select('SELECT * from tracks WHERE AlbumID=? AND Location IS NOT NULL', [rg['id']]))
+ marked_as_downloaded = False
if rg_exists:
if rg_exists['Status'] == 'Skipped' and ((have_track_count/float(total_track_count)) >= (headphones.ALBUM_COMPLETION_PCT/100.0)):
myDB.action('UPDATE albums SET Status=? WHERE AlbumID=?', ['Downloaded', rg['id']])
+ marked_as_downloaded = True
else:
if ((have_track_count/float(total_track_count)) >= (headphones.ALBUM_COMPLETION_PCT/100.0)):
myDB.action('UPDATE albums SET Status=? WHERE AlbumID=?', ['Downloaded', rg['id']])
+ marked_as_downloaded = True
logger.info(u"Seeing if we need album art for " + rg['title'])
cache.getThumb(AlbumID=rg['id'])
+
+ #start a search for the album if it's new, hasn't been marked as downloaded and autowant_all is selected:
+ if not rg_exists and not marked_as_downloaded and headphones.AUTOWANT_ALL:
+ from headphones import searcher
+ searcher.searchforalbum(albumid=rg['id'])
latestalbum = myDB.action('SELECT AlbumTitle, ReleaseDate, AlbumID from albums WHERE ArtistID=? order by ReleaseDate DESC', [artistid]).fetchone()
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=?', [artistid]))
diff --git a/headphones/librarysync.py b/headphones/librarysync.py
index ea95efa6..05ff49a0 100644
--- a/headphones/librarysync.py
+++ b/headphones/librarysync.py
@@ -22,8 +22,11 @@ import headphones
from headphones import db, logger, helpers, importer
# 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):
+def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=False):
+ if cron and not headphones.LIBRARYSCAN:
+ return
+
if not dir:
dir = headphones.MUSIC_DIR
diff --git a/headphones/mb.py b/headphones/mb.py
index b9c03499..5a20e0ad 100644
--- a/headphones/mb.py
+++ b/headphones/mb.py
@@ -157,7 +157,7 @@ def getArtist(artistid, extrasonly=False):
artist = None
try:
- limit = 100
+ limit = 200
artist = musicbrainzngs.get_artist_by_id(artistid)['artist']
newRgs = None
artist['release-group-list'] = []
diff --git a/headphones/music_encoder.py b/headphones/music_encoder.py
index 04dec8f5..4e057e20 100644
--- a/headphones/music_encoder.py
+++ b/headphones/music_encoder.py
@@ -27,7 +27,25 @@ try:
except ImportError:
import lib.argparse as argparse
+# xld
+
+if headphones.ENCODER == 'xld':
+ import getXldProfile
+ XLD = True
+else:
+ XLD = False
+
def encode(albumPath):
+
+ # Return if xld details not found
+
+ if XLD:
+ global xldProfile
+ (xldProfile, xldFormat, xldBitrate) = getXldProfile.getXldProfile(headphones.XLDPROFILE)
+ if not xldFormat:
+ logger.error(u'Details for xld profile "%s" not found, will not be reencoded' % (xldProfile))
+ return None
+
tempDirEncode=os.path.join(albumPath,"temp")
musicFiles=[]
musicFinalFiles=[]
@@ -46,26 +64,48 @@ def encode(albumPath):
for r,d,f in os.walk(albumPath):
for music in f:
if any(music.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
+
+ if not XLD:
+ encoderFormat = headphones.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING)
+ else:
+ xldMusicFile = os.path.join(r, music)
+ xldInfoMusic = MediaFile(xldMusicFile)
+ encoderFormat = xldFormat
+
if (headphones.ENCODERLOSSLESS):
- if (music.lower().endswith('.flac')):
+ ext = os.path.normpath(os.path.splitext(music)[1].lstrip(".")).lower()
+ if not XLD and ext == 'flac' or XLD and (ext != xldFormat and (xldInfoMusic.bitrate / 1000 > 500)):
musicFiles.append(os.path.join(r, music))
- musicTemp = os.path.normpath(os.path.splitext(music)[0]+'.'+headphones.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING))
+ musicTemp = os.path.normpath(os.path.splitext(music)[0] + '.' + encoderFormat)
musicTempFiles.append(os.path.join(tempDirEncode, musicTemp))
else:
logger.debug('Music "%s" is already encoded' % (music))
else:
musicFiles.append(os.path.join(r, music))
- musicTemp = os.path.normpath(os.path.splitext(music)[0]+'.'+headphones.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING))
+ musicTemp = os.path.normpath(os.path.splitext(music)[0] + '.' + encoderFormat)
musicTempFiles.append(os.path.join(tempDirEncode, musicTemp))
-
- if headphones.ENCODER=='lame':
+
+ if XLD:
+ if headphones.ENCODERFOLDER:
+ encoder = os.path.join(headphones.ENCODERFOLDER.encode(headphones.SYS_ENCODING), 'xld')
+ else:
+ encoder = os.path.join('/Applications', 'xld')
+ elif headphones.ENCODER=='lame':
encoder=os.path.join(headphones.ENCODERFOLDER.encode(headphones.SYS_ENCODING),'lame')
elif headphones.ENCODER=='ffmpeg':
encoder=os.path.join(headphones.ENCODERFOLDER.encode(headphones.SYS_ENCODING),'ffmpeg')
+
i=0
for music in musicFiles:
infoMusic=MediaFile(music)
- if headphones.ENCODER == 'lame':
+
+ if XLD:
+ if xldBitrate and (infoMusic.bitrate / 1000 <= xldBitrate):
+ logger.info('Music "%s" has bitrate <= "%skbit", will not be reencoded' % (music.decode(headphones.SYS_ENCODING, 'replace'), xldBitrate))
+ else:
+ command(encoder,music,musicTempFiles[i],albumPath)
+ ifencoded=1
+ elif headphones.ENCODER == 'lame':
if not any(music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.' + x) for x in ["mp3", "wav"]):
logger.warn(u'Lame cant encode "%s" format for "%s", use ffmpeg' % (os.path.splitext(music)[1].decode(headphones.SYS_ENCODING, 'replace'),music.decode(headphones.SYS_ENCODING, 'replace')))
else:
@@ -106,7 +146,17 @@ def command(encoder,musicSource,musicDest,albumPath):
return_code=1
cmd=''
startMusicTime=time.time()
- if headphones.ENCODER == 'lame':
+
+ if XLD:
+ xldDestDir = os.path.split(musicDest)[0]
+ cmd = encoder
+ cmd = cmd + ' "' + musicSource + '"'
+ cmd = cmd + ' --profile'
+ cmd = cmd + ' "' + xldProfile + '"'
+ cmd = cmd + ' -o'
+ cmd = cmd + ' "' + xldDestDir + '"'
+
+ elif headphones.ENCODER == 'lame':
if headphones.ADVANCEDENCODER =='':
cmd=encoder + ' -h'
if headphones.ENCODERVBRCBR=='cbr':
diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py
index ecabb271..1cc07a7f 100644
--- a/headphones/postprocessor.py
+++ b/headphones/postprocessor.py
@@ -20,6 +20,7 @@ import time
import threading
import music_encoder
import urllib, shutil, re
+import uuid
from headphones import notifiers
import lib.beets as beets
from lib.beets import autotag
@@ -27,6 +28,7 @@ from lib.beets.mediafile import MediaFile
import headphones
from headphones import db, albumart, librarysync, lyrics, logger, helpers
+from headphones.helpers import sab_replace_dots, sab_replace_spaces
postprocessor_lock = threading.Lock()
@@ -40,15 +42,27 @@ def checkFolder():
for album in snatched:
if album['FolderName']:
+
+ # Need to check for variations due to sab renaming. Ideally we'd check the sab config via api to
+ # figure out which options are checked, but oh well
+
+ nzb_album_possibilities = [ album['FolderName'],
+ sab_replace_dots(album['FolderName']),
+ sab_replace_spaces(album['FolderName']),
+ sab_replace_dots(sab_replace_spaces(album['FolderName']))
+ ]
- nzb_album_path = os.path.join(headphones.DOWNLOAD_DIR, album['FolderName']).encode(headphones.SYS_ENCODING)
torrent_album_path = os.path.join(headphones.DOWNLOAD_TORRENT_DIR, album['FolderName']).encode(headphones.SYS_ENCODING)
- if os.path.exists(nzb_album_path):
- logger.debug('Found %s in NZB download folder. Verifying....' % album['FolderName'])
- verify(album['AlbumID'], nzb_album_path)
+ for nzb_folder_name in nzb_album_possibilities:
+
+ nzb_album_path = os.path.join(headphones.DOWNLOAD_DIR, nzb_folder_name).encode(headphones.SYS_ENCODING)
- elif os.path.exists(torrent_album_path):
+ if os.path.exists(nzb_album_path):
+ logger.debug('Found %s in NZB download folder. Verifying....' % album['FolderName'])
+ verify(album['AlbumID'], nzb_album_path)
+
+ if os.path.exists(torrent_album_path):
logger.debug('Found %s in torrent download folder. Verifying....' % album['FolderName'])
verify(album['AlbumID'], torrent_album_path)
@@ -147,12 +161,75 @@ def verify(albumid, albumpath):
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
downloaded_track_list = []
-
+ downloaded_cuecount = 0
+
for r,d,f in os.walk(albumpath):
for files in f:
if any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
downloaded_track_list.append(os.path.join(r, files))
+ elif files.lower().endswith('.cue'):
+ downloaded_cuecount += 1
+ # use xld to split cue
+
+ if headphones.ENCODER == 'xld' and headphones.MUSIC_ENCODER and downloaded_cuecount and downloaded_cuecount >= len(downloaded_track_list):
+
+ import getXldProfile
+
+ (xldProfile, xldFormat, xldBitrate) = getXldProfile.getXldProfile(headphones.XLDPROFILE)
+ if not xldFormat:
+ logger.info(u'Details for xld profile "%s" not found, cannot split cue' % (xldProfile))
+ else:
+ if headphones.ENCODERFOLDER:
+ xldencoder = os.path.join(headphones.ENCODERFOLDER, 'xld')
+ else:
+ xldencoder = os.path.join('/Applications','xld')
+
+ for r,d,f in os.walk(albumpath):
+ xldfolder = r
+ xldfile = ''
+ xldcue = ''
+ for file in f:
+ if any(file.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS) and not xldfile:
+ xldfile = os.path.join(r, file)
+ elif file.lower().endswith('.cue') and not xldcue:
+ xldcue = os.path.join(r, file)
+
+ if xldfile and xldcue and xldfolder:
+ xldcmd = xldencoder
+ xldcmd = xldcmd + ' "' + xldfile + '"'
+ xldcmd = xldcmd + ' -c'
+ xldcmd = xldcmd + ' "' + xldcue + '"'
+ xldcmd = xldcmd + ' --profile'
+ xldcmd = xldcmd + ' "' + xldProfile + '"'
+ xldcmd = xldcmd + ' -o'
+ xldcmd = xldcmd + ' "' + xldfolder + '"'
+ logger.info(u"Cue found, splitting file " + xldfile.decode(headphones.SYS_ENCODING, 'replace'))
+ logger.debug(xldcmd)
+ os.system(xldcmd)
+
+ # count files, should now be more than original if xld successfully split
+
+ new_downloaded_track_list_count = 0
+ for r,d,f in os.walk(albumpath):
+ for file in f:
+ if any(file.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
+ new_downloaded_track_list_count += 1
+
+ if new_downloaded_track_list_count > len(downloaded_track_list):
+
+ # rename original unsplit files
+ for downloaded_track in downloaded_track_list:
+ os.rename(downloaded_track, downloaded_track + '.original')
+
+ #reload
+
+ downloaded_track_list = []
+ for r,d,f in os.walk(albumpath):
+ for file in f:
+ if any(file.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
+ downloaded_track_list.append(os.path.join(r, file))
+
# test #1: metadata - usually works
logger.debug('Verifying metadata...')
@@ -163,6 +240,11 @@ def verify(albumid, albumpath):
logger.info(u"Exception from MediaFile for: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + u" : " + unicode(e))
continue
+ if not f.artist:
+ continue
+ if not f.album:
+ continue
+
metaartist = helpers.latinToAscii(f.artist.lower()).encode('UTF-8')
dbartist = helpers.latinToAscii(release['ArtistName'].lower()).encode('UTF-8')
metaalbum = helpers.latinToAscii(f.album.lower()).encode('UTF-8')
@@ -182,6 +264,9 @@ def verify(albumid, albumpath):
split_track_name = re.sub('[\.\-\_]', ' ', track_name).lower()
for track in tracks:
+ if not track['TrackTitle']:
+ continue
+
dbtrack = helpers.latinToAscii(track['TrackTitle'].lower()).encode('UTF-8')
filetrack = helpers.latinToAscii(split_track_name).encode('UTF-8')
logger.debug('Checking if track title: %s is in file name: %s' % (dbtrack, filetrack))
@@ -233,9 +318,12 @@ def verify(albumid, albumpath):
def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list):
logger.info('Starting post-processing for: %s - %s' % (release['ArtistName'], release['AlbumTitle']))
- #start enconding
+ #start encoding
if headphones.MUSIC_ENCODER:
downloaded_track_list=music_encoder.encode(albumpath)
+
+ if not downloaded_track_list:
+ return
album_art_path = albumart.getAlbumArt(albumid)
@@ -374,8 +462,7 @@ def moveFiles(albumpath, release, tracks):
'$first': firstchar.lower()
}
-
- folder = helpers.replace_all(headphones.FOLDER_FORMAT, values)
+ folder = helpers.replace_all(headphones.FOLDER_FORMAT.strip(), values)
folder = folder.replace('./', '_/').replace(':','_').replace('?','_').replace('/.','/_').replace('<','_').replace('>','_')
if folder.endswith('.'):
@@ -402,8 +489,8 @@ def moveFiles(albumpath, release, tracks):
make_lossy_folder = False
make_lossless_folder = False
- lossy_destination_path = os.path.normpath(os.path.join(headphones.DESTINATION_DIR, folder)).encode(headphones.SYS_ENCODING)
- lossless_destination_path = os.path.normpath(os.path.join(headphones.LOSSLESS_DESTINATION_DIR, folder)).encode(headphones.SYS_ENCODING)
+ lossy_destination_path = os.path.normpath(os.path.join(headphones.DESTINATION_DIR, folder)).encode(headphones.SYS_ENCODING, 'replace')
+ lossless_destination_path = os.path.normpath(os.path.join(headphones.LOSSLESS_DESTINATION_DIR, folder)).encode(headphones.SYS_ENCODING, 'replace')
# If they set a destination dir for lossless media, only create the lossy folder if there is lossy media
if headphones.LOSSLESS_DESTINATION_DIR:
@@ -415,7 +502,7 @@ def moveFiles(albumpath, release, tracks):
else:
make_lossy_folder = True
- last_folder = headphones.FOLDER_FORMAT.split('/')[-1]
+ last_folder = headphones.FOLDER_FORMAT.strip().split('/')[-1]
if make_lossless_folder:
# Only rename the folder if they use the album name, otherwise merge into existing folder
@@ -426,7 +513,7 @@ def moveFiles(albumpath, release, tracks):
i = 1
while True:
newfolder = temp_folder + '[%i]' % i
- lossless_destination_path = os.path.normpath(os.path.join(headphones.LOSSLESS_DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING)
+ lossless_destination_path = os.path.normpath(os.path.join(headphones.LOSSLESS_DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING, 'replace')
if os.path.exists(lossless_destination_path):
i += 1
else:
@@ -439,7 +526,7 @@ def moveFiles(albumpath, release, tracks):
except Exception, e:
logger.error('Could not create lossless folder for %s. (Error: %s)' % (release['AlbumTitle'], e))
if not make_lossy_folder:
- return albumpath
+ return [albumpath]
if make_lossy_folder:
if os.path.exists(lossy_destination_path) and 'album' in last_folder.lower():
@@ -449,7 +536,7 @@ def moveFiles(albumpath, release, tracks):
i = 1
while True:
newfolder = temp_folder + '[%i]' % i
- lossy_destination_path = os.path.normpath(os.path.join(headphones.DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING)
+ lossy_destination_path = os.path.normpath(os.path.join(headphones.DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING, 'replace')
if os.path.exists(lossy_destination_path):
i += 1
else:
@@ -461,7 +548,7 @@ def moveFiles(albumpath, release, tracks):
os.makedirs(lossy_destination_path)
except Exception, e:
logger.error('Could not create folder for %s. Not moving: %s' % (release['AlbumTitle'], e))
- return albumpath
+ return [albumpath]
logger.info('Checking which files we need to move.....')
@@ -519,7 +606,7 @@ def moveFiles(albumpath, release, tracks):
temp_f = os.path.join(temp_f, f)
try:
- os.chmod(os.path.normpath(temp_f).encode(headphones.SYS_ENCODING), int(headphones.FOLDER_PERMISSIONS, 8))
+ os.chmod(os.path.normpath(temp_f).encode(headphones.SYS_ENCODING, 'replace'), int(headphones.FOLDER_PERMISSIONS, 8))
except Exception, e:
logger.error("Error trying to change permissions on folder: %s" % temp_f.decode(headphones.SYS_ENCODING, 'replace'))
@@ -663,10 +750,10 @@ def renameFiles(albumpath, downloaded_track_list, release):
ext = os.path.splitext(downloaded_track)[1]
- new_file_name = helpers.replace_all(headphones.FILE_FORMAT, values).replace('/','_') + ext
+ new_file_name = helpers.replace_all(headphones.FILE_FORMAT.strip(), values).replace('/','_') + ext
- new_file_name = new_file_name.replace('?','_').replace(':', '_').encode(headphones.SYS_ENCODING)
+ new_file_name = new_file_name.replace('?','_').replace(':', '_').encode(headphones.SYS_ENCODING, 'replace')
if new_file_name.startswith('.'):
new_file_name = new_file_name.replace(0, '_')
@@ -674,7 +761,7 @@ def renameFiles(albumpath, downloaded_track_list, release):
new_file = os.path.join(albumpath, new_file_name)
if downloaded_track == new_file_name:
- logger.debug("Renaming for: " + downloaded_track.decode(headphones.SYS_ENCODING) + " is not neccessary")
+ logger.debug("Renaming for: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + " is not neccessary")
continue
logger.debug('Renaming %s ---> %s' % (downloaded_track.decode(headphones.SYS_ENCODING,'replace'), new_file_name.decode(headphones.SYS_ENCODING,'replace')))
@@ -704,9 +791,9 @@ def forcePostProcess():
download_dirs = []
if headphones.DOWNLOAD_DIR:
- download_dirs.append(headphones.DOWNLOAD_DIR.encode(headphones.SYS_ENCODING))
+ download_dirs.append(headphones.DOWNLOAD_DIR.encode(headphones.SYS_ENCODING, 'replace'))
if headphones.DOWNLOAD_TORRENT_DIR:
- download_dirs.append(headphones.DOWNLOAD_TORRENT_DIR.encode(headphones.SYS_ENCODING))
+ download_dirs.append(headphones.DOWNLOAD_TORRENT_DIR.encode(headphones.SYS_ENCODING, 'replace'))
logger.info('Checking to see if there are any folders to process in download_dir(s): %s' % str(download_dirs).decode(headphones.SYS_ENCODING, 'replace'))
# Get a list of folders in the download_dir
@@ -723,20 +810,21 @@ def forcePostProcess():
logger.info('Found no folders to process in: %s' % str(download_dirs).decode(headphones.SYS_ENCODING, 'replace'))
# Parse the folder names to get artist album info
+ myDB = db.DBConnection()
+
for folder in folders:
folder_basename = os.path.basename(folder).decode(headphones.SYS_ENCODING, 'replace')
logger.info('Processing: %s' % folder_basename)
-
+
try:
name, album, year = helpers.extract_data(folder_basename)
except:
- logger.info("Couldn't parse " + folder_basename + " into any valid format.")
- continue
+ name = None
+
if name and album and year:
- myDB = db.DBConnection()
release = myDB.action('SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE ArtistName LIKE ? and AlbumTitle LIKE ?', [name, album]).fetchone()
if release:
logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album' % (release['ArtistName'], release['AlbumTitle']))
@@ -753,3 +841,25 @@ def forcePostProcess():
verify(rgid, folder)
else:
logger.info('No match found on MusicBrainz for: %s - %s' % (name, album))
+ continue
+
+ else:
+ try:
+ possible_rgid = folder_basename[-36:]
+ # re pattern match: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
+ rgid = uuid.UUID(possible_rgid)
+
+ except:
+ logger.info("Couldn't parse " + folder_basename + " into any valid format. If adding albums from another source, they must be in an 'Artist - Album [Year]' format, or end with the musicbrainz release group id")
+ continue
+
+
+ if rgid:
+ rgid = possible_rgid
+ release = myDB.action('SELECT ArtistName, AlbumTitle, AlbumID from albums WHERE AlbumID=?', [rgid]).fetchone()
+ if release:
+ logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album' % (release['ArtistName'], release['AlbumTitle']))
+ verify(release['AlbumID'], folder)
+ else:
+ logger.info('Found a (possibly) valid Musicbrainz identifier in album folder name - continuing post-processing')
+ verify(rgid, folder)
diff --git a/headphones/searcher.py b/headphones/searcher.py
index 22737fe1..016a4894 100644
--- a/headphones/searcher.py
+++ b/headphones/searcher.py
@@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Headphones. If not, see .
-import urllib, urllib2, urlparse
+import urllib, urllib2, urlparse, httplib
import lib.feedparser as feedparser
from lib.pygazelle import api as gazelleapi
from lib.pygazelle import encoding as gazelleencoding
@@ -84,7 +84,17 @@ def url_fix(s, charset='utf-8'):
scheme, netloc, path, qs, anchor = urlparse.urlsplit(s)
path = urllib.quote(path, '/%')
qs = urllib.quote_plus(qs, ':&=')
- return urlparse.urlunsplit((scheme, netloc, path, qs, anchor))
+ return urlparse.urlunsplit((scheme, netloc, path, qs, anchor))
+
+def patch_http_response_read(func):
+ def inner(*args):
+ try:
+ return func(*args)
+ except httplib.IncompleteRead, e:
+ return e.partial
+
+ return inner
+httplib.HTTPResponse.read = patch_http_response_read(httplib.HTTPResponse.read)
def searchforalbum(albumid=None, new=False, lossless=False):
@@ -244,6 +254,17 @@ def searchNZB(albumid=None, new=False, losslessOnly=False):
logger.info("Album type is audiobook/spokenword. Using audiobook category")
for newznab_host in newznab_hosts:
+
+ # Add a little mod for kere.ws
+ if newznab_host[0] == "http://kere.ws":
+ if categories == "3040":
+ categories = categories + ",4070"
+ elif categories == "3040,3010":
+ categories = categories + ",4070,4010"
+ elif categories == "3010":
+ categories = categories + ",4010"
+ else:
+ categories = categories + ",4050"
params = { "t": "search",
"apikey": newznab_host[1],
@@ -503,7 +524,8 @@ def searchNZB(albumid=None, new=False, losslessOnly=False):
if data and bestqual:
logger.info(u'Found best result: %s - %s' % (bestqual[2], bestqual[0], helpers.bytes_to_mb(bestqual[1])))
- nzb_folder_name = '%s - %s [%s]' % (helpers.latinToAscii(albums[0]).encode('UTF-8').replace('/', '_'), helpers.latinToAscii(albums[1]).encode('UTF-8').replace('/', '_'), year)
+ # Get rid of any dodgy chars here so we can prevent sab from renaming our downloads
+ nzb_folder_name = helpers.sab_sanitize_foldername(bestqual[0]) + '.' + albums[2]
if headphones.SAB_HOST and not headphones.BLACKHOLE:
nzb = classes.NZBDataSearchResult()
@@ -552,6 +574,11 @@ def verifyresult(title, artistterm, term):
#another attempt to weed out substrings. We don't want "Vol III" when we were looking for "Vol II"
+ # Filter out remix search results (if we're not looking for it)
+ if 'remix' not in term and 'remix' in title:
+ logger.info("Removed " + title + " from results because it's a remix album and we're not looking for a remix album right now")
+ return False
+
tokens = re.split('\W', term, re.IGNORECASE | re.UNICODE)
for token in tokens:
@@ -674,12 +701,12 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False):
else:
term = cleanartist + ' ' + cleanalbum
- semi_clean_artist_term = re.sub('[\.\-\/]', ' ', semi_cleanartist).encode('utf-8')
- semi_clean_album_term = re.sub('[\.\-\/]', ' ', semi_cleanalbum).encode('utf-8')
+ semi_clean_artist_term = re.sub('[\.\-\/]', ' ', semi_cleanartist).encode('utf-8', 'replace')
+ semi_clean_album_term = re.sub('[\.\-\/]', ' ', semi_cleanalbum).encode('utf-8', 'replace')
# Replace bad characters in the term and unicode it
term = re.sub('[\.\-\/]', ' ', term).encode('utf-8')
- artistterm = re.sub('[\.\-\/]', ' ', cleanartist).encode('utf-8')
- albumterm = re.sub('[\.\-\/]', ' ', cleanalbum).encode('utf-8')
+ artistterm = re.sub('[\.\-\/]', ' ', cleanartist).encode('utf-8', 'replace')
+ albumterm = re.sub('[\.\-\/]', ' ', cleanalbum).encode('utf-8', 'replace')
logger.info("Searching torrents for %s since it was marked as wanted" % term)
diff --git a/headphones/searcher_rutracker.py b/headphones/searcher_rutracker.py
index 7cdbd949..270b5fc3 100644
--- a/headphones/searcher_rutracker.py
+++ b/headphones/searcher_rutracker.py
@@ -146,18 +146,18 @@ class Rutracker():
# get headphones track count for album, return if not found
- hptrackcount = 0
-
myDB = db.DBConnection()
- tracks = myDB.select('SELECT TrackTitle from tracks WHERE AlbumID=?', [albumid])
- for track in tracks:
- hptrackcount += 1
+ tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
+ hptrackcount = len(tracks)
if not hptrackcount:
logger.info('headphones track info not found, cannot compare to torrent')
return False
# Return the first valid torrent, unless we want a preferred bitrate then we want all valid entries
+ unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd']
+ formatlist = ['.ape', '.flac', '.ogg', '.m4a', '.aac', '.mp3', '.wav', '.aif']
+ deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive']
for torrent in torrentlist:
@@ -170,8 +170,7 @@ class Rutracker():
title = returntitle.lower()
- if 'promo' not in title and 'vinyl' not in title and 'songbook' not in title and 'tvrip' not in title and 'hdtv' not in title and 'dvd' not in title \
- and int(size) <= maxsize and int(seeders) >= minseeders:
+ if not any(unwanted in title for unwanted in unwantedlist) and int(size) <= maxsize and int(seeders) >= minseeders:
# Check torrent info
@@ -202,7 +201,7 @@ class Rutracker():
for pathfile in metainfo['files']:
path = pathfile['path']
for file in path:
- if '.ape' in file or '.flac' in file or '.ogg' in file or '.m4a' in file or '.aac' in file or '.mp3' in file or '.wav' in file or '.aif' in file:
+ if any(format in file for format in formatlist):
trackcount += 1
if '.cue' in file:
cuecount += 1
@@ -252,7 +251,7 @@ class Rutracker():
if trackcount == hptrackcount:
valid = True
elif trackcount > hptrackcount:
- if 'deluxe' in title or 'edition' in title or 'japanese' or 'exclusive' in title:
+ if any(deluxe in title for deluxe in deluxelist):
valid = True
# return 1st valid torrent if not checking by bitrate, else add to list and return at end
diff --git a/headphones/webserve.py b/headphones/webserve.py
index bac8b3cf..08d1863f 100644
--- a/headphones/webserve.py
+++ b/headphones/webserve.py
@@ -368,7 +368,8 @@ class WebInterface(object):
raise cherrypy.HTTPRedirect("home")
importItunes.exposed = True
- def musicScan(self, path, scan=0, redirect=None, autoadd=0):
+ def musicScan(self, path, scan=0, redirect=None, autoadd=0, libraryscan=0):
+ headphones.LIBRARYSCAN = libraryscan
headphones.ADD_ARTISTS = autoadd
headphones.MUSIC_DIR = path
headphones.config_write()
@@ -623,6 +624,7 @@ class WebInterface(object):
"interface_list" : interface_list,
"music_encoder": checked(headphones.MUSIC_ENCODER),
"encoder": headphones.ENCODER,
+ "xldprofile": headphones.XLDPROFILE,
"bitrate": int(headphones.BITRATE),
"encoderfolder": headphones.ENCODERFOLDER,
"advancedencoder": headphones.ADVANCEDENCODER,
@@ -683,11 +685,11 @@ class WebInterface(object):
numberofseeders=10, use_isohunt=0, use_kat=0, use_mininova=0, waffles=0, waffles_uid=None, waffles_passkey=None, whatcd=0, whatcd_username=None, whatcd_password=None,
rutracker=0, rutracker_user=None, rutracker_password=None, rename_files=0, correct_metadata=0, cleanup_files=0, add_album_art=0, embed_album_art=0, embed_lyrics=0,
destination_dir=None, lossless_destination_dir=None, folder_format=None, file_format=None, include_extras=0, single=0, ep=0, compilation=0, soundtrack=0, live=0,
- remix=0, spokenword=0, audiobook=0, autowant_upcoming=False, autowant_all=False, interface=None, log_dir=None, cache_dir=None, music_encoder=0, encoder=None, bitrate=None,
- samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0,
+ remix=0, spokenword=0, audiobook=0, autowant_upcoming=False, autowant_all=False, interface=None, log_dir=None, cache_dir=None, music_encoder=0, encoder=None, xldprofile=None,
+ bitrate=None, samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0,
delete_lossless_files=0, prowl_enabled=0, prowl_onsnatch=0, prowl_keys=None, prowl_priority=0, xbmc_enabled=0, xbmc_host=None, xbmc_username=None, xbmc_password=None,
xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, synoindex_enabled=False, mirror=None, customhost=None, customport=None,
- customsleep=None, hpuser=None, hppass=None, preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, cache_sizemb=0, **kwargs):
+ customsleep=None, hpuser=None, hppass=None, preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, cache_sizemb=None, **kwargs):
headphones.HTTP_HOST = http_host
headphones.HTTP_PORT = http_port
@@ -760,6 +762,7 @@ class WebInterface(object):
headphones.CACHE_DIR = cache_dir
headphones.MUSIC_ENCODER = music_encoder
headphones.ENCODER = encoder
+ headphones.XLDPROFILE = xldprofile
headphones.BITRATE = int(bitrate)
headphones.SAMPLINGFREQUENCY = int(samplingfrequency)
headphones.ENCODERFOLDER = encoderfolder
diff --git a/headphones/webstart.py b/headphones/webstart.py
index a19a8b3e..b67c801f 100644
--- a/headphones/webstart.py
+++ b/headphones/webstart.py
@@ -74,6 +74,7 @@ def initialize(options={}):
'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict(
{options['http_username']:options['http_password']})
})
+ conf['/api'] = { 'tools.auth_basic.on': False }
# Prevent time-outs
|