Bug fixes, xld encoder, kere.ws support, use original nzb title, support for sab spaces->underscores

This commit is contained in:
rembo10
2012-11-04 21:27:08 -05:00
16 changed files with 608 additions and 105 deletions

View File

@@ -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':

View File

@@ -43,6 +43,11 @@
<td><input type="text" placeholder="Music Directory" onfocus="if (this.value==this.defaultValue) this.value='';" name="path" size="60" /></td>
%endif
</tr>
<tr>
<td colspan="2">
<input class="styled" type="checkbox" name="libraryscan" value="1" ${checked(headphones.LIBRARYSCAN)} /> <label for="libraryscan">Automatically scan library</label>
</td>
</tr>
<tr>
<td colspan="2">
<input class="styled" type="checkbox" name="autoadd" value="1" ${checked(headphones.ADD_ARTISTS)} /> <label for="autoadd">Automatically add new artists</label>
@@ -97,4 +102,4 @@
</div>
-->
</div>
</%def>
</%def>

View File

@@ -85,7 +85,6 @@
<label>Download Scan Interval</label>
<input type="text" name="download_scan_interval" value="${config['download_scan_interval']}" size="4">mins
</div>
<div class="row">
<label>Library Scan Interval</label>
<input type="text" name="libraryscan_interval" value="${config['libraryscan_interval']}" size="4">mins
@@ -417,7 +416,7 @@
<fieldset>
<legend>Re-Encoding Options</legend>
<small class="heading"><span style="float: left; margin-right: .3em; margin-top: 4px;" class="ui-icon ui-icon-info"></span>Note: this option requires the lame or ffmpeg encoder</small>
<small class="heading"><span style="float: left; margin-right: .3em; margin-top: 4px;" class="ui-icon ui-icon-info"></span>Note: this option requires the lame, ffmpeg or xld encoder</small>
<div class="checkbox row clearfix">
<input type="checkbox" name="music_encoder" id="music_encoder" value="1" ${config['music_encoder']}/><label>Re-encode downloads during postprocessing</label>
</div>
@@ -434,35 +433,42 @@
if config['encoder'] == 'lame':
lameselect = 'selected="selected"'
ffmpegselect = ''
else:
xldselect = ''
elif config['encoder'] == 'ffmpeg':
lameselect = ''
ffmpegselect = 'selected="selected"'
xldselect = ''
else:
lameselect = ''
ffmpegselect = ''
xldselect = 'selected="selected"'
%>
<div class="row">
<label>Encoder</label>
<select name="encoder">
<select name="encoder" id="encoder">
<option value="lame" ${lameselect}>lame</option>
<option value="ffmpeg" ${ffmpegselect}>ffmpeg</option>
<option value="xld" ${xldselect}>xld</option>
</select>
</div>
<div class="row">
<label>Format</label>
<select name="encoderoutputformat">
%for x in ['mp3', 'ogg', 'm4a']:
<%
if config['encoderoutputformat'] == x:
outputselect = 'selected'
else:
outputselect = ''
%>
<option value=${x} ${outputselect}>${x}</option>
%endfor
</select>
</div>
</fieldset>
<div id="lameffmpegproperties">
<fieldset>
<legend>Audio Properties</legend>
<div class="row">
<label>Format</label>
<select name="encoderoutputformat">
%for x in ['mp3', 'ogg', 'm4a']:
<%
if config['encoderoutputformat'] == x:
outputselect = 'selected'
else:
outputselect = ''
%>
<option value=${x} ${outputselect}>${x}</option>
%endfor
</select>
</div>
<div class="row">
<label>VBR/CBR</label>
<select name="encodervbrcbr">
@@ -530,13 +536,19 @@
<label>(ignores audio properties)</label>
<input type="text" name="advancedencoder" value="${config['advancedencoder']}" size="43">
</div>
<div class="row">
</fieldset>
</div>
<div id="xldproperties">
<div class="row">
<label>XLD Profile</label>
<input type="text" name="xldprofile" value="${config['xldprofile']}" size="43">
</div>
</div>
<div class="row">
<label>Path to Encoder</label>
<input type="text" name="encoderfolder" value="${config['encoderfolder']}" size="43">
</div>
</fieldset>
</td>
<td>
@@ -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 = '<input type="button" data-success="Changes saved successfully">';
@@ -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;

View File

@@ -58,10 +58,14 @@
%endif
</div>
<div class="row checkbox">
<input type="checkbox" name="autoadd" id="autoadd" value="1" ${checked(headphones.ADD_ARTISTS)}><label>Automatically add new artists</label>
<input type="checkbox" name="libraryscan" id="libraryscan" value="1" ${checked(headphones.LIBRARYSCAN)}><label>Automatically scan library</label>
</div>
<div class="row checkbox">
<input type="checkbox" name="autoadd" id="autoadd" value="1" ${checked(headphones.ADD_ARTISTS)}><label>Auto-add new artists</label>
</div>
</fieldset>
<br>
<input type="button" value="Save Changes and Scan" onclick="addScanAction();doAjaxCall('musicScan',$(this),'tabs',true);return false;" data-success="Changes saved. Library will be scanned">
<input type="button" value="Save Changes without Scanning Library" onclick="doAjaxCall('musicScan',$(this),'tabs',true);return false;" data-success="Changes Saved Successfully">
</form>

View File

@@ -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)

181
headphones/getXldProfile.py Executable file
View File

@@ -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)

View File

@@ -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<name>.*?)\s\-\s(?P<album>.*?)\s\[(?P<year>.*?)\]', 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<name>.*?)\s\-\s(?P<album>.*?)\s\((?P<year>\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

View File

@@ -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]))

View File

@@ -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

View File

@@ -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'] = []

View File

@@ -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':

View File

@@ -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)

View File

@@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
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: <a href="%s">%s</a> - %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)

View File

@@ -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

View File

@@ -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

View File

@@ -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