forked from openlp/openlp
HEAD
This commit is contained in:
commit
bc0ade07e0
@ -133,36 +133,21 @@ class ApiTab(SettingsTab):
|
||||
self.master_version_value.setObjectName('master_version_value')
|
||||
self.update_site_layout.addRow(self.master_version_label, self.master_version_value)
|
||||
self.left_layout.addWidget(self.update_site_group_box)
|
||||
self.android_app_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.android_app_group_box.setObjectName('android_app_group_box')
|
||||
self.right_layout.addWidget(self.android_app_group_box)
|
||||
self.android_qr_layout = QtWidgets.QVBoxLayout(self.android_app_group_box)
|
||||
self.android_qr_layout.setObjectName('android_qr_layout')
|
||||
self.android_qr_code_label = QtWidgets.QLabel(self.android_app_group_box)
|
||||
self.android_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/android_app_qr.png'))
|
||||
self.android_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.android_qr_code_label.setObjectName('android_qr_code_label')
|
||||
self.android_qr_layout.addWidget(self.android_qr_code_label)
|
||||
self.android_qr_description_label = QtWidgets.QLabel(self.android_app_group_box)
|
||||
self.android_qr_description_label.setObjectName('android_qr_description_label')
|
||||
self.android_qr_description_label.setOpenExternalLinks(True)
|
||||
self.android_qr_description_label.setWordWrap(True)
|
||||
self.android_qr_layout.addWidget(self.android_qr_description_label)
|
||||
self.ios_app_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.ios_app_group_box.setObjectName('ios_app_group_box')
|
||||
self.right_layout.addWidget(self.ios_app_group_box)
|
||||
self.ios_qr_layout = QtWidgets.QVBoxLayout(self.ios_app_group_box)
|
||||
self.ios_qr_layout.setObjectName('ios_qr_layout')
|
||||
self.ios_qr_code_label = QtWidgets.QLabel(self.ios_app_group_box)
|
||||
self.ios_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/ios_app_qr.png'))
|
||||
self.ios_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.ios_qr_code_label.setObjectName('ios_qr_code_label')
|
||||
self.ios_qr_layout.addWidget(self.ios_qr_code_label)
|
||||
self.ios_qr_description_label = QtWidgets.QLabel(self.ios_app_group_box)
|
||||
self.ios_qr_description_label.setObjectName('ios_qr_description_label')
|
||||
self.ios_qr_description_label.setOpenExternalLinks(True)
|
||||
self.ios_qr_description_label.setWordWrap(True)
|
||||
self.ios_qr_layout.addWidget(self.ios_qr_description_label)
|
||||
self.app_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.app_group_box.setObjectName('app_group_box')
|
||||
self.right_layout.addWidget(self.app_group_box)
|
||||
self.app_qr_layout = QtWidgets.QVBoxLayout(self.app_group_box)
|
||||
self.app_qr_layout.setObjectName('app_qr_layout')
|
||||
self.app_qr_code_label = QtWidgets.QLabel(self.app_group_box)
|
||||
self.app_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/app_qr.svg'))
|
||||
self.app_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.app_qr_code_label.setObjectName('app_qr_code_label')
|
||||
self.app_qr_layout.addWidget(self.app_qr_code_label)
|
||||
self.app_qr_description_label = QtWidgets.QLabel(self.app_group_box)
|
||||
self.app_qr_description_label.setObjectName('app_qr_description_label')
|
||||
self.app_qr_description_label.setOpenExternalLinks(True)
|
||||
self.app_qr_description_label.setWordWrap(True)
|
||||
self.app_qr_layout.addWidget(self.app_qr_description_label)
|
||||
self.left_layout.addStretch()
|
||||
self.right_layout.addStretch()
|
||||
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
|
||||
@ -199,16 +184,11 @@ class ApiTab(SettingsTab):
|
||||
self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
|
||||
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
|
||||
'Show thumbnails of non-text slides in remote and stage view.'))
|
||||
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
|
||||
self.android_qr_description_label.setText(
|
||||
self.app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Remote App'))
|
||||
self.app_qr_description_label.setText(
|
||||
translate('RemotePlugin.RemoteTab',
|
||||
'Scan the QR code or click <a href="{qr}">download</a> to install the Android app from Google '
|
||||
'Play.').format(qr='https://play.google.com/store/apps/details?id=org.openlp.android2'))
|
||||
self.ios_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'iOS App'))
|
||||
self.ios_qr_description_label.setText(
|
||||
translate('RemotePlugin.RemoteTab',
|
||||
'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App '
|
||||
'Store.').format(qr='https://itunes.apple.com/app/id1096218725'))
|
||||
'Scan the QR code or click <a href="{qr}">download</a> to download an app for your mobile device'
|
||||
).format(qr='https://openlp.org/#mobile-app-downloads'))
|
||||
self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication'))
|
||||
self.aa = UiStrings()
|
||||
self.update_site_group_box.setTitle(UiStrings().WebDownloadText)
|
||||
|
@ -245,6 +245,9 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
|
||||
elif item.is_capable(ItemCapabilities.CanSoftBreak):
|
||||
pages = []
|
||||
if '[---]' in text:
|
||||
# Remove Overflow split if at start of the text
|
||||
if text.startswith('[---]'):
|
||||
text = text[5:]
|
||||
# Remove two or more option slide breaks next to each other (causing infinite loop).
|
||||
while '\n[---]\n[---]\n' in text:
|
||||
text = text.replace('\n[---]\n[---]\n', '\n[---]\n')
|
||||
|
@ -429,10 +429,10 @@ class ServiceItem(RegistryProperties):
|
||||
self.background_audio = []
|
||||
for filename in header['background_audio']:
|
||||
# Give them real file paths.
|
||||
filepath = filename
|
||||
filepath = str(filename)
|
||||
if path:
|
||||
# Windows can handle both forward and backward slashes, so we use ntpath to get the basename
|
||||
filepath = os.path.join(path, ntpath.basename(filename))
|
||||
filepath = os.path.join(path, ntpath.basename(str(filename)))
|
||||
self.background_audio.append(filepath)
|
||||
self.theme_overwritten = header.get('theme_overwritten', False)
|
||||
if self.service_item_type == ServiceItemType.Text:
|
||||
|
@ -210,21 +210,21 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
||||
Switches to the given row.
|
||||
"""
|
||||
# Retrieve setting
|
||||
autoscrolling = Settings().value('advanced/autoscrolling')
|
||||
auto_scrolling = Settings().value('advanced/autoscrolling')
|
||||
# Check if auto-scroll disabled (None) and validate value as dict containing 'dist' and 'pos'
|
||||
# 'dist' represents the slide to scroll to relative to the new slide (-1 = previous, 0 = current, 1 = next)
|
||||
# 'pos' represents the vert position of of the slide (0 = in view, 1 = top, 2 = middle, 3 = bottom)
|
||||
if not (isinstance(autoscrolling, dict) and 'dist' in autoscrolling and 'pos' in autoscrolling and
|
||||
isinstance(autoscrolling['dist'], int) and isinstance(autoscrolling['pos'], int)):
|
||||
if not (isinstance(auto_scrolling, dict) and 'dist' in auto_scrolling and 'pos' in auto_scrolling and
|
||||
isinstance(auto_scrolling['dist'], int) and isinstance(auto_scrolling['pos'], int)):
|
||||
return
|
||||
# prevent scrolling past list bounds
|
||||
scroll_to_slide = slide + autoscrolling['dist']
|
||||
scroll_to_slide = slide + auto_scrolling['dist']
|
||||
if scroll_to_slide < 0:
|
||||
scroll_to_slide = 0
|
||||
if scroll_to_slide >= self.slide_count():
|
||||
scroll_to_slide = self.slide_count() - 1
|
||||
# Scroll to item if possible.
|
||||
self.scrollToItem(self.item(scroll_to_slide, 0), autoscrolling['pos'])
|
||||
self.scrollToItem(self.item(scroll_to_slide, 0), auto_scrolling['pos'])
|
||||
self.selectRow(slide)
|
||||
|
||||
def current_slide_number(self):
|
||||
|
@ -686,7 +686,7 @@ class AudioPlayer(OpenLPMixin, QtCore.QObject):
|
||||
if not isinstance(file_names, list):
|
||||
file_names = [file_names]
|
||||
for file_name in file_names:
|
||||
self.playlist.addMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(file_name)))
|
||||
self.playlist.addMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(str(file_name))))
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
|
@ -564,7 +564,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||
service_item = item['service_item'].get_service_repr(self._save_lite)
|
||||
if service_item['header']['background_audio']:
|
||||
for i, file_name in enumerate(service_item['header']['background_audio']):
|
||||
new_file = os.path.join('audio', item['service_item'].unique_identifier, file_name)
|
||||
new_file = os.path.join('audio', item['service_item'].unique_identifier, str(file_name))
|
||||
audio_files.append((file_name, new_file))
|
||||
service_item['header']['background_audio'][i] = new_file
|
||||
# Add the service item to the service.
|
||||
@ -589,6 +589,8 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||
for write_from in write_list:
|
||||
zip_file.write(write_from, write_from)
|
||||
for audio_from, audio_to in audio_files:
|
||||
audio_from = str(audio_from)
|
||||
audio_to = str(audio_to)
|
||||
if audio_from.startswith('audio'):
|
||||
# When items are saved, they get new unique_identifier. Let's copy the file to the new location.
|
||||
# Unused files can be ignored, OpenLP automatically cleans up the service manager dir on exit.
|
||||
|
@ -871,7 +871,7 @@ class SlideController(DisplayController, RegistryProperties):
|
||||
self.track_menu.clear()
|
||||
for counter in range(len(self.service_item.background_audio)):
|
||||
action = self.track_menu.addAction(
|
||||
os.path.basename(self.service_item.background_audio[counter]))
|
||||
os.path.basename(str(self.service_item.background_audio[counter])))
|
||||
action.setData(counter)
|
||||
action.triggered.connect(self.on_track_triggered)
|
||||
self.display.audio_player.repeat = \
|
||||
|
@ -25,25 +25,23 @@ used to edit songs.
|
||||
"""
|
||||
import logging
|
||||
import re
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.i18n import UiStrings, translate, get_natural_key
|
||||
from openlp.core.common.path import Path, path_to_str, create_paths
|
||||
from openlp.core.common.path import create_paths, copyfile
|
||||
from openlp.core.common.registry import Registry, RegistryProperties
|
||||
from openlp.core.lib import PluginStatus, MediaType, create_separated_list
|
||||
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
from openlp.plugins.songs.lib import VerseType, clean_song
|
||||
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry
|
||||
from openlp.plugins.songs.lib.ui import SongStrings
|
||||
from openlp.plugins.songs.lib.openlyricsxml import SongXML
|
||||
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
|
||||
from openlp.plugins.songs.forms.editverseform import EditVerseForm
|
||||
from openlp.plugins.songs.forms.mediafilesform import MediaFilesForm
|
||||
from openlp.plugins.songs.lib import VerseType, clean_song
|
||||
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry
|
||||
from openlp.plugins.songs.lib.openlyricsxml import SongXML
|
||||
from openlp.plugins.songs.lib.ui import SongStrings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -545,9 +543,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
||||
songbook_entry.entry)
|
||||
self.audio_list_widget.clear()
|
||||
for media in self.song.media_files:
|
||||
media_file = QtWidgets.QListWidgetItem(os.path.split(media.file_name)[1])
|
||||
media_file.setData(QtCore.Qt.UserRole, media.file_name)
|
||||
self.audio_list_widget.addItem(media_file)
|
||||
item = QtWidgets.QListWidgetItem(media.file_path.name)
|
||||
item.setData(QtCore.Qt.UserRole, media.file_path)
|
||||
self.audio_list_widget.addItem(item)
|
||||
self.title_edit.setFocus()
|
||||
# Hide or show the preview button.
|
||||
self.preview_button.setVisible(preview)
|
||||
@ -927,12 +925,11 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
||||
Loads file(s) from the filesystem.
|
||||
"""
|
||||
filters = '{text} (*)'.format(text=UiStrings().AllFiles)
|
||||
file_paths, selected_filter = FileDialog.getOpenFileNames(
|
||||
self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), Path(), filters)
|
||||
file_paths, filter_used = FileDialog.getOpenFileNames(
|
||||
parent=self, caption=translate('SongsPlugin.EditSongForm', 'Open File(s)'), filter=filters)
|
||||
for file_path in file_paths:
|
||||
filename = path_to_str(file_path)
|
||||
item = QtWidgets.QListWidgetItem(os.path.split(str(filename))[1])
|
||||
item.setData(QtCore.Qt.UserRole, filename)
|
||||
item = QtWidgets.QListWidgetItem(file_path.name)
|
||||
item.setData(QtCore.Qt.UserRole, file_path)
|
||||
self.audio_list_widget.addItem(item)
|
||||
|
||||
def on_audio_add_from_media_button_clicked(self):
|
||||
@ -940,9 +937,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
||||
Loads file(s) from the media plugin.
|
||||
"""
|
||||
if self.media_form.exec():
|
||||
for filename in self.media_form.get_selected_files():
|
||||
item = QtWidgets.QListWidgetItem(os.path.split(str(filename))[1])
|
||||
item.setData(QtCore.Qt.UserRole, filename)
|
||||
for file_path in self.media_form.get_selected_files():
|
||||
item = QtWidgets.QListWidgetItem(file_path.name)
|
||||
item.setData(QtCore.Qt.UserRole, file_path)
|
||||
self.audio_list_widget.addItem(item)
|
||||
|
||||
def on_audio_remove_button_clicked(self):
|
||||
@ -1066,34 +1063,33 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
||||
# 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(str(AppLocation.get_section_data_path(self.media_item.plugin.name)), 'audio',
|
||||
str(self.song.id))
|
||||
create_paths(Path(save_path))
|
||||
audio_paths = [a.file_path for a in self.song.media_files]
|
||||
log.debug(audio_paths)
|
||||
save_path = AppLocation.get_section_data_path(self.media_item.plugin.name) / 'audio' / str(self.song.id)
|
||||
create_paths(save_path)
|
||||
self.song.media_files = []
|
||||
files = []
|
||||
file_paths = []
|
||||
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):
|
||||
old_file, filename = filename, os.path.join(save_path, os.path.split(filename)[1])
|
||||
shutil.copyfile(old_file, filename)
|
||||
files.append(filename)
|
||||
file_path = item.data(QtCore.Qt.UserRole)
|
||||
if save_path not in file_path.parents:
|
||||
old_file_path, file_path = file_path, save_path / file_path.name
|
||||
copyfile(old_file_path, file_path)
|
||||
file_paths.append(file_path)
|
||||
media_file = MediaFile()
|
||||
media_file.file_name = filename
|
||||
media_file.file_path = file_path
|
||||
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:
|
||||
for audio_path in audio_paths:
|
||||
if audio_path not in file_paths:
|
||||
try:
|
||||
os.remove(audio)
|
||||
audio_path.unlink()
|
||||
except:
|
||||
log.exception('Could not remove file: {audio}'.format(audio=audio))
|
||||
if not files:
|
||||
log.exception('Could not remove file: {audio}'.format(audio=audio_path))
|
||||
if not file_paths:
|
||||
try:
|
||||
os.rmdir(save_path)
|
||||
save_path.rmdir()
|
||||
except OSError:
|
||||
log.exception('Could not remove directory: {path}'.format(path=save_path))
|
||||
clean_song(self.manager, self.song)
|
||||
|
@ -43,10 +43,14 @@ class Ui_EditVerseDialog(object):
|
||||
self.dialog_layout.addWidget(self.verse_text_edit)
|
||||
self.verse_type_layout = QtWidgets.QHBoxLayout()
|
||||
self.verse_type_layout.setObjectName('verse_type_layout')
|
||||
self.split_button = QtWidgets.QPushButton(edit_verse_dialog)
|
||||
self.split_button.setIcon(build_icon(':/general/general_add.png'))
|
||||
self.split_button.setObjectName('split_button')
|
||||
self.verse_type_layout.addWidget(self.split_button)
|
||||
self.forced_split_button = QtWidgets.QPushButton(edit_verse_dialog)
|
||||
self.forced_split_button.setIcon(build_icon(':/general/general_add.png'))
|
||||
self.forced_split_button.setObjectName('forced_split_button')
|
||||
self.verse_type_layout.addWidget(self.forced_split_button)
|
||||
self.overflow_split_button = QtWidgets.QPushButton(edit_verse_dialog)
|
||||
self.overflow_split_button.setIcon(build_icon(':/general/general_add.png'))
|
||||
self.overflow_split_button.setObjectName('overflow_split_button')
|
||||
self.verse_type_layout.addWidget(self.overflow_split_button)
|
||||
self.verse_type_label = QtWidgets.QLabel(edit_verse_dialog)
|
||||
self.verse_type_label.setObjectName('verse_type_label')
|
||||
self.verse_type_layout.addWidget(self.verse_type_label)
|
||||
@ -94,8 +98,11 @@ class Ui_EditVerseDialog(object):
|
||||
self.verse_type_combo_box.setItemText(VerseType.Intro, VerseType.translated_names[VerseType.Intro])
|
||||
self.verse_type_combo_box.setItemText(VerseType.Ending, VerseType.translated_names[VerseType.Ending])
|
||||
self.verse_type_combo_box.setItemText(VerseType.Other, VerseType.translated_names[VerseType.Other])
|
||||
self.split_button.setText(UiStrings().Split)
|
||||
self.split_button.setToolTip(UiStrings().SplitToolTip)
|
||||
self.overflow_split_button.setText(UiStrings().Split)
|
||||
self.overflow_split_button.setToolTip(UiStrings().SplitToolTip)
|
||||
self.forced_split_button.setText(translate('SongsPlugin.EditVerseForm', '&Forced Split'))
|
||||
self.forced_split_button.setToolTip(translate('SongsPlugin.EditVerseForm', 'Split the verse when displayed '
|
||||
'regardless of the screen size.'))
|
||||
self.insert_button.setText(translate('SongsPlugin.EditVerseForm', '&Insert'))
|
||||
self.insert_button.setToolTip(translate('SongsPlugin.EditVerseForm',
|
||||
'Split a slide into two by inserting a verse splitter.'))
|
||||
|
@ -49,12 +49,13 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
|
||||
self.setupUi(self)
|
||||
self.has_single_verse = False
|
||||
self.insert_button.clicked.connect(self.on_insert_button_clicked)
|
||||
self.split_button.clicked.connect(self.on_split_button_clicked)
|
||||
self.overflow_split_button.clicked.connect(self.on_overflow_split_button_clicked)
|
||||
self.verse_text_edit.cursorPositionChanged.connect(self.on_cursor_position_changed)
|
||||
self.verse_type_combo_box.currentIndexChanged.connect(self.on_verse_type_combo_box_changed)
|
||||
self.forced_split_button.clicked.connect(self.on_forced_split_button_clicked)
|
||||
if Settings().value('songs/enable chords'):
|
||||
self.transpose_down_button.clicked.connect(self.on_transepose_down_button_clicked)
|
||||
self.transpose_up_button.clicked.connect(self.on_transepose_up_button_clicked)
|
||||
self.transpose_down_button.clicked.connect(self.on_transpose_down_button_clicked)
|
||||
self.transpose_up_button.clicked.connect(self.on_transpose_up_button_clicked)
|
||||
|
||||
def insert_verse(self, verse_tag, verse_num=1):
|
||||
"""
|
||||
@ -69,13 +70,27 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
|
||||
self.verse_text_edit.insertPlainText('---[{tag}:{number}]---\n'.format(tag=verse_tag, number=verse_num))
|
||||
self.verse_text_edit.setFocus()
|
||||
|
||||
def on_split_button_clicked(self):
|
||||
def on_overflow_split_button_clicked(self):
|
||||
"""
|
||||
The split button has been pressed
|
||||
The optional split button has been pressed so we need add the split
|
||||
"""
|
||||
self._add_splitter_to_text('[---]')
|
||||
|
||||
def on_forced_split_button_clicked(self):
|
||||
"""
|
||||
The force split button has been pressed so we need add the split
|
||||
"""
|
||||
self._add_splitter_to_text('[--}{--]')
|
||||
|
||||
def _add_splitter_to_text(self, insert_string):
|
||||
"""
|
||||
Add a custom splitter to the song text
|
||||
|
||||
:param insert_string: The string to insert
|
||||
:return:
|
||||
"""
|
||||
text = self.verse_text_edit.toPlainText()
|
||||
position = self.verse_text_edit.textCursor().position()
|
||||
insert_string = '[---]'
|
||||
if position and text[position - 1] != '\n':
|
||||
insert_string = '\n' + insert_string
|
||||
if position == len(text) or text[position] != '\n':
|
||||
@ -102,7 +117,7 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
|
||||
"""
|
||||
self.update_suggested_verse_number()
|
||||
|
||||
def on_transepose_up_button_clicked(self):
|
||||
def on_transpose_up_button_clicked(self):
|
||||
"""
|
||||
The transpose up button clicked
|
||||
"""
|
||||
@ -119,7 +134,7 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
|
||||
self.verse_text_edit.setFocus()
|
||||
self.verse_text_edit.moveCursor(QtGui.QTextCursor.End)
|
||||
|
||||
def on_transepose_down_button_clicked(self):
|
||||
def on_transpose_down_button_clicked(self):
|
||||
"""
|
||||
The transpose down button clicked
|
||||
"""
|
||||
|
@ -41,12 +41,19 @@ class MediaFilesForm(QtWidgets.QDialog, Ui_MediaFilesDialog):
|
||||
QtCore.Qt.WindowCloseButtonHint)
|
||||
self.setupUi(self)
|
||||
|
||||
def populate_files(self, files):
|
||||
def populate_files(self, file_paths):
|
||||
"""
|
||||
:param list[openlp.core.common.path.Path] file_paths:
|
||||
:return:
|
||||
"""
|
||||
self.file_list_widget.clear()
|
||||
for file in files:
|
||||
item = QtWidgets.QListWidgetItem(os.path.split(file)[1])
|
||||
item.setData(QtCore.Qt.UserRole, file)
|
||||
for file_path in file_paths:
|
||||
item = QtWidgets.QListWidgetItem(file_path.name)
|
||||
item.setData(QtCore.Qt.UserRole, file_path)
|
||||
self.file_list_widget.addItem(item)
|
||||
|
||||
def get_selected_files(self):
|
||||
"""
|
||||
:rtype: list[openlp.core.common.path.Path]
|
||||
"""
|
||||
return [item.data(QtCore.Qt.UserRole) for item in self.file_list_widget.selectedItems()]
|
||||
|
@ -29,8 +29,10 @@ from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common.i18n import UiStrings, translate
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.lib import create_separated_list, build_icon
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.lib import create_separated_list
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui.lib import PathEdit, PathType
|
||||
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.plugins.songs.lib.db import Song
|
||||
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
|
||||
@ -77,7 +79,6 @@ class SongExportForm(OpenLPWizard):
|
||||
self.search_line_edit.textEdited.connect(self.on_search_line_edit_changed)
|
||||
self.uncheck_button.clicked.connect(self.on_uncheck_button_clicked)
|
||||
self.check_button.clicked.connect(self.on_check_button_clicked)
|
||||
self.directory_button.clicked.connect(self.on_directory_button_clicked)
|
||||
|
||||
def add_custom_pages(self):
|
||||
"""
|
||||
@ -121,21 +122,15 @@ class SongExportForm(OpenLPWizard):
|
||||
self.grid_layout.setObjectName('range_layout')
|
||||
self.selected_list_widget = QtWidgets.QListWidget(self.export_song_page)
|
||||
self.selected_list_widget.setObjectName('selected_list_widget')
|
||||
self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 1)
|
||||
# FIXME: self.horizontal_layout is already defined above?!?!? Replace with Path Eidt!
|
||||
self.horizontal_layout = QtWidgets.QHBoxLayout()
|
||||
self.horizontal_layout.setObjectName('horizontal_layout')
|
||||
self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 2)
|
||||
self.output_directory_path_edit = PathEdit(
|
||||
self.export_song_page, PathType.Directories,
|
||||
dialog_caption=translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'), show_revert=False)
|
||||
self.output_directory_path_edit.path = Settings().value('songs/last directory export')
|
||||
self.directory_label = QtWidgets.QLabel(self.export_song_page)
|
||||
self.directory_label.setObjectName('directory_label')
|
||||
self.horizontal_layout.addWidget(self.directory_label)
|
||||
self.directory_line_edit = QtWidgets.QLineEdit(self.export_song_page)
|
||||
self.directory_line_edit.setObjectName('directory_line_edit')
|
||||
self.horizontal_layout.addWidget(self.directory_line_edit)
|
||||
self.directory_button = QtWidgets.QToolButton(self.export_song_page)
|
||||
self.directory_button.setIcon(build_icon(':/exports/export_load.png'))
|
||||
self.directory_button.setObjectName('directory_button')
|
||||
self.horizontal_layout.addWidget(self.directory_button)
|
||||
self.grid_layout.addLayout(self.horizontal_layout, 0, 0, 1, 1)
|
||||
self.grid_layout.addWidget(self.directory_label, 0, 0)
|
||||
self.grid_layout.addWidget(self.output_directory_path_edit, 0, 1)
|
||||
self.export_song_layout.addLayout(self.grid_layout)
|
||||
self.addPage(self.export_song_page)
|
||||
|
||||
@ -189,11 +184,12 @@ class SongExportForm(OpenLPWizard):
|
||||
self.selected_list_widget.addItem(song)
|
||||
return True
|
||||
elif self.currentPage() == self.export_song_page:
|
||||
if not self.directory_line_edit.text():
|
||||
if not self.output_directory_path_edit.path:
|
||||
critical_error_message_box(
|
||||
translate('SongsPlugin.ExportWizardForm', 'No Save Location specified'),
|
||||
translate('SongsPlugin.ExportWizardForm', 'You need to specify a directory.'))
|
||||
return False
|
||||
Settings().setValue('songs/last directory export', self.output_directory_path_edit.path)
|
||||
return True
|
||||
elif self.currentPage() == self.progress_page:
|
||||
self.available_list_widget.clear()
|
||||
@ -212,8 +208,6 @@ class SongExportForm(OpenLPWizard):
|
||||
self.finish_button.setVisible(False)
|
||||
self.cancel_button.setVisible(True)
|
||||
self.available_list_widget.clear()
|
||||
self.selected_list_widget.clear()
|
||||
self.directory_line_edit.clear()
|
||||
self.search_line_edit.clear()
|
||||
# Load the list of songs.
|
||||
self.application.set_busy_cursor()
|
||||
@ -248,7 +242,7 @@ class SongExportForm(OpenLPWizard):
|
||||
song.data(QtCore.Qt.UserRole)
|
||||
for song in find_list_widget_items(self.selected_list_widget)
|
||||
]
|
||||
exporter = OpenLyricsExport(self, songs, self.directory_line_edit.text())
|
||||
exporter = OpenLyricsExport(self, songs, self.output_directory_path_edit.path)
|
||||
try:
|
||||
if exporter.do_export():
|
||||
self.progress_label.setText(
|
||||
@ -292,15 +286,6 @@ class SongExportForm(OpenLPWizard):
|
||||
if not item.isHidden():
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
|
||||
def on_directory_button_clicked(self):
|
||||
"""
|
||||
Called when the *directory_button* was clicked. Opens a dialog and writes
|
||||
the path to *directory_line_edit*.
|
||||
"""
|
||||
self.get_folder(
|
||||
translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'),
|
||||
self.directory_line_edit, 'last directory export')
|
||||
|
||||
|
||||
def find_list_widget_items(list_widget, text=''):
|
||||
"""
|
||||
|
@ -23,15 +23,14 @@
|
||||
The song import functions for OpenLP.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common.i18n import UiStrings, translate
|
||||
from openlp.core.common.path import path_to_str
|
||||
from openlp.core.common.registry import RegistryProperties
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui.lib import PathEdit, PathType
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
|
||||
@ -94,9 +93,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
||||
self.format_widgets[song_format]['addButton'].clicked.connect(self.on_add_button_clicked)
|
||||
self.format_widgets[song_format]['removeButton'].clicked.connect(self.on_remove_button_clicked)
|
||||
else:
|
||||
self.format_widgets[song_format]['browseButton'].clicked.connect(self.on_browse_button_clicked)
|
||||
self.format_widgets[song_format]['file_path_edit'].textChanged.\
|
||||
connect(self.on_filepath_edit_text_changed)
|
||||
self.format_widgets[song_format]['path_edit'].pathChanged.connect(self.on_path_edit_path_changed)
|
||||
|
||||
def add_custom_pages(self):
|
||||
"""
|
||||
@ -156,7 +153,6 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
||||
self.format_widgets[format_list]['removeButton'].setText(
|
||||
translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
|
||||
else:
|
||||
self.format_widgets[format_list]['browseButton'].setText(UiStrings().Browse)
|
||||
f_label = 'Filename:'
|
||||
if select_mode == SongFormatSelect.SingleFolder:
|
||||
f_label = 'Folder:'
|
||||
@ -173,16 +169,11 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
||||
self.error_save_to_button.setText(translate('SongsPlugin.ImportWizardForm', 'Save to File'))
|
||||
# Align all QFormLayouts towards each other.
|
||||
formats = [f for f in SongFormat.get_format_list() if 'filepathLabel' in self.format_widgets[f]]
|
||||
labels = [self.format_widgets[f]['filepathLabel'] for f in formats]
|
||||
labels = [self.format_widgets[f]['filepathLabel'] for f in formats] + [self.format_label]
|
||||
# Get max width of all labels
|
||||
max_label_width = max(self.format_label.minimumSizeHint().width(),
|
||||
max([label.minimumSizeHint().width() for label in labels]))
|
||||
self.format_spacer.changeSize(max_label_width, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||
spacers = [self.format_widgets[f]['filepathSpacer'] for f in formats]
|
||||
for index, spacer in enumerate(spacers):
|
||||
spacer.changeSize(
|
||||
max_label_width - labels[index].minimumSizeHint().width(), 0,
|
||||
QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||
max_label_width = max(labels, key=lambda label: label.minimumSizeHint().width()).minimumSizeHint().width()
|
||||
for label in labels:
|
||||
label.setFixedWidth(max_label_width)
|
||||
# Align descriptionLabels with rest of layout
|
||||
for format_list in SongFormat.get_format_list():
|
||||
if SongFormat.get(format_list, 'descriptionText') is not None:
|
||||
@ -209,13 +200,13 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
||||
Settings().setValue('songs/last import type', this_format)
|
||||
select_mode, class_, error_msg = SongFormat.get(this_format, 'selectMode', 'class', 'invalidSourceMsg')
|
||||
if select_mode == SongFormatSelect.MultipleFiles:
|
||||
import_source = self.get_list_of_files(self.format_widgets[this_format]['file_list_widget'])
|
||||
import_source = self.get_list_of_paths(self.format_widgets[this_format]['file_list_widget'])
|
||||
error_title = UiStrings().IFSp
|
||||
focus_button = self.format_widgets[this_format]['addButton']
|
||||
else:
|
||||
import_source = self.format_widgets[this_format]['file_path_edit'].text()
|
||||
import_source = self.format_widgets[this_format]['path_edit'].path
|
||||
error_title = (UiStrings().IFSs if select_mode == SongFormatSelect.SingleFile else UiStrings().IFdSs)
|
||||
focus_button = self.format_widgets[this_format]['browseButton']
|
||||
focus_button = self.format_widgets[this_format]['path_edit']
|
||||
if not class_.is_valid_source(import_source):
|
||||
critical_error_message_box(error_title, error_msg)
|
||||
focus_button.setFocus()
|
||||
@ -238,20 +229,23 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
||||
if filters:
|
||||
filters += ';;'
|
||||
filters += '{text} (*)'.format(text=UiStrings().AllFiles)
|
||||
file_paths, selected_filter = FileDialog.getOpenFileNames(
|
||||
self, title, Settings().value(self.plugin.settings_section + '/last directory import'), filters)
|
||||
file_paths, filter_used = FileDialog.getOpenFileNames(
|
||||
self, title,
|
||||
Settings().value(self.plugin.settings_section + '/last directory import'), filters)
|
||||
for file_path in file_paths:
|
||||
list_item = QtWidgets.QListWidgetItem(str(file_path))
|
||||
list_item.setData(QtCore.Qt.UserRole, file_path)
|
||||
listbox.addItem(list_item)
|
||||
if file_paths:
|
||||
file_names = [path_to_str(file_path) for file_path in file_paths]
|
||||
listbox.addItems(file_names)
|
||||
Settings().setValue(self.plugin.settings_section + '/last directory import', file_paths[0].parent)
|
||||
|
||||
def get_list_of_files(self, list_box):
|
||||
def get_list_of_paths(self, list_box):
|
||||
"""
|
||||
Return a list of file from the list_box
|
||||
|
||||
:param list_box: The source list box
|
||||
"""
|
||||
return [list_box.item(i).text() for i in range(list_box.count())]
|
||||
return [list_box.item(i).data(QtCore.Qt.UserRole) for i in range(list_box.count())]
|
||||
|
||||
def remove_selected_items(self, list_box):
|
||||
"""
|
||||
@ -263,20 +257,6 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
||||
item = list_box.takeItem(list_box.row(item))
|
||||
del item
|
||||
|
||||
def on_browse_button_clicked(self):
|
||||
"""
|
||||
Browse for files or a directory.
|
||||
"""
|
||||
this_format = self.current_format
|
||||
select_mode, format_name, ext_filter = SongFormat.get(this_format, 'selectMode', 'name', 'filter')
|
||||
file_path_edit = self.format_widgets[this_format]['file_path_edit']
|
||||
if select_mode == SongFormatSelect.SingleFile:
|
||||
self.get_file_name(WizardStrings.OpenTypeFile.format(file_type=format_name),
|
||||
file_path_edit, 'last directory import', ext_filter)
|
||||
elif select_mode == SongFormatSelect.SingleFolder:
|
||||
self.get_folder(
|
||||
WizardStrings.OpenTypeFolder.format(folder_name=format_name), file_path_edit, 'last directory import')
|
||||
|
||||
def on_add_button_clicked(self):
|
||||
"""
|
||||
Add a file or directory.
|
||||
@ -296,7 +276,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
||||
self.remove_selected_items(self.format_widgets[self.current_format]['file_list_widget'])
|
||||
self.source_page.completeChanged.emit()
|
||||
|
||||
def on_filepath_edit_text_changed(self):
|
||||
def on_path_edit_path_changed(self):
|
||||
"""
|
||||
Called when the content of the Filename/Folder edit box changes.
|
||||
"""
|
||||
@ -317,8 +297,6 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
||||
select_mode = SongFormat.get(format_list, 'selectMode')
|
||||
if select_mode == SongFormatSelect.MultipleFiles:
|
||||
self.format_widgets[format_list]['file_list_widget'].clear()
|
||||
else:
|
||||
self.format_widgets[format_list]['file_path_edit'].setText('')
|
||||
self.error_report_text_edit.clear()
|
||||
self.error_report_text_edit.setHidden(True)
|
||||
self.error_copy_to_button.setHidden(True)
|
||||
@ -341,14 +319,14 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
||||
select_mode = SongFormat.get(source_format, 'selectMode')
|
||||
if select_mode == SongFormatSelect.SingleFile:
|
||||
importer = self.plugin.import_songs(source_format,
|
||||
filename=self.format_widgets[source_format]['file_path_edit'].text())
|
||||
file_path=self.format_widgets[source_format]['path_edit'].path)
|
||||
elif select_mode == SongFormatSelect.SingleFolder:
|
||||
importer = self.plugin.import_songs(source_format,
|
||||
folder=self.format_widgets[source_format]['file_path_edit'].text())
|
||||
folder_path=self.format_widgets[source_format]['path_edit'].path)
|
||||
else:
|
||||
importer = self.plugin.import_songs(
|
||||
source_format,
|
||||
filenames=self.get_list_of_files(self.format_widgets[source_format]['file_list_widget']))
|
||||
file_paths=self.get_list_of_paths(self.format_widgets[source_format]['file_list_widget']))
|
||||
importer.do_import()
|
||||
self.progress_label.setText(WizardStrings.FinishedImport)
|
||||
|
||||
@ -366,18 +344,17 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
||||
"""
|
||||
file_path, filter_used = FileDialog.getSaveFileName(
|
||||
self, Settings().value(self.plugin.settings_section + '/last directory import'))
|
||||
if not file_path:
|
||||
if file_path is None:
|
||||
return
|
||||
with file_path.open('w', encoding='utf-8') as report_file:
|
||||
report_file.write(self.error_report_text_edit.toPlainText())
|
||||
file_path.write_text(self.error_report_text_edit.toPlainText(), encoding='utf-8')
|
||||
|
||||
def add_file_select_item(self):
|
||||
"""
|
||||
Add a file selection page.
|
||||
"""
|
||||
this_format = self.current_format
|
||||
prefix, can_disable, description_text, select_mode = \
|
||||
SongFormat.get(this_format, 'prefix', 'canDisable', 'descriptionText', 'selectMode')
|
||||
format_name, prefix, can_disable, description_text, select_mode, filters = \
|
||||
SongFormat.get(this_format, 'name', 'prefix', 'canDisable', 'descriptionText', 'selectMode', 'filter')
|
||||
page = QtWidgets.QWidget()
|
||||
page.setObjectName(prefix + 'Page')
|
||||
if can_disable:
|
||||
@ -403,26 +380,23 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
||||
if select_mode == SongFormatSelect.SingleFile or select_mode == SongFormatSelect.SingleFolder:
|
||||
file_path_layout = QtWidgets.QHBoxLayout()
|
||||
file_path_layout.setObjectName(prefix + '_file_path_layout')
|
||||
file_path_layout.setContentsMargins(0, self.format_v_spacing, 0, 0)
|
||||
file_path_label = QtWidgets.QLabel(import_widget)
|
||||
file_path_label.setObjectName(prefix + 'FilepathLabel')
|
||||
file_path_layout.addWidget(file_path_label)
|
||||
file_path_spacer = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||
file_path_layout.addSpacerItem(file_path_spacer)
|
||||
file_path_edit = QtWidgets.QLineEdit(import_widget)
|
||||
file_path_edit.setObjectName(prefix + '_file_path_edit')
|
||||
file_path_layout.addWidget(file_path_edit)
|
||||
browse_button = QtWidgets.QToolButton(import_widget)
|
||||
browse_button.setIcon(self.open_icon)
|
||||
browse_button.setObjectName(prefix + 'BrowseButton')
|
||||
file_path_layout.addWidget(browse_button)
|
||||
if select_mode == SongFormatSelect.SingleFile:
|
||||
path_type = PathType.Files
|
||||
dialog_caption = WizardStrings.OpenTypeFile.format(file_type=format_name)
|
||||
else:
|
||||
path_type = PathType.Directories
|
||||
dialog_caption = WizardStrings.OpenTypeFolder.format(folder_name=format_name)
|
||||
path_edit = PathEdit(
|
||||
parent=import_widget, path_type=path_type, dialog_caption=dialog_caption, show_revert=False)
|
||||
path_edit.filters = path_edit.filters + filters
|
||||
path_edit.path = Settings().value(self.plugin.settings_section + '/last directory import')
|
||||
file_path_layout.addWidget(path_edit)
|
||||
import_layout.addLayout(file_path_layout)
|
||||
import_layout.addSpacerItem(self.stack_spacer)
|
||||
self.format_widgets[this_format]['filepathLabel'] = file_path_label
|
||||
self.format_widgets[this_format]['filepathSpacer'] = file_path_spacer
|
||||
self.format_widgets[this_format]['file_path_layout'] = file_path_layout
|
||||
self.format_widgets[this_format]['file_path_edit'] = file_path_edit
|
||||
self.format_widgets[this_format]['browseButton'] = browse_button
|
||||
self.format_widgets[this_format]['path_edit'] = path_edit
|
||||
elif select_mode == SongFormatSelect.MultipleFiles:
|
||||
file_list_widget = QtWidgets.QListWidget(import_widget)
|
||||
file_list_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
@ -496,6 +470,8 @@ class SongImportSourcePage(QtWidgets.QWizardPage):
|
||||
* or if SingleFolder mode, the specified folder exists
|
||||
|
||||
When this method returns True, the wizard's Next button is enabled.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
wizard = self.wizard()
|
||||
this_format = wizard.current_format
|
||||
@ -505,10 +481,10 @@ class SongImportSourcePage(QtWidgets.QWizardPage):
|
||||
if wizard.format_widgets[this_format]['file_list_widget'].count() > 0:
|
||||
return True
|
||||
else:
|
||||
file_path = str(wizard.format_widgets[this_format]['file_path_edit'].text())
|
||||
file_path = wizard.format_widgets[this_format]['path_edit'].path
|
||||
if file_path:
|
||||
if select_mode == SongFormatSelect.SingleFile and os.path.isfile(file_path):
|
||||
if select_mode == SongFormatSelect.SingleFile and file_path.is_file():
|
||||
return True
|
||||
elif select_mode == SongFormatSelect.SingleFolder and os.path.isdir(file_path):
|
||||
elif select_mode == SongFormatSelect.SingleFolder and file_path.is_dir():
|
||||
return True
|
||||
return False
|
||||
|
@ -537,24 +537,24 @@ def delete_song(song_id, song_plugin):
|
||||
media_files = song_plugin.manager.get_all_objects(MediaFile, MediaFile.song_id == song_id)
|
||||
for media_file in media_files:
|
||||
try:
|
||||
os.remove(media_file.file_name)
|
||||
media_file.file_path.unlink()
|
||||
except OSError:
|
||||
log.exception('Could not remove file: {name}'.format(name=media_file.file_name))
|
||||
log.exception('Could not remove file: {name}'.format(name=media_file.file_path))
|
||||
try:
|
||||
save_path = os.path.join(str(AppLocation.get_section_data_path(song_plugin.name)), 'audio', str(song_id))
|
||||
if os.path.exists(save_path):
|
||||
os.rmdir(save_path)
|
||||
save_path = AppLocation.get_section_data_path(song_plugin.name) / 'audio' / str(song_id)
|
||||
if save_path.exists():
|
||||
save_path.rmdir()
|
||||
except OSError:
|
||||
log.exception('Could not remove directory: {path}'.format(path=save_path))
|
||||
song_plugin.manager.delete_object(Song, song_id)
|
||||
|
||||
|
||||
def transpose_lyrics(lyrics, transepose_value):
|
||||
def transpose_lyrics(lyrics, transpose_value):
|
||||
"""
|
||||
Transepose lyrics
|
||||
Transpose lyrics
|
||||
|
||||
:param lyrcs: The lyrics to be transposed
|
||||
:param transepose_value: The value to transpose the lyrics with
|
||||
:param lyrics: The lyrics to be transposed
|
||||
:param transpose_value: The value to transpose the lyrics with
|
||||
:return: The transposed lyrics
|
||||
"""
|
||||
# Split text by verse delimiter - both normal and optional
|
||||
@ -565,16 +565,17 @@ def transpose_lyrics(lyrics, transepose_value):
|
||||
if verse.startswith('---[') or verse == '[---]':
|
||||
transposed_lyrics += verse
|
||||
else:
|
||||
transposed_lyrics += transpose_verse(verse, transepose_value, notation)
|
||||
transposed_lyrics += transpose_verse(verse, transpose_value, notation)
|
||||
return transposed_lyrics
|
||||
|
||||
|
||||
def transpose_verse(verse_text, transepose_value, notation):
|
||||
def transpose_verse(verse_text, transpose_value, notation):
|
||||
"""
|
||||
Transepose lyrics
|
||||
Transpose Verse
|
||||
|
||||
:param lyrcs: The lyrics to be transposed
|
||||
:param transepose_value: The value to transpose the lyrics with
|
||||
:param verse_text: The lyrics to be transposed
|
||||
:param transpose_value: The value to transpose the lyrics with
|
||||
:param notation: which notation to use
|
||||
:return: The transposed lyrics
|
||||
"""
|
||||
if '[' not in verse_text:
|
||||
@ -592,11 +593,11 @@ def transpose_verse(verse_text, transepose_value, notation):
|
||||
if word == ']':
|
||||
in_tag = False
|
||||
transposed_lyrics += word
|
||||
elif word == '/':
|
||||
elif word == '/' or word == '--}{--':
|
||||
transposed_lyrics += word
|
||||
else:
|
||||
# This MUST be a chord
|
||||
transposed_lyrics += transpose_chord(word, transepose_value, notation)
|
||||
transposed_lyrics += transpose_chord(word, transpose_value, notation)
|
||||
# If still inside a chord tag something is wrong!
|
||||
if in_tag:
|
||||
return verse_text
|
||||
@ -632,36 +633,36 @@ def transpose_chord(chord, transpose_value, notation):
|
||||
for i in range(0, len(chord_split)):
|
||||
if i > 0:
|
||||
transposed_chord += '/'
|
||||
currentchord = chord_split[i]
|
||||
if currentchord and currentchord[0] == '(':
|
||||
current_chord = chord_split[i]
|
||||
if current_chord and current_chord[0] == '(':
|
||||
transposed_chord += '('
|
||||
if len(currentchord) > 1:
|
||||
currentchord = currentchord[1:]
|
||||
if len(current_chord) > 1:
|
||||
current_chord = current_chord[1:]
|
||||
else:
|
||||
currentchord = ''
|
||||
if len(currentchord) > 0:
|
||||
if len(currentchord) > 1:
|
||||
if '#b'.find(currentchord[1]) == -1:
|
||||
note = currentchord[0:1]
|
||||
rest = currentchord[1:]
|
||||
current_chord = ''
|
||||
if len(current_chord) > 0:
|
||||
if len(current_chord) > 1:
|
||||
if '#b'.find(current_chord[1]) == -1:
|
||||
note = current_chord[0:1]
|
||||
rest = current_chord[1:]
|
||||
else:
|
||||
note = currentchord[0:2]
|
||||
rest = currentchord[2:]
|
||||
note = current_chord[0:2]
|
||||
rest = current_chord[2:]
|
||||
else:
|
||||
note = currentchord
|
||||
note = current_chord
|
||||
rest = ''
|
||||
notenumber = notes_flat.index(note) if note not in notes_sharp else notes_sharp.index(note)
|
||||
notenumber += transpose_value
|
||||
while notenumber > 11:
|
||||
notenumber -= 12
|
||||
while notenumber < 0:
|
||||
notenumber += 12
|
||||
note_number = notes_flat.index(note) if note not in notes_sharp else notes_sharp.index(note)
|
||||
note_number += transpose_value
|
||||
while note_number > 11:
|
||||
note_number -= 12
|
||||
while note_number < 0:
|
||||
note_number += 12
|
||||
if i == 0:
|
||||
current_chord = notes_sharp[notenumber] if notes_preferred[notenumber] == '#' else notes_flat[
|
||||
notenumber]
|
||||
current_chord = notes_sharp[note_number] if notes_preferred[note_number] == '#' else notes_flat[
|
||||
note_number]
|
||||
last_chord = current_chord
|
||||
else:
|
||||
current_chord = notes_flat[notenumber] if last_chord not in notes_sharp else notes_sharp[notenumber]
|
||||
current_chord = notes_flat[note_number] if last_chord not in notes_sharp else notes_sharp[note_number]
|
||||
if not (note not in notes_flat and note not in notes_sharp):
|
||||
transposed_chord += current_chord + rest
|
||||
else:
|
||||
|
@ -23,13 +23,12 @@
|
||||
The :mod:`db` module provides the database and schema that is the backend for
|
||||
the Songs plugin
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, ForeignKey, Table, types
|
||||
from sqlalchemy.orm import mapper, relation, reconstructor
|
||||
from sqlalchemy.sql.expression import func, text
|
||||
|
||||
from openlp.core.lib.db import BaseModel, init_db
|
||||
from openlp.core.common.i18n import translate, get_natural_key
|
||||
from openlp.core.lib.db import BaseModel, PathType, init_db
|
||||
|
||||
|
||||
class Author(BaseModel):
|
||||
@ -237,7 +236,7 @@ def init_schema(url):
|
||||
|
||||
**media_files Table**
|
||||
* id
|
||||
* file_name
|
||||
* _file_path
|
||||
* type
|
||||
|
||||
**song_books Table**
|
||||
@ -304,7 +303,7 @@ def init_schema(url):
|
||||
'media_files', metadata,
|
||||
Column('id', types.Integer(), primary_key=True),
|
||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), default=None),
|
||||
Column('file_name', types.Unicode(255), nullable=False),
|
||||
Column('file_path', PathType, nullable=False),
|
||||
Column('type', types.Unicode(64), nullable=False, default='audio'),
|
||||
Column('weight', types.Integer(), default=0)
|
||||
)
|
||||
|
@ -19,11 +19,9 @@
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
import os
|
||||
import chardet
|
||||
import codecs
|
||||
import logging
|
||||
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.plugins.songs.lib import VerseType
|
||||
@ -48,7 +46,7 @@ class CCLIFileImport(SongImport):
|
||||
:param manager: The song manager for the running OpenLP installation.
|
||||
:param kwargs: The files to be imported.
|
||||
"""
|
||||
super(CCLIFileImport, self).__init__(manager, **kwargs)
|
||||
super().__init__(manager, **kwargs)
|
||||
|
||||
def do_import(self):
|
||||
"""
|
||||
@ -56,37 +54,35 @@ class CCLIFileImport(SongImport):
|
||||
"""
|
||||
log.debug('Starting CCLI File Import')
|
||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||
for filename in self.import_source:
|
||||
filename = str(filename)
|
||||
log.debug('Importing CCLI File: {name}'.format(name=filename))
|
||||
if os.path.isfile(filename):
|
||||
detect_file = open(filename, 'rb')
|
||||
detect_content = detect_file.read(2048)
|
||||
try:
|
||||
str(detect_content, 'utf-8')
|
||||
details = {'confidence': 1, 'encoding': 'utf-8'}
|
||||
except UnicodeDecodeError:
|
||||
details = chardet.detect(detect_content)
|
||||
detect_file.close()
|
||||
infile = codecs.open(filename, 'r', details['encoding'])
|
||||
if not infile.read(1) == '\ufeff':
|
||||
for file_path in self.import_source:
|
||||
log.debug('Importing CCLI File: {name}'.format(name=file_path))
|
||||
if file_path.is_file():
|
||||
with file_path.open('rb') as detect_file:
|
||||
detect_content = detect_file.read(2048)
|
||||
try:
|
||||
str(detect_content, 'utf-8')
|
||||
details = {'confidence': 1, 'encoding': 'utf-8'}
|
||||
except UnicodeDecodeError:
|
||||
details = chardet.detect(detect_content)
|
||||
in_file = codecs.open(str(file_path), 'r', details['encoding'])
|
||||
if not in_file.read(1) == '\ufeff':
|
||||
# not UTF or no BOM was found
|
||||
infile.seek(0)
|
||||
lines = infile.readlines()
|
||||
infile.close()
|
||||
ext = os.path.splitext(filename)[1]
|
||||
if ext.lower() == '.usr' or ext.lower() == '.bin':
|
||||
log.info('SongSelect USR format file found: {name}'.format(name=filename))
|
||||
in_file.seek(0)
|
||||
lines = in_file.readlines()
|
||||
in_file.close()
|
||||
ext = file_path.suffix.lower()
|
||||
if ext == '.usr' or ext == '.bin':
|
||||
log.info('SongSelect USR format file found: {name}'.format(name=file_path))
|
||||
if not self.do_import_usr_file(lines):
|
||||
self.log_error(filename)
|
||||
elif ext.lower() == '.txt':
|
||||
log.info('SongSelect TEXT format file found: {name}'.format(name=filename))
|
||||
self.log_error(file_path)
|
||||
elif ext == '.txt':
|
||||
log.info('SongSelect TEXT format file found: {name}'.format(name=file_path))
|
||||
if not self.do_import_txt_file(lines):
|
||||
self.log_error(filename)
|
||||
self.log_error(file_path)
|
||||
else:
|
||||
self.log_error(filename, translate('SongsPlugin.CCLIFileImport', 'The file does not have a valid '
|
||||
'extension.'))
|
||||
log.info('Extension {name} is not valid'.format(name=filename))
|
||||
self.log_error(file_path, translate('SongsPlugin.CCLIFileImport',
|
||||
'The file does not have a valid extension.'))
|
||||
log.info('Extension {name} is not valid'.format(name=file_path))
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
|
||||
|
@ -45,12 +45,11 @@ class ChordProImport(SongImport):
|
||||
"""
|
||||
def do_import(self):
|
||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||
for filename in self.import_source:
|
||||
for file_path in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
song_file = open(filename, 'rt')
|
||||
self.do_import_file(song_file)
|
||||
song_file.close()
|
||||
with file_path.open('rt') as song_file:
|
||||
self.do_import_file(song_file)
|
||||
|
||||
def do_import_file(self, song_file):
|
||||
"""
|
||||
|
@ -78,27 +78,29 @@ class DreamBeamImport(SongImport):
|
||||
|
||||
def do_import(self):
|
||||
"""
|
||||
Receive a single file or a list of files to import.
|
||||
Receive a single file_path or a list of files to import.
|
||||
"""
|
||||
if isinstance(self.import_source, list):
|
||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||
for file in self.import_source:
|
||||
for file_path in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.set_defaults()
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
try:
|
||||
parsed_file = etree.parse(open(file, 'r'), parser)
|
||||
with file_path.open('r') as xml_file:
|
||||
parsed_file = etree.parse(xml_file, parser)
|
||||
except etree.XMLSyntaxError:
|
||||
log.exception('XML syntax error in file {name}'.format(name=file))
|
||||
self.log_error(file, SongStrings.XMLSyntaxError)
|
||||
log.exception('XML syntax error in file_path {name}'.format(name=file_path))
|
||||
self.log_error(file_path, SongStrings.XMLSyntaxError)
|
||||
continue
|
||||
xml = etree.tostring(parsed_file).decode()
|
||||
song_xml = objectify.fromstring(xml)
|
||||
if song_xml.tag != 'DreamSong':
|
||||
self.log_error(
|
||||
file,
|
||||
translate('SongsPlugin.DreamBeamImport', 'Invalid DreamBeam song file. Missing DreamSong tag.'))
|
||||
file_path,
|
||||
translate('SongsPlugin.DreamBeamImport',
|
||||
'Invalid DreamBeam song file_path. Missing DreamSong tag.'))
|
||||
continue
|
||||
if hasattr(song_xml, 'Version'):
|
||||
self.version = float(song_xml.Version.text)
|
||||
@ -144,4 +146,4 @@ class DreamBeamImport(SongImport):
|
||||
else:
|
||||
self.parse_author(author_copyright)
|
||||
if not self.finish():
|
||||
self.log_error(file)
|
||||
self.log_error(file_path)
|
||||
|
@ -47,7 +47,7 @@ class EasySlidesImport(SongImport):
|
||||
def do_import(self):
|
||||
log.info('Importing EasySlides XML file {source}'.format(source=self.import_source))
|
||||
parser = etree.XMLParser(remove_blank_text=True, recover=True)
|
||||
parsed_file = etree.parse(self.import_source, parser)
|
||||
parsed_file = etree.parse(str(self.import_source), parser)
|
||||
xml = etree.tostring(parsed_file).decode()
|
||||
song_xml = objectify.fromstring(xml)
|
||||
self.import_wizard.progress_bar.setMaximum(len(song_xml.Item))
|
||||
|
@ -22,15 +22,16 @@
|
||||
"""
|
||||
The :mod:`easyworship` module provides the functionality for importing EasyWorship song databases into OpenLP.
|
||||
"""
|
||||
|
||||
import os
|
||||
import struct
|
||||
import re
|
||||
import zlib
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import zlib
|
||||
|
||||
import sqlite3
|
||||
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.common.path import Path
|
||||
from openlp.plugins.songs.lib import VerseType
|
||||
from openlp.plugins.songs.lib import retrieve_windows_encoding, strip_rtf
|
||||
from .songimport import SongImport
|
||||
@ -76,9 +77,11 @@ class EasyWorshipSongImport(SongImport):
|
||||
"""
|
||||
Determines the type of file to import and calls the appropiate method
|
||||
"""
|
||||
if self.import_source.lower().endswith('ews'):
|
||||
self.import_source = Path(self.import_source)
|
||||
ext = self.import_source.suffix.lower()
|
||||
if ext == '.ews':
|
||||
self.import_ews()
|
||||
elif self.import_source.endswith('DB'):
|
||||
elif ext == '.db':
|
||||
self.import_db()
|
||||
else:
|
||||
self.import_sqlite_db()
|
||||
@ -91,11 +94,11 @@ class EasyWorshipSongImport(SongImport):
|
||||
or here: http://wiki.openlp.org/Development:EasyWorship_EWS_Format
|
||||
"""
|
||||
# Open ews file if it exists
|
||||
if not os.path.isfile(self.import_source):
|
||||
if not self.import_source.is_file():
|
||||
log.debug('Given ews file does not exists.')
|
||||
return
|
||||
# Make sure there is room for at least a header and one entry
|
||||
if os.path.getsize(self.import_source) < 892:
|
||||
if self.import_source.stat().st_size < 892:
|
||||
log.debug('Given ews file is to small to contain valid data.')
|
||||
return
|
||||
# Take a stab at how text is encoded
|
||||
@ -104,7 +107,7 @@ class EasyWorshipSongImport(SongImport):
|
||||
if not self.encoding:
|
||||
log.debug('No encoding set.')
|
||||
return
|
||||
self.ews_file = open(self.import_source, 'rb')
|
||||
self.ews_file = self.import_source.open('rb')
|
||||
# EWS header, version '1.6'/' 3'/' 5':
|
||||
# Offset Field Data type Length Details
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
@ -199,23 +202,22 @@ class EasyWorshipSongImport(SongImport):
|
||||
Import the songs from the database
|
||||
"""
|
||||
# Open the DB and MB files if they exist
|
||||
import_source_mb = self.import_source.replace('.DB', '.MB')
|
||||
if not os.path.isfile(self.import_source):
|
||||
import_source_mb = self.import_source.with_suffix('.MB')
|
||||
if not self.import_source.is_file():
|
||||
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
|
||||
'This file does not exist.'))
|
||||
return
|
||||
if not os.path.isfile(import_source_mb):
|
||||
if not import_source_mb.is_file():
|
||||
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
|
||||
'Could not find the "Songs.MB" file. It must be in the same '
|
||||
'folder as the "Songs.DB" file.'))
|
||||
return
|
||||
db_size = os.path.getsize(self.import_source)
|
||||
if db_size < 0x800:
|
||||
if self.import_source.stat().st_size < 0x800:
|
||||
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
|
||||
'This file is not a valid EasyWorship database.'))
|
||||
return
|
||||
db_file = open(self.import_source, 'rb')
|
||||
self.memo_file = open(import_source_mb, 'rb')
|
||||
db_file = self.import_source.open('rb')
|
||||
self.memo_file = import_source_mb.open('rb')
|
||||
# Don't accept files that are clearly not paradox files
|
||||
record_size, header_size, block_size, first_block, num_fields = struct.unpack('<hhxb8xh17xh', db_file.read(35))
|
||||
if header_size != 0x800 or block_size < 1 or block_size > 4:
|
||||
@ -340,52 +342,34 @@ class EasyWorshipSongImport(SongImport):
|
||||
db_file.close()
|
||||
self.memo_file.close()
|
||||
|
||||
def _find_file(self, base_path, path_list):
|
||||
"""
|
||||
Find the specified file, with the option of the file being at any level in the specified directory structure.
|
||||
|
||||
:param base_path: the location search in
|
||||
:param path_list: the targeted file, preceded by directories that may be their parents relative to the base_path
|
||||
:return: path for targeted file
|
||||
"""
|
||||
target_file = ''
|
||||
while len(path_list) > 0:
|
||||
target_file = os.path.join(path_list[-1], target_file)
|
||||
path_list = path_list[:len(path_list) - 1]
|
||||
full_path = os.path.join(base_path, target_file)
|
||||
full_path = full_path[:len(full_path) - 1]
|
||||
if os.path.isfile(full_path):
|
||||
return full_path
|
||||
return ''
|
||||
|
||||
def import_sqlite_db(self):
|
||||
"""
|
||||
Import the songs from an EasyWorship 6 SQLite database
|
||||
"""
|
||||
songs_db_path = self._find_file(self.import_source, ["Databases", "Data", "Songs.db"])
|
||||
song_words_db_path = self._find_file(self.import_source, ["Databases", "Data", "SongWords.db"])
|
||||
invalid_dir_msg = 'This does not appear to be a valid Easy Worship 6 database directory.'
|
||||
songs_db_path = next(self.import_source.rglob('Songs.db'), None)
|
||||
song_words_db_path = next(self.import_source.rglob('SongWords.db'), None)
|
||||
invalid_dir_msg = translate('SongsPlugin.EasyWorshipSongImport',
|
||||
'This does not appear to be a valid Easy Worship 6 database directory.')
|
||||
invalid_db_msg = translate('SongsPlugin.EasyWorshipSongImport', 'This is not a valid Easy Worship 6 database.')
|
||||
# check to see if needed files are there
|
||||
if not os.path.isfile(songs_db_path):
|
||||
self.log_error(songs_db_path, translate('SongsPlugin.EasyWorshipSongImport', invalid_dir_msg))
|
||||
if not (songs_db_path and songs_db_path.is_file()):
|
||||
self.log_error(self.import_source, invalid_dir_msg)
|
||||
return
|
||||
if not os.path.isfile(song_words_db_path):
|
||||
self.log_error(song_words_db_path, translate('SongsPlugin.EasyWorshipSongImport', invalid_dir_msg))
|
||||
if not (song_words_db_path and song_words_db_path.is_file()):
|
||||
self.log_error(self.import_source, invalid_dir_msg)
|
||||
return
|
||||
# get database handles
|
||||
songs_conn = sqlite3.connect(songs_db_path)
|
||||
words_conn = sqlite3.connect(song_words_db_path)
|
||||
songs_conn = sqlite3.connect(str(songs_db_path))
|
||||
words_conn = sqlite3.connect(str(song_words_db_path))
|
||||
if songs_conn is None or words_conn is None:
|
||||
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
|
||||
'This is not a valid Easy Worship 6 database.'))
|
||||
self.log_error(self.import_source, invalid_db_msg)
|
||||
songs_conn.close()
|
||||
words_conn.close()
|
||||
return
|
||||
songs_db = songs_conn.cursor()
|
||||
words_db = words_conn.cursor()
|
||||
if songs_conn is None or words_conn is None:
|
||||
self.log_error(self.import_source, translate('SongsPlugin.EasyWorshipSongImport',
|
||||
'This is not a valid Easy Worship 6 database.'))
|
||||
self.log_error(self.import_source, invalid_db_msg)
|
||||
songs_conn.close()
|
||||
words_conn.close()
|
||||
return
|
||||
|
@ -82,10 +82,8 @@ The XML of `Foilpresenter <http://foilpresenter.de/>`_ songs is of the format::
|
||||
</kategorien>
|
||||
</foilpresenterfolie>
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
import os
|
||||
|
||||
from lxml import etree, objectify
|
||||
|
||||
@ -121,10 +119,9 @@ class FoilPresenterImport(SongImport):
|
||||
for file_path in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.import_wizard.increment_progress_bar(
|
||||
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
|
||||
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
|
||||
try:
|
||||
parsed_file = etree.parse(file_path, parser)
|
||||
parsed_file = etree.parse(str(file_path), parser)
|
||||
xml = etree.tostring(parsed_file).decode()
|
||||
self.foil_presenter.xml_to_song(xml)
|
||||
except etree.XMLSyntaxError:
|
||||
|
@ -50,12 +50,11 @@ class LyrixImport(SongImport):
|
||||
if not isinstance(self.import_source, list):
|
||||
return
|
||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||
for filename in self.import_source:
|
||||
for file_path in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
song_file = open(filename, 'rt', encoding='cp1251')
|
||||
self.do_import_file(song_file)
|
||||
song_file.close()
|
||||
with file_path.open('rt', encoding='cp1251') as song_file:
|
||||
self.do_import_file(song_file)
|
||||
|
||||
def do_import_file(self, file):
|
||||
"""
|
||||
|
@ -266,7 +266,7 @@ class OpenLPSongImport(SongImport):
|
||||
if has_media_files and song.media_files:
|
||||
for media_file in song.media_files:
|
||||
existing_media_file = self.manager.get_object_filtered(
|
||||
MediaFile, MediaFile.file_name == media_file.file_name)
|
||||
MediaFile, MediaFile.file_path == media_file.file_path)
|
||||
if existing_media_file:
|
||||
new_song.media_files.append(existing_media_file)
|
||||
else:
|
||||
|
@ -23,9 +23,7 @@
|
||||
The :mod:`openlyrics` module provides the functionality for importing
|
||||
songs which are saved as OpenLyrics files.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from lxml import etree
|
||||
|
||||
@ -58,12 +56,11 @@ class OpenLyricsImport(SongImport):
|
||||
for file_path in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.import_wizard.increment_progress_bar(
|
||||
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
|
||||
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
|
||||
try:
|
||||
# Pass a file object, because lxml does not cope with some
|
||||
# special characters in the path (see lp:757673 and lp:744337).
|
||||
parsed_file = etree.parse(open(file_path, 'rb'), parser)
|
||||
parsed_file = etree.parse(file_path.open('rb'), parser)
|
||||
xml = etree.tostring(parsed_file).decode()
|
||||
self.open_lyrics.xml_to_song(xml)
|
||||
except etree.XMLSyntaxError:
|
||||
|
@ -20,7 +20,6 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
from PyQt5 import QtCore
|
||||
@ -70,12 +69,11 @@ class OpenOfficeImport(SongImport):
|
||||
log.error(exc)
|
||||
return
|
||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||
for filename in self.import_source:
|
||||
for file_path in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
break
|
||||
filename = str(filename)
|
||||
if os.path.isfile(filename):
|
||||
self.open_ooo_file(filename)
|
||||
if file_path.is_file():
|
||||
self.open_ooo_file(file_path)
|
||||
if self.document:
|
||||
self.process_ooo_document()
|
||||
self.close_ooo_file()
|
||||
@ -144,12 +142,7 @@ class OpenOfficeImport(SongImport):
|
||||
Open the passed file in OpenOffice.org Impress
|
||||
"""
|
||||
self.file_path = file_path
|
||||
if is_win():
|
||||
url = file_path.replace('\\', '/')
|
||||
url = url.replace(':', '|').replace(' ', '%20')
|
||||
url = 'file:///' + url
|
||||
else:
|
||||
url = uno.systemPathToFileUrl(file_path)
|
||||
url = file_path.as_uri()
|
||||
properties = []
|
||||
properties.append(self.create_property('Hidden', True))
|
||||
properties = tuple(properties)
|
||||
@ -159,7 +152,7 @@ class OpenOfficeImport(SongImport):
|
||||
self.document.supportsService("com.sun.star.text.TextDocument"):
|
||||
self.close_ooo_file()
|
||||
else:
|
||||
self.import_wizard.increment_progress_bar('Processing file ' + file_path, 0)
|
||||
self.import_wizard.increment_progress_bar('Processing file {file_path}'.format(file_path=file_path), 0)
|
||||
except AttributeError:
|
||||
log.exception("open_ooo_file failed: {url}".format(url=url))
|
||||
return
|
||||
|
@ -116,12 +116,11 @@ class OpenSongImport(SongImport):
|
||||
if not isinstance(self.import_source, list):
|
||||
return
|
||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||
for filename in self.import_source:
|
||||
for file_path in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
song_file = open(filename, 'rb')
|
||||
self.do_import_file(song_file)
|
||||
song_file.close()
|
||||
with file_path.open('rb') as song_file:
|
||||
self.do_import_file(song_file)
|
||||
|
||||
def do_import_file(self, file):
|
||||
"""
|
||||
|
@ -231,16 +231,15 @@ class OPSProImport(SongImport):
|
||||
xor_pattern_2k = (0xa1, 0xec, 0x7a, 0x9c, 0xe1, 0x28, 0x34, 0x8a, 0x73, 0x7b, 0xd2, 0xdf, 0x50)
|
||||
# Access97 XOR of the source
|
||||
xor_pattern_97 = (0x86, 0xfb, 0xec, 0x37, 0x5d, 0x44, 0x9c, 0xfa, 0xc6, 0x5e, 0x28, 0xe6, 0x13)
|
||||
mdb = open(self.import_source, 'rb')
|
||||
mdb.seek(0x14)
|
||||
version = struct.unpack('B', mdb.read(1))[0]
|
||||
# Get encrypted logo
|
||||
mdb.seek(0x62)
|
||||
EncrypFlag = struct.unpack('B', mdb.read(1))[0]
|
||||
# Get encrypted password
|
||||
mdb.seek(0x42)
|
||||
encrypted_password = mdb.read(26)
|
||||
mdb.close()
|
||||
with self.import_source.open('rb') as mdb_file:
|
||||
mdb_file.seek(0x14)
|
||||
version = struct.unpack('B', mdb_file.read(1))[0]
|
||||
# Get encrypted logo
|
||||
mdb_file.seek(0x62)
|
||||
EncrypFlag = struct.unpack('B', mdb_file.read(1))[0]
|
||||
# Get encrypted password
|
||||
mdb_file.seek(0x42)
|
||||
encrypted_password = mdb_file.read(26)
|
||||
# "Decrypt" the password based on the version
|
||||
decrypted_password = ''
|
||||
if version < 0x01:
|
||||
|
@ -23,8 +23,6 @@
|
||||
The :mod:`powerpraiseimport` module provides the functionality for importing
|
||||
Powerpraise song files into the current database.
|
||||
"""
|
||||
|
||||
import os
|
||||
from lxml import objectify
|
||||
|
||||
from openlp.core.ui.lib.wizard import WizardStrings
|
||||
@ -41,10 +39,10 @@ class PowerPraiseImport(SongImport):
|
||||
for file_path in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.import_wizard.increment_progress_bar(
|
||||
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
|
||||
root = objectify.parse(open(file_path, 'rb')).getroot()
|
||||
self.process_song(root)
|
||||
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
|
||||
with file_path.open('rb') as xml_file:
|
||||
root = objectify.parse(xml_file).getroot()
|
||||
self.process_song(root)
|
||||
|
||||
def process_song(self, root):
|
||||
self.set_defaults()
|
||||
|
@ -72,10 +72,14 @@ class PowerSongImport(SongImport):
|
||||
Checks if source is a PowerSong 1.0 folder:
|
||||
* is a directory
|
||||
* contains at least one \*.song file
|
||||
|
||||
:param openlp.core.common.path.Path import_source: Should be a Path object that fulfills the above criteria
|
||||
:return: If the source is valid
|
||||
:rtype: bool
|
||||
"""
|
||||
if os.path.isdir(import_source):
|
||||
for file in os.listdir(import_source):
|
||||
if fnmatch.fnmatch(file, '*.song'):
|
||||
if import_source.is_dir():
|
||||
for file_path in import_source.iterdir():
|
||||
if file_path.suffix == '.song':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -23,13 +23,12 @@
|
||||
The :mod:`presentationmanager` module provides the functionality for importing
|
||||
Presentationmanager song files into the current database.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
|
||||
import chardet
|
||||
from lxml import objectify, etree
|
||||
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.common import get_file_encoding
|
||||
from openlp.core.ui.lib.wizard import WizardStrings
|
||||
from .songimport import SongImport
|
||||
|
||||
@ -44,17 +43,14 @@ class PresentationManagerImport(SongImport):
|
||||
for file_path in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.import_wizard.increment_progress_bar(
|
||||
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
|
||||
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name))
|
||||
try:
|
||||
tree = etree.parse(file_path, parser=etree.XMLParser(recover=True))
|
||||
tree = etree.parse(str(file_path), parser=etree.XMLParser(recover=True))
|
||||
except etree.XMLSyntaxError:
|
||||
# Try to detect encoding and use it
|
||||
file = open(file_path, mode='rb')
|
||||
encoding = chardet.detect(file.read())['encoding']
|
||||
file.close()
|
||||
encoding = get_file_encoding(file_path)['encoding']
|
||||
# Open file with detected encoding and remove encoding declaration
|
||||
text = open(file_path, mode='r', encoding=encoding).read()
|
||||
text = file_path.read_text(encoding=encoding)
|
||||
text = re.sub('.+\?>\n', '', text)
|
||||
try:
|
||||
tree = etree.fromstring(text, parser=etree.XMLParser(recover=True))
|
||||
@ -80,6 +76,11 @@ class PresentationManagerImport(SongImport):
|
||||
return ''
|
||||
|
||||
def process_song(self, root, file_path):
|
||||
"""
|
||||
:param root:
|
||||
:param openlp.core.common.path.Path file_path: Path to the file to process
|
||||
:rtype: None
|
||||
"""
|
||||
self.set_defaults()
|
||||
attrs = None
|
||||
if hasattr(root, 'attributes'):
|
||||
@ -123,4 +124,4 @@ class PresentationManagerImport(SongImport):
|
||||
|
||||
self.verse_order_list = verse_order_list
|
||||
if not self.finish():
|
||||
self.log_error(os.path.basename(file_path))
|
||||
self.log_error(file_path.name)
|
||||
|
@ -23,8 +23,6 @@
|
||||
The :mod:`propresenter` module provides the functionality for importing
|
||||
ProPresenter song files into the current installation database.
|
||||
"""
|
||||
|
||||
import os
|
||||
import base64
|
||||
import logging
|
||||
from lxml import objectify
|
||||
@ -47,11 +45,17 @@ class ProPresenterImport(SongImport):
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.import_wizard.increment_progress_bar(
|
||||
WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
|
||||
root = objectify.parse(open(file_path, 'rb')).getroot()
|
||||
self.process_song(root, file_path)
|
||||
WizardStrings.ImportingType.format(source=file_path.name))
|
||||
with file_path.open('rb') as xml_file:
|
||||
root = objectify.parse(xml_file).getroot()
|
||||
self.process_song(root, file_path)
|
||||
|
||||
def process_song(self, root, filename):
|
||||
def process_song(self, root, file_path):
|
||||
"""
|
||||
:param root:
|
||||
:param openlp.core.common.path.Path file_path: Path to the file thats being imported
|
||||
:rtype: None
|
||||
"""
|
||||
self.set_defaults()
|
||||
|
||||
# Extract ProPresenter versionNumber
|
||||
@ -64,9 +68,7 @@ class ProPresenterImport(SongImport):
|
||||
# Title
|
||||
self.title = root.get('CCLISongTitle')
|
||||
if not self.title or self.title == '':
|
||||
self.title = os.path.basename(filename)
|
||||
if self.title[-5:-1] == '.pro':
|
||||
self.title = self.title[:-5]
|
||||
self.title = file_path.stem
|
||||
# Notes
|
||||
self.comments = root.get('notes')
|
||||
# Author
|
||||
|
@ -113,7 +113,7 @@ class SongBeamerImport(SongImport):
|
||||
if not isinstance(self.import_source, list):
|
||||
return
|
||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||
for import_file in self.import_source:
|
||||
for file_path in self.import_source:
|
||||
# TODO: check that it is a valid SongBeamer file
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
@ -121,20 +121,19 @@ class SongBeamerImport(SongImport):
|
||||
self.current_verse = ''
|
||||
self.current_verse_type = VerseType.tags[VerseType.Verse]
|
||||
self.chord_table = None
|
||||
file_name = os.path.split(import_file)[1]
|
||||
if os.path.isfile(import_file):
|
||||
if file_path.is_file():
|
||||
# Detect the encoding
|
||||
self.input_file_encoding = get_file_encoding(Path(import_file))['encoding']
|
||||
self.input_file_encoding = get_file_encoding(file_path)['encoding']
|
||||
# The encoding should only be ANSI (cp1252), UTF-8, Unicode, Big-Endian-Unicode.
|
||||
# So if it doesn't start with 'u' we default to cp1252. See:
|
||||
# https://forum.songbeamer.com/viewtopic.php?p=419&sid=ca4814924e37c11e4438b7272a98b6f2
|
||||
if not self.input_file_encoding.lower().startswith('u'):
|
||||
self.input_file_encoding = 'cp1252'
|
||||
infile = open(import_file, 'rt', encoding=self.input_file_encoding)
|
||||
song_data = infile.readlines()
|
||||
with file_path.open(encoding=self.input_file_encoding) as song_file:
|
||||
song_data = song_file.readlines()
|
||||
else:
|
||||
continue
|
||||
self.title = file_name.split('.sng')[0]
|
||||
self.title = file_path.stem
|
||||
read_verses = False
|
||||
# The first verse separator doesn't count, but the others does, so line count starts at -1
|
||||
line_number = -1
|
||||
@ -186,7 +185,7 @@ class SongBeamerImport(SongImport):
|
||||
# inserted by songbeamer, but are manually added headings. So restart the loop, and
|
||||
# count tags as lines.
|
||||
self.set_defaults()
|
||||
self.title = file_name.split('.sng')[0]
|
||||
self.title = file_path.stem
|
||||
verse_tags_mode = VerseTagMode.ContainsNoTagsRestart
|
||||
read_verses = False
|
||||
# The first verseseparator doesn't count, but the others does, so linecount starts at -1
|
||||
@ -208,7 +207,7 @@ class SongBeamerImport(SongImport):
|
||||
self.replace_html_tags()
|
||||
self.add_verse(self.current_verse, self.current_verse_type)
|
||||
if not self.finish():
|
||||
self.log_error(import_file)
|
||||
self.log_error(file_path)
|
||||
|
||||
def insert_chords(self, line_number, line):
|
||||
"""
|
||||
@ -416,14 +415,15 @@ class SongBeamerImport(SongImport):
|
||||
"""
|
||||
# The path is relative to SongBeamers Song folder
|
||||
if is_win():
|
||||
user_doc_folder = os.path.expandvars('$DOCUMENTS')
|
||||
user_doc_path = Path(os.path.expandvars('$DOCUMENTS'))
|
||||
elif is_macosx():
|
||||
user_doc_folder = os.path.join(os.path.expanduser('~'), 'Documents')
|
||||
user_doc_path = Path.home() / 'Documents'
|
||||
else:
|
||||
# SongBeamer only runs on mac and win...
|
||||
return
|
||||
audio_file_path = os.path.normpath(os.path.join(user_doc_folder, 'SongBeamer', 'Songs', audio_file_path))
|
||||
if os.path.isfile(audio_file_path):
|
||||
audio_file_path = user_doc_path / 'SongBeamer' / 'Songs' / audio_file_path
|
||||
if audio_file_path.is_file():
|
||||
self.add_media_file(audio_file_path)
|
||||
else:
|
||||
log.debug('Could not import mediafile "%s" since it does not exists!' % audio_file_path)
|
||||
log.debug('Could not import mediafile "{audio_file_path}" since it does not exists!'
|
||||
.format(audio_file_path=audio_file_path))
|
||||
|
@ -22,14 +22,12 @@
|
||||
|
||||
import logging
|
||||
import re
|
||||
import shutil
|
||||
import os
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.common.path import Path, create_paths
|
||||
from openlp.core.common.path import copyfile, create_paths
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.ui.lib.wizard import WizardStrings
|
||||
from openlp.plugins.songs.lib import clean_song, VerseType
|
||||
@ -64,14 +62,14 @@ class SongImport(QtCore.QObject):
|
||||
"""
|
||||
self.manager = manager
|
||||
QtCore.QObject.__init__(self)
|
||||
if 'filename' in kwargs:
|
||||
self.import_source = kwargs['filename']
|
||||
elif 'filenames' in kwargs:
|
||||
self.import_source = kwargs['filenames']
|
||||
elif 'folder' in kwargs:
|
||||
self.import_source = kwargs['folder']
|
||||
if 'file_path' in kwargs:
|
||||
self.import_source = kwargs['file_path']
|
||||
elif 'file_paths' in kwargs:
|
||||
self.import_source = kwargs['file_paths']
|
||||
elif 'folder_path' in kwargs:
|
||||
self.import_source = kwargs['folder_path']
|
||||
else:
|
||||
raise KeyError('Keyword arguments "filename[s]" or "folder" not supplied.')
|
||||
raise KeyError('Keyword arguments "file_path[s]" or "folder_path" not supplied.')
|
||||
log.debug(self.import_source)
|
||||
self.import_wizard = None
|
||||
self.song = None
|
||||
@ -272,13 +270,13 @@ class SongImport(QtCore.QObject):
|
||||
return
|
||||
self.authors.append((author, type))
|
||||
|
||||
def add_media_file(self, filename, weight=0):
|
||||
def add_media_file(self, file_path, weight=0):
|
||||
"""
|
||||
Add a media file to the list
|
||||
"""
|
||||
if filename in [x[0] for x in self.media_files]:
|
||||
if file_path in [x[0] for x in self.media_files]:
|
||||
return
|
||||
self.media_files.append((filename, weight))
|
||||
self.media_files.append((file_path, weight))
|
||||
|
||||
def add_verse(self, verse_text, verse_def='v', lang=None):
|
||||
"""
|
||||
@ -405,29 +403,30 @@ class SongImport(QtCore.QObject):
|
||||
self.manager.save_object(song)
|
||||
# Now loop through the media files, copy them to the correct location,
|
||||
# and save the song again.
|
||||
for filename, weight in self.media_files:
|
||||
media_file = self.manager.get_object_filtered(MediaFile, MediaFile.file_name == filename)
|
||||
for file_path, weight in self.media_files:
|
||||
media_file = self.manager.get_object_filtered(MediaFile, MediaFile.file_path == file_path)
|
||||
if not media_file:
|
||||
if os.path.dirname(filename):
|
||||
filename = self.copy_media_file(song.id, filename)
|
||||
song.media_files.append(MediaFile.populate(file_name=filename, weight=weight))
|
||||
if file_path.parent:
|
||||
file_path = self.copy_media_file(song.id, file_path)
|
||||
song.media_files.append(MediaFile.populate(file_path=file_path, weight=weight))
|
||||
self.manager.save_object(song)
|
||||
self.set_defaults()
|
||||
return True
|
||||
|
||||
def copy_media_file(self, song_id, filename):
|
||||
def copy_media_file(self, song_id, file_path):
|
||||
"""
|
||||
This method copies the media file to the correct location and returns
|
||||
the new file location.
|
||||
|
||||
:param song_id:
|
||||
:param filename: The file to copy.
|
||||
:param openlp.core.common.path.Path file_path: The file to copy.
|
||||
:return: The new location of the file
|
||||
:rtype: openlp.core.common.path.Path
|
||||
"""
|
||||
if not hasattr(self, 'save_path'):
|
||||
self.save_path = os.path.join(str(AppLocation.get_section_data_path(self.import_wizard.plugin.name)),
|
||||
'audio', str(song_id))
|
||||
create_paths(Path(self.save_path))
|
||||
if not filename.startswith(self.save_path):
|
||||
old_file, filename = filename, os.path.join(self.save_path, os.path.split(filename)[1])
|
||||
shutil.copyfile(old_file, filename)
|
||||
return filename
|
||||
self.save_path = AppLocation.get_section_data_path(self.import_wizard.plugin.name) / 'audio' / str(song_id)
|
||||
create_paths(self.save_path)
|
||||
if self.save_path not in file_path.parents:
|
||||
old_path, file_path = file_path, self.save_path / file_path.name
|
||||
copyfile(old_path, file_path)
|
||||
return file_path
|
||||
|
@ -25,6 +25,7 @@ songs into the OpenLP database.
|
||||
"""
|
||||
import re
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
from openlp.plugins.songs.lib import strip_rtf
|
||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||
|
||||
@ -72,7 +73,8 @@ class SongProImport(SongImport):
|
||||
Receive a single file or a list of files to import.
|
||||
"""
|
||||
self.encoding = None
|
||||
with open(self.import_source, 'rt', errors='ignore') as songs_file:
|
||||
self.import_source = Path(self.import_source)
|
||||
with self.import_source.open('rt', errors='ignore') as songs_file:
|
||||
self.import_wizard.progress_bar.setMaximum(0)
|
||||
tag = ''
|
||||
text = ''
|
||||
|
@ -23,7 +23,6 @@
|
||||
The :mod:`songshowplus` module provides the functionality for importing SongShow Plus songs into the OpenLP
|
||||
database.
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
import struct
|
||||
@ -93,97 +92,95 @@ class SongShowPlusImport(SongImport):
|
||||
if not isinstance(self.import_source, list):
|
||||
return
|
||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||
for file in self.import_source:
|
||||
for file_path in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.ssp_verse_order_list = []
|
||||
self.other_count = 0
|
||||
self.other_list = {}
|
||||
file_name = os.path.split(file)[1]
|
||||
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_name), 0)
|
||||
song_data = open(file, 'rb')
|
||||
while True:
|
||||
block_key, = struct.unpack("I", song_data.read(4))
|
||||
log.debug('block_key: %d' % block_key)
|
||||
# The file ends with 4 NULL's
|
||||
if block_key == 0:
|
||||
break
|
||||
next_block_starts, = struct.unpack("I", song_data.read(4))
|
||||
next_block_starts += song_data.tell()
|
||||
if block_key in (VERSE, CHORUS, BRIDGE):
|
||||
null, verse_no, = struct.unpack("BB", song_data.read(2))
|
||||
elif block_key == CUSTOM_VERSE:
|
||||
null, verse_name_length, = struct.unpack("BB", song_data.read(2))
|
||||
verse_name = self.decode(song_data.read(verse_name_length))
|
||||
length_descriptor_size, = struct.unpack("B", song_data.read(1))
|
||||
log.debug('length_descriptor_size: %d' % length_descriptor_size)
|
||||
# In the case of song_numbers the number is in the data from the
|
||||
# current position to the next block starts
|
||||
if block_key == SONG_NUMBER:
|
||||
sn_bytes = song_data.read(length_descriptor_size - 1)
|
||||
self.song_number = int.from_bytes(sn_bytes, byteorder='little')
|
||||
continue
|
||||
# Detect if/how long the length descriptor is
|
||||
if length_descriptor_size == 12 or length_descriptor_size == 20:
|
||||
length_descriptor, = struct.unpack("I", song_data.read(4))
|
||||
elif length_descriptor_size == 2:
|
||||
length_descriptor = 1
|
||||
elif length_descriptor_size == 9:
|
||||
length_descriptor = 0
|
||||
else:
|
||||
length_descriptor, = struct.unpack("B", song_data.read(1))
|
||||
log.debug('length_descriptor: %d' % length_descriptor)
|
||||
data = song_data.read(length_descriptor)
|
||||
log.debug(data)
|
||||
if block_key == TITLE:
|
||||
self.title = self.decode(data)
|
||||
elif block_key == AUTHOR:
|
||||
authors = self.decode(data).split(" / ")
|
||||
for author in authors:
|
||||
if author.find(",") != -1:
|
||||
author_parts = author.split(", ")
|
||||
author = author_parts[1] + " " + author_parts[0]
|
||||
self.parse_author(author)
|
||||
elif block_key == COPYRIGHT:
|
||||
self.add_copyright(self.decode(data))
|
||||
elif block_key == CCLI_NO:
|
||||
# Try to get the CCLI number even if the field contains additional text
|
||||
match = re.search(r'\d+', self.decode(data))
|
||||
if match:
|
||||
self.ccli_number = int(match.group())
|
||||
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_path.name), 0)
|
||||
with file_path.open('rb') as song_file:
|
||||
while True:
|
||||
block_key, = struct.unpack("I", song_file.read(4))
|
||||
log.debug('block_key: %d' % block_key)
|
||||
# The file ends with 4 NULL's
|
||||
if block_key == 0:
|
||||
break
|
||||
next_block_starts, = struct.unpack("I", song_file.read(4))
|
||||
next_block_starts += song_file.tell()
|
||||
if block_key in (VERSE, CHORUS, BRIDGE):
|
||||
null, verse_no, = struct.unpack("BB", song_file.read(2))
|
||||
elif block_key == CUSTOM_VERSE:
|
||||
null, verse_name_length, = struct.unpack("BB", song_file.read(2))
|
||||
verse_name = self.decode(song_file.read(verse_name_length))
|
||||
length_descriptor_size, = struct.unpack("B", song_file.read(1))
|
||||
log.debug('length_descriptor_size: %d' % length_descriptor_size)
|
||||
# In the case of song_numbers the number is in the data from the
|
||||
# current position to the next block starts
|
||||
if block_key == SONG_NUMBER:
|
||||
sn_bytes = song_file.read(length_descriptor_size - 1)
|
||||
self.song_number = int.from_bytes(sn_bytes, byteorder='little')
|
||||
continue
|
||||
# Detect if/how long the length descriptor is
|
||||
if length_descriptor_size == 12 or length_descriptor_size == 20:
|
||||
length_descriptor, = struct.unpack("I", song_file.read(4))
|
||||
elif length_descriptor_size == 2:
|
||||
length_descriptor = 1
|
||||
elif length_descriptor_size == 9:
|
||||
length_descriptor = 0
|
||||
else:
|
||||
log.warning("Can't parse CCLI Number from string: {text}".format(text=self.decode(data)))
|
||||
elif block_key == VERSE:
|
||||
self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Verse],
|
||||
number=verse_no))
|
||||
elif block_key == CHORUS:
|
||||
self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Chorus],
|
||||
number=verse_no))
|
||||
elif block_key == BRIDGE:
|
||||
self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Bridge],
|
||||
number=verse_no))
|
||||
elif block_key == TOPIC:
|
||||
self.topics.append(self.decode(data))
|
||||
elif block_key == COMMENTS:
|
||||
self.comments = self.decode(data)
|
||||
elif block_key == VERSE_ORDER:
|
||||
verse_tag = self.to_openlp_verse_tag(self.decode(data), True)
|
||||
if verse_tag:
|
||||
if not isinstance(verse_tag, str):
|
||||
verse_tag = self.decode(verse_tag)
|
||||
self.ssp_verse_order_list.append(verse_tag)
|
||||
elif block_key == SONG_BOOK:
|
||||
self.song_book_name = self.decode(data)
|
||||
elif block_key == CUSTOM_VERSE:
|
||||
verse_tag = self.to_openlp_verse_tag(verse_name)
|
||||
self.add_verse(self.decode(data), verse_tag)
|
||||
else:
|
||||
log.debug("Unrecognised blockKey: {key}, data: {data}".format(key=block_key, data=data))
|
||||
song_data.seek(next_block_starts)
|
||||
self.verse_order_list = self.ssp_verse_order_list
|
||||
song_data.close()
|
||||
if not self.finish():
|
||||
self.log_error(file)
|
||||
length_descriptor, = struct.unpack("B", song_file.read(1))
|
||||
log.debug('length_descriptor: %d' % length_descriptor)
|
||||
data = song_file.read(length_descriptor)
|
||||
log.debug(data)
|
||||
if block_key == TITLE:
|
||||
self.title = self.decode(data)
|
||||
elif block_key == AUTHOR:
|
||||
authors = self.decode(data).split(" / ")
|
||||
for author in authors:
|
||||
if author.find(",") != -1:
|
||||
author_parts = author.split(", ")
|
||||
author = author_parts[1] + " " + author_parts[0]
|
||||
self.parse_author(author)
|
||||
elif block_key == COPYRIGHT:
|
||||
self.add_copyright(self.decode(data))
|
||||
elif block_key == CCLI_NO:
|
||||
# Try to get the CCLI number even if the field contains additional text
|
||||
match = re.search(r'\d+', self.decode(data))
|
||||
if match:
|
||||
self.ccli_number = int(match.group())
|
||||
else:
|
||||
log.warning("Can't parse CCLI Number from string: {text}".format(text=self.decode(data)))
|
||||
elif block_key == VERSE:
|
||||
self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Verse],
|
||||
number=verse_no))
|
||||
elif block_key == CHORUS:
|
||||
self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Chorus],
|
||||
number=verse_no))
|
||||
elif block_key == BRIDGE:
|
||||
self.add_verse(self.decode(data), "{tag}{number}".format(tag=VerseType.tags[VerseType.Bridge],
|
||||
number=verse_no))
|
||||
elif block_key == TOPIC:
|
||||
self.topics.append(self.decode(data))
|
||||
elif block_key == COMMENTS:
|
||||
self.comments = self.decode(data)
|
||||
elif block_key == VERSE_ORDER:
|
||||
verse_tag = self.to_openlp_verse_tag(self.decode(data), True)
|
||||
if verse_tag:
|
||||
if not isinstance(verse_tag, str):
|
||||
verse_tag = self.decode(verse_tag)
|
||||
self.ssp_verse_order_list.append(verse_tag)
|
||||
elif block_key == SONG_BOOK:
|
||||
self.song_book_name = self.decode(data)
|
||||
elif block_key == CUSTOM_VERSE:
|
||||
verse_tag = self.to_openlp_verse_tag(verse_name)
|
||||
self.add_verse(self.decode(data), verse_tag)
|
||||
else:
|
||||
log.debug("Unrecognised blockKey: {key}, data: {data}".format(key=block_key, data=data))
|
||||
song_file.seek(next_block_starts)
|
||||
self.verse_order_list = self.ssp_verse_order_list
|
||||
if not self.finish():
|
||||
self.log_error(file_path)
|
||||
|
||||
def to_openlp_verse_tag(self, verse_name, ignore_unique=False):
|
||||
"""
|
||||
|
@ -19,11 +19,8 @@
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
|
||||
|
||||
from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding
|
||||
from openlp.plugins.songs.lib import strip_rtf
|
||||
@ -60,12 +57,11 @@ class SundayPlusImport(SongImport):
|
||||
|
||||
def do_import(self):
|
||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||
for filename in self.import_source:
|
||||
for file_path in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
song_file = open(filename, 'rb')
|
||||
self.do_import_file(song_file)
|
||||
song_file.close()
|
||||
with file_path.open('rb') as song_file:
|
||||
self.do_import_file(song_file)
|
||||
|
||||
def do_import_file(self, file):
|
||||
"""
|
||||
|
@ -22,13 +22,13 @@
|
||||
"""
|
||||
The :mod:`lyrix` module provides the functionality for importing songs which are
|
||||
exported from Lyrix."""
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.common.path import Path
|
||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||
from openlp.plugins.songs.lib.db import AuthorType
|
||||
|
||||
@ -50,11 +50,10 @@ class VideoPsalmImport(SongImport):
|
||||
"""
|
||||
Process the VideoPsalm file - pass in a file-like object, not a file path.
|
||||
"""
|
||||
self.import_source = Path(self.import_source)
|
||||
self.set_defaults()
|
||||
# Open SongBook file
|
||||
song_file = open(self.import_source, 'rt', encoding='utf-8-sig')
|
||||
try:
|
||||
file_content = song_file.read()
|
||||
file_content = self.import_source.read_text(encoding='utf-8-sig')
|
||||
processed_content = ''
|
||||
inside_quotes = False
|
||||
# The VideoPsalm format is not valid json, it uses illegal line breaks and unquoted keys, this must be fixed
|
||||
@ -89,7 +88,7 @@ class VideoPsalmImport(SongImport):
|
||||
songs = songbook['Songs']
|
||||
self.import_wizard.progress_bar.setMaximum(len(songs))
|
||||
songbook_name = songbook['Text']
|
||||
media_folder = os.path.normpath(os.path.join(os.path.dirname(song_file.name), '..', 'Audio'))
|
||||
media_path = Path('..', 'Audio')
|
||||
for song in songs:
|
||||
self.song_book_name = songbook_name
|
||||
if 'Text' in song:
|
||||
@ -114,7 +113,7 @@ class VideoPsalmImport(SongImport):
|
||||
if 'Theme' in song:
|
||||
self.topics = song['Theme'].splitlines()
|
||||
if 'AudioFile' in song:
|
||||
self.add_media_file(os.path.join(media_folder, song['AudioFile']))
|
||||
self.add_media_file(media_path / song['AudioFile'])
|
||||
if 'Memo1' in song:
|
||||
self.add_comment(song['Memo1'])
|
||||
if 'Memo2' in song:
|
||||
@ -132,4 +131,5 @@ class VideoPsalmImport(SongImport):
|
||||
if not self.finish():
|
||||
self.log_error('Could not import {title}'.format(title=self.title))
|
||||
except Exception as e:
|
||||
self.log_error(song_file.name, translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e))
|
||||
self.log_error(self.import_source.name,
|
||||
translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e))
|
||||
|
@ -25,6 +25,7 @@ Worship songs into the OpenLP database.
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
from openlp.core.common.path import Path
|
||||
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||
@ -100,62 +101,60 @@ class WordsOfWorshipImport(SongImport):
|
||||
"""
|
||||
if isinstance(self.import_source, list):
|
||||
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
|
||||
for source in self.import_source:
|
||||
for file_path in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.set_defaults()
|
||||
song_data = open(source, 'rb')
|
||||
if song_data.read(19).decode() != 'WoW File\nSong Words':
|
||||
self.log_error(source,
|
||||
translate('SongsPlugin.WordsofWorshipSongImport',
|
||||
'Invalid Words of Worship song file. Missing "{text}" '
|
||||
'header.').format(text='WoW File\\nSong Words'))
|
||||
continue
|
||||
# Seek to byte which stores number of blocks in the song
|
||||
song_data.seek(56)
|
||||
no_of_blocks = ord(song_data.read(1))
|
||||
song_data.seek(66)
|
||||
if song_data.read(16).decode() != 'CSongDoc::CBlock':
|
||||
self.log_error(source,
|
||||
translate('SongsPlugin.WordsofWorshipSongImport',
|
||||
'Invalid Words of Worship song file. Missing "{text}" '
|
||||
'string.').format(text='CSongDoc::CBlock'))
|
||||
continue
|
||||
# Seek to the beginning of the first block
|
||||
song_data.seek(82)
|
||||
for block in range(no_of_blocks):
|
||||
skip_char_at_end = True
|
||||
self.lines_to_read = ord(song_data.read(4)[:1])
|
||||
block_text = ''
|
||||
while self.lines_to_read:
|
||||
self.line_text = str(song_data.read(ord(song_data.read(1))), 'cp1252')
|
||||
if skip_char_at_end:
|
||||
skip_char = ord(song_data.read(1))
|
||||
# Check if we really should skip a char. In some wsg files we shouldn't
|
||||
if skip_char != 0:
|
||||
song_data.seek(-1, os.SEEK_CUR)
|
||||
skip_char_at_end = False
|
||||
if block_text:
|
||||
block_text += '\n'
|
||||
block_text += self.line_text
|
||||
self.lines_to_read -= 1
|
||||
block_type = BLOCK_TYPES[ord(song_data.read(4)[:1])]
|
||||
# Blocks are separated by 2 bytes, skip them, but not if
|
||||
# this is the last block!
|
||||
if block + 1 < no_of_blocks:
|
||||
song_data.seek(2, os.SEEK_CUR)
|
||||
self.add_verse(block_text, block_type)
|
||||
# Now to extract the author
|
||||
author_length = ord(song_data.read(1))
|
||||
if author_length:
|
||||
self.parse_author(str(song_data.read(author_length), 'cp1252'))
|
||||
# Finally the copyright
|
||||
copyright_length = ord(song_data.read(1))
|
||||
if copyright_length:
|
||||
self.add_copyright(str(song_data.read(copyright_length), 'cp1252'))
|
||||
file_name = os.path.split(source)[1]
|
||||
# Get the song title
|
||||
self.title = file_name.rpartition('.')[0]
|
||||
song_data.close()
|
||||
if not self.finish():
|
||||
self.log_error(source)
|
||||
with file_path.open('rb') as song_data:
|
||||
if song_data.read(19).decode() != 'WoW File\nSong Words':
|
||||
self.log_error(file_path,
|
||||
translate('SongsPlugin.WordsofWorshipSongImport',
|
||||
'Invalid Words of Worship song file. Missing "{text}" '
|
||||
'header.').format(text='WoW File\\nSong Words'))
|
||||
continue
|
||||
# Seek to byte which stores number of blocks in the song
|
||||
song_data.seek(56)
|
||||
no_of_blocks = ord(song_data.read(1))
|
||||
song_data.seek(66)
|
||||
if song_data.read(16).decode() != 'CSongDoc::CBlock':
|
||||
self.log_error(file_path,
|
||||
translate('SongsPlugin.WordsofWorshipSongImport',
|
||||
'Invalid Words of Worship song file. Missing "{text}" '
|
||||
'string.').format(text='CSongDoc::CBlock'))
|
||||
continue
|
||||
# Seek to the beginning of the first block
|
||||
song_data.seek(82)
|
||||
for block in range(no_of_blocks):
|
||||
skip_char_at_end = True
|
||||
self.lines_to_read = ord(song_data.read(4)[:1])
|
||||
block_text = ''
|
||||
while self.lines_to_read:
|
||||
self.line_text = str(song_data.read(ord(song_data.read(1))), 'cp1252')
|
||||
if skip_char_at_end:
|
||||
skip_char = ord(song_data.read(1))
|
||||
# Check if we really should skip a char. In some wsg files we shouldn't
|
||||
if skip_char != 0:
|
||||
song_data.seek(-1, os.SEEK_CUR)
|
||||
skip_char_at_end = False
|
||||
if block_text:
|
||||
block_text += '\n'
|
||||
block_text += self.line_text
|
||||
self.lines_to_read -= 1
|
||||
block_type = BLOCK_TYPES[ord(song_data.read(4)[:1])]
|
||||
# Blocks are separated by 2 bytes, skip them, but not if
|
||||
# this is the last block!
|
||||
if block + 1 < no_of_blocks:
|
||||
song_data.seek(2, os.SEEK_CUR)
|
||||
self.add_verse(block_text, block_type)
|
||||
# Now to extract the author
|
||||
author_length = ord(song_data.read(1))
|
||||
if author_length:
|
||||
self.parse_author(str(song_data.read(author_length), 'cp1252'))
|
||||
# Finally the copyright
|
||||
copyright_length = ord(song_data.read(1))
|
||||
if copyright_length:
|
||||
self.add_copyright(str(song_data.read(copyright_length), 'cp1252'))
|
||||
# Get the song title
|
||||
self.title = file_path.stem
|
||||
if not self.finish():
|
||||
self.log_error(file_path)
|
||||
|
@ -23,11 +23,11 @@
|
||||
The :mod:`worshipassistant` module provides the functionality for importing
|
||||
Worship Assistant songs into the OpenLP database.
|
||||
"""
|
||||
import chardet
|
||||
import csv
|
||||
import logging
|
||||
import re
|
||||
|
||||
from openlp.core.common import get_file_encoding
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.plugins.songs.lib import VerseType
|
||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||
@ -81,19 +81,16 @@ class WorshipAssistantImport(SongImport):
|
||||
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 {number:d}: {error}').format(number=songs_reader.line_num, error=e))
|
||||
return
|
||||
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)
|
||||
@ -185,4 +182,3 @@ class WorshipAssistantImport(SongImport):
|
||||
self.log_error(translate('SongsPlugin.WorshipAssistantImport',
|
||||
'Record {count:d}').format(count=index) +
|
||||
(': "' + self.title + '"' if self.title else ''))
|
||||
songs_file.close()
|
||||
|
@ -76,7 +76,7 @@ class ZionWorxImport(SongImport):
|
||||
Receive a CSV file (from a ZionWorx database dump) to import.
|
||||
"""
|
||||
# Encoding should always be ISO-8859-1
|
||||
with open(self.import_source, 'rt', encoding='ISO-8859-1') as songs_file:
|
||||
with self.import_source.open('rt', encoding='ISO-8859-1') as songs_file:
|
||||
field_names = ['SongNum', 'Title1', 'Title2', 'Lyrics', 'Writer', 'Copyright', 'Keywords',
|
||||
'DefaultStyle']
|
||||
songs_reader = csv.DictReader(songs_file, field_names)
|
||||
|
@ -19,30 +19,28 @@
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
from sqlalchemy.sql import and_, or_
|
||||
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.i18n import UiStrings, translate, get_natural_key
|
||||
from openlp.core.common.path import Path, create_paths
|
||||
from openlp.core.common.path import copyfile, create_paths
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \
|
||||
check_item_selected, create_separated_list
|
||||
from openlp.core.lib.ui import create_widget_action
|
||||
from openlp.plugins.songs.forms.editsongform import EditSongForm
|
||||
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
|
||||
from openlp.plugins.songs.forms.songimportform import SongImportForm
|
||||
from openlp.plugins.songs.forms.songexportform import SongExportForm
|
||||
from openlp.plugins.songs.forms.songimportform import SongImportForm
|
||||
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
|
||||
from openlp.plugins.songs.lib import VerseType, clean_string, delete_song
|
||||
from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry, Topic
|
||||
from openlp.plugins.songs.lib.ui import SongStrings
|
||||
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML
|
||||
from openlp.plugins.songs.lib.ui import SongStrings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -90,11 +88,11 @@ class SongMediaItem(MediaManagerItem):
|
||||
def _update_background_audio(self, song, item):
|
||||
song.media_files = []
|
||||
for i, bga in enumerate(item.background_audio):
|
||||
dest_file = os.path.join(
|
||||
str(AppLocation.get_section_data_path(self.plugin.name)), 'audio', str(song.id), os.path.split(bga)[1])
|
||||
create_paths(Path(os.path.split(dest_file)[0]))
|
||||
shutil.copyfile(os.path.join(str(AppLocation.get_section_data_path('servicemanager')), bga), dest_file)
|
||||
song.media_files.append(MediaFile.populate(weight=i, file_name=dest_file))
|
||||
dest_path =\
|
||||
AppLocation.get_section_data_path(self.plugin.name) / 'audio' / str(song.id) / os.path.split(bga)[1]
|
||||
create_paths(dest_path.parent)
|
||||
copyfile(AppLocation.get_section_data_path('servicemanager') / bga, dest_path)
|
||||
song.media_files.append(MediaFile.populate(weight=i, file_path=dest_path))
|
||||
self.plugin.manager.save_object(song, True)
|
||||
|
||||
def add_end_header_bar(self):
|
||||
@ -536,14 +534,13 @@ class SongMediaItem(MediaManagerItem):
|
||||
'copy', 'For song cloning'))
|
||||
# Copy audio files from the old to the new song
|
||||
if len(old_song.media_files) > 0:
|
||||
save_path = os.path.join(
|
||||
str(AppLocation.get_section_data_path(self.plugin.name)), 'audio', str(new_song.id))
|
||||
create_paths(Path(save_path))
|
||||
save_path = AppLocation.get_section_data_path(self.plugin.name) / 'audio' / str(new_song.id)
|
||||
create_paths(save_path)
|
||||
for media_file in old_song.media_files:
|
||||
new_media_file_name = os.path.join(save_path, os.path.basename(media_file.file_name))
|
||||
shutil.copyfile(media_file.file_name, new_media_file_name)
|
||||
new_media_file_path = save_path / media_file.file_path.name
|
||||
copyfile(media_file.file_path, new_media_file_path)
|
||||
new_media_file = MediaFile()
|
||||
new_media_file.file_name = new_media_file_name
|
||||
new_media_file.file_path = new_media_file_path
|
||||
new_media_file.type = media_file.type
|
||||
new_media_file.weight = media_file.weight
|
||||
new_song.media_files.append(new_media_file)
|
||||
@ -581,7 +578,7 @@ class SongMediaItem(MediaManagerItem):
|
||||
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.
|
||||
# single char tags.
|
||||
verse_tag = verse[0]['type']
|
||||
verse_index = None
|
||||
if len(verse_tag) > 1:
|
||||
@ -592,7 +589,9 @@ class SongMediaItem(MediaManagerItem):
|
||||
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)
|
||||
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():
|
||||
@ -607,7 +606,9 @@ class SongMediaItem(MediaManagerItem):
|
||||
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'])
|
||||
service_item.add_from_text(verse[1], verse_def)
|
||||
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)}
|
||||
@ -615,7 +616,7 @@ class SongMediaItem(MediaManagerItem):
|
||||
# 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]
|
||||
service_item.background_audio = [m.file_path for m in song.media_files]
|
||||
return True
|
||||
|
||||
def generate_footer(self, item, song):
|
||||
|
@ -24,13 +24,12 @@ The :mod:`openlyricsexport` module provides the functionality for exporting song
|
||||
format.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from openlp.core.common import clean_filename
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.common.path import Path, create_paths
|
||||
from openlp.core.common.path import create_paths
|
||||
from openlp.core.common.registry import RegistryProperties
|
||||
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics
|
||||
|
||||
@ -44,13 +43,16 @@ class OpenLyricsExport(RegistryProperties):
|
||||
def __init__(self, parent, songs, save_path):
|
||||
"""
|
||||
Initialise the export.
|
||||
|
||||
:param openlp.core.common.path.Path save_path: The directory to save the exported songs in
|
||||
:rtype: None
|
||||
"""
|
||||
log.debug('initialise OpenLyricsExport')
|
||||
self.parent = parent
|
||||
self.manager = parent.plugin.manager
|
||||
self.songs = songs
|
||||
self.save_path = save_path
|
||||
create_paths(Path(self.save_path))
|
||||
create_paths(self.save_path)
|
||||
|
||||
def do_export(self):
|
||||
"""
|
||||
@ -71,15 +73,15 @@ class OpenLyricsExport(RegistryProperties):
|
||||
author=', '.join([author.display_name for author in song.authors]))
|
||||
filename = clean_filename(filename)
|
||||
# Ensure the filename isn't too long for some filesystems
|
||||
filename_with_ext = '{name}.xml'.format(name=filename[0:250 - len(self.save_path)])
|
||||
path_length = len(str(self.save_path))
|
||||
filename_with_ext = '{name}.xml'.format(name=filename[0:250 - path_length])
|
||||
# Make sure we're not overwriting an existing file
|
||||
conflicts = 0
|
||||
while os.path.exists(os.path.join(self.save_path, filename_with_ext)):
|
||||
while (self.save_path / filename_with_ext).exists():
|
||||
conflicts += 1
|
||||
filename_with_ext = '{name}-{extra}.xml'.format(name=filename[0:247 - len(self.save_path)],
|
||||
extra=conflicts)
|
||||
filename_with_ext = '{name}-{extra}.xml'.format(name=filename[0:247 - path_length], extra=conflicts)
|
||||
# Pass a file object, because lxml does not cope with some special
|
||||
# characters in the path (see lp:757673 and lp:744337).
|
||||
tree.write(open(os.path.join(self.save_path, filename_with_ext), 'wb'), encoding='utf-8',
|
||||
xml_declaration=True, pretty_print=True)
|
||||
with (self.save_path / filename_with_ext).open('wb') as out_file:
|
||||
tree.write(out_file, encoding='utf-8', xml_declaration=True, pretty_print=True)
|
||||
return True
|
||||
|
@ -72,6 +72,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
NAMESPACE = 'http://openlyrics.info/namespace/2009/song'
|
||||
NSMAP = '{{' + NAMESPACE + '}}{tag}'
|
||||
NEWPAGETAG = '<p style="page-break-after: always;"/>'
|
||||
|
||||
|
||||
class SongXML(object):
|
||||
@ -282,7 +283,7 @@ class OpenLyrics(object):
|
||||
tags_element = None
|
||||
match = re.search('\{/?\w+\}', song.lyrics, re.UNICODE)
|
||||
if match:
|
||||
# Named 'format_' - 'format' is built-in fuction in Python.
|
||||
# Named 'format_' - 'format' is built-in function in Python.
|
||||
format_ = etree.SubElement(song_xml, 'format')
|
||||
tags_element = etree.SubElement(format_, 'tags')
|
||||
tags_element.set('application', 'OpenLP')
|
||||
@ -473,6 +474,7 @@ class OpenLyrics(object):
|
||||
text = text.replace('{{/{tag}}}'.format(tag=tag), '</tag>')
|
||||
# Replace \n with <br/>.
|
||||
text = text.replace('\n', '<br/>')
|
||||
text = text.replace('[--}{--]', NEWPAGETAG)
|
||||
element = etree.XML('<lines>{text}</lines>'.format(text=text))
|
||||
verse_element.append(element)
|
||||
return element
|
||||
@ -635,6 +637,9 @@ class OpenLyrics(object):
|
||||
if element.tail:
|
||||
text += element.tail
|
||||
return text
|
||||
elif newlines and element.tag == NSMAP.format(tag='p') and 'page-break-after' in str(element.attrib):
|
||||
text += '[--}{--]'
|
||||
return text
|
||||
# Start formatting tag.
|
||||
if element.tag == NSMAP.format(tag='tag'):
|
||||
text += '{{{name}}}'.format(name=element.get('name'))
|
||||
|
@ -23,16 +23,20 @@
|
||||
The :mod:`upgrade` module provides a way for the database and schema that is the
|
||||
backend for the Songs plugin
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
|
||||
from sqlalchemy import Table, Column, ForeignKey, types
|
||||
from sqlalchemy.sql.expression import func, false, null, text
|
||||
|
||||
from openlp.core.common import AppLocation
|
||||
from openlp.core.common.db import drop_columns
|
||||
from openlp.core.lib.db import get_upgrade_op
|
||||
from openlp.core.common.json import OpenLPJsonEncoder
|
||||
from openlp.core.common.path import Path
|
||||
from openlp.core.lib.db import PathType, get_upgrade_op
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
__version__ = 6
|
||||
__version__ = 7
|
||||
|
||||
|
||||
# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version
|
||||
@ -162,3 +166,28 @@ def upgrade_6(session, metadata):
|
||||
op.drop_column('songs', 'song_number')
|
||||
# Finally, clean up our mess in people's databases
|
||||
op.execute('DELETE FROM songs_songbooks WHERE songbook_id = 0')
|
||||
|
||||
|
||||
def upgrade_7(session, metadata):
|
||||
"""
|
||||
Version 7 upgrade - Move file path from old db to JSON encoded path to new db. Upgrade added in 2.5 dev
|
||||
"""
|
||||
log.debug('Starting upgrade_7 for file_path to JSON')
|
||||
old_table = Table('media_files', metadata, autoload=True)
|
||||
if 'file_path' not in [col.name for col in old_table.c.values()]:
|
||||
op = get_upgrade_op(session)
|
||||
op.add_column('media_files', Column('file_path', PathType()))
|
||||
conn = op.get_bind()
|
||||
results = conn.execute('SELECT * FROM media_files')
|
||||
data_path = AppLocation.get_data_path()
|
||||
for row in results.fetchall():
|
||||
file_path_json = json.dumps(Path(row.file_name), cls=OpenLPJsonEncoder, base_path=data_path)
|
||||
sql = 'UPDATE media_files SET file_path = \'{file_path_json}\' WHERE id = {id}'.format(
|
||||
file_path_json=file_path_json, id=row.id)
|
||||
conn.execute(sql)
|
||||
# Drop old columns
|
||||
if metadata.bind.url.get_dialect().name == 'sqlite':
|
||||
drop_columns(op, 'media_files', ['file_name', ])
|
||||
else:
|
||||
op.drop_constraint('media_files', 'foreignkey')
|
||||
op.drop_column('media_files', 'filenames')
|
||||
|
@ -32,7 +32,6 @@ from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
from openlp.plugins.songs.lib.db import Song
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -59,9 +58,9 @@ def report_song_list():
|
||||
report_file_path.with_suffix('.csv')
|
||||
Registry().get('application').set_busy_cursor()
|
||||
try:
|
||||
with report_file_path.open('wt') as file_handle:
|
||||
with report_file_path.open('wt') as export_file:
|
||||
fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic')
|
||||
writer = csv.DictWriter(file_handle, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
|
||||
writer = csv.DictWriter(export_file, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
|
||||
headers = dict((n, n) for n in fieldnames)
|
||||
writer.writerow(headers)
|
||||
song_list = plugin.manager.get_all_objects(Song)
|
||||
|
@ -38,7 +38,6 @@ from openlp.core.common.registry import Registry
|
||||
from openlp.core.lib import Plugin, StringContent, build_icon
|
||||
from openlp.core.lib.db import Manager
|
||||
from openlp.core.lib.ui import create_action
|
||||
|
||||
from openlp.plugins.songs import reporting
|
||||
from openlp.plugins.songs.endpoint import api_songs_endpoint, songs_endpoint
|
||||
from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm
|
||||
@ -51,7 +50,6 @@ from openlp.plugins.songs.lib.mediaitem import SongMediaItem
|
||||
from openlp.plugins.songs.lib.mediaitem import SongSearch
|
||||
from openlp.plugins.songs.lib.songstab import SongsTab
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
__default_settings__ = {
|
||||
'songs/db type': 'sqlite',
|
||||
@ -341,7 +339,7 @@ class SongsPlugin(Plugin):
|
||||
progress.forceShow()
|
||||
self.application.process_events()
|
||||
for db in song_dbs:
|
||||
importer = OpenLPSongImport(self.manager, filename=db)
|
||||
importer = OpenLPSongImport(self.manager, file_path=db)
|
||||
importer.do_import(progress)
|
||||
self.application.process_events()
|
||||
progress.setValue(song_count)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 766 B |
446
resources/images/app_qr.svg
Normal file
446
resources/images/app_qr.svg
Normal file
@ -0,0 +1,446 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" baseProfile="full" width="296" height="296" viewBox="0 0 296 296"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events">
|
||||
<desc></desc>
|
||||
<rect width="296" height="296" fill="#ffffff" cx="0" cy="0" />
|
||||
<defs>
|
||||
<rect id="p" width="8" height="8" />
|
||||
</defs>
|
||||
<g fill="#000000">
|
||||
<use x="32" y="32" xlink:href="#p" />
|
||||
<use x="32" y="40" xlink:href="#p" />
|
||||
<use x="32" y="48" xlink:href="#p" />
|
||||
<use x="32" y="56" xlink:href="#p" />
|
||||
<use x="32" y="64" xlink:href="#p" />
|
||||
<use x="32" y="72" xlink:href="#p" />
|
||||
<use x="32" y="80" xlink:href="#p" />
|
||||
<use x="32" y="96" xlink:href="#p" />
|
||||
<use x="32" y="104" xlink:href="#p" />
|
||||
<use x="32" y="120" xlink:href="#p" />
|
||||
<use x="32" y="128" xlink:href="#p" />
|
||||
<use x="32" y="168" xlink:href="#p" />
|
||||
<use x="32" y="184" xlink:href="#p" />
|
||||
<use x="32" y="192" xlink:href="#p" />
|
||||
<use x="32" y="208" xlink:href="#p" />
|
||||
<use x="32" y="216" xlink:href="#p" />
|
||||
<use x="32" y="224" xlink:href="#p" />
|
||||
<use x="32" y="232" xlink:href="#p" />
|
||||
<use x="32" y="240" xlink:href="#p" />
|
||||
<use x="32" y="248" xlink:href="#p" />
|
||||
<use x="32" y="256" xlink:href="#p" />
|
||||
<use x="40" y="32" xlink:href="#p" />
|
||||
<use x="40" y="80" xlink:href="#p" />
|
||||
<use x="40" y="96" xlink:href="#p" />
|
||||
<use x="40" y="104" xlink:href="#p" />
|
||||
<use x="40" y="120" xlink:href="#p" />
|
||||
<use x="40" y="136" xlink:href="#p" />
|
||||
<use x="40" y="152" xlink:href="#p" />
|
||||
<use x="40" y="160" xlink:href="#p" />
|
||||
<use x="40" y="176" xlink:href="#p" />
|
||||
<use x="40" y="184" xlink:href="#p" />
|
||||
<use x="40" y="208" xlink:href="#p" />
|
||||
<use x="40" y="256" xlink:href="#p" />
|
||||
<use x="48" y="32" xlink:href="#p" />
|
||||
<use x="48" y="48" xlink:href="#p" />
|
||||
<use x="48" y="56" xlink:href="#p" />
|
||||
<use x="48" y="64" xlink:href="#p" />
|
||||
<use x="48" y="80" xlink:href="#p" />
|
||||
<use x="48" y="96" xlink:href="#p" />
|
||||
<use x="48" y="104" xlink:href="#p" />
|
||||
<use x="48" y="112" xlink:href="#p" />
|
||||
<use x="48" y="120" xlink:href="#p" />
|
||||
<use x="48" y="128" xlink:href="#p" />
|
||||
<use x="48" y="136" xlink:href="#p" />
|
||||
<use x="48" y="152" xlink:href="#p" />
|
||||
<use x="48" y="160" xlink:href="#p" />
|
||||
<use x="48" y="184" xlink:href="#p" />
|
||||
<use x="48" y="192" xlink:href="#p" />
|
||||
<use x="48" y="208" xlink:href="#p" />
|
||||
<use x="48" y="224" xlink:href="#p" />
|
||||
<use x="48" y="232" xlink:href="#p" />
|
||||
<use x="48" y="240" xlink:href="#p" />
|
||||
<use x="48" y="256" xlink:href="#p" />
|
||||
<use x="56" y="32" xlink:href="#p" />
|
||||
<use x="56" y="48" xlink:href="#p" />
|
||||
<use x="56" y="56" xlink:href="#p" />
|
||||
<use x="56" y="64" xlink:href="#p" />
|
||||
<use x="56" y="80" xlink:href="#p" />
|
||||
<use x="56" y="96" xlink:href="#p" />
|
||||
<use x="56" y="112" xlink:href="#p" />
|
||||
<use x="56" y="128" xlink:href="#p" />
|
||||
<use x="56" y="136" xlink:href="#p" />
|
||||
<use x="56" y="176" xlink:href="#p" />
|
||||
<use x="56" y="192" xlink:href="#p" />
|
||||
<use x="56" y="208" xlink:href="#p" />
|
||||
<use x="56" y="224" xlink:href="#p" />
|
||||
<use x="56" y="232" xlink:href="#p" />
|
||||
<use x="56" y="240" xlink:href="#p" />
|
||||
<use x="56" y="256" xlink:href="#p" />
|
||||
<use x="64" y="32" xlink:href="#p" />
|
||||
<use x="64" y="48" xlink:href="#p" />
|
||||
<use x="64" y="56" xlink:href="#p" />
|
||||
<use x="64" y="64" xlink:href="#p" />
|
||||
<use x="64" y="80" xlink:href="#p" />
|
||||
<use x="64" y="120" xlink:href="#p" />
|
||||
<use x="64" y="128" xlink:href="#p" />
|
||||
<use x="64" y="136" xlink:href="#p" />
|
||||
<use x="64" y="152" xlink:href="#p" />
|
||||
<use x="64" y="168" xlink:href="#p" />
|
||||
<use x="64" y="176" xlink:href="#p" />
|
||||
<use x="64" y="208" xlink:href="#p" />
|
||||
<use x="64" y="224" xlink:href="#p" />
|
||||
<use x="64" y="232" xlink:href="#p" />
|
||||
<use x="64" y="240" xlink:href="#p" />
|
||||
<use x="64" y="256" xlink:href="#p" />
|
||||
<use x="72" y="32" xlink:href="#p" />
|
||||
<use x="72" y="80" xlink:href="#p" />
|
||||
<use x="72" y="120" xlink:href="#p" />
|
||||
<use x="72" y="136" xlink:href="#p" />
|
||||
<use x="72" y="160" xlink:href="#p" />
|
||||
<use x="72" y="176" xlink:href="#p" />
|
||||
<use x="72" y="208" xlink:href="#p" />
|
||||
<use x="72" y="256" xlink:href="#p" />
|
||||
<use x="80" y="32" xlink:href="#p" />
|
||||
<use x="80" y="40" xlink:href="#p" />
|
||||
<use x="80" y="48" xlink:href="#p" />
|
||||
<use x="80" y="56" xlink:href="#p" />
|
||||
<use x="80" y="64" xlink:href="#p" />
|
||||
<use x="80" y="72" xlink:href="#p" />
|
||||
<use x="80" y="80" xlink:href="#p" />
|
||||
<use x="80" y="96" xlink:href="#p" />
|
||||
<use x="80" y="112" xlink:href="#p" />
|
||||
<use x="80" y="128" xlink:href="#p" />
|
||||
<use x="80" y="144" xlink:href="#p" />
|
||||
<use x="80" y="160" xlink:href="#p" />
|
||||
<use x="80" y="176" xlink:href="#p" />
|
||||
<use x="80" y="192" xlink:href="#p" />
|
||||
<use x="80" y="208" xlink:href="#p" />
|
||||
<use x="80" y="216" xlink:href="#p" />
|
||||
<use x="80" y="224" xlink:href="#p" />
|
||||
<use x="80" y="232" xlink:href="#p" />
|
||||
<use x="80" y="240" xlink:href="#p" />
|
||||
<use x="80" y="248" xlink:href="#p" />
|
||||
<use x="80" y="256" xlink:href="#p" />
|
||||
<use x="88" y="112" xlink:href="#p" />
|
||||
<use x="88" y="120" xlink:href="#p" />
|
||||
<use x="88" y="160" xlink:href="#p" />
|
||||
<use x="88" y="168" xlink:href="#p" />
|
||||
<use x="88" y="192" xlink:href="#p" />
|
||||
<use x="96" y="32" xlink:href="#p" />
|
||||
<use x="96" y="48" xlink:href="#p" />
|
||||
<use x="96" y="56" xlink:href="#p" />
|
||||
<use x="96" y="64" xlink:href="#p" />
|
||||
<use x="96" y="80" xlink:href="#p" />
|
||||
<use x="96" y="96" xlink:href="#p" />
|
||||
<use x="96" y="112" xlink:href="#p" />
|
||||
<use x="96" y="120" xlink:href="#p" />
|
||||
<use x="96" y="136" xlink:href="#p" />
|
||||
<use x="96" y="152" xlink:href="#p" />
|
||||
<use x="96" y="168" xlink:href="#p" />
|
||||
<use x="96" y="176" xlink:href="#p" />
|
||||
<use x="96" y="200" xlink:href="#p" />
|
||||
<use x="96" y="232" xlink:href="#p" />
|
||||
<use x="96" y="240" xlink:href="#p" />
|
||||
<use x="96" y="248" xlink:href="#p" />
|
||||
<use x="96" y="256" xlink:href="#p" />
|
||||
<use x="104" y="40" xlink:href="#p" />
|
||||
<use x="104" y="48" xlink:href="#p" />
|
||||
<use x="104" y="56" xlink:href="#p" />
|
||||
<use x="104" y="112" xlink:href="#p" />
|
||||
<use x="104" y="120" xlink:href="#p" />
|
||||
<use x="104" y="128" xlink:href="#p" />
|
||||
<use x="104" y="168" xlink:href="#p" />
|
||||
<use x="104" y="176" xlink:href="#p" />
|
||||
<use x="104" y="192" xlink:href="#p" />
|
||||
<use x="104" y="200" xlink:href="#p" />
|
||||
<use x="104" y="208" xlink:href="#p" />
|
||||
<use x="104" y="216" xlink:href="#p" />
|
||||
<use x="104" y="224" xlink:href="#p" />
|
||||
<use x="104" y="232" xlink:href="#p" />
|
||||
<use x="104" y="240" xlink:href="#p" />
|
||||
<use x="104" y="248" xlink:href="#p" />
|
||||
<use x="104" y="256" xlink:href="#p" />
|
||||
<use x="112" y="32" xlink:href="#p" />
|
||||
<use x="112" y="40" xlink:href="#p" />
|
||||
<use x="112" y="48" xlink:href="#p" />
|
||||
<use x="112" y="56" xlink:href="#p" />
|
||||
<use x="112" y="72" xlink:href="#p" />
|
||||
<use x="112" y="80" xlink:href="#p" />
|
||||
<use x="112" y="88" xlink:href="#p" />
|
||||
<use x="112" y="96" xlink:href="#p" />
|
||||
<use x="112" y="104" xlink:href="#p" />
|
||||
<use x="112" y="112" xlink:href="#p" />
|
||||
<use x="112" y="120" xlink:href="#p" />
|
||||
<use x="112" y="128" xlink:href="#p" />
|
||||
<use x="112" y="200" xlink:href="#p" />
|
||||
<use x="112" y="208" xlink:href="#p" />
|
||||
<use x="112" y="216" xlink:href="#p" />
|
||||
<use x="112" y="232" xlink:href="#p" />
|
||||
<use x="112" y="240" xlink:href="#p" />
|
||||
<use x="120" y="40" xlink:href="#p" />
|
||||
<use x="120" y="64" xlink:href="#p" />
|
||||
<use x="120" y="72" xlink:href="#p" />
|
||||
<use x="120" y="96" xlink:href="#p" />
|
||||
<use x="120" y="112" xlink:href="#p" />
|
||||
<use x="120" y="136" xlink:href="#p" />
|
||||
<use x="120" y="144" xlink:href="#p" />
|
||||
<use x="120" y="152" xlink:href="#p" />
|
||||
<use x="120" y="184" xlink:href="#p" />
|
||||
<use x="120" y="216" xlink:href="#p" />
|
||||
<use x="120" y="224" xlink:href="#p" />
|
||||
<use x="120" y="232" xlink:href="#p" />
|
||||
<use x="120" y="256" xlink:href="#p" />
|
||||
<use x="128" y="32" xlink:href="#p" />
|
||||
<use x="128" y="40" xlink:href="#p" />
|
||||
<use x="128" y="56" xlink:href="#p" />
|
||||
<use x="128" y="64" xlink:href="#p" />
|
||||
<use x="128" y="80" xlink:href="#p" />
|
||||
<use x="128" y="88" xlink:href="#p" />
|
||||
<use x="128" y="96" xlink:href="#p" />
|
||||
<use x="128" y="104" xlink:href="#p" />
|
||||
<use x="128" y="112" xlink:href="#p" />
|
||||
<use x="128" y="128" xlink:href="#p" />
|
||||
<use x="128" y="136" xlink:href="#p" />
|
||||
<use x="128" y="160" xlink:href="#p" />
|
||||
<use x="128" y="216" xlink:href="#p" />
|
||||
<use x="128" y="240" xlink:href="#p" />
|
||||
<use x="128" y="248" xlink:href="#p" />
|
||||
<use x="136" y="32" xlink:href="#p" />
|
||||
<use x="136" y="40" xlink:href="#p" />
|
||||
<use x="136" y="48" xlink:href="#p" />
|
||||
<use x="136" y="56" xlink:href="#p" />
|
||||
<use x="136" y="72" xlink:href="#p" />
|
||||
<use x="136" y="120" xlink:href="#p" />
|
||||
<use x="136" y="128" xlink:href="#p" />
|
||||
<use x="136" y="176" xlink:href="#p" />
|
||||
<use x="136" y="184" xlink:href="#p" />
|
||||
<use x="136" y="192" xlink:href="#p" />
|
||||
<use x="136" y="216" xlink:href="#p" />
|
||||
<use x="136" y="232" xlink:href="#p" />
|
||||
<use x="136" y="248" xlink:href="#p" />
|
||||
<use x="144" y="48" xlink:href="#p" />
|
||||
<use x="144" y="56" xlink:href="#p" />
|
||||
<use x="144" y="64" xlink:href="#p" />
|
||||
<use x="144" y="80" xlink:href="#p" />
|
||||
<use x="144" y="96" xlink:href="#p" />
|
||||
<use x="144" y="128" xlink:href="#p" />
|
||||
<use x="144" y="152" xlink:href="#p" />
|
||||
<use x="144" y="160" xlink:href="#p" />
|
||||
<use x="144" y="176" xlink:href="#p" />
|
||||
<use x="144" y="184" xlink:href="#p" />
|
||||
<use x="144" y="192" xlink:href="#p" />
|
||||
<use x="144" y="208" xlink:href="#p" />
|
||||
<use x="144" y="216" xlink:href="#p" />
|
||||
<use x="144" y="240" xlink:href="#p" />
|
||||
<use x="152" y="64" xlink:href="#p" />
|
||||
<use x="152" y="88" xlink:href="#p" />
|
||||
<use x="152" y="96" xlink:href="#p" />
|
||||
<use x="152" y="120" xlink:href="#p" />
|
||||
<use x="152" y="128" xlink:href="#p" />
|
||||
<use x="152" y="136" xlink:href="#p" />
|
||||
<use x="152" y="160" xlink:href="#p" />
|
||||
<use x="152" y="168" xlink:href="#p" />
|
||||
<use x="152" y="176" xlink:href="#p" />
|
||||
<use x="152" y="232" xlink:href="#p" />
|
||||
<use x="152" y="248" xlink:href="#p" />
|
||||
<use x="160" y="32" xlink:href="#p" />
|
||||
<use x="160" y="64" xlink:href="#p" />
|
||||
<use x="160" y="72" xlink:href="#p" />
|
||||
<use x="160" y="80" xlink:href="#p" />
|
||||
<use x="160" y="88" xlink:href="#p" />
|
||||
<use x="160" y="96" xlink:href="#p" />
|
||||
<use x="160" y="104" xlink:href="#p" />
|
||||
<use x="160" y="112" xlink:href="#p" />
|
||||
<use x="160" y="128" xlink:href="#p" />
|
||||
<use x="160" y="136" xlink:href="#p" />
|
||||
<use x="160" y="152" xlink:href="#p" />
|
||||
<use x="160" y="184" xlink:href="#p" />
|
||||
<use x="160" y="192" xlink:href="#p" />
|
||||
<use x="160" y="200" xlink:href="#p" />
|
||||
<use x="160" y="208" xlink:href="#p" />
|
||||
<use x="160" y="240" xlink:href="#p" />
|
||||
<use x="160" y="256" xlink:href="#p" />
|
||||
<use x="168" y="56" xlink:href="#p" />
|
||||
<use x="168" y="88" xlink:href="#p" />
|
||||
<use x="168" y="96" xlink:href="#p" />
|
||||
<use x="168" y="104" xlink:href="#p" />
|
||||
<use x="168" y="112" xlink:href="#p" />
|
||||
<use x="168" y="128" xlink:href="#p" />
|
||||
<use x="168" y="136" xlink:href="#p" />
|
||||
<use x="168" y="168" xlink:href="#p" />
|
||||
<use x="168" y="184" xlink:href="#p" />
|
||||
<use x="168" y="192" xlink:href="#p" />
|
||||
<use x="168" y="208" xlink:href="#p" />
|
||||
<use x="168" y="224" xlink:href="#p" />
|
||||
<use x="168" y="232" xlink:href="#p" />
|
||||
<use x="176" y="32" xlink:href="#p" />
|
||||
<use x="176" y="80" xlink:href="#p" />
|
||||
<use x="176" y="88" xlink:href="#p" />
|
||||
<use x="176" y="96" xlink:href="#p" />
|
||||
<use x="176" y="104" xlink:href="#p" />
|
||||
<use x="176" y="112" xlink:href="#p" />
|
||||
<use x="176" y="144" xlink:href="#p" />
|
||||
<use x="176" y="184" xlink:href="#p" />
|
||||
<use x="176" y="192" xlink:href="#p" />
|
||||
<use x="176" y="200" xlink:href="#p" />
|
||||
<use x="176" y="216" xlink:href="#p" />
|
||||
<use x="176" y="232" xlink:href="#p" />
|
||||
<use x="176" y="248" xlink:href="#p" />
|
||||
<use x="176" y="256" xlink:href="#p" />
|
||||
<use x="184" y="32" xlink:href="#p" />
|
||||
<use x="184" y="56" xlink:href="#p" />
|
||||
<use x="184" y="64" xlink:href="#p" />
|
||||
<use x="184" y="72" xlink:href="#p" />
|
||||
<use x="184" y="104" xlink:href="#p" />
|
||||
<use x="184" y="136" xlink:href="#p" />
|
||||
<use x="184" y="144" xlink:href="#p" />
|
||||
<use x="184" y="152" xlink:href="#p" />
|
||||
<use x="184" y="160" xlink:href="#p" />
|
||||
<use x="184" y="184" xlink:href="#p" />
|
||||
<use x="184" y="192" xlink:href="#p" />
|
||||
<use x="184" y="208" xlink:href="#p" />
|
||||
<use x="184" y="240" xlink:href="#p" />
|
||||
<use x="184" y="248" xlink:href="#p" />
|
||||
<use x="184" y="256" xlink:href="#p" />
|
||||
<use x="192" y="32" xlink:href="#p" />
|
||||
<use x="192" y="48" xlink:href="#p" />
|
||||
<use x="192" y="72" xlink:href="#p" />
|
||||
<use x="192" y="80" xlink:href="#p" />
|
||||
<use x="192" y="104" xlink:href="#p" />
|
||||
<use x="192" y="136" xlink:href="#p" />
|
||||
<use x="192" y="144" xlink:href="#p" />
|
||||
<use x="192" y="152" xlink:href="#p" />
|
||||
<use x="192" y="160" xlink:href="#p" />
|
||||
<use x="192" y="176" xlink:href="#p" />
|
||||
<use x="192" y="192" xlink:href="#p" />
|
||||
<use x="192" y="200" xlink:href="#p" />
|
||||
<use x="192" y="208" xlink:href="#p" />
|
||||
<use x="192" y="216" xlink:href="#p" />
|
||||
<use x="192" y="224" xlink:href="#p" />
|
||||
<use x="192" y="248" xlink:href="#p" />
|
||||
<use x="192" y="256" xlink:href="#p" />
|
||||
<use x="200" y="96" xlink:href="#p" />
|
||||
<use x="200" y="104" xlink:href="#p" />
|
||||
<use x="200" y="112" xlink:href="#p" />
|
||||
<use x="200" y="136" xlink:href="#p" />
|
||||
<use x="200" y="152" xlink:href="#p" />
|
||||
<use x="200" y="192" xlink:href="#p" />
|
||||
<use x="200" y="224" xlink:href="#p" />
|
||||
<use x="200" y="232" xlink:href="#p" />
|
||||
<use x="200" y="256" xlink:href="#p" />
|
||||
<use x="208" y="32" xlink:href="#p" />
|
||||
<use x="208" y="40" xlink:href="#p" />
|
||||
<use x="208" y="48" xlink:href="#p" />
|
||||
<use x="208" y="56" xlink:href="#p" />
|
||||
<use x="208" y="64" xlink:href="#p" />
|
||||
<use x="208" y="72" xlink:href="#p" />
|
||||
<use x="208" y="80" xlink:href="#p" />
|
||||
<use x="208" y="104" xlink:href="#p" />
|
||||
<use x="208" y="112" xlink:href="#p" />
|
||||
<use x="208" y="120" xlink:href="#p" />
|
||||
<use x="208" y="136" xlink:href="#p" />
|
||||
<use x="208" y="144" xlink:href="#p" />
|
||||
<use x="208" y="176" xlink:href="#p" />
|
||||
<use x="208" y="184" xlink:href="#p" />
|
||||
<use x="208" y="192" xlink:href="#p" />
|
||||
<use x="208" y="208" xlink:href="#p" />
|
||||
<use x="208" y="224" xlink:href="#p" />
|
||||
<use x="208" y="248" xlink:href="#p" />
|
||||
<use x="208" y="256" xlink:href="#p" />
|
||||
<use x="216" y="32" xlink:href="#p" />
|
||||
<use x="216" y="80" xlink:href="#p" />
|
||||
<use x="216" y="104" xlink:href="#p" />
|
||||
<use x="216" y="112" xlink:href="#p" />
|
||||
<use x="216" y="120" xlink:href="#p" />
|
||||
<use x="216" y="144" xlink:href="#p" />
|
||||
<use x="216" y="152" xlink:href="#p" />
|
||||
<use x="216" y="160" xlink:href="#p" />
|
||||
<use x="216" y="168" xlink:href="#p" />
|
||||
<use x="216" y="184" xlink:href="#p" />
|
||||
<use x="216" y="192" xlink:href="#p" />
|
||||
<use x="216" y="224" xlink:href="#p" />
|
||||
<use x="216" y="240" xlink:href="#p" />
|
||||
<use x="216" y="248" xlink:href="#p" />
|
||||
<use x="224" y="32" xlink:href="#p" />
|
||||
<use x="224" y="48" xlink:href="#p" />
|
||||
<use x="224" y="56" xlink:href="#p" />
|
||||
<use x="224" y="64" xlink:href="#p" />
|
||||
<use x="224" y="80" xlink:href="#p" />
|
||||
<use x="224" y="96" xlink:href="#p" />
|
||||
<use x="224" y="104" xlink:href="#p" />
|
||||
<use x="224" y="112" xlink:href="#p" />
|
||||
<use x="224" y="120" xlink:href="#p" />
|
||||
<use x="224" y="152" xlink:href="#p" />
|
||||
<use x="224" y="160" xlink:href="#p" />
|
||||
<use x="224" y="176" xlink:href="#p" />
|
||||
<use x="224" y="192" xlink:href="#p" />
|
||||
<use x="224" y="200" xlink:href="#p" />
|
||||
<use x="224" y="208" xlink:href="#p" />
|
||||
<use x="224" y="216" xlink:href="#p" />
|
||||
<use x="224" y="224" xlink:href="#p" />
|
||||
<use x="224" y="232" xlink:href="#p" />
|
||||
<use x="224" y="256" xlink:href="#p" />
|
||||
<use x="232" y="32" xlink:href="#p" />
|
||||
<use x="232" y="48" xlink:href="#p" />
|
||||
<use x="232" y="56" xlink:href="#p" />
|
||||
<use x="232" y="64" xlink:href="#p" />
|
||||
<use x="232" y="80" xlink:href="#p" />
|
||||
<use x="232" y="96" xlink:href="#p" />
|
||||
<use x="232" y="128" xlink:href="#p" />
|
||||
<use x="232" y="160" xlink:href="#p" />
|
||||
<use x="232" y="168" xlink:href="#p" />
|
||||
<use x="232" y="192" xlink:href="#p" />
|
||||
<use x="232" y="200" xlink:href="#p" />
|
||||
<use x="232" y="208" xlink:href="#p" />
|
||||
<use x="232" y="216" xlink:href="#p" />
|
||||
<use x="232" y="232" xlink:href="#p" />
|
||||
<use x="232" y="248" xlink:href="#p" />
|
||||
<use x="240" y="32" xlink:href="#p" />
|
||||
<use x="240" y="48" xlink:href="#p" />
|
||||
<use x="240" y="56" xlink:href="#p" />
|
||||
<use x="240" y="64" xlink:href="#p" />
|
||||
<use x="240" y="80" xlink:href="#p" />
|
||||
<use x="240" y="96" xlink:href="#p" />
|
||||
<use x="240" y="112" xlink:href="#p" />
|
||||
<use x="240" y="128" xlink:href="#p" />
|
||||
<use x="240" y="136" xlink:href="#p" />
|
||||
<use x="240" y="144" xlink:href="#p" />
|
||||
<use x="240" y="168" xlink:href="#p" />
|
||||
<use x="240" y="176" xlink:href="#p" />
|
||||
<use x="240" y="184" xlink:href="#p" />
|
||||
<use x="240" y="192" xlink:href="#p" />
|
||||
<use x="240" y="200" xlink:href="#p" />
|
||||
<use x="240" y="224" xlink:href="#p" />
|
||||
<use x="240" y="240" xlink:href="#p" />
|
||||
<use x="248" y="32" xlink:href="#p" />
|
||||
<use x="248" y="80" xlink:href="#p" />
|
||||
<use x="248" y="112" xlink:href="#p" />
|
||||
<use x="248" y="136" xlink:href="#p" />
|
||||
<use x="248" y="144" xlink:href="#p" />
|
||||
<use x="248" y="152" xlink:href="#p" />
|
||||
<use x="248" y="160" xlink:href="#p" />
|
||||
<use x="248" y="168" xlink:href="#p" />
|
||||
<use x="248" y="200" xlink:href="#p" />
|
||||
<use x="248" y="208" xlink:href="#p" />
|
||||
<use x="248" y="224" xlink:href="#p" />
|
||||
<use x="248" y="248" xlink:href="#p" />
|
||||
<use x="248" y="256" xlink:href="#p" />
|
||||
<use x="256" y="32" xlink:href="#p" />
|
||||
<use x="256" y="40" xlink:href="#p" />
|
||||
<use x="256" y="48" xlink:href="#p" />
|
||||
<use x="256" y="56" xlink:href="#p" />
|
||||
<use x="256" y="64" xlink:href="#p" />
|
||||
<use x="256" y="72" xlink:href="#p" />
|
||||
<use x="256" y="80" xlink:href="#p" />
|
||||
<use x="256" y="96" xlink:href="#p" />
|
||||
<use x="256" y="104" xlink:href="#p" />
|
||||
<use x="256" y="120" xlink:href="#p" />
|
||||
<use x="256" y="136" xlink:href="#p" />
|
||||
<use x="256" y="144" xlink:href="#p" />
|
||||
<use x="256" y="200" xlink:href="#p" />
|
||||
<use x="256" y="216" xlink:href="#p" />
|
||||
<use x="256" y="224" xlink:href="#p" />
|
||||
<use x="256" y="232" xlink:href="#p" />
|
||||
<use x="256" y="240" xlink:href="#p" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
Before Width: | Height: | Size: 758 B |
@ -183,7 +183,6 @@
|
||||
<file>projector_warmup.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="remotes">
|
||||
<file>android_app_qr.png</file>
|
||||
<file>ios_app_qr.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
<file>app_qr.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
@ -80,5 +80,6 @@ class TestImageDBUpgrade(TestCase, TestMixin):
|
||||
2: Path('/', 'test', 'dir', 'image2.jpg'),
|
||||
3: Path('/', 'test', 'dir', 'subdir', 'image3.jpg')}
|
||||
|
||||
self.assertEqual(len(upgraded_results), 3)
|
||||
for result in upgraded_results:
|
||||
self.assertEqual(expected_result_data[result.id], result.file_path)
|
||||
|
@ -24,6 +24,8 @@ This module contains tests for the OpenSong song importer.
|
||||
"""
|
||||
import os
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
@ -48,5 +50,5 @@ class TestChordProFileImport(SongImportTestHelper):
|
||||
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
|
||||
mocked_settings.return_value = mocked_returned_settings
|
||||
# Do the test import
|
||||
self.file_import([os.path.join(TEST_PATH, 'swing-low.chordpro')],
|
||||
self.file_import([Path(TEST_PATH, 'swing-low.chordpro')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'swing-low.json')))
|
||||
|
@ -21,9 +21,10 @@
|
||||
"""
|
||||
This module contains tests for the EasySlides song importer.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
|
||||
TEST_PATH = os.path.abspath(
|
||||
@ -41,7 +42,7 @@ class TestEasySlidesFileImport(SongImportTestHelper):
|
||||
"""
|
||||
Test that loading an EasySlides file works correctly on various files
|
||||
"""
|
||||
self.file_import(os.path.join(TEST_PATH, 'amazing-grace.xml'),
|
||||
self.file_import(Path(TEST_PATH, 'amazing-grace.xml'),
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
self.file_import(os.path.join(TEST_PATH, 'Export_2017-01-12_BB.xml'),
|
||||
self.file_import(Path(TEST_PATH, 'Export_2017-01-12_BB.xml'),
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Export_2017-01-12_BB.json')))
|
||||
|
@ -72,3 +72,31 @@ class TestEditVerseForm(TestCase, TestMixin):
|
||||
|
||||
# THEN the verse number must not be changed
|
||||
self.assertEqual(3, self.edit_verse_form.verse_number_box.value(), 'The verse number should be 3')
|
||||
|
||||
def test_on_divide_split_button_clicked(self):
|
||||
"""
|
||||
Test that divide adds text at the correct position
|
||||
"""
|
||||
# GIVEN some input values
|
||||
self.edit_verse_form.verse_type_combo_box.currentIndex = MagicMock(return_value=4)
|
||||
self.edit_verse_form.verse_text_edit.setPlainText('Text\n')
|
||||
|
||||
# WHEN the method is called
|
||||
self.edit_verse_form.on_forced_split_button_clicked()
|
||||
# THEN the verse number must not be changed
|
||||
self.assertEqual('[--}{--]\nText\n', self.edit_verse_form.verse_text_edit.toPlainText(),
|
||||
'The verse number should be [--}{--]\nText\n')
|
||||
|
||||
def test_on_split_button_clicked(self):
|
||||
"""
|
||||
Test that divide adds text at the correct position
|
||||
"""
|
||||
# GIVEN some input values
|
||||
self.edit_verse_form.verse_type_combo_box.currentIndex = MagicMock(return_value=4)
|
||||
self.edit_verse_form.verse_text_edit.setPlainText('Text\n')
|
||||
|
||||
# WHEN the method is called
|
||||
self.edit_verse_form.on_overflow_split_button_clicked()
|
||||
# THEN the verse number must not be changed
|
||||
self.assertEqual('[---]\nText\n', self.edit_verse_form.verse_text_edit.toPlainText(),
|
||||
'The verse number should be [---]\nText\n')
|
||||
|
@ -97,7 +97,7 @@ class EasyWorshipSongImportLogger(EasyWorshipSongImport):
|
||||
_title_assignment_list = []
|
||||
|
||||
def __init__(self, manager):
|
||||
EasyWorshipSongImport.__init__(self, manager, filenames=[])
|
||||
EasyWorshipSongImport.__init__(self, manager, file_paths=[])
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
@ -180,7 +180,7 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
||||
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||
|
||||
# THEN: The importer object should not be None
|
||||
self.assertIsNotNone(importer, 'Import should not be none')
|
||||
@ -192,7 +192,7 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions.
|
||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
||||
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||
importer.field_descriptions = TEST_FIELD_DESCS
|
||||
|
||||
# WHEN: Called with a field name that exists
|
||||
@ -210,7 +210,7 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions
|
||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
||||
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||
importer.field_descriptions = TEST_FIELD_DESCS
|
||||
|
||||
# WHEN: Called with a field name that does not exist
|
||||
@ -229,7 +229,7 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct:
|
||||
mocked_manager = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
||||
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||
|
||||
# WHEN: db_set_record_struct is called with a list of field descriptions
|
||||
return_value = importer.db_set_record_struct(TEST_FIELD_DESCS)
|
||||
@ -246,7 +246,7 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager", an encoding and some test data and known results
|
||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
||||
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||
importer.encoding = TEST_DATA_ENCODING
|
||||
importer.fields = TEST_FIELDS
|
||||
importer.field_descriptions = TEST_FIELD_DESCS
|
||||
@ -270,7 +270,7 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_memo_file = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
||||
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||
importer.memo_file = mocked_memo_file
|
||||
importer.encoding = TEST_DATA_ENCODING
|
||||
|
||||
@ -294,44 +294,25 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
else:
|
||||
mocked_memo_file.seek.assert_any_call(call[0], call[1])
|
||||
|
||||
def test_do_import_source(self):
|
||||
"""
|
||||
Test the :mod:`do_import` module opens the correct files
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path:
|
||||
mocked_manager = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
||||
mocked_os_path.isfile.side_effect = [True, False]
|
||||
|
||||
# WHEN: Supplied with an import source
|
||||
importer.import_source = 'Songs.DB'
|
||||
|
||||
# THEN: do_import should return None having called os.path.isfile
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None')
|
||||
mocked_os_path.isfile.assert_any_call('Songs.DB')
|
||||
mocked_os_path.isfile.assert_any_call('Songs.MB')
|
||||
|
||||
def test_do_import_source_invalid(self):
|
||||
"""
|
||||
Test the :mod:`do_import` module produces an error when Songs.MB not found.
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path:
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', side_effect=[True, False]):
|
||||
mocked_manager = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
||||
importer.log_error = MagicMock()
|
||||
mocked_os_path.isfile.side_effect = [True, False]
|
||||
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||
with patch.object(importer, 'log_error') as mocked_log_error:
|
||||
|
||||
# WHEN: do_import is supplied with an import source (Songs.MB missing)
|
||||
importer.import_source = 'Songs.DB'
|
||||
importer.do_import()
|
||||
# WHEN: do_import is supplied with an import source (Songs.MB missing)
|
||||
importer.import_source = 'Songs.DB'
|
||||
importer.do_import()
|
||||
|
||||
# THEN: do_import should have logged an error that the Songs.MB file could not be found.
|
||||
importer.log_error.assert_any_call(importer.import_source, 'Could not find the "Songs.MB" file. It must be '
|
||||
'in the same folder as the "Songs.DB" file.')
|
||||
# THEN: do_import should have logged an error that the Songs.MB file could not be found.
|
||||
mocked_log_error.assert_any_call(importer.import_source,
|
||||
'Could not find the "Songs.MB" file. It must be in the same folder as '
|
||||
'the "Songs.DB" file.')
|
||||
|
||||
def test_do_import_database_validity(self):
|
||||
"""
|
||||
@ -339,18 +320,19 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, os.path and a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path:
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True), \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat') as mocked_stat:
|
||||
|
||||
mocked_manager = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
||||
mocked_os_path.isfile.return_value = True
|
||||
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||
importer.import_source = 'Songs.DB'
|
||||
|
||||
# WHEN: DB file size is less than 0x800
|
||||
mocked_os_path.getsize.return_value = 0x7FF
|
||||
mocked_stat.return_value.st_size = 0x7FF
|
||||
|
||||
# THEN: do_import should return None having called os.path.isfile
|
||||
# THEN: do_import should return None having called Path.stat()
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None when db_size is less than 0x800')
|
||||
mocked_os_path.getsize.assert_any_call('Songs.DB')
|
||||
mocked_stat.assert_called_once_with()
|
||||
|
||||
def test_do_import_memo_validty(self):
|
||||
"""
|
||||
@ -358,13 +340,12 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path, \
|
||||
patch('builtins.open') as mocked_open, \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True), \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat', **{'return_value.st_size': 0x800}), \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.Path.open') as mocked_open, \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct:
|
||||
mocked_manager = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
||||
mocked_os_path.isfile.return_value = True
|
||||
mocked_os_path.getsize.return_value = 0x800
|
||||
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||
importer.import_source = 'Songs.DB'
|
||||
|
||||
# WHEN: Unpacking first 35 bytes of Memo file
|
||||
@ -385,14 +366,14 @@ class TestEasyWorshipSongImport(TestCase):
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.importers.easyworship.SongImport'), \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.os.path') as mocked_os_path, \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.Path.is_file', return_value=True), \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.Path.stat', **{'return_value.st_size': 0x800}), \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.Path.open'), \
|
||||
patch('builtins.open'), patch('openlp.plugins.songs.lib.importers.easyworship.struct') as mocked_struct, \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.retrieve_windows_encoding') as \
|
||||
patch('openlp.plugins.songs.lib.importers.easyworship.retrieve_windows_encoding') as \
|
||||
mocked_retrieve_windows_encoding:
|
||||
mocked_manager = MagicMock()
|
||||
importer = EasyWorshipSongImport(mocked_manager, filenames=[])
|
||||
mocked_os_path.isfile.return_value = True
|
||||
mocked_os_path.getsize.return_value = 0x800
|
||||
importer = EasyWorshipSongImport(mocked_manager, file_paths=[])
|
||||
importer.import_source = 'Songs.DB'
|
||||
|
||||
# WHEN: Unpacking the code page
|
||||
|
@ -22,7 +22,8 @@
|
||||
This module contains tests for the LyriX song importer.
|
||||
"""
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
|
||||
@ -41,9 +42,9 @@ class TestLyrixFileImport(SongImportTestHelper):
|
||||
"""
|
||||
Test that loading an LyriX file works correctly on various files
|
||||
"""
|
||||
self.file_import([os.path.join(TEST_PATH, 'A06.TXT')],
|
||||
self.file_import([Path(TEST_PATH, 'A06.TXT')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'A002.TXT')],
|
||||
self.file_import([Path(TEST_PATH, 'A002.TXT')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace2.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'AO05.TXT')],
|
||||
self.file_import([Path(TEST_PATH, 'AO05.TXT')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'in die regterhand.json')))
|
||||
|
@ -51,7 +51,7 @@ class TestMediaShoutImport(TestCase):
|
||||
"""
|
||||
# GIVEN: A MediaShoutImport class
|
||||
# WHEN: It is created
|
||||
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
|
||||
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
|
||||
|
||||
# THEN: It should not be None
|
||||
self.assertIsNotNone(importer)
|
||||
@ -62,7 +62,7 @@ class TestMediaShoutImport(TestCase):
|
||||
Test that do_import exits early when unable to connect to the database
|
||||
"""
|
||||
# GIVEN: A MediaShoutImport instance
|
||||
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
|
||||
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
|
||||
mocked_pyodbc.connect.side_effect = Exception('Unable to connect')
|
||||
|
||||
# WHEN: do_import is called
|
||||
@ -89,7 +89,7 @@ class TestMediaShoutImport(TestCase):
|
||||
group = GroupRecord('Hymns')
|
||||
|
||||
# GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out
|
||||
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
|
||||
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
|
||||
mocked_cursor = MagicMock()
|
||||
mocked_cursor.fetchall.side_effect = [[song], [verse], [play_order], [theme], [group]]
|
||||
mocked_cursor.tables.fetchone.return_value = True
|
||||
@ -124,7 +124,7 @@ class TestMediaShoutImport(TestCase):
|
||||
song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 1, '654321', '')
|
||||
|
||||
# GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out
|
||||
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
|
||||
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
|
||||
mocked_cursor = MagicMock()
|
||||
mocked_cursor.fetchall.return_value = [song]
|
||||
mocked_connection = MagicMock()
|
||||
@ -158,7 +158,7 @@ class TestMediaShoutImport(TestCase):
|
||||
play_order = PlayOrderRecord(0, 1, 1)
|
||||
theme = ThemeRecord('Grace')
|
||||
group = GroupRecord('Hymns')
|
||||
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
|
||||
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
|
||||
|
||||
# WHEN: A song is processed
|
||||
with patch.object(importer, 'set_defaults') as mocked_set_defaults, \
|
||||
@ -200,7 +200,7 @@ class TestMediaShoutImport(TestCase):
|
||||
play_order = PlayOrderRecord(0, 1, 1)
|
||||
theme = ThemeRecord('Grace')
|
||||
group = GroupRecord('Hymns')
|
||||
importer = MediaShoutImport(MagicMock(), filename='mediashout.db')
|
||||
importer = MediaShoutImport(MagicMock(), file_path='mediashout.db')
|
||||
|
||||
# WHEN: A song is processed
|
||||
with patch.object(importer, 'set_defaults') as mocked_set_defaults, \
|
||||
|
@ -48,7 +48,7 @@ class TestOpenLPImport(TestCase):
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = OpenLPSongImport(mocked_manager, filenames=[])
|
||||
importer = OpenLPSongImport(mocked_manager, file_paths=[])
|
||||
|
||||
# THEN: The importer object should not be None
|
||||
self.assertIsNotNone(importer, 'Import should not be none')
|
||||
@ -61,7 +61,7 @@ class TestOpenLPImport(TestCase):
|
||||
with patch('openlp.plugins.songs.lib.importers.openlp.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
importer = OpenLPSongImport(mocked_manager, filenames=[])
|
||||
importer = OpenLPSongImport(mocked_manager, file_paths=[])
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.stop_import_flag = True
|
||||
|
||||
|
@ -22,14 +22,14 @@
|
||||
"""
|
||||
This module contains tests for the OpenLyrics song importer.
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
from tempfile import mkdtemp
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.path import Path, rmtree
|
||||
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
|
||||
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
||||
@ -43,13 +43,13 @@ class TestOpenLyricsExport(TestCase, TestMixin):
|
||||
Create the registry
|
||||
"""
|
||||
Registry.create()
|
||||
self.temp_folder = mkdtemp()
|
||||
self.temp_folder = Path(mkdtemp())
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Cleanup
|
||||
"""
|
||||
shutil.rmtree(self.temp_folder)
|
||||
rmtree(self.temp_folder)
|
||||
|
||||
def test_export_same_filename(self):
|
||||
"""
|
||||
@ -73,7 +73,9 @@ class TestOpenLyricsExport(TestCase, TestMixin):
|
||||
ol_export.do_export()
|
||||
|
||||
# THEN: The exporter should have created 2 files
|
||||
self.assertTrue(os.path.exists(os.path.join(self.temp_folder,
|
||||
'%s (%s).xml' % (song.title, author.display_name))))
|
||||
self.assertTrue(os.path.exists(os.path.join(self.temp_folder,
|
||||
'%s (%s)-1.xml' % (song.title, author.display_name))))
|
||||
self.assertTrue((self.temp_folder /
|
||||
'{title} ({display_name}).xml'.format(
|
||||
title=song.title, display_name=author.display_name)).exists())
|
||||
self.assertTrue((self.temp_folder /
|
||||
'{title} ({display_name})-1.xml'.format(
|
||||
title=song.title, display_name=author.display_name)).exists())
|
||||
|
@ -29,6 +29,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
from lxml import etree, objectify
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.plugins.songs.lib.importers.openlyrics import OpenLyricsImport
|
||||
@ -110,7 +111,7 @@ class TestOpenLyricsImport(TestCase, TestMixin):
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = OpenLyricsImport(mocked_manager, filenames=[])
|
||||
importer = OpenLyricsImport(mocked_manager, file_paths=[])
|
||||
|
||||
# THEN: The importer should be an instance of SongImport
|
||||
self.assertIsInstance(importer, SongImport)
|
||||
@ -123,13 +124,13 @@ class TestOpenLyricsImport(TestCase, TestMixin):
|
||||
for song_file in SONG_TEST_DATA:
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
importer = OpenLyricsImport(mocked_manager, filenames=[])
|
||||
importer = OpenLyricsImport(mocked_manager, file_paths=[])
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.open_lyrics = MagicMock()
|
||||
importer.open_lyrics.xml_to_song = MagicMock()
|
||||
|
||||
# WHEN: Importing each file
|
||||
importer.import_source = [os.path.join(TEST_PATH, song_file)]
|
||||
importer.import_source = [Path(TEST_PATH, song_file)]
|
||||
importer.do_import()
|
||||
|
||||
# THEN: The xml_to_song() method should have been called
|
||||
|
@ -54,7 +54,7 @@ class TestOpenOfficeImport(TestCase, TestMixin):
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = OpenOfficeImport(mocked_manager, filenames=[])
|
||||
importer = OpenOfficeImport(mocked_manager, file_paths=[])
|
||||
|
||||
# THEN: The importer object should not be None
|
||||
self.assertIsNotNone(importer, 'Import should not be none')
|
||||
@ -66,7 +66,7 @@ class TestOpenOfficeImport(TestCase, TestMixin):
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a document that raises an exception
|
||||
mocked_manager = MagicMock()
|
||||
importer = OpenOfficeImport(mocked_manager, filenames=[])
|
||||
importer = OpenOfficeImport(mocked_manager, file_paths=[])
|
||||
importer.document = MagicMock()
|
||||
importer.document.close = MagicMock(side_effect=Exception())
|
||||
|
||||
|
@ -27,6 +27,7 @@ from unittest import TestCase
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.path import Path
|
||||
from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
@ -52,15 +53,15 @@ class TestOpenSongFileImport(SongImportTestHelper):
|
||||
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
|
||||
mocked_settings.return_value = mocked_returned_settings
|
||||
# Do the test import
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace')],
|
||||
self.file_import([Path(TEST_PATH, 'Amazing Grace')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer')],
|
||||
self.file_import([Path(TEST_PATH, 'Beautiful Garden Of Prayer')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'One, Two, Three, Four, Five')],
|
||||
self.file_import([Path(TEST_PATH, 'One, Two, Three, Four, Five')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'One, Two, Three, Four, Five.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace2')],
|
||||
self.file_import([Path(TEST_PATH, 'Amazing Grace2')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace with bad CCLI')],
|
||||
self.file_import([Path(TEST_PATH, 'Amazing Grace with bad CCLI')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace without CCLI.json')))
|
||||
|
||||
|
||||
@ -83,7 +84,7 @@ class TestOpenSongImport(TestCase):
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = OpenSongImport(mocked_manager, filenames=[])
|
||||
importer = OpenSongImport(mocked_manager, file_paths=[])
|
||||
|
||||
# THEN: The importer object should not be None
|
||||
self.assertIsNotNone(importer, 'Import should not be none')
|
||||
@ -96,7 +97,7 @@ class TestOpenSongImport(TestCase):
|
||||
with patch('openlp.plugins.songs.lib.importers.opensong.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
importer = OpenSongImport(mocked_manager, filenames=[])
|
||||
importer = OpenSongImport(mocked_manager, file_paths=[])
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.stop_import_flag = True
|
||||
|
||||
@ -117,7 +118,7 @@ class TestOpenSongImport(TestCase):
|
||||
with patch('openlp.plugins.songs.lib.importers.opensong.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
importer = OpenSongImport(mocked_manager, filenames=[])
|
||||
importer = OpenSongImport(mocked_manager, file_paths=[])
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.stop_import_flag = True
|
||||
|
||||
|
@ -86,7 +86,7 @@ class TestOpsProSongImport(TestCase):
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = OPSProImport(mocked_manager, filenames=[])
|
||||
importer = OPSProImport(mocked_manager, file_paths=[])
|
||||
|
||||
# THEN: The importer object should not be None
|
||||
self.assertIsNotNone(importer, 'Import should not be none')
|
||||
@ -98,7 +98,7 @@ class TestOpsProSongImport(TestCase):
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||
mocked_manager = MagicMock()
|
||||
importer = OPSProImport(mocked_manager, filenames=[])
|
||||
importer = OPSProImport(mocked_manager, file_paths=[])
|
||||
importer.finish = MagicMock()
|
||||
song, lyrics = _build_data('you are so faithfull.txt', False)
|
||||
|
||||
@ -118,7 +118,7 @@ class TestOpsProSongImport(TestCase):
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||
mocked_manager = MagicMock()
|
||||
importer = OPSProImport(mocked_manager, filenames=[])
|
||||
importer = OPSProImport(mocked_manager, file_paths=[])
|
||||
importer.finish = MagicMock()
|
||||
song, lyrics = _build_data('amazing grace.txt', False)
|
||||
|
||||
@ -138,7 +138,7 @@ class TestOpsProSongImport(TestCase):
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||
mocked_manager = MagicMock()
|
||||
importer = OPSProImport(mocked_manager, filenames=[])
|
||||
importer = OPSProImport(mocked_manager, file_paths=[])
|
||||
importer.finish = MagicMock()
|
||||
song, lyrics = _build_data('amazing grace2.txt', True)
|
||||
|
||||
@ -158,7 +158,7 @@ class TestOpsProSongImport(TestCase):
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||
mocked_manager = MagicMock()
|
||||
importer = OPSProImport(mocked_manager, filenames=[])
|
||||
importer = OPSProImport(mocked_manager, file_paths=[])
|
||||
importer.finish = MagicMock()
|
||||
song, lyrics = _build_data('amazing grace3.txt', True)
|
||||
|
||||
|
@ -23,11 +23,11 @@
|
||||
The :mod:`powerpraiseimport` module provides the functionality for importing
|
||||
ProPresenter song files into the current installation database.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
from openlp.core.common.registry import Registry
|
||||
|
||||
TEST_PATH = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'powerpraisesongs'))
|
||||
@ -44,7 +44,7 @@ class TestPowerPraiseFileImport(SongImportTestHelper):
|
||||
"""
|
||||
Test that loading a PowerPraise file works correctly
|
||||
"""
|
||||
self.file_import([os.path.join(TEST_PATH, 'Naher, mein Gott zu Dir.ppl')],
|
||||
self.file_import([Path(TEST_PATH, 'Naher, mein Gott zu Dir.ppl')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Naher, mein Gott zu Dir.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'You are so faithful.ppl')],
|
||||
self.file_import([Path(TEST_PATH, 'You are so faithful.ppl')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'You are so faithful.json')))
|
||||
|
@ -22,9 +22,10 @@
|
||||
"""
|
||||
This module contains tests for the PresentationManager song importer.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
|
||||
TEST_PATH = os.path.abspath(
|
||||
@ -42,7 +43,7 @@ class TestPresentationManagerFileImport(SongImportTestHelper):
|
||||
"""
|
||||
Test that loading a PresentationManager file works correctly
|
||||
"""
|
||||
self.file_import([os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.sng')],
|
||||
self.file_import([Path(TEST_PATH, 'Great Is Thy Faithfulness.sng')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.sng')],
|
||||
self.file_import([Path(TEST_PATH, 'Amazing Grace.sng')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
|
@ -23,9 +23,10 @@
|
||||
The :mod:`propresenterimport` module provides the functionality for importing
|
||||
ProPresenter song files into the current installation database.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
|
||||
TEST_PATH = os.path.abspath(
|
||||
@ -43,19 +44,19 @@ class TestProPresenterFileImport(SongImportTestHelper):
|
||||
"""
|
||||
Test that loading a ProPresenter 4 file works correctly
|
||||
"""
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro4')],
|
||||
self.file_import([Path(TEST_PATH, 'Amazing Grace.pro4')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
|
||||
def test_pro5_song_import(self):
|
||||
"""
|
||||
Test that loading a ProPresenter 5 file works correctly
|
||||
"""
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro5')],
|
||||
self.file_import([Path(TEST_PATH, 'Amazing Grace.pro5')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
|
||||
def test_pro6_song_import(self):
|
||||
"""
|
||||
Test that loading a ProPresenter 6 file works correctly
|
||||
"""
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro6')],
|
||||
self.file_import([Path(TEST_PATH, 'Amazing Grace.pro6')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
|
@ -26,8 +26,9 @@ import os
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from openlp.plugins.songs.lib.importers.songbeamer import SongBeamerImport, SongBeamerTypes
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.path import Path
|
||||
from openlp.plugins.songs.lib.importers.songbeamer import SongBeamerImport, SongBeamerTypes
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
|
||||
@ -51,18 +52,18 @@ class TestSongBeamerFileImport(SongImportTestHelper):
|
||||
mocked_returned_settings = MagicMock()
|
||||
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
|
||||
mocked_settings.return_value = mocked_returned_settings
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.sng')],
|
||||
self.file_import([Path(TEST_PATH, 'Amazing Grace.sng')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Lobsinget dem Herrn.sng')],
|
||||
self.file_import([Path(TEST_PATH, 'Lobsinget dem Herrn.sng')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Lobsinget dem Herrn.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'When I Call On You.sng')],
|
||||
self.file_import([Path(TEST_PATH, 'When I Call On You.sng')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'When I Call On You.json')))
|
||||
|
||||
def test_cp1252_encoded_file(self):
|
||||
"""
|
||||
Test that a CP1252 encoded file get's decoded properly.
|
||||
"""
|
||||
self.file_import([os.path.join(TEST_PATH, 'cp1252song.sng')],
|
||||
self.file_import([Path(TEST_PATH, 'cp1252song.sng')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'cp1252song.json')))
|
||||
|
||||
|
||||
@ -78,7 +79,7 @@ class TestSongBeamerImport(TestCase):
|
||||
self.song_import_patcher = patch('openlp.plugins.songs.lib.importers.songbeamer.SongImport')
|
||||
self.song_import_patcher.start()
|
||||
mocked_manager = MagicMock()
|
||||
self.importer = SongBeamerImport(mocked_manager, filenames=[])
|
||||
self.importer = SongBeamerImport(mocked_manager, file_paths=[])
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
@ -95,7 +96,7 @@ class TestSongBeamerImport(TestCase):
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = SongBeamerImport(mocked_manager, filenames=[])
|
||||
importer = SongBeamerImport(mocked_manager, file_paths=[])
|
||||
|
||||
# THEN: The importer object should not be None
|
||||
self.assertIsNotNone(importer, 'Import should not be none')
|
||||
|
@ -23,9 +23,10 @@
|
||||
The :mod:`songproimport` module provides the functionality for importing
|
||||
SongPro song files into the current installation database.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
|
||||
TEST_PATH = os.path.abspath(
|
||||
@ -43,5 +44,5 @@ class TestSongProFileImport(SongImportTestHelper):
|
||||
"""
|
||||
Test that loading an SongPro file works correctly
|
||||
"""
|
||||
self.file_import(os.path.join(TEST_PATH, 'amazing-grace.txt'),
|
||||
self.file_import(Path(TEST_PATH, 'amazing-grace.txt'),
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
|
@ -31,6 +31,7 @@ from urllib.error import URLError
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from openlp.core import Registry
|
||||
from openlp.core.common.path import Path
|
||||
from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker
|
||||
from openlp.plugins.songs.lib import Song
|
||||
from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGIN_PAGE, LOGOUT_URL, BASE_URL
|
||||
@ -810,15 +811,15 @@ class TestSongSelectFileImport(SongImportTestHelper):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.importer_class_name = 'CCLIFileImport'
|
||||
self.importer_module_name = 'cclifile'
|
||||
super(TestSongSelectFileImport, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def test_song_import(self):
|
||||
"""
|
||||
Test that loading an OpenSong file works correctly on various files
|
||||
"""
|
||||
self.file_import([os.path.join(TEST_PATH, 'TestSong.bin')],
|
||||
self.file_import([Path(TEST_PATH, 'TestSong.bin')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'TestSong-bin.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'TestSong.txt')],
|
||||
self.file_import([Path(TEST_PATH, 'TestSong.txt')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'TestSong-txt.json')))
|
||||
|
||||
|
||||
|
@ -26,6 +26,7 @@ import os
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
from openlp.plugins.songs.lib import VerseType
|
||||
from openlp.plugins.songs.lib.importers.songshowplus import SongShowPlusImport
|
||||
|
||||
@ -46,13 +47,13 @@ class TestSongShowPlusFileImport(SongImportTestHelper):
|
||||
"""
|
||||
Test that loading a SongShow Plus file works correctly on various files
|
||||
"""
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.sbsong')],
|
||||
self.file_import([Path(TEST_PATH, 'Amazing Grace.sbsong')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.sbsong')],
|
||||
self.file_import([Path(TEST_PATH, 'Beautiful Garden Of Prayer.sbsong')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'a mighty fortress is our god.sbsong')],
|
||||
self.file_import([Path(TEST_PATH, 'a mighty fortress is our god.sbsong')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'a mighty fortress is our god.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'cleanse-me.sbsong')],
|
||||
self.file_import([Path(TEST_PATH, 'cleanse-me.sbsong')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'cleanse-me.json')))
|
||||
|
||||
|
||||
@ -69,7 +70,7 @@ class TestSongShowPlusImport(TestCase):
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = SongShowPlusImport(mocked_manager, filenames=[])
|
||||
importer = SongShowPlusImport(mocked_manager, file_paths=[])
|
||||
|
||||
# THEN: The importer object should not be None
|
||||
self.assertIsNotNone(importer, 'Import should not be none')
|
||||
@ -82,7 +83,7 @@ class TestSongShowPlusImport(TestCase):
|
||||
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
importer = SongShowPlusImport(mocked_manager, filenames=[])
|
||||
importer = SongShowPlusImport(mocked_manager, file_paths=[])
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.stop_import_flag = True
|
||||
|
||||
@ -103,7 +104,7 @@ class TestSongShowPlusImport(TestCase):
|
||||
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
importer = SongShowPlusImport(mocked_manager, filenames=[])
|
||||
importer = SongShowPlusImport(mocked_manager, file_paths=[])
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.stop_import_flag = True
|
||||
|
||||
@ -123,7 +124,7 @@ class TestSongShowPlusImport(TestCase):
|
||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
importer = SongShowPlusImport(mocked_manager, filenames=[])
|
||||
importer = SongShowPlusImport(mocked_manager, file_paths=[])
|
||||
|
||||
# WHEN: Supplied with the following arguments replicating verses being added
|
||||
test_values = [
|
||||
@ -151,7 +152,7 @@ class TestSongShowPlusImport(TestCase):
|
||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.importers.songshowplus.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
importer = SongShowPlusImport(mocked_manager, filenames=[])
|
||||
importer = SongShowPlusImport(mocked_manager, file_paths=[])
|
||||
|
||||
# WHEN: Supplied with the following arguments replicating a verse order being added
|
||||
test_values = [
|
||||
|
@ -24,6 +24,8 @@ This module contains tests for the SundayPlus song importer.
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
|
||||
TEST_PATH = os.path.abspath(
|
||||
@ -44,5 +46,5 @@ class TestSundayPlusFileImport(SongImportTestHelper):
|
||||
with patch('openlp.plugins.songs.lib.importers.sundayplus.retrieve_windows_encoding') as \
|
||||
mocked_retrieve_windows_encoding:
|
||||
mocked_retrieve_windows_encoding.return_value = 'cp1252'
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.ptf')],
|
||||
self.file_import([Path(TEST_PATH, 'Amazing Grace.ptf')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
|
@ -21,9 +21,10 @@
|
||||
"""
|
||||
This module contains tests for the VideoPsalm song importer.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
@ -48,7 +49,7 @@ class TestVideoPsalmFileImport(SongImportTestHelper):
|
||||
mocked_returned_settings.value.side_effect = lambda value: True if value == 'songs/enable chords' else False
|
||||
mocked_settings.return_value = mocked_returned_settings
|
||||
# Do the test import
|
||||
self.file_import(os.path.join(TEST_PATH, 'videopsalm-as-safe-a-stronghold.json'),
|
||||
self.file_import(Path(TEST_PATH, 'videopsalm-as-safe-a-stronghold.json'),
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'as-safe-a-stronghold.json')))
|
||||
self.file_import(os.path.join(TEST_PATH, 'videopsalm-as-safe-a-stronghold2.json'),
|
||||
self.file_import(Path(TEST_PATH, 'videopsalm-as-safe-a-stronghold2.json'),
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'as-safe-a-stronghold2.json')))
|
||||
|
@ -22,9 +22,10 @@
|
||||
"""
|
||||
This module contains tests for the Words of Worship song importer.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
from openlp.plugins.songs.lib.importers.wordsofworship import WordsOfWorshipImport
|
||||
|
||||
@ -43,10 +44,10 @@ class TestWordsOfWorshipFileImport(SongImportTestHelper):
|
||||
"""
|
||||
Test that loading a Words of Worship file works correctly
|
||||
"""
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).wow-song')],
|
||||
self.file_import([Path(TEST_PATH, 'Amazing Grace (6 Verses).wow-song')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'When morning gilds the skies.wsg')],
|
||||
self.file_import([Path(TEST_PATH, 'When morning gilds the skies.wsg')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'When morning gilds the skies.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Holy Holy Holy Lord God Almighty.wow-song')],
|
||||
self.file_import([Path(TEST_PATH, 'Holy Holy Holy Lord God Almighty.wow-song')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH,
|
||||
'Holy Holy Holy Lord God Almighty.json')))
|
||||
|
@ -23,9 +23,10 @@
|
||||
The :mod:`worshipassistantimport` module provides the functionality for importing
|
||||
WorshipAssistant song files into the current installation database.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from openlp.core.common.path import Path
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
|
||||
TEST_PATH = os.path.abspath(
|
||||
@ -43,9 +44,9 @@ class TestWorshipAssistantFileImport(SongImportTestHelper):
|
||||
"""
|
||||
Test that loading an Worship Assistant file works correctly
|
||||
"""
|
||||
self.file_import(os.path.join(TEST_PATH, 'du_herr.csv'),
|
||||
self.file_import(Path(TEST_PATH, 'du_herr.csv'),
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'du_herr.json')))
|
||||
self.file_import(os.path.join(TEST_PATH, 'would_you_be_free.csv'),
|
||||
self.file_import(Path(TEST_PATH, 'would_you_be_free.csv'),
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
|
||||
self.file_import(os.path.join(TEST_PATH, 'would_you_be_free2.csv'),
|
||||
self.file_import(Path(TEST_PATH, 'would_you_be_free2.csv'),
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
|
||||
|
@ -55,7 +55,7 @@ if CAN_RUN_TESTS:
|
||||
_title_assignment_list = []
|
||||
|
||||
def __init__(self, manager):
|
||||
WorshipCenterProImport.__init__(self, manager, filenames=[])
|
||||
WorshipCenterProImport.__init__(self, manager, file_paths=[])
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
@ -153,7 +153,7 @@ class TestWorshipCenterProSongImport(TestCase):
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = WorshipCenterProImport(mocked_manager, filenames=[])
|
||||
importer = WorshipCenterProImport(mocked_manager, file_paths=[])
|
||||
|
||||
# THEN: The importer object should not be None
|
||||
self.assertIsNotNone(importer, 'Import should not be none')
|
||||
@ -170,7 +170,7 @@ class TestWorshipCenterProSongImport(TestCase):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_log_error = MagicMock()
|
||||
mocked_translate.return_value = 'Translated Text'
|
||||
importer = WorshipCenterProImport(mocked_manager, filenames=[])
|
||||
importer = WorshipCenterProImport(mocked_manager, file_paths=[])
|
||||
importer.log_error = mocked_log_error
|
||||
importer.import_source = 'import_source'
|
||||
pyodbc_errors = [pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError]
|
||||
|
@ -26,9 +26,10 @@ import os
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.path import Path
|
||||
from openlp.plugins.songs.lib.importers.zionworx import ZionWorxImport
|
||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||
from openlp.core.common.registry import Registry
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
|
||||
@ -55,7 +56,7 @@ class TestZionWorxImport(TestCase):
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = ZionWorxImport(mocked_manager, filenames=[])
|
||||
importer = ZionWorxImport(mocked_manager, file_paths=[])
|
||||
|
||||
# THEN: The importer should be an instance of SongImport
|
||||
self.assertIsInstance(importer, SongImport)
|
||||
@ -72,5 +73,5 @@ class TestZionWorxFileImport(SongImportTestHelper):
|
||||
"""
|
||||
Test that loading an ZionWorx file works correctly on various files
|
||||
"""
|
||||
self.file_import(os.path.join(TEST_PATH, 'zionworx.csv'),
|
||||
self.file_import(Path(TEST_PATH, 'zionworx.csv'),
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'zionworx.json')))
|
||||
|
@ -89,7 +89,7 @@ class SongImportTestHelper(TestCase):
|
||||
"""
|
||||
Import the given file and check that it has imported correctly
|
||||
"""
|
||||
importer = self.importer_class(self.mocked_manager, filenames=[source_file_name])
|
||||
importer = self.importer_class(self.mocked_manager, file_paths=[source_file_name])
|
||||
importer.import_wizard = self.mocked_import_wizard
|
||||
importer.stop_import_flag = False
|
||||
importer.topics = []
|
||||
|
Loading…
Reference in New Issue
Block a user