Fixed up the OpenSong importer.

This commit is contained in:
Raoul Snyman 2010-08-29 01:09:05 +02:00
parent 7fcd2512ce
commit 59d2e2dab6
2 changed files with 110 additions and 86 deletions

View File

@ -36,91 +36,119 @@ log = logging.getLogger(__name__)
class OpenSongImportError(Exception): class OpenSongImportError(Exception):
pass pass
class OpenSongImport(object): class OpenSongImport(SongImport):
""" """
Import songs exported from OpenSong - the format is described loosly here: Import songs exported from OpenSong
http://www.opensong.org/d/manual/song_file_format_specification
However, it doesn't describe the <lyrics> section, so here's an attempt: The format is described loosly on the `OpenSong File Format Specification
<http://www.opensong.org/d/manual/song_file_format_specification>`_ page on
the OpenSong web site. However, it doesn't describe the <lyrics> section,
so here's an attempt:
Verses can be expressed in one of 2 ways: Verses can be expressed in one of 2 ways, either in complete verses, or by
<lyrics> line grouping, i.e. grouping all line 1's of a verse together, all line 2's
[v1]List of words of a verse together, and so on.
Another Line
[v2]Some words for the 2nd verse An example of complete verses::
etc...
</lyrics>
The 'v' can be left out - it is implied <lyrics>
or: [v1]
<lyrics> List of words
[V] Another Line
1List of words
2Some words for the 2nd Verse
1Another Line [v2]
2etc... Some words for the 2nd verse
</lyrics> etc...
</lyrics>
Either or both forms can be used in one song. The Number does not The 'v' in the verse specifiers above can be left out, it is implied.
necessarily appear at the start of the line
An example of line grouping::
<lyrics>
[V]
1List of words
2Some words for the 2nd Verse
1Another Line
2etc...
</lyrics>
Either or both forms can be used in one song. The number does not
necessarily appear at the start of the line. Additionally, the [v1] labels
can have either upper or lower case Vs.
The [v1] labels can have either upper or lower case Vs
Other labels can be used also: Other labels can be used also:
C - Chorus
B - Bridge
Guitar chords can be provided 'above' the lyrics (the line is C
preceeded by a'.') and _s can be used to signify long-drawn-out Chorus
words:
. A7 Bm B
1 Some____ Words Bridge
Chords and _s are removed by this importer. All verses are imported and tagged appropriately.
The verses etc. are imported and tagged appropriately. Guitar chords can be provided "above" the lyrics (the line is preceeded by
a period "."), and one or more "_" can be used to signify long-drawn-out
words. Chords and "_" are removed by this importer. For example::
The <presentation> tag is used to populate the OpenLP verse . A7 Bm
display order field. The Author and Copyright tags are also 1 Some____ Words
imported to the appropriate places.
The <presentation> tag is used to populate the OpenLP verse display order
field. The Author and Copyright tags are also imported to the appropriate
places.
""" """
def __init__(self, songmanager): def __init__(self, manager, **kwargs):
""" """
Initialise the class. Requires a songmanager class which Initialise the class.
is passed to SongImport for writing song to disk
""" """
self.songmanager = songmanager SongImport.__init__(self, manager)
self.filenames = kwargs[u'filenames']
self.song = None self.song = None
self.commit = True
def do_import(self, filename, commit=True): def do_import(self):
""" """
Import either a single opensong file, or a zipfile Import either a single opensong file, or a zipfile containing multiple
containing multiple opensong files If the commit parameter is opensong files. If `self.commit` is set False, the import will not be
set False, the import will not be committed to the database committed to the database (useful for test scripts).
(useful for test scripts)
""" """
ext = os.path.splitext(filename)[1] success = False
if ext.lower() == ".zip": self.import_wizard.importProgressBar.setMaximum(len(self.filenames))
log.info('Zipfile found %s', filename) for filename in self.filenames:
z = ZipFile(filename, u'r') if self.stop_import_flag:
for song in z.infolist(): break
parts = os.path.split(song.filename) ext = os.path.splitext(filename)[1]
if parts[-1] == u'': if ext.lower() == u'.zip':
#No final part => directory log.debug(u'Zipfile found %s', filename)
continue z = ZipFile(filename, u'r')
songfile = z.open(song) for song in z.infolist():
self.do_import_file(songfile) if self.stop_import_flag:
if commit: break
parts = os.path.split(song.filename)
if parts[-1] == u'':
#No final part => directory
continue
self.import_wizard.incrementProgressBar(u'Importing %s...' \
% parts[-1])
songfile = z.open(song)
self.do_import_file(songfile)
if self.commit:
self.finish()
if self.stop_import_flag:
break
else:
log.info('Direct import %s', filename)
self.import_wizard.incrementProgressBar(u'Importing %s...' \
% os.path.split(filename)[-1])
file = open(filename)
self.do_import_file(file)
if self.commit:
self.finish() self.finish()
else: if not self.commit:
log.info('Direct import %s', filename) self.finish()
file = open(filename)
self.do_import_file(file)
if commit:
self.finish()
def do_import_file(self, file): def do_import_file(self, file):
@ -128,27 +156,28 @@ class OpenSongImport(object):
Process the OpenSong file - pass in a file-like object, Process the OpenSong file - pass in a file-like object,
not a filename not a filename
""" """
self.song_import = SongImport(self.songmanager)
tree = objectify.parse(file) tree = objectify.parse(file)
root = tree.getroot() root = tree.getroot()
fields = dir(root) fields = dir(root)
decode = {u'copyright':self.song_import.add_copyright, decode = {
u'ccli':u'ccli_number', u'copyright': self.add_copyright,
u'author':self.song_import.parse_author, u'ccli': u'ccli_number',
u'title':u'title', u'author': self.parse_author,
u'aka':u'alternate_title', u'title': u'title',
u'hymn_number':u'song_number'} u'aka': u'alternate_title',
u'hymn_number': u'song_number'
}
for (attr, fn_or_string) in decode.items(): for (attr, fn_or_string) in decode.items():
if attr in fields: if attr in fields:
ustring = unicode(root.__getattr__(attr)) ustring = unicode(root.__getattr__(attr))
if type(fn_or_string) == type(u''): if type(fn_or_string) == type(u''):
self.song_import.__setattr__(fn_or_string, ustring) self.__setattr__(fn_or_string, ustring)
else: else:
fn_or_string(ustring) fn_or_string(ustring)
if u'theme' in fields: if u'theme' in fields and unicode(root.theme) not in self.topics:
self.song_import.topics.append(unicode(root.theme)) self.topics.append(unicode(root.theme))
if u'alttheme' in fields: if u'alttheme' in fields and unicode(root.alttheme) not in self.topics:
self.song_import.topics.append(unicode(root.alttheme)) self.topics.append(unicode(root.alttheme))
# data storage while importing # data storage while importing
verses = {} verses = {}
lyrics = unicode(root.lyrics) lyrics = unicode(root.lyrics)
@ -158,6 +187,7 @@ class OpenSongImport(object):
# in the absence of any other indication, verses are the default, # in the absence of any other indication, verses are the default,
# erm, versetype! # erm, versetype!
versetype = u'V' versetype = u'V'
versenum = None
for thisline in lyrics.split(u'\n'): for thisline in lyrics.split(u'\n'):
# remove comments # remove comments
semicolon = thisline.find(u';') semicolon = thisline.find(u';')
@ -170,7 +200,6 @@ class OpenSongImport(object):
if thisline[0] == u'.' or thisline.startswith(u'---') \ if thisline[0] == u'.' or thisline.startswith(u'---') \
or thisline.startswith(u'-!!'): or thisline.startswith(u'-!!'):
continue continue
# verse/chorus/etc. marker # verse/chorus/etc. marker
if thisline[0] == u'[': if thisline[0] == u'[':
versetype = thisline[1].upper() versetype = thisline[1].upper()
@ -186,7 +215,6 @@ class OpenSongImport(object):
versenum = u'1' versenum = u'1'
continue continue
words = None words = None
# number at start of line.. it's verse number # number at start of line.. it's verse number
if thisline[0].isdigit(): if thisline[0].isdigit():
versenum = thisline[0] versenum = thisline[0]
@ -207,7 +235,7 @@ class OpenSongImport(object):
our_verse_order.append(versetag) our_verse_order.append(versetag)
if words: if words:
# Tidy text and remove the ____s from extended words # Tidy text and remove the ____s from extended words
words = self.song_import.tidy_text(words) words = self.tidy_text(words)
words = words.replace('_', '') words = words.replace('_', '')
verses[versetype][versenum].append(words) verses[versetype][versenum].append(words)
# done parsing # done parsing
@ -220,7 +248,7 @@ class OpenSongImport(object):
for num in versenums: for num in versenums:
versetag = u'%s%s' % (versetype, num) versetag = u'%s%s' % (versetype, num)
lines = u'\n'.join(verses[versetype][num]) lines = u'\n'.join(verses[versetype][num])
self.song_import.verses.append([versetag, lines]) self.verses.append([versetag, lines])
# Keep track of what we have for error checking later # Keep track of what we have for error checking later
versetags[versetag] = 1 versetags[versetag] = 1
# now figure out the presentation order # now figure out the presentation order
@ -236,8 +264,4 @@ class OpenSongImport(object):
if not versetags.has_key(tag): if not versetags.has_key(tag):
log.warn(u'Got order %s but not in versetags, skipping', tag) log.warn(u'Got order %s but not in versetags, skipping', tag)
else: else:
self.song_import.verse_order_list.append(tag) self.verse_order_list.append(tag)
def finish(self):
""" Separate function, allows test suite to not pollute database"""
self.song_import.finish()

View File

@ -236,7 +236,7 @@ class SongImport(QtCore.QObject):
""" """
All fields have been set to this song. Write it away All fields have been set to this song. Write it away
""" """
if len(self.authors) == 0: if not self.authors:
self.authors.append(u'Author unknown') self.authors.append(u'Author unknown')
self.commit_song() self.commit_song()