forked from openlp/openlp
Change duplicate check to pass int-string tuples to workers, to workaround bug #1388850, also added multiprocessing.freeze_support to __main__ to support multiprocessing in windows builds.
Try to fix DVD 0 track length by waiting. Fixes bug 1387293. Fix for import of Words of Worship file, bug 1388768, added tests. bzr-revno: 2440
This commit is contained in:
commit
c16d32dc8a
@ -28,7 +28,9 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import multiprocessing
|
||||||
|
|
||||||
|
from openlp.core.common import is_win, is_macosx
|
||||||
from openlp.core import main
|
from openlp.core import main
|
||||||
|
|
||||||
|
|
||||||
@ -36,9 +38,13 @@ if __name__ == '__main__':
|
|||||||
"""
|
"""
|
||||||
Instantiate and run the application.
|
Instantiate and run the application.
|
||||||
"""
|
"""
|
||||||
|
# Add support for using multiprocessing from frozen Windows executable (built using PyInstaller),
|
||||||
|
# see https://docs.python.org/3/library/multiprocessing.html#multiprocessing.freeze_support
|
||||||
|
if is_win():
|
||||||
|
multiprocessing.freeze_support()
|
||||||
# Mac OS X passes arguments like '-psn_XXXX' to the application. This argument is actually a process serial number.
|
# Mac OS X passes arguments like '-psn_XXXX' to the application. This argument is actually a process serial number.
|
||||||
# However, this causes a conflict with other OpenLP arguments. Since we do not use this argument we can delete it
|
# However, this causes a conflict with other OpenLP arguments. Since we do not use this argument we can delete it
|
||||||
# to avoid any potential conflicts.
|
# to avoid any potential conflicts.
|
||||||
if sys.platform.startswith('darwin'):
|
if is_macosx():
|
||||||
sys.argv = [x for x in sys.argv if not x.startswith('-psn')]
|
sys.argv = [x for x in sys.argv if not x.startswith('-psn')]
|
||||||
main()
|
main()
|
||||||
|
@ -28,4 +28,4 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
"""
|
"""
|
||||||
The Projector driver module.
|
The Projector driver module.
|
||||||
"""
|
"""
|
||||||
|
@ -446,6 +446,13 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector, RegistryPropert
|
|||||||
# Set media length info
|
# Set media length info
|
||||||
self.playback_length = self.vlc_media_player.get_length()
|
self.playback_length = self.vlc_media_player.get_length()
|
||||||
log.debug('playback_length: %d ms' % self.playback_length)
|
log.debug('playback_length: %d ms' % self.playback_length)
|
||||||
|
# if length is 0, wait a bit, maybe vlc will change its mind...
|
||||||
|
loop_count = 0
|
||||||
|
while self.playback_length == 0 and loop_count < 20:
|
||||||
|
sleep(0.1)
|
||||||
|
self.playback_length = self.vlc_media_player.get_length()
|
||||||
|
loop_count += 1
|
||||||
|
log.debug('in loop, playback_length: %d ms' % self.playback_length)
|
||||||
self.position_slider.setMaximum(self.playback_length)
|
self.position_slider.setMaximum(self.playback_length)
|
||||||
# setup start and end time
|
# setup start and end time
|
||||||
rounded_vlc_ms_length = int(round(self.playback_length / 100.0) * 100.0)
|
rounded_vlc_ms_length = int(round(self.playback_length / 100.0) * 100.0)
|
||||||
|
@ -48,14 +48,15 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def song_generator(songs):
|
def song_generator(songs):
|
||||||
"""
|
"""
|
||||||
This is a generator function to return tuples of two songs. When completed then all songs have once been returned
|
This is a generator function to return tuples of tuple with two songs and their position in the song array.
|
||||||
combined with any other songs.
|
When completed then all songs have once been returned combined with any other songs.
|
||||||
|
|
||||||
:param songs: All songs in the database.
|
:param songs: All songs in the database.
|
||||||
"""
|
"""
|
||||||
for outer_song_counter in range(len(songs) - 1):
|
for outer_song_counter in range(len(songs) - 1):
|
||||||
for inner_song_counter in range(outer_song_counter + 1, len(songs)):
|
for inner_song_counter in range(outer_song_counter + 1, len(songs)):
|
||||||
yield (songs[outer_song_counter], songs[inner_song_counter])
|
yield ((outer_song_counter, songs[outer_song_counter].search_lyrics),
|
||||||
|
(inner_song_counter, songs[inner_song_counter].search_lyrics))
|
||||||
|
|
||||||
|
|
||||||
class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
|
class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
|
||||||
@ -187,16 +188,17 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
|
|||||||
# Do not accept any further tasks. Also this closes the processes if all tasks are done.
|
# Do not accept any further tasks. Also this closes the processes if all tasks are done.
|
||||||
pool.close()
|
pool.close()
|
||||||
# While the processes are still working, start to look at the results.
|
# While the processes are still working, start to look at the results.
|
||||||
for song_tuple in result:
|
for pos_tuple in result:
|
||||||
self.duplicate_search_progress_bar.setValue(self.duplicate_search_progress_bar.value() + 1)
|
self.duplicate_search_progress_bar.setValue(self.duplicate_search_progress_bar.value() + 1)
|
||||||
# The call to process_events() will keep the GUI responsive.
|
# The call to process_events() will keep the GUI responsive.
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
if self.break_search:
|
if self.break_search:
|
||||||
pool.terminate()
|
pool.terminate()
|
||||||
return
|
return
|
||||||
if song_tuple is None:
|
if pos_tuple is None:
|
||||||
continue
|
continue
|
||||||
song1, song2 = song_tuple
|
song1 = songs[pos_tuple[0]]
|
||||||
|
song2 = songs[pos_tuple[1]]
|
||||||
duplicate_added = self.add_duplicates_to_song_list(song1, song2)
|
duplicate_added = self.add_duplicates_to_song_list(song1, song2)
|
||||||
if duplicate_added:
|
if duplicate_added:
|
||||||
self.found_duplicates_edit.appendPlainText(song1.title + " = " + song2.title)
|
self.found_duplicates_edit.appendPlainText(song1.title + " = " + song2.title)
|
||||||
|
@ -99,7 +99,7 @@ class WordsOfWorshipImport(SongImport):
|
|||||||
"""
|
"""
|
||||||
Initialise the Words of Worship importer.
|
Initialise the Words of Worship importer.
|
||||||
"""
|
"""
|
||||||
SongImport.__init__(self, manager, **kwargs)
|
super(WordsOfWorshipImport, self).__init__(manager, **kwargs)
|
||||||
|
|
||||||
def do_import(self):
|
def do_import(self):
|
||||||
"""
|
"""
|
||||||
@ -112,17 +112,17 @@ class WordsOfWorshipImport(SongImport):
|
|||||||
return
|
return
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
song_data = open(source, 'rb')
|
song_data = open(source, 'rb')
|
||||||
if song_data.read(19) != 'WoW File\nSong Words':
|
if song_data.read(19).decode() != 'WoW File\nSong Words':
|
||||||
self.log_error(source,
|
self.log_error(source,
|
||||||
str(translate('SongsPlugin.WordsofWorshipSongImport',
|
str(translate('SongsPlugin.WordsofWorshipSongImport',
|
||||||
'Invalid Words of Worship song file. Missing "Wow File\\nSong '
|
'Invalid Words of Worship song file. Missing "WoW File\\nSong '
|
||||||
'Words" header.')))
|
'Words" header.')))
|
||||||
continue
|
continue
|
||||||
# Seek to byte which stores number of blocks in the song
|
# Seek to byte which stores number of blocks in the song
|
||||||
song_data.seek(56)
|
song_data.seek(56)
|
||||||
no_of_blocks = ord(song_data.read(1))
|
no_of_blocks = ord(song_data.read(1))
|
||||||
song_data.seek(66)
|
song_data.seek(66)
|
||||||
if song_data.read(16) != 'CSongDoc::CBlock':
|
if song_data.read(16).decode() != 'CSongDoc::CBlock':
|
||||||
self.log_error(source,
|
self.log_error(source,
|
||||||
str(translate('SongsPlugin.WordsofWorshipSongImport',
|
str(translate('SongsPlugin.WordsofWorshipSongImport',
|
||||||
'Invalid Words of Worship song file. Missing "CSongDoc::CBlock" '
|
'Invalid Words of Worship song file. Missing "CSongDoc::CBlock" '
|
||||||
@ -131,11 +131,17 @@ class WordsOfWorshipImport(SongImport):
|
|||||||
# Seek to the beginning of the first block
|
# Seek to the beginning of the first block
|
||||||
song_data.seek(82)
|
song_data.seek(82)
|
||||||
for block in range(no_of_blocks):
|
for block in range(no_of_blocks):
|
||||||
|
skip_char_at_end = True
|
||||||
self.lines_to_read = ord(song_data.read(4)[:1])
|
self.lines_to_read = ord(song_data.read(4)[:1])
|
||||||
block_text = ''
|
block_text = ''
|
||||||
while self.lines_to_read:
|
while self.lines_to_read:
|
||||||
self.line_text = str(song_data.read(ord(song_data.read(1))), 'cp1252')
|
self.line_text = str(song_data.read(ord(song_data.read(1))), 'cp1252')
|
||||||
song_data.seek(1, os.SEEK_CUR)
|
if skip_char_at_end:
|
||||||
|
skip_char = ord(song_data.read(1))
|
||||||
|
# Check if we really should skip a char. In some wsg files we shouldn't
|
||||||
|
if skip_char != 0:
|
||||||
|
song_data.seek(-1, os.SEEK_CUR)
|
||||||
|
skip_char_at_end = False
|
||||||
if block_text:
|
if block_text:
|
||||||
block_text += '\n'
|
block_text += '\n'
|
||||||
block_text += self.line_text
|
block_text += self.line_text
|
||||||
|
@ -59,12 +59,14 @@ def songs_probably_equal(song_tupel):
|
|||||||
:param song_tupel: A tuple of two songs to compare.
|
:param song_tupel: A tuple of two songs to compare.
|
||||||
"""
|
"""
|
||||||
song1, song2 = song_tupel
|
song1, song2 = song_tupel
|
||||||
if len(song1.search_lyrics) < len(song2.search_lyrics):
|
pos1, lyrics1 = song1
|
||||||
small = song1.search_lyrics
|
pos2, lyrics2 = song2
|
||||||
large = song2.search_lyrics
|
if len(lyrics1) < len(lyrics2):
|
||||||
|
small = lyrics1
|
||||||
|
large = lyrics2
|
||||||
else:
|
else:
|
||||||
small = song2.search_lyrics
|
small = lyrics2
|
||||||
large = song1.search_lyrics
|
large = lyrics1
|
||||||
differ = difflib.SequenceMatcher(a=large, b=small)
|
differ = difflib.SequenceMatcher(a=large, b=small)
|
||||||
diff_tuples = differ.get_opcodes()
|
diff_tuples = differ.get_opcodes()
|
||||||
diff_no_typos = _remove_typos(diff_tuples)
|
diff_no_typos = _remove_typos(diff_tuples)
|
||||||
@ -77,7 +79,7 @@ def songs_probably_equal(song_tupel):
|
|||||||
length_of_equal_blocks += _op_length(element)
|
length_of_equal_blocks += _op_length(element)
|
||||||
|
|
||||||
if length_of_equal_blocks >= MIN_BLOCK_SIZE:
|
if length_of_equal_blocks >= MIN_BLOCK_SIZE:
|
||||||
return song1, song2
|
return pos1, pos2
|
||||||
# Check 2: Similarity based on the relative length of the longest equal block.
|
# Check 2: Similarity based on the relative length of the longest equal block.
|
||||||
# Calculate the length of the largest equal block of the diff set.
|
# Calculate the length of the largest equal block of the diff set.
|
||||||
length_of_longest_equal_block = 0
|
length_of_longest_equal_block = 0
|
||||||
@ -85,7 +87,7 @@ def songs_probably_equal(song_tupel):
|
|||||||
if element[0] == "equal" and _op_length(element) > length_of_longest_equal_block:
|
if element[0] == "equal" and _op_length(element) > length_of_longest_equal_block:
|
||||||
length_of_longest_equal_block = _op_length(element)
|
length_of_longest_equal_block = _op_length(element)
|
||||||
if length_of_longest_equal_block > len(small) * 2 // 3:
|
if length_of_longest_equal_block > len(small) * 2 // 3:
|
||||||
return song1, song2
|
return pos1, pos2
|
||||||
# Both checks failed. We assume the songs are not equal.
|
# Both checks failed. We assume the songs are not equal.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -157,4 +157,4 @@ class TestSettingsForm(TestCase):
|
|||||||
|
|
||||||
# THEN: The general tab's cancel() method should have been called, but not the themes tab
|
# THEN: The general tab's cancel() method should have been called, but not the themes tab
|
||||||
mocked_general_cancel.assert_called_with()
|
mocked_general_cancel.assert_called_with()
|
||||||
self.assertEqual(0, mocked_theme_cancel.call_count, 'The Themes tab\'s cancel() should not have been called')
|
self.assertEqual(0, mocked_theme_cancel.call_count, 'The Themes tab\'s cancel() should not have been called')
|
||||||
|
@ -95,4 +95,4 @@ class TestImageMediaItem(TestCase, TestMixin):
|
|||||||
self.form.save()
|
self.form.save()
|
||||||
# THEN: the post process should be requested
|
# THEN: the post process should be requested
|
||||||
self.assertEqual(1, self.form.settings_form.register_post_process.call_count,
|
self.assertEqual(1, self.form.settings_form.register_post_process.call_count,
|
||||||
'Image Post processing should have been requested')
|
'Image Post processing should have been requested')
|
||||||
|
@ -58,8 +58,6 @@ class TestLib(TestCase):
|
|||||||
i love that old cross where the dearest and best for a world of lost sinners was slain so ill cherish the
|
i love that old cross where the dearest and best for a world of lost sinners was slain so ill cherish the
|
||||||
old rugged cross till my trophies at last i lay down i will cling to the old rugged cross and exchange it
|
old rugged cross till my trophies at last i lay down i will cling to the old rugged cross and exchange it
|
||||||
some day for a crown'''
|
some day for a crown'''
|
||||||
self.song1 = MagicMock()
|
|
||||||
self.song2 = MagicMock()
|
|
||||||
|
|
||||||
def clean_string_test(self):
|
def clean_string_test(self):
|
||||||
"""
|
"""
|
||||||
@ -92,53 +90,53 @@ class TestLib(TestCase):
|
|||||||
Test the songs_probably_equal function with twice the same song.
|
Test the songs_probably_equal function with twice the same song.
|
||||||
"""
|
"""
|
||||||
# GIVEN: Two equal songs.
|
# GIVEN: Two equal songs.
|
||||||
self.song1.search_lyrics = self.full_lyrics
|
song_tuple1 = (2, self.full_lyrics)
|
||||||
self.song2.search_lyrics = self.full_lyrics
|
song_tuple2 = (4, self.full_lyrics)
|
||||||
|
|
||||||
# WHEN: We compare those songs for equality.
|
# WHEN: We compare those songs for equality.
|
||||||
result = songs_probably_equal((self.song1, self.song2))
|
result = songs_probably_equal((song_tuple1, song_tuple2))
|
||||||
|
|
||||||
# THEN: The result should be a tuple..
|
# THEN: The result should be a tuple..
|
||||||
assert result == (self.song1, self.song2), 'The result should be the tuble of songs'
|
assert result == (2, 4), 'The result should be the tuble of song positions'
|
||||||
|
|
||||||
def songs_probably_equal_short_song_test(self):
|
def songs_probably_equal_short_song_test(self):
|
||||||
"""
|
"""
|
||||||
Test the songs_probably_equal function with a song and a shorter version of the same song.
|
Test the songs_probably_equal function with a song and a shorter version of the same song.
|
||||||
"""
|
"""
|
||||||
# GIVEN: A song and a short version of the same song.
|
# GIVEN: A song and a short version of the same song.
|
||||||
self.song1.search_lyrics = self.full_lyrics
|
song_tuple1 = (1, self.full_lyrics)
|
||||||
self.song2.search_lyrics = self.short_lyrics
|
song_tuple2 = (3, self.short_lyrics)
|
||||||
|
|
||||||
# WHEN: We compare those songs for equality.
|
# WHEN: We compare those songs for equality.
|
||||||
result = songs_probably_equal((self.song1, self.song2))
|
result = songs_probably_equal((song_tuple1, song_tuple2))
|
||||||
|
|
||||||
# THEN: The result should be a tuple..
|
# THEN: The result should be a tuple..
|
||||||
assert result == (self.song1, self.song2), 'The result should be the tuble of songs'
|
assert result == (1, 3), 'The result should be the tuble of song positions'
|
||||||
|
|
||||||
def songs_probably_equal_error_song_test(self):
|
def songs_probably_equal_error_song_test(self):
|
||||||
"""
|
"""
|
||||||
Test the songs_probably_equal function with a song and a very erroneous version of the same song.
|
Test the songs_probably_equal function with a song and a very erroneous version of the same song.
|
||||||
"""
|
"""
|
||||||
# GIVEN: A song and the same song with lots of errors.
|
# GIVEN: A song and the same song with lots of errors.
|
||||||
self.song1.search_lyrics = self.full_lyrics
|
song_tuple1 = (4, self.full_lyrics)
|
||||||
self.song2.search_lyrics = self.error_lyrics
|
song_tuple2 = (7, self.error_lyrics)
|
||||||
|
|
||||||
# WHEN: We compare those songs for equality.
|
# WHEN: We compare those songs for equality.
|
||||||
result = songs_probably_equal((self.song1, self.song2))
|
result = songs_probably_equal((song_tuple1, song_tuple2))
|
||||||
|
|
||||||
# THEN: The result should be a tuple of songs..
|
# THEN: The result should be a tuple of song positions.
|
||||||
assert result == (self.song1, self.song2), 'The result should be the tuble of songs'
|
assert result == (4, 7), 'The result should be the tuble of song positions'
|
||||||
|
|
||||||
def songs_probably_equal_different_song_test(self):
|
def songs_probably_equal_different_song_test(self):
|
||||||
"""
|
"""
|
||||||
Test the songs_probably_equal function with two different songs.
|
Test the songs_probably_equal function with two different songs.
|
||||||
"""
|
"""
|
||||||
# GIVEN: Two different songs.
|
# GIVEN: Two different songs.
|
||||||
self.song1.search_lyrics = self.full_lyrics
|
song_tuple1 = (5, self.full_lyrics)
|
||||||
self.song2.search_lyrics = self.different_lyrics
|
song_tuple2 = (8, self.different_lyrics)
|
||||||
|
|
||||||
# WHEN: We compare those songs for equality.
|
# WHEN: We compare those songs for equality.
|
||||||
result = songs_probably_equal((self.song1, self.song2))
|
result = songs_probably_equal((song_tuple1, song_tuple2))
|
||||||
|
|
||||||
# THEN: The result should be None.
|
# THEN: The result should be None.
|
||||||
assert result is None, 'The result should be None'
|
assert result is None, 'The result should be None'
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2014 Raoul Snyman #
|
||||||
|
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
||||||
|
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
||||||
|
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
||||||
|
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
||||||
|
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
||||||
|
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
||||||
|
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 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 Words of Worship song importer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
|
from openlp.plugins.songs.lib.importers.wordsofworship import WordsOfWorshipImport
|
||||||
|
|
||||||
|
TEST_PATH = os.path.abspath(
|
||||||
|
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'wordsofworshipsongs'))
|
||||||
|
|
||||||
|
|
||||||
|
class TestWordsOfWorshipFileImport(SongImportTestHelper):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.importer_class_name = 'WordsOfWorshipImport'
|
||||||
|
self.importer_module_name = 'wordsofworship'
|
||||||
|
super(TestWordsOfWorshipFileImport, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def test_song_import(self):
|
||||||
|
"""
|
||||||
|
Test that loading a Words of Worship file works correctly
|
||||||
|
"""
|
||||||
|
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).wow-song')],
|
||||||
|
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).json')))
|
||||||
|
self.file_import([os.path.join(TEST_PATH, 'When morning gilds the skies.wsg')],
|
||||||
|
self.load_external_result_data(os.path.join(TEST_PATH, 'When morning gilds the skies.json')))
|
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"John Newton (1725-1807)"
|
||||||
|
],
|
||||||
|
"title": "Amazing Grace (6 Verses)",
|
||||||
|
"verse_order_list": [],
|
||||||
|
"verses": [
|
||||||
|
[
|
||||||
|
"Amazing grace! how sweet the sound\nThat saved a wretch like me;\nI once was lost, but now am found,\nWas blind, but now I see.",
|
||||||
|
"V"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"'Twas grace that taught my heart to fear,\nAnd grace my fears relieved;\nHow precious did that grace appear,\nThe hour I first believed!",
|
||||||
|
"V"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Through many dangers, toils and snares\nI have already come;\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.",
|
||||||
|
"V"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"The Lord has promised good to me,\nHis word my hope secures;\nHe will my shield and portion be\nAs long as life endures.",
|
||||||
|
"V"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Yes, when this heart and flesh shall fail,\nAnd mortal life shall cease,\nI shall possess within the veil\nA life of joy and peace.",
|
||||||
|
"V"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"When we've been there ten thousand years,\nBright shining as the sun,\nWe've no less days to sing God's praise\nThan when we first begun.",
|
||||||
|
"V"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"Author Unknown. Tr. Edward Caswall"
|
||||||
|
],
|
||||||
|
"title": "When morning gilds the skies",
|
||||||
|
"verse_order_list": [],
|
||||||
|
"verses": [
|
||||||
|
[
|
||||||
|
"When morning gilds the skies\nMy heart awaking cries:\n'May Jesus Christ be prais'd!'\nAlike at work and prayer to Jesus I repair:\n'May Jesus Christ be prais'd!'",
|
||||||
|
"V"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Does sadness fill my mind?\nA solace here I find:\n'May Jesus Christ be praised!'\nWhen evil thoughts molest,\nWith this I shield my breast:\n'May Jesus Christ be prais'd!'",
|
||||||
|
"V"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"To God, the Word, on high\nThe hosts of angels cry:\n'May Jesus Christ be prais'd!'\nLet mortals, too, upraise\nTheir voice in hymns of praise:\n'May Jesus Christ be prais'd!'",
|
||||||
|
"V"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Let earth's wide circle round\nIn joyful notes resound:\n'May Jesus Christ be prais'd!'\nLet air, and sea, and sky,\nFrom depth to height, reply:\n'May Jesus Christ be prais'd!'",
|
||||||
|
"V"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Be this while life is mine\nMy canticle divine\n'May Jesus Christ be prais'd!'\nBe this the eternal song,\nThrough all the ages long:\n'May Jesus Christ be prais'd!'",
|
||||||
|
"V"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user