forked from openlp/openlp
Added support for ews import
This commit is contained in:
parent
54895af75c
commit
0ee684d3a8
@ -34,13 +34,13 @@ EasyWorship song databases into the current installation database.
|
|||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import re
|
import re
|
||||||
|
import zlib
|
||||||
|
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate
|
||||||
from openlp.plugins.songs.lib import VerseType
|
from openlp.plugins.songs.lib import VerseType
|
||||||
from openlp.plugins.songs.lib import retrieve_windows_encoding, strip_rtf
|
from openlp.plugins.songs.lib import retrieve_windows_encoding, strip_rtf
|
||||||
from .songimport import SongImport
|
from .songimport import SongImport
|
||||||
|
|
||||||
RTF_STRIPPING_REGEX = re.compile(r'\{\\tx[^}]*\}')
|
|
||||||
# regex: at least two newlines, can have spaces between them
|
# regex: at least two newlines, can have spaces between them
|
||||||
SLIDE_BREAK_REGEX = re.compile(r'\n *?\n[\n ]*')
|
SLIDE_BREAK_REGEX = re.compile(r'\n *?\n[\n ]*')
|
||||||
NUMBER_REGEX = re.compile(r'[0-9]+')
|
NUMBER_REGEX = re.compile(r'[0-9]+')
|
||||||
@ -77,7 +77,91 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
|
|
||||||
def do_import(self):
|
def do_import(self):
|
||||||
"""
|
"""
|
||||||
Import the songs
|
Determines the type of file to import and calls the appropiate method
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if self.import_source.lower().endswith('ews'):
|
||||||
|
self.import_ews()
|
||||||
|
else:
|
||||||
|
self.import_db()
|
||||||
|
|
||||||
|
def import_ews(self):
|
||||||
|
"""
|
||||||
|
Import the songs from service file
|
||||||
|
The full spec of the ews files can be found here:
|
||||||
|
https://github.com/meinders/lithium-ews/blob/master/docs/ews%20file%20format.md
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
# Open ews file if it exists
|
||||||
|
if not os.path.isfile(self.import_source):
|
||||||
|
return
|
||||||
|
# Make sure there is room for at least a header and one entry
|
||||||
|
if os.path.getsize(self.import_source) < 892:
|
||||||
|
return
|
||||||
|
# Take a stab at how text is encoded
|
||||||
|
self.encoding = 'cp1252'
|
||||||
|
self.ews_file = open(self.import_source, 'rb')
|
||||||
|
# Get file version
|
||||||
|
type, = struct.unpack('<38s', self.ews_file.read(38))
|
||||||
|
version = type.decode()[-3:]
|
||||||
|
# Set fileposition based on filetype/version
|
||||||
|
file_pos = 0
|
||||||
|
if version == ' 5':
|
||||||
|
file_pos = 56
|
||||||
|
elif version == ' 3':
|
||||||
|
file_pos = 48
|
||||||
|
elif version == '1.6':
|
||||||
|
file_pos = 40
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
entry_count = self.get_i32(file_pos)
|
||||||
|
entry_length = self.get_i16(file_pos+4)
|
||||||
|
file_pos += 6
|
||||||
|
self.import_wizard.progress_bar.setMaximum(entry_count)
|
||||||
|
# Loop over songs
|
||||||
|
for x in range(1, entry_count):
|
||||||
|
# Load entry metadata
|
||||||
|
self.set_defaults()
|
||||||
|
self.title = self.get_string(file_pos, 50)
|
||||||
|
resource = self.get_string(file_pos + 51, 255)
|
||||||
|
authors = self.get_string(file_pos + 307, 50)
|
||||||
|
copyright = self.get_string(file_pos + 358, 100)
|
||||||
|
admin = self.get_string(file_pos + 459, 50)
|
||||||
|
cont_ptr = self.get_i32(file_pos + 800)
|
||||||
|
cont_type = self.get_i32(file_pos + 820)
|
||||||
|
notes = self.get_string(file_pos + 1155, 160)
|
||||||
|
self.ccli_number = self.get_string(file_pos + 1410, 10)
|
||||||
|
# Only handle content type 1 (songs)
|
||||||
|
if cont_type != 1:
|
||||||
|
file_pos += entry_length
|
||||||
|
continue
|
||||||
|
# Load song content
|
||||||
|
content_length = self.get_i32(cont_ptr)
|
||||||
|
deflated_content = self.get_bytes(cont_ptr + 4, content_length - 10)
|
||||||
|
deflated_length = self.get_i32(cont_ptr + 4 + content_length - 6)
|
||||||
|
inflated_content = zlib.decompress(deflated_content, 15, deflated_length)
|
||||||
|
if copyright:
|
||||||
|
self.copyright = copyright
|
||||||
|
if admin:
|
||||||
|
if copyright:
|
||||||
|
self.copyright += ', '
|
||||||
|
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
|
'Administered by %s') % admin
|
||||||
|
# Set the SongImport object members.
|
||||||
|
self.set_song_import_object(authors, inflated_content)
|
||||||
|
if self.stop_import_flag:
|
||||||
|
break
|
||||||
|
if not self.finish():
|
||||||
|
self.log_error(self.import_source)
|
||||||
|
# Set file_pos for next entry
|
||||||
|
file_pos += entry_length
|
||||||
|
self.ews_file.close()
|
||||||
|
|
||||||
|
def import_db(self):
|
||||||
|
"""
|
||||||
|
Import the songs from the database
|
||||||
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
@ -176,7 +260,6 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
ccli = self.get_field(fi_ccli)
|
ccli = self.get_field(fi_ccli)
|
||||||
authors = self.get_field(fi_author)
|
authors = self.get_field(fi_author)
|
||||||
words = self.get_field(fi_words)
|
words = self.get_field(fi_words)
|
||||||
# Set the SongImport object members.
|
|
||||||
if copy:
|
if copy:
|
||||||
self.copyright = copy.decode()
|
self.copyright = copy.decode()
|
||||||
if admin:
|
if admin:
|
||||||
@ -187,55 +270,11 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
if ccli:
|
if ccli:
|
||||||
self.ccli_number = ccli.decode()
|
self.ccli_number = ccli.decode()
|
||||||
if authors:
|
if authors:
|
||||||
# Split up the authors
|
authors = authors.decode()
|
||||||
author_list = authors.split(b'/')
|
else:
|
||||||
if len(author_list) < 2:
|
authors = ''
|
||||||
author_list = authors.split(b';')
|
# Set the SongImport object members.
|
||||||
if len(author_list) < 2:
|
self.set_song_import_object(authors, words)
|
||||||
author_list = authors.split(b',')
|
|
||||||
for author_name in author_list:
|
|
||||||
self.add_author(author_name.decode().strip())
|
|
||||||
if words:
|
|
||||||
# Format the lyrics
|
|
||||||
result = strip_rtf(words.decode(), self.encoding)
|
|
||||||
if result is None:
|
|
||||||
return
|
|
||||||
words, self.encoding = result
|
|
||||||
verse_type = VerseType.tags[VerseType.Verse]
|
|
||||||
for verse in SLIDE_BREAK_REGEX.split(words):
|
|
||||||
verse = verse.strip()
|
|
||||||
if not verse:
|
|
||||||
continue
|
|
||||||
verse_split = verse.split('\n', 1)
|
|
||||||
first_line_is_tag = False
|
|
||||||
# EW tags: verse, chorus, pre-chorus, bridge, tag,
|
|
||||||
# intro, ending, slide
|
|
||||||
for tag in VerseType.tags + ['tag', 'slide']:
|
|
||||||
tag = tag.lower()
|
|
||||||
ew_tag = verse_split[0].strip().lower()
|
|
||||||
if ew_tag.startswith(tag):
|
|
||||||
verse_type = tag[0]
|
|
||||||
if tag == 'tag' or tag == 'slide':
|
|
||||||
verse_type = VerseType.tags[VerseType.Other]
|
|
||||||
first_line_is_tag = True
|
|
||||||
number_found = False
|
|
||||||
# check if tag is followed by number and/or note
|
|
||||||
if len(ew_tag) > len(tag):
|
|
||||||
match = NUMBER_REGEX.search(ew_tag)
|
|
||||||
if match:
|
|
||||||
number = match.group()
|
|
||||||
verse_type += number
|
|
||||||
number_found = True
|
|
||||||
match = NOTE_REGEX.search(ew_tag)
|
|
||||||
if match:
|
|
||||||
self.comments += ew_tag + '\n'
|
|
||||||
if not number_found:
|
|
||||||
verse_type += '1'
|
|
||||||
break
|
|
||||||
self.add_verse(verse_split[-1].strip() if first_line_is_tag else verse, verse_type)
|
|
||||||
if len(self.comments) > 5:
|
|
||||||
self.comments += str(translate('SongsPlugin.EasyWorshipSongImport',
|
|
||||||
'\n[above are Song Tags with notes imported from EasyWorship]'))
|
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
break
|
break
|
||||||
if not self.finish():
|
if not self.finish():
|
||||||
@ -243,6 +282,61 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
db_file.close()
|
db_file.close()
|
||||||
self.memo_file.close()
|
self.memo_file.close()
|
||||||
|
|
||||||
|
def set_song_import_object(self, authors, words):
|
||||||
|
"""
|
||||||
|
Set the SongImport object members.
|
||||||
|
"""
|
||||||
|
if authors:
|
||||||
|
# Split up the authors
|
||||||
|
author_list = authors.split('/')
|
||||||
|
if len(author_list) < 2:
|
||||||
|
author_list = authors.split(';')
|
||||||
|
if len(author_list) < 2:
|
||||||
|
author_list = authors.split(',')
|
||||||
|
for author_name in author_list:
|
||||||
|
self.add_author(author_name.strip())
|
||||||
|
if words:
|
||||||
|
# Format the lyrics
|
||||||
|
result = strip_rtf(words.decode(), self.encoding)
|
||||||
|
if result is None:
|
||||||
|
return
|
||||||
|
words, self.encoding = result
|
||||||
|
verse_type = VerseType.tags[VerseType.Verse]
|
||||||
|
for verse in SLIDE_BREAK_REGEX.split(words):
|
||||||
|
verse = verse.strip()
|
||||||
|
if not verse:
|
||||||
|
continue
|
||||||
|
verse_split = verse.split('\n', 1)
|
||||||
|
first_line_is_tag = False
|
||||||
|
# EW tags: verse, chorus, pre-chorus, bridge, tag,
|
||||||
|
# intro, ending, slide
|
||||||
|
for tag in VerseType.tags + ['tag', 'slide']:
|
||||||
|
tag = tag.lower()
|
||||||
|
ew_tag = verse_split[0].strip().lower()
|
||||||
|
if ew_tag.startswith(tag):
|
||||||
|
verse_type = tag[0]
|
||||||
|
if tag == 'tag' or tag == 'slide':
|
||||||
|
verse_type = VerseType.tags[VerseType.Other]
|
||||||
|
first_line_is_tag = True
|
||||||
|
number_found = False
|
||||||
|
# check if tag is followed by number and/or note
|
||||||
|
if len(ew_tag) > len(tag):
|
||||||
|
match = NUMBER_REGEX.search(ew_tag)
|
||||||
|
if match:
|
||||||
|
number = match.group()
|
||||||
|
verse_type += number
|
||||||
|
number_found = True
|
||||||
|
match = NOTE_REGEX.search(ew_tag)
|
||||||
|
if match:
|
||||||
|
self.comments += ew_tag + '\n'
|
||||||
|
if not number_found:
|
||||||
|
verse_type += '1'
|
||||||
|
break
|
||||||
|
self.add_verse(verse_split[-1].strip() if first_line_is_tag else verse, verse_type)
|
||||||
|
if len(self.comments) > 5:
|
||||||
|
self.comments += str(translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
|
'\n[above are Song Tags with notes imported from EasyWorship]'))
|
||||||
|
|
||||||
def find_field(self, field_name):
|
def find_field(self, field_name):
|
||||||
"""
|
"""
|
||||||
Find a field in the descriptions
|
Find a field in the descriptions
|
||||||
@ -323,3 +417,38 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
return self.memo_file.read(blob_size)
|
return self.memo_file.read(blob_size)
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def get_bytes(self, pos, length):
|
||||||
|
"""
|
||||||
|
Get bytes from ews_file
|
||||||
|
"""
|
||||||
|
self.ews_file.seek(pos)
|
||||||
|
return self.ews_file.read(length)
|
||||||
|
|
||||||
|
def get_string(self, pos, length):
|
||||||
|
"""
|
||||||
|
Get string from ews_file
|
||||||
|
"""
|
||||||
|
bytes = self.get_bytes(pos, length)
|
||||||
|
mask = '<' + str(length) + 's'
|
||||||
|
byte_str, = struct.unpack(mask, bytes)
|
||||||
|
return byte_str.decode('unicode-escape').replace('\0', '').strip()
|
||||||
|
|
||||||
|
def get_i16(self, pos):
|
||||||
|
"""
|
||||||
|
Get short int from ews_file
|
||||||
|
"""
|
||||||
|
|
||||||
|
bytes = self.get_bytes(pos, 2)
|
||||||
|
mask = '<h'
|
||||||
|
number, = struct.unpack(mask, bytes)
|
||||||
|
return number
|
||||||
|
|
||||||
|
def get_i32(self, pos):
|
||||||
|
"""
|
||||||
|
Get long int from ews_file
|
||||||
|
"""
|
||||||
|
bytes = self.get_bytes(pos, 4)
|
||||||
|
mask = '<i'
|
||||||
|
number, = struct.unpack(mask, bytes)
|
||||||
|
return number
|
||||||
|
@ -229,7 +229,10 @@ class SongFormat(object):
|
|||||||
'name': 'EasyWorship',
|
'name': 'EasyWorship',
|
||||||
'prefix': 'ew',
|
'prefix': 'ew',
|
||||||
'selectMode': SongFormatSelect.SingleFile,
|
'selectMode': SongFormatSelect.SingleFile,
|
||||||
'filter': '%s (*.db)' % translate('SongsPlugin.ImportWizardForm', 'EasyWorship Song Database')
|
'filter': '%s (*.db);; %s (*.ews)' % (translate('SongsPlugin.ImportWizardForm',
|
||||||
|
'EasyWorship Song Database'),
|
||||||
|
translate('SongsPlugin.ImportWizardForm',
|
||||||
|
'EasyWorship Service File'))
|
||||||
},
|
},
|
||||||
FoilPresenter: {
|
FoilPresenter: {
|
||||||
'class': FoilPresenterImport,
|
'class': FoilPresenterImport,
|
||||||
|
Loading…
Reference in New Issue
Block a user