mirror of
https://github.com/rembo10/headphones.git
synced 2026-05-19 18:15:31 +01:00
Bug fixes, xld encoder, kere.ws support, use original nzb title, support for sab spaces->underscores
This commit is contained in:
@@ -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':
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
181
headphones/getXldProfile.py
Executable 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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'] = []
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user