Started on chords support

This commit is contained in:
Tomas Groth 2016-01-03 18:01:44 +01:00
parent 339a8e9cd6
commit dec1aa6720
10 changed files with 274 additions and 30 deletions

View File

@ -252,7 +252,8 @@ class Settings(QtCore.QSettings):
'shortcuts/blankScreen': [QtGui.QKeySequence(QtCore.Qt.Key_Period)],
'shortcuts/collapse': [QtGui.QKeySequence(QtCore.Qt.Key_Minus)],
'shortcuts/desktopScreen': [QtGui.QKeySequence(QtCore.Qt.Key_D)],
'shortcuts/delete': [QtGui.QKeySequence(QtGui.QKeySequence.Delete), QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
'shortcuts/delete': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
'shortcuts/down': [QtGui.QKeySequence(QtCore.Qt.Key_Down)],
'shortcuts/editSong': [],
'shortcuts/escapeItem': [QtGui.QKeySequence(QtCore.Qt.Key_Escape)],
@ -329,7 +330,8 @@ class Settings(QtCore.QSettings):
'shortcuts/moveBottom': [QtGui.QKeySequence(QtCore.Qt.Key_End)],
'shortcuts/moveDown': [QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
'shortcuts/nextTrackItem': [],
'shortcuts/nextItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Down), QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
'shortcuts/nextItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Down),
QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
'shortcuts/nextItem_preview': [QtGui.QKeySequence(QtCore.Qt.Key_Down),
QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
'shortcuts/nextService': [QtGui.QKeySequence(QtCore.Qt.Key_Right)],
@ -339,7 +341,8 @@ class Settings(QtCore.QSettings):
QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_F1)],
'shortcuts/openService': [],
'shortcuts/saveService': [],
'shortcuts/previousItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Up), QtGui.QKeySequence(QtCore.Qt.Key_PageUp)],
'shortcuts/previousItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Up),
QtGui.QKeySequence(QtCore.Qt.Key_PageUp)],
'shortcuts/playbackPause': [],
'shortcuts/playbackPlay': [],
'shortcuts/playbackStop': [],

View File

@ -27,6 +27,7 @@ OpenLP work.
from distutils.version import LooseVersion
import logging
import os
import re
from PyQt5 import QtCore, QtGui, Qt, QtWidgets
@ -258,11 +259,12 @@ def check_item_selected(list_widget, message):
return True
def clean_tags(text):
def clean_tags(text, chords=False):
"""
Remove Tags from text for display
:param text: Text to be cleaned
:param chords: Clean ChordPro tags
"""
text = text.replace('<br>', '\n')
text = text.replace('{br}', '\n')
@ -270,21 +272,47 @@ def clean_tags(text):
for tag in FormattingTags.get_html_tags():
text = text.replace(tag['start tag'], '')
text = text.replace(tag['end tag'], '')
# Remove ChordPro tags
if chords:
text = re.sub(r'\[.+?\]', r'', text)
return text
def expand_tags(text):
def expand_tags(text, chords=False):
"""
Expand tags HTML for display
:param text: The text to be expanded.
:param chords: Convert ChordPro tags to html
"""
if chords:
text = expand_chords(text)
for tag in FormattingTags.get_html_tags():
text = text.replace(tag['start tag'], tag['start html'])
text = text.replace(tag['end tag'], tag['end html'])
return text
def expand_chords(text):
"""
Expand ChordPro tags
:param text:
"""
text_lines = text.split('{br}')
expanded_text_lines = []
for line in text_lines:
# If a ChordPro is detected in the line, replace it with a html-span tag and wrap the line in a span tag.
if '[' in line and ']' in line:
new_line = '<span class="chordline">'
new_line += re.sub(r'(.*?)\[(.+?)\](.*?)', r'\1<span class="chord" style="display:none">\2</span>\3', line)
new_line += '</span>'
expanded_text_lines.append(new_line)
else:
expanded_text_lines.append(line)
return '{br}'.join(expanded_text_lines)
def create_separated_list(string_list):
"""
Returns a string that represents a join of a list of strings with a localized separator. This function corresponds

View File

@ -254,11 +254,11 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
# If there are (at least) two occurrences of [---] we use the first two slides (and neglect the last
# for now).
if len(slides) == 3:
html_text = expand_tags('\n'.join(slides[:2]))
html_text = expand_tags('\n'.join(slides[:2]), item.is_capable(ItemCapabilities.HasChords))
# We check both slides to determine if the optional split is needed (there is only one optional
# split).
else:
html_text = expand_tags('\n'.join(slides))
html_text = expand_tags('\n'.join(slides), item.is_capable(ItemCapabilities.HasChords))
html_text = html_text.replace('\n', '<br>')
if self._text_fits_on_slide(html_text):
# The first two optional slides fit (as a whole) on one slide. Replace the first occurrence

View File

@ -34,7 +34,7 @@ import ntpath
from PyQt5 import QtGui
from openlp.core.common import RegistryProperties, Settings, translate, AppLocation, md5_hash
from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, create_thumb
from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, expand_chords
log = logging.getLogger(__name__)
@ -118,6 +118,8 @@ class ItemCapabilities(object):
``HasThumbnails``
The item has related thumbnails available
``HasChords``
The item has chords - only for songs
"""
CanPreview = 1
CanEdit = 2
@ -140,6 +142,7 @@ class ItemCapabilities(object):
HasDisplayTitle = 19
HasNotes = 20
HasThumbnails = 21
HasChords = 22
class ServiceItem(RegistryProperties):
@ -260,13 +263,16 @@ class ServiceItem(RegistryProperties):
previous_pages[verse_tag] = (slide['raw_slide'], pages)
for page in pages:
page = page.replace('<br>', '{br}')
html_data = expand_tags(html.escape(page.rstrip()))
self._display_frames.append({
html_data = expand_tags(html.escape(page.rstrip()), self.is_capable(ItemCapabilities.HasChords))
new_frame = {
'title': clean_tags(page),
'text': clean_tags(page.rstrip()),
'text': clean_tags(page.rstrip(), self.is_capable(ItemCapabilities.HasChords)),
'html': html_data.replace('&amp;nbsp;', '&nbsp;'),
'verseTag': verse_tag
})
}
if self.is_capable(ItemCapabilities.HasChords):
new_frame['chords_text'] = expand_chords(clean_tags(page.rstrip()))
self._display_frames.append(new_frame)
elif self.service_item_type == ServiceItemType.Image or self.service_item_type == ServiceItemType.Command:
pass
else:

View File

@ -16,22 +16,22 @@
* 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: sans-serif;
font-family: 'Arial Narrow', 'Avenir Next Condensed', sans-serif-condensed, Arial, sans-serif;
font-size: 4.4vw;
overflow: hidden;
}
#currentslide {
font-size: 40pt;
color: white;
font-size: 100%;
color: lightgray;
padding-bottom: 0px;
}
#nextslide {
font-size: 40pt;
color: grey;
font-size: 100%;
color: gray;
padding-top: 0px;
padding-bottom: 0px;
}
@ -41,24 +41,90 @@ body {
}
#clock {
font-size: 30pt;
font-size: 75%;
color: yellow;
text-align: right;
}
#notes {
font-size: 36pt;
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: 30pt;
font-size: 75%;
color: green;
text-align: left;
line-height: 1.5;
display: inline-block;
vertical-align: middle;
}
.currenttag {
color: lightgreen;
font-weight: bold;
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;
}

View File

@ -32,9 +32,15 @@
<input type="hidden" id="next-text" value="${next}" />
<div id="right">
<div id="clock"></div>
<div id="chords">chords on/off</div>
<div id="plus" class="button">+</div>
<div id="minus" class="button">-</div>
<div id="notes"></div>
</div>
<div id="verseorder"></div>
<div id="header">
<div id="verseorder"></div>
<div id="transpose">Transpose:</div> <div class="button" id="transposedown">-</div> <div id="transposevalue">0</div> <div class="button" id="transposeup">+</div> <div id="capodisplay">(Capo)</div>
</div>
<div id="currentslide"></div>
<div id="nextslide"></div>
</body>

View File

@ -16,7 +16,69 @@
* 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",
@ -27,6 +89,7 @@ window.OpenLP = {
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"];
}
@ -42,6 +105,7 @@ window.OpenLP = {
"/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");
@ -61,7 +125,7 @@ window.OpenLP = {
}
else {
if ((slide["text"] == data.results.slides[lastChange]["text"]) &&
(data.results.slides.length >= idx + (idx - lastChange))) {
(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.
@ -92,6 +156,37 @@ window.OpenLP = {
// 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 += '&nbsp;';}
}
} 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 += '&nbsp;';}
}
};
} else {
if (!$2 && $3.charAt(0) == ' ') {for (c = 0; c < $1len; c++) {w += '&nbsp;';}}
}
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];
@ -101,6 +196,10 @@ window.OpenLP = {
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"]) {
@ -121,19 +220,24 @@ window.OpenLP = {
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 />");
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");
@ -141,6 +245,7 @@ window.OpenLP = {
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 + '';
@ -151,8 +256,7 @@ window.OpenLP = {
"/api/poll",
function (data, status) {
OpenLP.updateClock(data);
if (OpenLP.currentItem != data.results.item ||
OpenLP.currentService != data.results.service) {
if (OpenLP.currentItem != data.results.item || OpenLP.currentService != data.results.service) {
OpenLP.currentItem = data.results.item;
OpenLP.currentService = data.results.service;
OpenLP.loadSlides();
@ -163,8 +267,27 @@ window.OpenLP = {
}
}
);
// $('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);});
});

View File

@ -546,8 +546,14 @@ class HttpRouter(RegistryProperties):
item['tag'] = str(frame['verseTag'])
else:
item['tag'] = str(index + 1)
item['text'] = str(frame['text'])
# Use chords if available and enabled
if current_item.is_capable(ItemCapabilities.HasChords):
item['text'] = str(frame['chords_text'])
else:
item['text'] = str(frame['text'])
item['html'] = str(frame['html'])
print('text: %s' % item['text'])
print('html: %s' % item['html'])
# Handle images, unless a custom thumbnail is given or if thumbnails is disabled
elif current_item.is_image() and not frame.get('image', '') and Settings().value('remotes/thumbnails'):
item['tag'] = str(index + 1)

View File

@ -471,6 +471,9 @@ class SongMediaItem(MediaManagerItem):
if song.media_files:
service_item.add_capability(ItemCapabilities.HasBackgroundAudio)
service_item.background_audio = [m.file_name for m in song.media_files]
# If chords are enabled and detected, mark the item as having chords
if Settings().value(self.settings_section + '/chords') and '[' in song.lyrics:
service_item.add_capability(ItemCapabilities.HasChords)
return True
def generate_footer(self, item, song):

View File

@ -65,7 +65,10 @@ __default_settings__ = {
'songs/last directory export': '',
'songs/songselect username': '',
'songs/songselect password': '',
'songs/songselect searches': ''
'songs/songselect searches': '',
'songs/chords': True,
'songs/stageview chords': False,
'songs/mainview chords': False
}