forked from openlp/openlp
trunk
This commit is contained in:
commit
663fef79bd
|
@ -12,7 +12,7 @@
|
|||
(function ( root, doc, factory ) {
|
||||
if ( typeof define === "function" && define.amd ) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define( [ "jquery" ], function ( $ ) {
|
||||
define( [ "jquery" ], function ($ ) {
|
||||
factory( $, root, doc );
|
||||
return $.mobile;
|
||||
});
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -18,11 +18,11 @@
|
|||
******************************************************************************/
|
||||
|
||||
.ui-icon-blank {
|
||||
background-image: url(images/ui-icon-blank.png);
|
||||
background-image: url(../images/ui-icon-blank.png);
|
||||
}
|
||||
|
||||
.ui-icon-unblank {
|
||||
background-image: url(images/ui-icon-unblank.png);
|
||||
background-image: url(../images/ui-icon-unblank.png);
|
||||
}
|
||||
|
||||
/* Overwrite style from jquery-mobile.min.css */
|
|
@ -24,12 +24,12 @@
|
|||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1" />
|
||||
<title>${app_title}</title>
|
||||
<link rel="stylesheet" href="/files/jquery.mobile.min.css" />
|
||||
<link rel="stylesheet" href="/files/openlp.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/files/images/favicon.ico">
|
||||
<script type="text/javascript" src="/files/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/files/openlp.js"></script>
|
||||
<script type="text/javascript" src="/files/jquery.mobile.min.js"></script>
|
||||
<link rel="stylesheet" href="/assets/jquery.mobile.min.css" />
|
||||
<link rel="stylesheet" href="/css/openlp.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/js/openlp.js"></script>
|
||||
<script type="text/javascript" src="/assets/jquery.mobile.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
translationStrings = {
|
||||
"go_live": "${go_live}",
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -23,10 +23,10 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>${live_title}</title>
|
||||
<link rel="stylesheet" href="/files/main.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/files/images/favicon.ico">
|
||||
<script type="text/javascript" src="/files/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/files/main.js"></script>
|
||||
<link rel="stylesheet" href="/css/main.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/js/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<img id="image" class="size"/>
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/******************************************************************************
|
||||
* OpenLP - Open Source Lyrics Projection *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2008-2016 OpenLP Developers *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* This program is free software; you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU General Public License as published by the Free *
|
||||
* Software Foundation; version 2 of the License. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT *
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
|
||||
* more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License along *
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59 *
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
||||
******************************************************************************/
|
||||
body {
|
||||
background-color: black;
|
||||
font-family: 'Arial Narrow', 'Avenir Next Condensed', sans-serif-condensed, Arial, sans-serif;
|
||||
font-size: 4.4vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#currentslide {
|
||||
font-size: 100%;
|
||||
color: lightgray;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
#nextslide {
|
||||
font-size: 100%;
|
||||
color: gray;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
#right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#clock {
|
||||
font-size: 75%;
|
||||
color: yellow;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#notes {
|
||||
font-size: 90%;
|
||||
color: salmon;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#controls {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#chords {
|
||||
font-size: 50%;
|
||||
color: gray;
|
||||
background-color: gray;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#header {
|
||||
height: 1.4em;
|
||||
}
|
||||
|
||||
#transpose,
|
||||
#transposevalue,
|
||||
#capodisplay {
|
||||
display: inline-block;
|
||||
font-size: 75%;
|
||||
color: gray;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#header .button,
|
||||
#plus,
|
||||
#minus {
|
||||
display: inline-block;
|
||||
width: 3vw;
|
||||
line-height: 3vw;
|
||||
vertical-align: middle;
|
||||
color: white;
|
||||
background-color: gray;
|
||||
font-size: 75%;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#verseorder {
|
||||
font-size: 75%;
|
||||
color: green;
|
||||
text-align: left;
|
||||
line-height: 1.5;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.currenttag {
|
||||
color: lightgreen;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.chordline {
|
||||
line-height: 2.0;
|
||||
}
|
||||
|
||||
.chordline1 {
|
||||
line-height: 1.0
|
||||
}
|
||||
|
||||
.chordline span.chord span {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chordline span.chord span strong {
|
||||
position: absolute;
|
||||
top: -1em;
|
||||
left: 0;
|
||||
font: 500 75% 'Arial Narrow', 'Avenir Next Condensed', sans-serif-condensed, Arial, sans-serif;
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
.nextslide .chordline span.chord span strong {
|
||||
color: gray;
|
||||
}
|
|
@ -23,10 +23,10 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>${stage_title}</title>
|
||||
<link rel="stylesheet" href="/files/stage.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/files/images/favicon.ico">
|
||||
<script type="text/javascript" src="/files/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/files/stage.js"></script>
|
||||
<link rel="stylesheet" href="/css/stage.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/js/stage.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<input type="hidden" id="next-text" value="${next}" />
|
||||
|
|
|
@ -0,0 +1,293 @@
|
|||
/******************************************************************************
|
||||
* OpenLP - Open Source Lyrics Projection *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* Copyright (c) 2008-2016 OpenLP Developers *
|
||||
* --------------------------------------------------------------------------- *
|
||||
* This program is free software; you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU General Public License as published by the Free *
|
||||
* Software Foundation; version 2 of the License. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT *
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
|
||||
* more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License along *
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59 *
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
||||
******************************************************************************/
|
||||
var lastChord;
|
||||
|
||||
function getTransposeValue(songId) {
|
||||
if (localStorage.getItem(songId + '_transposeValue')) {return localStorage.getItem(songId + '_transposeValue');}
|
||||
else {return 0;}
|
||||
}
|
||||
|
||||
function storeTransposeValue(songId,transposeValueToSet) {
|
||||
localStorage.setItem(songId + '_transposeValue', transposeValueToSet);
|
||||
}
|
||||
|
||||
function transposeChord(chord, transposeValue) {
|
||||
var chordSplit = chord.replace('♭', 'b').split(/[\/\(\)]/), transposedChord = '', note, notenumber, rest, currentChord,
|
||||
notesSharp = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','H'],
|
||||
notesFlat = ['C','Db','D','Eb','Fb','F','Gb','G','Ab','A','B','H'],
|
||||
notesPreferred = ['b','#','#','#','#','#','#','#','#','#','#','#'];
|
||||
chordNotes = Array();
|
||||
for (i = 0; i <= chordSplit.length - 1; i++) {
|
||||
if (i > 0) {
|
||||
transposedChord += '/';
|
||||
}
|
||||
currentchord = chordSplit[i];
|
||||
if (currentchord.charAt(0) === '(') {
|
||||
transposedChord += '(';
|
||||
if (currentchord.length > 1) {
|
||||
currentchord = currentchord.substr(1);
|
||||
} else {
|
||||
currentchord = "";
|
||||
}
|
||||
}
|
||||
if (currentchord.length > 0) {
|
||||
if (currentchord.length > 1) {
|
||||
if ('#b'.indexOf(currentchord.charAt(1)) === -1) {
|
||||
note = currentchord.substr(0, 1);
|
||||
rest = currentchord.substr(1);
|
||||
} else {
|
||||
note = currentchord.substr(0, 2);
|
||||
rest = currentchord.substr(2);
|
||||
}
|
||||
} else {
|
||||
note = currentchord;
|
||||
rest = "";
|
||||
}
|
||||
notenumber = (notesSharp.indexOf(note) === -1?notesFlat.indexOf(note):notesSharp.indexOf(note));
|
||||
notenumber -= parseInt(transposeValue);
|
||||
while (notenumber > 11) {notenumber -= 12;}
|
||||
while (notenumber < 0) {notenumber += 12;}
|
||||
if (i === 0) {
|
||||
currentChord = notesPreferred[notenumber] === '#' ? notesSharp[notenumber] : notesFlat[notenumber];
|
||||
lastChord = currentChord;
|
||||
}else {
|
||||
currentChord = notesSharp.indexOf(lastChord) === -1 ? notesFlat[notenumber] : notesSharp[notenumber];
|
||||
}
|
||||
if(!(notesFlat.indexOf(note)===-1 && notesSharp.indexOf(note)===-1)) transposedChord += currentChord + rest; else transposedChord += note + rest; //note+rest;
|
||||
//transposedChord += currentChord + rest;
|
||||
}
|
||||
}
|
||||
return transposedChord;
|
||||
}
|
||||
|
||||
var OpenLPChordOverflowFillCount = 0;
|
||||
window.OpenLP = {
|
||||
showchords:true,
|
||||
loadService: function (event) {
|
||||
$.getJSON(
|
||||
"/api/service/list",
|
||||
function (data, status) {
|
||||
OpenLP.nextSong = "";
|
||||
$("#notes").html("");
|
||||
for (idx in data.results.items) {
|
||||
idx = parseInt(idx, 10);
|
||||
if (data.results.items[idx]["selected"]) {
|
||||
$("#notes").html(data.results.items[idx]["notes"].replace(/\n/g, "<br />"));
|
||||
$("#songtitle").html(data.results.items[idx]["title"].replace(/\n/g, "<br />"));
|
||||
if (data.results.items.length > idx + 1) {
|
||||
OpenLP.nextSong = data.results.items[idx + 1]["title"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
OpenLP.updateSlide();
|
||||
}
|
||||
);
|
||||
},
|
||||
loadSlides: function (event) {
|
||||
$.getJSON(
|
||||
"/api/controller/live/text",
|
||||
function (data, status) {
|
||||
OpenLP.currentSlides = data.results.slides;
|
||||
$('#transposevalue').text(getTransposeValue(OpenLP.currentSlides[0].text.split("\n")[0]));
|
||||
OpenLP.currentSlide = 0;
|
||||
OpenLP.currentTags = Array();
|
||||
var div = $("#verseorder");
|
||||
div.html("");
|
||||
var tag = "";
|
||||
var tags = 0;
|
||||
var lastChange = 0;
|
||||
$.each(data.results.slides, function(idx, slide) {
|
||||
var prevtag = tag;
|
||||
tag = slide["tag"];
|
||||
if (tag != prevtag) {
|
||||
// If the tag has changed, add new one to the list
|
||||
lastChange = idx;
|
||||
tags = tags + 1;
|
||||
div.append(" <span>");
|
||||
$("#verseorder span").last().attr("id", "tag" + tags).text(tag);
|
||||
}
|
||||
else {
|
||||
if ((slide["text"] == data.results.slides[lastChange]["text"]) &&
|
||||
(data.results.slides.length > idx + (idx - lastChange))) {
|
||||
// If the tag hasn't changed, check to see if the same verse
|
||||
// has been repeated consecutively. Note the verse may have been
|
||||
// split over several slides, so search through. If so, repeat the tag.
|
||||
var match = true;
|
||||
for (var idx2 = 0; idx2 < idx - lastChange; idx2++) {
|
||||
if(data.results.slides[lastChange + idx2]["text"] != data.results.slides[idx + idx2]["text"]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
lastChange = idx;
|
||||
tags = tags + 1;
|
||||
div.append(" <span>");
|
||||
$("#verseorder span").last().attr("id", "tag" + tags).text(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
OpenLP.currentTags[idx] = tags;
|
||||
if (slide["selected"])
|
||||
OpenLP.currentSlide = idx;
|
||||
})
|
||||
OpenLP.loadService();
|
||||
}
|
||||
);
|
||||
},
|
||||
updateSlide: function() {
|
||||
// Show the current slide on top. Any trailing slides for the same verse
|
||||
// are shown too underneath in grey.
|
||||
// Then leave a blank line between following verses
|
||||
var transposeValue = getTransposeValue(OpenLP.currentSlides[0].text.split("\n")[0]),
|
||||
chordclass=/class="[a-z\s]*chord[a-z\s]*"\s*style="display:\s?none"/g,
|
||||
chordclassshow='class="chord" style="display:inline"',
|
||||
regchord=/<span class="chord" style="display:inline">([\(\w#b♭\+\*\d/\)-]+)<\/span>([\u0080-\uFFFF,\w]*)([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(<br>)?/g,
|
||||
replaceChords=function(mstr,$1,$2,$3,$4) {
|
||||
// regchord=/<span class="chord" style="display:inline">[\[{]([\(\w#b♭\+\*\d/\)-]+)[\]}]<\/span>([\u0080-\uFFFF,\w]*)([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(<br>)?/g,
|
||||
var v='', w='';
|
||||
var $1len = 0, $2len = 0, slimchars='fiíIÍjlĺľrtť.,;/ ()|"\'!:\\';
|
||||
$1 = transposeChord($1, transposeValue);
|
||||
for (var i = 0; i < $1.length; i++) if (slimchars.indexOf($1.charAt(i)) === -1) {$1len += 2;} else {$1len += 1;}
|
||||
for (var i = 0; i < $2.length; i++) if (slimchars.indexOf($2.charAt(i)) === -1) {$2len += 2;} else {$2len += 1;}
|
||||
for (var i = 0; i < $3.length; i++) if (slimchars.indexOf($2.charAt(i)) === -1) {$2len += 2;} else {$2len += 1;}
|
||||
if ($1len>=$2len && !$4) {
|
||||
if ($2.length){
|
||||
if (!$3.length) {
|
||||
for (c = 0; c < Math.ceil(($1len - $2len) / 2) + 1; c++) {w += '_';}
|
||||
} else {
|
||||
for (c = 0; c < $1len - $2len + 2; c++) {w += ' ';}
|
||||
}
|
||||
} else {
|
||||
if (!$3.length) {
|
||||
for (c = 0; c < Math.floor(($1len - $2len) / 2) + 1; c++) {w += '_';}
|
||||
} else {
|
||||
for (c = 0; c < $1len - $2len + 1; c++) {w += ' ';}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
if (!$2 && $3.charAt(0) == ' ') {for (c = 0; c < $1len; c++) {w += ' ';}}
|
||||
}
|
||||
return $.grep(['<span class="chord" style="display:inline"><span><strong>', $1, '</strong></span>', $2, w, $3, '</span>', $4], Boolean).join('');
|
||||
};
|
||||
$("#verseorder span").removeClass("currenttag");
|
||||
$("#tag" + OpenLP.currentTags[OpenLP.currentSlide]).addClass("currenttag");
|
||||
var slide = OpenLP.currentSlides[OpenLP.currentSlide];
|
||||
var text = "";
|
||||
// use title if available
|
||||
if (slide["title"]) {
|
||||
text = slide["title"];
|
||||
} else {
|
||||
text = slide["text"];
|
||||
if(OpenLP.showchords) {
|
||||
text = text.replace(chordclass,chordclassshow);
|
||||
text = text.replace(regchord, replaceChords);
|
||||
}
|
||||
}
|
||||
// use thumbnail if available
|
||||
if (slide["img"]) {
|
||||
text += "<br /><img src='" + slide["img"].replace("/thumbnails/", "/thumbnails320x240/") + "'><br />";
|
||||
}
|
||||
// use notes if available
|
||||
if (slide["slide_notes"]) {
|
||||
text += '<br />' + slide["slide_notes"];
|
||||
}
|
||||
text = text.replace(/\n/g, "<br />");
|
||||
$("#currentslide").html(text);
|
||||
text = "";
|
||||
if (OpenLP.currentSlide < OpenLP.currentSlides.length - 1) {
|
||||
for (var idx = OpenLP.currentSlide + 1; idx < OpenLP.currentSlides.length; idx++) {
|
||||
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
|
||||
text = text + "<p class=\"nextslide\">";
|
||||
if (OpenLP.currentSlides[idx]["title"]) {
|
||||
text = text + OpenLP.currentSlides[idx]["title"];
|
||||
} else {
|
||||
text = text + OpenLP.currentSlides[idx]["text"];
|
||||
if(OpenLP.showchords) {
|
||||
text = text.replace(chordclass,chordclassshow);
|
||||
text = text.replace(regchord, replaceChords);
|
||||
}
|
||||
}
|
||||
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
|
||||
text = text + "</p>";
|
||||
else
|
||||
text = text + "<br />";
|
||||
}
|
||||
text = text.replace(/\n/g, "<br />");
|
||||
$("#nextslide").html(text);
|
||||
}
|
||||
else {
|
||||
text = "<p class=\"nextslide\">" + $("#next-text").val() + ": " + OpenLP.nextSong + "</p>";
|
||||
$("#nextslide").html(text);
|
||||
}
|
||||
if(!OpenLP.showchords) $(".chordline").toggleClass('chordline1');
|
||||
},
|
||||
updateClock: function(data) {
|
||||
var div = $("#clock");
|
||||
var t = new Date();
|
||||
var h = t.getHours();
|
||||
if (data.results.twelve && h > 12)
|
||||
h = h - 12;
|
||||
if (h < 10) h = '0' + h + '';
|
||||
var m = t.getMinutes();
|
||||
if (m < 10)
|
||||
m = '0' + m + '';
|
||||
div.html(h + ":" + m);
|
||||
},
|
||||
pollServer: function () {
|
||||
$.getJSON(
|
||||
"/api/poll",
|
||||
function (data, status) {
|
||||
OpenLP.updateClock(data);
|
||||
if (OpenLP.currentItem != data.results.item || OpenLP.currentService != data.results.service) {
|
||||
OpenLP.currentItem = data.results.item;
|
||||
OpenLP.currentService = data.results.service;
|
||||
OpenLP.loadSlides();
|
||||
}
|
||||
else if (OpenLP.currentSlide != data.results.slide) {
|
||||
OpenLP.currentSlide = parseInt(data.results.slide, 10);
|
||||
OpenLP.updateSlide();
|
||||
}
|
||||
}
|
||||
);
|
||||
// $('span.chord').each(function(){this.style.display="inline"});
|
||||
}
|
||||
}
|
||||
$.ajaxSetup({ cache: false });
|
||||
setInterval("OpenLP.pollServer();", 500);
|
||||
OpenLP.pollServer();
|
||||
$(document).ready(function() {
|
||||
$('#transposeup').click(function(e) {
|
||||
$('#transposevalue').text(parseInt($('#transposevalue').text()) + 1);
|
||||
storeTransposeValue(OpenLP.currentSlides[0].text.split("\n")[0], $('#transposevalue').text());
|
||||
//alert(getTransposeValue(OpenLP.currentSlides[0].text.split("\n")[0]));
|
||||
//$('body').get(0).style.'font-size' = (parseFloat($('body').css('font-size')) + 0.1) + 'vw');
|
||||
OpenLP.loadSlides();
|
||||
});
|
||||
$('#transposedown').click(function(e) {
|
||||
$('#transposevalue').text(parseInt($('#transposevalue').text()) - 1);
|
||||
storeTransposeValue(OpenLP.currentSlides[0].text.split("\n")[0], $('#transposevalue').text());
|
||||
OpenLP.loadSlides();
|
||||
});
|
||||
$("#chords").click(function(){ OpenLP.showchords=OpenLP.showchords?false:true; OpenLP.updateSlide(); });
|
||||
$('#plus').click(function() { var fs=$('#currentslide').css('font-size').match(/\d+/); $('#currentslide').css("font-size",+fs+10+"px");$('#nextslide').css("font-size",+fs+10+"px"); } );
|
||||
$("#minus").click(function() {var fs=$('#currentslide').css('font-size').match(/\d+/); $('#currentslide').css("font-size",+fs-10+"px");$('#nextslide').css("font-size",+fs-10+"px"); } );
|
||||
$('body').hover(function(){ $('#controls').fadeIn(500);},function(){ $('#controls').fadeOut(500);});
|
||||
});
|
|
@ -146,12 +146,12 @@ class HttpRouter(RegistryProperties):
|
|||
self.auth = base64.b64encode(auth_code)
|
||||
except TypeError:
|
||||
self.auth = base64.b64encode(auth_code.encode()).decode()
|
||||
self.default_route = {'function': self.serve_file, 'secure': False}
|
||||
self.routes = [
|
||||
('^/$', {'function': self.serve_file, 'secure': False}),
|
||||
('^/(stage)$', {'function': self.serve_file, 'secure': False}),
|
||||
('^/(stage)/(.*)$', {'function': self.stages, 'secure': False}),
|
||||
('^/(main)$', {'function': self.serve_file, 'secure': False}),
|
||||
(r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}),
|
||||
(r'^/(\w+)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}),
|
||||
(r'^/api/poll$', {'function': self.poll, 'secure': False}),
|
||||
(r'^/main/poll$', {'function': self.main_poll, 'secure': False}),
|
||||
|
@ -221,6 +221,7 @@ class HttpRouter(RegistryProperties):
|
|||
self.request_data = None
|
||||
url_path_split = urlparse(url_path)
|
||||
url_query = parse_qs(url_path_split.query)
|
||||
# GET
|
||||
if 'data' in url_query.keys():
|
||||
self.request_data = url_query['data'][0]
|
||||
for route, func in self.routes:
|
||||
|
@ -231,7 +232,7 @@ class HttpRouter(RegistryProperties):
|
|||
for param in match.groups():
|
||||
args.append(param)
|
||||
return func, args
|
||||
return None, None
|
||||
return self.default_route, [url_path_split.path]
|
||||
|
||||
def set_cache_headers(self):
|
||||
self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
|
@ -404,6 +405,8 @@ class HttpRouter(RegistryProperties):
|
|||
file_name = 'stage.html'
|
||||
elif file_name == 'main':
|
||||
file_name = 'main.html'
|
||||
if file_name.startswith('/'):
|
||||
file_name = file_name[1:]
|
||||
path = os.path.normpath(os.path.join(self.html_dir, file_name))
|
||||
if not path.startswith(self.html_dir):
|
||||
return self.do_not_found()
|
||||
|
|
|
@ -78,6 +78,13 @@ if is_win():
|
|||
HAS_WORSHIPCENTERPRO = True
|
||||
except ImportError:
|
||||
log.exception('Error importing %s', 'WorshipCenterProImport')
|
||||
HAS_OPSPRO = False
|
||||
if is_win():
|
||||
try:
|
||||
from .importers.opspro import OPSProImport
|
||||
HAS_OPSPRO = True
|
||||
except ImportError:
|
||||
log.exception('Error importing %s', 'OPSProImport')
|
||||
|
||||
|
||||
class SongFormatSelect(object):
|
||||
|
@ -156,20 +163,21 @@ class SongFormat(object):
|
|||
Lyrix = 9
|
||||
MediaShout = 10
|
||||
OpenSong = 11
|
||||
PowerPraise = 12
|
||||
PowerSong = 13
|
||||
PresentationManager = 14
|
||||
ProPresenter = 15
|
||||
SongBeamer = 16
|
||||
SongPro = 17
|
||||
SongShowPlus = 18
|
||||
SongsOfFellowship = 19
|
||||
SundayPlus = 20
|
||||
VideoPsalm = 21
|
||||
WordsOfWorship = 22
|
||||
WorshipAssistant = 23
|
||||
WorshipCenterPro = 24
|
||||
ZionWorx = 25
|
||||
OPSPro = 12
|
||||
PowerPraise = 13
|
||||
PowerSong = 14
|
||||
PresentationManager = 15
|
||||
ProPresenter = 16
|
||||
SongBeamer = 17
|
||||
SongPro = 18
|
||||
SongShowPlus = 19
|
||||
SongsOfFellowship = 20
|
||||
SundayPlus = 21
|
||||
VideoPsalm = 22
|
||||
WordsOfWorship = 23
|
||||
WorshipAssistant = 24
|
||||
WorshipCenterPro = 25
|
||||
ZionWorx = 26
|
||||
|
||||
# Set optional attribute defaults
|
||||
__defaults__ = {
|
||||
|
@ -272,6 +280,17 @@ class SongFormat(object):
|
|||
'name': WizardStrings.OS,
|
||||
'prefix': 'openSong'
|
||||
},
|
||||
OPSPro: {
|
||||
'name': 'OPS Pro',
|
||||
'prefix': 'OPSPro',
|
||||
'canDisable': True,
|
||||
'selectMode': SongFormatSelect.SingleFile,
|
||||
'filter': '%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', 'OPS Pro database'),
|
||||
'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
|
||||
'The OPS Pro importer is only supported on Windows. It has been '
|
||||
'disabled due to a missing Python module. If you want to use this '
|
||||
'importer, you will need to install the "pyodbc" module.')
|
||||
},
|
||||
PowerPraise: {
|
||||
'class': PowerPraiseImport,
|
||||
'name': 'PowerPraise',
|
||||
|
@ -403,6 +422,7 @@ class SongFormat(object):
|
|||
SongFormat.Lyrix,
|
||||
SongFormat.MediaShout,
|
||||
SongFormat.OpenSong,
|
||||
SongFormat.OPSPro,
|
||||
SongFormat.PowerPraise,
|
||||
SongFormat.PowerSong,
|
||||
SongFormat.PresentationManager,
|
||||
|
@ -416,7 +436,7 @@ class SongFormat(object):
|
|||
SongFormat.WordsOfWorship,
|
||||
SongFormat.WorshipAssistant,
|
||||
SongFormat.WorshipCenterPro,
|
||||
SongFormat.ZionWorx,
|
||||
SongFormat.ZionWorx
|
||||
])
|
||||
|
||||
@staticmethod
|
||||
|
@ -465,6 +485,9 @@ if HAS_MEDIASHOUT:
|
|||
SongFormat.set(SongFormat.WorshipCenterPro, 'availability', HAS_WORSHIPCENTERPRO)
|
||||
if HAS_WORSHIPCENTERPRO:
|
||||
SongFormat.set(SongFormat.WorshipCenterPro, 'class', WorshipCenterProImport)
|
||||
SongFormat.set(SongFormat.OPSPro, 'availability', HAS_OPSPRO)
|
||||
if HAS_OPSPRO:
|
||||
SongFormat.set(SongFormat.OPSPro, 'class', OPSProImport)
|
||||
|
||||
|
||||
__all__ = ['SongFormat', 'SongFormatSelect']
|
||||
|
|
|
@ -0,0 +1,260 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2016 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
The :mod:`opspro` module provides the functionality for importing
|
||||
a OPS Pro database into the OpenLP database.
|
||||
"""
|
||||
import logging
|
||||
import re
|
||||
import pyodbc
|
||||
import struct
|
||||
|
||||
from openlp.core.common import translate
|
||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OPSProImport(SongImport):
|
||||
"""
|
||||
The :class:`OPSProImport` class provides the ability to import the
|
||||
WorshipCenter Pro Access Database
|
||||
"""
|
||||
def __init__(self, manager, **kwargs):
|
||||
"""
|
||||
Initialise the WorshipCenter Pro importer.
|
||||
"""
|
||||
super(OPSProImport, self).__init__(manager, **kwargs)
|
||||
|
||||
def do_import(self):
|
||||
"""
|
||||
Receive a single file to import.
|
||||
"""
|
||||
password = self.extract_mdb_password()
|
||||
try:
|
||||
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s;PWD=%s' % (self.import_source,
|
||||
password))
|
||||
except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e:
|
||||
log.warning('Unable to connect the OPS Pro database %s. %s', self.import_source, str(e))
|
||||
# Unfortunately no specific exception type
|
||||
self.log_error(self.import_source, translate('SongsPlugin.OPSProImport',
|
||||
'Unable to connect the OPS Pro database.'))
|
||||
return
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT Song.ID, SongNumber, SongBookName, Title, CopyrightText, Version, Origin FROM Song '
|
||||
'LEFT JOIN SongBook ON Song.SongBookID = SongBook.ID ORDER BY Title')
|
||||
songs = cursor.fetchall()
|
||||
self.import_wizard.progress_bar.setMaximum(len(songs))
|
||||
for song in songs:
|
||||
if self.stop_import_flag:
|
||||
break
|
||||
# Type means: 0=Original, 1=Projection, 2=Own
|
||||
cursor.execute('SELECT Lyrics, Type, IsDualLanguage FROM Lyrics WHERE SongID = %d AND Type < 2 '
|
||||
'ORDER BY Type DESC' % song.ID)
|
||||
lyrics = cursor.fetchone()
|
||||
cursor.execute('SELECT CategoryName FROM Category INNER JOIN SongCategory '
|
||||
'ON Category.ID = SongCategory.CategoryID WHERE SongCategory.SongID = %d '
|
||||
'ORDER BY CategoryName' % song.ID)
|
||||
topics = cursor.fetchall()
|
||||
try:
|
||||
self.process_song(song, lyrics, topics)
|
||||
except Exception as e:
|
||||
self.log_error(self.import_source,
|
||||
translate('SongsPlugin.OPSProImport', '"%s" could not be imported. %s')
|
||||
% (song.Title, e))
|
||||
|
||||
def process_song(self, song, lyrics, topics):
|
||||
"""
|
||||
Create the song, i.e. title, verse etc.
|
||||
|
||||
The OPS Pro format is a fairly simple text format using tags and anchors/labels. Linebreaks are \r\n.
|
||||
Double linebreaks are slide dividers. OPS Pro support dual language using tags.
|
||||
Tags are in [], see the liste below:
|
||||
[join] are used to separate verses that should be keept on the same slide.
|
||||
[split] or [splits] can be used to split a verse over several slides, while still being the same verse
|
||||
Dual language tags:
|
||||
[trans off] or [vertaal uit] turns dual language mode off for the following text
|
||||
[trans on] or [vertaal aan] turns dual language mode on for the following text
|
||||
[taal a] means the following lines are language a
|
||||
[taal b] means the following lines are language b
|
||||
"""
|
||||
self.set_defaults()
|
||||
self.title = song.Title
|
||||
if song.CopyrightText:
|
||||
for line in song.CopyrightText.splitlines():
|
||||
if line.startswith('©') or line.lower().startswith('copyright'):
|
||||
self.add_copyright(line)
|
||||
else:
|
||||
self.parse_author(line)
|
||||
if song.Origin:
|
||||
self.comments = song.Origin
|
||||
if song.SongBookName:
|
||||
self.song_book_name = song.SongBookName
|
||||
if song.SongNumber:
|
||||
self.song_number = song.SongNumber
|
||||
for topic in topics:
|
||||
self.topics.append(topic.CategoryName)
|
||||
# Try to split lyrics based on various rules
|
||||
if lyrics:
|
||||
lyrics_text = lyrics.Lyrics
|
||||
verses = re.split('\r\n\s*?\r\n', lyrics_text)
|
||||
verse_tag_defs = {}
|
||||
verse_tag_texts = {}
|
||||
for verse_text in verses:
|
||||
if verse_text.strip() == '':
|
||||
continue
|
||||
verse_def = 'v'
|
||||
# Detect verse number
|
||||
verse_number = re.match('^(\d+)\r\n', verse_text)
|
||||
if verse_number:
|
||||
verse_text = re.sub('^\d+\r\n', '', verse_text)
|
||||
verse_def = 'v' + verse_number.group(1)
|
||||
# Detect verse tags
|
||||
elif re.match('^.+?\:\r\n', verse_text):
|
||||
tag_match = re.match('^(.+?)\:\r\n(.*)', verse_text, flags=re.DOTALL)
|
||||
tag = tag_match.group(1).lower()
|
||||
tag = tag.split(' ')[0]
|
||||
verse_text = tag_match.group(2)
|
||||
if 'refrein' in tag or 'chorus' in tag:
|
||||
verse_def = 'c'
|
||||
elif 'bridge' in tag:
|
||||
verse_def = 'b'
|
||||
verse_tag_defs[tag] = verse_def
|
||||
verse_tag_texts[tag] = verse_text
|
||||
# Detect tag reference
|
||||
elif re.match('^\(.*?\)$', verse_text):
|
||||
tag_match = re.match('^\((.*?)\)$', verse_text)
|
||||
tag = tag_match.group(1).lower()
|
||||
if tag in verse_tag_defs:
|
||||
verse_text = verse_tag_texts[tag]
|
||||
verse_def = verse_tag_defs[tag]
|
||||
# Detect end tag
|
||||
elif re.match('^\[slot\]\r\n', verse_text, re.IGNORECASE):
|
||||
verse_def = 'e'
|
||||
verse_text = re.sub('^\[slot\]\r\n', '', verse_text, flags=re.IGNORECASE)
|
||||
# Replace the join tag with line breaks
|
||||
verse_text = verse_text.replace('[join]', '')
|
||||
# Replace the split tag with line breaks and an optional split
|
||||
verse_text = re.sub('\[splits?\]', '\r\n[---]', verse_text)
|
||||
# Handle translations
|
||||
if lyrics.IsDualLanguage:
|
||||
verse_text = self.handle_translation(verse_text)
|
||||
# Remove comments
|
||||
verse_text = re.sub('\(.*?\)\r\n', '', verse_text, flags=re.IGNORECASE)
|
||||
self.add_verse(verse_text, verse_def)
|
||||
self.finish()
|
||||
|
||||
def handle_translation(self, verse_text):
|
||||
"""
|
||||
Replace OPS Pro translation tags with a {translation} tag
|
||||
|
||||
:param verse_text: the verse text
|
||||
:return: the verse text with replaced tags
|
||||
"""
|
||||
language = None
|
||||
translation = True
|
||||
translation_verse_text = ''
|
||||
start_tag = '{translation}'
|
||||
end_tag = '{/translation}'
|
||||
verse_text_lines = verse_text.splitlines()
|
||||
idx = 0
|
||||
while idx < len(verse_text_lines):
|
||||
# Detect if translation is turned on or off
|
||||
if verse_text_lines[idx] in ['[trans off]', '[vertaal uit]']:
|
||||
translation = False
|
||||
idx += 1
|
||||
elif verse_text_lines[idx] in ['[trans on]', '[vertaal aan]']:
|
||||
translation = True
|
||||
idx += 1
|
||||
elif verse_text_lines[idx] == '[taal a]':
|
||||
language = 'a'
|
||||
idx += 1
|
||||
elif verse_text_lines[idx] == '[taal b]':
|
||||
language = 'b'
|
||||
idx += 1
|
||||
if not idx < len(verse_text_lines):
|
||||
break
|
||||
# Handle the text based on whether translation is off or on
|
||||
if language:
|
||||
if language == 'b':
|
||||
translation_verse_text += start_tag
|
||||
while idx < len(verse_text_lines) and not verse_text_lines[idx].startswith('['):
|
||||
translation_verse_text += verse_text_lines[idx] + '\r\n'
|
||||
idx += 1
|
||||
if language == 'b':
|
||||
translation_verse_text += end_tag
|
||||
language = None
|
||||
elif translation:
|
||||
translation_verse_text += verse_text_lines[idx] + '\r\n'
|
||||
idx += 1
|
||||
if idx < len(verse_text_lines) and not verse_text_lines[idx].startswith('['):
|
||||
translation_verse_text += start_tag + verse_text_lines[idx] + end_tag + '\r\n'
|
||||
idx += 1
|
||||
else:
|
||||
translation_verse_text += verse_text_lines[idx] + '\r\n'
|
||||
idx += 1
|
||||
while idx < len(verse_text_lines) and not verse_text_lines[idx].startswith('['):
|
||||
translation_verse_text += verse_text_lines[idx] + '\r\n'
|
||||
idx += 1
|
||||
return translation_verse_text
|
||||
|
||||
def extract_mdb_password(self):
|
||||
"""
|
||||
Extract password from mdb. Based on code from
|
||||
http://tutorialsto.com/database/access/crack-access-*.-mdb-all-current-versions-of-the-password.html
|
||||
"""
|
||||
# The definition of 13 bytes as the source XOR Access2000. Encrypted with the corresponding signs are 0x13
|
||||
xor_pattern_2k = (0xa1, 0xec, 0x7a, 0x9c, 0xe1, 0x28, 0x34, 0x8a, 0x73, 0x7b, 0xd2, 0xdf, 0x50)
|
||||
# Access97 XOR of the source
|
||||
xor_pattern_97 = (0x86, 0xfb, 0xec, 0x37, 0x5d, 0x44, 0x9c, 0xfa, 0xc6, 0x5e, 0x28, 0xe6, 0x13)
|
||||
mdb = open(self.import_source, 'rb')
|
||||
mdb.seek(0x14)
|
||||
version = struct.unpack('B', mdb.read(1))[0]
|
||||
# Get encrypted logo
|
||||
mdb.seek(0x62)
|
||||
EncrypFlag = struct.unpack('B', mdb.read(1))[0]
|
||||
# Get encrypted password
|
||||
mdb.seek(0x42)
|
||||
encrypted_password = mdb.read(26)
|
||||
mdb.close()
|
||||
# "Decrypt" the password based on the version
|
||||
decrypted_password = ''
|
||||
if version < 0x01:
|
||||
# Access 97
|
||||
if int(encrypted_password[0] ^ xor_pattern_97[0]) == 0:
|
||||
# No password
|
||||
decrypted_password = ''
|
||||
else:
|
||||
for j in range(0, 12):
|
||||
decrypted_password = decrypted_password + chr(encrypted_password[j] ^ xor_pattern_97[j])
|
||||
else:
|
||||
# Access 2000 or 2002
|
||||
for j in range(0, 12):
|
||||
if j % 2 == 0:
|
||||
# Every byte with a different sign or encrypt. Encryption signs here for the 0x13
|
||||
t1 = chr(0x13 ^ EncrypFlag ^ encrypted_password[j * 2] ^ xor_pattern_2k[j])
|
||||
else:
|
||||
t1 = chr(encrypted_password[j * 2] ^ xor_pattern_2k[j])
|
||||
decrypted_password = decrypted_password + t1
|
||||
if ord(decrypted_password[1]) < 0x20 or ord(decrypted_password[1]) > 0x7e:
|
||||
decrypted_password = ''
|
||||
return decrypted_password
|
|
@ -371,7 +371,7 @@ class SongImport(QtCore.QObject):
|
|||
song_book = self.manager.get_object_filtered(Book, Book.name == self.song_book_name)
|
||||
if song_book is None:
|
||||
song_book = Book.populate(name=self.song_book_name, publisher=self.song_book_pub)
|
||||
song.book = song_book
|
||||
song.add_songbook_entry(song_book, song.song_number)
|
||||
for topic_text in self.topics:
|
||||
if not topic_text:
|
||||
continue
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2016 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
This module contains tests for the WorshipCenter Pro song importer.
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
from unittest import TestCase, SkipTest
|
||||
|
||||
if os.name != 'nt':
|
||||
raise SkipTest('Not Windows, skipping test')
|
||||
|
||||
from tests.functional import patch, MagicMock
|
||||
|
||||
from openlp.core.common import Registry
|
||||
from openlp.plugins.songs.lib.importers.opspro import OPSProImport
|
||||
|
||||
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'opsprosongs'))
|
||||
|
||||
|
||||
class TestOpsProSongImport(TestCase):
|
||||
"""
|
||||
Test the functions in the :mod:`opsproimport` module.
|
||||
"""
|
||||
def setUp(self):
|
||||
"""
|
||||
Create the registry
|
||||
"""
|
||||
Registry.create()
|
||||
|
||||
@patch('openlp.plugins.songs.lib.importers.opspro.SongImport')
|
||||
def create_importer_test(self, mocked_songimport):
|
||||
"""
|
||||
Test creating an instance of the OPS Pro file importer
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = OPSProImport(mocked_manager, filenames=[])
|
||||
|
||||
# THEN: The importer object should not be None
|
||||
self.assertIsNotNone(importer, 'Import should not be none')
|
||||
|
||||
@patch('openlp.plugins.songs.lib.importers.opspro.SongImport')
|
||||
def detect_chorus_test(self, mocked_songimport):
|
||||
"""
|
||||
Test importing lyrics with a chorus in OPS Pro
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||
mocked_manager = MagicMock()
|
||||
importer = OPSProImport(mocked_manager, filenames=[])
|
||||
importer.finish = MagicMock()
|
||||
song, lyrics = self._build_test_data('you are so faithfull.txt', False)
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer.process_song(song, lyrics, [])
|
||||
|
||||
# THEN: The imported data should look like expected
|
||||
result_file = open(os.path.join(TEST_PATH, 'You are so faithful.json'), 'rb')
|
||||
result_data = json.loads(result_file.read().decode())
|
||||
self.assertListEqual(importer.verses, self._get_data(result_data, 'verses'))
|
||||
self.assertListEqual(importer.verse_order_list_generated, self._get_data(result_data, 'verse_order_list'))
|
||||
|
||||
@patch('openlp.plugins.songs.lib.importers.opspro.SongImport')
|
||||
def join_and_split_test(self, mocked_songimport):
|
||||
"""
|
||||
Test importing lyrics with a split and join tags works in OPS Pro
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||
mocked_manager = MagicMock()
|
||||
importer = OPSProImport(mocked_manager, filenames=[])
|
||||
importer.finish = MagicMock()
|
||||
song, lyrics = self._build_test_data('amazing grace.txt', False)
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer.process_song(song, lyrics, [])
|
||||
|
||||
# THEN: The imported data should look like expected
|
||||
result_file = open(os.path.join(TEST_PATH, 'Amazing Grace.json'), 'rb')
|
||||
result_data = json.loads(result_file.read().decode())
|
||||
self.assertListEqual(importer.verses, self._get_data(result_data, 'verses'))
|
||||
self.assertListEqual(importer.verse_order_list_generated, self._get_data(result_data, 'verse_order_list'))
|
||||
|
||||
@patch('openlp.plugins.songs.lib.importers.opspro.SongImport')
|
||||
def trans_off_tag_test(self, mocked_songimport):
|
||||
"""
|
||||
Test importing lyrics with a split and join and translations tags works in OPS Pro
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||
mocked_manager = MagicMock()
|
||||
importer = OPSProImport(mocked_manager, filenames=[])
|
||||
importer.finish = MagicMock()
|
||||
song, lyrics = self._build_test_data('amazing grace2.txt', True)
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer.process_song(song, lyrics, [])
|
||||
|
||||
# THEN: The imported data should look like expected
|
||||
result_file = open(os.path.join(TEST_PATH, 'Amazing Grace.json'), 'rb')
|
||||
result_data = json.loads(result_file.read().decode())
|
||||
self.assertListEqual(importer.verses, self._get_data(result_data, 'verses'))
|
||||
self.assertListEqual(importer.verse_order_list_generated, self._get_data(result_data, 'verse_order_list'))
|
||||
|
||||
@patch('openlp.plugins.songs.lib.importers.opspro.SongImport')
|
||||
def trans_tag_test(self, mocked_songimport):
|
||||
"""
|
||||
Test importing lyrics with various translations tags works in OPS Pro
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||
mocked_manager = MagicMock()
|
||||
importer = OPSProImport(mocked_manager, filenames=[])
|
||||
importer.finish = MagicMock()
|
||||
song, lyrics = self._build_test_data('amazing grace3.txt', True)
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer.process_song(song, lyrics, [])
|
||||
|
||||
# THEN: The imported data should look like expected
|
||||
result_file = open(os.path.join(TEST_PATH, 'Amazing Grace3.json'), 'rb')
|
||||
result_data = json.loads(result_file.read().decode())
|
||||
self.assertListEqual(importer.verses, self._get_data(result_data, 'verses'))
|
||||
self.assertListEqual(importer.verse_order_list_generated, self._get_data(result_data, 'verse_order_list'))
|
||||
|
||||
def _get_data(self, data, key):
|
||||
if key in data:
|
||||
return data[key]
|
||||
return ''
|
||||
|
||||
def _build_test_data(self, test_file, dual_language):
|
||||
song = MagicMock()
|
||||
song.ID = 100
|
||||
song.SongNumber = 123
|
||||
song.SongBookName = 'The Song Book'
|
||||
song.Title = 'Song Title'
|
||||
song.CopyrightText = 'Music and text by me'
|
||||
song.Version = '1'
|
||||
song.Origin = '...'
|
||||
lyrics = MagicMock()
|
||||
test_file = open(os.path.join(TEST_PATH, test_file), 'rb')
|
||||
lyrics.Lyrics = test_file.read().decode()
|
||||
lyrics.Type = 1
|
||||
lyrics.IsDualLanguage = dual_language
|
||||
return song, lyrics
|
|
@ -23,11 +23,8 @@ This module contains tests for the VideoPsalm song importer.
|
|||
"""
|
||||
|
||||
import os
|
||||
from unittest import TestCase
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
from openlp.core.common import Registry
|
||||
from tests.functional import patch, MagicMock
|
||||
|
||||
TEST_PATH = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'videopsalmsongs'))
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"title": "Amazing Grace",
|
||||
"verse_order_list": ["v1", "v2", "v3"],
|
||||
"verses": [
|
||||
[
|
||||
"v1",
|
||||
"Amazing grace! How sweet the sound!\r\nThat saved a wretch like me!\r\nI once was lost, but now am found;\r\nWas blind, but now I see.\r\n\r\n'Twas grace that taught my heart to fear,\r\nAnd grace my fears relieved.\r\nHow precious did that grace appear,\r\nThe hour I first believed.",
|
||||
null
|
||||
],
|
||||
[
|
||||
"v2",
|
||||
"The Lord has promised good to me,\r\nHis Word my hope secures.\r\nHe will my shield and portion be\r\nAs long as life endures.",
|
||||
null
|
||||
],
|
||||
[
|
||||
"v3",
|
||||
"Thro' many dangers, toils and snares\r\nI have already come.\r\n'Tis grace that brought me safe thus far,\r\nAnd grace will lead me home.\r\n\r\n[---]\r\nWhen we've been there ten thousand years,\r\nBright shining as the sun,\r\nWe've no less days to sing God's praise,\r\nThan when we first begun.",
|
||||
null
|
||||
]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"title": "Amazing Grace",
|
||||
"verse_order_list": ["v1", "v2", "v3", "v4", "v5"],
|
||||
"verses": [
|
||||
[
|
||||
"v1",
|
||||
"Amazing grace! How sweet the sound!\r\n{translation}That saved a wretch like me!{/translation}\r\nI once was lost, but now am found;\r\n{translation}Was blind, but now I see.{/translation}",
|
||||
null
|
||||
],
|
||||
[
|
||||
"v2",
|
||||
"'Twas grace that taught my heart to fear,\r\nAnd grace my fears relieved.\r\n{translation}How precious did that grace appear,\r\nThe hour I first believed.\r\n{/translation}",
|
||||
null
|
||||
],
|
||||
[
|
||||
"v3",
|
||||
"The Lord has promised good to me,\r\nHis Word my hope secures.\r\nHe will my shield and portion be\r\n{translation}As long as life endures.{/translation}",
|
||||
null
|
||||
],
|
||||
[
|
||||
"v4",
|
||||
"Thro' many dangers, toils and snares\r\nI have already come.\r\n'Tis grace that brought me safe thus far,\r\n{translation}And grace will lead me home.{/translation}",
|
||||
null
|
||||
],
|
||||
[
|
||||
"v5",
|
||||
"[end]\r\n{translation}When we've been there ten thousand years,{/translation}\r\nBright shining as the sun,\r\n{translation}We've no less days to sing God's praise,{/translation}\r\nThan when we first begun.",
|
||||
null
|
||||
]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"title": "You are so faithful",
|
||||
"verse_order_list": ["v1", "c1", "v2", "c1", "v3", "c1", "v4"],
|
||||
"verses": [
|
||||
[
|
||||
"v1",
|
||||
"You are so faithful\r\nso faithful, so faithful.\r\nYou are so faithful\r\nso faithful, so faithful.",
|
||||
null
|
||||
],
|
||||
[
|
||||
"c1",
|
||||
"That's why I praise you\r\nin the morning\r\nThat's why I praise you\r\nin the noontime.\r\nThat's why I praise you\r\nin the evening\r\nThat's why I praise you\r\nall the time.",
|
||||
null
|
||||
],
|
||||
[
|
||||
"v2",
|
||||
"You are so loving\r\nso loving, so loving.\r\nYou are so loving\r\nso loving, so loving.",
|
||||
null
|
||||
],
|
||||
[
|
||||
"v3",
|
||||
"You are so caring\r\nso caring, so caring.\r\nYou are so caring\r\nso caring, so caring.",
|
||||
null
|
||||
],
|
||||
[
|
||||
"v4",
|
||||
"You are so mighty\r\nso mighty, so mighty.\r\nYou are so mighty\r\nso mighty, so mighty.",
|
||||
null
|
||||
]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
Amazing grace! How sweet the sound!
|
||||
That saved a wretch like me!
|
||||
I once was lost, but now am found;
|
||||
Was blind, but now I see.
|
||||
[join]
|
||||
'Twas grace that taught my heart to fear,
|
||||
And grace my fears relieved.
|
||||
How precious did that grace appear,
|
||||
The hour I first believed.
|
||||
|
||||
The Lord has promised good to me,
|
||||
His Word my hope secures.
|
||||
He will my shield and portion be
|
||||
As long as life endures.
|
||||
|
||||
Thro' many dangers, toils and snares
|
||||
I have already come.
|
||||
'Tis grace that brought me safe thus far,
|
||||
And grace will lead me home.
|
||||
[split]
|
||||
When we've been there ten thousand years,
|
||||
Bright shining as the sun,
|
||||
We've no less days to sing God's praise,
|
||||
Than when we first begun.
|
|
@ -0,0 +1,29 @@
|
|||
[trans off]
|
||||
Amazing grace! How sweet the sound!
|
||||
That saved a wretch like me!
|
||||
I once was lost, but now am found;
|
||||
Was blind, but now I see.
|
||||
[join]
|
||||
[trans off]
|
||||
'Twas grace that taught my heart to fear,
|
||||
And grace my fears relieved.
|
||||
How precious did that grace appear,
|
||||
The hour I first believed.
|
||||
|
||||
[trans off]
|
||||
The Lord has promised good to me,
|
||||
His Word my hope secures.
|
||||
He will my shield and portion be
|
||||
As long as life endures.
|
||||
|
||||
[trans off]
|
||||
Thro' many dangers, toils and snares
|
||||
I have already come.
|
||||
'Tis grace that brought me safe thus far,
|
||||
And grace will lead me home.
|
||||
[trans off]
|
||||
[split]
|
||||
When we've been there ten thousand years,
|
||||
Bright shining as the sun,
|
||||
We've no less days to sing God's praise,
|
||||
Than when we first begun.
|
|
@ -0,0 +1,31 @@
|
|||
Amazing grace! How sweet the sound!
|
||||
That saved a wretch like me!
|
||||
I once was lost, but now am found;
|
||||
Was blind, but now I see.
|
||||
|
||||
[taal a]
|
||||
'Twas grace that taught my heart to fear,
|
||||
And grace my fears relieved.
|
||||
[taal b]
|
||||
How precious did that grace appear,
|
||||
The hour I first believed.
|
||||
|
||||
[trans off]
|
||||
The Lord has promised good to me,
|
||||
His Word my hope secures.
|
||||
[trans on]
|
||||
He will my shield and portion be
|
||||
As long as life endures.
|
||||
|
||||
[vertaal uit]
|
||||
Thro' many dangers, toils and snares
|
||||
I have already come.
|
||||
[vertaal aan]
|
||||
'Tis grace that brought me safe thus far,
|
||||
And grace will lead me home.
|
||||
|
||||
[end]
|
||||
When we've been there ten thousand years,
|
||||
Bright shining as the sun,
|
||||
We've no less days to sing God's praise,
|
||||
Than when we first begun.
|
|
@ -0,0 +1,37 @@
|
|||
1
|
||||
You are so faithful
|
||||
so faithful, so faithful.
|
||||
You are so faithful
|
||||
so faithful, so faithful.
|
||||
|
||||
Refrein:
|
||||
That's why I praise you
|
||||
in the morning
|
||||
That's why I praise you
|
||||
in the noontime.
|
||||
That's why I praise you
|
||||
in the evening
|
||||
That's why I praise you
|
||||
all the time.
|
||||
|
||||
2
|
||||
You are so loving
|
||||
so loving, so loving.
|
||||
You are so loving
|
||||
so loving, so loving.
|
||||
|
||||
(refrein)
|
||||
|
||||
3
|
||||
You are so caring
|
||||
so caring, so caring.
|
||||
You are so caring
|
||||
so caring, so caring.
|
||||
|
||||
(refrein)
|
||||
|
||||
4
|
||||
You are so mighty
|
||||
so mighty, so mighty.
|
||||
You are so mighty
|
||||
so mighty, so mighty.
|
Loading…
Reference in New Issue