mirror of
https://github.com/rembo10/headphones.git
synced 2026-09-10 00:32:52 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e67d32445f | ||
|
|
83280f3f99 | ||
|
|
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 | ||
|
|
0457c3c30e | ||
|
|
945cfbf21b | ||
|
|
5769cb0dee | ||
|
|
efd043e309 | ||
|
|
fd19563160 | ||
|
|
1a2ed24566 | ||
|
|
d2fecc47ec | ||
|
|
f9b0a5efb9 | ||
|
|
e304e34ac7 | ||
|
|
ff3826c0c8 | ||
|
|
c96b043a19 | ||
|
|
a1d0f14eb8 | ||
|
|
0234f4dab9 | ||
|
|
e00411f7ad | ||
|
|
3b2350cd8f | ||
|
|
d490091a9a | ||
|
|
5be36b517f | ||
|
|
dab323e8ff | ||
|
|
7e02f86af8 | ||
|
|
26ed43fca8 | ||
|
|
dab4e3d5f8 | ||
|
|
07e8b6b095 | ||
|
|
52523a5d3c | ||
|
|
ded4b7ef6f | ||
|
|
aa9d669a2b | ||
|
|
5378884bf8 | ||
|
|
631d947a9a | ||
|
|
7280807588 | ||
|
|
ca010ac522 | ||
|
|
e2e1ef9bc6 | ||
|
|
a96b793c98 | ||
|
|
f7ba0ff634 | ||
|
|
a0339d9814 | ||
|
|
eb7a1d7117 | ||
|
|
65fa0e21fa | ||
|
|
60ea86d499 | ||
|
|
0411bb6b44 | ||
|
|
d0198b15ee | ||
|
|
c4e8678d8f | ||
|
|
d6cd9cd7e4 |
@@ -172,6 +172,14 @@ table.display tr.even.gradeA {
|
||||
background-color: #ddffdd;
|
||||
}
|
||||
|
||||
table.display tr.odd.gradeI {
|
||||
background-color: #bebebe;
|
||||
}
|
||||
|
||||
table.display tr.even.gradeI {
|
||||
background-color: #bebebe;
|
||||
}
|
||||
|
||||
table.display tr.odd.gradeC {
|
||||
background-color: #ddddff;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div id="subhead_menu">
|
||||
|
||||
<a id="menu_link_delete" href="deleteAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}"><i class="fa fa-trash-o"></i> Delete Album</a>
|
||||
%if album['Status'] == 'Skipped':
|
||||
%if album['Status'] == 'Skipped' or album['Status'] == 'Ignored':
|
||||
<a id="menu_link_wanted" href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}&new=False', $(this),true)" data-success="'${album['AlbumTitle']}' added to queue"><i class="fa fa-heart"></i> Mark Album as Wanted</a>
|
||||
%elif album['Status'] == 'Wanted':
|
||||
<a id="menu_link_check" href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}&new=True', $(this));" data-success="Forced checking successful"><i class="fa fa-search"></i> Force Check</a>
|
||||
|
||||
@@ -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>
|
||||
@@ -16,7 +16,7 @@
|
||||
<a id="menu_link_pauze" href="#" onclick="doAjaxCall('pauseArtist?ArtistID=${artist['ArtistID']}',$(this),true)" data-success="${artist['ArtistName']} paused"><i class="fa fa-pause"></i> Pause Artist</a>
|
||||
%endif
|
||||
%if artist['IncludeExtras']:
|
||||
<a id="menu_link_removeextra" href="#" onclick="doAjaxCall('removeExtras?ArtistID=${artist['ArtistID']}',$(this),true)" data-success="Extras removed for ${artist['ArtistName']}"><i class="fa fa-minus"></i> Remove Extras</a>
|
||||
<a id="menu_link_removeextra" href="#" onclick="doAjaxCall('removeExtras?ArtistID=${artist['ArtistID']}&ArtistName=${artist['ArtistName']}',$(this),'submenu&table')" data-success="Extras removed for ${artist['ArtistName']}"><i class="fa fa-minus"></i> Remove Extras</a>
|
||||
<a class="menu_link_edit" id="menu_link_modifyextra" href="#"><i class="fa fa-pencil"></i> Modify Extras</a>
|
||||
%else:
|
||||
<a id="menu_link_getextra" href="#"><i class="fa fa-plus"></i> Get Extras</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>
|
||||
@@ -62,6 +56,7 @@
|
||||
<option value="Wanted">Wanted</option>
|
||||
<option value="WantedNew">Wanted (new only)</option>
|
||||
<option value="Skipped">Skipped</option>
|
||||
<option value="Ignored">Ignored</option>
|
||||
<option value="Downloaded">Downloaded</option>
|
||||
</select>
|
||||
<input type="hidden" value="Go">
|
||||
@@ -89,6 +84,8 @@
|
||||
grade = 'X'
|
||||
elif album['Status'] == 'Snatched':
|
||||
grade = 'C'
|
||||
elif album['Status'] == 'Ignored':
|
||||
grade = 'I'
|
||||
else:
|
||||
grade = 'A'
|
||||
|
||||
@@ -123,12 +120,12 @@
|
||||
%>
|
||||
<tr class="grade${grade}">
|
||||
<td id="select"><input type="checkbox" name="${album['AlbumID']}" class="checkbox" /></td>
|
||||
<td id="albumart"><img class="albumArt" id="${album['AlbumID']}" src="artwork/thumbs/album/${album['AlbumID']}" height="64" width="64"></td>
|
||||
<td id="albumart"><img class="albumArtnostretch" id="${album['AlbumID']}" src="artwork/thumbs/album/${album['AlbumID']}" height="64" width="64"></td>
|
||||
<td id="albumname"><a href="albumPage?AlbumID=${album['AlbumID']}">${album['AlbumTitle']}</a></td>
|
||||
<td id="reldate">${album['ReleaseDate']}</td>
|
||||
<td id="type">${album['Type']}</td>
|
||||
<td id="status">${album['Status']}
|
||||
%if album['Status'] == 'Skipped':
|
||||
%if album['Status'] == 'Skipped' or album['Status'] == 'Ignored':
|
||||
[<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}',$(this),'table')" data-success="'${album['AlbumTitle']}' added to Wanted list">want</a>]
|
||||
%elif (album['Status'] == 'Wanted' or album['Status'] == 'Wanted Lossless'):
|
||||
[<a href="#" onclick="doAjaxCall('unqueueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}',$(this),'table')" data-success="'${album['AlbumTitle']}' skipped">skip</a>]
|
||||
@@ -153,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()">
|
||||
@@ -168,7 +162,6 @@
|
||||
getInfo(elem,id,'artist');
|
||||
}
|
||||
|
||||
|
||||
<%
|
||||
if headphones.SONGKICK_FILTER_ENABLED:
|
||||
songkick_filter_enabled = "true"
|
||||
@@ -237,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){
|
||||
showMsg("Getting artist information",true);
|
||||
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()
|
||||
$('#ajaxMsg').empty()
|
||||
$('#ajaxMsg').removeAttr('style')
|
||||
spinner_active = false
|
||||
loadingtext_active = false
|
||||
loadingMessage = false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initThisPage() {
|
||||
$('#menu_link_getextra').click(function() {
|
||||
$('#dialog').dialog();
|
||||
@@ -245,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": [
|
||||
@@ -290,6 +313,10 @@
|
||||
if( ${songkick_enabled} ){
|
||||
getArtistsCalendar();
|
||||
}
|
||||
checkArtistStatus();
|
||||
setInterval(function(){
|
||||
checkArtistStatus();
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -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>
|
||||
@@ -678,9 +682,9 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<h3>LMS</h3>
|
||||
<h3>Logitech Media Server</h3>
|
||||
<div class="checkbox row">
|
||||
<input type="checkbox" name="lms_enabled" id="lms" value="1" ${config['lms_enabled']} /><label>Enable Squeezebox Updates</label>
|
||||
<input type="checkbox" name="lms_enabled" id="lms" value="1" ${config['lms_enabled']} /><label>Enable LMS Updates</label>
|
||||
</div>
|
||||
<div id="lmsoptions">
|
||||
<div class="row">
|
||||
@@ -690,8 +694,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
<fieldset>
|
||||
<h3>Pushalot</h3>
|
||||
<div class="checkbox row">
|
||||
@@ -708,6 +711,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
<fieldset>
|
||||
<h3>Synology NAS</h3>
|
||||
<div class="checkbox row">
|
||||
@@ -776,6 +783,47 @@
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<h3>OS X</h3>
|
||||
<div class="row checkbox">
|
||||
<input type="checkbox" name="osx_notify_enabled" id="osx_notify" value="1" ${config['osx_notify_enabled']} /><label>Enable OS X Notifications</label>
|
||||
</div>
|
||||
<div id="osx_notify_options">
|
||||
<div class="row">
|
||||
<small>Enter the path/application name to be registered with the Notification Center, default is /Applications/Headphones</small>
|
||||
<input type="text" id="osx_notify_reg" name="osx_notify_app" value="${config['osx_notify_app']}" size="50"><label>Register Notify App</label>
|
||||
<input type="button" value="Register" id="osxnotifyregister">
|
||||
</div>
|
||||
<div class="row checkbox">
|
||||
<input type="checkbox" name="osx_notify_onsnatch" value="1" ${config['osx_notify_onsnatch']} /><label>Notify on snatch?</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<h3>Boxcar2</h3>
|
||||
<div class="row checkbox">
|
||||
<input type="checkbox" name="boxcar_enabled" id="boxcar" value="1" ${config['boxcar_enabled']} /><label>Enable Boxcar2 Notifications</label>
|
||||
</div>
|
||||
<div id="boxcar_options">
|
||||
<div class="row">
|
||||
<label>Access Token</label><input type="text" name="boxcar_token" value="${config['boxcar_token']}" size="35">
|
||||
</div>
|
||||
<div class="row checkbox">
|
||||
<input type="checkbox" name="boxcar_onsnatch" value="1" ${config['boxcar_onsnatch']} /><label>Notify on snatch?</label>
|
||||
</div>
|
||||
</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>
|
||||
@@ -1427,6 +1475,7 @@
|
||||
$("#pushbulletoptions").hide();
|
||||
}
|
||||
|
||||
|
||||
$("#pushbullet").click(function(){
|
||||
if ($("#pushbullet").is(":checked"))
|
||||
{
|
||||
@@ -1458,6 +1507,46 @@
|
||||
}
|
||||
});
|
||||
|
||||
if ($("#osx_notify").is(":checked"))
|
||||
{
|
||||
$("#osx_notify_options").show();
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#osx_notify_options").hide();
|
||||
}
|
||||
|
||||
$("#osx_notify").click(function(){
|
||||
if ($("#osx_notify").is(":checked"))
|
||||
{
|
||||
$("#osx_notify_options").slideDown();
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#osx_notify_options").slideUp();
|
||||
}
|
||||
});
|
||||
|
||||
if ($("#boxcar").is(":checked"))
|
||||
{
|
||||
$("#boxcar_options").show();
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#boxcar_options").hide();
|
||||
}
|
||||
|
||||
$("#boxcar").click(function(){
|
||||
if ($("#boxcar").is(":checked"))
|
||||
{
|
||||
$("#boxcar_options").slideDown();
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#boxcar_options").slideUp();
|
||||
}
|
||||
});
|
||||
|
||||
if ($("#songkick").is(":checked"))
|
||||
{
|
||||
$("#songkickoptions").show();
|
||||
@@ -1671,6 +1760,12 @@
|
||||
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
|
||||
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||
});
|
||||
|
||||
$('#osxnotifyregister').click(function () {
|
||||
var osx_notify_app = $("#osx_notify_reg").val();
|
||||
$.get("/osxnotifyregister", {'app': osx_notify_app}, function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
|
||||
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut()
|
||||
})
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
@@ -224,6 +224,14 @@ table.display tr.even.gradeA {
|
||||
background-color: #ddffdd;
|
||||
}
|
||||
|
||||
table.display tr.odd.gradeI {
|
||||
background-color: #bebebe;
|
||||
}
|
||||
|
||||
table.display tr.even.gradeI {
|
||||
background-color: #bebebe;
|
||||
}
|
||||
|
||||
table.display tr.odd.gradeC {
|
||||
background-color: #ebf5ff;
|
||||
}
|
||||
@@ -254,6 +262,13 @@ table.display tr.even.gradeU {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
table.display tr.odd.gradeW {
|
||||
background-color: #ffffaa;
|
||||
}
|
||||
|
||||
table.display tr.even.gradeW {
|
||||
background-color: #ffffaa;
|
||||
}
|
||||
|
||||
table.display tr.odd.gradeZ {
|
||||
background-color: #FAFAFA;
|
||||
@@ -269,6 +284,7 @@ table.display tr.gradeL #status {
|
||||
}
|
||||
table.display tr.gradeA td,
|
||||
table.display tr.gradeC td,
|
||||
table.display tr.gradeI td,
|
||||
table.display tr.gradeX td,
|
||||
table.display tr.gradeU td,
|
||||
table.display tr.gradeZ td {border-bottom: 1px solid #FFF;}
|
||||
|
||||
@@ -163,6 +163,14 @@ img.albumArt {
|
||||
max-height: 300px;
|
||||
position: relative;
|
||||
}
|
||||
img.albumArt-nostretch {
|
||||
float: left;
|
||||
min-height: 64px;
|
||||
min-width: 64px;
|
||||
max-width: 250px;
|
||||
max-height: 300px;
|
||||
position: relative;
|
||||
}
|
||||
.title {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
|
||||
@@ -316,6 +316,10 @@ function doAjaxCall(url,elem,reload,form) {
|
||||
}
|
||||
if ( reload == "tabs") refreshTab();
|
||||
if ( reload == "page") location.reload();
|
||||
if ( reload == "submenu&table") {
|
||||
refreshSubmenu();
|
||||
refreshTable();
|
||||
}
|
||||
if ( form ) {
|
||||
// Change the option to 'choose...'
|
||||
$(formID + " select").children('option[disabled=disabled]').attr('selected','selected');
|
||||
@@ -330,6 +334,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 + "");
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
lossless<%inherit file="base.html"/>
|
||||
<%inherit file="base.html"/>
|
||||
<%!
|
||||
from headphones import helpers
|
||||
%>
|
||||
|
||||
<%def name="headerIncludes()">
|
||||
<div id="subhead_container">
|
||||
<div id="subhead_menu">
|
||||
<a class="menu_link_edit" href="clearLogs"><i class="fa fa-trash-o"></i> Clear log</a>
|
||||
</div>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
<div class="title">
|
||||
<div id="paddingheader">
|
||||
<h1 class="clearfix"><i class="fa fa-flag"></i> Logs</h1>
|
||||
</div>
|
||||
<table class="display" id="log_table">
|
||||
@@ -40,6 +48,8 @@ lossless<%inherit file="base.html"/>
|
||||
<script src="js/libs/jquery.dataTables.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
initActions();
|
||||
|
||||
$('#log_table').dataTable( {
|
||||
"bProcessing": true,
|
||||
"bServerSide": true,
|
||||
@@ -49,23 +59,26 @@ $(document).ready(function() {
|
||||
"iDisplayLength": 25,
|
||||
"bStateSave": true,
|
||||
"oLanguage": {
|
||||
"sSearch":"",
|
||||
"sSearch":"Filter:",
|
||||
"sLengthMenu":"Show _MENU_ lines per page",
|
||||
"sEmptyTable": "No log information available",
|
||||
"sInfo":"Showing _START_ to _END_ of _TOTAL_ lines",
|
||||
"sInfoEmpty":"Showing 0 to 0 of 0 lines",
|
||||
"sInfoFiltered":"(filtered from _MAX_ total lines)"},
|
||||
"fnRowCallback": function (nRow, aData, iDisplayIndex, iDisplayIndexFull) {
|
||||
if (aData[1] === "WARNING" || aData[1] === "ERROR")
|
||||
if (aData[1] === "ERROR")
|
||||
{
|
||||
$('td', nRow).closest('tr').addClass("gradeX");
|
||||
}
|
||||
else if (aData[1] === "WARNING")
|
||||
{
|
||||
$('td', nRow).closest('tr').addClass("gradeW");
|
||||
}
|
||||
else
|
||||
{
|
||||
$('td', nRow).closest('tr').addClass("gradeZ");
|
||||
}
|
||||
|
||||
|
||||
return nRow;
|
||||
},
|
||||
"fnServerData": function ( sSource, aoData, fnCallback ) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<a href="manageAlbums?Status=Snatched"><i class="fa fa-cloud-download fa-fw"></i> </span>Manage Snatched Albums</a><br>
|
||||
<a href="manageAlbums?Status=Upcoming"><i class="fa fa-calendar fa-fw"></i> Manage Upcoming Albums</a><br>
|
||||
<a href="manageAlbums?Status=Wanted"><i class="fa fa-heart fa-fw"></i> </span>Manage Wanted Albums</a><br>
|
||||
<a href="manageAlbums?Status=Ignored"><i class="fa fa-meh-o fa-fw"></i> </span>Manage Ignored Albums</a><br>
|
||||
<br><br>
|
||||
<a href="manageAlbums">Manage All Albums</a>
|
||||
</div>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<option value="WantedNew">Wanted (new only)</option>
|
||||
<option value="WantedLossless">Wanted (lossless)</option>
|
||||
<option value="Skipped">Skipped</option>
|
||||
<option value="Ignored">Ignored</option>
|
||||
<option value="Downloaded">Downloaded</option>
|
||||
</select>
|
||||
<input type="hidden" value="Go">
|
||||
@@ -50,6 +51,8 @@
|
||||
grade = 'Z'
|
||||
elif album['Status'] == 'Wanted':
|
||||
grade = 'X'
|
||||
elif album['Status'] == 'Ignored':
|
||||
grade = 'I'
|
||||
elif album['Status'] == 'Snatched':
|
||||
grade = 'C'
|
||||
else:
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<select name="action" onChange="doAjaxCall('markAlbums',$(this),'table',true);" data-error="You didn't select any albums">
|
||||
<option disabled="disabled" selected="selected">Choose...</option>
|
||||
<option value="Skipped">Skipped</option>
|
||||
<option value="Ignored">Ignored</option>
|
||||
<option value="Downloaded">Downloaded</option>
|
||||
</select>
|
||||
<input type="hidden" value="Go">
|
||||
|
||||
+34
-7
@@ -15,20 +15,17 @@
|
||||
|
||||
# NZBGet support added by CurlyMo <curlymoo1@gmail.com> as a part of XBian - XBMC on the Raspberry Pi
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import os, sys, subprocess
|
||||
|
||||
import threading
|
||||
import webbrowser
|
||||
import sqlite3
|
||||
import itertools
|
||||
import cherrypy
|
||||
|
||||
from lib.apscheduler.scheduler import Scheduler
|
||||
from lib.configobj import ConfigObj
|
||||
|
||||
import cherrypy
|
||||
|
||||
from headphones import versioncheck, logger, version
|
||||
from headphones.common import *
|
||||
|
||||
@@ -157,6 +154,7 @@ TRANSMISSION_PASSWORD = None
|
||||
UTORRENT_HOST = None
|
||||
UTORRENT_USERNAME = None
|
||||
UTORRENT_PASSWORD = None
|
||||
UTORRENT_LABEL = None
|
||||
|
||||
NEWZNAB = False
|
||||
NEWZNAB_HOST = None
|
||||
@@ -270,6 +268,12 @@ TWITTER_ONSNATCH = False
|
||||
TWITTER_USERNAME = None
|
||||
TWITTER_PASSWORD = None
|
||||
TWITTER_PREFIX = None
|
||||
OSX_NOTIFY_ENABLED = False
|
||||
OSX_NOTIFY_ONSNATCH = False
|
||||
OSX_NOTIFY_APP = None
|
||||
BOXCAR_ENABLED = False
|
||||
BOXCAR_ONSNATCH = False
|
||||
BOXCAR_TOKEN = None
|
||||
MIRRORLIST = ["musicbrainz.org","headphones","custom"]
|
||||
MIRROR = None
|
||||
CUSTOMHOST = None
|
||||
@@ -281,6 +285,7 @@ SONGKICK_ENABLED = False
|
||||
SONGKICK_APIKEY = None
|
||||
SONGKICK_LOCATION = None
|
||||
SONGKICK_FILTER_ENABLED = False
|
||||
MPC_ENABLED = False
|
||||
|
||||
CACHE_SIZEMB = 32
|
||||
JOURNAL_MODE = None
|
||||
@@ -343,13 +348,13 @@ 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, \
|
||||
MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, ENCODERVBRCBR, ENCODERLOSSLESS, ENCODER_MULTICORE, ENCODER_MULTICORE_COUNT, DELETE_LOSSLESS_FILES, \
|
||||
GROWL_ENABLED, GROWL_HOST, GROWL_PASSWORD, GROWL_ONSNATCH, PROWL_ENABLED, PROWL_PRIORITY, PROWL_KEYS, PROWL_ONSNATCH, PUSHOVER_ENABLED, PUSHOVER_PRIORITY, PUSHOVER_KEYS, PUSHOVER_ONSNATCH, PUSHOVER_APITOKEN, MIRRORLIST, \
|
||||
TWITTER_ENABLED, TWITTER_ONSNATCH, TWITTER_USERNAME, TWITTER_PASSWORD, TWITTER_PREFIX, \
|
||||
TWITTER_ENABLED, TWITTER_ONSNATCH, TWITTER_USERNAME, TWITTER_PASSWORD, TWITTER_PREFIX, OSX_NOTIFY_ENABLED, OSX_NOTIFY_ONSNATCH, OSX_NOTIFY_APP, BOXCAR_ENABLED, BOXCAR_ONSNATCH, BOXCAR_TOKEN, \
|
||||
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, \
|
||||
@@ -386,6 +391,8 @@ def initialize():
|
||||
CheckSection('Pushalot')
|
||||
CheckSection('Synoindex')
|
||||
CheckSection('Twitter')
|
||||
CheckSection('OSX_Notify')
|
||||
CheckSection('Boxcar')
|
||||
CheckSection('Songkick')
|
||||
CheckSection('Advanced')
|
||||
|
||||
@@ -506,6 +513,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', '')
|
||||
@@ -610,6 +618,14 @@ def initialize():
|
||||
TWITTER_PASSWORD = check_setting_str(CFG, 'Twitter', 'twitter_password', '')
|
||||
TWITTER_PREFIX = check_setting_str(CFG, 'Twitter', 'twitter_prefix', 'Headphones')
|
||||
|
||||
OSX_NOTIFY_ENABLED = bool(check_setting_int(CFG, 'OSX_Notify', 'osx_notify_enabled', 0))
|
||||
OSX_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'OSX_Notify', 'osx_notify_onsnatch', 0))
|
||||
OSX_NOTIFY_APP = check_setting_str(CFG, 'OSX_Notify', 'osx_notify_app', '/Applications/Headphones')
|
||||
|
||||
BOXCAR_ENABLED = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_enabled', 0))
|
||||
BOXCAR_ONSNATCH = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_onsnatch', 0))
|
||||
BOXCAR_TOKEN = check_setting_str(CFG, 'Boxcar', 'boxcar_token', '')
|
||||
|
||||
SONGKICK_ENABLED = bool(check_setting_int(CFG, 'Songkick', 'songkick_enabled', 1))
|
||||
SONGKICK_APIKEY = check_setting_str(CFG, 'Songkick', 'songkick_apikey', 'nd1We7dFW2RqxPw8')
|
||||
SONGKICK_LOCATION = check_setting_str(CFG, 'Songkick', 'songkick_location', '')
|
||||
@@ -928,6 +944,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)
|
||||
@@ -1025,6 +1042,16 @@ def config_write():
|
||||
new_config['Twitter']['twitter_password'] = TWITTER_PASSWORD
|
||||
new_config['Twitter']['twitter_prefix'] = TWITTER_PREFIX
|
||||
|
||||
new_config['OSX_Notify'] = {}
|
||||
new_config['OSX_Notify']['osx_notify_enabled'] = int(OSX_NOTIFY_ENABLED)
|
||||
new_config['OSX_Notify']['osx_notify_onsnatch'] = int(OSX_NOTIFY_ONSNATCH)
|
||||
new_config['OSX_Notify']['osx_notify_app'] = OSX_NOTIFY_APP
|
||||
|
||||
new_config['Boxcar'] = {}
|
||||
new_config['Boxcar']['boxcar_enabled'] = int(BOXCAR_ENABLED)
|
||||
new_config['Boxcar']['boxcar_onsnatch'] = int(BOXCAR_ONSNATCH)
|
||||
new_config['Boxcar']['boxcar_token'] = BOXCAR_TOKEN
|
||||
|
||||
new_config['Songkick'] = {}
|
||||
new_config['Songkick']['songkick_enabled'] = int(SONGKICK_ENABLED)
|
||||
new_config['Songkick']['songkick_apikey'] = SONGKICK_APIKEY
|
||||
@@ -1093,7 +1120,7 @@ def start():
|
||||
started = True
|
||||
|
||||
def sig_handler(signum=None, frame=None):
|
||||
if type(signum) != type(None):
|
||||
if signum is not None:
|
||||
logger.info("Signal %i caught, saving and exiting...", signum)
|
||||
shutdown()
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ def switch(AlbumID, ReleaseID):
|
||||
myDB.action('UPDATE albums SET Status=? WHERE AlbumID=?', ['Downloaded', AlbumID])
|
||||
|
||||
# Update have track counts on index
|
||||
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=?', [newalbumdata['ArtistID']]))
|
||||
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND AlbumID IN (SELECT AlbumID FROM albums WHERE Status != "Ignored")', [newalbumdata['ArtistID']]))
|
||||
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [newalbumdata['ArtistID']])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ?', [newalbumdata['ArtistID']]))
|
||||
|
||||
controlValueDict = {"ArtistID": newalbumdata['ArtistID']}
|
||||
|
||||
+11
-11
@@ -274,7 +274,7 @@ def expand_subfolders(f):
|
||||
difference = max(path_depths) - min(path_depths)
|
||||
|
||||
if difference > 0:
|
||||
logger.info("Found %d media folders, but depth difference between lowest and deepest media folder is %d (expected zero). If this is a discography or a collection of albums, make sure albums are per folder" % (len(media_folders), difference))
|
||||
logger.info("Found %d media folders, but depth difference between lowest and deepest media folder is %d (expected zero). If this is a discography or a collection of albums, make sure albums are per folder.", len(media_folders), difference)
|
||||
|
||||
# While already failed, advice the user what he could try. We assume the
|
||||
# directory may contain separate CD's and maybe some extra's. The
|
||||
@@ -283,7 +283,7 @@ def expand_subfolders(f):
|
||||
extra_media_folders = [ media_folder[:min(path_depths)] for media_folder in media_folders if len(media_folder) > min(path_depths) ]
|
||||
extra_media_folders = list(set([ os.path.join(*media_folder) for media_folder in extra_media_folders ]))
|
||||
|
||||
logger.info("Please look at the following folder(s), since they cause the depth difference: %s" % extra_media_folders)
|
||||
logger.info("Please look at the following folder(s), since they cause the depth difference: %s", extra_media_folders)
|
||||
return
|
||||
|
||||
# Convert back to paths and remove duplicates, which may be there after
|
||||
@@ -297,7 +297,7 @@ def expand_subfolders(f):
|
||||
logger.debug("Did not expand subfolder, as it resulted in one folder.")
|
||||
return
|
||||
|
||||
logger.debug("Expanded subfolders in folder: " % media_folders)
|
||||
logger.debug("Expanded subfolders in folder: %s", media_folders)
|
||||
return media_folders
|
||||
|
||||
def extract_data(s):
|
||||
@@ -360,7 +360,7 @@ def extract_metadata(f):
|
||||
# Try to read the file info
|
||||
try:
|
||||
media_file = MediaFile(os.path.join(root, file))
|
||||
except FileTypeError, UnreadableFileError:
|
||||
except (FileTypeError, UnreadableFileError):
|
||||
# Probably not a media file
|
||||
continue
|
||||
|
||||
@@ -374,14 +374,14 @@ def extract_metadata(f):
|
||||
|
||||
# Verify results
|
||||
if len(results) == 0:
|
||||
logger.info("No metadata in media files found, ignoring")
|
||||
logger.info("No metadata in media files found, ignoring.")
|
||||
return (None, None, None)
|
||||
|
||||
# Require that some percentage of files have tags
|
||||
count_ratio = 0.75
|
||||
|
||||
if count < (count_ratio * len(results)):
|
||||
logger.info("Counted %d media files, but only %d have tags, ignoring" % (count, len(results)))
|
||||
logger.info("Counted %d media files, but only %d have tags, ignoring.", count, len(results))
|
||||
return (None, None, None)
|
||||
|
||||
# Count distinct values
|
||||
@@ -399,7 +399,7 @@ def extract_metadata(f):
|
||||
old_album = new_albums[index]
|
||||
new_albums[index] = RE_CD_ALBUM.sub("", album).strip()
|
||||
|
||||
logger.debug("Stripped albumd number identifier: %s -> %s" % (old_album, new_albums[index]))
|
||||
logger.debug("Stripped albumd number identifier: %s -> %s", old_album, new_albums[index])
|
||||
|
||||
# Remove duplicates
|
||||
new_albums = list(set(new_albums))
|
||||
@@ -417,7 +417,7 @@ def extract_metadata(f):
|
||||
if len(artists) > 1 and len(albums) == 1:
|
||||
split_artists = [ RE_FEATURING.split(artist) for artist in artists ]
|
||||
featurings = [ len(split_artist) - 1 for split_artist in split_artists ]
|
||||
logger.info("Album seem to feature %d different artists" % sum(featurings))
|
||||
logger.info("Album seem to feature %d different artists", sum(featurings))
|
||||
|
||||
if sum(featurings) > 0:
|
||||
# Find the artist of which the least splits have been generated.
|
||||
@@ -429,8 +429,8 @@ def extract_metadata(f):
|
||||
return (artist, albums[0], years[0])
|
||||
|
||||
# Not sure what to do here.
|
||||
logger.info("Found %d artists, %d albums and %d years in metadata, so ignoring" % (len(artists), len(albums), len(years)))
|
||||
logger.debug("Artists: %s, Albums: %s, Years: %s" % (artists, albums, years))
|
||||
logger.info("Found %d artists, %d albums and %d years in metadata, so ignoring", len(artists), len(albums), len(years))
|
||||
logger.debug("Artists: %s, Albums: %s, Years: %s", artists, albums, years)
|
||||
|
||||
return (None, None, None)
|
||||
|
||||
@@ -486,7 +486,7 @@ def smartMove(src, dest, delete=True):
|
||||
filename = os.path.basename(src)
|
||||
|
||||
if os.path.isfile(os.path.join(dest, filename)):
|
||||
logger.info('Destination file exists: %s', os.path.join(dest, filename).decode(headphones.SYS_ENCODING, 'replace'))
|
||||
logger.info('Destination file exists: %s', os.path.join(dest, filename))
|
||||
title = os.path.splitext(filename)[0]
|
||||
ext = os.path.splitext(filename)[1]
|
||||
i = 1
|
||||
|
||||
+40
-31
@@ -210,6 +210,9 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
myDB.action('DELETE from releases WHERE ReleaseGroupID=?', [items['AlbumID']])
|
||||
logger.info("[%s] Removing all references to release group %s to reflect MusicBrainz" % (artist['artist_name'], items['AlbumID']))
|
||||
force_repackage = 1
|
||||
elif extrasonly:
|
||||
# Not really sure what we're doing here but don't want to log the message below if we're fetching extras only
|
||||
pass
|
||||
else:
|
||||
logger.info("[%s] There was either an error pulling data from MusicBrainz or there might not be any releases for this category" % artist['artist_name'])
|
||||
|
||||
@@ -224,14 +227,15 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
#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:
|
||||
|
||||
try:
|
||||
check_release_date = rg_exists['ReleaseDate']
|
||||
except TypeError:
|
||||
check_release_date = None
|
||||
|
||||
if check_release_date:
|
||||
if check_release_date[0] is None:
|
||||
logger.info("[%s] Now updating: %s (No Release Date)" % (artist['artist_name'], rg['title']))
|
||||
@@ -265,11 +269,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']])
|
||||
@@ -378,7 +377,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:
|
||||
@@ -399,8 +398,11 @@ 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
|
||||
@@ -469,10 +471,34 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
if skip_log == 0:
|
||||
logger.info(u"[%s] No new releases, so no changes made to %s" % (artist['artist_name'], rg['title']))
|
||||
|
||||
finalize_update(artistid, artist['artist_name'], errors)
|
||||
|
||||
logger.info(u"Seeing if we need album art for: %s" % artist['artist_name'])
|
||||
cache.getThumb(ArtistID=artistid)
|
||||
|
||||
if errors:
|
||||
logger.info("[%s] Finished updating artist: %s but with errors, so not marking it as updated in the database" % (artist['artist_name'], artist['artist_name']))
|
||||
else:
|
||||
myDB.action('DELETE FROM newartists WHERE ArtistName = ?', [artist['artist_name']])
|
||||
logger.info(u"Updating complete for: %s" % artist['artist_name'])
|
||||
|
||||
# Start searching for newly added albums
|
||||
if album_searches:
|
||||
from headphones import searcher
|
||||
logger.info("Start searching for %d albums.", len(album_searches))
|
||||
|
||||
for album_search in album_searches:
|
||||
searcher.searchforalbum(albumid=album_search)
|
||||
|
||||
def finalize_update(artistid, artistname, errors=False):
|
||||
# Moving this little bit to it's own function so we can update have tracks & latest album when deleting extras
|
||||
|
||||
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]))
|
||||
#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"', [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]))
|
||||
|
||||
controlValueDict = {"ArtistID": artistid}
|
||||
|
||||
@@ -493,23 +519,6 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
|
||||
myDB.upsert("artists", newValueDict, controlValueDict)
|
||||
|
||||
logger.info(u"Seeing if we need album art for: %s" % artist['artist_name'])
|
||||
cache.getThumb(ArtistID=artistid)
|
||||
|
||||
if errors:
|
||||
logger.info("[%s] Finished updating artist: %s but with errors, so not marking it as updated in the database" % (artist['artist_name'], artist['artist_name']))
|
||||
else:
|
||||
myDB.action('DELETE FROM newartists WHERE ArtistName = ?', [artist['artist_name']])
|
||||
logger.info(u"Updating complete for: %s" % artist['artist_name'])
|
||||
|
||||
# Start searching for newly added albums
|
||||
if album_searches:
|
||||
from headphones import searcher
|
||||
logger.info("Start searching for %d albums.", len(album_searches))
|
||||
|
||||
for album_search in album_searches:
|
||||
searcher.searchforalbum(albumid=album_search)
|
||||
|
||||
def addReleaseById(rid):
|
||||
|
||||
myDB = db.DBConnection()
|
||||
|
||||
@@ -328,8 +328,8 @@ 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')
|
||||
|
||||
@@ -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"
|
||||
else:
|
||||
if album['Status'] == "Skipped" or album['Status'] == "Downloaded":
|
||||
new_album_status = "Skipped"
|
||||
|
||||
# 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:
|
||||
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))
|
||||
|
||||
@@ -55,7 +55,7 @@ def initLogger(verbose=1):
|
||||
|
||||
# Configure the logger to accept all messages
|
||||
logger.propagate = False
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.setLevel(logging.DEBUG if verbose == 2 else logging.INFO)
|
||||
|
||||
# Setup file logger
|
||||
filename = os.path.join(headphones.LOG_DIR, FILENAME)
|
||||
@@ -69,7 +69,7 @@ def initLogger(verbose=1):
|
||||
|
||||
# Add list logger
|
||||
loglist_handler = LogListHandler()
|
||||
loglist_handler.setLevel(logging.INFO)
|
||||
loglist_handler.setLevel(logging.DEBUG)
|
||||
|
||||
logger.addHandler(loglist_handler)
|
||||
|
||||
@@ -78,10 +78,6 @@ def initLogger(verbose=1):
|
||||
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)
|
||||
|
||||
if verbose == 1:
|
||||
console_handler.setLevel(logging.INFO)
|
||||
elif verbose == 2:
|
||||
console_handler.setLevel(logging.DEBUG)
|
||||
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
+8
-8
@@ -13,8 +13,6 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import time
|
||||
import threading
|
||||
|
||||
@@ -200,7 +198,7 @@ def getArtist(artistid, extrasonly=False):
|
||||
|
||||
if not extrasonly:
|
||||
for rg in artist['release-group-list']:
|
||||
if rg['type'] != 'Album': #only add releases without a secondary type
|
||||
if "secondary-type-list" in rg.keys(): #only add releases without a secondary type
|
||||
continue
|
||||
releasegroups.append({
|
||||
'title': unicode(rg['title']),
|
||||
@@ -234,17 +232,19 @@ def getArtist(artistid, extrasonly=False):
|
||||
|
||||
for include in includes:
|
||||
|
||||
artist = None
|
||||
mb_extras_list = []
|
||||
|
||||
try:
|
||||
artist = musicbrainzngs.get_artist_by_id(artistid,includes=["releases","release-groups"],release_status=['official'],release_type=include)['artist']
|
||||
limit = 200
|
||||
newRgs = None
|
||||
while newRgs == None or len(newRgs) >= limit:
|
||||
newRgs = musicbrainzngs.browse_release_groups(artistid,release_type=include,offset=len(mb_extras_list),limit=limit)['release-group-list']
|
||||
mb_extras_list += newRgs
|
||||
except WebServiceError, e:
|
||||
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s (%s)' % (artistid, str(e)))
|
||||
time.sleep(5)
|
||||
|
||||
if not artist:
|
||||
continue
|
||||
for rg in artist['release-group-list']:
|
||||
for rg in mb_extras_list:
|
||||
releasegroups.append({
|
||||
'title': unicode(rg['title']),
|
||||
'id': unicode(rg['id']),
|
||||
|
||||
+105
-12
@@ -22,6 +22,7 @@ import simplejson
|
||||
import os.path
|
||||
import subprocess
|
||||
import gntp.notifier
|
||||
import time
|
||||
|
||||
from xml.dom import minidom
|
||||
from httplib import HTTPSConnection
|
||||
@@ -168,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):
|
||||
@@ -191,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']
|
||||
@@ -238,6 +250,7 @@ class XBMC:
|
||||
|
||||
except:
|
||||
logger.warn('Error sending notification request to XBMC')
|
||||
|
||||
class LMS:
|
||||
|
||||
#Class for updating a Logitech Media Server
|
||||
@@ -247,10 +260,10 @@ class LMS:
|
||||
self.hosts = headphones.LMS_HOST
|
||||
|
||||
def _sendjson(self, host):
|
||||
data = {'id': 1, 'method': 'slim.request', 'params': ["",["rescan"]]} #Had a lot of trouble with simplejson, but this works.
|
||||
data = {'id': 1, 'method': 'slim.request', 'params': ["",["rescan"]]}
|
||||
data = simplejson.JSONEncoder().encode(data)
|
||||
|
||||
content = {'Content-Type': 'application/json', 'Content-Length': len(data)}
|
||||
content = {'Content-Type': 'application/json'}
|
||||
|
||||
req = urllib2.Request(host+'/jsonrpc.js', data, content)
|
||||
|
||||
@@ -261,20 +274,15 @@ class LMS:
|
||||
return
|
||||
|
||||
response = simplejson.JSONDecoder().decode(handle.read())
|
||||
server_result = simplejson.dumps(response)
|
||||
|
||||
try:
|
||||
return response[0]['result']
|
||||
return response['result']
|
||||
except:
|
||||
logger.warn('LMS returned error: %s' % response[0]['error'])
|
||||
logger.warn('LMS returned error: %s' % response['error'])
|
||||
return
|
||||
|
||||
def update(self):
|
||||
|
||||
#Send the ["rescan"] command to an LMS server.
|
||||
#Note that the command must be prefixed with the 'player' that the command is aimed at,
|
||||
#But with this being a request for the server to update its library, the player is blank, so ""
|
||||
|
||||
hosts = [x.strip() for x in self.hosts.split(',')]
|
||||
|
||||
for host in hosts:
|
||||
@@ -454,7 +462,6 @@ class PUSHBULLET:
|
||||
|
||||
self.notify('Main Screen Activate', 'Test Message')
|
||||
|
||||
|
||||
class PUSHALOT:
|
||||
|
||||
def notify(self, message, event):
|
||||
@@ -529,6 +536,7 @@ class Synoindex:
|
||||
if isinstance(path_list, list):
|
||||
for path in path_list:
|
||||
self.notify(path)
|
||||
|
||||
class PUSHOVER:
|
||||
|
||||
application_token = "LdPCoy0dqC21ktsbEyAVCcwvQiVlsz"
|
||||
@@ -694,3 +702,88 @@ class TwitterNotifier:
|
||||
return self._send_tweet(prefix+": "+message)
|
||||
|
||||
notifier = TwitterNotifier
|
||||
|
||||
class OSX_NOTIFY:
|
||||
|
||||
objc = None
|
||||
|
||||
def __init__(self):
|
||||
try:
|
||||
self.objc = __import__("objc")
|
||||
except:
|
||||
return False
|
||||
|
||||
def swizzle(self, cls, SEL, func):
|
||||
old_IMP = cls.instanceMethodForSelector_(SEL)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
return func(self, old_IMP, *args, **kwargs)
|
||||
new_IMP = self.objc.selector(wrapper, selector=old_IMP.selector,
|
||||
signature=old_IMP.signature)
|
||||
self.objc.classAddMethod(cls, SEL, new_IMP)
|
||||
|
||||
def notify(self, title, subtitle=None, text=None, sound=True):
|
||||
|
||||
try:
|
||||
self.swizzle(self.objc.lookUpClass('NSBundle'),
|
||||
b'bundleIdentifier',
|
||||
self.swizzled_bundleIdentifier)
|
||||
|
||||
NSUserNotification = self.objc.lookUpClass('NSUserNotification')
|
||||
NSUserNotificationCenter = self.objc.lookUpClass('NSUserNotificationCenter')
|
||||
NSAutoreleasePool = self.objc.lookUpClass('NSAutoreleasePool')
|
||||
|
||||
if not NSUserNotification or not NSUserNotificationCenter:
|
||||
return False
|
||||
|
||||
pool = NSAutoreleasePool.alloc().init()
|
||||
|
||||
notification = NSUserNotification.alloc().init()
|
||||
notification.setTitle_(title)
|
||||
if subtitle:
|
||||
notification.setSubtitle_(subtitle)
|
||||
if text:
|
||||
notification.setInformativeText_(text)
|
||||
if sound:
|
||||
notification.setSoundName_("NSUserNotificationDefaultSoundName")
|
||||
notification.setHasActionButton_(False)
|
||||
|
||||
notification_center = NSUserNotificationCenter.defaultUserNotificationCenter()
|
||||
notification_center.deliverNotification_(notification)
|
||||
|
||||
del pool
|
||||
return True
|
||||
|
||||
except Exception, e:
|
||||
logger.warn('Error sending OS X Notification: %s' % e)
|
||||
return False
|
||||
|
||||
def swizzled_bundleIdentifier(self, original, swizzled):
|
||||
return 'ade.headphones.osxnotify'
|
||||
|
||||
class BOXCAR:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.url = 'https://new.boxcar.io/api/notifications'
|
||||
|
||||
def notify(self, title, message, rgid=None):
|
||||
|
||||
try:
|
||||
if rgid:
|
||||
message += '<br></br><a href="http://musicbrainz.org/release-group/%s">MusicBrainz</a>' % rgid
|
||||
|
||||
data = urllib.urlencode({
|
||||
'user_credentials': headphones.BOXCAR_TOKEN,
|
||||
'notification[title]': title.encode('utf-8'),
|
||||
'notification[long_message]': message.encode('utf-8'),
|
||||
'notification[sound]': "done"
|
||||
})
|
||||
|
||||
req = urllib2.Request(self.url)
|
||||
handle = urllib2.urlopen(req, data)
|
||||
handle.close()
|
||||
return True
|
||||
|
||||
except urllib2.URLError, e:
|
||||
logger.warn('Error sending Boxcar2 Notification: %s' % e)
|
||||
return False
|
||||
|
||||
+86
-64
@@ -13,22 +13,20 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
import music_encoder
|
||||
import shutil, re
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from headphones import notifiers
|
||||
import beets
|
||||
from beets import autotag
|
||||
from beets.mediafile import MediaFile
|
||||
|
||||
import threading
|
||||
import headphones
|
||||
from headphones import db, albumart, librarysync, lyrics, logger, helpers, request
|
||||
from headphones.helpers import sab_replace_dots, sab_replace_spaces
|
||||
|
||||
from beets import autotag
|
||||
from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError
|
||||
|
||||
from headphones import notifiers
|
||||
from headphones import db, albumart, librarysync, lyrics
|
||||
from headphones import logger, helpers, request, mb, music_encoder
|
||||
|
||||
postprocessor_lock = threading.Lock()
|
||||
|
||||
@@ -44,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'],
|
||||
sab_replace_dots(album['FolderName']),
|
||||
sab_replace_spaces(album['FolderName']),
|
||||
sab_replace_spaces(sab_replace_dots(album['FolderName']))
|
||||
]
|
||||
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'])
|
||||
|
||||
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':
|
||||
|
||||
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):
|
||||
|
||||
@@ -80,18 +66,16 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
||||
#from an RSS feed or etc
|
||||
#TODO: This should be a call to a class method.. copied it out of importer with only minor changes
|
||||
#TODO: odd things can happen when there are diacritic characters in the folder name, need to translate them?
|
||||
import mb
|
||||
|
||||
release_list = None
|
||||
|
||||
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
|
||||
@@ -99,6 +83,10 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
||||
|
||||
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'])
|
||||
|
||||
if release_dict['artist_name'].startswith('The '):
|
||||
@@ -350,6 +338,27 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
downloaded_track_list.append(os.path.join(r, files))
|
||||
elif files.lower().endswith('.cue'):
|
||||
downloaded_cuecount += 1
|
||||
|
||||
# Check if files are valid media files and are writeable, before the steps
|
||||
# below are executed. This simplifies errors and prevents unfinished steps.
|
||||
for downloaded_track in downloaded_track_list:
|
||||
try:
|
||||
media_file = MediaFile(downloaded_track)
|
||||
except (FileTypeError, UnreadableFileError):
|
||||
logger.error("Track file is not a valid media file: %s. Not continuing.", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
return
|
||||
|
||||
# Not sure if line(s) below are needed, since it is possible to not
|
||||
# touch any files.
|
||||
if headphones.EMBED_ALBUM_ART or headphones.CLEANUP_FILES or \
|
||||
headphones.ADD_ALBUM_ART or headphones.CORRECT_METADATA or \
|
||||
headphones.EMBED_LYRICS or headphones.RENAME_FILES or \
|
||||
headphones.MOVE_FILES:
|
||||
|
||||
if not os.access(downloaded_track, os.W_OK):
|
||||
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
|
||||
if headphones.MUSIC_ENCODER:
|
||||
downloaded_track_list=music_encoder.encode(albumpath)
|
||||
@@ -474,6 +483,21 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
twitter = notifiers.TwitterNotifier()
|
||||
twitter.notify_download(pushmessage)
|
||||
|
||||
if headphones.OSX_NOTIFY_ENABLED:
|
||||
logger.info(u"Sending OS X notification")
|
||||
osx_notify = notifiers.OSX_NOTIFY()
|
||||
osx_notify.notify(release['ArtistName'], release['AlbumTitle'], "Download and Postprocessing completed")
|
||||
|
||||
if headphones.BOXCAR_ENABLED:
|
||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
||||
logger.info(u"Sending Boxcar2 notification")
|
||||
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')
|
||||
|
||||
@@ -632,7 +656,7 @@ def moveFiles(albumpath, release, tracks):
|
||||
try:
|
||||
shutil.rmtree(lossless_destination_path)
|
||||
except Exception, e:
|
||||
logger.error("Error deleting existing folder: %s. Creating duplicate folder. Error: %s" % (lossless_destination_path.decode(headphones.SYS_ENCODING, 'replace'), str(e)))
|
||||
logger.error("Error deleting existing folder: %s. Creating duplicate folder. Error: %s" % (lossless_destination_path.decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||
create_duplicate_folder = True
|
||||
|
||||
if not headphones.REPLACE_EXISTING_FOLDERS or create_duplicate_folder:
|
||||
@@ -665,7 +689,7 @@ def moveFiles(albumpath, release, tracks):
|
||||
try:
|
||||
shutil.rmtree(lossy_destination_path)
|
||||
except Exception, e:
|
||||
logger.error("Error deleting existing folder: %s. Creating duplicate folder. Error: %s" % (lossy_destination_path.decode(headphones.SYS_ENCODING, 'replace'), str(e)))
|
||||
logger.error("Error deleting existing folder: %s. Creating duplicate folder. Error: %s" % (lossy_destination_path.decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||
create_duplicate_folder = True
|
||||
|
||||
if not headphones.REPLACE_EXISTING_FOLDERS or create_duplicate_folder:
|
||||
@@ -746,13 +770,13 @@ def moveFiles(albumpath, release, tracks):
|
||||
try:
|
||||
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'))
|
||||
logger.error("Error trying to change permissions on folder: %s. %s", temp_f, e)
|
||||
|
||||
# If we failed to move all the files out of the directory, this will fail too
|
||||
try:
|
||||
shutil.rmtree(albumpath)
|
||||
except Exception, e:
|
||||
logger.error('Could not remove directory: %s. %s' % (albumpath.decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||
logger.error('Could not remove directory: %s. %s', albumpath, e)
|
||||
|
||||
destination_paths = []
|
||||
|
||||
@@ -779,10 +803,10 @@ def correctMetadata(albumid, release, downloaded_track_list):
|
||||
elif any(downloaded_track.lower().endswith('.' + x.lower()) for x in headphones.LOSSY_MEDIA_FORMATS):
|
||||
lossy_items.append(beets.library.Item.from_path(downloaded_track))
|
||||
else:
|
||||
logger.warn("Skipping: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + " because it is not a mutagen friendly file format")
|
||||
logger.warn("Skipping: %s because it is not a mutagen friendly file format", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
except Exception, e:
|
||||
|
||||
logger.error("Beets couldn't create an Item from: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + " - not a media file?" + str(e))
|
||||
logger.error("Beets couldn't create an Item from: %s - not a media file? %s", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), str(e))
|
||||
|
||||
for items in [lossy_items, lossless_items]:
|
||||
|
||||
@@ -792,16 +816,16 @@ def correctMetadata(albumid, release, downloaded_track_list):
|
||||
try:
|
||||
cur_artist, cur_album, candidates, rec = autotag.tag_album(items, search_artist=helpers.latinToAscii(release['ArtistName']), search_album=helpers.latinToAscii(release['AlbumTitle']))
|
||||
except Exception, e:
|
||||
logger.error('Error getting recommendation: %s. Not writing metadata' % e)
|
||||
logger.error('Error getting recommendation: %s. Not writing metadata', e)
|
||||
return
|
||||
if str(rec) == 'recommendation.none':
|
||||
logger.warn('No accurate album match found for %s, %s - not writing metadata' % (release['ArtistName'], release['AlbumTitle']))
|
||||
logger.warn('No accurate album match found for %s, %s - not writing metadata', release['ArtistName'], release['AlbumTitle'])
|
||||
return
|
||||
|
||||
if candidates:
|
||||
dist, info, mapping, extra_items, extra_tracks = candidates[0]
|
||||
else:
|
||||
logger.warn('No accurate album match found for %s, %s - not writing metadata' % (release['ArtistName'], release['AlbumTitle']))
|
||||
logger.warn('No accurate album match found for %s, %s - not writing metadata', release['ArtistName'], release['AlbumTitle'])
|
||||
return
|
||||
|
||||
logger.info('Beets recommendation for tagging items: %s' % rec)
|
||||
@@ -813,9 +837,9 @@ def correctMetadata(albumid, release, downloaded_track_list):
|
||||
for item in items:
|
||||
try:
|
||||
item.write()
|
||||
logger.info("Successfully applied metadata to: " + item.path.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
logger.info("Successfully applied metadata to: %s", item.path.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
except Exception, e:
|
||||
logger.warn("Error writing metadata to " + item.path.decode(headphones.SYS_ENCODING, 'replace') + ": " + str(e))
|
||||
logger.warn("Error writing metadata to '%s': %s", item.path.decode(headphones.SYS_ENCODING, 'replace'), str(e))
|
||||
|
||||
def embedLyrics(downloaded_track_list):
|
||||
logger.info('Adding lyrics')
|
||||
@@ -827,7 +851,7 @@ def embedLyrics(downloaded_track_list):
|
||||
try:
|
||||
f = MediaFile(downloaded_track)
|
||||
except:
|
||||
logger.error('Could not read %s. Not checking lyrics' % downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
logger.error('Could not read %s. Not checking lyrics', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
continue
|
||||
|
||||
if f.albumartist and f.title:
|
||||
@@ -835,16 +859,16 @@ def embedLyrics(downloaded_track_list):
|
||||
elif f.artist and f.title:
|
||||
metalyrics = lyrics.getLyrics(f.artist, f.title)
|
||||
else:
|
||||
logger.info('No artist/track metadata found for track: %s. Not fetching lyrics' % downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
logger.info('No artist/track metadata found for track: %s. Not fetching lyrics', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
metalyrics = None
|
||||
|
||||
if lyrics:
|
||||
logger.debug('Adding lyrics to: %s' % downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
logger.debug('Adding lyrics to: %s', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
f.lyrics = metalyrics
|
||||
try:
|
||||
f.save()
|
||||
except:
|
||||
logger.error('Cannot save lyrics to: %s. Skipping' % downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
logger.error('Cannot save lyrics to: %s. Skipping', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
continue
|
||||
|
||||
def renameFiles(albumpath, downloaded_track_list, release):
|
||||
@@ -859,7 +883,7 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
||||
try:
|
||||
f = MediaFile(downloaded_track)
|
||||
except:
|
||||
logger.info("MediaFile couldn't parse: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
logger.info("MediaFile couldn't parse: %s", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
continue
|
||||
|
||||
if not f.disc:
|
||||
@@ -928,11 +952,11 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
||||
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')))
|
||||
logger.debug('Renaming %s ---> %s', downloaded_track.decode(headphones.SYS_ENCODING,'replace'), new_file_name.decode(headphones.SYS_ENCODING,'replace'))
|
||||
try:
|
||||
os.rename(downloaded_track, new_file)
|
||||
except Exception, e:
|
||||
logger.error('Error renaming file: %s. Error: %s' % (downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||
logger.error('Error renaming file: %s. Error: %s', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), e)
|
||||
continue
|
||||
|
||||
def updateFilePermissions(albumpaths):
|
||||
@@ -1044,7 +1068,6 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None):
|
||||
continue
|
||||
else:
|
||||
logger.info('Querying MusicBrainz for the release group id for: %s - %s', name, album)
|
||||
from headphones import mb
|
||||
try:
|
||||
rgid = mb.findAlbumID(helpers.latinToAscii(name), helpers.latinToAscii(album))
|
||||
except:
|
||||
@@ -1072,7 +1095,6 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None):
|
||||
continue
|
||||
else:
|
||||
logger.info('Querying MusicBrainz for the release group id for: %s - %s', name, album)
|
||||
from headphones import mb
|
||||
try:
|
||||
rgid = mb.findAlbumID(helpers.latinToAscii(name), helpers.latinToAscii(album))
|
||||
except:
|
||||
@@ -1108,17 +1130,18 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None):
|
||||
continue
|
||||
|
||||
# Attempt 4: Hail mary. Just assume the folder name is the album name if it doesn't have a separator in it
|
||||
if '-' not in folder:
|
||||
release = myDB.action('SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE AlbumTitle LIKE ?', [folder]).fetchone()
|
||||
logger.debug('Attempt to extract album name by assuming it is the folder name')
|
||||
|
||||
if '-' not in folder_basename:
|
||||
release = myDB.action('SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE AlbumTitle LIKE ?', [folder_basename]).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)
|
||||
continue
|
||||
else:
|
||||
logger.info('Querying MusicBrainz for the release group id for: %s', folder)
|
||||
from headphones import mb
|
||||
logger.info('Querying MusicBrainz for the release group id for: %s', folder_basename)
|
||||
try:
|
||||
rgid = mb.findAlbumID(album=helpers.latinToAscii(folder))
|
||||
rgid = mb.findAlbumID(album=helpers.latinToAscii(folder_basename))
|
||||
except:
|
||||
logger.error('Can not get release information for this album')
|
||||
rgid = None
|
||||
@@ -1128,4 +1151,3 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None):
|
||||
continue
|
||||
else:
|
||||
logger.info('No match found on MusicBrainz for: %s - %s', name, album)
|
||||
|
||||
|
||||
@@ -30,8 +30,11 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N
|
||||
# is white listed.
|
||||
if whitelist_status_code and auto_raise:
|
||||
if response.status_code not in whitelist_status_code:
|
||||
logger.debug("Response status code %d is not white listed, raising exception", response.status_code)
|
||||
try:
|
||||
response.raise_for_status()
|
||||
except:
|
||||
logger.debug("Response status code %d is not white listed, raised exception", response.status_code)
|
||||
raise
|
||||
elif auto_raise:
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
@@ -119,35 +119,6 @@ def sendNZB(nzb):
|
||||
|
||||
if sabText == "ok":
|
||||
logger.info(u"NZB sent to SAB successfully")
|
||||
if headphones.GROWL_ENABLED and headphones.GROWL_ONSNATCH:
|
||||
logger.info(u"Sending Growl notification")
|
||||
growl = notifiers.GROWL()
|
||||
growl.notify(nzb.name,"Download started")
|
||||
if headphones.PROWL_ENABLED and headphones.PROWL_ONSNATCH:
|
||||
logger.info(u"Sending Prowl notification")
|
||||
prowl = notifiers.PROWL()
|
||||
prowl.notify(nzb.name,"Download started")
|
||||
if headphones.PUSHOVER_ENABLED and headphones.PUSHOVER_ONSNATCH:
|
||||
logger.info(u"Sending Pushover notification")
|
||||
prowl = notifiers.PUSHOVER()
|
||||
prowl.notify(nzb.name,"Download started")
|
||||
if headphones.PUSHBULLET_ENABLED and headphones.PUSHBULLET_ONSNATCH:
|
||||
logger.info(u"Sending PushBullet notification")
|
||||
pushbullet = notifiers.PUSHBULLET()
|
||||
pushbullet.notify(nzb.name + " has been snatched!", "Download started")
|
||||
if headphones.TWITTER_ENABLED and headphones.TWITTER_ONSNATCH:
|
||||
logger.info(u"Sending Twitter notification")
|
||||
twitter = notifiers.TwitterNotifier()
|
||||
twitter.notify_snatch(nzb.name)
|
||||
if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
|
||||
logger.debug(u"Sending NMA notification")
|
||||
nma = notifiers.NMA()
|
||||
nma.notify(snatched_nzb=nzb.name)
|
||||
if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH:
|
||||
logger.info(u"Sending Pushalot notification")
|
||||
pushalot = notifiers.PUSHALOT()
|
||||
pushalot.notify(nzb.name,"Download started")
|
||||
|
||||
return True
|
||||
elif sabText == "Missing authentication":
|
||||
logger.info(u"Incorrect username/password sent to SAB, NZB not sent")
|
||||
|
||||
+88
-9
@@ -31,7 +31,7 @@ import subprocess
|
||||
import headphones
|
||||
from headphones.common import USER_AGENT
|
||||
from headphones import logger, db, helpers, classes, sab, nzbget, request
|
||||
from headphones import transmission
|
||||
from headphones import utorrent, transmission, notifiers
|
||||
|
||||
import lib.bencode as bencode
|
||||
|
||||
@@ -307,13 +307,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)
|
||||
)
|
||||
@@ -359,6 +359,9 @@ def searchNZB(album, new=False, losslessOnly=False):
|
||||
logger.info("Album type is audiobook/spokenword. Using audiobook category")
|
||||
|
||||
for newznab_host in newznab_hosts:
|
||||
|
||||
provider = newznab_host[0]
|
||||
|
||||
# Add a little mod for kere.ws
|
||||
if newznab_host[0] == "http://kere.ws":
|
||||
if categories == "3040":
|
||||
@@ -573,14 +576,16 @@ def send_to_downloader(data, bestqual, album):
|
||||
nzb = classes.NZBDataSearchResult()
|
||||
nzb.extraInfo.append(data)
|
||||
nzb.name = folder_name
|
||||
nzbget.sendNZB(nzb)
|
||||
if not nzbget.sendNZB(nzb):
|
||||
return
|
||||
|
||||
elif headphones.NZB_DOWNLOADER == 0:
|
||||
|
||||
nzb = classes.NZBDataSearchResult()
|
||||
nzb.extraInfo.append(data)
|
||||
nzb.name = folder_name
|
||||
sab.sendNZB(nzb)
|
||||
if not sab.sendNZB(nzb):
|
||||
return
|
||||
|
||||
# If we sent the file to sab, we can check how it was renamed and insert that into the snatched table
|
||||
(replace_spaces, replace_dots) = sab.checkConfig()
|
||||
@@ -691,10 +696,82 @@ def send_to_downloader(data, bestqual, album):
|
||||
except Exception, e:
|
||||
logger.exception("Unhandled exception")
|
||||
|
||||
else:
|
||||
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]
|
||||
|
||||
folder_name = utorrent.addTorrent(file_or_url)
|
||||
|
||||
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])
|
||||
|
||||
# notify
|
||||
artist = album[1]
|
||||
albumname = album[2]
|
||||
rgid = album[6]
|
||||
title = artist + ' - ' + albumname
|
||||
provider = bestqual[3]
|
||||
if provider.startswith(("http://", "https://")):
|
||||
provider = provider.split("//")[1]
|
||||
name = folder_name if folder_name else None
|
||||
|
||||
if headphones.GROWL_ENABLED and headphones.GROWL_ONSNATCH:
|
||||
logger.info(u"Sending Growl notification")
|
||||
growl = notifiers.GROWL()
|
||||
growl.notify(name,"Download started")
|
||||
if headphones.PROWL_ENABLED and headphones.PROWL_ONSNATCH:
|
||||
logger.info(u"Sending Prowl notification")
|
||||
prowl = notifiers.PROWL()
|
||||
prowl.notify(name,"Download started")
|
||||
if headphones.PUSHOVER_ENABLED and headphones.PUSHOVER_ONSNATCH:
|
||||
logger.info(u"Sending Pushover notification")
|
||||
prowl = notifiers.PUSHOVER()
|
||||
prowl.notify(name,"Download started")
|
||||
if headphones.PUSHBULLET_ENABLED and headphones.PUSHBULLET_ONSNATCH:
|
||||
logger.info(u"Sending PushBullet notification")
|
||||
pushbullet = notifiers.PUSHBULLET()
|
||||
pushbullet.notify(name + " has been snatched!", "Download started")
|
||||
if headphones.TWITTER_ENABLED and headphones.TWITTER_ONSNATCH:
|
||||
logger.info(u"Sending Twitter notification")
|
||||
twitter = notifiers.TwitterNotifier()
|
||||
twitter.notify_snatch(name)
|
||||
if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
|
||||
logger.info(u"Sending NMA notification")
|
||||
nma = notifiers.NMA()
|
||||
nma.notify(snatched_nzb=name)
|
||||
if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH:
|
||||
logger.info(u"Sending Pushalot notification")
|
||||
pushalot = notifiers.PUSHALOT()
|
||||
pushalot.notify(name,"Download started")
|
||||
if headphones.OSX_NOTIFY_ENABLED and headphones.OSX_NOTIFY_ONSNATCH:
|
||||
logger.info(u"Sending OS X notification")
|
||||
osx_notify = notifiers.OSX_NOTIFY()
|
||||
osx_notify.notify(artist, albumname, 'Snatched: ' + provider + '. ' + name)
|
||||
if headphones.BOXCAR_ENABLED and headphones.BOXCAR_ONSNATCH:
|
||||
logger.info(u"Sending Boxcar2 notification")
|
||||
b2msg = 'From ' + provider + '<br></br>' + name
|
||||
boxcar = notifiers.BOXCAR()
|
||||
boxcar.notify('Headphones snatched: ' + title, b2msg, rgid)
|
||||
|
||||
def verifyresult(title, artistterm, term, lossless):
|
||||
|
||||
title = re.sub('[\.\-\/\_]', ' ', title)
|
||||
@@ -862,7 +939,7 @@ def searchTorrent(album, new=False, losslessOnly=False):
|
||||
minimumseeders = int(headphones.NUMBEROFSEEDERS) - 1
|
||||
|
||||
if headphones.KAT:
|
||||
provider = "Kick Ass Torrent"
|
||||
provider = "Kick Ass Torrents"
|
||||
providerurl = url_fix("http://kickass.to/usearch/" + term)
|
||||
if headphones.PREFERRED_QUALITY == 3 or losslessOnly:
|
||||
categories = "7" #music
|
||||
@@ -1051,7 +1128,7 @@ def searchTorrent(album, new=False, losslessOnly=False):
|
||||
if re.search(bitrate, encoding_string, flags=re.I):
|
||||
bitrate_string = encoding_string
|
||||
if bitrate_string not in gazelleencoding.ALL_ENCODINGS:
|
||||
raise Exception("Preferred bitrate %s not recognized by %s" % (bitrate_string, provider))
|
||||
logger.info(u"Your preferred bitrate is not one of the available What.cd filters, so not using it as a search parameter.")
|
||||
maxsize = 10000000000
|
||||
elif headphones.PREFERRED_QUALITY == 1: # Highest quality including lossless
|
||||
search_formats = [gazelleformat.FLAC, gazelleformat.MP3]
|
||||
@@ -1179,6 +1256,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:
|
||||
@@ -1324,7 +1403,7 @@ def preprocess(resultlist):
|
||||
#Get out of here if we're using Transmission or uTorrent
|
||||
if headphones.TORRENT_DOWNLOADER != 0:
|
||||
return True, result
|
||||
# get outta here if rutracker or piratebay
|
||||
# get outta here if rutracker
|
||||
if result[3] == 'rutracker.org':
|
||||
return True, result
|
||||
# Get out of here if it's a magnet link
|
||||
@@ -1334,7 +1413,7 @@ def preprocess(resultlist):
|
||||
# Download the torrent file
|
||||
headers = {}
|
||||
|
||||
if result[3] == 'Kick Ass Torrent':
|
||||
if result[3] == 'Kick Ass Torrents':
|
||||
headers['Referer'] = 'http://kat.ph/'
|
||||
elif result[3] == 'What.cd':
|
||||
headers['User-Agent'] = 'Headphones'
|
||||
|
||||
@@ -49,31 +49,6 @@ def addTorrent(link):
|
||||
retid = False
|
||||
|
||||
logger.info(u"Torrent sent to Transmission successfully")
|
||||
if headphones.GROWL_ENABLED and headphones.GROWL_ONSNATCH:
|
||||
logger.info(u"Sending Growl notification")
|
||||
growl = notifiers.GROWL()
|
||||
growl.notify(name,"Download started")
|
||||
if headphones.PROWL_ENABLED and headphones.PROWL_ONSNATCH:
|
||||
logger.info(u"Sending Prowl notification")
|
||||
prowl = notifiers.PROWL()
|
||||
prowl.notify(name,"Download started")
|
||||
if headphones.PUSHOVER_ENABLED and headphones.PUSHOVER_ONSNATCH:
|
||||
logger.info(u"Sending Pushover notification")
|
||||
pushover = notifiers.PUSHOVER()
|
||||
pushover.notify(name,"Download started")
|
||||
if headphones.TWITTER_ENABLED and headphones.TWITTER_ONSNATCH:
|
||||
logger.info(u"Sending Twitter notification")
|
||||
twitter = notifiers.TwitterNotifier()
|
||||
twitter.notify_snatch(nzb.name)
|
||||
if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
|
||||
logger.info(u"Sending NMA notification")
|
||||
nma = notifiers.NMA()
|
||||
nma.notify(snatched_nzb=name)
|
||||
if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH:
|
||||
logger.info(u"Sending Pushalot notification")
|
||||
pushalot = notifiers.PUSHALOT()
|
||||
pushalot.notify(name,"Download started")
|
||||
|
||||
return retid
|
||||
|
||||
else:
|
||||
|
||||
@@ -12,3 +12,87 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
import os
|
||||
import time
|
||||
import base64
|
||||
import headphones
|
||||
|
||||
import simplejson as json
|
||||
|
||||
from headphones import logger, notifiers, request
|
||||
|
||||
# This is just a simple script to send torrents to transmission. The
|
||||
# intention is to turn this into a class where we can check the state
|
||||
# of the download, set the download dir, etc.
|
||||
# TODO: Store the session id so we don't need to make 2 calls
|
||||
# Store torrent id so we can check up on it
|
||||
|
||||
|
||||
def addTorrent(link):
|
||||
|
||||
host = headphones.UTORRENT_HOST
|
||||
username = headphones.UTORRENT_USERNAME
|
||||
password = headphones.UTORRENT_PASSWORD
|
||||
label = headphones.UTORRENT_LABEL
|
||||
token = ''
|
||||
|
||||
if not host.startswith('http'):
|
||||
host = 'http://' + host
|
||||
|
||||
if host.endswith('/'):
|
||||
host = host[:-1]
|
||||
|
||||
if host.endswith('/gui'):
|
||||
host = host + '/'
|
||||
else:
|
||||
host = host + '/gui/'
|
||||
|
||||
# Retrieve session id
|
||||
auth = (username, password) if username and password else None
|
||||
token_request = request.request_response(host + 'token.html', auth=auth)
|
||||
|
||||
token = re.findall('<div.*?>(.*?)</', token_request.content)[0]
|
||||
guid = token_request.cookies['GUID']
|
||||
|
||||
cookies = dict(GUID = guid)
|
||||
|
||||
if link.startswith("magnet") or link.startswith("http") or link.endswith(".torrent"):
|
||||
params = {'action':'add-url', 's':link, 'token':token}
|
||||
response = request.request_json(host, params=params, auth=auth, cookies=cookies)
|
||||
else:
|
||||
params = {'action':'add-file', 'token':token}
|
||||
files = {'torrent_file':{'music.torrent' : link}}
|
||||
response = request.request_json(host, method="post", params=params, files=files, auth=auth, cookies=cookies)
|
||||
if not response:
|
||||
logger.error("Error sending torrent to uTorrent")
|
||||
return
|
||||
|
||||
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(link.content)["info"]
|
||||
tor_hash = sha1(bencode(info)).hexdigest()
|
||||
|
||||
params = {'action':'setprops', 'hash':tor_hash,'s':'label', 'v':label, 'token':token}
|
||||
response = request.request_json(host, params=params, auth=auth, cookies=cookies)
|
||||
if not response:
|
||||
logger.error("Error setting torrent label in uTorrent")
|
||||
return
|
||||
|
||||
# folder info can probably be cleaned up with getprops
|
||||
folder = None
|
||||
|
||||
params = {'list':'1', 'token':token}
|
||||
response = request.request_json(host, params=params, auth=auth, cookies=cookies)
|
||||
if not response:
|
||||
logger.error("Error getting torrent information from uTorrent")
|
||||
return
|
||||
|
||||
for torrent in response['torrents']:
|
||||
folder = os.path.basename(torrent[26])
|
||||
|
||||
return folder
|
||||
@@ -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)
|
||||
|
||||
+65
-10
@@ -163,7 +163,7 @@ class WebInterface(object):
|
||||
raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID)
|
||||
getExtras.exposed = True
|
||||
|
||||
def removeExtras(self, ArtistID):
|
||||
def removeExtras(self, ArtistID, ArtistName):
|
||||
myDB = db.DBConnection()
|
||||
controlValueDict = {'ArtistID': ArtistID}
|
||||
newValueDict = {'IncludeExtras': 0}
|
||||
@@ -174,7 +174,8 @@ class WebInterface(object):
|
||||
myDB.action('DELETE from albums WHERE ArtistID=? AND AlbumID=?', [ArtistID, album['AlbumID']])
|
||||
myDB.action('DELETE from allalbums WHERE ArtistID=? AND AlbumID=?', [ArtistID, album['AlbumID']])
|
||||
myDB.action('DELETE from alltracks WHERE ArtistID=? AND AlbumID=?', [ArtistID, album['AlbumID']])
|
||||
myDB.action('DELETE from releases WHERE ReleaseGroupID=?', album['AlbumID'])
|
||||
myDB.action('DELETE from releases WHERE ReleaseGroupID=?', [album['AlbumID']])
|
||||
importer.finalize_update(ArtistID, ArtistName)
|
||||
raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID)
|
||||
removeExtras.exposed = True
|
||||
|
||||
@@ -268,6 +269,11 @@ class WebInterface(object):
|
||||
searcher.searchforalbum(mbid, new=True)
|
||||
if action == 'WantedLossless':
|
||||
searcher.searchforalbum(mbid, lossless=True)
|
||||
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:
|
||||
@@ -629,8 +635,8 @@ class WebInterface(object):
|
||||
for rgid in rgids:
|
||||
myDB.action('DELETE from releases WHERE ReleaseGroupID=?', [rgid['ReleaseGroupID']])
|
||||
|
||||
myDB.action('DELETE from allalbums WHERE AlbumID=?', [AlbumID])
|
||||
myDB.action('DELETE from alltracks WHERE AlbumID=?', [AlbumID])
|
||||
myDB.action('DELETE from allalbums WHERE ArtistID=?', [ArtistID])
|
||||
myDB.action('DELETE from alltracks WHERE ArtistID=?', [ArtistID])
|
||||
myDB.action('INSERT OR REPLACE into blacklist VALUES (?)', [ArtistID])
|
||||
elif action == 'pause':
|
||||
controlValueDict = {'ArtistID': ArtistID}
|
||||
@@ -724,6 +730,11 @@ class WebInterface(object):
|
||||
return serve_template(templatename="logs.html", title="Log", lineList=headphones.LOG_LIST)
|
||||
logs.exposed = True
|
||||
|
||||
def clearLogs(self):
|
||||
headphones.LOG_LIST = []
|
||||
logger.info("Web logs cleared")
|
||||
raise cherrypy.HTTPRedirect("logs")
|
||||
clearLogs.exposed = True
|
||||
|
||||
def getLog(self,iDisplayStart=0,iDisplayLength=100,iSortCol_0=0,sSortDir_0="desc",sSearch="",**kwargs):
|
||||
|
||||
@@ -843,6 +854,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:
|
||||
@@ -924,6 +945,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),
|
||||
@@ -1059,6 +1081,12 @@ class WebInterface(object):
|
||||
"pushbullet_deviceid": headphones.PUSHBULLET_DEVICEID,
|
||||
"twitter_enabled": checked(headphones.TWITTER_ENABLED),
|
||||
"twitter_onsnatch": checked(headphones.TWITTER_ONSNATCH),
|
||||
"osx_notify_enabled": checked(headphones.OSX_NOTIFY_ENABLED),
|
||||
"osx_notify_onsnatch": checked(headphones.OSX_NOTIFY_ONSNATCH),
|
||||
"osx_notify_app": headphones.OSX_NOTIFY_APP,
|
||||
"boxcar_enabled": checked(headphones.BOXCAR_ENABLED),
|
||||
"boxcar_onsnatch": checked(headphones.BOXCAR_ONSNATCH),
|
||||
"boxcar_token": headphones.BOXCAR_TOKEN,
|
||||
"mirror_list": headphones.MIRRORLIST,
|
||||
"mirror": headphones.MIRROR,
|
||||
"customhost": headphones.CUSTOMHOST,
|
||||
@@ -1072,7 +1100,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
|
||||
@@ -1095,7 +1124,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,
|
||||
@@ -1105,10 +1134,11 @@ class WebInterface(object):
|
||||
bitrate=None, samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0,
|
||||
delete_lossless_files=0, growl_enabled=0, growl_onsnatch=0, growl_host=None, growl_password=None, 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, 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, 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):
|
||||
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, mpc_enabled=False, **kwargs ):
|
||||
|
||||
headphones.HTTP_HOST = http_host
|
||||
headphones.HTTP_PORT = http_port
|
||||
@@ -1140,6 +1170,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
|
||||
@@ -1268,6 +1299,17 @@ class WebInterface(object):
|
||||
headphones.SONGKICK_FILTER_ENABLED = songkick_filter_enabled
|
||||
headphones.TWITTER_ENABLED = twitter_enabled
|
||||
headphones.TWITTER_ONSNATCH = twitter_onsnatch
|
||||
|
||||
headphones.OSX_NOTIFY_ENABLED = osx_notify_enabled
|
||||
headphones.OSX_NOTIFY_ONSNATCH = osx_notify_onsnatch
|
||||
headphones.OSX_NOTIFY_APP = osx_notify_app
|
||||
|
||||
headphones.BOXCAR_ENABLED = boxcar_enabled
|
||||
headphones.BOXCAR_ONSNATCH = boxcar_onsnatch
|
||||
headphones.BOXCAR_TOKEN = boxcar_token
|
||||
|
||||
headphones.MPC_ENABLED = mpc_enabled
|
||||
|
||||
headphones.MIRROR = mirror
|
||||
headphones.CUSTOMHOST = customhost
|
||||
headphones.CUSTOMPORT = customport
|
||||
@@ -1435,6 +1477,19 @@ class WebInterface(object):
|
||||
return "Error sending tweet"
|
||||
testTwitter.exposed = True
|
||||
|
||||
def osxnotifyregister(self, app):
|
||||
cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
|
||||
from lib.osxnotify import registerapp as osxnotify
|
||||
result, msg = osxnotify.registerapp(app)
|
||||
if result:
|
||||
osx_notify = notifiers.OSX_NOTIFY()
|
||||
osx_notify.notify('Registered', result, 'Success :-)')
|
||||
logger.info('Registered %s, to re-register a different app, delete this app first' % result)
|
||||
else:
|
||||
logger.warn(msg)
|
||||
return msg
|
||||
osxnotifyregister.exposed = True
|
||||
|
||||
class Artwork(object):
|
||||
def index(self):
|
||||
return "Artwork"
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
# This particular version has been slightly modified to work with headphones
|
||||
# https://github.com/rembo10/headphones
|
||||
import os
|
||||
|
||||
__version__ = '1.3.4'
|
||||
__author__ = 'Adrian Sampson <adrian@radbox.org>'
|
||||
@@ -23,4 +24,4 @@ from beets.util import confit
|
||||
|
||||
Library = beets.library.Library
|
||||
|
||||
config = confit.LazyConfig('beets', __name__)
|
||||
config = confit.LazyConfig(os.path.dirname(__file__), __name__)
|
||||
|
||||
Executable
Executable
BIN
Binary file not shown.
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import shutil
|
||||
import os
|
||||
import stat
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
def registerapp(app):
|
||||
|
||||
# don't do any of this unless >= 10.8
|
||||
v, _, _ = platform.mac_ver()
|
||||
v = float('.'.join(v.split('.')[:2]))
|
||||
if v < 10.8:
|
||||
return None, 'Registering requires OS X version >= 10.8'
|
||||
|
||||
app_path = None
|
||||
|
||||
# check app bundle doesn't already exist
|
||||
app_path = subprocess.check_output(['/usr/bin/mdfind', 'kMDItemCFBundleIdentifier == "ade.headphones.osxnotify"']).strip()
|
||||
if app_path:
|
||||
return app_path, 'App previously registered'
|
||||
|
||||
# check app doesn't already exist
|
||||
app = app.strip()
|
||||
if not app:
|
||||
return None, 'Path/Application not entered'
|
||||
if os.path.splitext(app)[1] == ".app":
|
||||
app_path = app
|
||||
else:
|
||||
app_path = app + '.app'
|
||||
if os.path.exists(app_path):
|
||||
return None, 'App %s already exists, choose a different name' % app_path
|
||||
|
||||
# generate app
|
||||
try:
|
||||
os.mkdir(app_path)
|
||||
os.mkdir(app_path + "/Contents")
|
||||
os.mkdir(app_path + "/Contents/MacOS")
|
||||
os.mkdir(app_path + "/Contents/Resources")
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "appIcon.icns"), app_path + "/Contents/Resources/")
|
||||
|
||||
version = "1.0.0"
|
||||
bundleName = "OSXNotify"
|
||||
bundleIdentifier = "ade.headphones.osxnotify"
|
||||
|
||||
f = open(app_path + "/Contents/Info.plist", "w")
|
||||
f.write("""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>main.py</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>%s</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>appIcon.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>%s</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>%s</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>%s</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>%s</string>
|
||||
<key>NSAppleScriptEnabled</key>
|
||||
<string>YES</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
""" % (bundleName + " " + version, bundleIdentifier, bundleName, bundleName + " " + version, version))
|
||||
f.close()
|
||||
|
||||
f = open(app_path + "/Contents/PkgInfo", "w")
|
||||
f.write("APPL????")
|
||||
f.close()
|
||||
|
||||
f = open(app_path + "/Contents/MacOS/main.py", "w")
|
||||
f.write("""#!/usr/bin/python
|
||||
|
||||
objc = None
|
||||
|
||||
def swizzle(cls, SEL, func):
|
||||
old_IMP = cls.instanceMethodForSelector_(SEL)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
return func(self, old_IMP, *args, **kwargs)
|
||||
new_IMP = objc.selector(wrapper, selector=old_IMP.selector,
|
||||
signature=old_IMP.signature)
|
||||
objc.classAddMethod(cls, SEL, new_IMP)
|
||||
|
||||
def notify(title, subtitle=None, text=None, sound=True):
|
||||
global objc
|
||||
objc = __import__("objc")
|
||||
swizzle(objc.lookUpClass('NSBundle'),
|
||||
b'bundleIdentifier',
|
||||
swizzled_bundleIdentifier)
|
||||
NSUserNotification = objc.lookUpClass('NSUserNotification')
|
||||
NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')
|
||||
NSAutoreleasePool = objc.lookUpClass('NSAutoreleasePool')
|
||||
pool = NSAutoreleasePool.alloc().init()
|
||||
notification = NSUserNotification.alloc().init()
|
||||
notification.setTitle_(title)
|
||||
notification.setSubtitle_(subtitle)
|
||||
notification.setInformativeText_(text)
|
||||
notification.setSoundName_("NSUserNotificationDefaultSoundName")
|
||||
notification_center = NSUserNotificationCenter.defaultUserNotificationCenter()
|
||||
notification_center.deliverNotification_(notification)
|
||||
del pool
|
||||
|
||||
def swizzled_bundleIdentifier(self, original):
|
||||
return 'ade.headphones.osxnotify'
|
||||
|
||||
if __name__ == '__main__':
|
||||
notify('Half Man Half Biscuit', 'Back in the DHSS', '99% Of Gargoyles Look Like Bob Todd')
|
||||
""")
|
||||
f.close()
|
||||
|
||||
oldmode = os.stat(app_path + "/Contents/MacOS/main.py").st_mode
|
||||
os.chmod(app_path + "/Contents/MacOS/main.py", oldmode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
|
||||
return app_path, 'App registered'
|
||||
|
||||
except Exception, e:
|
||||
return None, 'Error creating App %s. %s' % (app_path, e)
|
||||
Reference in New Issue
Block a user