diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 6fb6b38a0..f2a59ba81 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -83,6 +83,12 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard): QtCore.QObject.connect(self.wordsOfWorshipRemoveButton, QtCore.SIGNAL(u'clicked()'), self.onWordsOfWorshipRemoveButtonClicked) + QtCore.QObject.connect(self.ccliAddButton, + QtCore.SIGNAL(u'clicked()'), + self.onCCLIAddButtonClicked) + QtCore.QObject.connect(self.ccliRemoveButton, + QtCore.SIGNAL(u'clicked()'), + self.onCCLIRemoveButtonClicked) QtCore.QObject.connect(self.songsOfFellowshipAddButton, QtCore.SIGNAL(u'clicked()'), self.onSongsOfFellowshipAddButtonClicked) @@ -277,6 +283,16 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard): def onWordsOfWorshipRemoveButtonClicked(self): self.removeSelectedItems(self.wordsOfWorshipFileListWidget) + def onCCLIAddButtonClicked(self): + self.getFiles( + translate('SongsPlugin.ImportWizardForm', + 'Select CCLI Files'), + self.ccliFileListWidget + ) + + def onCCLIRemoveButtonClicked(self): + self.removeSelectedItems(self.ccliFileListWidget) + def onSongsOfFellowshipAddButtonClicked(self): self.getFiles( translate('SongsPlugin.ImportWizardForm', diff --git a/openlp/plugins/songs/lib/cclifileimport.py b/openlp/plugins/songs/lib/cclifileimport.py new file mode 100755 index 000000000..08bccef79 --- /dev/null +++ b/openlp/plugins/songs/lib/cclifileimport.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian # +# Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard, Frode Woldsund, Derek Scotney # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### + +import logging +import os +import chardet +import codecs + +from songimport import SongImport + +log = logging.getLogger(__name__) + +class CCLIFileImportError(Exception): + pass + +class CCLIFileImport(SongImport): + """ + The :class:`CCLIFileImport` class provides OpenLP with the + ability to import CCLI SongSelect song files in both .txt and + .usr formats. See http://www.ccli.com + """ + + def __init__(self, manager, **kwargs): + """ + Initialise the import. + + ``manager`` + The song manager for the running OpenLP installation. + ``filenames`` + The files to be imported. + """ + SongImport.__init__(self, manager) + if u'filenames' in kwargs: + self.filenames = kwargs[u'filenames'] + log.debug(self.filenames) + else: + raise KeyError(u'Keyword argument "filenames" not supplied.') + + def do_import(self): + """ + Import either a .usr or a .txt SongSelect file + """ + log.debug(u'Starting CCLI File Import') + song_total = len(self.filenames) + self.import_wizard.importProgressBar.setMaximum(song_total) + song_count = 1 + for filename in self.filenames: + self.import_wizard.incrementProgressBar( + u'Importing song %s of %s' % (song_count, song_total)) + filename = unicode(filename) + log.debug(u'Importing CCLI File: %s', filename) + lines = [] + if os.path.isfile(filename): + detect_file = open(filename, u'r') + details = chardet.detect(detect_file.read(2048)) + detect_file.close() + infile = codecs.open(filename, u'r', details['encoding']) + lines = infile.readlines() + ext = os.path.splitext(filename)[1] + if ext.lower() == ".usr": + log.info(u'SongSelect .usr format file found %s: ' , filename) + self.do_import_usr_file(lines) + elif ext.lower() == ".txt": + log.info(u'SongSelect .txt format file found %s: ', filename) + self.do_import_txt_file(lines) + else: + log.info(u'Extension %s is not valid', filename) + pass + song_count += 1 + if self.stop_import_flag: + return False + return True + + def do_import_usr_file(self, textList): + """ + The :method:`do_import_usr_file` method provides OpenLP + with the ability to import CCLI SongSelect songs in + *USR* file format + + ``textList`` + An array of strings containing the usr file content. + + **SongSelect .usr file format** + ``[File]`` + USR file format first line + ``Type=`` + Indicates the file type + e.g. *Type=SongSelect Import File* + ``Version=3.0`` + File format version + ``[S A2672885]`` + Contains the CCLI Song number e.g. *2672885* + ``Title=`` + Contains the song title (e.g. *Title=Above All*) + ``Author=`` + Contains a | delimited list of the song authors + e.g. *Author=LeBlanc, Lenny | Baloche, Paul* + ``Copyright=`` + Contains a | delimited list of the song copyrights + e.g. Copyright=1999 Integrity's Hosanna! Music | + LenSongs Publishing (Verwaltet von Gerth Medien + Musikverlag) + ``Admin=`` + Contains the song administrator + e.g. *Admin=Gerth Medien Musikverlag* + ``Themes=`` + Contains a /t delimited list of the song themes + e.g. *Themes=Cross/tKingship/tMajesty/tRedeemer* + ``Keys=`` + Contains the keys in which the music is played?? + e.g. *Keys=A* + ``Fields=`` + Contains a list of the songs fields in order /t delimited + e.g. *Fields=Vers 1/tVers 2/tChorus 1/tAndere 1* + ``Words=`` + Contains the songs various lyrics in order as shown by the + *Fields* description + e.g. *Words=Above all powers....* [/n = CR, /n/t = CRLF] + """ + log.debug(u'USR file text: %s', textList) + lyrics = [] + self.set_defaults() + for line in textList: + if line.startswith(u'Title='): + song_name = line[6:].strip() + elif line.startswith(u'Author='): + song_author = line[7:].strip() + elif line.startswith(u'Copyright='): + song_copyright = line[10:].strip() + elif line.startswith(u'[S A'): + song_ccli = line[4:-3].strip() + elif line.startswith(u'Fields='): + #Fields contain single line indicating verse, chorus, etc, + #/t delimited, same as with words field. store seperately + #and process at end. + song_fields = line[7:].strip() + elif line.startswith(u'Words='): + song_words = line[6:].strip() + #Unhandled usr keywords:Type,Version,Admin,Themes,Keys + #Process Fields and words sections + field_list = song_fields.split(u'/t') + words_list = song_words.split(u'/t') + for counter in range(0, len(field_list)): + if field_list[counter].startswith(u'Ver'): + verse_type = u'V' + elif field_list[counter].startswith(u'Ch'): + verse_type = u'C' + elif field_list[counter].startswith(u'Br'): + verse_type = u'B' + else: #Other + verse_type = u'O' + verse_text = unicode(words_list[counter]) + verse_text = verse_text.replace("/n", "\n") + if len(verse_text) > 0: + self.add_verse(verse_text, verse_type); + #Handle multiple authors + author_list = song_author.split(u'/') + if len(author_list) < 2: + author_list = song_author.split(u'|') + for author in author_list: + seperated = author.split(u',') + self.add_author(seperated[1].strip() + " " + seperated[0].strip()) + self.title = song_name + self.copyright = song_copyright + self.ccli_number = song_ccli + self.finish() + + def do_import_txt_file(self, textList): + """ + The :method:`do_import_txt_file` method provides OpenLP + with the ability to import CCLI SongSelect songs in + *TXT* file format + + ``textList`` + An array of strings containing the txt file content. + + **SongSelect .txt file format** + + ``Song Title`` + Contains the song title + + + + ``Title of following verse/chorus and number`` + e.g. Verse 1, Chorus 1 + + ``Verse/Chorus lyrics`` + + + + + + ``Title of next verse/chorus (repeats)`` + + ``Verse/Chorus lyrics`` + + + + + + ``Song CCLI Number`` + e.g. CCLI Number (e.g.CCLI-Liednummer: 2672885) + ``Song Copyright`` + e.g. © 1999 Integrity's Hosanna! Music | LenSongs Publishing + ``Song Authors`` + e.g. Lenny LeBlanc | Paul Baloche + ``Licencing info`` + e.g. For use solely with the SongSelect Terms of Use. + All rights Reserved. www.ccli.com + ``CCLI Licence number of user`` + e.g. CCL-Liedlizenznummer: 14 / CCLI License No. 14 + """ + log.debug(u'TXT file text: %s', textList) + self.set_defaults() + line_number = 0 + verse_text = u'' + song_comments = u'' + song_copyright = u''; + verse_start = False + for line in textList: + clean_line = line.strip() + if not clean_line: + if line_number==0: + continue + elif verse_start: + if verse_text: + self.add_verse(verse_text, verse_type) + verse_text = '' + verse_start = False + else: + #line_number=0, song title + if line_number==0: + song_name = clean_line + line_number += 1 + #line_number=1, verses + elif line_number==1: + #line_number=1, ccli number, first line after verses + if clean_line.startswith(u'CCLI'): + line_number += 1 + ccli_parts = clean_line.split(' ') + song_ccli = ccli_parts[len(ccli_parts)-1] + elif not verse_start: + # We have the verse descriptor + verse_desc_parts = clean_line.split(' ') + if len(verse_desc_parts) == 2: + if verse_desc_parts[0].startswith(u'Ver'): + verse_type = u'V' + elif verse_desc_parts[0].startswith(u'Ch'): + verse_type = u'C' + elif verse_desc_parts[0].startswith(u'Br'): + verse_type = u'B' + else: + verse_type = u'O' + verse_number = verse_desc_parts[1] + else: + verse_type = u'O' + verse_number = 1 + verse_start = True + else: + # We have verse content or the start of the + # last part. Add l so as to keep the CRLF + verse_text = verse_text + line + else: + #line_number=2, copyright + if line_number==2: + line_number += 1 + song_copyright = clean_line + #n=3, authors + elif line_number==3: + line_number += 1 + song_author = clean_line + #line_number=4, comments lines before last line + elif (line_number==4) and (not clean_line.startswith(u'CCL')): + song_comments = song_comments + clean_line + # split on known separators + author_list = song_author.split(u'/') + if len(author_list) < 2: + author_list = song_author.split(u'|') + #Clean spaces before and after author names + for author_name in author_list: + self.add_author(author_name.strip()) + self.title = song_name + self.copyright = song_copyright + self.ccli_number = song_ccli + self.comments = song_comments + self.finish() + diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index ae18f4389..5801ea44a 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -29,6 +29,7 @@ from olpimport import OpenLPSongImport try: from sofimport import SofImport from oooimport import OooImport + from cclifileimport import CCLIFileImport from wowimport import WowImport except ImportError: pass @@ -68,6 +69,8 @@ class SongFormat(object): return WowImport elif format == SongFormat.Generic: return OooImport + elif format == SongFormat.CCLI: + return CCLIFileImport # else: return None diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index dd83c679d..bf5079c8c 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -43,12 +43,6 @@ class SongImport(QtCore.QObject): whether the authors etc already exist and add them or refer to them as necessary """ - - COPYRIGHT_STRING = unicode(translate( - 'SongsPlugin.SongImport', 'copyright')) - COPYRIGHT_SYMBOL = unicode(translate( - 'SongsPlugin.SongImport', '\xa9')) - def __init__(self, manager): """ Initialise and create defaults for properties @@ -58,11 +52,11 @@ class SongImport(QtCore.QObject): """ self.manager = manager self.stop_import_flag = False + self.set_defaults() QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'songs_stop_import'), self.stop_import) - self.setDefaults() - - def setDefaults(self): + + def set_defaults(self): self.title = u'' self.song_number = u'' self.alternate_title = u'' @@ -78,6 +72,10 @@ class SongImport(QtCore.QObject): self.verses = [] self.versecount = 0 self.choruscount = 0 + self.copyright_string = unicode(translate( + 'SongsPlugin.SongImport', 'copyright')) + self.copyright_symbol = unicode(translate( + 'SongsPlugin.SongImport', '\xa9')) def stop_import(self): """ @@ -163,8 +161,7 @@ class SongImport(QtCore.QObject): def parse_author(self, text): """ Add the author. OpenLP stores them individually so split by 'and', '&' - and comma. - However need to check for 'Mr and Mrs Smith' and turn it to + and comma. However need to check for 'Mr and Mrs Smith' and turn it to 'Mr Smith' and 'Mrs Smith'. """ for author in text.split(u','): @@ -241,7 +238,7 @@ class SongImport(QtCore.QObject): """ 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.commit_song()