def set_verse(self, text, single=False, tag='%s1' % VerseType.tags[VerseType.Verse]): """ Save the verse :param text: The text :param single: is this a single verse :param tag: The tag """ self.has_single_verse = single if single: verse_type_index = VerseType.from_tag(tag[0], None) verse_number = tag[1:] if verse_type_index is not None: self.verse_type_combo_box.setCurrentIndex(verse_type_index) self.verse_number_box.setValue(int(verse_number)) self.insert_button.setVisible(False) else: if not text: text = '---[%s:1]---\n' % VerseType.translated_names[VerseType.Verse] self.verse_type_combo_box.setCurrentIndex(0) self.verse_number_box.setValue(1) self.insert_button.setVisible(True) self.verse_text_edit.setPlainText(text) self.verse_text_edit.setFocus() self.verse_text_edit.moveCursor(QtGui.QTextCursor.End)
def song_to_line_dict(self, song): song_xml = SongXML() if song.chords: verse_chords_xml = song_xml.get_verses(song.chords) else: verse_chords_xml = song_xml.get_verses(song.lyrics) section_line_dict = {} for count, verse in enumerate(verse_chords_xml): # This silently migrates from localized verse type markup. # If we trusted the database, this would be unnecessary. verse_tag = verse[0]['type'] index = None if len(verse_tag) > 1: index = VerseType.from_translated_string(verse_tag) if index is None: index = VerseType.from_string(verse_tag, None) else: verse_tags_translated = True if index is None: index = VerseType.from_tag(verse_tag) verse[0]['type'] = VerseType.tags[index] if verse[0]['label'] == '': verse[0]['label'] = '1' section_header = '%s%s' % (verse[0]['type'], verse[0]['label']) line_list = [] for line in verse[1].split('\n'): line_list.append(line) section_line_dict[section_header] = line_list return section_line_dict
def from_tag_with_none_default_test(self): """ Test that the from_tag() method returns a sane default when passed an invalid tag and None as default. """ # GIVEN: A mocked out translate() function that just returns what it was given with patch('openlp.plugins.songs.lib.translate') as mocked_translate: mocked_translate.side_effect = lambda x, y: y # WHEN: We run the from_tag() method with an invalid verse type, we get the specified default back result = VerseType.from_tag('m', None) # THEN: The result should be None self.assertIsNone(result, 'The result should be None, but was "%s"' % result)
def from_tag_with_specified_default_test(self): """ Test that the from_tag() method returns the specified default when passed an invalid tag. """ # GIVEN: A mocked out translate() function that just returns what it was given with patch('openlp.plugins.songs.lib.translate') as mocked_translate: mocked_translate.side_effect = lambda x, y: y # WHEN: We run the from_tag() method with an invalid verse type, we get the specified default back result = VerseType.from_tag('x', VerseType.Chorus) # THEN: The result should be VerseType.Chorus self.assertEqual(result, VerseType.Chorus, 'The result should be VerseType.Chorus, but was "%s"' % result)
def from_tag_test(self): """ Test that the from_tag() method returns the correct VerseType. """ # GIVEN: A mocked out translate() function that just returns what it was given with patch('openlp.plugins.songs.lib.translate') as mocked_translate: mocked_translate.side_effect = lambda x, y: y # WHEN: We run the from_tag() method with a valid verse type, we get the name back result = VerseType.from_tag('v') # THEN: The result should be VerseType.Verse self.assertEqual(result, VerseType.Verse, 'The result should be VerseType.Verse, but was "%s"' % result)
def from_tag_with_invalid_default_test(self): """ Test that the from_tag() method returns a sane default when passed an invalid tag and an invalid default. """ # GIVEN: A mocked out translate() function that just returns what it was given with patch('openlp.plugins.songs.lib.translate') as mocked_translate: mocked_translate.side_effect = lambda x, y: y # WHEN: We run the from_tag() method with an invalid verse type, we get the specified default back result = VerseType.from_tag('m', 29) # THEN: The result should be VerseType.Other self.assertEqual(result, VerseType.Other, 'The result should be VerseType.Other, but was "%s"' % result)
def test_from_tag_with_invalid_default(self): """ Test that the from_tag() method returns a sane default when passed an invalid tag and an invalid default. """ # GIVEN: A mocked out translate() function that just returns what it was given with patch('openlp.plugins.songs.lib.translate') as mocked_translate: mocked_translate.side_effect = lambda x, y: y # WHEN: We run the from_tag() method with an invalid verse type, we get the specified default back result = VerseType.from_tag('@', 'asdf') # THEN: The result should be VerseType.Other assert result == VerseType.Other, 'The result should be VerseType.Other, but was "%s"' % result
def _process_lyrics(self, foilpresenterfolie, song): """ Processes the verses and search_lyrics for the song. :param foilpresenterfolie: The foilpresenterfolie object (lxml.objectify.ObjectifiedElement). :param song: The song object. """ sxml = SongXML() temp_verse_order = {} temp_verse_order_backup = [] temp_sortnr_backup = 1 temp_sortnr_liste = [] verse_count = { VerseType.tags[VerseType.Verse]: 1, VerseType.tags[VerseType.Chorus]: 1, VerseType.tags[VerseType.Bridge]: 1, VerseType.tags[VerseType.Ending]: 1, VerseType.tags[VerseType.Other]: 1, VerseType.tags[VerseType.Intro]: 1, VerseType.tags[VerseType.PreChorus]: 1 } if not hasattr(foilpresenterfolie.strophen, 'strophe'): self.importer.log_error( to_str(foilpresenterfolie.titel), str( translate( 'SongsPlugin.FoilPresenterSongImport', 'Invalid Foilpresenter song file. No verses found.'))) self.save_song = False return for strophe in foilpresenterfolie.strophen.strophe: text = to_str(strophe.text_) if hasattr(strophe, 'text_') else '' verse_name = to_str(strophe.key) children = strophe.getchildren() sortnr = False for child in children: if child.tag == 'sortnr': verse_sortnr = to_str(strophe.sortnr) sortnr = True # In older Version there is no sortnr, but we need one if not sortnr: verse_sortnr = str(temp_sortnr_backup) temp_sortnr_backup += 1 # Foilpresenter allows e. g. "Ref" or "1", but we need "C1" or "V1". temp_sortnr_liste.append(verse_sortnr) temp_verse_name = re.compile('[0-9].*').sub('', verse_name) temp_verse_name = temp_verse_name[:3].lower() if temp_verse_name == 'ref': verse_type = VerseType.tags[VerseType.Chorus] elif temp_verse_name == 'r': verse_type = VerseType.tags[VerseType.Chorus] elif temp_verse_name == '': verse_type = VerseType.tags[VerseType.Verse] elif temp_verse_name == 'v': verse_type = VerseType.tags[VerseType.Verse] elif temp_verse_name == 'bri': verse_type = VerseType.tags[VerseType.Bridge] elif temp_verse_name == 'cod': verse_type = VerseType.tags[VerseType.Ending] elif temp_verse_name == 'sch': verse_type = VerseType.tags[VerseType.Ending] elif temp_verse_name == 'pre': verse_type = VerseType.tags[VerseType.PreChorus] elif temp_verse_name == 'int': verse_type = VerseType.tags[VerseType.Intro] else: verse_type = VerseType.tags[VerseType.Other] verse_number = re.compile('[a-zA-Z.+-_ ]*').sub('', verse_name) # Foilpresenter allows e. g. "C", but we need "C1". if not verse_number: verse_number = str(verse_count[verse_type]) verse_count[verse_type] += 1 else: # test if foilpresenter have the same versenumber two times with # different parts raise the verse number for value in temp_verse_order_backup: if value == ''.join((verse_type, verse_number)): verse_number = str(int(verse_number) + 1) verse_type_index = VerseType.from_tag(verse_type[0]) verse_type = VerseType.tags[verse_type_index] temp_verse_order[verse_sortnr] = ''.join( (verse_type[0], verse_number)) temp_verse_order_backup.append(''.join( (verse_type[0], verse_number))) sxml.add_verse_to_lyrics(verse_type, verse_number, text) song.lyrics = str(sxml.extract_xml(), 'utf-8') # Process verse order verse_order = [] verse_strophenr = [] try: for strophennummer in foilpresenterfolie.reihenfolge.strophennummer: verse_strophenr.append(strophennummer) except AttributeError: pass # Currently we do not support different "parts"! if '0' in temp_verse_order: for vers in temp_verse_order_backup: verse_order.append(vers) else: for number in verse_strophenr: numberx = temp_sortnr_liste[int(number)] verse_order.append(temp_verse_order[str(numberx)]) song.verse_order = ' '.join(verse_order)
def setupUi(self): self.song_vertical_layout = QtWidgets.QVBoxLayout(self) self.song_vertical_layout.setObjectName('song_vertical_layout') self.song_group_box = QtWidgets.QGroupBox(self) self.song_group_box.setObjectName('song_group_box') self.song_group_box.setFixedWidth(300) self.song_group_box_layout = QtWidgets.QVBoxLayout(self.song_group_box) self.song_group_box_layout.setObjectName('song_group_box_layout') self.song_info_form_layout = QtWidgets.QFormLayout() self.song_info_form_layout.setObjectName('song_info_form_layout') # Add title widget. self.song_title_label = QtWidgets.QLabel(self) self.song_title_label.setObjectName('song_title_label') self.song_info_form_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.song_title_label) self.song_title_content = QtWidgets.QLabel(self) self.song_title_content.setObjectName('song_title_content') self.song_title_content.setText(self.song.title) self.song_title_content.setWordWrap(True) self.song_info_form_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.song_title_content) # Add alternate title widget. self.song_alternate_title_label = QtWidgets.QLabel(self) self.song_alternate_title_label.setObjectName( 'song_alternate_title_label') self.song_info_form_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.song_alternate_title_label) self.song_alternate_title_content = QtWidgets.QLabel(self) self.song_alternate_title_content.setObjectName( 'song_alternate_title_content') self.song_alternate_title_content.setText(self.song.alternate_title) self.song_alternate_title_content.setWordWrap(True) self.song_info_form_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.song_alternate_title_content) # Add CCLI number widget. self.song_ccli_number_label = QtWidgets.QLabel(self) self.song_ccli_number_label.setObjectName('song_ccli_number_label') self.song_info_form_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.song_ccli_number_label) self.song_ccli_number_content = QtWidgets.QLabel(self) self.song_ccli_number_content.setObjectName('song_ccli_number_content') self.song_ccli_number_content.setText(self.song.ccli_number) self.song_ccli_number_content.setWordWrap(True) self.song_info_form_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.song_ccli_number_content) # Add copyright widget. self.song_copyright_label = QtWidgets.QLabel(self) self.song_copyright_label.setObjectName('song_copyright_label') self.song_info_form_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.song_copyright_label) self.song_copyright_content = QtWidgets.QLabel(self) self.song_copyright_content.setObjectName('song_copyright_content') self.song_copyright_content.setWordWrap(True) self.song_copyright_content.setText(self.song.copyright) self.song_info_form_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.song_copyright_content) # Add comments widget. self.song_comments_label = QtWidgets.QLabel(self) self.song_comments_label.setObjectName('song_comments_label') self.song_info_form_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.song_comments_label) self.song_comments_content = QtWidgets.QLabel(self) self.song_comments_content.setObjectName('song_comments_content') self.song_comments_content.setText(self.song.comments) self.song_comments_content.setWordWrap(True) self.song_info_form_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.song_comments_content) # Add authors widget. self.song_authors_label = QtWidgets.QLabel(self) self.song_authors_label.setObjectName('song_authors_label') self.song_info_form_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.song_authors_label) self.song_authors_content = QtWidgets.QLabel(self) self.song_authors_content.setObjectName('song_authors_content') self.song_authors_content.setWordWrap(True) authors_text = ', '.join( [author.display_name for author in self.song.authors]) self.song_authors_content.setText(authors_text) self.song_info_form_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.song_authors_content) # Add verse order widget. self.song_verse_order_label = QtWidgets.QLabel(self) self.song_verse_order_label.setObjectName('song_verse_order_label') self.song_info_form_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.song_verse_order_label) self.song_verse_order_content = QtWidgets.QLabel(self) self.song_verse_order_content.setObjectName('song_verse_order_content') self.song_verse_order_content.setText(self.song.verse_order) self.song_verse_order_content.setWordWrap(True) self.song_info_form_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.song_verse_order_content) self.song_group_box_layout.addLayout(self.song_info_form_layout) # Add verses widget. self.song_info_verse_list_widget = QtWidgets.QTableWidget( self.song_group_box) self.song_info_verse_list_widget.setColumnCount(1) self.song_info_verse_list_widget.horizontalHeader().setVisible(False) self.song_info_verse_list_widget.setObjectName( 'song_info_verse_list_widget') self.song_info_verse_list_widget.setSelectionMode( QtWidgets.QAbstractItemView.NoSelection) self.song_info_verse_list_widget.setEditTriggers( QtWidgets.QAbstractItemView.NoEditTriggers) self.song_info_verse_list_widget.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff) self.song_info_verse_list_widget.setVerticalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff) self.song_info_verse_list_widget.setAlternatingRowColors(True) song_xml = SongXML() verses = song_xml.get_verses(self.song.lyrics) self.song_info_verse_list_widget.setRowCount(len(verses)) song_tags = [] for verse_number, verse in enumerate(verses): item = QtWidgets.QTableWidgetItem() item.setText(verse[1]) self.song_info_verse_list_widget.setItem(verse_number, 0, item) # We cannot use from_loose_input() here, because database # is supposed to contain English lowercase singlechar tags. verse_tag = verse[0]['type'] verse_index = None if len(verse_tag) > 1: verse_index = VerseType.from_translated_string(verse_tag) if verse_index is None: verse_index = VerseType.from_string(verse_tag, None) if verse_index is None: verse_index = VerseType.from_tag(verse_tag) verse_tag = VerseType.translated_tags[verse_index].upper() song_tags.append(str(verse_tag + verse[0]['label'])) self.song_info_verse_list_widget.setVerticalHeaderLabels(song_tags) # Resize table fields to content and table to columns self.song_info_verse_list_widget.setColumnWidth( 0, self.song_group_box.width()) self.song_info_verse_list_widget.resizeRowsToContents() # The 6 is a trial and error value since verticalHeader().length() + offset() is a little bit to small. # It seems there is no clean way to determine the real height of the table contents. # The "correct" value slightly fluctuates depending on the theme used, in the worst case # Some pixels are missing at the bottom of the table, but all themes I tried still allowed # to read the last verse line, so I'll just leave it at that. self.song_info_verse_list_widget.setFixedHeight( self.song_info_verse_list_widget.verticalHeader().length() + self.song_info_verse_list_widget.verticalHeader().offset() + 6) self.song_group_box_layout.addWidget(self.song_info_verse_list_widget) self.song_group_box_layout.addStretch() self.song_vertical_layout.addWidget(self.song_group_box) self.song_remove_button = QtWidgets.QPushButton(self) self.song_remove_button.setObjectName('song_remove_button') self.song_remove_button.setIcon(build_icon(':/songs/song_delete.png')) self.song_remove_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) self.song_vertical_layout.addWidget(self.song_remove_button, alignment=QtCore.Qt.AlignHCenter)
def setupUi(self): self.song_vertical_layout = QtGui.QVBoxLayout(self) self.song_vertical_layout.setObjectName('song_vertical_layout') self.song_group_box = QtGui.QGroupBox(self) self.song_group_box.setObjectName('song_group_box') self.song_group_box.setFixedWidth(300) self.song_group_box_layout = QtGui.QVBoxLayout(self.song_group_box) self.song_group_box_layout.setObjectName('song_group_box_layout') self.song_info_form_layout = QtGui.QFormLayout() self.song_info_form_layout.setObjectName('song_info_form_layout') # Add title widget. self.song_title_label = QtGui.QLabel(self) self.song_title_label.setObjectName('song_title_label') self.song_info_form_layout.setWidget(0, QtGui.QFormLayout.LabelRole, self.song_title_label) self.song_title_content = QtGui.QLabel(self) self.song_title_content.setObjectName('song_title_content') self.song_title_content.setText(self.song.title) self.song_title_content.setWordWrap(True) self.song_info_form_layout.setWidget(0, QtGui.QFormLayout.FieldRole, self.song_title_content) # Add alternate title widget. self.song_alternate_title_label = QtGui.QLabel(self) self.song_alternate_title_label.setObjectName('song_alternate_title_label') self.song_info_form_layout.setWidget(1, QtGui.QFormLayout.LabelRole, self.song_alternate_title_label) self.song_alternate_title_content = QtGui.QLabel(self) self.song_alternate_title_content.setObjectName('song_alternate_title_content') self.song_alternate_title_content.setText(self.song.alternate_title) self.song_alternate_title_content.setWordWrap(True) self.song_info_form_layout.setWidget(1, QtGui.QFormLayout.FieldRole, self.song_alternate_title_content) # Add CCLI number widget. self.song_ccli_number_label = QtGui.QLabel(self) self.song_ccli_number_label.setObjectName('song_ccli_number_label') self.song_info_form_layout.setWidget(2, QtGui.QFormLayout.LabelRole, self.song_ccli_number_label) self.song_ccli_number_content = QtGui.QLabel(self) self.song_ccli_number_content.setObjectName('song_ccli_number_content') self.song_ccli_number_content.setText(self.song.ccli_number) self.song_ccli_number_content.setWordWrap(True) self.song_info_form_layout.setWidget(2, QtGui.QFormLayout.FieldRole, self.song_ccli_number_content) # Add copyright widget. self.song_copyright_label = QtGui.QLabel(self) self.song_copyright_label.setObjectName('song_copyright_label') self.song_info_form_layout.setWidget(3, QtGui.QFormLayout.LabelRole, self.song_copyright_label) self.song_copyright_content = QtGui.QLabel(self) self.song_copyright_content.setObjectName('song_copyright_content') self.song_copyright_content.setWordWrap(True) self.song_copyright_content.setText(self.song.copyright) self.song_info_form_layout.setWidget(3, QtGui.QFormLayout.FieldRole, self.song_copyright_content) # Add comments widget. self.song_comments_label = QtGui.QLabel(self) self.song_comments_label.setObjectName('song_comments_label') self.song_info_form_layout.setWidget(4, QtGui.QFormLayout.LabelRole, self.song_comments_label) self.song_comments_content = QtGui.QLabel(self) self.song_comments_content.setObjectName('song_comments_content') self.song_comments_content.setText(self.song.comments) self.song_comments_content.setWordWrap(True) self.song_info_form_layout.setWidget(4, QtGui.QFormLayout.FieldRole, self.song_comments_content) # Add authors widget. self.song_authors_label = QtGui.QLabel(self) self.song_authors_label.setObjectName('song_authors_label') self.song_info_form_layout.setWidget(5, QtGui.QFormLayout.LabelRole, self.song_authors_label) self.song_authors_content = QtGui.QLabel(self) self.song_authors_content.setObjectName('song_authors_content') self.song_authors_content.setWordWrap(True) authors_text = ', '.join([author.display_name for author in self.song.authors]) self.song_authors_content.setText(authors_text) self.song_info_form_layout.setWidget(5, QtGui.QFormLayout.FieldRole, self.song_authors_content) # Add verse order widget. self.song_verse_order_label = QtGui.QLabel(self) self.song_verse_order_label.setObjectName('song_verse_order_label') self.song_info_form_layout.setWidget(6, QtGui.QFormLayout.LabelRole, self.song_verse_order_label) self.song_verse_order_content = QtGui.QLabel(self) self.song_verse_order_content.setObjectName('song_verse_order_content') self.song_verse_order_content.setText(self.song.verse_order) self.song_verse_order_content.setWordWrap(True) self.song_info_form_layout.setWidget(6, QtGui.QFormLayout.FieldRole, self.song_verse_order_content) self.song_group_box_layout.addLayout(self.song_info_form_layout) # Add verses widget. self.song_info_verse_list_widget = QtGui.QTableWidget(self.song_group_box) self.song_info_verse_list_widget.setColumnCount(1) self.song_info_verse_list_widget.horizontalHeader().setVisible(False) self.song_info_verse_list_widget.setObjectName('song_info_verse_list_widget') self.song_info_verse_list_widget.setSelectionMode(QtGui.QAbstractItemView.NoSelection) self.song_info_verse_list_widget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) self.song_info_verse_list_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.song_info_verse_list_widget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.song_info_verse_list_widget.setAlternatingRowColors(True) song_xml = SongXML() verses = song_xml.get_verses(self.song.lyrics) self.song_info_verse_list_widget.setRowCount(len(verses)) song_tags = [] for verse_number, verse in enumerate(verses): item = QtGui.QTableWidgetItem() item.setText(verse[1]) self.song_info_verse_list_widget.setItem(verse_number, 0, item) # We cannot use from_loose_input() here, because database # is supposed to contain English lowercase singlechar tags. verse_tag = verse[0]['type'] verse_index = None if len(verse_tag) > 1: verse_index = VerseType.from_translated_string(verse_tag) if verse_index is None: verse_index = VerseType.from_string(verse_tag, None) if verse_index is None: verse_index = VerseType.from_tag(verse_tag) verse_tag = VerseType.translated_tags[verse_index].upper() song_tags.append(str(verse_tag + verse[0]['label'])) self.song_info_verse_list_widget.setVerticalHeaderLabels(song_tags) # Resize table fields to content and table to columns self.song_info_verse_list_widget.setColumnWidth(0, self.song_group_box.width()) self.song_info_verse_list_widget.resizeRowsToContents() # The 6 is a trial and error value since verticalHeader().length() + offset() is a little bit to small. # It seems there is no clean way to determine the real height of the table contents. # The "correct" value slightly fluctuates depending on the theme used, in the worst case # Some pixels are missing at the bottom of the table, but all themes I tried still allowed # to read the last verse line, so I'll just leave it at that. self.song_info_verse_list_widget.setFixedHeight(self.song_info_verse_list_widget.verticalHeader().length() + self.song_info_verse_list_widget.verticalHeader().offset() + 6) self.song_group_box_layout.addWidget(self.song_info_verse_list_widget) self.song_group_box_layout.addStretch() self.song_vertical_layout.addWidget(self.song_group_box) self.song_remove_button = QtGui.QPushButton(self) self.song_remove_button.setObjectName('song_remove_button') self.song_remove_button.setIcon(build_icon(':/songs/song_delete.png')) self.song_remove_button.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) self.song_vertical_layout.addWidget(self.song_remove_button, alignment=QtCore.Qt.AlignHCenter)
def generate_slide_data(self, service_item, *, item=None, context=ServiceItemContext.Service, **kwargs): """ Generate the slide data. Needs to be implemented by the plugin. :param service_item: The service item to be built on :param item: The Song item to be used :param context: Why is it being generated :param kwargs: Consume other unused args specified by the base implementation, but not use by this one. """ log.debug('generate_slide_data: {service}, {item}, {remote}'.format( service=service_item, item=item, remote=self.remote_song)) item_id = self._get_id_of_item_to_generate(item, self.remote_song) service_item.add_capability(ItemCapabilities.CanEdit) service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanLoop) service_item.add_capability(ItemCapabilities.OnLoadUpdate) service_item.add_capability(ItemCapabilities.AddIfNewItem) service_item.add_capability(ItemCapabilities.CanSoftBreak) service_item.add_capability(ItemCapabilities.HasMetaData) song = self.plugin.manager.get_object(Song, item_id) service_item.theme = song.theme_name service_item.edit_id = item_id verse_list = SongXML().get_verses(song.lyrics) if self.settings.value( 'songs/add songbook slide') and song.songbook_entries: first_slide = '\n' for songbook_entry in song.songbook_entries: first_slide += '{book} #{num}'.format( book=songbook_entry.songbook.name, num=songbook_entry.entry) if songbook_entry.songbook.publisher: first_slide += ' ({pub})'.format( pub=songbook_entry.songbook.publisher) first_slide += '\n\n' service_item.add_from_text(first_slide, 'O1') # no verse list or only 1 space (in error) verse_tags_translated = False if VerseType.from_translated_string(str( verse_list[0][0]['type'])) is not None: verse_tags_translated = True if not song.verse_order.strip(): for verse in verse_list: # We cannot use from_loose_input() here, because database is supposed to contain English lowercase # single char tags. verse_tag = verse[0]['type'] verse_index = None if len(verse_tag) > 1: verse_index = VerseType.from_translated_string(verse_tag) if verse_index is None: verse_index = VerseType.from_string(verse_tag, None) if verse_index is None: verse_index = VerseType.from_tag(verse_tag) verse_tag = VerseType.translated_tags[verse_index].upper() verse_def = '{tag}{label}'.format(tag=verse_tag, label=verse[0]['label']) force_verse = verse[1].split('[--}{--]\n') for split_verse in force_verse: service_item.add_from_text(split_verse, verse_def) else: # Loop through the verse list and expand the song accordingly. for order in song.verse_order.lower().split(): if not order: break for verse in verse_list: if verse[0]['type'][0].lower() == \ order[0] and (verse[0]['label'].lower() == order[1:] or not order[1:]): if verse_tags_translated: verse_index = VerseType.from_translated_tag( verse[0]['type']) else: verse_index = VerseType.from_tag(verse[0]['type']) verse_tag = VerseType.translated_tags[verse_index] verse_def = '{tag}{label}'.format( tag=verse_tag, label=verse[0]['label']) force_verse = verse[1].split('[--}{--]\n') for split_verse in force_verse: service_item.add_from_text(split_verse, verse_def) service_item.title = song.title author_list = self.generate_footer(service_item, song) service_item.data_string = { 'title': song.search_title, 'authors': ', '.join(author_list) } service_item.xml_version = self.open_lyrics.song_to_xml(song) # Add the audio file to the service item. if song.media_files: if State().check_preconditions('media'): service_item.add_capability( ItemCapabilities.HasBackgroundAudio) total_length = 0 for m in song.media_files: total_length += self.media_controller.media_length( m.file_path) service_item.background_audio = [ m.file_path for m in song.media_files ] service_item.set_media_length(total_length) service_item.metadata.append( '<em>{label}:</em> {media}'.format( label=translate('SongsPlugin.MediaItem', 'Media'), media=service_item.background_audio)) return True
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False, context=ServiceItemContext.Service): """ Generate the slide data. Needs to be implemented by the plugin. :param service_item: The service item to be built on :param item: The Song item to be used :param xml_version: The xml version (not used) :param remote: Triggered from remote :param context: Why is it being generated """ log.debug('generate_slide_data: {service}, {item}, {remote}'.format(service=service_item, item=item, remote=self.remote_song)) item_id = self._get_id_of_item_to_generate(item, self.remote_song) service_item.add_capability(ItemCapabilities.CanEdit) service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanLoop) service_item.add_capability(ItemCapabilities.OnLoadUpdate) service_item.add_capability(ItemCapabilities.AddIfNewItem) service_item.add_capability(ItemCapabilities.CanSoftBreak) song = self.plugin.manager.get_object(Song, item_id) service_item.theme = song.theme_name service_item.edit_id = item_id verse_list = SongXML().get_verses(song.lyrics) # no verse list or only 1 space (in error) verse_tags_translated = False if VerseType.from_translated_string(str(verse_list[0][0]['type'])) is not None: verse_tags_translated = True if not song.verse_order.strip(): for verse in verse_list: # We cannot use from_loose_input() here, because database is supposed to contain English lowercase # singlechar tags. verse_tag = verse[0]['type'] verse_index = None if len(verse_tag) > 1: verse_index = VerseType.from_translated_string(verse_tag) if verse_index is None: verse_index = VerseType.from_string(verse_tag, None) if verse_index is None: verse_index = VerseType.from_tag(verse_tag) verse_tag = VerseType.translated_tags[verse_index].upper() verse_def = '{tag}{label}'.format(tag=verse_tag, label=verse[0]['label']) service_item.add_from_text(str(verse[1]), verse_def) else: # Loop through the verse list and expand the song accordingly. for order in song.verse_order.lower().split(): if not order: break for verse in verse_list: if verse[0]['type'][0].lower() == \ order[0] and (verse[0]['label'].lower() == order[1:] or not order[1:]): if verse_tags_translated: verse_index = VerseType.from_translated_tag(verse[0]['type']) else: verse_index = VerseType.from_tag(verse[0]['type']) verse_tag = VerseType.translated_tags[verse_index] verse_def = '{tag}{text}'.format(tag=verse_tag, text=verse[0]['label']) service_item.add_from_text(verse[1], verse_def) service_item.title = song.title author_list = self.generate_footer(service_item, song) service_item.data_string = {'title': song.search_title, 'authors': ', '.join(author_list)} service_item.xml_version = self.open_lyrics.song_to_xml(song) # Add the audio file to the service item. if song.media_files: service_item.add_capability(ItemCapabilities.HasBackgroundAudio) service_item.background_audio = [m.file_name for m in song.media_files] return True
def do_import_file(self, file): """ Process the OpenSong file - pass in a file-like object, not a file path. """ self.set_defaults() try: tree = objectify.parse(file) except (Error, LxmlError): self.log_error(file.name, SongStrings.XMLSyntaxError) log.exception('Error parsing XML') return root = tree.getroot() if root.tag != 'song': self.log_error( file.name, str( translate( 'SongsPlugin.OpenSongImport', 'Invalid OpenSong song file. Missing song tag.'))) return fields = dir(root) decode = { 'copyright': self.add_copyright, 'ccli': 'ccli_number', 'author': self.parse_author, 'title': 'title', 'aka': 'alternate_title', 'hymn_number': self.parse_song_book_name_and_number, 'user1': self.add_comment, 'user2': self.add_comment, 'user3': self.add_comment } for attr, fn_or_string in list(decode.items()): if attr in fields: ustring = str(root.__getattr__(attr)) if isinstance(fn_or_string, str): if attr in ['ccli']: ustring = ''.join(re.findall('\d+', ustring)) if ustring: setattr(self, fn_or_string, int(ustring)) else: setattr(self, fn_or_string, None) else: setattr(self, fn_or_string, ustring) else: fn_or_string(ustring) # Themes look like "God: Awe/Wonder", but we just want # "Awe" and "Wonder". We use a set to ensure each topic # is only added once, in case it is already there, which # is actually quite likely if the alttheme is set topics = set(self.topics) if 'theme' in fields: theme = str(root.theme) subthemes = theme[theme.find(':') + 1:].split('/') for topic in subthemes: topics.add(topic.strip()) if 'alttheme' in fields: theme = str(root.alttheme) subthemes = theme[theme.find(':') + 1:].split('/') for topic in subthemes: topics.add(topic.strip()) self.topics = list(topics) self.topics.sort() # data storage while importing verses = {} # keep track of verses appearance order our_verse_order = [] # default verse verse_tag = VerseType.tags[VerseType.Verse] verse_num = '1' # for the case where song has several sections with same marker inst = 1 if 'lyrics' in fields: lyrics = str(root.lyrics) else: lyrics = '' for this_line in lyrics.split('\n'): if not this_line.strip(): continue # skip this line if it is a comment if this_line.startswith(';'): continue # skip guitar chords and page and column breaks if this_line.startswith('.') or this_line.startswith( '---') or this_line.startswith('-!!'): continue # verse/chorus/etc. marker if this_line.startswith('['): # drop the square brackets right_bracket = this_line.find(']') content = this_line[1:right_bracket].lower() # have we got any digits? If so, verse number is everything from the digits to the end (openlp does not # have concept of part verses, so just ignore any non integers on the end (including floats)) match = re.match('(\D*)(\d+)', content) if match is not None: verse_tag = match.group(1) verse_num = match.group(2) else: # otherwise we assume number 1 and take the whole prefix as the verse tag verse_tag = content verse_num = '1' verse_index = VerseType.from_loose_input( verse_tag) if verse_tag else 0 verse_tag = VerseType.tags[verse_index] inst = 1 if [verse_tag, verse_num, inst ] in our_verse_order and verse_num in verses.get( verse_tag, {}): inst = len(verses[verse_tag][verse_num]) + 1 continue # number at start of line.. it's verse number if this_line[0].isdigit(): verse_num = this_line[0] this_line = this_line[1:].strip() verses.setdefault(verse_tag, {}) verses[verse_tag].setdefault(verse_num, {}) if inst not in verses[verse_tag][verse_num]: verses[verse_tag][verse_num][inst] = [] our_verse_order.append([verse_tag, verse_num, inst]) # Tidy text and remove the ____s from extended words this_line = self.tidy_text(this_line) this_line = this_line.replace('_', '') this_line = this_line.replace('||', '\n[---]\n') this_line = this_line.strip() # If the line consists solely of a '|', then just use the implicit newline # Otherwise, add a newline for each '|' if this_line == '|': this_line = '' else: this_line = this_line.replace('|', '\n') verses[verse_tag][verse_num][inst].append(this_line) # done parsing # add verses in original order verse_joints = {} for (verse_tag, verse_num, inst) in our_verse_order: lines = '\n'.join(verses[verse_tag][verse_num][inst]) length = 0 while length < len(verse_num) and verse_num[length].isnumeric(): length += 1 verse_def = '{tag}{number}'.format(tag=verse_tag, number=verse_num[:length]) verse_joints[verse_def] = '{verse}\n[---]\n{lines}'.format(verse=verse_joints[verse_def], lines=lines) \ if verse_def in verse_joints else lines # Parsing the dictionary produces the elements in a non-intuitive order. While it "works", it's not a # natural layout should the user come back to edit the song. Instead we sort by the verse type, so that we # get all the verses in order (v1, v2, ...), then the chorus(es), bridge(s), pre-chorus(es) etc. We use a # tuple for the key, since tuples naturally sort in this manner. verse_defs = sorted( verse_joints.keys(), key=lambda verse_def: (VerseType.from_tag(verse_def[0]), int(verse_def[1:]))) for verse_def in verse_defs: lines = verse_joints[verse_def] self.add_verse(lines, verse_def) if not self.verses: self.add_verse('') # figure out the presentation order, if present if 'presentation' in fields and root.presentation: order = str(root.presentation) # We make all the tags in the lyrics lower case, so match that here and then split into a list on the # whitespace. order = order.lower().split() for verse_def in order: match = re.match('(\D*)(\d+.*)', verse_def) if match is not None: verse_tag = match.group(1) verse_num = match.group(2) if not verse_tag: verse_tag = VerseType.tags[VerseType.Verse] else: # Assume it's no.1 if there are no digits verse_tag = verse_def verse_num = '1' verse_index = VerseType.from_loose_input(verse_tag) verse_tag = VerseType.tags[verse_index] verse_def = '{tag}{number}'.format(tag=verse_tag, number=verse_num) if verse_num in verses.get(verse_tag, {}): self.verse_order_list.append(verse_def) else: log.info( 'Got order {order} but not in verse tags, dropping this item from presentation ' 'order'.format(order=verse_def)) if not self.finish(): self.log_error(file.name)
def generateSlideData(self, service_item, item=None, xmlVersion=False, remote=False, context=ServiceItemContext.Service): log.debug(u'generateSlideData: %s, %s, %s' % (service_item, item, self.remoteSong)) item_id = self._getIdOfItemToGenerate(item, self.remoteSong) service_item.add_capability(ItemCapabilities.CanEdit) service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanLoop) service_item.add_capability(ItemCapabilities.OnLoadUpdate) service_item.add_capability(ItemCapabilities.AddIfNewItem) service_item.add_capability(ItemCapabilities.CanSoftBreak) song = self.plugin.manager.get_object(Song, item_id) service_item.theme = song.theme_name service_item.edit_id = item_id if song.lyrics.startswith(u'<?xml version='): verse_list = SongXML().get_verses(song.lyrics) # no verse list or only 1 space (in error) verse_tags_translated = False if VerseType.from_translated_string(unicode(verse_list[0][0][u'type'])) is not None: verse_tags_translated = True if not song.verse_order.strip(): for verse in verse_list: # We cannot use from_loose_input() here, because database # is supposed to contain English lowercase singlechar tags. verse_tag = verse[0][u'type'] verse_index = None if len(verse_tag) > 1: verse_index = VerseType.from_translated_string(verse_tag) if verse_index is None: verse_index = VerseType.from_string(verse_tag, None) if verse_index is None: verse_index = VerseType.from_tag(verse_tag) verse_tag = VerseType.TranslatedTags[verse_index].upper() verse_def = u'%s%s' % (verse_tag, verse[0][u'label']) service_item.add_from_text(unicode(verse[1]), verse_def) else: # Loop through the verse list and expand the song accordingly. for order in song.verse_order.lower().split(): if not order: break for verse in verse_list: if verse[0][u'type'][0].lower() == order[0] and (verse[0][u'label'].lower() == order[1:] or \ not order[1:]): if verse_tags_translated: verse_index = VerseType.from_translated_tag(verse[0][u'type']) else: verse_index = VerseType.from_tag(verse[0][u'type']) verse_tag = VerseType.TranslatedTags[verse_index] verse_def = u'%s%s' % (verse_tag, verse[0][u'label']) service_item.add_from_text(verse[1], verse_def) else: verses = song.lyrics.split(u'\n\n') for slide in verses: service_item.add_from_text(unicode(slide)) service_item.title = song.title author_list = [unicode(author.display_name) for author in song.authors] service_item.raw_footer.append(song.title) service_item.raw_footer.append(create_separated_list(author_list)) service_item.raw_footer.append(song.copyright) if Settings().value(u'general/ccli number'): service_item.raw_footer.append(translate('SongsPlugin.MediaItem', 'CCLI License: ') + Settings().value(u'general/ccli number')) service_item.audit = [ song.title, author_list, song.copyright, unicode(song.ccli_number) ] service_item.data_string = {u'title': song.search_title, u'authors': u', '.join(author_list)} service_item.xml_version = self.openLyrics.song_to_xml(song) # Add the audio file to the service item. if song.media_files: service_item.add_capability(ItemCapabilities.HasBackgroundAudio) service_item.background_audio = [m.file_name for m in song.media_files] return True
def _process_lyrics(self, foilpresenterfolie, song): """ Processes the verses and search_lyrics for the song. :param foilpresenterfolie: The foilpresenterfolie object (lxml.objectify.ObjectifiedElement). :param song: The song object. """ sxml = SongXML() temp_verse_order = {} temp_verse_order_backup = [] temp_sortnr_backup = 1 temp_sortnr_liste = [] verse_count = { VerseType.tags[VerseType.Verse]: 1, VerseType.tags[VerseType.Chorus]: 1, VerseType.tags[VerseType.Bridge]: 1, VerseType.tags[VerseType.Ending]: 1, VerseType.tags[VerseType.Other]: 1, VerseType.tags[VerseType.Intro]: 1, VerseType.tags[VerseType.PreChorus]: 1 } if not hasattr(foilpresenterfolie.strophen, 'strophe'): self.importer.log_error(self._child(foilpresenterfolie.titel), str(translate('SongsPlugin.FoilPresenterSongImport', 'Invalid Foilpresenter song file. No verses found.'))) self.save_song = False return for strophe in foilpresenterfolie.strophen.strophe: text = self._child(strophe.text_) if hasattr(strophe, 'text_') else '' verse_name = self._child(strophe.key) children = strophe.getchildren() sortnr = False for child in children: if child.tag == 'sortnr': verse_sortnr = self._child(strophe.sortnr) sortnr = True # In older Version there is no sortnr, but we need one if not sortnr: verse_sortnr = str(temp_sortnr_backup) temp_sortnr_backup += 1 # Foilpresenter allows e. g. "Ref" or "1", but we need "C1" or "V1". temp_sortnr_liste.append(verse_sortnr) temp_verse_name = re.compile('[0-9].*').sub('', verse_name) temp_verse_name = temp_verse_name[:3].lower() if temp_verse_name == 'ref': verse_type = VerseType.tags[VerseType.Chorus] elif temp_verse_name == 'r': verse_type = VerseType.tags[VerseType.Chorus] elif temp_verse_name == '': verse_type = VerseType.tags[VerseType.Verse] elif temp_verse_name == 'v': verse_type = VerseType.tags[VerseType.Verse] elif temp_verse_name == 'bri': verse_type = VerseType.tags[VerseType.Bridge] elif temp_verse_name == 'cod': verse_type = VerseType.tags[VerseType.Ending] elif temp_verse_name == 'sch': verse_type = VerseType.tags[VerseType.Ending] elif temp_verse_name == 'pre': verse_type = VerseType.tags[VerseType.PreChorus] elif temp_verse_name == 'int': verse_type = VerseType.tags[VerseType.Intro] else: verse_type = VerseType.tags[VerseType.Other] verse_number = re.compile('[a-zA-Z.+-_ ]*').sub('', verse_name) # Foilpresenter allows e. g. "C", but we need "C1". if not verse_number: verse_number = str(verse_count[verse_type]) verse_count[verse_type] += 1 else: # test if foilpresenter have the same versenumber two times with # different parts raise the verse number for value in temp_verse_order_backup: if value == ''.join((verse_type, verse_number)): verse_number = str(int(verse_number) + 1) verse_type_index = VerseType.from_tag(verse_type[0]) verse_type = VerseType.tags[verse_type_index] temp_verse_order[verse_sortnr] = ''.join((verse_type[0], verse_number)) temp_verse_order_backup.append(''.join((verse_type[0], verse_number))) sxml.add_verse_to_lyrics(verse_type, verse_number, text) song.lyrics = str(sxml.extract_xml(), 'utf-8') # Process verse order verse_order = [] verse_strophenr = [] try: for strophennummer in foilpresenterfolie.reihenfolge.strophennummer: verse_strophenr.append(strophennummer) except AttributeError: pass # Currently we do not support different "parts"! if '0' in temp_verse_order: for vers in temp_verse_order_backup: verse_order.append(vers) else: for number in verse_strophenr: numberx = temp_sortnr_liste[int(number)] verse_order.append(temp_verse_order[str(numberx)]) song.verse_order = ' '.join(verse_order)
def load_song(self, song_id, preview=False): """ Loads a song. ``song_id`` The song id (int). ``preview`` Should be ``True`` if the song is also previewed (boolean). """ log.debug('Load Song') self.initialise() self.song_tab_widget.setCurrentIndex(0) self.load_authors() self.load_topics() self.load_books() self.load_media_files() self.song = self.manager.get_object(Song, song_id) self.title_edit.setText(self.song.title) self.alternative_edit.setText( self.song.alternate_title if self.song.alternate_title else '') if self.song.song_book_id != 0: book_name = self.manager.get_object(Book, self.song.song_book_id) find_and_set_in_combo_box(self.song_book_combo_box, str(book_name.name)) else: self.song_book_combo_box.setEditText('') self.song_book_combo_box.setCurrentIndex(0) if self.song.theme_name: find_and_set_in_combo_box(self.theme_combo_box, str(self.song.theme_name)) else: # Clear the theme combo box in case it was previously set (bug #1212801) self.theme_combo_box.setEditText('') self.theme_combo_box.setCurrentIndex(0) self.copyright_edit.setText(self.song.copyright if self.song.copyright else '') self.comments_edit.setPlainText(self.song.comments if self.song.comments else '') self.ccli_number_edit.setText(self.song.ccli_number if self.song.ccli_number else '') self.song_book_number_edit.setText(self.song.song_number if self.song.song_number else '') # lazy xml migration for now self.verse_list_widget.clear() self.verse_list_widget.setRowCount(0) verse_tags_translated = False if self.song.lyrics.startswith('<?xml version='): songXML = SongXML() verse_list = songXML.get_verses(self.song.lyrics) for count, verse in enumerate(verse_list): self.verse_list_widget.setRowCount(self.verse_list_widget.rowCount() + 1) # This silently migrates from localized verse type markup. # If we trusted the database, this would be unnecessary. verse_tag = verse[0]['type'] index = None if len(verse_tag) > 1: index = VerseType.from_translated_string(verse_tag) if index is None: index = VerseType.from_string(verse_tag, None) else: verse_tags_translated = True if index is None: index = VerseType.from_tag(verse_tag) verse[0]['type'] = VerseType.tags[index] if verse[0]['label'] == '': verse[0]['label'] = '1' verse_def = '%s%s' % (verse[0]['type'], verse[0]['label']) item = QtGui.QTableWidgetItem(verse[1]) item.setData(QtCore.Qt.UserRole, verse_def) self.verse_list_widget.setItem(count, 0, item) else: verses = self.song.lyrics.split('\n\n') for count, verse in enumerate(verses): self.verse_list_widget.setRowCount(self.verse_list_widget.rowCount() + 1) item = QtGui.QTableWidgetItem(verse) verse_def = '%s%s' % (VerseType.tags[VerseType.Verse], str(count + 1)) item.setData(QtCore.Qt.UserRole, verse_def) self.verse_list_widget.setItem(count, 0, item) if self.song.verse_order: # we translate verse order translated = [] for verse_def in self.song.verse_order.split(): verse_index = None if verse_tags_translated: verse_index = VerseType.from_translated_tag(verse_def[0], None) if verse_index is None: verse_index = VerseType.from_tag(verse_def[0]) verse_tag = VerseType.translated_tags[verse_index].upper() translated.append('%s%s' % (verse_tag, verse_def[1:])) self.verse_order_edit.setText(' '.join(translated)) else: self.verse_order_edit.setText('') self.tag_rows() # clear the results self.authors_list_view.clear() for author in self.song.authors: author_name = QtGui.QListWidgetItem(str(author.display_name)) author_name.setData(QtCore.Qt.UserRole, author.id) self.authors_list_view.addItem(author_name) # clear the results self.topics_list_view.clear() for topic in self.song.topics: topic_name = QtGui.QListWidgetItem(str(topic.name)) topic_name.setData(QtCore.Qt.UserRole, topic.id) self.topics_list_view.addItem(topic_name) self.audio_list_widget.clear() for media in self.song.media_files: media_file = QtGui.QListWidgetItem(os.path.split(media.file_name)[1]) media_file.setData(QtCore.Qt.UserRole, media.file_name) self.audio_list_widget.addItem(media_file) self.title_edit.setFocus() # Hide or show the preview button. self.preview_button.setVisible(preview) # Check if all verse tags are used. self.on_verse_order_text_changed(self.verse_order_edit.text())
def do_import_file(self, file): """ Process the OpenSong file - pass in a file-like object, not a file path. """ self.set_defaults() try: tree = objectify.parse(file) except (Error, LxmlError): self.log_error(file.name, SongStrings.XMLSyntaxError) log.exception('Error parsing XML') return root = tree.getroot() if root.tag != 'song': self.log_error(file.name, str( translate('SongsPlugin.OpenSongImport', 'Invalid OpenSong song file. Missing song tag.'))) return fields = dir(root) decode = { 'copyright': self.add_copyright, 'ccli': 'ccli_number', 'author': self.parse_author, 'title': 'title', 'aka': 'alternate_title', 'hymn_number': self.parse_song_book_name_and_number, 'user1': self.add_comment, 'user2': self.add_comment, 'user3': self.add_comment } for attr, fn_or_string in list(decode.items()): if attr in fields: ustring = str(root.__getattr__(attr)) if isinstance(fn_or_string, str): if attr in ['ccli']: if ustring: setattr(self, fn_or_string, int(ustring)) else: setattr(self, fn_or_string, None) else: setattr(self, fn_or_string, ustring) else: fn_or_string(ustring) # Themes look like "God: Awe/Wonder", but we just want # "Awe" and "Wonder". We use a set to ensure each topic # is only added once, in case it is already there, which # is actually quite likely if the alttheme is set topics = set(self.topics) if 'theme' in fields: theme = str(root.theme) subthemes = theme[theme.find(':')+1:].split('/') for topic in subthemes: topics.add(topic.strip()) if 'alttheme' in fields: theme = str(root.alttheme) subthemes = theme[theme.find(':')+1:].split('/') for topic in subthemes: topics.add(topic.strip()) self.topics = list(topics) self.topics.sort() # data storage while importing verses = {} # keep track of verses appearance order our_verse_order = [] # default verse verse_tag = VerseType.tags[VerseType.Verse] verse_num = '1' # for the case where song has several sections with same marker inst = 1 if 'lyrics' in fields: lyrics = str(root.lyrics) else: lyrics = '' for this_line in lyrics.split('\n'): if not this_line.strip(): continue # skip this line if it is a comment if this_line.startswith(';'): continue # skip guitar chords and page and column breaks if this_line.startswith('.') or this_line.startswith('---') or this_line.startswith('-!!'): continue # verse/chorus/etc. marker if this_line.startswith('['): # drop the square brackets right_bracket = this_line.find(']') content = this_line[1:right_bracket].lower() # have we got any digits? If so, verse number is everything from the digits to the end (openlp does not # have concept of part verses, so just ignore any non integers on the end (including floats)) match = re.match('(\D*)(\d+)', content) if match is not None: verse_tag = match.group(1) verse_num = match.group(2) else: # otherwise we assume number 1 and take the whole prefix as the verse tag verse_tag = content verse_num = '1' verse_index = VerseType.from_loose_input(verse_tag) if verse_tag else 0 verse_tag = VerseType.tags[verse_index] inst = 1 if [verse_tag, verse_num, inst] in our_verse_order and verse_num in verses.get(verse_tag, {}): inst = len(verses[verse_tag][verse_num]) + 1 continue # number at start of line.. it's verse number if this_line[0].isdigit(): verse_num = this_line[0] this_line = this_line[1:].strip() verses.setdefault(verse_tag, {}) verses[verse_tag].setdefault(verse_num, {}) if inst not in verses[verse_tag][verse_num]: verses[verse_tag][verse_num][inst] = [] our_verse_order.append([verse_tag, verse_num, inst]) # Tidy text and remove the ____s from extended words this_line = self.tidy_text(this_line) this_line = this_line.replace('_', '') this_line = this_line.replace('||', '\n[---]\n') this_line = this_line.strip() # If the line consists solely of a '|', then just use the implicit newline # Otherwise, add a newline for each '|' if this_line == '|': this_line = '' else: this_line = this_line.replace('|', '\n') verses[verse_tag][verse_num][inst].append(this_line) # done parsing # add verses in original order verse_joints = {} for (verse_tag, verse_num, inst) in our_verse_order: lines = '\n'.join(verses[verse_tag][verse_num][inst]) length = 0 while length < len(verse_num) and verse_num[length].isnumeric(): length += 1 verse_def = '%s%s' % (verse_tag, verse_num[:length]) verse_joints[verse_def] = '%s\n[---]\n%s' % (verse_joints[verse_def], lines) \ if verse_def in verse_joints else lines # Parsing the dictionary produces the elements in a non-intuitive order. While it "works", it's not a # natural layout should the user come back to edit the song. Instead we sort by the verse type, so that we # get all the verses in order (v1, v2, ...), then the chorus(es), bridge(s), pre-chorus(es) etc. We use a # tuple for the key, since tuples naturally sort in this manner. verse_defs = sorted(verse_joints.keys(), key=lambda verse_def: (VerseType.from_tag(verse_def[0]), int(verse_def[1:]))) for verse_def in verse_defs: lines = verse_joints[verse_def] self.add_verse(lines, verse_def) if not self.verses: self.add_verse('') # figure out the presentation order, if present if 'presentation' in fields and root.presentation: order = str(root.presentation) # We make all the tags in the lyrics lower case, so match that here and then split into a list on the # whitespace. order = order.lower().split() for verse_def in order: match = re.match('(\D*)(\d+.*)', verse_def) if match is not None: verse_tag = match.group(1) verse_num = match.group(2) if not verse_tag: verse_tag = VerseType.tags[VerseType.Verse] else: # Assume it's no.1 if there are no digits verse_tag = verse_def verse_num = '1' verse_index = VerseType.from_loose_input(verse_tag) verse_tag = VerseType.tags[verse_index] verse_def = '%s%s' % (verse_tag, verse_num) if verse_num in verses.get(verse_tag, {}): self.verse_order_list.append(verse_def) else: log.info('Got order %s but not in verse tags, dropping this item from presentation order', verse_def) if not self.finish(): self.log_error(file.name)
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False, context=ServiceItemContext.Service): """ Generate the slide data. Needs to be implemented by the plugin. :param service_item: The service item to be built on :param item: The Song item to be used :param xml_version: The xml version (not used) :param remote: Triggered from remote :param context: Why is it being generated """ log.debug('generate_slide_data: %s, %s, %s' % (service_item, item, self.remote_song)) item_id = self._get_id_of_item_to_generate(item, self.remote_song) service_item.add_capability(ItemCapabilities.CanEdit) service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanLoop) service_item.add_capability(ItemCapabilities.OnLoadUpdate) service_item.add_capability(ItemCapabilities.AddIfNewItem) service_item.add_capability(ItemCapabilities.CanSoftBreak) song = self.plugin.manager.get_object(Song, item_id) service_item.theme = song.theme_name service_item.edit_id = item_id verse_list = SongXML().get_verses(song.lyrics) # no verse list or only 1 space (in error) verse_tags_translated = False if VerseType.from_translated_string(str(verse_list[0][0]['type'])) is not None: verse_tags_translated = True if not song.verse_order.strip(): for verse in verse_list: # We cannot use from_loose_input() here, because database is supposed to contain English lowercase # singlechar tags. verse_tag = verse[0]['type'] verse_index = None if len(verse_tag) > 1: verse_index = VerseType.from_translated_string(verse_tag) if verse_index is None: verse_index = VerseType.from_string(verse_tag, None) if verse_index is None: verse_index = VerseType.from_tag(verse_tag) verse_tag = VerseType.translated_tags[verse_index].upper() verse_def = '%s%s' % (verse_tag, verse[0]['label']) service_item.add_from_text(str(verse[1]), verse_def) else: # Loop through the verse list and expand the song accordingly. for order in song.verse_order.lower().split(): if not order: break for verse in verse_list: if verse[0]['type'][0].lower() == \ order[0] and (verse[0]['label'].lower() == order[1:] or not order[1:]): if verse_tags_translated: verse_index = VerseType.from_translated_tag(verse[0]['type']) else: verse_index = VerseType.from_tag(verse[0]['type']) verse_tag = VerseType.translated_tags[verse_index] verse_def = '%s%s' % (verse_tag, verse[0]['label']) service_item.add_from_text(verse[1], verse_def) service_item.title = song.title author_list = self.generate_footer(service_item, song) service_item.data_string = {'title': song.search_title, 'authors': ', '.join(author_list)} service_item.set_extra_data_dict(self.open_lyrics.song_to_line_dict(song)) service_item.xml_version = self.open_lyrics.song_to_xml(song) # Add the audio file to the service item. if song.media_files: service_item.add_capability(ItemCapabilities.HasBackgroundAudio) service_item.background_audio = [m.file_name for m in song.media_files] return True