openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #27998
[Merge] lp:~tomasgroth/openlp/more-song-import-formats into lp:openlp
Tomas Groth has proposed merging lp:~tomasgroth/openlp/more-song-import-formats into lp:openlp.
Requested reviews:
OpenLP Core (openlp-core)
Related bugs:
Bug #1509550 in OpenLP: "Support song import from VideoPsalm"
https://bugs.launchpad.net/openlp/+bug/1509550
For more details, see:
https://code.launchpad.net/~tomasgroth/openlp/more-song-import-formats/+merge/281039
Added importer for Lyrix and VideoPsalm + tests.
Added support for author-type in importers.
Migrated songselect and songbeamer tests to the SongImportTestHelper based structure.
--
Your team OpenLP Core is requested to review the proposed merge of lp:~tomasgroth/openlp/more-song-import-formats into lp:openlp.
=== modified file 'openlp/plugins/songs/lib/importer.py'
--- openlp/plugins/songs/lib/importer.py 2015-08-26 07:26:19 +0000
+++ openlp/plugins/songs/lib/importer.py 2015-12-20 19:34:43 +0000
@@ -46,6 +46,8 @@
from .importers.worshipassistant import WorshipAssistantImport
from .importers.powerpraise import PowerPraiseImport
from .importers.presentationmanager import PresentationManagerImport
+from .importers.lyrix import LyrixImport
+from .importers.videopsalm import VideoPsalmImport
log = logging.getLogger(__name__)
@@ -166,6 +168,8 @@
WorshipAssistant = 21
WorshipCenterPro = 22
ZionWorx = 23
+ Lyrix = 24
+ VideoPsalm = 25
# Set optional attribute defaults
__defaults__ = {
@@ -244,6 +248,13 @@
'prefix': 'foilPresenter',
'filter': '%s (*.foil)' % translate('SongsPlugin.ImportWizardForm', 'Foilpresenter Song Files')
},
+ Lyrix: {
+ 'class': LyrixImport,
+ 'name': 'LyriX',
+ 'prefix': 'lyrix',
+ 'filter': '%s (*.txt)' % translate('SongsPlugin.ImportWizardForm', 'LyriX Files'),
+ 'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'LyriX (Exported TXT-files)')
+ },
MediaShout: {
'name': 'MediaShout',
'prefix': 'mediaShout',
@@ -324,6 +335,16 @@
'prefix': 'sundayPlus',
'filter': '%s (*.ptf)' % translate('SongsPlugin.ImportWizardForm', 'SundayPlus Song Files')
},
+ VideoPsalm: {
+ 'class': VideoPsalmImport,
+ 'name': 'VideoPsalm',
+ 'prefix': 'videopsalm',
+ 'selectMode': SongFormatSelect.SingleFile,
+ 'filter': '%s (*.json)' % translate('SongsPlugin.ImportWizardForm', 'VideoPsalm Files'),
+ 'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'VideoPsalm'),
+ 'descriptionText': translate('SongsPlugin.ImportWizardForm', 'The VideoPsalm songbooks are normally located'
+ ' in %s') % 'C:\\Users\\Public\\Documents\\VideoPsalm\\SongBooks\\'
+ },
WordsOfWorship: {
'class': WordsOfWorshipImport,
'name': 'Words of Worship',
@@ -393,7 +414,9 @@
SongFormat.WordsOfWorship,
SongFormat.WorshipAssistant,
SongFormat.WorshipCenterPro,
- SongFormat.ZionWorx
+ SongFormat.ZionWorx,
+ SongFormat.Lyrix,
+ SongFormat.VideoPsalm
]
@staticmethod
=== modified file 'openlp/plugins/songs/lib/importers/cclifile.py'
--- openlp/plugins/songs/lib/importers/cclifile.py 2015-01-18 13:39:21 +0000
+++ openlp/plugins/songs/lib/importers/cclifile.py 2015-12-20 19:34:43 +0000
@@ -48,7 +48,7 @@
:param manager: The song manager for the running OpenLP installation.
:param kwargs: The files to be imported.
"""
- SongImport.__init__(self, manager, **kwargs)
+ super(CCLIFileImport, self).__init__(manager, **kwargs)
def do_import(self):
"""
@@ -161,7 +161,7 @@
elif line.startswith('Author='):
song_author = line[7:].strip()
elif line.startswith('Copyright='):
- self.copyright = line[10:].strip()
+ self.add_copyright(line[10:].strip())
elif line.startswith('Themes='):
song_topics = line[7:].strip().replace(' | ', '/t')
elif line.startswith('Fields='):
@@ -318,14 +318,14 @@
if line_number == 2:
line_number += 1
if clean_line.startswith('©'):
- self.copyright = clean_line
+ self.add_copyright(clean_line)
else:
song_author = clean_line
# n=3, authors
elif line_number == 3:
line_number += 1
if song_author:
- self.copyright = clean_line
+ self.add_copyright(clean_line)
else:
song_author = clean_line
# line_number=4, comments lines before last line
=== added file 'openlp/plugins/songs/lib/importers/lyrix.py'
--- openlp/plugins/songs/lib/importers/lyrix.py 1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/importers/lyrix.py 2015-12-20 19:34:43 +0000
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2015 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:`lyrix` module provides the functionality for importing songs which are
+exproted from Lyrix."""
+
+import logging
+import re
+
+from openlp.core.common import translate
+from openlp.plugins.songs.lib.importers.songimport import SongImport
+
+log = logging.getLogger(__name__)
+
+
+class LyrixImport(SongImport):
+ """
+ Import songs exported from Lyrix
+ """
+
+ def __init__(self, manager, **kwargs):
+ """
+ Initialise the class.
+ """
+ super(LyrixImport, self).__init__(manager, **kwargs)
+
+ 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 filename in self.import_source:
+ if self.stop_import_flag:
+ return
+ song_file = open(filename, 'rt', encoding='cp1251')
+ self.do_import_file(song_file)
+ song_file.close()
+
+ def do_import_file(self, file):
+ """
+ Process the Lyrix file - pass in a file-like object, not a file path.
+ """
+ self.set_defaults()
+ # Setup variables
+ line_number = 0
+ song_title = 'Standard Song Title'
+ ccli = '0'
+ current_verse = ''
+ verses = []
+ author = ''
+ copyright = ''
+ try:
+ # Read the file
+ for line in file:
+ line = line.strip()
+ line_number += 1
+ if line_number == 4:
+ song_title = line
+ if line_number < 7:
+ continue
+ # Detect and get CCLI number
+ if line.lower().startswith('ccli'):
+ ccli = re.findall('\d+', line)[0]
+ try:
+ # If the CCLI was found, we are near the end
+ # Find author
+ line = next(file).strip()
+ author = line[line.find(':') + 2:]
+ # Find copyright
+ copyright = next(file).strip()
+ except StopIteration:
+ pass
+ break
+ if line == '':
+ if current_verse != '':
+ verses.append(current_verse)
+ current_verse = ''
+ else:
+ if current_verse == '':
+ current_verse += line
+ else:
+ current_verse += '\n' + line
+ except Exception as e:
+ self.log_error(translate('SongsPlugin.LyrixImport', 'File %s' % file.name),
+ translate('SongsPlugin.LyrixImport', 'Error: %s') % e)
+ return
+ self.title = song_title
+ self.parse_author(author)
+ self.ccli_number = ccli
+ self.add_copyright(copyright)
+ for verse in verses:
+ self.add_verse(verse, 'v')
+ if not self.finish():
+ self.log_error(file.name)
=== modified file 'openlp/plugins/songs/lib/importers/songimport.py'
--- openlp/plugins/songs/lib/importers/songimport.py 2015-09-08 19:13:59 +0000
+++ openlp/plugins/songs/lib/importers/songimport.py 2015-12-20 19:34:43 +0000
@@ -255,13 +255,13 @@
if author2:
self.add_author(author2)
- def add_author(self, author):
+ def add_author(self, author, type=None):
"""
Add an author to the list
"""
- if author in self.authors:
+ if (author, type) in self.authors:
return
- self.authors.append(author)
+ self.authors.append((author, type))
def add_media_file(self, filename, weight=0):
"""
@@ -360,13 +360,13 @@
song.comments = self.comments
song.theme_name = self.theme_name
song.ccli_number = self.ccli_number
- for author_text in self.authors:
+ for author_text, author_type in self.authors:
author = self.manager.get_object_filtered(Author, Author.display_name == author_text)
if not author:
author = Author.populate(display_name=author_text,
last_name=author_text.split(' ')[-1],
first_name=' '.join(author_text.split(' ')[:-1]))
- song.add_author(author)
+ song.add_author(author, author_type)
if self.song_book_name:
song_book = self.manager.get_object_filtered(Book, Book.name == self.song_book_name)
if song_book is None:
=== added file 'openlp/plugins/songs/lib/importers/videopsalm.py'
--- openlp/plugins/songs/lib/importers/videopsalm.py 1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/importers/videopsalm.py 2015-12-20 19:34:43 +0000
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2015 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:`lyrix` module provides the functionality for importing songs which are
+exproted from Lyrix."""
+
+import logging
+import json
+import os
+
+from openlp.core.common import translate
+from openlp.plugins.songs.lib import VerseType
+from openlp.plugins.songs.lib.importers.songimport import SongImport
+from openlp.plugins.songs.lib.db import AuthorType
+
+log = logging.getLogger(__name__)
+
+
+class VideoPsalmImport(SongImport):
+ """
+ Import songs exported from Lyrix
+ """
+
+ def __init__(self, manager, **kwargs):
+ """
+ Initialise the class.
+ """
+ super(VideoPsalmImport, self).__init__(manager, **kwargs)
+
+ def do_import(self):
+ """
+ Process the VideoPsalm file - pass in a file-like object, not a file path.
+ """
+ self.set_defaults()
+ # Open SongBook file
+ song_file = open(self.import_source, 'rt', encoding='utf-8-sig')
+ try:
+ file_content = song_file.read()
+ processed_content = ''
+ inside_quotes = False
+ # The VideoPsalm format is not valid json, it uses illegal line breaks and unquoted keys, this must be fixed
+ file_content_it = iter(file_content)
+ for c in file_content_it:
+ if c == '"':
+ inside_quotes = not inside_quotes
+ # Detect invalid linebreak
+ if c == '\n':
+ if inside_quotes:
+ processed_content += '\\n'
+ # Put keys in quotes
+ elif c.isalnum() and not inside_quotes:
+ processed_content += '"' + c
+ c = next(file_content_it)
+ while c.isalnum():
+ processed_content += c
+ c = next(file_content_it)
+ processed_content += '"' + c
+ else:
+ processed_content += c
+ songbook = json.loads(processed_content.strip())
+ # Get song array
+ songs = songbook['Songs']
+ self.import_wizard.progress_bar.setMaximum(len(songs))
+ songbook_name = songbook['Text']
+ media_folder = os.path.normpath(os.path.join(os.path.dirname(song_file.name), '..', 'Audio'))
+ for song in songs:
+ self.song_book_name = songbook_name
+ if 'Text' in song:
+ self.title = song['Text']
+ composer = None
+ author = None
+ if 'Composer' in song:
+ composer = song['Composer']
+ if 'Author' in song:
+ author = song['Author']
+ if author and composer == author:
+ self.add_author(author, AuthorType.WordsAndMusic)
+ else:
+ if author:
+ self.add_author(author, AuthorType.Words)
+ if composer:
+ self.add_author(composer, AuthorType.Music)
+ if 'Copyright' in song:
+ self.add_copyright(song['Copyright'].replace('\n', ' ').strip())
+ if 'CCLI' in song:
+ self.ccli_number = song['CCLI']
+ if 'Theme' in song:
+ self.topics = song['Theme'].splitlines()
+ if 'AudioFile' in song:
+ self.add_media_file(os.path.join(media_folder, song['AudioFile']))
+ if 'Memo1' in song:
+ self.add_comment(song['Memo1'])
+ if 'Memo2' in song:
+ self.add_comment(song['Memo2'])
+ if 'Memo3' in song:
+ self.add_comment(song['Memo3'])
+ for verse in song['Verses']:
+ self.add_verse(verse['Text'], 'v')
+ if not self.finish():
+ self.log_error('Could not import %s' % self.title)
+ except Exception as e:
+ self.log_error(translate('SongsPlugin.VideoPsalmImport', 'File %s' % file.name),
+ translate('SongsPlugin.VideoPsalmImport', 'Error: %s') % e)
+ song_file.close()
=== added file 'tests/functional/openlp_plugins/songs/test_lyriximport.py'
--- tests/functional/openlp_plugins/songs/test_lyriximport.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/songs/test_lyriximport.py 2015-12-20 19:34:43 +0000
@@ -0,0 +1,53 @@
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2015 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 LyriX song importer.
+"""
+
+import os
+from unittest import TestCase
+
+from tests.helpers.songfileimport import SongImportTestHelper
+from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
+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', 'lyrixsongs'))
+
+
+class TestLyrixFileImport(SongImportTestHelper):
+
+ def __init__(self, *args, **kwargs):
+ self.importer_class_name = 'LyrixImport'
+ self.importer_module_name = 'lyrix'
+ super(TestLyrixFileImport, self).__init__(*args, **kwargs)
+
+ def test_song_import(self):
+ """
+ Test that loading an LyriX file works correctly on various files
+ """
+ self.file_import([os.path.join(TEST_PATH, 'A06.TXT')],
+ self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
+ self.file_import([os.path.join(TEST_PATH, 'A002.TXT')],
+ self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace2.json')))
+ self.file_import([os.path.join(TEST_PATH, 'AO05.TXT')],
+ self.load_external_result_data(os.path.join(TEST_PATH, 'in die regterhand.json')))
=== modified file 'tests/functional/openlp_plugins/songs/test_songbeamerimport.py'
--- tests/functional/openlp_plugins/songs/test_songbeamerimport.py 2015-01-18 13:39:21 +0000
+++ tests/functional/openlp_plugins/songs/test_songbeamerimport.py 2015-12-20 19:34:43 +0000
@@ -26,27 +26,28 @@
import os
from unittest import TestCase
+from tests.helpers.songfileimport import SongImportTestHelper
from tests.functional import MagicMock, patch
from openlp.plugins.songs.lib.importers.songbeamer import SongBeamerImport
-from openlp.plugins.songs.lib import VerseType
from openlp.core.common import Registry
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
'..', '..', '..', 'resources', 'songbeamersongs'))
-SONG_TEST_DATA = {
- 'Lobsinget dem Herrn.sng': {
- 'title': 'GL 1 - Lobsinget dem Herrn',
- 'verses': [
- ('1. Lobsinget dem Herrn,\no preiset Ihn gern!\nAnbetung und Lob Ihm gebühret.\n', 'v'),
- ('2. Lobsingt Seiner Lieb´,\ndie einzig ihn trieb,\nzu sterben für unsere Sünden!\n', 'v'),
- ('3. Lobsingt Seiner Macht!\nSein Werk ist vollbracht:\nEr sitzet zur Rechten des Vaters.\n', 'v'),
- ('4. Lobsingt seiner Treu´,\ndie immerdar neu,\nbis Er uns zur Herrlichket führet!\n\n', 'v')
- ],
- 'song_book_name': 'Glaubenslieder I',
- 'song_number': "1",
- 'authors': ['Carl Brockhaus', 'Johann Jakob Vetter']
- }
-}
+
+
+class TestSongBeamerFileImport(SongImportTestHelper):
+
+ def __init__(self, *args, **kwargs):
+ self.importer_class_name = 'SongBeamerImport'
+ self.importer_module_name = 'songbeamer'
+ super(TestSongBeamerFileImport, self).__init__(*args, **kwargs)
+
+ def test_song_import(self):
+ """
+ Test that loading an OpenSong file works correctly on various files
+ """
+ self.file_import([os.path.join(TEST_PATH, 'Lobsinget dem Herrn.sng')],
+ self.load_external_result_data(os.path.join(TEST_PATH, 'Lobsinget dem Herrn.json')))
class TestSongBeamerImport(TestCase):
@@ -115,51 +116,6 @@
'do_import should return None when import_source is a list and stop_import_flag is True')
mocked_import_wizard.progress_bar.setMaximum.assert_called_with(len(importer.import_source))
- def file_import_test(self):
- """
- Test the actual import of real song files and check that the imported data is correct.
- """
-
- # GIVEN: Test files with a mocked out SongImport class, a mocked out "manager", a mocked out "import_wizard",
- # and mocked out "author", "add_copyright", "add_verse", "finish" methods.
- with patch('openlp.plugins.songs.lib.importers.songbeamer.SongImport'):
- for song_file in SONG_TEST_DATA:
- mocked_manager = MagicMock()
- mocked_import_wizard = MagicMock()
- mocked_add_verse = MagicMock()
- mocked_finish = MagicMock()
- mocked_finish.return_value = True
- importer = SongBeamerImport(mocked_manager, filenames=[])
- importer.import_wizard = mocked_import_wizard
- importer.stop_import_flag = False
- importer.add_verse = mocked_add_verse
- importer.finish = mocked_finish
-
- # WHEN: Importing each file
- importer.import_source = [os.path.join(TEST_PATH, song_file)]
- title = SONG_TEST_DATA[song_file]['title']
- add_verse_calls = SONG_TEST_DATA[song_file]['verses']
- song_book_name = SONG_TEST_DATA[song_file]['song_book_name']
- song_number = SONG_TEST_DATA[song_file]['song_number']
- song_authors = SONG_TEST_DATA[song_file]['authors']
-
- # THEN: do_import should return none, the song data should be as expected, and finish should have been
- # called.
- self.assertIsNone(importer.do_import(), 'do_import should return None when it has completed')
- self.assertEqual(importer.title, title, 'title for %s should be "%s"' % (song_file, title))
- for verse_text, verse_tag in add_verse_calls:
- mocked_add_verse.assert_any_call(verse_text, verse_tag)
- if song_book_name:
- self.assertEqual(importer.song_book_name, song_book_name,
- 'song_book_name for %s should be "%s"' % (song_file, song_book_name))
- if song_number:
- self.assertEqual(importer.song_number, song_number,
- 'song_number for %s should be %s' % (song_file, song_number))
- if song_authors:
- for author in importer.authors:
- self.assertIn(author, song_authors)
- mocked_finish.assert_called_with()
-
def check_verse_marks_test(self):
"""
Tests different lines to see if a verse mark is detected or not
=== modified file 'tests/functional/openlp_plugins/songs/test_songselect.py'
--- tests/functional/openlp_plugins/songs/test_songselect.py 2015-12-17 21:43:49 +0000
+++ tests/functional/openlp_plugins/songs/test_songselect.py 2015-12-20 19:34:43 +0000
@@ -28,6 +28,7 @@
from PyQt4 import QtGui
+from tests.helpers.songfileimport import SongImportTestHelper
from openlp.core import Registry
from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker
from openlp.plugins.songs.lib import Song
@@ -37,6 +38,9 @@
from tests.functional import MagicMock, patch, call
from tests.helpers.testmixin import TestMixin
+TEST_PATH = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'songselect'))
+
class TestSongSelectImport(TestCase, TestMixin):
"""
@@ -645,68 +649,21 @@
mocked_view_button.setEnabled.assert_called_with(True)
-class TestSongSelectFileImport(TestCase, TestMixin):
- """
- Test SongSelect file import
- """
- def setUp(self):
- """
- Initial setups
- """
- Registry.create()
- test_song_name = 'TestSong'
- self.file_name = os.path.join('tests', 'resources', 'songselect', test_song_name)
- self.title = 'Test Song'
- self.ccli_number = '0000000'
- self.authors = ['Author One', 'Author Two']
- self.topics = ['Adoration', 'Praise']
-
- def songselect_import_bin_file_test(self):
- """
- Verify import SongSelect BIN file parses file properly
- """
- # GIVEN: Text file to import and mocks
- copyright_bin = '2011 OpenLP Programmer One (Admin. by OpenLP One) | ' \
- 'Openlp Programmer Two (Admin. by OpenLP Two)'
- verses_bin = [
- ['v1', 'Line One Verse One\nLine Two Verse One\nLine Three Verse One\nLine Four Verse One', None],
- ['v2', 'Line One Verse Two\nLine Two Verse Two\nLine Three Verse Two\nLine Four Verse Two', None]
- ]
- song_import = CCLIFileImport(manager=None, filename=['{}.bin'.format(self.file_name)])
-
- with patch.object(song_import, 'import_wizard'), patch.object(song_import, 'finish'):
- # WHEN: We call the song importer
- song_import.do_import()
- # THEN: Song values should be equal to test values in setUp
- self.assertEquals(song_import.title, self.title, 'Song title should match')
- self.assertEquals(song_import.ccli_number, self.ccli_number, 'CCLI Song Number should match')
- self.assertEquals(song_import.authors, self.authors, 'Author(s) should match')
- self.assertEquals(song_import.copyright, copyright_bin, 'Copyright should match')
- self.assertEquals(song_import.topics, self.topics, 'Theme(s) should match')
- self.assertEquals(song_import.verses, verses_bin, 'Verses should match with test verses')
-
- def songselect_import_text_file_test(self):
- """
- Verify import SongSelect TEXT file parses file properly
- """
- # GIVEN: Text file to import and mocks
- copyright_txt = '© 2011 OpenLP Programmer One (Admin. by OpenLP One)'
- verses_txt = [
- ['v1', 'Line One Verse One\r\nLine Two Verse One\r\nLine Three Verse One\r\nLine Four Verse One', None],
- ['v2', 'Line One Verse Two\r\nLine Two Verse Two\r\nLine Three Verse Two\r\nLine Four Verse Two', None]
- ]
- song_import = CCLIFileImport(manager=None, filename=['{}.txt'.format(self.file_name)])
-
- with patch.object(song_import, 'import_wizard'), patch.object(song_import, 'finish'):
- # WHEN: We call the song importer
- song_import.do_import()
-
- # THEN: Song values should be equal to test values in setUp
- self.assertEquals(song_import.title, self.title, 'Song title should match')
- self.assertEquals(song_import.ccli_number, self.ccli_number, 'CCLI Song Number should match')
- self.assertEquals(song_import.authors, self.authors, 'Author(s) should match')
- self.assertEquals(song_import.copyright, copyright_txt, 'Copyright should match')
- self.assertEquals(song_import.verses, verses_txt, 'Verses should match with test verses')
+class TestSongSelectFileImport(SongImportTestHelper):
+
+ def __init__(self, *args, **kwargs):
+ self.importer_class_name = 'CCLIFileImport'
+ self.importer_module_name = 'cclifile'
+ super(TestSongSelectFileImport, self).__init__(*args, **kwargs)
+
+ def test_song_import(self):
+ """
+ Test that loading an OpenSong file works correctly on various files
+ """
+ self.file_import([os.path.join(TEST_PATH, 'TestSong.bin')],
+ self.load_external_result_data(os.path.join(TEST_PATH, 'TestSong-bin.json')))
+ self.file_import([os.path.join(TEST_PATH, 'TestSong.txt')],
+ self.load_external_result_data(os.path.join(TEST_PATH, 'TestSong-txt.json')))
class TestSearchWorker(TestCase, TestMixin):
=== added file 'tests/functional/openlp_plugins/songs/test_videopsalm.py'
--- tests/functional/openlp_plugins/songs/test_videopsalm.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/songs/test_videopsalm.py 2015-12-20 19:34:43 +0000
@@ -0,0 +1,49 @@
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2015 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 VideoPsalm song importer.
+"""
+
+import os
+from unittest import TestCase
+
+from tests.helpers.songfileimport import SongImportTestHelper
+from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
+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'))
+
+
+class TestVideoPsalmFileImport(SongImportTestHelper):
+
+ def __init__(self, *args, **kwargs):
+ self.importer_class_name = 'VideoPsalmImport'
+ self.importer_module_name = 'videopsalm'
+ super(TestVideoPsalmFileImport, self).__init__(*args, **kwargs)
+
+ def test_song_import(self):
+ """
+ Test that loading an VideoPsalm file works correctly on various files
+ """
+ self.file_import(os.path.join(TEST_PATH, 'videopsalm-as-safe-a-stronghold.json'),
+ self.load_external_result_data(os.path.join(TEST_PATH, 'as-safe-a-stronghold.json')))
=== modified file 'tests/helpers/songfileimport.py'
--- tests/helpers/songfileimport.py 2015-01-18 13:39:21 +0000
+++ tests/helpers/songfileimport.py 2015-12-20 19:34:43 +0000
@@ -124,7 +124,10 @@
self.assertEqual(importer.title, title, 'title for %s should be "%s"' % (source_file_name, title))
for author in author_calls:
- self.mocked_add_author.assert_any_call(author)
+ if isinstance(author, str):
+ self.mocked_add_author.assert_any_call(author)
+ else:
+ self.mocked_add_author.assert_any_call(*author)
if song_copyright:
self.mocked_add_copyright.assert_called_with(song_copyright)
if ccli_number:
=== added directory 'tests/resources/lyrixsongs'
=== added file 'tests/resources/lyrixsongs/A002.TXT'
--- tests/resources/lyrixsongs/A002.TXT 1970-01-01 00:00:00 +0000
+++ tests/resources/lyrixsongs/A002.TXT 2015-12-20 19:34:43 +0000
@@ -0,0 +1,34 @@
+
+-------------
+A002
+AMAZING GRACE
+-------------
+
+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. THROUGH MANY DANGERS, TRIALS AND SNARES
+I HAVE ALREADY COME;
+'TIS GRACE THAT BROUGHT ME SAFE THUS FAR
+AND GRACE WILL LEAD ME HOME.
+
+4. THE LORD HAS PROMISED GOOD TO ME,
+HIS WORD MY HOPE SECURES;
+HE WILL MY SHIELD AND FORTRESS BE
+AS LONG AS LIFE ENDURES.
+
+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'D FIRST BEGUN.
+
+CCLI no.: 22025
+Words/Music: Edwin Excell, John Newton, John P. Rees
+Public Domain
=== added file 'tests/resources/lyrixsongs/A06.TXT'
--- tests/resources/lyrixsongs/A06.TXT 1970-01-01 00:00:00 +0000
+++ tests/resources/lyrixsongs/A06.TXT 2015-12-20 19:34:43 +0000
@@ -0,0 +1,31 @@
+
+----------------------------------
+A06
+AMAZING GRACE, HOW SWEET THE SOUND
+----------------------------------
+
+1. AMAZING GRACE, HOW SWEET THE SOUND
+THAT SAVED A WRETCH LIKE ME
+I ONCE WAS LOST, BUT NOW I'M 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. THROUGH MANY DANGERS, TOILS AND SNARES
+I HAVE ALREADY COME, 'TWAS GRACE
+THAT BROUGHT ME SAFE THUS FAR
+AND GRACE WILL LEAD ME HOME
+
+4. 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'VE FIRST BEGUN
+
+5. PRAISE GOD, PRAISE GOD
+PRAISE GOD, PRAISE GOD
+PRAISE GOD, PRAISE GOD
+PRAISE GOD..
+
=== added file 'tests/resources/lyrixsongs/AO05.TXT'
--- tests/resources/lyrixsongs/AO05.TXT 1970-01-01 00:00:00 +0000
+++ tests/resources/lyrixsongs/AO05.TXT 2015-12-20 19:34:43 +0000
@@ -0,0 +1,48 @@
+
+--------------------------------
+AO05
+IN DIE REGTERHAND VAN HOM WAT IN
+--------------------------------
+
+1. IN DIE REGTERHAND VAN HOM
+WAT IN MAJESTEIT REGEER
+L�IE BOEKROL VAN VERLOSSING
+SEWEMAAL VERSE�+EN NIEMAND KON GEVIND WORD
+OM SY SE� OOP TE BREEK
+TOT DIE LAM VAN GOD NA VORE KOM
+OM DIE BOEKROL SELF TE NEEM
+DIE VIER-EN-TWINTIG OUDERLINGE
+VAL TOE VOOR HOM NEER
+SAAM MET AL DIE ENG'LE
+IN DIE GANSE HEMELLE�
+EN ELKE WESE WAT ASEM HET
+OP DIE AARDE, IN DIE SEE, EN OOR DIE
+LENGTE EN DIE BREEDTE
+VAN DIE SKEPPING BRING HOM EER
+
+2. HERE U ALLEEN IS WAARDIG
+OM DIE LEWENSBOEK TE NEEM
+EN U ALLEEN IS MAGTIG
+OM SY SE� OOP TE BREEK,
+WANT U'T VIR ONS GESTERWE,
+MET U BLOED HET U BETAAL
+OM ONS LOS TE KOOP
+UIT ELKE STAM EN NASIE,VOLK EN TAAL
+
+AL DIE LOF EN DIE KRAG
+EN DIE EER
+EN DIE HEERLIKHEID
+EN RYKDON EN STERKTE
+AAN U ALLEEN O HEER
+AL DIE LOF EN DIE KRAG
+EN DIE EER
+EN WYSHEID AAN DIE LAM
+OP DIE TROON
+WAT NOU EN TOT IN
+EWIGHEID REGEER
+NOU EN TOT IN EWIGHEID
+REGEER
+DIE LAM WAT TOT IN
+EWIGHEID REGEER
+
=== added file 'tests/resources/lyrixsongs/Amazing Grace.json'
--- tests/resources/lyrixsongs/Amazing Grace.json 1970-01-01 00:00:00 +0000
+++ tests/resources/lyrixsongs/Amazing Grace.json 2015-12-20 19:34:43 +0000
@@ -0,0 +1,26 @@
+{
+ "title": "AMAZING GRACE, HOW SWEET THE SOUND",
+ "verse_order_list": [],
+ "verses": [
+ [
+ "1. AMAZING GRACE, HOW SWEET THE SOUND\nTHAT SAVED A WRETCH LIKE ME\nI ONCE WAS LOST, BUT NOW I'M FOUND\nWAS BLIND, BUT NOW I SEE",
+ "v"
+ ],
+ [
+ "2. '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"
+ ],
+ [
+ "3. THROUGH MANY DANGERS, TOILS AND SNARES\nI HAVE ALREADY COME, 'TWAS GRACE\nTHAT BROUGHT ME SAFE THUS FAR\nAND GRACE WILL LEAD ME HOME",
+ "v"
+ ],
+ [
+ "4. 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'VE FIRST BEGUN",
+ "v"
+ ],
+ [
+ "5. PRAISE GOD, PRAISE GOD\nPRAISE GOD, PRAISE GOD\nPRAISE GOD, PRAISE GOD\nPRAISE GOD..",
+ "v"
+ ]
+ ]
+}
=== added file 'tests/resources/lyrixsongs/Amazing Grace2.json'
--- tests/resources/lyrixsongs/Amazing Grace2.json 1970-01-01 00:00:00 +0000
+++ tests/resources/lyrixsongs/Amazing Grace2.json 2015-12-20 19:34:43 +0000
@@ -0,0 +1,33 @@
+{
+ "authors": [
+ "John Newton",
+ "Edwin Excell",
+ "John P. Rees"
+ ],
+ "ccli_number": "22025",
+ "copyright": "Public Domain",
+ "title": "AMAZING GRACE",
+ "verse_order_list": [],
+ "verses": [
+ [
+ "1. 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"
+ ],
+ [
+ "2. '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"
+ ],
+ [
+ "3. THROUGH MANY DANGERS, TRIALS AND SNARES\nI HAVE ALREADY COME;\n'TIS GRACE THAT BROUGHT ME SAFE THUS FAR\nAND GRACE WILL LEAD ME HOME.",
+ "v"
+ ],
+ [
+ "4. THE LORD HAS PROMISED GOOD TO ME,\nHIS WORD MY HOPE SECURES;\nHE WILL MY SHIELD AND FORTRESS BE\nAS LONG AS LIFE ENDURES.",
+ "v"
+ ],
+ [
+ "5. 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'D FIRST BEGUN.",
+ "v"
+ ]
+ ]
+}
=== added file 'tests/resources/lyrixsongs/in die regterhand.json'
--- tests/resources/lyrixsongs/in die regterhand.json 1970-01-01 00:00:00 +0000
+++ tests/resources/lyrixsongs/in die regterhand.json 2015-12-20 19:34:43 +0000
@@ -0,0 +1,18 @@
+{
+ "title": "IN DIE REGTERHAND VAN HOM WAT IN",
+ "verse_order_list": [],
+ "verses": [
+ [
+ "1. IN DIE REGTERHAND VAN HOM\nWAT IN MAJESTEIT REGEER\nLк DIE BOEKROL VAN VERLOSSING\nSEWEMAAL VERSEлL\nEN NIEMAND KON GEVIND WORD\nOM SY SEлLS OOP TE BREEK\nTOT DIE LAM VAN GOD NA VORE KOM\nOM DIE BOEKROL SELF TE NEEM\nDIE VIER-EN-TWINTIG OUDERLINGE\nVAL TOE VOOR HOM NEER\nSAAM MET AL DIE ENG'LE\nIN DIE GANSE HEMELLEлR,\nEN ELKE WESE WAT ASEM HET\nOP DIE AARDE, IN DIE SEE, EN OOR DIE\nLENGTE EN DIE BREEDTE\nVAN DIE SKEPPING BRING HOM EER",
+ "v"
+ ],
+ [
+ "2. HERE U ALLEEN IS WAARDIG\nOM DIE LEWENSBOEK TE NEEM\nEN U ALLEEN IS MAGTIG\nOM SY SEлLS OOP TE BREEK,\nWANT U'T VIR ONS GESTERWE,\nMET U BLOED HET U BETAAL\nOM ONS LOS TE KOOP\nUIT ELKE STAM EN NASIE,VOLK EN TAAL",
+ "v"
+ ],
+ [
+ "AL DIE LOF EN DIE KRAG\nEN DIE EER\nEN DIE HEERLIKHEID\nEN RYKDON EN STERKTE\nAAN U ALLEEN O HEER\nAL DIE LOF EN DIE KRAG\nEN DIE EER\nEN WYSHEID AAN DIE LAM\nOP DIE TROON\nWAT NOU EN TOT IN\nEWIGHEID REGEER\nNOU EN TOT IN EWIGHEID\nREGEER\nDIE LAM WAT TOT IN\nEWIGHEID REGEER",
+ "v"
+ ]
+ ]
+}
=== added file 'tests/resources/songbeamersongs/Lobsinget dem Herrn.json'
--- tests/resources/songbeamersongs/Lobsinget dem Herrn.json 1970-01-01 00:00:00 +0000
+++ tests/resources/songbeamersongs/Lobsinget dem Herrn.json 2015-12-20 19:34:43 +0000
@@ -0,0 +1,12 @@
+{
+ "title": "GL 1 - Lobsinget dem Herrn",
+ "verses": [
+ ["1. Lobsinget dem Herrn,\no preiset Ihn gern!\nAnbetung und Lob Ihm gebühret.\n", "v"],
+ ["2. Lobsingt Seiner Lieb´,\ndie einzig ihn trieb,\nzu sterben für unsere Sünden!\n", "v"],
+ ["3. Lobsingt Seiner Macht!\nSein Werk ist vollbracht:\nEr sitzet zur Rechten des Vaters.\n", "v"],
+ ["4. Lobsingt seiner Treu´,\ndie immerdar neu,\nbis Er uns zur Herrlichket führet!\n\n", "v"]
+ ],
+ "song_book_name": "Glaubenslieder I",
+ "song_number": "1",
+ "authors": ["Carl Brockhaus", "Johann Jakob Vetter"]
+}
=== added file 'tests/resources/songselect/TestSong-bin.json'
--- tests/resources/songselect/TestSong-bin.json 1970-01-01 00:00:00 +0000
+++ tests/resources/songselect/TestSong-bin.json 2015-12-20 19:34:43 +0000
@@ -0,0 +1,25 @@
+{
+ "authors": [
+ "Author One",
+ "Author Two"
+ ],
+ "ccli_number": "0000000",
+ "song_number": 0,
+ "title": "Test Song",
+ "topics": [
+ "Adoration",
+ "Praise"
+ ],
+ "copyright": "2011 OpenLP Programmer One (Admin. by OpenLP One) | Openlp Programmer Two (Admin. by OpenLP Two)",
+ "verse_order_list": [],
+ "verses": [
+ [
+ "Line One Verse One\nLine Two Verse One\nLine Three Verse One\nLine Four Verse One\n",
+ "v"
+ ],
+ [
+ "Line One Verse Two\nLine Two Verse Two\nLine Three Verse Two\nLine Four Verse Two\n",
+ "v"
+ ]
+ ]
+}
=== added file 'tests/resources/songselect/TestSong-txt.json'
--- tests/resources/songselect/TestSong-txt.json 1970-01-01 00:00:00 +0000
+++ tests/resources/songselect/TestSong-txt.json 2015-12-20 19:34:43 +0000
@@ -0,0 +1,21 @@
+{
+ "authors": [
+ "Author One",
+ "Author Two"
+ ],
+ "ccli_number": "0000000",
+ "song_number": 0,
+ "title": "Test Song",
+ "copyright": "© 2011 OpenLP Programmer One (Admin. by OpenLP One)",
+ "verse_order_list": [],
+ "verses": [
+ [
+ "Line One Verse One\nLine Two Verse One\nLine Three Verse One\nLine Four Verse One\n",
+ "v"
+ ],
+ [
+ "Line One Verse Two\nLine Two Verse Two\nLine Three Verse Two\nLine Four Verse Two\n",
+ "v"
+ ]
+ ]
+}
=== added directory 'tests/resources/videopsalmsongs'
=== added file 'tests/resources/videopsalmsongs/as-safe-a-stronghold.json'
--- tests/resources/videopsalmsongs/as-safe-a-stronghold.json 1970-01-01 00:00:00 +0000
+++ tests/resources/videopsalmsongs/as-safe-a-stronghold.json 2015-12-20 19:34:43 +0000
@@ -0,0 +1,35 @@
+{
+ "authors": [
+ ["Martin Luther", "words"],
+ ["Unknown", "music"]
+ ],
+ "ccli_number": "12345",
+ "comments": "This is\nthe first comment\nThis is\nthe second comment\nThis is\nthe third comment\n",
+ "copyright": "Public Domain",
+ "song_book_name": "SongBook1",
+ "song_number": 0,
+ "title": "A Safe Stronghold Our God is Still",
+ "topics": [
+ "tema1",
+ "tema2"
+ ],
+ "verse_order_list": [],
+ "verses": [
+ [
+ "As safe a stronghold our God is still,\nA trusty shield and weapon;\nHe’ll help us clear from all the ill\nThat hath us now o’ertaken.\nThe ancient prince of hell\nHath risen with purpose fell;\nStrong mail of craft and power\nHe weareth in this hour;\nOn earth is not His fellow.",
+ "v"
+ ],
+ [
+ "With force of arms we nothing can,\nFull soon were we down-ridden;\nBut for us fights the proper Man,\nWhom God Himself hath bidden.\nAsk ye: Who is this same?\nChrist Jesus is His name,\nThe Lord Sabaoth’s Son;\nHe, and no other one,\nShall conquer in the battle.",
+ "v"
+ ],
+ [
+ "And were this world all devils o’er,\nAnd watching to devour us,\nWe lay it not to heart so sore;\nNot they can overpower us.\nAnd let the prince of ill\nLook grim as e’er he will,\nHe harms us not a whit;\nFor why? his doom is writ;\nA word shall quickly slay him.",
+ "v"
+ ],
+ [
+ "God’s word, for all their craft and force,\nOne moment will not linger,\nBut, spite of hell, shall have its course;\n’Tis written by His finger.\nAnd though they take our life,\nGoods, honour, children, wife,\nYet is their profit small:\nThese things shall vanish all;\nThe city of God remaineth.",
+ "v"
+ ]
+ ]
+}
=== added file 'tests/resources/videopsalmsongs/videopsalm-as-safe-a-stronghold.json'
--- tests/resources/videopsalmsongs/videopsalm-as-safe-a-stronghold.json 1970-01-01 00:00:00 +0000
+++ tests/resources/videopsalmsongs/videopsalm-as-safe-a-stronghold.json 2015-12-20 19:34:43 +0000
@@ -0,0 +1,47 @@
+{Abbreviation:"SB1",Copyright:"Public domain",Songs:[{ID:3,Composer:"Unknown",Author:"Martin Luther",Copyright:"Public
+Domain",Theme:"tema1
+tema2",CCLI:"12345",Alias:"A safe stronghold",Memo1:"This is
+the first comment
+",Memo2:"This is
+the second comment
+",Memo3:"This is
+the third comment
+",Reference:"reference",Guid:"jtCkrJdPIUOmECjaQylg/g",Verses:[{
+Text:"As safe a stronghold our God is still,
+A trusty shield and weapon;
+He’ll help us clear from all the ill
+That hath us now o’ertaken.
+The ancient prince of hell
+Hath risen with purpose fell;
+Strong mail of craft and power
+He weareth in this hour;
+On earth is not His fellow."},{ID:2,
+Text:"With force of arms we nothing can,
+Full soon were we down-ridden;
+But for us fights the proper Man,
+Whom God Himself hath bidden.
+Ask ye: Who is this same?
+Christ Jesus is His name,
+The Lord Sabaoth’s Son;
+He, and no other one,
+Shall conquer in the battle."},{ID:3,
+Text:"And were this world all devils o’er,
+And watching to devour us,
+We lay it not to heart so sore;
+Not they can overpower us.
+And let the prince of ill
+Look grim as e’er he will,
+He harms us not a whit;
+For why? his doom is writ;
+A word shall quickly slay him."},{ID:4,
+Text:"God’s word, for all their craft and force,
+One moment will not linger,
+But, spite of hell, shall have its course;
+’Tis written by His finger.
+And though they take our life,
+Goods, honour, children, wife,
+Yet is their profit small:
+These things shall vanish all;
+The city of God remaineth."}],AudioFile:"282.mp3",IsAudioFileEnabled:1,
+Text:"A Safe Stronghold Our God is Still"}],Guid:"khiHU2blX0Kb41dGdbDLhA",VersionDate:"20121012000000",
+Text:"SongBook1"}
References