← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~tomasgroth/openlp/opspro-import into lp:openlp

 

Tomas Groth has proposed merging lp:~tomasgroth/openlp/opspro-import into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~tomasgroth/openlp/opspro-import/+merge/289587

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.

-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~tomasgroth/openlp/opspro-import into lp:openlp.
=== modified file 'openlp/plugins/songs/lib/importer.py'
--- openlp/plugins/songs/lib/importer.py	2016-01-10 00:17:58 +0000
+++ openlp/plugins/songs/lib/importer.py	2016-03-20 20:33:48 +0000
@@ -78,6 +78,13 @@
         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 @@
     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 @@
             '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 @@
             SongFormat.Lyrix,
             SongFormat.MediaShout,
             SongFormat.OpenSong,
+            SongFormat.OPSPro,
             SongFormat.PowerPraise,
             SongFormat.PowerSong,
             SongFormat.PresentationManager,
@@ -416,7 +436,7 @@
             SongFormat.WordsOfWorship,
             SongFormat.WorshipAssistant,
             SongFormat.WorshipCenterPro,
-            SongFormat.ZionWorx,
+            SongFormat.ZionWorx
         ])
 
     @staticmethod
@@ -465,6 +485,9 @@
 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']

=== added file 'openlp/plugins/songs/lib/importers/opspro.py'
--- openlp/plugins/songs/lib/importers/opspro.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/importers/opspro.py	2016-03-20 20:33:48 +0000
@@ -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

=== modified file 'openlp/plugins/songs/lib/importers/songimport.py'
--- openlp/plugins/songs/lib/importers/songimport.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/lib/importers/songimport.py	2016-03-20 20:33:48 +0000
@@ -371,7 +371,7 @@
             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

=== added file 'tests/functional/openlp_plugins/songs/test_opsproimport.py'
--- tests/functional/openlp_plugins/songs/test_opsproimport.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/songs/test_opsproimport.py	2016-03-20 20:33:48 +0000
@@ -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

=== modified file 'tests/functional/openlp_plugins/songs/test_videopsalm.py'
--- tests/functional/openlp_plugins/songs/test_videopsalm.py	2016-01-08 19:52:24 +0000
+++ tests/functional/openlp_plugins/songs/test_videopsalm.py	2016-03-20 20:33:48 +0000
@@ -23,11 +23,8 @@
 """
 
 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'))

=== added directory 'tests/resources/opsprosongs'
=== added file 'tests/resources/opsprosongs/Amazing Grace.json'
--- tests/resources/opsprosongs/Amazing Grace.json	1970-01-01 00:00:00 +0000
+++ tests/resources/opsprosongs/Amazing Grace.json	2016-03-20 20:33:48 +0000
@@ -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
+        ]
+    ]
+}

=== added file 'tests/resources/opsprosongs/Amazing Grace3.json'
--- tests/resources/opsprosongs/Amazing Grace3.json	1970-01-01 00:00:00 +0000
+++ tests/resources/opsprosongs/Amazing Grace3.json	2016-03-20 20:33:48 +0000
@@ -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
+        ]
+    ]
+}

=== added file 'tests/resources/opsprosongs/You are so faithful.json'
--- tests/resources/opsprosongs/You are so faithful.json	1970-01-01 00:00:00 +0000
+++ tests/resources/opsprosongs/You are so faithful.json	2016-03-20 20:33:48 +0000
@@ -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
+        ]
+    ]
+}

=== added file 'tests/resources/opsprosongs/amazing grace.txt'
--- tests/resources/opsprosongs/amazing grace.txt	1970-01-01 00:00:00 +0000
+++ tests/resources/opsprosongs/amazing grace.txt	2016-03-20 20:33:48 +0000
@@ -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.

=== added file 'tests/resources/opsprosongs/amazing grace2.txt'
--- tests/resources/opsprosongs/amazing grace2.txt	1970-01-01 00:00:00 +0000
+++ tests/resources/opsprosongs/amazing grace2.txt	2016-03-20 20:33:48 +0000
@@ -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.

=== added file 'tests/resources/opsprosongs/amazing grace3.txt'
--- tests/resources/opsprosongs/amazing grace3.txt	1970-01-01 00:00:00 +0000
+++ tests/resources/opsprosongs/amazing grace3.txt	2016-03-20 20:33:48 +0000
@@ -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.

=== added file 'tests/resources/opsprosongs/you are so faithfull.txt'
--- tests/resources/opsprosongs/you are so faithfull.txt	1970-01-01 00:00:00 +0000
+++ tests/resources/opsprosongs/you are so faithfull.txt	2016-03-20 20:33:48 +0000
@@ -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.


References