mirror of
https://github.com/rembo10/headphones.git
synced 2026-09-09 16:22:52 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88af84579c | ||
|
|
917dc7695f | ||
|
|
65e3e4faa2 | ||
|
|
9e7ec3a65c | ||
|
|
7ac7b127c6 | ||
|
|
3945753c5c | ||
|
|
599585d57a | ||
|
|
10cf36401f | ||
|
|
fe2834f4a6 | ||
|
|
674617c0f8 | ||
|
|
f92f8928b0 | ||
|
|
45868bc523 | ||
|
|
9b2594355f | ||
|
|
e30cb26200 | ||
|
|
63d3ab1cc1 | ||
|
|
188d907e10 | ||
|
|
e972a86233 | ||
|
|
52a76e87ad | ||
|
|
8ab19f8121 | ||
|
|
6f3c8c68d4 | ||
|
|
98385ff4c8 | ||
|
|
62a6be26af | ||
|
|
5c1169ba9d | ||
|
|
0b47a5ca3a | ||
|
|
9da75a777b | ||
|
|
2fc2190b59 | ||
|
|
e108935430 | ||
|
|
bbde74db8d | ||
|
|
9fce4edf00 | ||
|
|
ea3c6c3278 | ||
|
|
6a0bd397cd | ||
|
|
7bbb137fd9 | ||
|
|
e67d32445f | ||
|
|
83280f3f99 | ||
|
|
9c81fbaa2b | ||
|
|
1d7415b91d | ||
|
|
bc5a042dcc | ||
|
|
e5af0bdd52 | ||
|
|
dc2939a4f9 | ||
|
|
f52ec56a0f | ||
|
|
75b38df3d6 | ||
|
|
0f5aeea345 | ||
|
|
d80fc36b29 | ||
|
|
4ca42c9c11 | ||
|
|
6c760902d6 | ||
|
|
2a0fcc7566 | ||
|
|
b763ccdd08 | ||
|
|
e72a0d1141 | ||
|
|
295e5a1ccf | ||
|
|
497fafb555 | ||
|
|
585f556006 | ||
|
|
7b115126c9 | ||
|
|
1a9165a084 | ||
|
|
4a74d54cdc | ||
|
|
a0755d4b1c | ||
|
|
084181a9a5 | ||
|
|
5c381b9410 | ||
|
|
0be9500518 | ||
|
|
9c2da72dcc | ||
|
|
3f6145cd07 | ||
|
|
e155f19d0d | ||
|
|
0c7ea8a5a0 | ||
|
|
1f3474a154 | ||
|
|
c0c1f9cc61 | ||
|
|
e4382c23a3 | ||
|
|
29b5305ab4 | ||
|
|
758154e87e | ||
|
|
ea8b1815f6 | ||
|
|
ebc0b7205f | ||
|
|
06ee770184 | ||
|
|
900325186b | ||
|
|
3b9816f3b3 | ||
|
|
3e5779e5ce | ||
|
|
66d0cb4a1b | ||
|
|
54c85c7951 | ||
|
|
941da4be1c | ||
|
|
69c94baf18 | ||
|
|
90016f6eda | ||
|
|
06b647e30d | ||
|
|
84ebcb84f9 | ||
|
|
b11610face | ||
|
|
6e90419014 |
+6
-6
@@ -77,16 +77,16 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
headphones.VERBOSE = 2
|
||||
elif args.quiet:
|
||||
headphones.VERBOSE = 0
|
||||
headphones.VERBOSE = True
|
||||
if args.quiet:
|
||||
headphones.QUIET = True
|
||||
|
||||
if args.daemon:
|
||||
if sys.platform == 'win32':
|
||||
print "Daemonize not supported under Windows, starting normally"
|
||||
else:
|
||||
headphones.DAEMON=True
|
||||
headphones.VERBOSE = False
|
||||
headphones.DAEMON = True
|
||||
headphones.QUIET = True
|
||||
|
||||
if args.pidfile:
|
||||
headphones.PIDFILE = str(args.pidfile)
|
||||
@@ -101,7 +101,7 @@ def main():
|
||||
try:
|
||||
file(headphones.PIDFILE, 'w').write("pid\n")
|
||||
except IOError, e:
|
||||
raise SystemExit("Unable to write PID file: %s [%d]" % (e.strerror, e.errno))
|
||||
raise SystemExit("Unable to write PID file: %s [%d]", e.strerror, e.errno)
|
||||
else:
|
||||
logger.warn("Not running in daemon mode. PID file creation disabled.")
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<%def name="headerIncludes()">
|
||||
<div id="subhead_container">
|
||||
<div id="subhead_menu">
|
||||
<a id="menu_link_refresh" onclick="doAjaxCall('refreshArtist?ArtistID=${artist['ArtistID']}', $(this)),'table'" href="#" data-success="'${artist['ArtistName']}' is being refreshed"><i class="fa fa-refresh"></i> Refresh Artist</a>
|
||||
<a id="menu_link_refresh" onclick="doSimpleAjaxCall('refreshArtist?ArtistID=${artist['ArtistID']}')" href="#"><i class="fa fa-refresh"></i> Refresh Artist</a>
|
||||
<a id="menu_link_delete" href="deleteArtist?ArtistID=${artist['ArtistID']}"><i class="fa fa-trash-o"></i> Delete Artist</a>
|
||||
%if artist['Status'] == 'Paused':
|
||||
<a id="menu_link_resume" href="#" onclick="doAjaxCall('resumeArtist?ArtistID=${artist['ArtistID']}',$(this),true)" data-success="${artist['ArtistName']} resumed"><i class="fa fa-play"></i> Resume Artist</a>
|
||||
@@ -42,14 +42,8 @@
|
||||
<div id="artistImg">
|
||||
<img id="artistImage" class="albumArt" alt="" src="artwork/artist/${artist['ArtistID']}"/>
|
||||
</div>
|
||||
<h1>
|
||||
%if artist['Status'] == 'Loading':
|
||||
<i class="fa fa-refresh fa-spin"></i>
|
||||
%endif
|
||||
<a href="http://musicbrainz.org/artist/${artist['ArtistID']}">${artist['ArtistName']}</a>
|
||||
%if artist['Status'] == 'Loading':
|
||||
<h3><i>(Album information for this artist is currently being loaded)</i></h3>
|
||||
%endif
|
||||
<h1 id="artistname">
|
||||
<a href="http://musicbrainz.org/artist/${artist['ArtistID']}" id="artistnamelink">${artist['ArtistName']}</a>
|
||||
</h1>
|
||||
<div id="artistBio"></div>
|
||||
</div>
|
||||
@@ -156,9 +150,6 @@
|
||||
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="interfaces/default/css/data_table.css">
|
||||
%if artist['Status'] == 'Loading':
|
||||
<meta http-equiv="refresh" content="5">
|
||||
%endif
|
||||
</%def>
|
||||
|
||||
<%def name="javascriptIncludes()">
|
||||
@@ -171,7 +162,6 @@
|
||||
getInfo(elem,id,'artist');
|
||||
}
|
||||
|
||||
|
||||
<%
|
||||
if headphones.SONGKICK_FILTER_ENABLED:
|
||||
songkick_filter_enabled = "true"
|
||||
@@ -240,6 +230,40 @@
|
||||
});
|
||||
}
|
||||
|
||||
var loadingMessage = false;
|
||||
var spinner_active = false;
|
||||
var loadingtext_active = false;
|
||||
|
||||
function checkArtistStatus() {
|
||||
$.getJSON("getArtistjson?ArtistID=${artist['ArtistID']}", function(data) {
|
||||
if (data['Status'] == "Loading"){
|
||||
refreshTable();
|
||||
$('#artistnamelink').text(data["ArtistName"]);
|
||||
if (loadingMessage == false){
|
||||
$("#ajaxMsg").after( "<div id='ajaxMsg2' class='ajaxMsg'></div>" );
|
||||
showArtistMsg("Getting artist information");
|
||||
loadingMessage = true;
|
||||
}
|
||||
if (spinner_active == false){
|
||||
$('#artistname').prepend('<i class="fa fa-refresh fa-spin" id="artistnamespinner"></i>')
|
||||
spinner_active = true;
|
||||
}
|
||||
if (loadingtext_active == false){
|
||||
$('#artistname').append('<h3 id="loadingtext"><i>(Album information for this artist is currently being loaded)</i></h3>')
|
||||
loadingtext_active = true;
|
||||
}
|
||||
}
|
||||
else{
|
||||
$('#artistnamespinner').remove()
|
||||
$('#loadingtext').remove()
|
||||
$('#ajaxMsg2').remove()
|
||||
spinner_active = false
|
||||
loadingtext_active = false
|
||||
loadingMessage = false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initThisPage() {
|
||||
$('#menu_link_getextra').click(function() {
|
||||
$('#dialog').dialog();
|
||||
@@ -248,10 +272,6 @@
|
||||
$('#menu_link_modifyextra').click(function() {
|
||||
$('#dialog').dialog();
|
||||
});
|
||||
|
||||
%if artist['Status'] == 'Loading':
|
||||
showMsg("Getting artist information",true);
|
||||
%endif
|
||||
$('#album_table').dataTable({
|
||||
"bDestroy": true,
|
||||
"aoColumns": [
|
||||
@@ -283,7 +303,7 @@
|
||||
resetFilters("albums");
|
||||
setTimeout(function(){
|
||||
initFancybox();
|
||||
},1500)
|
||||
},1500);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
@@ -291,8 +311,12 @@
|
||||
initThisPage();
|
||||
getArtistBio();
|
||||
if( ${songkick_enabled} ){
|
||||
getArtistsCalendar();
|
||||
}
|
||||
getArtistsCalendar();
|
||||
}
|
||||
checkArtistStatus();
|
||||
setInterval(function(){
|
||||
checkArtistStatus();
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="ajaxMsg"></div>
|
||||
<div id="ajaxMsg" class="ajaxMsg"></div>
|
||||
% if not headphones.CURRENT_VERSION:
|
||||
<div id="updatebar">
|
||||
You're running an unknown version of Headphones. <a href="update">Update</a> or
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
<td>
|
||||
<fieldset>
|
||||
<legend>Usenet</legend>
|
||||
<input type="radio" name="nzb_downloader" id="nzb_downloader_sabnzbd" value="0" ${config['nzb_downloader_sabnzbd']}>Sabnzbd <input type="radio" name="nzb_downloader" id="nzb_downloader_nzbget" value="1" ${config['nzb_downloader_nzbget']}> NZBget <input type="radio" name="nzb_downloader" id="nzb_downloader_blackhole" value="2" ${config['nzb_downloader_blackhole']}>Black Hole
|
||||
<input type="radio" name="nzb_downloader" id="nzb_downloader_sabnzbd" value="0" ${config['nzb_downloader_sabnzbd']}> Sabnzbd <input type="radio" name="nzb_downloader" id="nzb_downloader_nzbget" value="1" ${config['nzb_downloader_nzbget']}> NZBget <input type="radio" name="nzb_downloader" id="nzb_downloader_blackhole" value="2" ${config['nzb_downloader_blackhole']}> Black Hole
|
||||
</fieldset>
|
||||
<fieldset id="sabnzbd_options">
|
||||
<div class="row">
|
||||
@@ -192,7 +192,7 @@
|
||||
<td>
|
||||
<fieldset>
|
||||
<legend>Torrents</legend>
|
||||
<input type="radio" name="torrent_downloader" id="torrent_downloader_blackhole" value="0" ${config['torrent_downloader_blackhole']}>Black Hole <input type="radio" name="torrent_downloader" id="torrent_downloader_transmission" value="1" ${config['torrent_downloader_transmission']}> Transmission <!-- <input type="radio" name="torrent_downloader" id="torrent_downloader_utorrent" value="2" ${config['torrent_downloader_utorrent']}> uTorrent -->
|
||||
<input type="radio" name="torrent_downloader" id="torrent_downloader_blackhole" value="0" ${config['torrent_downloader_blackhole']}> Black Hole <input type="radio" name="torrent_downloader" id="torrent_downloader_transmission" value="1" ${config['torrent_downloader_transmission']}> Transmission <input type="radio" name="torrent_downloader" id="torrent_downloader_utorrent" value="2" ${config['torrent_downloader_utorrent']}> uTorrent (Beta)
|
||||
</fieldset>
|
||||
<fieldset id="torrent_blackhole_options">
|
||||
<div class="row">
|
||||
@@ -237,7 +237,11 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>uTorrent Password:</label>
|
||||
<input type="text" name="utorrent_password" value="${config['utorrent_pass']}" size="30">
|
||||
<input type="password" name="utorrent_password" value="${config['utorrent_pass']}" size="30">
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>uTorrent Label:</label>
|
||||
<input type="text" name="utorrent_label" value="${config['utorrent_label']}" size="30">
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset id="general_torrent_options">
|
||||
@@ -293,7 +297,7 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Newznab</legend>
|
||||
<legend>Custom Newznab Providers</legend>
|
||||
<div class="row checkbox">
|
||||
<input id="usenewznab" type="checkbox" name="newznab" onclick="initConfigCheckbox($(this));" value="1" ${config['use_newznab']} /><label>Use Newznab</label>
|
||||
</div>
|
||||
@@ -466,7 +470,14 @@
|
||||
<div class="row radio clearfix">
|
||||
<input type="radio" name="preferred_quality" id="preferred_quality0" value="0" ${config['pref_qual_0']}><label>Highest Quality excluding Lossless</label>
|
||||
<input type="radio" name="preferred_quality" id="preferred_quality1" value="1" ${config['pref_qual_1']}><label>Highest Quality including Lossless</label>
|
||||
<input type="radio" name="preferred_quality" id="preferred_quality3" value="3" ${config['pref_qual_3']}><label>Lossless Only</label>
|
||||
<input type="radio" name="preferred_quality" id="lossless_only" value="3" ${config['pref_qual_3']}><label>Lossless Only</label>
|
||||
<div id="lossless_only_options">
|
||||
<span style="padding-left: 20px">
|
||||
Reject if target size is not in bitrate range: \
|
||||
<input type="text" class="override-float" name="lossless_bitrate_from" value="${config['lossless_bitrate_from']}" size="4" title="e.g. if album length = 40 mins, from = 350 kbps, then min target size = 102.5 mb, anything less will be rejected">to\
|
||||
<input type="text" class="override-float" name="lossless_bitrate_to" value="${config['lossless_bitrate_to']}" size="5" title="e.g. if album length = 40 mins, to = 2000 kbps, then max target size = 586 mb, anything greater will be rejected">kbps
|
||||
</span>
|
||||
</div>
|
||||
<input type="radio" id="preferred_bitrate" name="preferred_quality" value="2" ${config['pref_qual_2']}>Preferred Bitrate: <input type="text" class="override-float" name="preferred_bitrate" value="${config['pref_bitrate']}" size="3">kbps<br>
|
||||
<div id="preferred_bitrate_options">
|
||||
Reject if <strong>less than</strong> <input type="text" class="override-float" name="preferred_bitrate_low_buffer" value="${config['pref_bitrate_low']}" size="3">% or <strong>more than</strong> <input type="text" class="override-float" name="preferred_bitrate_high_buffer" value="${config['pref_bitrate_high']}" size="3">% of the target size (leave blank for no limit)<br><br>
|
||||
@@ -492,7 +503,7 @@
|
||||
<div class="row">
|
||||
<label>Preferred Words</label>
|
||||
<input type="text" name="preferred_words" value="${config['preferred_words']}" size="50">
|
||||
<small>Results with these words in the title will be preferred over results without them</small>
|
||||
<small>Results with these words in the title will be preferred over results without them (search provider names can also be entered)</small>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>Required Words</label>
|
||||
@@ -812,6 +823,14 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<h3>MPC</h3>
|
||||
<div class="checkbox row">
|
||||
<input type="checkbox" name="mpc_enabled" id="mpc" value="1" ${config['mpc_enabled']} /><label>Enable MPC Update</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -1575,6 +1594,15 @@
|
||||
}
|
||||
});
|
||||
|
||||
if ($("#lossless_only").is(":checked"))
|
||||
{
|
||||
$("#lossless_only_options").show();
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#lossless_only_options").hide();
|
||||
}
|
||||
|
||||
if ($("#preferred_bitrate").is(":checked"))
|
||||
{
|
||||
$("#preferred_bitrate_options").show();
|
||||
@@ -1620,18 +1648,22 @@
|
||||
if ($("#preferred_bitrate").is(":checked"))
|
||||
{
|
||||
$("#preferred_bitrate_options").slideDown("fast");
|
||||
$("#lossless_only_options").slideUp("fast");
|
||||
}
|
||||
if ($("#preferred_quality0").is(":checked"))
|
||||
{
|
||||
$("#preferred_bitrate_options").slideUp("fast");
|
||||
$("#lossless_only_options").slideUp("fast");
|
||||
}
|
||||
if ($("#preferred_quality1").is(":checked"))
|
||||
{
|
||||
$("#preferred_bitrate_options").slideUp("fast");
|
||||
$("#lossless_only_options").slideUp("fast");
|
||||
}
|
||||
if ($("#preferred_quality3").is(":checked"))
|
||||
if ($("#lossless_only").is(":checked"))
|
||||
{
|
||||
$("#preferred_bitrate_options").slideUp("fast");
|
||||
$("#lossless_only_options").slideDown("fast");
|
||||
$("#preferred_bitrate_options").slideUp("fast");
|
||||
}
|
||||
if ($("#nzb_downloader_sabnzbd").is(":checked"))
|
||||
{
|
||||
|
||||
@@ -545,7 +545,7 @@ footer {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
#ajaxMsg {
|
||||
.ajaxMsg {
|
||||
border: 1px solid #cccccc;
|
||||
background-image: -moz-linear-gradient(#ffffff, #eeeeee) !important;
|
||||
background-image: linear-gradient(#ffffff, #eeeeee) !important;
|
||||
@@ -576,16 +576,16 @@ footer {
|
||||
-o-opacity: 0.8 !important;
|
||||
opacity: 0.8 !important;
|
||||
}
|
||||
#ajaxMsg .msg {
|
||||
.ajaxMsg .msg {
|
||||
font-family: "Trebuchet MS", Helvetica, Arial, sans-serif;
|
||||
line-height: normal;
|
||||
padding-left: 20px;
|
||||
}
|
||||
#ajaxMsg .loader {
|
||||
.ajaxMsg .loader {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
#ajaxMsg.success {
|
||||
.ajaxMsg .success {
|
||||
background-image: -moz-linear-gradient(#d3ffd7, #c2edc6) !important;
|
||||
background-image: linear-gradient(#d3ffd7, #c2edc6) !important;
|
||||
background-image: -webkit-linear-gradient(#d3ffd7, #c2edc6) !important;
|
||||
@@ -595,7 +595,7 @@ footer {
|
||||
padding: 15px 10px;
|
||||
text-align: left;
|
||||
}
|
||||
#ajaxMsg.error {
|
||||
.ajaxMsg .error {
|
||||
background-image: -moz-linear-gradient(#ffd3d3, #edc4c4) !important;
|
||||
background-image: linear-gradient(#ffd3d3, #edc4c4) !important;
|
||||
background-image: -webkit-linear-gradient(#ffd3d3, #edc4c4) !important;
|
||||
@@ -605,7 +605,7 @@ footer {
|
||||
padding: 15px 10px;
|
||||
text-align: left;
|
||||
}
|
||||
#ajaxMsg .ui-icon {
|
||||
.ajaxMsg .ui-icon {
|
||||
display: inline-block;
|
||||
margin-left: -20px;
|
||||
top: 2px;
|
||||
|
||||
@@ -228,6 +228,21 @@ function showMsg(msg,loader,timeout,ms) {
|
||||
}
|
||||
}
|
||||
|
||||
function showArtistMsg(msg) {
|
||||
var feedback = $("#ajaxMsg2");
|
||||
update = $("#updatebar");
|
||||
if ( update.is(":visible") ) {
|
||||
var height = update.height() + 35;
|
||||
feedback.css("bottom",height + "px");
|
||||
} else {
|
||||
feedback.removeAttr("style");
|
||||
}
|
||||
feedback.fadeIn();
|
||||
var message = $("<i class='fa fa-refresh fa-spin'></i> " + msg + "</div>");
|
||||
feedback.css("padding","14px 10px")
|
||||
$(feedback).prepend(message);
|
||||
}
|
||||
|
||||
function doAjaxCall(url,elem,reload,form) {
|
||||
// Set Message
|
||||
feedback = $("#ajaxMsg");
|
||||
@@ -334,6 +349,10 @@ function doAjaxCall(url,elem,reload,form) {
|
||||
});
|
||||
}
|
||||
|
||||
function doSimpleAjaxCall(url) {
|
||||
$.ajax(url);
|
||||
}
|
||||
|
||||
function resetFilters(text){
|
||||
if ( $(".dataTables_filter").length > 0 ) {
|
||||
$(".dataTables_filter input").attr("placeholder","filter " + text + "");
|
||||
|
||||
@@ -35,12 +35,12 @@
|
||||
%if type == 'album':
|
||||
<td id="albumname"><a href="${result['albumurl']}">${result['title']}</a></td>
|
||||
%endif
|
||||
<td id="artistname"><a href="${result['url']}" title="${result['uniquename']}">${result['uniquename']}</a></td>
|
||||
<td id="artistname"><a href="addArtist?artistid=${result['id']}" title="${result['uniquename']}">${result['uniquename']}</a></td>
|
||||
<td id="score"><div class="bar"><div class="score" style="width: ${result['score']}px">${result['score']}</div></div></td>
|
||||
%if type == 'album':
|
||||
%if type == 'album':
|
||||
<td id="add"><a href="addReleaseById?rid=${result['albumid']}"><i class="fa fa-plus"></i> Add this album</a></td>
|
||||
%else:
|
||||
<td id="add"><a href="addArtist?artistid=${result['id']}"><i class="fa fa-plus"></i> Add this artist</a></td>
|
||||
<td id="add"><a href="${result['url']}"></i> View on MusicBrainz</a></td>
|
||||
%endif
|
||||
</tr>
|
||||
%endfor
|
||||
@@ -57,10 +57,10 @@
|
||||
<%def name="javascriptIncludes()">
|
||||
|
||||
<script src="js/libs/jquery.dataTables.min.js"></script>
|
||||
|
||||
|
||||
<script>
|
||||
function getArt() {
|
||||
$("table#searchresults_table tr td#albumart img").each(function(){
|
||||
$("table#searchresults_table tr td#albumart img").each(function(){
|
||||
var id = $(this).attr('title');
|
||||
var image = $(this);
|
||||
if ( !image.hasClass('done') ) {
|
||||
@@ -76,7 +76,7 @@
|
||||
"bDestroy": true,
|
||||
"aoColumnDefs": [
|
||||
{ 'bSortable': false, 'aTargets': [ 0,3 ] }
|
||||
],
|
||||
],
|
||||
"oLanguage": {
|
||||
"sLengthMenu":"Show _MENU_ results per page",
|
||||
"sEmptyTable": "No results",
|
||||
@@ -91,7 +91,7 @@
|
||||
resetFilters("album");
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initThisPage();
|
||||
initThisPage();
|
||||
});
|
||||
$(window).load(function(){
|
||||
initFancybox();
|
||||
|
||||
+26
-9
@@ -38,7 +38,8 @@ SIGNAL = None
|
||||
SYS_PLATFORM = None
|
||||
SYS_ENCODING = None
|
||||
|
||||
VERBOSE = 1
|
||||
QUIET = False
|
||||
VERBOSE = False
|
||||
DAEMON = False
|
||||
CREATEPID = False
|
||||
PIDFILE= None
|
||||
@@ -103,6 +104,8 @@ PREFERRED_BITRATE_HIGH_BUFFER = None
|
||||
PREFERRED_BITRATE_LOW_BUFFER = None
|
||||
PREFERRED_BITRATE_ALLOW_LOSSLESS = False
|
||||
DETECT_BITRATE = False
|
||||
LOSSLESS_BITRATE_FROM = None
|
||||
LOSSLESS_BITRATE_TO = None
|
||||
ADD_ARTISTS = False
|
||||
CORRECT_METADATA = False
|
||||
MOVE_FILES = False
|
||||
@@ -154,6 +157,7 @@ TRANSMISSION_PASSWORD = None
|
||||
UTORRENT_HOST = None
|
||||
UTORRENT_USERNAME = None
|
||||
UTORRENT_PASSWORD = None
|
||||
UTORRENT_LABEL = None
|
||||
|
||||
NEWZNAB = False
|
||||
NEWZNAB_HOST = None
|
||||
@@ -247,7 +251,7 @@ PLEX_UPDATE = False
|
||||
PLEX_NOTIFY = False
|
||||
NMA_ENABLED = False
|
||||
NMA_APIKEY = None
|
||||
NMA_PRIORITY = None
|
||||
NMA_PRIORITY = 0
|
||||
NMA_ONSNATCH = None
|
||||
PUSHALOT_ENABLED = False
|
||||
PUSHALOT_APIKEY = None
|
||||
@@ -284,12 +288,15 @@ SONGKICK_ENABLED = False
|
||||
SONGKICK_APIKEY = None
|
||||
SONGKICK_LOCATION = None
|
||||
SONGKICK_FILTER_ENABLED = False
|
||||
MPC_ENABLED = False
|
||||
|
||||
CACHE_SIZEMB = 32
|
||||
JOURNAL_MODE = None
|
||||
|
||||
UMASK = None
|
||||
|
||||
VERIFY_SSL_CERT = True
|
||||
|
||||
def CheckSection(sec):
|
||||
""" Check if INI section exists, if not create it """
|
||||
try:
|
||||
@@ -336,7 +343,7 @@ def initialize():
|
||||
|
||||
with INIT_LOCK:
|
||||
|
||||
global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, SYS_PLATFORM, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \
|
||||
global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, QUIET, 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, GIT_USER, GIT_BRANCH, DO_NOT_OVERRIDE_GIT_BRANCH, \
|
||||
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, \
|
||||
@@ -346,7 +353,7 @@ def initialize():
|
||||
RUTRACKER, RUTRACKER_USER, RUTRACKER_PASSWORD, WHATCD, WHATCD_USERNAME, WHATCD_PASSWORD, DOWNLOAD_TORRENT_DIR, \
|
||||
LIBRARYSCAN, LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, UPDATE_DB_INTERVAL, MB_IGNORE_AGE, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \
|
||||
NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_HOST, HEADPHONES_INDEXER, NZBMATRIX, TRANSMISSION_HOST, TRANSMISSION_USERNAME, TRANSMISSION_PASSWORD, \
|
||||
UTORRENT_HOST, UTORRENT_USERNAME, UTORRENT_PASSWORD, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, NEWZNAB_ENABLED, EXTRA_NEWZNABS, \
|
||||
UTORRENT_HOST, UTORRENT_USERNAME, UTORRENT_PASSWORD, UTORRENT_LABEL, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, NEWZNAB_ENABLED, EXTRA_NEWZNABS, \
|
||||
NZBSORG, NZBSORG_UID, NZBSORG_HASH, NZBSRUS, NZBSRUS_UID, NZBSRUS_APIKEY, OMGWTFNZBS, OMGWTFNZBS_UID, OMGWTFNZBS_APIKEY, \
|
||||
NZB_DOWNLOADER, TORRENT_DOWNLOADER, PREFERRED_WORDS, REQUIRED_WORDS, IGNORED_WORDS, LASTFM_USERNAME, \
|
||||
INTERFACE, FOLDER_PERMISSIONS, FILE_PERMISSIONS, ENCODERFOLDER, ENCODER_PATH, ENCODER, XLDPROFILE, BITRATE, SAMPLINGFREQUENCY, \
|
||||
@@ -356,9 +363,9 @@ def initialize():
|
||||
PUSHBULLET_ENABLED, PUSHBULLET_APIKEY, PUSHBULLET_DEVICEID, PUSHBULLET_ONSNATCH, \
|
||||
MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \
|
||||
XBMC_NOTIFY, LMS_ENABLED, LMS_HOST, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
|
||||
PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, CACHE_SIZEMB, JOURNAL_MODE, UMASK, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \
|
||||
PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, LOSSLESS_BITRATE_FROM, LOSSLESS_BITRATE_TO, CACHE_SIZEMB, JOURNAL_MODE, UMASK, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \
|
||||
PLEX_ENABLED, PLEX_SERVER_HOST, PLEX_CLIENT_HOST, PLEX_USERNAME, PLEX_PASSWORD, PLEX_UPDATE, PLEX_NOTIFY, PUSHALOT_ENABLED, PUSHALOT_APIKEY, \
|
||||
PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED
|
||||
PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED, VERIFY_SSL_CERT
|
||||
|
||||
|
||||
if __INITIALIZED__:
|
||||
@@ -436,6 +443,8 @@ def initialize():
|
||||
PREFERRED_BITRATE_LOW_BUFFER = check_setting_int(CFG, 'General', 'preferred_bitrate_low_buffer', '')
|
||||
PREFERRED_BITRATE_ALLOW_LOSSLESS = bool(check_setting_int(CFG, 'General', 'preferred_bitrate_allow_lossless', 0))
|
||||
DETECT_BITRATE = bool(check_setting_int(CFG, 'General', 'detect_bitrate', 0))
|
||||
LOSSLESS_BITRATE_FROM = check_setting_int(CFG, 'General', 'lossless_bitrate_from', '')
|
||||
LOSSLESS_BITRATE_TO = check_setting_int(CFG, 'General', 'lossless_bitrate_to', '')
|
||||
ADD_ARTISTS = bool(check_setting_int(CFG, 'General', 'auto_add_artists', 1))
|
||||
CORRECT_METADATA = bool(check_setting_int(CFG, 'General', 'correct_metadata', 0))
|
||||
MOVE_FILES = bool(check_setting_int(CFG, 'General', 'move_files', 0))
|
||||
@@ -511,6 +520,7 @@ def initialize():
|
||||
UTORRENT_HOST = check_setting_str(CFG, 'uTorrent', 'utorrent_host', '')
|
||||
UTORRENT_USERNAME = check_setting_str(CFG, 'uTorrent', 'utorrent_username', '')
|
||||
UTORRENT_PASSWORD = check_setting_str(CFG, 'uTorrent', 'utorrent_password', '')
|
||||
UTORRENT_LABEL = check_setting_str(CFG, 'uTorrent', 'utorrent_label', '')
|
||||
|
||||
NEWZNAB = bool(check_setting_int(CFG, 'Newznab', 'newznab', 0))
|
||||
NEWZNAB_HOST = check_setting_str(CFG, 'Newznab', 'newznab_host', '')
|
||||
@@ -640,6 +650,8 @@ def initialize():
|
||||
|
||||
ALBUM_COMPLETION_PCT = check_setting_int(CFG, 'Advanced', 'album_completion_pct', 80)
|
||||
|
||||
VERIFY_SSL_CERT = bool(check_setting_int(CFG, 'Advanced', 'verify_ssl_cert', 1))
|
||||
|
||||
# update folder formats in the config & bump up config version
|
||||
if CONFIG_VERSION == '0':
|
||||
from headphones.helpers import replace_all
|
||||
@@ -710,8 +722,9 @@ def initialize():
|
||||
if VERBOSE:
|
||||
sys.stderr.write('Unable to create the log directory. Logging to screen only.\n')
|
||||
|
||||
# Start the logger, silence console logging if we need to
|
||||
logger.initLogger(verbose=VERBOSE)
|
||||
# Start the logger, disable console if needed
|
||||
logger.initLogger(console=not QUIET, verbose=VERBOSE)
|
||||
logger.initLogger(console=not QUIET, verbose=False)
|
||||
|
||||
if not CACHE_DIR:
|
||||
# Put the cache dir in the data dir for now
|
||||
@@ -859,6 +872,8 @@ def config_write():
|
||||
new_config['General']['preferred_bitrate_low_buffer'] = PREFERRED_BITRATE_LOW_BUFFER
|
||||
new_config['General']['preferred_bitrate_allow_lossless'] = int(PREFERRED_BITRATE_ALLOW_LOSSLESS)
|
||||
new_config['General']['detect_bitrate'] = int(DETECT_BITRATE)
|
||||
new_config['General']['lossless_bitrate_from'] = LOSSLESS_BITRATE_FROM
|
||||
new_config['General']['lossless_bitrate_to'] = LOSSLESS_BITRATE_TO
|
||||
new_config['General']['auto_add_artists'] = int(ADD_ARTISTS)
|
||||
new_config['General']['correct_metadata'] = int(CORRECT_METADATA)
|
||||
new_config['General']['move_files'] = int(MOVE_FILES)
|
||||
@@ -941,6 +956,7 @@ def config_write():
|
||||
new_config['uTorrent']['utorrent_host'] = UTORRENT_HOST
|
||||
new_config['uTorrent']['utorrent_username'] = UTORRENT_USERNAME
|
||||
new_config['uTorrent']['utorrent_password'] = UTORRENT_PASSWORD
|
||||
new_config['uTorrent']['utorrent_label'] = UTORRENT_LABEL
|
||||
|
||||
new_config['Newznab'] = {}
|
||||
new_config['Newznab']['newznab'] = int(NEWZNAB)
|
||||
@@ -1010,7 +1026,7 @@ def config_write():
|
||||
new_config['NMA'] = {}
|
||||
new_config['NMA']['nma_enabled'] = int(NMA_ENABLED)
|
||||
new_config['NMA']['nma_apikey'] = NMA_APIKEY
|
||||
new_config['NMA']['nma_priority'] = NMA_PRIORITY
|
||||
new_config['NMA']['nma_priority'] = int(NMA_PRIORITY)
|
||||
new_config['NMA']['nma_onsnatch'] = int(NMA_ONSNATCH)
|
||||
|
||||
new_config['Pushalot'] = {}
|
||||
@@ -1088,6 +1104,7 @@ def config_write():
|
||||
new_config['Advanced']['album_completion_pct'] = ALBUM_COMPLETION_PCT
|
||||
new_config['Advanced']['cache_sizemb'] = CACHE_SIZEMB
|
||||
new_config['Advanced']['journal_mode'] = JOURNAL_MODE
|
||||
new_config['Advanced']['verify_ssl_cert'] = int(VERIFY_SSL_CERT)
|
||||
|
||||
new_config.write()
|
||||
|
||||
|
||||
+35
-28
@@ -226,26 +226,37 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
skip_log = 0
|
||||
#Make a user configurable variable to skip update of albums with release dates older than this date (in days)
|
||||
pause_delta = headphones.MB_IGNORE_AGE
|
||||
|
||||
check_release_date = myDB.action("SELECT ReleaseDate, Status from albums WHERE ArtistID=? AND AlbumTitle=?", (artistid, al_title)).fetchone()
|
||||
|
||||
#Skip update if Status set
|
||||
if check_release_date and check_release_date[1]:
|
||||
logger.info("[%s] Not updating: %s (Status is %s, skipping)" % (artist['artist_name'], rg['title'], check_release_date[1]))
|
||||
continue
|
||||
|
||||
rg_exists = myDB.action("SELECT * from albums WHERE AlbumID=?", [rg['id']]).fetchone()
|
||||
|
||||
if not forcefull:
|
||||
if check_release_date:
|
||||
if check_release_date[0] is None:
|
||||
|
||||
new_release_group = False
|
||||
|
||||
try:
|
||||
check_release_date = rg_exists['ReleaseDate']
|
||||
except TypeError:
|
||||
check_release_date = None
|
||||
new_release_group = True
|
||||
|
||||
|
||||
if new_release_group:
|
||||
|
||||
logger.info("[%s] Now adding: %s (New Release Group)" % (artist['artist_name'], rg['title']))
|
||||
new_releases = mb.get_new_releases(rgid,includeExtras)
|
||||
|
||||
else:
|
||||
|
||||
if check_release_date is None or check_release_date == u"None":
|
||||
logger.info("[%s] Now updating: %s (No Release Date)" % (artist['artist_name'], rg['title']))
|
||||
new_releases = mb.get_new_releases(rgid,includeExtras,True)
|
||||
else:
|
||||
if len(check_release_date[0]) == 10:
|
||||
release_date = check_release_date[0]
|
||||
elif len(check_release_date[0]) == 7:
|
||||
release_date = check_release_date[0]+"-31"
|
||||
elif len(check_release_date[0]) == 4:
|
||||
release_date = check_release_date[0]+"-12-31"
|
||||
if len(check_release_date) == 10:
|
||||
release_date = check_release_date
|
||||
elif len(check_release_date) == 7:
|
||||
release_date = check_release_date+"-31"
|
||||
elif len(check_release_date) == 4:
|
||||
release_date = check_release_date+"-12-31"
|
||||
else:
|
||||
release_date = today
|
||||
if helpers.get_age(today) - helpers.get_age(release_date) < pause_delta:
|
||||
@@ -255,9 +266,6 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
logger.info("[%s] Skipping: %s (Release Date >%s Days)" % (artist['artist_name'], rg['title'], pause_delta))
|
||||
skip_log = 1
|
||||
new_releases = 0
|
||||
else:
|
||||
logger.info("[%s] Now adding: %s (New Release Group)" % (artist['artist_name'], rg['title']))
|
||||
new_releases = mb.get_new_releases(rgid,includeExtras)
|
||||
|
||||
if force_repackage == 1:
|
||||
new_releases = -1
|
||||
@@ -268,11 +276,6 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
logger.info("[%s] Now adding/updating: %s (Comprehensive Force)" % (artist['artist_name'], rg['title']))
|
||||
new_releases = mb.get_new_releases(rgid,includeExtras,forcefull)
|
||||
|
||||
#What this does is adds new releases per artist to the allalbums + alltracks databases
|
||||
#new_releases = mb.get_new_releases(rgid,includeExtras)
|
||||
#print al_title
|
||||
#print new_releases
|
||||
|
||||
if new_releases != 0:
|
||||
#Dump existing hybrid release since we're repackaging/replacing it
|
||||
myDB.action("DELETE from albums WHERE ReleaseID=?", [rg['id']])
|
||||
@@ -381,7 +384,7 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
# If there's no release in the main albums tables, add the default (hybrid)
|
||||
# If there is a release, check the ReleaseID against the AlbumID to see if they differ (user updated)
|
||||
# check if the album already exists
|
||||
rg_exists = myDB.action("SELECT * from albums WHERE AlbumID=?", [rg['id']]).fetchone()
|
||||
|
||||
if not rg_exists:
|
||||
releaseid = rg['id']
|
||||
else:
|
||||
@@ -402,11 +405,14 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
"ReleaseFormat": album['ReleaseFormat']
|
||||
}
|
||||
|
||||
if not rg_exists:
|
||||
|
||||
if rg_exists:
|
||||
newValueDict['DateAdded'] = rg_exists['DateAdded']
|
||||
newValueDict['Status'] = rg_exists['Status']
|
||||
|
||||
else:
|
||||
today = helpers.today()
|
||||
|
||||
newValueDict['DateAdded']= today
|
||||
newValueDict['DateAdded'] = today
|
||||
|
||||
if headphones.AUTOWANT_ALL:
|
||||
newValueDict['Status'] = "Wanted"
|
||||
@@ -497,7 +503,7 @@ def finalize_update(artistid, artistname, errors=False):
|
||||
myDB = db.DBConnection()
|
||||
|
||||
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]))
|
||||
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND AlbumID IN (SELECT AlbumID FROM albums WHERE Status != "Ignored")', [artistid]))
|
||||
#havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ?', [artist['artist_name']]))
|
||||
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [artistname]))
|
||||
|
||||
@@ -583,6 +589,7 @@ def addReleaseById(rid):
|
||||
controlValueDict = {"AlbumID": rgid}
|
||||
|
||||
newValueDict = {"ArtistID": release_dict['artist_id'],
|
||||
"ReleaseID": rgid,
|
||||
"ArtistName": release_dict['artist_name'],
|
||||
"AlbumTitle": release_dict['rg_title'],
|
||||
"AlbumASIN": release_dict['asin'],
|
||||
|
||||
@@ -328,12 +328,12 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [ArtistID])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [ArtistName]))
|
||||
myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistID=?', [havetracks, ArtistID])
|
||||
|
||||
update_album_status()
|
||||
if not append:
|
||||
update_album_status()
|
||||
lastfm.getSimilar()
|
||||
logger.info('Library scan complete')
|
||||
|
||||
#ADDED THIS SECTION TO MARK ALBUMS AS DOWNLOADED IF ARTISTS ARE ADDED EN MASSE BEFORE LIBRARY IS SCANNED
|
||||
#ADDED THIS SECTION TO MARK ALBUMS AS DOWNLOADED IF ARTISTS ARE ADDED EN MASSE BEFORE LIBRARY IS SCANNED
|
||||
def update_album_status(AlbumID=None):
|
||||
myDB = db.DBConnection()
|
||||
logger.info('Counting matched tracks to mark albums as skipped/downloaded')
|
||||
@@ -355,13 +355,21 @@ def update_album_status(AlbumID=None):
|
||||
album_completion = 0
|
||||
logger.info('Album %s does not have any tracks in database' % album['AlbumTitle'])
|
||||
|
||||
if album_completion >= headphones.ALBUM_COMPLETION_PCT:
|
||||
if album_completion >= headphones.ALBUM_COMPLETION_PCT and album['Status'] == 'Skipped':
|
||||
new_album_status = "Downloaded"
|
||||
|
||||
# I don't think we want to change Downloaded->Skipped.....
|
||||
# I think we can only automatically change Skipped->Downloaded when updating
|
||||
# There was a bug report where this was causing infinite downloads if the album was
|
||||
# recent, but matched to less than 80%. It would go Downloaded->Skipped->Wanted->Downloaded->Skipped->Wanted->etc....
|
||||
#else:
|
||||
# if album['Status'] == "Skipped" or album['Status'] == "Downloaded":
|
||||
# new_album_status = "Skipped"
|
||||
# else:
|
||||
# new_album_status = album['Status']
|
||||
else:
|
||||
if album['Status'] == "Skipped" or album['Status'] == "Downloaded":
|
||||
new_album_status = "Skipped"
|
||||
else:
|
||||
new_album_status = album['Status']
|
||||
new_album_status = album['Status']
|
||||
|
||||
myDB.upsert("albums", {'Status' : new_album_status}, {'AlbumID' : album['AlbumID']})
|
||||
if new_album_status != album['Status']:
|
||||
logger.info('Album %s changed to %s' % (album['AlbumTitle'], new_album_status))
|
||||
|
||||
+17
-4
@@ -43,19 +43,32 @@ class LogListHandler(logging.Handler):
|
||||
|
||||
headphones.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName))
|
||||
|
||||
def initLogger(verbose=1):
|
||||
def initLogger(console=False, verbose=False):
|
||||
"""
|
||||
Setup logging for Headphones. It uses the logger instance with the name
|
||||
'headphones'. Three log handlers are added:
|
||||
|
||||
* RotatingFileHandler: for the file headphones.log
|
||||
* LogListHandler: for Web UI
|
||||
* StreamHandler: for console (if verbose > 0)
|
||||
* StreamHandler: for console (if console)
|
||||
|
||||
Console logging is only enabled if console is set to True.
|
||||
"""
|
||||
|
||||
# Close and remove old handlers. This is required to reinit the loggers
|
||||
# at runtime
|
||||
for handler in logger.handlers[:]:
|
||||
# Just make sure it is cleaned up.
|
||||
if isinstance(handler, handlers.RotatingFileHandler):
|
||||
handler.close()
|
||||
elif isinstance(handler, logging.StreamHandler):
|
||||
handler.flush()
|
||||
|
||||
logger.removeHandler(handler)
|
||||
|
||||
# Configure the logger to accept all messages
|
||||
logger.propagate = False
|
||||
logger.setLevel(logging.DEBUG if verbose == 2 else logging.INFO)
|
||||
logger.setLevel(logging.DEBUG if verbose else logging.INFO)
|
||||
|
||||
# Setup file logger
|
||||
filename = os.path.join(headphones.LOG_DIR, FILENAME)
|
||||
@@ -74,7 +87,7 @@ def initLogger(verbose=1):
|
||||
logger.addHandler(loglist_handler)
|
||||
|
||||
# Setup console logger
|
||||
if verbose:
|
||||
if console:
|
||||
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(threadName)s : %(message)s', '%d-%b-%Y %H:%M:%S')
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(console_formatter)
|
||||
|
||||
+41
-24
@@ -27,7 +27,7 @@ import time
|
||||
from xml.dom import minidom
|
||||
from httplib import HTTPSConnection
|
||||
from urllib import urlencode
|
||||
|
||||
from lib.pynma import pynma
|
||||
import lib.oauth2 as oauth
|
||||
import lib.pythontwitter as twitter
|
||||
|
||||
@@ -169,6 +169,17 @@ class PROWL:
|
||||
|
||||
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
||||
|
||||
class MPC:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
pass
|
||||
|
||||
def notify( self ):
|
||||
|
||||
subprocess.call( ["mpc", "update"] )
|
||||
|
||||
|
||||
class XBMC:
|
||||
|
||||
def __init__(self):
|
||||
@@ -192,9 +203,9 @@ class XBMC:
|
||||
url = host + '/jsonrpc'
|
||||
|
||||
if self.password:
|
||||
response = request.request_json(url, method="POST", data=simplejson.dumps(data), headers=headers, auth=(self.username, self.password))
|
||||
response = request.request_json(url, method="post", data=simplejson.dumps(data), headers=headers, auth=(self.username, self.password))
|
||||
else:
|
||||
response = request.request_json(url, method="POST", data=simplejson.dumps(data), headers=headers)
|
||||
response = request.request_json(url, method="post", data=simplejson.dumps(data), headers=headers)
|
||||
|
||||
if response:
|
||||
return response[0]['result']
|
||||
@@ -369,34 +380,40 @@ class Plex:
|
||||
logger.warn('Error sending notification request to Plex Media Server')
|
||||
|
||||
class NMA:
|
||||
def notify(self, artist=None, album=None, snatched=None):
|
||||
title = 'Headphones'
|
||||
api = headphones.NMA_APIKEY
|
||||
nma_priority = headphones.NMA_PRIORITY
|
||||
|
||||
def __init__(self):
|
||||
logger.debug(u"NMA title: " + title)
|
||||
logger.debug(u"NMA API: " + api)
|
||||
logger.debug(u"NMA Priority: " + str(nma_priority))
|
||||
|
||||
self.apikey = headphones.NMA_APIKEY
|
||||
self.priority = headphones.NMA_PRIORITY
|
||||
|
||||
def _send(self, data):
|
||||
return request.request_content('https://www.notifymyandroid.com/publicapi/notify', data=data)
|
||||
|
||||
def notify(self, artist=None, album=None, snatched_nzb=None):
|
||||
|
||||
apikey = self.apikey
|
||||
priority = self.priority
|
||||
|
||||
if snatched_nzb:
|
||||
event = snatched_nzb + " snatched!"
|
||||
description = "Headphones has snatched: " + snatched_nzb + " and has sent it to SABnzbd+"
|
||||
if snatched:
|
||||
event = snatched + " snatched!"
|
||||
message = "Headphones has snatched: " + snatched
|
||||
else:
|
||||
event = artist + ' - ' + album + ' complete!'
|
||||
description = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
|
||||
message = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
|
||||
|
||||
data = { 'apikey': apikey, 'application':'Headphones', 'event': event, 'description': description, 'priority': priority}
|
||||
logger.debug(u"NMA event: " + event)
|
||||
logger.debug(u"NMA message: " + message)
|
||||
|
||||
logger.info('Sending notification request to NotifyMyAndroid')
|
||||
request = self._send(data)
|
||||
batch = False
|
||||
|
||||
if not request:
|
||||
logger.warn('Error sending notification request to NotifyMyAndroid')
|
||||
p = pynma.PyNMA()
|
||||
keys = api.split(',')
|
||||
p.addkey(keys)
|
||||
|
||||
if len(keys) > 1: batch = True
|
||||
|
||||
response = p.push(title, event, message, priority=nma_priority, batch_mode=batch)
|
||||
|
||||
if not response[api][u'code'] == u'200':
|
||||
logger.error(u'Could not send notification to NotifyMyAndroid')
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
class PUSHBULLET:
|
||||
|
||||
|
||||
+22
-26
@@ -31,7 +31,7 @@ from headphones import logger, helpers, request, mb, music_encoder
|
||||
postprocessor_lock = threading.Lock()
|
||||
|
||||
def checkFolder():
|
||||
|
||||
|
||||
with postprocessor_lock:
|
||||
|
||||
myDB = db.DBConnection()
|
||||
@@ -42,30 +42,18 @@ def checkFolder():
|
||||
if album['FolderName']:
|
||||
|
||||
if album['Kind'] == 'nzb':
|
||||
# We're now checking sab config options after sending to determine renaming - but we'll keep the
|
||||
# iterations in just in case we can't read the config for some reason
|
||||
download_dir = headphones.DOWNLOAD_DIR
|
||||
else:
|
||||
download_dir = headphones.DOWNLOAD_TORRENT_DIR
|
||||
|
||||
nzb_album_possibilities = [ album['FolderName'],
|
||||
helpers.sab_replace_dots(album['FolderName']),
|
||||
helpers.sab_replace_spaces(album['FolderName']),
|
||||
helpers.sab_replace_spaces(sab_replace_dots(album['FolderName']))
|
||||
]
|
||||
|
||||
for nzb_folder_name in nzb_album_possibilities:
|
||||
|
||||
nzb_album_path = os.path.join(headphones.DOWNLOAD_DIR, nzb_folder_name).encode(headphones.SYS_ENCODING, 'replace')
|
||||
|
||||
if os.path.exists(nzb_album_path):
|
||||
logger.debug('Found %s in NZB download folder. Verifying....' % album['FolderName'])
|
||||
verify(album['AlbumID'], nzb_album_path, 'nzb')
|
||||
|
||||
if album['Kind'] == 'torrent':
|
||||
album_path = os.path.join(download_dir, album['FolderName']).encode(headphones.SYS_ENCODING,'replace')
|
||||
logger.info("Checking if %s exists" % album_path)
|
||||
if os.path.exists(album_path):
|
||||
logger.info('Found "' + album['FolderName'] + '" in ' + album['Kind'] + ' download folder. Verifying....')
|
||||
verify(album['AlbumID'], album_path, album['Kind'])
|
||||
|
||||
torrent_album_path = os.path.join(headphones.DOWNLOAD_TORRENT_DIR, album['FolderName']).encode(headphones.SYS_ENCODING,'replace')
|
||||
|
||||
if os.path.exists(torrent_album_path):
|
||||
logger.debug('Found %s in torrent download folder. Verifying....' % album['FolderName'])
|
||||
verify(album['AlbumID'], torrent_album_path, 'torrent')
|
||||
else:
|
||||
logger.info("No folder name found for " + album['Title'])
|
||||
|
||||
def verify(albumid, albumpath, Kind=None, forced=False):
|
||||
|
||||
@@ -83,17 +71,21 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
||||
try:
|
||||
release_list = mb.getReleaseGroup(albumid)
|
||||
except Exception, e:
|
||||
logger.info('Unable to get release information for manual album with rgid: %s. Error: %s' % (albumid, e))
|
||||
logger.error('Unable to get release information for manual album with rgid: %s. Error: %s' % (albumid, e))
|
||||
return
|
||||
|
||||
if not release_list:
|
||||
logger.info('Unable to get release information for manual album with rgid: %s' % albumid)
|
||||
logger.error('Unable to get release information for manual album with rgid: %s' % albumid)
|
||||
return
|
||||
|
||||
# Since we're just using this to create the bare minimum information to insert an artist/album combo, use the first release
|
||||
releaseid = release_list[0]['id']
|
||||
|
||||
release_dict = mb.getRelease(releaseid)
|
||||
|
||||
if not release_dict:
|
||||
logger.error('Unable to get release information for manual album with rgid: %s. Cannot continue' % albumid)
|
||||
return
|
||||
|
||||
logger.info(u"Now adding/updating artist: " + release_dict['artist_name'])
|
||||
|
||||
@@ -364,7 +356,7 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
headphones.MOVE_FILES:
|
||||
|
||||
if not os.access(downloaded_track, os.W_OK):
|
||||
logger.error("Track file is not writeable, which is equired for some post processing steps: %s", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
logger.error("Track file is not writeable, which is required for some post processing steps: %s", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
return
|
||||
|
||||
#start encoding
|
||||
@@ -502,6 +494,10 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
boxcar = notifiers.BOXCAR()
|
||||
boxcar.notify('Headphones processed: ' + pushmessage, "Download and Postprocessing completed", release['AlbumID'])
|
||||
|
||||
if headphones.MPC_ENABLED:
|
||||
mpc = notifiers.MPC()
|
||||
mpc.notify()
|
||||
|
||||
def embedAlbumArt(artwork, downloaded_track_list):
|
||||
logger.info('Embedding album art')
|
||||
|
||||
|
||||
+15
-2
@@ -5,6 +5,7 @@ from bs4 import BeautifulSoup
|
||||
|
||||
import requests
|
||||
import feedparser
|
||||
import headphones
|
||||
|
||||
def request_response(url, method="get", auto_raise=True, whitelist_status_code=None, **kwargs):
|
||||
"""
|
||||
@@ -17,9 +18,13 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N
|
||||
if whitelist_status_code and type(whitelist_status_code) != list:
|
||||
whitelist_status_code = [whitelist_status_code]
|
||||
|
||||
# Disable verification of SSL certificates if requested. Note: this could
|
||||
# pose a security issue!
|
||||
kwargs["verify"] = headphones.VERIFY_SSL_CERT
|
||||
|
||||
# Map method to the request.XXX method. This is a simple hack, but it allows
|
||||
# requests to apply more magic per method. See lib/requests/api.py.
|
||||
request_method = getattr(requests, method)
|
||||
request_method = getattr(requests, method.lower())
|
||||
|
||||
try:
|
||||
# Request the URL
|
||||
@@ -45,7 +50,15 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N
|
||||
logger.error("Request timed out.")
|
||||
except requests.HTTPError, e:
|
||||
if e.response is not None:
|
||||
logger.error("Request raise HTTP error with status code: %d", e.response.status_code)
|
||||
if e.response.status_code >= 500:
|
||||
cause = "remote server error"
|
||||
elif e.response.status_code >= 400:
|
||||
cause = "local request error"
|
||||
else:
|
||||
# I don't think we will end up here, but for completeness
|
||||
cause = "unknown"
|
||||
|
||||
logger.error("Request raise HTTP error with status code %d (%s).", e.response.status_code, cause)
|
||||
else:
|
||||
logger.error("Request raised HTTP error.")
|
||||
except requests.RequestException, e:
|
||||
|
||||
+98
-15
@@ -21,6 +21,8 @@ from lib.pygazelle import encoding as gazelleencoding
|
||||
from lib.pygazelle import format as gazelleformat
|
||||
from lib.pygazelle import media as gazellemedia
|
||||
from xml.dom import minidom
|
||||
from base64 import b16encode, b32decode
|
||||
from hashlib import sha1
|
||||
|
||||
import os, re, time
|
||||
import string
|
||||
@@ -28,12 +30,13 @@ import shutil
|
||||
import requests
|
||||
import subprocess
|
||||
|
||||
|
||||
import headphones
|
||||
from headphones.common import USER_AGENT
|
||||
from headphones import logger, db, helpers, classes, sab, nzbget, request
|
||||
from headphones import transmission, notifiers
|
||||
from headphones import utorrent, transmission, notifiers
|
||||
|
||||
import lib.bencode as bencode
|
||||
from lib.bencode import bencode as bencode, bdecode
|
||||
|
||||
import headphones.searcher_rutracker as rutrackersearch
|
||||
rutracker = rutrackersearch.Rutracker()
|
||||
@@ -148,11 +151,20 @@ def sort_search_results(resultlist, album, new):
|
||||
|
||||
# Add a priority if it has any of the preferred words
|
||||
temp_list = []
|
||||
preferred_words = None
|
||||
if headphones.PREFERRED_WORDS:
|
||||
preferred_words = helpers.split_string(headphones.PREFERRED_WORDS)
|
||||
for result in resultlist:
|
||||
if headphones.PREFERRED_WORDS and any(word.lower() in result[0].lower() for word in helpers.split_string(headphones.PREFERRED_WORDS)):
|
||||
temp_list.append((result[0],result[1],result[2],result[3],result[4],1))
|
||||
else:
|
||||
temp_list.append((result[0],result[1],result[2],result[3],result[4],0))
|
||||
priority = 0
|
||||
if preferred_words:
|
||||
if any(word.lower() in result[0].lower() for word in preferred_words):
|
||||
priority = 1
|
||||
# add a search provider priority (weighted based on position)
|
||||
i = next((i for i, word in enumerate(preferred_words) if word in result[3].lower()), None)
|
||||
if i != None:
|
||||
priority += round((len(preferred_words) - i) / float(len(preferred_words)),2)
|
||||
|
||||
temp_list.append((result[0],result[1],result[2],result[3],result[4],priority))
|
||||
|
||||
resultlist = temp_list
|
||||
|
||||
@@ -215,6 +227,34 @@ def sort_search_results(resultlist, album, new):
|
||||
|
||||
finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
|
||||
|
||||
# lossless - ignore results if target size outside bitrate range
|
||||
elif headphones.PREFERRED_QUALITY == 3 and (headphones.LOSSLESS_BITRATE_FROM or headphones.LOSSLESS_BITRATE_TO):
|
||||
|
||||
finallist = []
|
||||
tracks = myDB.select('SELECT TrackDuration from tracks WHERE AlbumID=?', [album['AlbumID']])
|
||||
|
||||
if len(tracks):
|
||||
|
||||
albumlength = sum([pair[0] for pair in tracks])
|
||||
mintargetsize = 0
|
||||
maxtargetsize = 0
|
||||
if headphones.LOSSLESS_BITRATE_FROM:
|
||||
mintargetsize = albumlength/1000 * int(headphones.LOSSLESS_BITRATE_FROM) * 128
|
||||
if headphones.LOSSLESS_BITRATE_TO:
|
||||
maxtargetsize = albumlength/1000 * int(headphones.LOSSLESS_BITRATE_TO) * 128
|
||||
|
||||
if mintargetsize > 0 or maxtargetsize > 0:
|
||||
for i, result in reversed(list(enumerate(resultlist))):
|
||||
if int(result[1]) < mintargetsize and mintargetsize > 0 or int(result[1]) > maxtargetsize and maxtargetsize > 0:
|
||||
if int(result[1]) < mintargetsize:
|
||||
logger.info("%s is too small for this album - not considering it. (Size: %s, Minsize: %s)", result[0], helpers.bytes_to_mb(result[1]), helpers.bytes_to_mb(mintargetsize))
|
||||
else:
|
||||
logger.info("%s is too large for this album - not considering it. (Size: %s, Maxsize: %s)", result[0], helpers.bytes_to_mb(result[1]), helpers.bytes_to_mb(maxtargetsize))
|
||||
del resultlist[i]
|
||||
|
||||
if len (resultlist):
|
||||
finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
|
||||
|
||||
else:
|
||||
|
||||
finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True)
|
||||
@@ -307,13 +347,13 @@ def searchNZB(album, new=False, losslessOnly=False):
|
||||
params = {
|
||||
"t": "search",
|
||||
"cat": categories,
|
||||
"apikey": '89edf227c1de9b3de50383fff11466c6',
|
||||
"apikey": '964d601959918a578a670984bdee9357',
|
||||
"maxage": headphones.USENET_RETENTION,
|
||||
"q": term
|
||||
}
|
||||
|
||||
data = request.request_feed(
|
||||
url="http://headphones.codeshy.com/newznab/api",
|
||||
url="http://indexer.codeshy.com/api",
|
||||
params=params, headers=headers,
|
||||
auth=(headphones.HPUSER, headphones.HPPASS)
|
||||
)
|
||||
@@ -534,8 +574,7 @@ def searchNZB(album, new=False, losslessOnly=False):
|
||||
|
||||
data = request.request_json(
|
||||
url='http://api.omgwtfnzbs.org/json/',
|
||||
params=params, headers=headers,
|
||||
validator=lambda x: type(x) == dict
|
||||
params=params, headers=headers
|
||||
)
|
||||
|
||||
# Parse response
|
||||
@@ -659,7 +698,7 @@ def send_to_downloader(data, bestqual, album):
|
||||
#Open the fresh torrent file again so we can extract the proper torrent name
|
||||
#Used later in post-processing.
|
||||
with open(download_path, 'rb') as fp:
|
||||
torrent_info = bencode.bdecode(fp.read())
|
||||
torrent_info = bdecode(fp.read())
|
||||
|
||||
folder_name = torrent_info['info'].get('name', '')
|
||||
logger.info('Torrent folder name: %s' % folder_name)
|
||||
@@ -696,6 +735,32 @@ def send_to_downloader(data, bestqual, album):
|
||||
except Exception, e:
|
||||
logger.exception("Unhandled exception")
|
||||
|
||||
else:# if headphones.TORRENT_DOWNLOADER == 2:
|
||||
logger.info("Sending torrent to uTorrent")
|
||||
|
||||
# rutracker needs cookies to be set, pass the .torrent file instead of url
|
||||
if bestqual[3] == 'rutracker.org':
|
||||
file_or_url = rutracker.get_torrent(bestqual[2])
|
||||
else:
|
||||
file_or_url = bestqual[2]
|
||||
|
||||
_hash = CalculateTorrentHash(file_or_url, data)
|
||||
|
||||
folder_name = utorrent.addTorrent(file_or_url, _hash)
|
||||
|
||||
if folder_name:
|
||||
logger.info('Torrent folder name: %s' % folder_name)
|
||||
else:
|
||||
logger.error('Torrent folder name could not be determined')
|
||||
return
|
||||
|
||||
# remove temp .torrent file created above
|
||||
if bestqual[3] == 'rutracker.org':
|
||||
try:
|
||||
shutil.rmtree(os.path.split(file_or_url)[0])
|
||||
except Exception, e:
|
||||
logger.exception("Unhandled exception")
|
||||
|
||||
myDB = db.DBConnection()
|
||||
myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [album['AlbumID']])
|
||||
myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?, ?)', [album['AlbumID'], bestqual[0], bestqual[1], bestqual[2], "Snatched", folder_name, kind])
|
||||
@@ -733,7 +798,7 @@ def send_to_downloader(data, bestqual, album):
|
||||
if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
|
||||
logger.info(u"Sending NMA notification")
|
||||
nma = notifiers.NMA()
|
||||
nma.notify(snatched_nzb=name)
|
||||
nma.notify(snatched=name)
|
||||
if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH:
|
||||
logger.info(u"Sending Pushalot notification")
|
||||
pushalot = notifiers.PUSHALOT()
|
||||
@@ -1232,6 +1297,8 @@ def searchTorrent(album, new=False, losslessOnly=False):
|
||||
continue
|
||||
else:
|
||||
url = item.findAll("a")[3]['href']
|
||||
if url.lower().startswith("//"):
|
||||
url = "http:" + url
|
||||
formatted_size = re.search('Size (.*),', unicode(item)).group(1).replace(u'\xa0', ' ')
|
||||
size = helpers.piratesize(formatted_size)
|
||||
if size < maxsize and minimumseeders < seeds and url != None:
|
||||
@@ -1374,8 +1441,8 @@ def preprocess(resultlist):
|
||||
for result in resultlist:
|
||||
|
||||
if result[4] == 'torrent':
|
||||
#Get out of here if we're using Transmission or uTorrent
|
||||
if headphones.TORRENT_DOWNLOADER != 0:
|
||||
#Get out of here if we're using Transmission
|
||||
if headphones.TORRENT_DOWNLOADER == 1: ## if not a magnet link still need the .torrent to generate hash... uTorrent support labeling
|
||||
return True, result
|
||||
# get outta here if rutracker
|
||||
if result[3] == 'rutracker.org':
|
||||
@@ -1424,4 +1491,20 @@ def preprocess(resultlist):
|
||||
logger.error("Couldn't retrieve the best nzb. Skipping.")
|
||||
continue
|
||||
|
||||
return (None, None)
|
||||
return (None, None)
|
||||
|
||||
|
||||
|
||||
def CalculateTorrentHash(link, data):
|
||||
|
||||
if link.startswith('magnet'):
|
||||
tor_hash = re.findall('urn:btih:([\w]{32,40})', link)[0]
|
||||
if len(tor_hash) == 32:
|
||||
tor_hash = b16encode(b32decode(tor_hash)).lower()
|
||||
else:
|
||||
info = bdecode(data)["info"]
|
||||
tor_hash = sha1(bencode(info)).hexdigest()
|
||||
|
||||
logger.debug('Torrent Hash: ' + str(tor_hash))
|
||||
|
||||
return tor_hash
|
||||
+107
-113
@@ -18,6 +18,7 @@ from tempfile import mkdtemp
|
||||
class Rutracker():
|
||||
|
||||
logged_in = False
|
||||
|
||||
# Stores a number of login attempts to prevent recursion.
|
||||
#login_counter = 0
|
||||
|
||||
@@ -29,7 +30,7 @@ class Rutracker():
|
||||
|
||||
def login(self, login, password):
|
||||
"""Implements tracker login procedure."""
|
||||
|
||||
|
||||
self.logged_in = False
|
||||
|
||||
if login is None or password is None:
|
||||
@@ -51,7 +52,6 @@ class Rutracker():
|
||||
pass
|
||||
|
||||
# Check if we're logged in
|
||||
|
||||
for cookie in self.cookiejar:
|
||||
if cookie.name == 'bb_data':
|
||||
self.logged_in = True
|
||||
@@ -64,7 +64,6 @@ class Rutracker():
|
||||
"""
|
||||
|
||||
# Build search url
|
||||
|
||||
searchterm = ''
|
||||
if artist != 'Various Artists':
|
||||
searchterm = artist
|
||||
@@ -82,8 +81,7 @@ class Rutracker():
|
||||
else:
|
||||
format = '+mp3||aac'
|
||||
|
||||
# sort by size, descending.
|
||||
|
||||
# sort by size, descending.
|
||||
sort = '&o=7&s=2'
|
||||
|
||||
searchurl = "%s?nm=%s%s%s" % (providerurl, urllib.quote(searchterm), format, sort)
|
||||
@@ -111,25 +109,21 @@ class Rutracker():
|
||||
#logger.debug (soup.prettify())
|
||||
|
||||
# Title
|
||||
|
||||
for link in soup.find_all('a', attrs={'class' : 'med tLink hl-tags bold'}):
|
||||
title = link.get_text()
|
||||
titles.append(title)
|
||||
|
||||
# Download URL
|
||||
|
||||
for link in soup.find_all('a', attrs={'class' : 'small tr-dl dl-stub'}):
|
||||
url = link.get('href')
|
||||
urls.append(url)
|
||||
|
||||
# Seeders
|
||||
|
||||
for link in soup.find_all('b', attrs={'class' : 'seedmed'}):
|
||||
seeder = link.get_text()
|
||||
seeders.append(seeder)
|
||||
|
||||
# Size
|
||||
|
||||
for link in soup.find_all('td', attrs={'class' : 'row4 small nowrap tor-size'}):
|
||||
size = link.u.string
|
||||
sizes.append(size)
|
||||
@@ -138,30 +132,33 @@ class Rutracker():
|
||||
pass
|
||||
|
||||
# Combine lists
|
||||
|
||||
torrentlist = zip(titles, urls, seeders, sizes)
|
||||
|
||||
# return if nothing found
|
||||
|
||||
if not torrentlist:
|
||||
return False
|
||||
|
||||
# get headphones track count for album, return if not found
|
||||
|
||||
myDB = db.DBConnection()
|
||||
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 all valid entries, ignored, required words now checked in searcher.py
|
||||
|
||||
#unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd']
|
||||
|
||||
formatlist = ['ape', 'flac', 'ogg', 'm4a', 'aac', 'mp3', 'wav', 'aif']
|
||||
deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive']
|
||||
# don't bother checking track counts anymore, let searcher filter instead
|
||||
# leave code in just in case
|
||||
check_track_count = False
|
||||
|
||||
if check_track_count:
|
||||
|
||||
# get headphones track count for album, return if not found
|
||||
myDB = db.DBConnection()
|
||||
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 all valid entries, ignored, required words now checked in searcher.py
|
||||
|
||||
#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:
|
||||
|
||||
@@ -169,105 +166,102 @@ class Rutracker():
|
||||
url = torrent[1]
|
||||
seeders = torrent[2]
|
||||
size = torrent[3]
|
||||
|
||||
title = returntitle.lower()
|
||||
|
||||
if int(size) <= maxsize and int(seeders) >= minseeders:
|
||||
|
||||
# Check torrent info
|
||||
|
||||
torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t']
|
||||
self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False))
|
||||
|
||||
# Debug
|
||||
#for cookie in self.cookiejar:
|
||||
# logger.debug ('Cookie: %s' % cookie)
|
||||
|
||||
try:
|
||||
page = self.opener.open(url)
|
||||
torrent = page.read()
|
||||
if torrent:
|
||||
decoded = bencode.bdecode(torrent)
|
||||
metainfo = decoded['info']
|
||||
page.close ()
|
||||
except Exception, e:
|
||||
logger.error('Error getting torrent: %s' % e)
|
||||
return False
|
||||
|
||||
# get torrent track count and check for cue
|
||||
|
||||
trackcount = 0
|
||||
cuecount = 0
|
||||
|
||||
if 'files' in metainfo: # multi
|
||||
for pathfile in metainfo['files']:
|
||||
path = pathfile['path']
|
||||
for file in path:
|
||||
if any(file.lower().endswith('.' + x.lower()) for x in formatlist):
|
||||
trackcount += 1
|
||||
if '.cue' in file:
|
||||
cuecount += 1
|
||||
|
||||
#Torrent topic page
|
||||
|
||||
topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id
|
||||
logger.debug ('torrent title: %s' % title)
|
||||
logger.debug ('headphones trackcount: %s' % hptrackcount)
|
||||
logger.debug ('rutracker trackcount: %s' % trackcount)
|
||||
|
||||
# If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s)
|
||||
# This is for the case where we have a single .flac/.wav which can be split by cue
|
||||
# Not great, but shouldn't be doing this too often
|
||||
|
||||
totallogcount = 0
|
||||
if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount:
|
||||
page = self.opener.open(topicurl, timeout=60)
|
||||
soup = BeautifulSoup(page.read())
|
||||
findtoc = soup.find_all(text='TOC of the extracted CD')
|
||||
if not findtoc:
|
||||
findtoc = soup.find_all(text='TOC извлечённого CD')
|
||||
for toc in findtoc:
|
||||
logcount = 0
|
||||
for toccontent in toc.find_all_next(text=True):
|
||||
cut_string = toccontent.split('|')
|
||||
new_string = cut_string[0].lstrip().rstrip()
|
||||
if new_string == '1' or new_string == '01':
|
||||
logcount = 1
|
||||
elif logcount > 0:
|
||||
if new_string.isdigit():
|
||||
logcount += 1
|
||||
else:
|
||||
break
|
||||
totallogcount = totallogcount + logcount
|
||||
|
||||
if totallogcount > 0:
|
||||
trackcount = totallogcount
|
||||
logger.debug ('rutracker logtrackcount: %s' % totallogcount)
|
||||
|
||||
# If torrent track count = hp track count then return torrent,
|
||||
# if greater, check for deluxe/special/foreign editions
|
||||
# if less, then allow if it's a single track with a cue
|
||||
|
||||
valid = False
|
||||
|
||||
if trackcount == hptrackcount:
|
||||
if int(size) <= maxsize and int(seeders) >= minseeders:
|
||||
|
||||
#Torrent topic page
|
||||
torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t']
|
||||
topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id
|
||||
|
||||
# add to list
|
||||
if not check_track_count:
|
||||
valid = True
|
||||
elif trackcount > hptrackcount:
|
||||
if any(deluxe in title for deluxe in deluxelist):
|
||||
else:
|
||||
|
||||
# Check torrent info
|
||||
self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False))
|
||||
|
||||
# Debug
|
||||
#for cookie in self.cookiejar:
|
||||
# logger.debug ('Cookie: %s' % cookie)
|
||||
|
||||
try:
|
||||
page = self.opener.open(url)
|
||||
torrent = page.read()
|
||||
if torrent:
|
||||
decoded = bencode.bdecode(torrent)
|
||||
metainfo = decoded['info']
|
||||
page.close ()
|
||||
except Exception, e:
|
||||
logger.error('Error getting torrent: %s' % e)
|
||||
return False
|
||||
|
||||
# get torrent track count and check for cue
|
||||
trackcount = 0
|
||||
cuecount = 0
|
||||
|
||||
if 'files' in metainfo: # multi
|
||||
for pathfile in metainfo['files']:
|
||||
path = pathfile['path']
|
||||
for file in path:
|
||||
if any(file.lower().endswith('.' + x.lower()) for x in formatlist):
|
||||
trackcount += 1
|
||||
if '.cue' in file:
|
||||
cuecount += 1
|
||||
|
||||
title = returntitle.lower()
|
||||
logger.debug ('torrent title: %s' % title)
|
||||
logger.debug ('headphones trackcount: %s' % hptrackcount)
|
||||
logger.debug ('rutracker trackcount: %s' % trackcount)
|
||||
|
||||
# If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s)
|
||||
# This is for the case where we have a single .flac/.wav which can be split by cue
|
||||
# Not great, but shouldn't be doing this too often
|
||||
totallogcount = 0
|
||||
if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount:
|
||||
page = self.opener.open(topicurl, timeout=60)
|
||||
soup = BeautifulSoup(page.read())
|
||||
findtoc = soup.find_all(text='TOC of the extracted CD')
|
||||
if not findtoc:
|
||||
findtoc = soup.find_all(text='TOC извлечённого CD')
|
||||
for toc in findtoc:
|
||||
logcount = 0
|
||||
for toccontent in toc.find_all_next(text=True):
|
||||
cut_string = toccontent.split('|')
|
||||
new_string = cut_string[0].lstrip().rstrip()
|
||||
if new_string == '1' or new_string == '01':
|
||||
logcount = 1
|
||||
elif logcount > 0:
|
||||
if new_string.isdigit():
|
||||
logcount += 1
|
||||
else:
|
||||
break
|
||||
totallogcount = totallogcount + logcount
|
||||
|
||||
if totallogcount > 0:
|
||||
trackcount = totallogcount
|
||||
logger.debug ('rutracker logtrackcount: %s' % totallogcount)
|
||||
|
||||
# If torrent track count = hp track count then return torrent,
|
||||
# if greater, check for deluxe/special/foreign editions
|
||||
# if less, then allow if it's a single track with a cue
|
||||
valid = False
|
||||
|
||||
if trackcount == hptrackcount:
|
||||
valid = True
|
||||
elif trackcount > hptrackcount:
|
||||
if any(deluxe in title for deluxe in deluxelist):
|
||||
valid = True
|
||||
|
||||
# Add to list
|
||||
|
||||
if valid:
|
||||
rulist.append((returntitle, size, topicurl))
|
||||
else:
|
||||
if topicurl:
|
||||
logger.info(u'<a href="%s">Torrent</a> found with %s tracks but the selected headphones release has %s tracks, skipping for rutracker.org' % (topicurl, trackcount, hptrackcount))
|
||||
|
||||
else:
|
||||
logger.info('%s is larger than the maxsize or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i)' % (returntitle, int(size), int(seeders)))
|
||||
|
||||
|
||||
return rulist
|
||||
|
||||
def get_torrent(self, url, savelocation=None):
|
||||
|
||||
@@ -30,7 +30,14 @@ from headphones import logger, notifiers, request
|
||||
|
||||
def addTorrent(link):
|
||||
method = 'torrent-add'
|
||||
arguments = {'filename': link, 'download-dir': headphones.DOWNLOAD_TORRENT_DIR}
|
||||
|
||||
if link.endswith('.torrent'):
|
||||
f = open(link,'rb')
|
||||
metainfo = str(base64.b64encode(f.read()))
|
||||
f.close()
|
||||
arguments = {'metainfo': metainfo, 'download-dir':headphones.DOWNLOAD_TORRENT_DIR}
|
||||
else:
|
||||
arguments = {'filename': link, 'download-dir': headphones.DOWNLOAD_TORRENT_DIR}
|
||||
|
||||
response = torrentAction(method,arguments)
|
||||
|
||||
|
||||
@@ -12,3 +12,155 @@
|
||||
#
|
||||
# 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, cookielib
|
||||
import json, re, os, time
|
||||
|
||||
import headphones
|
||||
|
||||
from headphones import logger
|
||||
|
||||
class utorrentclient(object):
|
||||
TOKEN_REGEX = "<div id='token' style='display:none;'>([^<>]+)</div>"
|
||||
|
||||
def __init__(self, base_url = None, username = None, password = None,):
|
||||
|
||||
host = headphones.UTORRENT_HOST
|
||||
if not host.startswith('http'):
|
||||
host = 'http://' + host
|
||||
|
||||
if host.endswith('/'):
|
||||
host = host[:-1]
|
||||
|
||||
if host.endswith('/gui'):
|
||||
host = host[:-4]
|
||||
|
||||
self.base_url = host
|
||||
self.username = headphones.UTORRENT_USERNAME
|
||||
self.password = headphones.UTORRENT_PASSWORD
|
||||
self.opener = self._make_opener('uTorrent', self.base_url, self.username, self.password)
|
||||
self.token = self._get_token()
|
||||
#TODO refresh token, when necessary
|
||||
|
||||
def _make_opener(self, realm, base_url, username, password):
|
||||
"""uTorrent API need HTTP Basic Auth and cookie support for token verify."""
|
||||
auth = urllib2.HTTPBasicAuthHandler()
|
||||
auth.add_password(realm=realm,uri=base_url,user=username,passwd=password)
|
||||
opener = urllib2.build_opener(auth)
|
||||
urllib2.install_opener(opener)
|
||||
|
||||
cookie_jar = cookielib.CookieJar()
|
||||
cookie_handler = urllib2.HTTPCookieProcessor(cookie_jar)
|
||||
|
||||
handlers = [auth, cookie_handler]
|
||||
opener = urllib2.build_opener(*handlers)
|
||||
return opener
|
||||
|
||||
def _get_token(self):
|
||||
url = urlparse.urljoin(self.base_url, 'gui/token.html')
|
||||
try:
|
||||
response = self.opener.open(url)
|
||||
except urllib2.HTTPError as err:
|
||||
logger.debug('URL: ' + str(url))
|
||||
logger.debug('Error getting Token. uTorrent responded with error: ' + str(err))
|
||||
match = re.search(utorrentclient.TOKEN_REGEX, response.read())
|
||||
return match.group(1)
|
||||
|
||||
def list(self, **kwargs):
|
||||
params = [('list', '1')]
|
||||
params += kwargs.items()
|
||||
return self._action(params)
|
||||
|
||||
def add_url(self, url):
|
||||
#can receive magnet or normal .torrent link
|
||||
params = [('action', 'add-url'), ('s', url)]
|
||||
return self._action(params)
|
||||
|
||||
def start(self, *hashes):
|
||||
params = [('action', 'start'), ]
|
||||
for hash in hashes:
|
||||
params.append(('hash', hash))
|
||||
return self._action(params)
|
||||
|
||||
def stop(self, *hashes):
|
||||
params = [('action', 'stop'), ]
|
||||
for hash in hashes:
|
||||
params.append(('hash', hash))
|
||||
return self._action(params)
|
||||
|
||||
def pause(self, *hashes):
|
||||
params = [('action', 'pause'), ]
|
||||
for hash in hashes:
|
||||
params.append(('hash', hash))
|
||||
return self._action(params)
|
||||
|
||||
def forcestart(self, *hashes):
|
||||
params = [('action', 'forcestart'), ]
|
||||
for hash in hashes:
|
||||
params.append(('hash', hash))
|
||||
return self._action(params)
|
||||
|
||||
def getfiles(self, hash):
|
||||
params = [('action', 'getfiles'), ('hash', hash)]
|
||||
return self._action(params)
|
||||
|
||||
def getprops(self, hash):
|
||||
params = [('action', 'getprops'), ('hash', hash)]
|
||||
return self._action(params)
|
||||
|
||||
def setprops(self, hash, s, v):
|
||||
params = [('action', 'setprops'), ('hash', hash), ("s", s), ("v", v)]
|
||||
return self._action(params)
|
||||
|
||||
def setprio(self, hash, priority, *files):
|
||||
params = [('action', 'setprio'), ('hash', hash), ('p', str(priority))]
|
||||
for file_index in files:
|
||||
params.append(('f', str(file_index)))
|
||||
|
||||
return self._action(params)
|
||||
|
||||
def _action(self, params, body=None, content_type=None):
|
||||
url = self.base_url + '/gui/' + '?token=' + self.token + '&' + urllib.urlencode(params)
|
||||
request = urllib2.Request(url)
|
||||
|
||||
if body:
|
||||
request.add_data(body)
|
||||
request.add_header('Content-length', len(body))
|
||||
if content_type:
|
||||
request.add_header('Content-type', content_type)
|
||||
|
||||
try:
|
||||
response = self.opener.open(request)
|
||||
return response.code, json.loads(response.read())
|
||||
except urllib2.HTTPError as err:
|
||||
logger.debug('URL: ' + str(url))
|
||||
logger.debug('uTorrent webUI raised the following error: ' + str(err))
|
||||
|
||||
|
||||
def labelTorrent(hash):
|
||||
label = headphones.UTORRENT_LABEL
|
||||
uTorrentClient = utorrentclient()
|
||||
settinglabel = True
|
||||
while settinglabel:
|
||||
torrentList = uTorrentClient.list()
|
||||
for torrent in torrentList[1].get('torrents'):
|
||||
if (torrent[0].lower() == hash):
|
||||
uTorrentClient.setprops(hash,'label',label)
|
||||
settinglabel = False
|
||||
return True
|
||||
|
||||
|
||||
def dirTorrent(hash):
|
||||
uTorrentClient = utorrentclient()
|
||||
torrentList = uTorrentClient.list()
|
||||
for torrent in torrentList[1].get('torrents'):
|
||||
if (torrent[0].lower() == hash):
|
||||
return torrent[26]
|
||||
return False
|
||||
|
||||
|
||||
def addTorrent(link, hash):
|
||||
uTorrentClient = utorrentclient()
|
||||
uTorrentClient.add_url(link)
|
||||
labelTorrent(hash)
|
||||
return dirTorrent(hash)
|
||||
|
||||
@@ -76,7 +76,7 @@ def getVersion():
|
||||
logger.error('Couldn\'t find latest installed version.')
|
||||
cur_commit_hash = None
|
||||
|
||||
cur_commit_hash = output
|
||||
cur_commit_hash = str(output)
|
||||
|
||||
if not re.match('^[a-z0-9]+$', cur_commit_hash):
|
||||
logger.error('Output doesn\'t look like a hash, not using it')
|
||||
@@ -136,6 +136,10 @@ def checkGithub():
|
||||
logger.info('You are running an unknown version of Headphones. Run the updater to identify your version')
|
||||
return headphones.LATEST_VERSION
|
||||
|
||||
if headphones.LATEST_VERSION == headphones.CURRENT_VERSION:
|
||||
logger.info('Headphones is up to date')
|
||||
return headphones.LATEST_VERSION
|
||||
|
||||
logger.info('Comparing currently installed version with latest GitHub version')
|
||||
url = 'https://api.github.com/repos/%s/headphones/compare/%s...%s' % (headphones.GIT_USER, headphones.LATEST_VERSION, headphones.CURRENT_VERSION)
|
||||
commits = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict)
|
||||
|
||||
+37
-6
@@ -269,7 +269,11 @@ class WebInterface(object):
|
||||
searcher.searchforalbum(mbid, new=True)
|
||||
if action == 'WantedLossless':
|
||||
searcher.searchforalbum(mbid, lossless=True)
|
||||
myDB.action('UPDATE artists SET TotalTracks=(SELECT COUNT(*) FROM tracks, artists WHERE tracks.ArtistName = artists.ArtistName AND AlbumTitle IN (SELECT AlbumTitle FROM albums WHERE Status != "Ignored")) WHERE ArtistID=(SELECT ArtistID FROM albums WHERE AlbumID=?)', [mbid])
|
||||
if ArtistID:
|
||||
ArtistIDT = ArtistID
|
||||
else:
|
||||
ArtistIDT = myDB.action('SELECT ArtistID FROM albums WHERE AlbumID=?', [mbid]).fetchone()[0]
|
||||
myDB.action('UPDATE artists SET TotalTracks=(SELECT COUNT(*) FROM tracks WHERE ArtistID = ? AND AlbumTitle IN (SELECT AlbumTitle FROM albums WHERE Status != "Ignored")) WHERE ArtistID = ?', [ArtistIDT, ArtistIDT])
|
||||
if ArtistID:
|
||||
raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID)
|
||||
else:
|
||||
@@ -732,6 +736,14 @@ class WebInterface(object):
|
||||
raise cherrypy.HTTPRedirect("logs")
|
||||
clearLogs.exposed = True
|
||||
|
||||
def toggleVerbose(self):
|
||||
headphones.VERBOSE = not headphones.VERBOSE
|
||||
logger.initLogger(not headphones.QUIET, headphones.VERBOSE)
|
||||
logger.info("Verbose toggled, set to %s", headphones.VERBOSE)
|
||||
logger.debug("If you read this message, debug logging is available")
|
||||
raise cherrypy.HTTPRedirect("logs")
|
||||
toggleVerbose.exposed = True
|
||||
|
||||
def getLog(self,iDisplayStart=0,iDisplayLength=100,iSortCol_0=0,sSortDir_0="desc",sSearch="",**kwargs):
|
||||
|
||||
iDisplayStart = int(iDisplayStart)
|
||||
@@ -850,6 +862,16 @@ class WebInterface(object):
|
||||
return json_albums
|
||||
getAlbumsByArtist_json.exposed=True
|
||||
|
||||
def getArtistjson(self, ArtistID, **kwargs):
|
||||
myDB = db.DBConnection()
|
||||
artist = myDB.action('SELECT * FROM artists WHERE ArtistID=?', [ArtistID]).fetchone()
|
||||
artist_json = json.dumps({
|
||||
'ArtistName': artist['ArtistName'],
|
||||
'Status': artist['Status']
|
||||
})
|
||||
return artist_json
|
||||
getArtistjson.exposed=True
|
||||
|
||||
def clearhistory(self, type=None, date_added=None, title=None):
|
||||
myDB = db.DBConnection()
|
||||
if type:
|
||||
@@ -931,6 +953,7 @@ class WebInterface(object):
|
||||
"utorrent_host" : headphones.UTORRENT_HOST,
|
||||
"utorrent_user" : headphones.UTORRENT_USERNAME,
|
||||
"utorrent_pass" : headphones.UTORRENT_PASSWORD,
|
||||
"utorrent_label" : headphones.UTORRENT_LABEL,
|
||||
"nzb_downloader_sabnzbd" : radio(headphones.NZB_DOWNLOADER, 0),
|
||||
"nzb_downloader_nzbget" : radio(headphones.NZB_DOWNLOADER, 1),
|
||||
"nzb_downloader_blackhole" : radio(headphones.NZB_DOWNLOADER, 2),
|
||||
@@ -985,6 +1008,8 @@ class WebInterface(object):
|
||||
"pref_bitrate_low" : headphones.PREFERRED_BITRATE_LOW_BUFFER,
|
||||
"pref_bitrate_allow_lossless" : checked(headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS),
|
||||
"detect_bitrate" : checked(headphones.DETECT_BITRATE),
|
||||
"lossless_bitrate_from" : headphones.LOSSLESS_BITRATE_FROM,
|
||||
"lossless_bitrate_to" : headphones.LOSSLESS_BITRATE_TO,
|
||||
"move_files" : checked(headphones.MOVE_FILES),
|
||||
"rename_files" : checked(headphones.RENAME_FILES),
|
||||
"correct_metadata" : checked(headphones.CORRECT_METADATA),
|
||||
@@ -1085,7 +1110,8 @@ class WebInterface(object):
|
||||
"songkick_filter_enabled": checked(headphones.SONGKICK_FILTER_ENABLED),
|
||||
"cache_sizemb": headphones.CACHE_SIZEMB,
|
||||
"file_permissions": headphones.FILE_PERMISSIONS,
|
||||
"folder_permissions": headphones.FOLDER_PERMISSIONS
|
||||
"folder_permissions": headphones.FOLDER_PERMISSIONS,
|
||||
"mpc_enabled": checked(headphones.MPC_ENABLED)
|
||||
}
|
||||
|
||||
# Need to convert EXTRAS to a dictionary we can pass to the config: it'll come in as a string like 2,5,6,8
|
||||
@@ -1108,7 +1134,7 @@ class WebInterface(object):
|
||||
def configUpdate(self, http_host='0.0.0.0', http_username=None, http_port=8181, http_password=None, launch_browser=0, api_enabled=0, api_key=None,
|
||||
download_scan_interval=None, update_db_interval=None, mb_ignore_age=None, nzb_search_interval=None, libraryscan_interval=None, sab_host=None, sab_username=None, sab_apikey=None, sab_password=None,
|
||||
sab_category=None, nzbget_host=None, nzbget_username=None, nzbget_password=None, nzbget_category=None, transmission_host=None, transmission_username=None, transmission_password=None,
|
||||
utorrent_host=None, utorrent_username=None, utorrent_password=None, nzb_downloader=0, torrent_downloader=0, download_dir=None, blackhole_dir=None, usenet_retention=None,
|
||||
utorrent_host=None, utorrent_username=None, utorrent_password=None, utorrent_label=None,nzb_downloader=0, torrent_downloader=0, download_dir=None, blackhole_dir=None, usenet_retention=None,
|
||||
use_headphones_indexer=0, newznab=0, newznab_host=None, newznab_apikey=None, newznab_enabled=0, nzbsorg=0, nzbsorg_uid=None, nzbsorg_hash=None, nzbsrus=0, nzbsrus_uid=None, nzbsrus_apikey=None, omgwtfnzbs=0, omgwtfnzbs_uid=None, omgwtfnzbs_apikey=None,
|
||||
preferred_words=None, required_words=None, ignored_words=None, preferred_quality=0, preferred_bitrate=None, detect_bitrate=0, move_files=0, torrentblackhole_dir=None, download_torrent_dir=None,
|
||||
numberofseeders=None, use_piratebay=0, piratebay_proxy_url=None, 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,
|
||||
@@ -1120,9 +1146,9 @@ class WebInterface(object):
|
||||
xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, pushalot_enabled=False, pushalot_apikey=None, pushalot_onsnatch=0, synoindex_enabled=False, lms_enabled=0, lms_host=None,
|
||||
pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, pushover_apitoken=None, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0,
|
||||
osx_notify_enabled=0, osx_notify_onsnatch=0, osx_notify_app=None, boxcar_enabled=0, boxcar_onsnatch=0, boxcar_token=None, mirror=None, customhost=None, customport=None, customsleep=None, hpuser=None, hppass=None,
|
||||
preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None, file_permissions=None, folder_permissions=None,
|
||||
plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, plex_password=None, plex_update=0, plex_notify=0,
|
||||
songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, encoder_multicore=False, encoder_multicore_count=0, **kwargs):
|
||||
preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, lossless_bitrate_from=None, lossless_bitrate_to=None, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None,
|
||||
file_permissions=None, folder_permissions=None, plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, plex_password=None, plex_update=0, plex_notify=0,
|
||||
songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, encoder_multicore=False, encoder_multicore_count=0, mpc_enabled=False, **kwargs ):
|
||||
|
||||
headphones.HTTP_HOST = http_host
|
||||
headphones.HTTP_PORT = http_port
|
||||
@@ -1154,6 +1180,7 @@ class WebInterface(object):
|
||||
headphones.UTORRENT_HOST = utorrent_host
|
||||
headphones.UTORRENT_USERNAME = utorrent_username
|
||||
headphones.UTORRENT_PASSWORD = utorrent_password
|
||||
headphones.UTORRENT_LABEL = utorrent_label
|
||||
headphones.NZB_DOWNLOADER = int(nzb_downloader)
|
||||
headphones.TORRENT_DOWNLOADER = int(torrent_downloader)
|
||||
headphones.DOWNLOAD_DIR = download_dir
|
||||
@@ -1199,6 +1226,8 @@ class WebInterface(object):
|
||||
headphones.PREFERRED_BITRATE_LOW_BUFFER = preferred_bitrate_low_buffer
|
||||
headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS = preferred_bitrate_allow_lossless
|
||||
headphones.DETECT_BITRATE = detect_bitrate
|
||||
headphones.LOSSLESS_BITRATE_FROM = lossless_bitrate_from
|
||||
headphones.LOSSLESS_BITRATE_TO = lossless_bitrate_to
|
||||
headphones.MOVE_FILES = move_files
|
||||
headphones.CORRECT_METADATA = correct_metadata
|
||||
headphones.RENAME_FILES = rename_files
|
||||
@@ -1291,6 +1320,8 @@ class WebInterface(object):
|
||||
headphones.BOXCAR_ONSNATCH = boxcar_onsnatch
|
||||
headphones.BOXCAR_TOKEN = boxcar_token
|
||||
|
||||
headphones.MPC_ENABLED = mpc_enabled
|
||||
|
||||
headphones.MIRROR = mirror
|
||||
headphones.CUSTOMHOST = customhost
|
||||
headphones.CUSTOMPORT = customport
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
from pynma import PyNMA
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
from xml.dom.minidom import parseString
|
||||
from httplib import HTTPSConnection
|
||||
from urllib import urlencode
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
API_SERVER = 'nma.usk.bz'
|
||||
ADD_PATH = '/publicapi/notify'
|
||||
|
||||
USER_AGENT="PyNMA/v%s"%__version__
|
||||
|
||||
def uniq_preserve(seq): # Dave Kirby
|
||||
# Order preserving
|
||||
seen = set()
|
||||
return [x for x in seq if x not in seen and not seen.add(x)]
|
||||
|
||||
def uniq(seq):
|
||||
# Not order preserving
|
||||
return {}.fromkeys(seq).keys()
|
||||
|
||||
class PyNMA(object):
|
||||
"""PyNMA(apikey=[], developerkey=None)
|
||||
takes 2 optional arguments:
|
||||
- (opt) apykey: might me a string containing 1 key or an array of keys
|
||||
- (opt) developerkey: where you can store your developer key
|
||||
"""
|
||||
|
||||
def __init__(self, apikey=[], developerkey=None):
|
||||
self._developerkey = None
|
||||
self.developerkey(developerkey)
|
||||
if apikey:
|
||||
if type(apikey) == str:
|
||||
apikey = [apikey]
|
||||
self._apikey = uniq(apikey)
|
||||
|
||||
def addkey(self, key):
|
||||
"Add a key (register ?)"
|
||||
if type(key) == str:
|
||||
if not key in self._apikey:
|
||||
self._apikey.append(key)
|
||||
elif type(key) == list:
|
||||
for k in key:
|
||||
if not k in self._apikey:
|
||||
self._apikey.append(k)
|
||||
|
||||
def delkey(self, key):
|
||||
"Removes a key (unregister ?)"
|
||||
if type(key) == str:
|
||||
if key in self._apikey:
|
||||
self._apikey.remove(key)
|
||||
elif type(key) == list:
|
||||
for k in key:
|
||||
if key in self._apikey:
|
||||
self._apikey.remove(k)
|
||||
|
||||
def developerkey(self, developerkey):
|
||||
"Sets the developer key (and check it has the good length)"
|
||||
if type(developerkey) == str and len(developerkey) == 48:
|
||||
self._developerkey = developerkey
|
||||
|
||||
def push(self, application="", event="", description="", url="", priority=0, batch_mode=False):
|
||||
"""Pushes a message on the registered API keys.
|
||||
takes 5 arguments:
|
||||
- (req) application: application name [256]
|
||||
- (req) event: event name [1000]
|
||||
- (req) description: description [10000]
|
||||
- (opt) url: url [512]
|
||||
- (opt) priority: from -2 (lowest) to 2 (highest) (def:0)
|
||||
- (opt) batch_mode: call API 5 by 5 (def:False)
|
||||
|
||||
Warning: using batch_mode will return error only if all API keys are bad
|
||||
cf: http://nma.usk.bz/api.php
|
||||
"""
|
||||
datas = {
|
||||
'application': application[:256].encode('utf8'),
|
||||
'event': event[:1024].encode('utf8'),
|
||||
'description': description[:10000].encode('utf8'),
|
||||
'priority': priority
|
||||
}
|
||||
|
||||
if url:
|
||||
datas['url'] = url[:512]
|
||||
|
||||
if self._developerkey:
|
||||
datas['developerkey'] = self._developerkey
|
||||
|
||||
results = {}
|
||||
|
||||
if not batch_mode:
|
||||
for key in self._apikey:
|
||||
datas['apikey'] = key
|
||||
res = self.callapi('POST', ADD_PATH, datas)
|
||||
results[key] = res
|
||||
else:
|
||||
for i in range(0, len(self._apikey), 5):
|
||||
datas['apikey'] = ",".join(self._apikey[i:i+5])
|
||||
res = self.callapi('POST', ADD_PATH, datas)
|
||||
results[datas['apikey']] = res
|
||||
return results
|
||||
|
||||
def callapi(self, method, path, args):
|
||||
headers = { 'User-Agent': USER_AGENT }
|
||||
if method == "POST":
|
||||
headers['Content-type'] = "application/x-www-form-urlencoded"
|
||||
http_handler = HTTPSConnection(API_SERVER)
|
||||
http_handler.request(method, path, urlencode(args), headers)
|
||||
resp = http_handler.getresponse()
|
||||
|
||||
try:
|
||||
res = self._parse_reponse(resp.read())
|
||||
except Exception, e:
|
||||
res = {'type': "pynmaerror",
|
||||
'code': 600,
|
||||
'message': str(e)
|
||||
}
|
||||
pass
|
||||
|
||||
return res
|
||||
|
||||
def _parse_reponse(self, response):
|
||||
root = parseString(response).firstChild
|
||||
for elem in root.childNodes:
|
||||
if elem.nodeType == elem.TEXT_NODE: continue
|
||||
if elem.tagName == 'success':
|
||||
res = dict(elem.attributes.items())
|
||||
res['message'] = ""
|
||||
res['type'] = elem.tagName
|
||||
return res
|
||||
if elem.tagName == 'error':
|
||||
res = dict(elem.attributes.items())
|
||||
res['message'] = elem.firstChild.nodeValue
|
||||
res['type'] = elem.tagName
|
||||
return res
|
||||
|
||||
|
||||
Reference in New Issue
Block a user