Example #1
0
    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
Example #2
0
    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
Example #3
0
 def on_verse_edit_all_button_clicked(self):
     verse_list = ''
     if self.verse_list_widget.rowCount() > 0:
         for row in range(self.verse_list_widget.rowCount()):
             item = self.verse_list_widget.item(row, 0)
             field = item.data(QtCore.Qt.UserRole)
             verse_tag = VerseType.translated_name(field[0])
             verse_num = field[1:]
             verse_list += '---[%s:%s]---\n' % (verse_tag, verse_num)
             verse_list += item.text()
             verse_list += '\n'
         self.verse_form.set_verse(verse_list)
     else:
         self.verse_form.set_verse('')
     if not self.verse_form.exec_():
         return
     verse_list = self.verse_form.get_all_verses()
     verse_list = str(verse_list.replace('\r\n', '\n'))
     self.verse_list_widget.clear()
     self.verse_list_widget.setRowCount(0)
     for row in self.find_verse_split.split(verse_list):
         for match in row.split('---['):
             for count, parts in enumerate(match.split(']---\n')):
                 if count == 0:
                     if len(parts) == 0:
                         continue
                     # handling carefully user inputted versetags
                     separator = parts.find(':')
                     if separator >= 0:
                         verse_name = parts[0:separator].strip()
                         verse_num = parts[separator+1:].strip()
                     else:
                         verse_name = parts
                         verse_num = '1'
                     verse_index = VerseType.from_loose_input(verse_name)
                     verse_tag = VerseType.tags[verse_index]
                     # Later we need to handle v1a as well.
                     #regex = re.compile(r'(\d+\w.)')
                     regex = re.compile(r'\D*(\d+)\D*')
                     match = regex.match(verse_num)
                     if match:
                         verse_num = match.group(1)
                     else:
                         verse_num = '1'
                     verse_def = '%s%s' % (verse_tag, verse_num)
                 else:
                     if parts.endswith('\n'):
                         parts = parts.rstrip('\n')
                     item = QtGui.QTableWidgetItem(parts)
                     item.setData(QtCore.Qt.UserRole, verse_def)
                     self.verse_list_widget.setRowCount(self.verse_list_widget.rowCount() + 1)
                     self.verse_list_widget.setItem(self.verse_list_widget.rowCount() - 1, 0, item)
     self.tag_rows()
     self.verse_edit_button.setEnabled(False)
     self.verse_delete_button.setEnabled(False)
     # Check if all verse tags are used.
     self.on_verse_order_text_changed(self.verse_order_edit.text())
Example #4
0
    def save_song(self, song):
        """
        Save a song to the database, using the db_manager

        :param song:
        :return:
        """
        db_song = Song.populate(title=song['title'], copyright=song['copyright'], ccli_number=song['ccli_number'])
        song_xml = SongXML()
        verse_order = []
        for verse in song['verses']:
            verse_type, verse_number = verse['label'].split(' ')[:2]
            verse_type = VerseType.from_loose_input(verse_type)
            verse_number = int(verse_number)
            song_xml.add_verse_to_lyrics(VerseType.tags[verse_type], verse_number, verse['lyrics'])
            verse_order.append('%s%s' % (VerseType.tags[verse_type], verse_number))
        db_song.verse_order = ' '.join(verse_order)
        db_song.lyrics = song_xml.extract_xml()
        clean_song(self.db_manager, db_song)
        self.db_manager.save_object(db_song)
        db_song.authors_songs = []
        for author_name in song['authors']:
            author = self.db_manager.get_object_filtered(Author, Author.display_name == author_name)
            if not author:
                author = Author.populate(first_name=author_name.rsplit(' ', 1)[0],
                                         last_name=author_name.rsplit(' ', 1)[1],
                                         display_name=author_name)
            db_song.add_author(author)
        self.db_manager.save_object(db_song)
        return db_song
Example #5
0
    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)
Example #6
0
 def _validate_verse_list(self, verse_order, verse_count):
     verses = []
     invalid_verses = []
     verse_names = []
     order_names = str(verse_order).split()
     order = self._extract_verse_order(verse_order)
     for index in range(verse_count):
         verse = self.verse_list_widget.item(index, 0)
         verse = verse.data(QtCore.Qt.UserRole)
         if verse not in verse_names:
             verses.append(verse)
             verse_names.append('%s%s' % (VerseType.translated_tag(verse[0]), verse[1:]))
     for count, item in enumerate(order):
         if item not in verses:
             invalid_verses.append(order_names[count])
     if invalid_verses:
         valid = create_separated_list(verse_names)
         if len(invalid_verses) > 1:
             msg = translate('SongsPlugin.EditSongForm', 'There are no verses corresponding to "%(invalid)s".'
                 'Valid entries are %(valid)s.\nPlease enter the verses seperated by spaces.') \
                 % {'invalid' : ', '.join(invalid_verses), 'valid' : valid}
         else:
             msg = translate('SongsPlugin.EditSongForm', 'There is no verse corresponding to "%(invalid)s".'
                 'Valid entries are %(valid)s.\nPlease enter the verses seperated by spaces.') \
                 % {'invalid' : invalid_verses[0], 'valid' : valid}
         critical_error_message_box(title=translate('SongsPlugin.EditSongForm', 'Invalid Verse Order'),
                                    message=msg)
     return len(invalid_verses) == 0
    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)
Example #8
0
    def test_from_loose_input_with_valid_input(self, mocked_translated_tags):
        """
        Test that the from_loose_input() method returns valid output on valid input.
        """
        # GIVEN: A mocked VerseType.translated_tags
        # WHEN: We run the from_loose_input() method with a valid verse type, we get the expected VerseType back
        result = VerseType.from_loose_input('v')

        # THEN: The result should be a Verse
        self.assertEqual(result, VerseType.Verse, 'The result should be a verse, but was "%s"' % result)
Example #9
0
    def test_from_loose_input_with_invalid_input(self, mocked_translated_tags):
        """
        Test that the from_loose_input() method returns a sane default when passed an invalid tag and None as default.
        """
        # GIVEN: A mocked VerseType.translated_tags
        # WHEN: We run the from_loose_input() method with an invalid verse type, we get the specified default back
        result = VerseType.from_loose_input('m', None)

        # THEN: The result should be None
        self.assertIsNone(result, 'The result should be None, but was "%s"' % result)
Example #10
0
    def translated_name_test(self):
        """
        Test that the translated_name() method returns the correct name
        """
        # 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 translated_name() method with a "verse"
            result = VerseType.translated_name('v')

            # THEN: The result should be "Verse"
            self.assertEqual(result, 'Verse', 'The result should be "Verse"')

            # WHEN: We run the translated_name() method with a "chorus"
            result = VerseType.translated_name('c')

            # THEN: The result should be "Chorus"
            self.assertEqual(result, 'Chorus', 'The result should be "Chorus"')
Example #11
0
    def test_from_loose_input_with_valid_input(self, mocked_translated_tags):
        """
        Test that the from_loose_input() method returns valid output on valid input.
        """
        # GIVEN: A mocked VerseType.translated_tags
        # WHEN: We run the from_loose_input() method with a valid verse type, we get the expected VerseType back
        result = VerseType.from_loose_input('v')

        # THEN: The result should be a Verse
        assert result == VerseType.Verse, 'The result should be a verse, but was "%s"' % result
Example #12
0
    def test_from_loose_input_with_invalid_input(self, mocked_translated_tags):
        """
        Test that the from_loose_input() method returns a sane default when passed an invalid tag and None as default.
        """
        # GIVEN: A mocked VerseType.translated_tags
        # WHEN: We run the from_loose_input() method with an invalid verse type, we get the specified default back
        result = VerseType.from_loose_input('m', None)

        # THEN: The result should be None
        assert result is None, 'The result should be None, but was "%s"' % result
Example #13
0
    def translated_name_test(self):
        """
        Test that the translated_name() method returns the correct name
        """
        # 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 translated_name() method with a "verse"
            result = VerseType.translated_name('v')

            # THEN: The result should be "Verse"
            self.assertEqual(result, 'Verse', 'The result should be "Verse"')

            # WHEN: We run the translated_name() method with a "chorus"
            result = VerseType.translated_name('c')

            # THEN: The result should be "Chorus"
            self.assertEqual(result, 'Chorus', 'The result should be "Chorus"')
Example #14
0
    def test_translated_tag(self):
        """
        Test that the translated_tag() method returns the correct tags
        """
        # 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 translated_tag() method with a "verse"
            result = VerseType.translated_tag('v')

            # THEN: The result should be "V"
            assert result == 'V', 'The result should be "V"'

            # WHEN: We run the translated_tag() method with a "chorus"
            result = VerseType.translated_tag('c')

            # THEN: The result should be "C"
            assert result == 'C', 'The result should be "C"'
Example #15
0
    def insert_verse(self, verse_tag, verse_num=1):
        """
        Insert a verse

        :param verse_tag: The verse tag
        :param verse_num: The verse number
        """
        if self.verse_text_edit.textCursor().columnNumber() != 0:
            self.verse_text_edit.insertPlainText('\n')
        verse_tag = VerseType.translated_name(verse_tag)
        self.verse_text_edit.insertPlainText('---[%s:%s]---\n' % (verse_tag, verse_num))
        self.verse_text_edit.setFocus()
Example #16
0
 def _extract_verse_order(self, verse_order):
     order = []
     order_names = str(verse_order).split()
     for item in order_names:
         if len(item) == 1:
             verse_index = VerseType.from_translated_tag(item, None)
             if verse_index is not None:
                 order.append(VerseType.tags[verse_index] + '1')
             else:
                 # it matches no verses anyway
                 order.append('')
         else:
             verse_index = VerseType.from_translated_tag(item[0], None)
             if verse_index is None:
                 # it matches no verses anyway
                 order.append('')
             else:
                 verse_tag = VerseType.tags[verse_index]
                 verse_num = item[1:].lower()
                 order.append(verse_tag + verse_num)
     return order
Example #17
0
    def insert_verse(self, verse_tag, verse_num=1):
        """
        Insert a verse

        :param verse_tag: The verse tag
        :param verse_num: The verse number
        """
        if self.verse_text_edit.textCursor().columnNumber() != 0:
            self.verse_text_edit.insertPlainText('\n')
        verse_tag = VerseType.translated_name(verse_tag)
        self.verse_text_edit.insertPlainText('---[%s:%s]---\n' % (verse_tag, verse_num))
        self.verse_text_edit.setFocus()
Example #18
0
    def save_song(self, song):
        """
        Save a song to the database, using the db_manager

        :param song:
        :return:
        """
        db_song = Song.populate(title=song['title'],
                                copyright=song['copyright'],
                                ccli_number=song['ccli_number'])
        song_xml = SongXML()
        verse_order = []
        for verse in song['verses']:
            if ' ' in verse['label']:
                verse_type, verse_number = verse['label'].split(' ', 1)
            else:
                verse_type = verse['label']
                verse_number = 1
            verse_type = VerseType.from_loose_input(verse_type)
            verse_number = int(verse_number)
            song_xml.add_verse_to_lyrics(VerseType.tags[verse_type],
                                         verse_number, verse['lyrics'])
            verse_order.append('%s%s' %
                               (VerseType.tags[verse_type], verse_number))
        db_song.verse_order = ' '.join(verse_order)
        db_song.lyrics = song_xml.extract_xml()
        clean_song(self.db_manager, db_song)
        self.db_manager.save_object(db_song)
        db_song.authors_songs = []
        for author_name in song['authors']:
            author = self.db_manager.get_object_filtered(
                Author, Author.display_name == author_name)
            if not author:
                name_parts = author_name.rsplit(' ', 1)
                first_name = name_parts[0]
                if len(name_parts) == 1:
                    last_name = ''
                else:
                    last_name = name_parts[1]
                author = Author.populate(first_name=first_name,
                                         last_name=last_name,
                                         display_name=author_name)
            db_song.add_author(author)
        db_song.topics = []
        for topic_name in song.get('topics', []):
            topic = self.db_manager.get_object_filtered(
                Topic, Topic.name == topic_name)
            if not topic:
                topic = Topic.populate(name=topic_name)
            db_song.topics.append(topic)
        self.db_manager.save_object(db_song)
        return db_song
Example #19
0
    def test_translated_invalid_tag_with_invalid_default(self):
        """
        Test that the translated_tag() method returns a sane default tag when passed 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 translated_tag() method with an invalid verse type and an invalid default
            result = VerseType.translated_tag('q', 29)

            # THEN: The result should be "O"
            assert result == 'O', 'The result should be "O", but was "%s"' % result
Example #20
0
    def test_translated_invalid_name(self):
        """
        Test that the translated_name() method returns the default name 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 translated_name() method with an invalid verse type
            result = VerseType.translated_name('z')

            # THEN: The result should be "Other"
            assert result == 'Other', 'The result should be "Other", but was "%s"' % result
Example #21
0
    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
Example #22
0
    def test_translated_invalid_tag(self):
        """
        Test that the translated_tag() method returns the default tag 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 translated_tag() method with an invalid verse type
            result = VerseType.translated_tag('z')

            # THEN: The result should be "O"
            self.assertEqual(result, 'O', 'The result should be "O", but was "%s"' % result)
Example #23
0
    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)
Example #24
0
    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)
Example #25
0
    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)
Example #26
0
    def translated_invalid_name_with_invalid_default_test(self):
        """
        Test that the translated_name() method returns the specified default tag 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 translated_name() method with an invalid verse type and specify an invalid default
            result = VerseType.translated_name('q', 29)

            # THEN: The result should be "Other"
            self.assertEqual(result, 'Other', 'The result should be "Other", but was "%s"' % result)
Example #27
0
    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)
Example #28
0
    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)
Example #29
0
    def translated_invalid_name_with_invalid_default_test(self):
        """
        Test that the translated_name() method returns the specified default tag 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 translated_name() method with an invalid verse type and specify an invalid default
            result = VerseType.translated_name('q', 29)

            # THEN: The result should be "Other"
            self.assertEqual(result, 'Other', 'The result should be "Other", but was "%s"' % result)
Example #30
0
 def tag_rows(self):
     """
     Tag the Song List rows based on the verse list
     """
     row_label = []
     for row in range(self.verse_list_widget.rowCount()):
         item = self.verse_list_widget.item(row, 0)
         verse_def = item.data(QtCore.Qt.UserRole)
         verse_tag = VerseType.translated_tag(verse_def[0])
         row_def = '%s%s' % (verse_tag, verse_def[1:])
         row_label.append(row_def)
     self.verse_list_widget.setVerticalHeaderLabels(row_label)
     self.verse_list_widget.resizeRowsToContents()
     self.verse_list_widget.repaint()
Example #31
0
    def save_song(self, song):
        """
        Save a song to the database, using the db_manager

        :param song:
        :return:
        """
        db_song = Song.populate(title=song['title'], copyright=song['copyright'], ccli_number=song['ccli_number'])
        song_xml = SongXML()
        verse_order = []
        for verse in song['verses']:
            if ' ' in verse['label']:
                verse_type, verse_number = verse['label'].split(' ', 1)
            else:
                verse_type = verse['label']
                verse_number = 1
            verse_type = VerseType.from_loose_input(verse_type)
            verse_number = int(verse_number)
            song_xml.add_verse_to_lyrics(VerseType.tags[verse_type], verse_number, verse['lyrics'])
            verse_order.append('{tag}{number}'.format(tag=VerseType.tags[verse_type], number=verse_number))
        db_song.verse_order = ' '.join(verse_order)
        db_song.lyrics = song_xml.extract_xml()
        clean_song(self.db_manager, db_song)
        self.db_manager.save_object(db_song)
        db_song.authors_songs = []
        for author_name in song['authors']:
            author = self.db_manager.get_object_filtered(Author, Author.display_name == author_name)
            if not author:
                name_parts = author_name.rsplit(' ', 1)
                first_name = name_parts[0]
                if len(name_parts) == 1:
                    last_name = ''
                else:
                    last_name = name_parts[1]
                author = Author.populate(first_name=first_name, last_name=last_name, display_name=author_name)
            db_song.add_author(author)
        for topic_name in song.get('topics', []):
            topic = self.db_manager.get_object_filtered(Topic, Topic.name == topic_name)
            if not topic:
                topic = Topic.populate(name=topic_name)
            db_song.topics.append(topic)
        self.db_manager.save_object(db_song)
        return db_song
Example #32
0
    def save_song(self, song):
        """
        Save a song to the database, using the db_manager

        :param song:
        :return:
        """
        db_song = Song.populate(title=song['title'],
                                copyright=song['copyright'],
                                ccli_number=song['ccli_number'])
        song_xml = SongXML()
        verse_order = []
        for verse in song['verses']:
            verse_type, verse_number = verse['label'].split(' ')[:2]
            verse_type = VerseType.from_loose_input(verse_type)
            verse_number = int(verse_number)
            song_xml.add_verse_to_lyrics(VerseType.tags[verse_type],
                                         verse_number, verse['lyrics'])
            verse_order.append('%s%s' %
                               (VerseType.tags[verse_type], verse_number))
        db_song.verse_order = ' '.join(verse_order)
        db_song.lyrics = song_xml.extract_xml()
        clean_song(self.db_manager, db_song)
        self.db_manager.save_object(db_song)
        db_song.authors_songs = []
        for author_name in song['authors']:
            author = self.db_manager.get_object_filtered(
                Author, Author.display_name == author_name)
            if not author:
                author = Author.populate(first_name=author_name.rsplit(' ',
                                                                       1)[0],
                                         last_name=author_name.rsplit(' ',
                                                                      1)[1],
                                         display_name=author_name)
            db_song.add_author(author)
        self.db_manager.save_object(db_song)
        return db_song
Example #33
0
    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)
Example #34
0
 def do_import(self):
     """
     Receive a CSV file to import.
     """
     # Get encoding
     encoding = get_file_encoding(self.import_source)['encoding']
     with self.import_source.open('r', encoding=encoding) as songs_file:
         songs_reader = csv.DictReader(songs_file, escapechar='\\')
         try:
             records = list(songs_reader)
         except csv.Error as e:
             self.log_error(
                 translate('SongsPlugin.WorshipAssistantImport',
                           'Error reading CSV file.'),
                 translate('SongsPlugin.WorshipAssistantImport',
                           'Line {number:d}: {error}').format(
                               number=songs_reader.line_num, error=e))
             return
     num_records = len(records)
     log.info('{count} records found in CSV file'.format(count=num_records))
     self.import_wizard.progress_bar.setMaximum(num_records)
     # Create regex to strip html tags
     re_html_strip = re.compile(r'<[^>]+>')
     for index, record in enumerate(records, 1):
         if self.stop_import_flag:
             return
         # Ensure that all keys are uppercase
         record = dict(
             (field.upper(), value) for field, value in record.items())
         # The CSV file has a line in the middle of the file where the headers are repeated.
         #  We need to skip this line.
         if record['TITLE'] == "TITLE" and record[
                 'AUTHOR'] == 'AUTHOR' and record['LYRICS2'] == 'LYRICS2':
             continue
         self.set_defaults()
         verse_order_list = []
         try:
             self.title = record['TITLE']
             if record['AUTHOR'] != EMPTY_STR:
                 self.parse_author(record['AUTHOR'])
             if record['COPYRIGHT'] != EMPTY_STR:
                 self.add_copyright(record['COPYRIGHT'])
             if record['CCLINR'] != EMPTY_STR:
                 self.ccli_number = record['CCLINR']
             if record['ROADMAP'] != EMPTY_STR:
                 verse_order_list = [
                     x.strip() for x in record['ROADMAP'].split(',')
                 ]
             lyrics = record['LYRICS2']
         except UnicodeDecodeError as e:
             self.log_error(
                 translate('SongsPlugin.WorshipAssistantImport',
                           'Record {count:d}').format(count=index),
                 translate('SongsPlugin.WorshipAssistantImport',
                           'Decoding error: {error}').format(error=e))
             continue
         except TypeError as e:
             self.log_error(
                 translate('SongsPlugin.WorshipAssistantImport',
                           'File not valid WorshipAssistant CSV format.'),
                 'TypeError: {error}'.format(error=e))
             return
         verse = ''
         used_verses = []
         verse_id = VerseType.tags[VerseType.Verse] + '1'
         for line in lyrics.splitlines():
             if line.startswith('['):  # verse marker
                 # Add previous verse
                 if verse:
                     # remove trailing linebreak, part of the WA syntax
                     self.add_verse(verse[:-1], verse_id)
                     used_verses.append(verse_id)
                     verse = ''
                 # drop the square brackets
                 right_bracket = line.find(']')
                 content = line[1:right_bracket].lower()
                 match = re.match(r'(\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]
                 # Update verse order when the verse name has changed
                 verse_id = verse_tag + verse_num
                 # Make sure we've not choosen an id already used
                 while verse_id in verse_order_list and content in verse_order_list:
                     verse_num = str(int(verse_num) + 1)
                     verse_id = verse_tag + verse_num
                 if content != verse_id:
                     for i in range(len(verse_order_list)):
                         if verse_order_list[i].lower() == content.lower():
                             verse_order_list[i] = verse_id
             else:
                 # add line text to verse. Strip out html
                 verse += re_html_strip.sub('', line) + '\n'
         if verse:
             # remove trailing linebreak, part of the WA syntax
             if verse.endswith('\n\n'):
                 verse = verse[:-1]
             self.add_verse(verse, verse_id)
             used_verses.append(verse_id)
         if verse_order_list:
             # Use the verse order in the import, but remove entries that doesn't have a text
             cleaned_verse_order_list = []
             for verse in verse_order_list:
                 if verse in used_verses:
                     cleaned_verse_order_list.append(verse)
             self.verse_order_list = cleaned_verse_order_list
         if not self.finish():
             self.log_error(
                 translate('SongsPlugin.WorshipAssistantImport',
                           'Record {count:d}').format(count=index) +
                 (': "' + self.title + '"' if self.title else ''))
Example #35
0
    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)
Example #36
0
 def doImportFile(self, file):
     """
     Process the OpenSong file - pass in a file-like object, not a file path.
     """
     self.setDefaults()
     try:
         tree = objectify.parse(file)
     except (Error, LxmlError):
         self.logError(file.name, SongStrings.XMLSyntaxError)
         log.exception(u'Error parsing XML')
         return
     root = tree.getroot()
     if root.tag != u'song':
         self.logError(file.name, unicode(
             translate('SongsPlugin.OpenSongImport', ('Invalid OpenSong song file. Missing song tag.'))))
         return
     fields = dir(root)
     decode = {
         u'copyright': self.addCopyright,
         u'ccli': u'ccli_number',
         u'author': self.parseAuthor,
         u'title': u'title',
         u'aka': u'alternate_title',
         u'hymn_number': u'song_number'
     }
     for attr, fn_or_string in decode.items():
         if attr in fields:
             ustring = unicode(root.__getattr__(attr))
             if isinstance(fn_or_string, basestring):
                 setattr(self, fn_or_string, ustring)
             else:
                 fn_or_string(ustring)
     if u'theme' in fields and unicode(root.theme) not in self.topics:
         self.topics.append(unicode(root.theme))
     if u'alttheme' in fields and unicode(root.alttheme) not in self.topics:
         self.topics.append(unicode(root.alttheme))
     # data storage while importing
     verses = {}
     # keep track of verses appearance order
     our_verse_order = []
     # default verse
     verse_tag = VerseType.Tags[VerseType.Verse]
     verse_num = u'1'
     # for the case where song has several sections with same marker
     inst = 1
     if u'lyrics' in fields:
         lyrics = unicode(root.lyrics)
     else:
         lyrics = u''
     for this_line in lyrics.split(u'\n'):
         # remove comments
         semicolon = this_line.find(u';')
         if semicolon >= 0:
             this_line = this_line[:semicolon]
         this_line = this_line.strip()
         if not this_line:
             continue
         # skip guitar chords and page and column breaks
         if this_line.startswith(u'.') or this_line.startswith(u'---') or this_line.startswith(u'-!!'):
             continue
         # verse/chorus/etc. marker
         if this_line.startswith(u'['):
             # drop the square brackets
             right_bracket = this_line.find(u']')
             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(u'(\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 = u'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()
             our_verse_order.append([verse_tag, verse_num, inst])
         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.tidyText(this_line)
         this_line = this_line.replace(u'_', u'')
         this_line = this_line.replace(u'|', u'\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 = u'\n'.join(verses[verse_tag][verse_num][inst])
         length = 0
         while(length < len(verse_num) and verse_num[length].isnumeric()):
             length += 1
         verse_def = u'%s%s' % (verse_tag, verse_num[:length])
         verse_joints[verse_def] = u'%s\n[---]\n%s' % (verse_joints[verse_def], lines) \
             if verse_def in verse_joints else lines
     for verse_def, lines in verse_joints.iteritems():
         self.addVerse(lines, verse_def)
     if not self.verses:
         self.addVerse('')
     # figure out the presentation order, if present
     if u'presentation' in fields and root.presentation:
         order = unicode(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(u'(\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 = u'1'
             verse_def = u'%s%s' % (verse_tag, verse_num)
             if verse_num in verses.get(verse_tag, {}):
                 self.verseOrderList.append(verse_def)
             else:
                 log.info(u'Got order %s but not in verse tags, dropping'
                     u'this item from presentation order', verse_def)
     if not self.finish():
         self.logError(file.name)
Example #37
0
    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
Example #38
0
    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)
Example #39
0
    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())
Example #40
0
    def parse(self, data, cell=False):
        """
        Process the records

        :param data: The data to be processed
        :param cell: ?
        :return:
        """
        if not cell and (len(data) == 0 or data[0:1] != b'['
                         or data.strip()[-1:] != b']'):
            self.log_error('File is malformed')
            return False
        i = 1
        verse_type = VerseType.tags[VerseType.Verse]
        while i < len(data):
            # Data is held as #name: value pairs inside groups marked as [].
            # Now we are looking for the name.
            if data[i:i + 1] == b'#':
                name_end = data.find(b':', i + 1)
                name = data[i + 1:name_end].decode(self.encoding).upper()
                i = name_end + 1
                while data[i:i + 1] == b' ':
                    i += 1
                if data[i:i + 1] == b'"':
                    end = data.find(b'"', i + 1)
                    value = data[i + 1:end]
                elif data[i:i + 1] == b'[':
                    j = i
                    inside_quotes = False
                    while j < len(data):
                        char = data[j:j + 1]
                        if char == b'"':
                            inside_quotes = not inside_quotes
                        elif not inside_quotes and char == b']':
                            end = j + 1
                            break
                        j += 1
                    value = data[i:end]
                else:
                    end = data.find(b',', i + 1)
                    if data.find(b'(', i, end) != -1:
                        end = data.find(b')', i) + 1
                    value = data[i:end]
                # If we are in the main group.
                if not cell:
                    if name == 'TITLE':
                        self.title = self.decode(self.unescape(value))
                    elif name == 'AUTHOR':
                        author = self.decode(self.unescape(value))
                        if len(author):
                            self.add_author(author)
                    elif name == 'COPYRIGHT':
                        self.add_copyright(self.decode(self.unescape(value)))
                    elif name[0:4] == 'CELL':
                        self.parse(value, cell=name[4:])
                # We are in a verse group.
                else:
                    if name == 'MARKER_NAME':
                        value = self.decode(value).strip()
                        if len(value):
                            verse_type = VerseType.tags[
                                VerseType.from_loose_input(value[0])]
                            if len(value) >= 2 and value[-1] in [
                                    '0', '1', '2', '3', '4', '5', '6', '7',
                                    '8', '9'
                            ]:
                                verse_type = "{verse}{value}".format(
                                    verse=verse_type, value=value[-1])
                    elif name == 'HOTKEY':
                        value = self.decode(value).strip()
                        # HOTKEY always appears after MARKER_NAME, so it
                        # effectively overrides MARKER_NAME, if present.
                        if len(value) and value in list(
                                HOTKEY_TO_VERSE_TYPE.keys()):
                            verse_type = HOTKEY_TO_VERSE_TYPE[value]
                    if name == 'RTF':
                        value = self.unescape(value)
                        value = self.decode(value)
                        result = strip_rtf(value, self.encoding)
                        if result is None:
                            return False
                        verse, self.encoding = result
                        lines = verse.strip().split('\n')
                        # If any line inside any verse contains CCLI or
                        # only Public Domain, we treat this as special data:
                        # we remove that line and add data to specific field.
                        processed_lines = []
                        for i in range(len(lines)):
                            line = lines[i].strip()
                            if line[:3].lower() == 'ccl':
                                m = re.search(r'[0-9]+', line)
                                if m:
                                    self.ccli_number = int(m.group(0))
                                    continue
                            elif line.lower() == 'public domain':
                                self.add_copyright('Public Domain')
                                continue
                            processed_lines.append(line)
                        self.add_verse('\n'.join(processed_lines).strip(),
                                       verse_type)
                if end == -1:
                    break
                i = end + 1
            i += 1
        return True
Example #41
0
    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
Example #42
0
 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
Example #43
0
 def do_import(self):
     """
     Receive a CSV file to import.
     """
     # Get encoding
     detect_file = open(self.import_source, 'rb')
     detect_content = detect_file.read()
     details = chardet.detect(detect_content)
     detect_file.close()
     songs_file = open(self.import_source, 'r', encoding=details['encoding'])
     songs_reader = csv.DictReader(songs_file, escapechar='\\')
     try:
         records = list(songs_reader)
     except csv.Error as e:
         self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Error reading CSV file.'),
                        translate('SongsPlugin.WorshipAssistantImport', 'Line %d: %s') %
                        (songs_reader.line_num, e))
         return
     num_records = len(records)
     log.info('%s records found in CSV file' % num_records)
     self.import_wizard.progress_bar.setMaximum(num_records)
     # Create regex to strip html tags
     re_html_strip = re.compile(r'<[^>]+>')
     for index, record in enumerate(records, 1):
         if self.stop_import_flag:
             return
         # Ensure that all keys are uppercase
         record = dict((field.upper(), value) for field, value in record.items())
         # The CSV file has a line in the middle of the file where the headers are repeated.
         #  We need to skip this line.
         if record['TITLE'] == "TITLE" and record['AUTHOR'] == 'AUTHOR' and record['LYRICS2'] == 'LYRICS2':
             continue
         self.set_defaults()
         verse_order_list = []
         try:
             self.title = record['TITLE']
             if record['AUTHOR'] != EMPTY_STR:
                 self.parse_author(record['AUTHOR'])
             if record['COPYRIGHT'] != EMPTY_STR:
                 self.add_copyright(record['COPYRIGHT'])
             if record['CCLINR'] != EMPTY_STR:
                 self.ccli_number = record['CCLINR']
             if record['ROADMAP'] != EMPTY_STR:
                 verse_order_list = [x.strip() for x in record['ROADMAP'].split(',')]
             lyrics = record['LYRICS2']
         except UnicodeDecodeError as e:
             self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Record %d' % index),
                            translate('SongsPlugin.WorshipAssistantImport', 'Decoding error: %s') % e)
             continue
         except TypeError as e:
             self.log_error(translate('SongsPlugin.WorshipAssistantImport',
                                      'File not valid WorshipAssistant CSV format.'), 'TypeError: %s' % e)
             return
         verse = ''
         used_verses = []
         verse_id = VerseType.tags[VerseType.Verse] + '1'
         for line in lyrics.splitlines():
             if line.startswith('['):  # verse marker
                 # Add previous verse
                 if verse:
                     # remove trailing linebreak, part of the WA syntax
                     self.add_verse(verse[:-1], verse_id)
                     used_verses.append(verse_id)
                     verse = ''
                 # drop the square brackets
                 right_bracket = line.find(']')
                 content = line[1:right_bracket].lower()
                 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]
                 # Update verse order when the verse name has changed
                 verse_id = verse_tag + verse_num
                 # Make sure we've not choosen an id already used
                 while verse_id in verse_order_list and content in verse_order_list:
                     verse_num = str(int(verse_num) + 1)
                     verse_id = verse_tag + verse_num
                 if content != verse_id:
                     for i in range(len(verse_order_list)):
                         if verse_order_list[i].lower() == content.lower():
                             verse_order_list[i] = verse_id
             else:
                 # add line text to verse. Strip out html
                 verse += re_html_strip.sub('', line) + '\n'
         if verse:
             # remove trailing linebreak, part of the WA syntax
             if verse.endswith('\n\n'):
                 verse = verse[:-1]
             self.add_verse(verse, verse_id)
             used_verses.append(verse_id)
         if verse_order_list:
             # Use the verse order in the import, but remove entries that doesn't have a text
             cleaned_verse_order_list = []
             for verse in verse_order_list:
                 if verse in used_verses:
                     cleaned_verse_order_list.append(verse)
             self.verse_order_list = cleaned_verse_order_list
         if not self.finish():
             self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Record %d') % index +
                            (': "' + self.title + '"' if self.title else ''))
         songs_file.close()
Example #44
0
    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)
Example #45
0
 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)
Example #46
0
    def parse(self, data, cell=False):
        """
        Process the records

        :param data: The data to be processed
        :param cell: ?
        :return:
        """
        if len(data) == 0 or data[0:1] != '[' or data[-1] != ']':
            self.log_error('File is malformed')
            return False
        i = 1
        verse_type = VerseType.tags[VerseType.Verse]
        while i < len(data):
            # Data is held as #name: value pairs inside groups marked as [].
            # Now we are looking for the name.
            if data[i:i + 1] == '#':
                name_end = data.find(':', i + 1)
                name = data[i + 1:name_end].upper()
                i = name_end + 1
                while data[i:i + 1] == ' ':
                    i += 1
                if data[i:i + 1] == '"':
                    end = data.find('"', i + 1)
                    value = data[i + 1:end]
                elif data[i:i + 1] == '[':
                    j = i
                    inside_quotes = False
                    while j < len(data):
                        char = data[j:j + 1]
                        if char == '"':
                            inside_quotes = not inside_quotes
                        elif not inside_quotes and char == ']':
                            end = j + 1
                            break
                        j += 1
                    value = data[i:end]
                else:
                    end = data.find(',', i + 1)
                    if data.find('(', i, end) != -1:
                        end = data.find(')', i) + 1
                    value = data[i:end]
                # If we are in the main group.
                if not cell:
                    if name == 'TITLE':
                        self.title = self.decode(self.unescape(value))
                    elif name == 'AUTHOR':
                        author = self.decode(self.unescape(value))
                        if len(author):
                            self.add_author(author)
                    elif name == 'COPYRIGHT':
                        self.copyright = self.decode(self.unescape(value))
                    elif name[0:4] == 'CELL':
                        self.parse(value, cell=name[4:])
                # We are in a verse group.
                else:
                    if name == 'MARKER_NAME':
                        value = value.strip()
                        if len(value):
                            verse_type = VerseType.tags[VerseType.from_loose_input(value[0])]
                            if len(value) >= 2 and value[-1] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
                                verse_type = "%s%s" % (verse_type, value[-1])
                    elif name == 'HOTKEY':
                        # HOTKEY always appears after MARKER_NAME, so it
                        # effectively overrides MARKER_NAME, if present.
                        if len(value) and value in list(HOTKEY_TO_VERSE_TYPE.keys()):
                            verse_type = HOTKEY_TO_VERSE_TYPE[value]
                    if name == 'RTF':
                        value = self.unescape(value)
                        result = strip_rtf(value, self.encoding)
                        if result is None:
                            return
                        verse, self.encoding = result
                        lines = verse.strip().split('\n')
                        # If any line inside any verse contains CCLI or
                        # only Public Domain, we treat this as special data:
                        # we remove that line and add data to specific field.
                        processed_lines = []
                        for i in range(len(lines)):
                            line = lines[i].strip()
                            if line[:3].lower() == 'ccl':
                                m = re.search(r'[0-9]+', line)
                                if m:
                                    self.ccli_number = int(m.group(0))
                                    continue
                            elif line.lower() == 'public domain':
                                self.copyright = 'Public Domain'
                                continue
                            processed_lines.append(line)
                        self.add_verse('\n'.join(processed_lines).strip(), verse_type)
                if end == -1:
                    break
                i = end + 1
            i += 1
        return True
Example #47
0
    def save_song(self, preview=False):
        """
        Get all the data from the widgets on the form, and then save it to the
        database. The form has been validated and all reference items
        (Authors, Books and Topics) have been saved before this function is
        called.

        ``preview``
            Should be ``True`` if the song is also previewed (boolean).
        """
        # The Song() assignment. No database calls should be made while a
        # Song() is in a partially complete state.
        if not self.song:
            self.song = Song()
        self.song.title = self.title_edit.text()
        self.song.alternate_title = self.alternative_edit.text()
        self.song.copyright = self.copyright_edit.text()
        # Values will be set when cleaning the song.
        self.song.search_title = ''
        self.song.search_lyrics = ''
        self.song.verse_order = ''
        self.song.comments = self.comments_edit.toPlainText()
        ordertext = self.verse_order_edit.text()
        order = []
        for item in ordertext.split():
            verse_tag = VerseType.tags[VerseType.from_translated_tag(item[0])]
            verse_num = item[1:].lower()
            order.append('%s%s' % (verse_tag, verse_num))
        self.song.verse_order = ' '.join(order)
        self.song.ccli_number = self.ccli_number_edit.text()
        self.song.song_number = self.song_book_number_edit.text()
        book_name = self.song_book_combo_box.currentText()
        if book_name:
            self.song.book = self.manager.get_object_filtered(Book,
                Book.name == book_name)
        else:
            self.song.book = None
        theme_name = self.theme_combo_box.currentText()
        if theme_name:
            self.song.theme_name = theme_name
        else:
            self.song.theme_name = None
        self._process_lyrics()
        self.song.authors = []
        for row in range(self.authors_list_view.count()):
            item = self.authors_list_view.item(row)
            authorId = (item.data(QtCore.Qt.UserRole))
            author = self.manager.get_object(Author, authorId)
            if author is not None:
                self.song.authors.append(author)
        self.song.topics = []
        for row in range(self.topics_list_view.count()):
            item = self.topics_list_view.item(row)
            topicId = (item.data(QtCore.Qt.UserRole))
            topic = self.manager.get_object(Topic, topicId)
            if topic is not None:
                self.song.topics.append(topic)
        # Save the song here because we need a valid id for the audio files.
        clean_song(self.manager, self.song)
        self.manager.save_object(self.song)
        audio_files = [a.file_name for a in self.song.media_files]
        log.debug(audio_files)
        save_path = os.path.join(AppLocation.get_section_data_path(self.media_item.plugin.name), 'audio',
            str(self.song.id))
        check_directory_exists(save_path)
        self.song.media_files = []
        files = []
        for row in range(self.audio_list_widget.count()):
            item = self.audio_list_widget.item(row)
            filename = item.data(QtCore.Qt.UserRole)
            if not filename.startswith(save_path):
                oldfile, filename = filename, os.path.join(save_path, os.path.split(filename)[1])
                shutil.copyfile(oldfile, filename)
            files.append(filename)
            media_file = MediaFile()
            media_file.file_name = filename
            media_file.type = 'audio'
            media_file.weight = row
            self.song.media_files.append(media_file)
        for audio in audio_files:
            if audio not in files:
                try:
                    os.remove(audio)
                except:
                    log.exception('Could not remove file: %s', audio)
        if not files:
            try:
                os.rmdir(save_path)
            except OSError:
                log.exception('Could not remove directory: %s', save_path)
        clean_song(self.manager, self.song)
        self.manager.save_object(self.song)
        self.media_item.auto_select_id = self.song.id
Example #48
0
 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)
Example #49
0
    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