Added a song importer for EasyWorship

This commit is contained in:
Jeffrey S. Smith 2010-09-20 13:22:57 -05:00
parent 906f33026a
commit d4a1b7f796
4 changed files with 314 additions and 3 deletions

View File

@ -109,6 +109,9 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
QtCore.QObject.connect(self.genericRemoveButton, QtCore.QObject.connect(self.genericRemoveButton,
QtCore.SIGNAL(u'clicked()'), QtCore.SIGNAL(u'clicked()'),
self.onGenericRemoveButtonClicked) self.onGenericRemoveButtonClicked)
QtCore.QObject.connect(self.ewBrowseButton,
QtCore.SIGNAL(u'clicked()'),
self.onEWBrowseButtonClicked)
QtCore.QObject.connect(self.cancelButton, QtCore.QObject.connect(self.cancelButton,
QtCore.SIGNAL(u'clicked(bool)'), QtCore.SIGNAL(u'clicked(bool)'),
self.onCancelButtonClicked) self.onCancelButtonClicked)
@ -214,6 +217,16 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
'presentation file to import from.')) 'presentation file to import from.'))
self.genericAddButton.setFocus() self.genericAddButton.setFocus()
return False return False
elif source_format == SongFormat.EasyWorship:
if self.ewFilenameEdit.text().isEmpty():
QtGui.QMessageBox.critical(self,
translate('SongsPlugin.ImportWizardForm',
'No EasyWorship Song Database Selected'),
translate('SongsPlugin.ImportWizardForm',
'You need to select an EasyWorship song database '
'file to import from.'))
self.ewBrowseButton.setFocus()
return False
return True return True
elif self.currentId() == 2: elif self.currentId() == 2:
# Progress page # Progress page
@ -322,6 +335,13 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
def onGenericRemoveButtonClicked(self): def onGenericRemoveButtonClicked(self):
self.removeSelectedItems(self.genericFileListWidget) self.removeSelectedItems(self.genericFileListWidget)
def onEWBrowseButtonClicked(self):
self.getFileName(
translate('SongsPlugin.ImportWizardForm',
'Select EasyWorship Database File'),
self.ewFilenameEdit
)
def onCancelButtonClicked(self, checked): def onCancelButtonClicked(self, checked):
""" """
Stop the import on pressing the cancel button. Stop the import on pressing the cancel button.
@ -350,6 +370,7 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
self.ccliFileListWidget.clear() self.ccliFileListWidget.clear()
self.songsOfFellowshipFileListWidget.clear() self.songsOfFellowshipFileListWidget.clear()
self.genericFileListWidget.clear() self.genericFileListWidget.clear()
self.ewFilenameEdit.setText(u'')
#self.csvFilenameEdit.setText(u'') #self.csvFilenameEdit.setText(u'')
def incrementProgressBar(self, status_text, increment=1): def incrementProgressBar(self, status_text, increment=1):
@ -420,6 +441,11 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
importer = self.plugin.importSongs(SongFormat.Generic, importer = self.plugin.importSongs(SongFormat.Generic,
filenames=self.getListOfFiles(self.genericFileListWidget) filenames=self.getListOfFiles(self.genericFileListWidget)
) )
elif source_format == SongFormat.EasyWorship:
# Import an OpenLP 2.0 database
importer = self.plugin.importSongs(SongFormat.EasyWorship,
filename=unicode(self.ewFilenameEdit.text())
)
success = importer.do_import() success = importer.do_import()
if success: if success:
# reload songs # reload songs

View File

@ -96,6 +96,7 @@ class Ui_SongImportWizard(object):
self.formatComboBox.addItem(u'') self.formatComboBox.addItem(u'')
self.formatComboBox.addItem(u'') self.formatComboBox.addItem(u'')
self.formatComboBox.addItem(u'') self.formatComboBox.addItem(u'')
self.formatComboBox.addItem(u'')
# self.formatComboBox.addItem(u'') # self.formatComboBox.addItem(u'')
self.formatLayout.addWidget(self.formatComboBox) self.formatLayout.addWidget(self.formatComboBox)
self.formatSpacer = QtGui.QSpacerItem(40, 20, self.formatSpacer = QtGui.QSpacerItem(40, 20,
@ -413,6 +414,30 @@ class Ui_SongImportWizard(object):
self.genericImportLayout.addLayout(self.genericButtonLayout) self.genericImportLayout.addLayout(self.genericButtonLayout)
self.genericLayout.addWidget(self.genericImportWidget) self.genericLayout.addWidget(self.genericImportWidget)
self.formatStackedWidget.addWidget(self.genericPage) self.formatStackedWidget.addWidget(self.genericPage)
# EasyWorship
self.ewPage = QtGui.QWidget()
self.ewPage.setObjectName(u'ewPage')
self.ewLayout = QtGui.QFormLayout(self.ewPage)
self.ewLayout.setMargin(0)
self.ewLayout.setSpacing(8)
self.ewLayout.setObjectName(u'ewLayout')
self.ewFilenameLabel = QtGui.QLabel(self.ewPage)
self.ewFilenameLabel.setObjectName(u'ewFilenameLabel')
self.ewLayout.setWidget(0, QtGui.QFormLayout.LabelRole,
self.ewFilenameLabel)
self.ewFileLayout = QtGui.QHBoxLayout()
self.ewFileLayout.setSpacing(8)
self.ewFileLayout.setObjectName(u'ewFileLayout')
self.ewFilenameEdit = QtGui.QLineEdit(self.ewPage)
self.ewFilenameEdit.setObjectName(u'ewFilenameEdit')
self.ewFileLayout.addWidget(self.ewFilenameEdit)
self.ewBrowseButton = QtGui.QToolButton(self.ewPage)
self.ewBrowseButton.setIcon(openIcon)
self.ewBrowseButton.setObjectName(u'ewBrowseButton')
self.ewFileLayout.addWidget(self.ewBrowseButton)
self.ewLayout.setLayout(0, QtGui.QFormLayout.FieldRole,
self.ewFileLayout)
self.formatStackedWidget.addWidget(self.ewPage)
# Commented out for future use. # Commented out for future use.
# self.csvPage = QtGui.QWidget() # self.csvPage = QtGui.QWidget()
# self.csvPage.setObjectName(u'CSVPage') # self.csvPage.setObjectName(u'CSVPage')
@ -497,7 +522,9 @@ class Ui_SongImportWizard(object):
self.formatComboBox.setItemText(7, self.formatComboBox.setItemText(7,
translate('SongsPlugin.ImportWizardForm', translate('SongsPlugin.ImportWizardForm',
'Generic Document/Presentation')) 'Generic Document/Presentation'))
# self.formatComboBox.setItemText(8, self.formatComboBox.setItemText(8,
translate('SongsPlugin.ImportWizardForm', 'EasyWorship'))
# self.formatComboBox.setItemText(9,
# translate('SongsPlugin.ImportWizardForm', 'CSV')) # translate('SongsPlugin.ImportWizardForm', 'CSV'))
self.openLP2FilenameLabel.setText( self.openLP2FilenameLabel.setText(
translate('SongsPlugin.ImportWizardForm', 'Filename:')) translate('SongsPlugin.ImportWizardForm', 'Filename:'))
@ -549,6 +576,10 @@ class Ui_SongImportWizard(object):
translate('SongsPlugin.ImportWizardForm', 'The generic document/' translate('SongsPlugin.ImportWizardForm', 'The generic document/'
'presentation importer has been disabled because OpenLP cannot ' 'presentation importer has been disabled because OpenLP cannot '
'find OpenOffice.org on your computer.')) 'find OpenOffice.org on your computer.'))
self.ewFilenameLabel.setText(
translate('SongsPlugin.ImportWizardForm', 'Filename:'))
self.ewBrowseButton.setText(
translate('SongsPlugin.ImportWizardForm', 'Browse...'))
# self.csvFilenameLabel.setText( # self.csvFilenameLabel.setText(
# translate('SongsPlugin.ImportWizardForm', 'Filename:')) # translate('SongsPlugin.ImportWizardForm', 'Filename:'))
# self.csvBrowseButton.setText( # self.csvBrowseButton.setText(

View File

@ -0,0 +1,249 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
"""
The :mod:`ewimport` module provides the functionality for importing
EasyWorship song databases into the current installation database.
"""
import sys
import os
import struct
from songimport import SongImport
def strip_rtf(blob):
depth = 0
control = False
clear_text = []
control_word = []
for c in blob:
if control:
# for delimiters, set control to False
if c == '{':
depth += 1
control = False
elif c == '}':
depth -= 1
control = False
elif c == '\\':
control = False
elif c.isspace():
control = False
else:
control_word.append(c)
if len(control_word) == 3 and control_word[0] == '\'':
control = False
if not control:
control_word_str = ''.join(control_word)
if control_word_str == 'par':
clear_text.append(u'\n')
# Really should take RTF character set into account
# but for now assume ANSI (Windows-1252) and call it good
if control_word_str[0] == '\'':
s = chr(int(control_word_str[1:3], 16))
clear_text.append(s.decode(u'windows-1252'))
del control_word[:]
if c == '\\':
control = True
elif c == '{':
depth += 1
elif c == '}':
depth -= 1
elif depth > 2:
continue
elif c == '\n' or c == '\r':
continue
elif c == '\\':
control = True
else:
clear_text.append(c)
return u''.join(clear_text)
class FieldDescEntry:
def __init__(self, name, type, size):
self.name = name
self.type = type
self.size = size
class EasyWorshipSongImport(SongImport):
"""
The :class:`EasyWorshipSongImport` class provides OpenLP with the
ability to import EasyWorship song files.
"""
def __init__(self, manager, **kwargs):
self.import_source = kwargs[u'filename']
SongImport.__init__(self, manager)
def do_import(self):
# Open the DB and MB files if they exist
import_source_mb = self.import_source.replace('.DB', '.MB')
if not os.path.isfile(self.import_source):
return False
if not os.path.isfile(import_source_mb):
return False
db_size = os.path.getsize(self.import_source)
if db_size < 0x800:
return False
db_file = open(self.import_source, 'rb')
self.memo_file = open(import_source_mb, 'rb')
# Don't accept files that are clearly not paradox files
record_size, header_size, block_size, next_block, num_fields \
= struct.unpack('<hhxb4xh21xh', db_file.read(35))
if header_size != 0x800 or block_size < 1 or block_size > 4:
db_file.close()
self.memo_file.close()
return False
# There does not appear to be a _reliable_ way of getting the number
# of songs/records, so let's use file blocks for measuring progress.
total_blocks = (db_size - header_size) / (block_size * 1024)
self.import_wizard.importProgressBar.setMaximum(total_blocks)
# Read the field description information
db_file.seek(120)
field_info = db_file.read(num_fields * 2)
db_file.seek(4 + (num_fields * 4) + 261, os.SEEK_CUR)
field_names = db_file.read(header_size - db_file.tell()).split('\0',
num_fields)
field_names.pop()
field_descs = []
for i,field_name in enumerate(field_names):
field_type, field_size = struct.unpack_from('BB', field_info, i * 2)
field_descs.append(FieldDescEntry(field_name, field_type,
field_size))
self.set_record_struct(field_descs)
# Pick out the field description indexes we will need
success = True
try:
fi_title = self.find_field(u'Title')
fi_author = self.find_field(u'Author')
fi_copy = self.find_field(u'Copyright')
fi_admin = self.find_field(u'Administrator')
fi_words = self.find_field(u'Words')
fi_ccli = self.find_field(u'Song Number')
except IndexError:
# This is the wrong table
success = False
while next_block != 0 and success:
db_file.seek(header_size + ((next_block - 1) * 1024 * block_size))
next_block, rec_count = struct.unpack('<h2xh', db_file.read(6))
rec_count = (rec_count + record_size) / record_size
for i in range(rec_count):
if self.stop_import_flag:
success = False
break
raw_record = db_file.read(record_size)
self.fields = self.record_struct.unpack(raw_record)
self.set_defaults()
self.title = self.get_field(fi_title)
self.import_wizard.incrementProgressBar(
u'Importing "%s"...' % self.title, 0)
self.copyright = self.get_field(fi_copy) + \
u', Administered by ' + self.get_field(fi_admin)
self.ccli_number = self.get_field(fi_ccli)
# Format the lyrics
if self.stop_import_flag:
success = False
break
words = self.get_field(fi_words)
words = strip_rtf(words)
for verse in words.split(u'\n\n'):
self.add_verse(verse.strip(), u'V')
# Split up the authors
authors = self.get_field(fi_author)
author_list = authors.split(u'/')
if len(author_list) < 2:
author_list = authors.split(u',')
for author_name in author_list:
self.add_author(author_name.strip())
if self.stop_import_flag:
success = False
break
self.finish()
if not self.stop_import_flag:
self.import_wizard.incrementProgressBar(u'')
db_file.close()
self.memo_file.close()
return success
def find_field(self, field_name):
return [i for i,x in enumerate(self.field_descs) \
if x.name == field_name][0]
def set_record_struct(self, field_descs):
# Begin with empty field struct list
fsl = ['>']
for field_desc in field_descs:
if field_desc.type == 1: # string
fsl.append('%ds' % field_desc.size)
elif field_desc.type == 3: # 16-bit int
fsl.append('H')
elif field_desc.type == 4: # 32-bit int
fsl.append('I')
elif field_desc.type == 9: # Logical
fsl.append('B')
elif field_desc.type == 0x0c: # Memo
fsl.append('%ds' % field_desc.size)
elif field_desc.type == 0x0d: # Blob
fsl.append('%ds' % field_desc.size)
elif field_desc.type == 0x15: # Timestamp
fsl.append('Q')
else:
fsl.append('%ds' % field_desc.size)
self.record_struct = struct.Struct(''.join(fsl))
self.field_descs = field_descs
def get_field(self, field_desc_index):
field = self.fields[field_desc_index]
field_desc = self.field_descs[field_desc_index]
# Check for 'blank' entries
if isinstance(field, str):
if len(field.rstrip('\0')) == 0:
return u''
elif field == 0:
return 0
if field_desc.type == 1: # string
return field.rstrip('\0').decode(u'windows-1252')
elif field_desc.type == 3: # 16-bit int
return field ^ 0x8000
elif field_desc.type == 4: # 32-bit int
return field ^ 0x80000000
elif field_desc.type == 9: # Logical
return (field ^ 0x80 == 1)
elif field_desc.type == 0x0c or field_desc.type == 0x0d:
sub_block, block_start, blob_size = \
struct.unpack_from('<bhxi', field, len(field)-10)
self.memo_file.seek(block_start * 256)
memo_block_type, = struct.unpack('b', self.memo_file.read(1))
if memo_block_type == 2:
self.memo_file.seek(8, os.SEEK_CUR)
elif memo_block_type == 3:
if sub_block < 0 or sub_block > 63:
return u'';
self.memo_file.seek(11 + (5 * sub_block), os.SEEK_CUR)
sub_block_start, = struct.unpack('B', self.memo_file.read(1))
self.memo_file.seek((block_start * 256) +
(sub_block_start * 16))
else:
return u'';
return self.memo_file.read(blob_size)
else:
return 0

View File

@ -28,6 +28,7 @@ from opensongimport import OpenSongImport
from olpimport import OpenLPSongImport from olpimport import OpenLPSongImport
from wowimport import WowImport from wowimport import WowImport
from cclifileimport import CCLIFileImport from cclifileimport import CCLIFileImport
from ewimport import EasyWorshipSongImport
# Imports that might fail # Imports that might fail
try: try:
from olp1import import OpenLP1SongImport from olp1import import OpenLP1SongImport
@ -61,7 +62,8 @@ class SongFormat(object):
CCLI = 5 CCLI = 5
SongsOfFellowship = 6 SongsOfFellowship = 6
Generic = 7 Generic = 7
CSV = 8 #CSV = 8
EasyWorship = 8
@staticmethod @staticmethod
def get_class(format): def get_class(format):
@ -85,6 +87,8 @@ class SongFormat(object):
return OooImport return OooImport
elif format == SongFormat.CCLI: elif format == SongFormat.CCLI:
return CCLIFileImport return CCLIFileImport
elif format == SongFormat.EasyWorship:
return EasyWorshipSongImport
# else: # else:
return None return None
@ -101,7 +105,8 @@ class SongFormat(object):
SongFormat.WordsOfWorship, SongFormat.WordsOfWorship,
SongFormat.CCLI, SongFormat.CCLI,
SongFormat.SongsOfFellowship, SongFormat.SongsOfFellowship,
SongFormat.Generic SongFormat.Generic,
SongFormat.EasyWorship
] ]
@staticmethod @staticmethod