openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #34281
[Merge] lp:~john+ubuntu-g/openlp/singingthefaith into lp:openlp
John Lines has proposed merging lp:~john+ubuntu-g/openlp/singingthefaith into lp:openlp.
Commit message:
Initial merge of SingingTheFaithImport, including update to importer.py
Requested reviews:
Raoul Snyman (raoul-snyman)
For more details, see:
https://code.launchpad.net/~john+ubuntu-g/openlp/singingthefaith/+merge/369489
Singing The Faith is the new Authorized Hymn book for the Methodist Church of Great Britain.
There is an electronic version of the Hymn book, for Windows only, which can export Hymns as text files.
This import module smooths the process of converting these text files into OpenLP. The input format is messy and not intended for automatic processing so the importer uses a combination of heuristics and a hints file. This version has not been tested on all the hymns in Singing The Faith, but deals with most of the, more than 100, hymns it has been tested with.
Note that it includes a test module, which works for the single verse case. Multiple verse songs import OK, but tests fail.
--
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/plugins/songs/lib/importer.py'
--- openlp/plugins/songs/lib/importer.py 2019-04-13 13:00:22 +0000
+++ openlp/plugins/songs/lib/importer.py 2019-06-30 18:28:14 +0000
@@ -42,6 +42,7 @@
from .importers.powersong import PowerSongImport
from .importers.presentationmanager import PresentationManagerImport
from .importers.propresenter import ProPresenterImport
+from .importers.singingthefaith import SingingTheFaithImport
from .importers.songbeamer import SongBeamerImport
from .importers.songpro import SongProImport
from .importers.songshowplus import SongShowPlusImport
@@ -173,16 +174,17 @@
PowerSong = 16
PresentationManager = 17
ProPresenter = 18
- SongBeamer = 19
- SongPro = 20
- SongShowPlus = 21
- SongsOfFellowship = 22
- SundayPlus = 23
- VideoPsalm = 24
- WordsOfWorship = 25
- WorshipAssistant = 26
- WorshipCenterPro = 27
- ZionWorx = 28
+ SingingTheFaith = 19
+ SongBeamer = 20
+ SongPro = 21
+ SongShowPlus = 22
+ SongsOfFellowship = 23
+ SundayPlus = 24
+ VideoPsalm = 25
+ WordsOfWorship = 26
+ WorshipAssistant = 27
+ WorshipCenterPro = 28
+ ZionWorx = 29
# Set optional attribute defaults
__defaults__ = {
@@ -343,6 +345,15 @@
'filter': '{text} (*.pro4 *.pro5 *.pro6)'.format(text=translate('SongsPlugin.ImportWizardForm',
'ProPresenter Song Files'))
},
+ SingingTheFaith: {
+ 'class': SingingTheFaithImport,
+ 'name': 'SingingTheFaith',
+ 'prefix': 'singingTheFaith',
+ 'filter': '%s (*.txt)' % translate('SongsPlugin.ImportWizardForm', 'Singing The Faith Exported Files'),
+ 'descriptionText': translate('SongsPlugin.ImportWizardForm',
+ 'First use Singing The Faith Electonic edition to export '
+ 'the song(s) in Text format.')
+ },
SongBeamer: {
'class': SongBeamerImport,
'name': 'SongBeamer',
@@ -462,6 +473,7 @@
SongFormat.PowerSong,
SongFormat.PresentationManager,
SongFormat.ProPresenter,
+ SongFormat.SingingTheFaith,
SongFormat.SongBeamer,
SongFormat.SongPro,
SongFormat.SongShowPlus,
=== added file 'openlp/plugins/songs/lib/importers/singingthefaith.py'
--- openlp/plugins/songs/lib/importers/singingthefaith.py 1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/importers/singingthefaith.py 2019-06-30 18:28:14 +0000
@@ -0,0 +1,347 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2019 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:`singingthefaith` module provides the functionality for importing songs which are
+exported from Singing The Faith - an Authorised songbook for the Methodist Church of
+Great Britain."""
+
+import logging
+import re
+from pathlib import Path
+
+from openlp.core.common.i18n import translate
+from openlp.plugins.songs.lib.importers.songimport import SongImport
+
+log = logging.getLogger(__name__)
+
+
+class SingingTheFaithImport(SongImport):
+ """
+ Import songs exported from SingingTheFaith
+ """
+
+ hints_available = False
+ checks_needed = True
+ hintline = {}
+ hintfile_version = '0'
+ hint_verseOrder = ''
+ hint_songtitle = ''
+ hint_comments = ''
+ hint_ignoreIndent = False
+
+ def do_import(self):
+ """
+ Receive a single file or a list of files to import.
+ """
+ if not isinstance(self.import_source, list):
+ return
+ self.import_wizard.progress_bar.setMaximum(len(self.import_source))
+ for file_path in self.import_source:
+ if self.stop_import_flag:
+ return
+ with file_path.open('rt', encoding='cp1251') as song_file:
+ self.do_import_file(song_file)
+
+ def do_import_file(self, file):
+ """
+ Process the SingingTheFaith file - pass in a file-like object, not a file path.
+ """
+ singingTheFaithVersion = 1
+ self.set_defaults()
+ # Setup variables
+ line_number = 0
+ old_indent = 0
+ # The chorus indent is how many spaces the chorus is indented - it might be 6,
+ # but we test for >= and I do not know how consistent to formatting of the
+ # exported songs is.
+ chorus_indent = 5
+ song_title = 'STF000 -'
+ song_number = '0'
+ ccli = '0'
+ current_verse = ''
+ current_verse_type = 'v'
+ current_verse_number = 1
+ # Potentially we could try to track current chorus number to automatically handle
+ # more than 1 chorus, currently unused.
+ # current_chorus_number = 1
+ has_chorus = False
+ chorus_written = False
+ auto_verse_order_ok = False
+ copyright = ''
+ # the check_flag is prepended to the title, removed if the import should be OK
+ # all the songs which need manual editing should sort below all the OK songs
+ check_flag = 'z'
+
+ self.add_comment("Imported with Singing The Faith Importer v " + str(singingTheFaithVersion))
+
+ # Get the file_song_number - so we can use it for hints
+ filename = Path(file.name)
+ song_number_file = filename.stem
+ song_number_match = re.search(r'\d+', song_number_file)
+ if song_number_match:
+ song_number_file = song_number_match.group()
+
+ # See if there is a hints file in the same location as the file
+ dir_path = filename.parent
+ hints_file_path = dir_path / 'hints.tag'
+ try:
+ with hints_file_path.open('r') as hints_file:
+ hints_available = self.read_hints(hints_file, song_number_file)
+ except FileNotFoundError:
+ hints_available = False
+
+ try:
+ for line in file:
+ line_number += 1
+ if hints_available and (str(line_number) in self.hintline):
+ hint = self.hintline[str(line_number)]
+ if hint == "Comment":
+ line.strip()
+ self.add_comment(line)
+ line_number += 1
+ next(file)
+ continue
+ elif hint == "Ignore":
+ line_number += 1
+ next(file)
+ continue
+ elif hint == "Author":
+ # add as a raw author - do not split and make them a words author
+ line.strip()
+ self.add_author(line, 'words')
+ line_number += 1
+ next(file)
+ continue
+ elif hint.startswith("VariantVerse"):
+ (vv, hintverse, replace) = hint.split(" ", 2)
+ this_verse = self.verses[int(hintverse) - 1]
+ this_verse_str = this_verse[1]
+ new_verse = this_verse_str
+ # There might be multiple replace pairs separated by |
+ replaces = replace.split("|")
+ for rep in replaces:
+ (source_str, dest_str) = rep.split("/")
+ new_verse = new_verse.replace(source_str, dest_str)
+ self.add_verse(new_verse, 'v')
+ self.verse_order_list.append('v' + str(current_verse_number))
+ current_verse_number += 1
+ line_number += 1
+ next(file)
+ continue
+ else:
+ self.log_error(translate('SongsPlugin.SingingTheFaithImport', 'File %s' % file.name),
+ translate('SongsPlugin.SingingTheFaithImport', 'Unknown hint %s' % hint))
+ return
+ # STF exported lines have a leading verse number at the start of each verse.
+ # remove them - note that we want to track the indent as that shows a chorus
+ # so will deal with that before stipping all leading spaces.
+ indent = 0
+ if line.strip():
+ verse_num_match = re.search(r'^\d+', line)
+ if verse_num_match:
+ # Could extract the verse number and check it against the calculated
+ # verse number - TODO
+ # verse_num = verse_num_match.group()
+ line = line.lstrip("0123456789")
+ indent_match = re.search(r'^\s+', line)
+ if indent_match:
+ indent = len(indent_match.group())
+ # Assuming we have sorted out what is verse and what is chorus, strip lines,
+ # unless ignoreIndent
+ if self.hint_ignoreIndent:
+ line = line.rstrip()
+ else:
+ line = line.strip()
+ if line_number == 2:
+ # note that songs seem to start with a blank line
+ song_title = line
+ # Detect the 'Reproduced from Singing the Faith Electronic Words Edition' line
+ if line.startswith('Reproduced from Singing the Faith Electronic Words Edition'):
+ song_number_match = re.search(r'\d+', line)
+ if song_number_match:
+ song_number = song_number_match.group()
+ continue
+ # If the indent is 0 and it contains '(c)' then it is a Copyright line
+ elif (indent == 0) and ("(c)" in line):
+ copyright = line
+ continue
+ elif (indent == 0) and (line.startswith('Liturgical ')):
+ self.add_comment(line)
+ continue
+ elif (indent == 0) and (line.startswith('From The ')):
+ self.add_comment(line)
+ continue
+ elif (indent == 0) and (line.startswith('From Common ')):
+ self.add_comment(line)
+ continue
+ # If indent is 0 it may be the author, unless it was one of the cases covered above
+ elif (indent == 0) and len(line) > 0:
+ # May have more than one author, separated by ' and '
+ authors = line.split(' and ')
+ for a in authors:
+ self.parse_author(a)
+ continue
+ if line == '':
+ if current_verse != '':
+ self.add_verse(current_verse, current_verse_type)
+ self.verse_order_list.append(current_verse_type + str(current_verse_number))
+ if current_verse_type == 'c':
+ chorus_written = True
+ else:
+ current_verse_number += 1
+ current_verse = ''
+ if chorus_written:
+ current_verse_type = 'v'
+ else:
+ # If the line is indented more than or equal chorus_indent then assume it is a chorus
+ # If the indent has just changed then start a new verse just like hitting a blank line
+ if not self.hint_ignoreIndent and ((indent >= chorus_indent) and (old_indent < indent)):
+ if current_verse != '':
+ self.add_verse(current_verse, current_verse_type)
+ self.verse_order_list.append(current_verse_type + str(current_verse_number))
+ if current_verse_type == 'v':
+ current_verse_number += 1
+ current_verse = line
+ current_verse_type = 'c'
+ old_indent = indent
+ chorus_written = False
+ has_chorus = True
+ continue
+ if current_verse == '':
+ current_verse += line
+ else:
+ current_verse += '\n' + line
+ old_indent = indent
+ except Exception as e:
+ self.log_error(translate('SongsPlugin.SingingTheFaithImport', 'File %s' % file.name),
+ translate('SongsPlugin.SingingTheFaithImport', 'Error: %s') % e)
+ return
+
+ if self.hint_songtitle:
+ song_title = self.hint_songtitle
+ self.title = check_flag + "STF" + song_number.zfill(3) + " - " + song_title
+ self.song_book_name = "Singing The Faith"
+ self.song_number = song_number
+ self.ccli_number = ccli
+ self.add_copyright(copyright)
+# If we have a chorus then the generated Verse order can not be used directly, but we can generate
+# one for two special cases - Verse followed by one chorus (to be repeated after every verse)
+# of Chorus, followed by verses. If hints for ManualCheck or VerseOrder are supplied ignore this
+ if has_chorus and not self.hint_verseOrder and not self.checks_needed:
+ auto_verse_order_ok = False
+ # Popular case V1 C2 V2 ...
+ if len(self.verse_order_list) >= 1: # protect against odd cases
+ if (self.verse_order_list[0] == "v1") and (self.verse_order_list[1] == "c2"):
+ new_verse_order_list = ['v1', 'c1']
+ i = 2
+ auto_verse_order_ok = True
+ elif (self.verse_order_list[0] == "c1") and (self.verse_order_list[1] == "v1"):
+ new_verse_order_list = ['c1', 'v1', 'c1']
+ i = 2
+ auto_verse_order_ok = True
+ # if we are in a case we can deal with
+ if auto_verse_order_ok:
+ while i < len(self.verse_order_list):
+ if self.verse_order_list[i].startswith('v'):
+ new_verse_order_list.append(self.verse_order_list[i])
+ new_verse_order_list.append("c1")
+ else:
+ # Would like to notify, but want a warning, which we will do via the
+ # Check_needed mechanism, as log_error aborts input of that song.
+ # self.log_error(translate('SongsPlugin.SingingTheFaithImport', 'File %s' % file.name),
+ # 'Error: Strange verse order entry ' + self.verse_order_list[i])
+ auto_verse_order_ok = False
+ i += 1
+ self.verse_order_list = new_verse_order_list
+ else:
+ if not auto_verse_order_ok:
+ self.verse_order_list = []
+ if self.hint_verseOrder:
+ self.verse_order_list = self.hint_verseOrder.split(',')
+ if self.hint_comments:
+ self.add_comment(self.hint_comments)
+ # Write the title last as by now we will know if we need checks
+ if hints_available and not self.checks_needed:
+ check_flag = ''
+ elif not hints_available and not has_chorus:
+ check_flag = ''
+ elif not hints_available and has_chorus and auto_verse_order_ok:
+ check_flag = ''
+ self.title = check_flag + "STF" + song_number.zfill(3) + " - " + song_title
+ if not self.finish():
+ self.log_error(file.name)
+
+ def read_hints(self, file, song_number):
+ hintfound = False
+ self.hint_verseOrder = ''
+ self.hintline.clear()
+ self.hint_comments = ''
+ self.hint_songtitle = ''
+ self.hint_ignoreIndent = False
+ for tl in file:
+ if not tl.strip():
+ return hintfound
+ tagval = tl.split(':')
+ tag = tagval[0].strip()
+ val = tagval[1].strip()
+ if tag == "Version":
+ self.hintfile_version = val
+ continue
+ if (tag == "Hymn") and (val == song_number):
+ self.add_comment("Using hints version " + str(self.hintfile_version))
+ hintfound = True
+ # Assume, unless the hints has ManualCheck that if hinted all will be OK
+ self.checks_needed = False
+ for tl in file:
+ tagval = tl.split(':')
+ tag = tagval[0].strip()
+ val = tagval[1].strip()
+ if tag == "End":
+ return hintfound
+ elif tag == "CommentsLine":
+ vals = val.split(',')
+ for v in vals:
+ self.hintline[v] = "Comment"
+ elif tag == "IgnoreLine":
+ vals = val.split(',')
+ for v in vals:
+ self.hintline[v] = "Ignore"
+ elif tag == "AuthorLine":
+ vals = val.split(',')
+ for v in vals:
+ self.hintline[v] = "Author"
+ elif tag == "VerseOrder":
+ self.hint_verseOrder = val
+ elif tag == "ManualCheck":
+ self.checks_needed = True
+ elif tag == "IgnoreIndent":
+ self.hint_ignoreIndent = True
+ elif tag == "VariantVerse":
+ vvline = val.split(' ', 1)
+ self.hintline[vvline[0].strip()] = "VariantVerse " + vvline[1].strip()
+ elif tag == "SongTitle":
+ self.hint_songtitle = val
+ elif tag == "AddComment":
+ self.hint_comments += '\n' + val
+ else:
+ self.log_error(file.name, "Unknown tag " + tag + " value " + val)
+ return hintfound
=== added file 'tests/functional/openlp_plugins/songs/test_singingthefaithimport.py'
--- tests/functional/openlp_plugins/songs/test_singingthefaithimport.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/songs/test_singingthefaithimport.py 2019-06-30 18:28:14 +0000
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+##########################################################################
+# OpenLP - Open Source Lyrics Projection #
+# ---------------------------------------------------------------------- #
+# Copyright (c) 2008-2019 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, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# 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, see <https://www.gnu.org/licenses/>. #
+##########################################################################
+"""
+This module contains tests for the SingingTheFaith song importer.
+"""
+from tests.helpers.songfileimport import SongImportTestHelper
+from tests.utils.constants import RESOURCE_PATH
+
+
+TEST_PATH = RESOURCE_PATH / 'songs' / 'singingthefaith'
+
+
+class TestSingingTheFaithFileImport(SongImportTestHelper):
+
+ def __init__(self, *args, **kwargs):
+ self.importer_class_name = 'SingingTheFaithImport'
+ self.importer_module_name = 'singingthefaith'
+ super(TestSingingTheFaithFileImport, self).__init__(*args, **kwargs)
+
+ def test_song_import(self):
+ """
+ Test that loading a Singing The Faith file works correctly on various files
+ """
+ # Single verse
+ self.file_import([TEST_PATH / 'H1.txt'],
+ self.load_external_result_data(TEST_PATH / 'STF001.json'))
+ # Whole song - currently not working - test needs debugging.
+## self.file_import([TEST_PATH / 'H2.txt'],
+## self.load_external_result_data(TEST_PATH / 'STF002.json'))
+
+
=== modified file 'tests/helpers/songfileimport.py'
--- tests/helpers/songfileimport.py 2019-05-22 06:47:00 +0000
+++ tests/helpers/songfileimport.py 2019-06-30 18:28:14 +0000
@@ -123,7 +123,7 @@
log.debug("Song copyright imported: %s" % importer.song_number)
log.debug("Topics imported: %s" % importer.topics)
- assert importer.title == title, 'title for %s should be "%s"' % (source_file_name, title)
+ assert importer.title == title, 'title for %s should be "%s" and is "%s"' % (source_file_name, title, importer.title)
for author in author_calls:
if isinstance(author, str):
self.mocked_add_author.assert_any_call(author)
=== added directory 'tests/resources/songs/singingthefaith'
=== added file 'tests/resources/songs/singingthefaith/H1.txt'
--- tests/resources/songs/singingthefaith/H1.txt 1970-01-01 00:00:00 +0000
+++ tests/resources/songs/singingthefaith/H1.txt 2019-06-30 18:28:14 +0000
@@ -0,0 +1,9 @@
+
+1 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.
+
+John Newton (d. 1807)
+
+Reproduced from Singing the Faith Electronic Words Edition, number 1 - or not as this is a hand made test file
=== added file 'tests/resources/songs/singingthefaith/H2.txt'
--- tests/resources/songs/singingthefaith/H2.txt 1970-01-01 00:00:00 +0000
+++ tests/resources/songs/singingthefaith/H2.txt 2019-06-30 18:28:14 +0000
@@ -0,0 +1,30 @@
+
+1 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.
+
+2 'Twas grace that taught my heart to fear,
+ And grace my fears relieved.
+ How precious did that grace appear,
+ The hour I first believed.
+
+3 The Lord has promised good to me,
+ His Word my hope secures.
+ He will my shield and portion be
+ As long as life endures.
+
+4 Thro' many dangers, toils and snares
+ I have already come.
+ 'Tis grace that brought me safe thus far,
+ And grace will lead me home.
+
+5 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.
+
+
+John Newton (d. 1807)
+
+Reproduced from Singing the Faith Electronic Words Edition, number 2 - or not as this is a hand made test file
=== added file 'tests/resources/songs/singingthefaith/STF001.json'
--- tests/resources/songs/singingthefaith/STF001.json 1970-01-01 00:00:00 +0000
+++ tests/resources/songs/singingthefaith/STF001.json 2019-06-30 18:28:14 +0000
@@ -0,0 +1,13 @@
+{
+ "title": "STF001 - Amazing Grace! how sweet the sound!",
+ "authors": [
+ "John Newton (d. 1807)"
+ ],
+ "verse_order_list": ["v1"],
+ "verses": [
+ [
+ "Amazing Grace! how sweet the sound!\nThat saved a wretch like me!\nI once was lost, but now am found;\nWas blind, but now I see.",
+ "v"
+ ]
+ ]
+}
=== added file 'tests/resources/songs/singingthefaith/STF002.json'
--- tests/resources/songs/singingthefaith/STF002.json 1970-01-01 00:00:00 +0000
+++ tests/resources/songs/singingthefaith/STF002.json 2019-06-30 18:28:14 +0000
@@ -0,0 +1,29 @@
+{
+ "title": "STF002 - Amazing Grace! how sweet the sound!",
+ "authors": [
+ "John Newton (d. 1807)"
+ ],
+ "verse_order_list": ["v1", "v2", "v3", "v4", "v5"],
+ "verses": [
+ [
+ "Amazing Grace! how sweet the sound!\nThat saved a wretch like me!\nI once was lost, but now am found;\nWas blind, but now I see.",
+ "v"
+ ],
+ [
+ "'Twas grace that taught my heart to fear,\nAnd grace my fears relieved.\nHow precious did that grace appear,\nThe hour I first believed.",
+ "v"
+ ],
+ [
+ "The Lord has promised good to me,\nHis Word my hope secures.\nHe will my shield and portion be\nAs long as life endures.",
+ "v"
+ ],
+ [
+ "Thro' many dangers, toils and snares\nI have already come.\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.",
+ "v"
+ ],
+ [
+ "When we've been there ten thousand years,\nBright shining as the sun,\nWe've no less days to sing God's praise,\nThan when we first begun.",
+ "v"
+ ]
+ ]
+}
Follow ups