Add support for importing OPS Pro song DB. Translations are supported using a {translation} tag.

Fix bug that prevents song book entries to be imported.

bzr-revno: 2631
This commit is contained in:
second@tgc.dk 2016-03-22 17:45:23 +00:00 committed by Tim Bentley
commit b74221daff
12 changed files with 666 additions and 19 deletions

View File

@ -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']

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'))

View File

@ -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
]
]
}

View File

@ -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
]
]
}

View File

@ -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
]
]
}

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.