Head
@ -14,6 +14,8 @@ Contents:
|
||||
introduction
|
||||
glossary
|
||||
dualmonitors
|
||||
mediamanager
|
||||
songs
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
26
documentation/manual/source/mediamanager.rst
Normal file
@ -0,0 +1,26 @@
|
||||
=============
|
||||
Media Manager
|
||||
=============
|
||||
|
||||
Once you get your system set up for OpenLP you will be ready to add content to
|
||||
your setup. This will all happen through the **Media Manager**. The
|
||||
`Media Manager` contains all the bibles, songs, presentations, media, and
|
||||
everything else that you will project through OpenLP.
|
||||
|
||||
Enabling the Plugins
|
||||
--------------------
|
||||
|
||||
You may need to enable the plugins that came with OpenLP. As you can see below
|
||||
this is what the `Media Manager` looks like with all the plugins enabled.
|
||||
|
||||
.. image:: pics/mediamanager.png
|
||||
|
||||
To enable the plugins navigate to :menuselection:`Settings --> Plugins` or
|
||||
press `F7`. You will then want to click on the plugin to the left that you want
|
||||
to enable and select **active** from the drop down box to the right.
|
||||
|
||||
.. image:: pics/plugins.png
|
||||
|
||||
|
||||
Now you should be ready to add content to OpenLP check out the section of this
|
||||
guide on the individual plugins.
|
BIN
documentation/manual/source/pics/finishedimport.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
documentation/manual/source/pics/plugins.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
documentation/manual/source/pics/selectsongs.png
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
documentation/manual/source/pics/songimporter.png
Normal file
After Width: | Height: | Size: 138 KiB |
BIN
documentation/manual/source/pics/songimporterchoices.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
documentation/manual/source/pics/songselectlyrics.png
Normal file
After Width: | Height: | Size: 206 KiB |
BIN
documentation/manual/source/pics/songselectsongsearch.png
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
documentation/manual/source/pics/songusage.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
documentation/manual/source/pics/songusagedelete.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
documentation/manual/source/pics/songusagereport.png
Normal file
After Width: | Height: | Size: 55 KiB |
0
documentation/manual/source/pics/vistadisplaysettings.png
Executable file → Normal file
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
0
documentation/manual/source/pics/vistapersonalize.png
Executable file → Normal file
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
0
documentation/manual/source/pics/winsevendisplay.png
Executable file → Normal file
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 155 KiB |
0
documentation/manual/source/pics/winsevenresolution.png
Executable file → Normal file
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 24 KiB |
100
documentation/manual/source/songs.rst
Normal file
@ -0,0 +1,100 @@
|
||||
=====
|
||||
Songs
|
||||
=====
|
||||
|
||||
Managing your songs in OpenLP is a relatively simple process. There are also
|
||||
converters provided to get data from other formats into OpenLP.
|
||||
|
||||
Song Importer
|
||||
=============
|
||||
|
||||
If you are using an earlier version of OpenLP or come from another software
|
||||
package, you may be able to convert your existing database to work in OpenLP
|
||||
2.0. To access the Song Importer :menuselection:`File --> Import --> Song`.
|
||||
You will then see the Song Importer window, then click :guilabel:`Next`.
|
||||
|
||||
.. image:: pics/songimporter.png
|
||||
|
||||
After choosing :guilabel:`Next` you can then select from the various types of
|
||||
software that OpenLP will convert songs from.
|
||||
|
||||
.. image:: pics/songimporterchoices.png
|
||||
|
||||
Then click on the file folder icon to choose the file of the song database you
|
||||
want to import. See the following sections for information on the different
|
||||
formats that OpenLP will import.
|
||||
|
||||
Importing from OpenLP Version 1
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Converting from OpenLP Version 1 is a pretty simple process. You will first
|
||||
need to locate your version 1 database file.
|
||||
|
||||
Windows XP::
|
||||
|
||||
C:\Documents and Settings\All Users\Application Data\openlp.org\Data\songs.olp
|
||||
|
||||
Windows Vista / Windows 7::
|
||||
|
||||
C:\ProgramData\openlp.org\Data\songs.olp
|
||||
|
||||
After clicking :guilabel:`Next` your conversion should be complete.
|
||||
|
||||
.. image:: pics/finishedimport.png
|
||||
|
||||
Then press :guilabel:`Finish` and you should now be ready to use your OpenLP
|
||||
version one songs.
|
||||
|
||||
Importing from OpenSong
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Converting from OpenSong you will need to locate your songs database. In the
|
||||
later versions of OpenSong you are asked to define the location of this. The
|
||||
songs will be located in a folder named :guilabel:`Songs`. This folder should
|
||||
contain files with all your songs in them without a file extension. (file.xxx).
|
||||
When you have located this folder you will then need to select the songs from
|
||||
the folder.
|
||||
|
||||
.. image:: pics/selectsongs.png
|
||||
|
||||
On most operating systems to select all the songs, first select the first song
|
||||
in the lest then press shift and select the last song in the list. After this
|
||||
press :guilabel:`Next` and you should see that your import has been successful.
|
||||
|
||||
.. image:: pics/finishedimport.png
|
||||
|
||||
Press :guilabel:`Finish` and you will now be ready to use your songs imported
|
||||
from OpenSong.
|
||||
|
||||
Importing from CCLI Song Select
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To import from CCLI Song Select you must be a CCLI Subscriber and also a
|
||||
subscriber of the Song Select service. For info on that go to:
|
||||
http://www.ccli.com
|
||||
|
||||
The first step for importing from CCLI Song Select is to log into your account.
|
||||
Then search for your desired song. For this example we will be adding the song
|
||||
"Amazing Grace".
|
||||
|
||||
.. image:: pics/songselectsongsearch.png
|
||||
|
||||
For the song you are searching for select `lyrics` This should take you to a
|
||||
page displaying the lyrics and copyright info for your song.
|
||||
|
||||
.. image:: pics/songselectlyrics.png
|
||||
|
||||
Next, hover over the :guilabel:`Lyrics` menu from the upper right corner. Then
|
||||
choose either the .txt or .usr file. You will then be asked to chose a download
|
||||
location if your browser does not automatically select that for you. Select
|
||||
this file from the OpenLP import window and then click :guilabel:`Next` You can
|
||||
also select multiple songs for import at once on most operating systems by
|
||||
selecting the first item in the list then holding shift select the last item in
|
||||
the list. When finished you should see that your import has completed.
|
||||
|
||||
.. image:: pics/finishedimport.png
|
||||
|
||||
Press :guilabel:`Finish` and you will now be ready to use your songs imported
|
||||
from CCLI SongSelect.
|
||||
|
||||
|
@ -294,4 +294,5 @@ class Manager(object):
|
||||
"""
|
||||
if self.is_dirty:
|
||||
engine = create_engine(self.db_url)
|
||||
engine.execute("vacuum")
|
||||
if self.db_url.startswith(u'sqlite'):
|
||||
engine.execute("vacuum")
|
||||
|
@ -320,15 +320,9 @@ class MediaManagerItem(QtGui.QWidget):
|
||||
translate('OpenLP.MediaManagerItem',
|
||||
'&Add to selected Service Item'),
|
||||
self.onAddEditClick))
|
||||
if QtCore.QSettings().value(u'advanced/double click live',
|
||||
QtCore.QVariant(False)).toBool():
|
||||
QtCore.QObject.connect(self.listView,
|
||||
QtCore.SIGNAL(u'doubleClicked(QModelIndex)'),
|
||||
self.onLiveClick)
|
||||
else:
|
||||
QtCore.QObject.connect(self.listView,
|
||||
QtCore.SIGNAL(u'doubleClicked(QModelIndex)'),
|
||||
self.onPreviewClick)
|
||||
QtCore.QObject.connect(self.listView,
|
||||
QtCore.SIGNAL(u'doubleClicked(QModelIndex)'),
|
||||
self.onClickPressed)
|
||||
|
||||
def initialise(self):
|
||||
"""
|
||||
@ -426,10 +420,20 @@ class MediaManagerItem(QtGui.QWidget):
|
||||
raise NotImplementedError(u'MediaManagerItem.onDeleteClick needs to '
|
||||
u'be defined by the plugin')
|
||||
|
||||
def generateSlideData(self, service_item, item=None):
|
||||
def generateSlideData(self, serviceItem, item=None, xmlVersion=False):
|
||||
raise NotImplementedError(u'MediaManagerItem.generateSlideData needs '
|
||||
u'to be defined by the plugin')
|
||||
|
||||
def onClickPressed(self):
|
||||
"""
|
||||
Allows the list click action to be determined dynamically
|
||||
"""
|
||||
if QtCore.QSettings().value(u'advanced/double click live',
|
||||
QtCore.QVariant(False)).toBool():
|
||||
self.onLiveClick()
|
||||
else:
|
||||
self.onPreviewClick()
|
||||
|
||||
def onPreviewClick(self):
|
||||
"""
|
||||
Preview an item by building a service item then adding that service
|
||||
@ -442,10 +446,10 @@ class MediaManagerItem(QtGui.QWidget):
|
||||
'You must select one or more items to preview.'))
|
||||
else:
|
||||
log.debug(self.plugin.name + u' Preview requested')
|
||||
service_item = self.buildServiceItem()
|
||||
if service_item:
|
||||
service_item.from_plugin = True
|
||||
self.parent.previewController.addServiceItem(service_item)
|
||||
serviceItem = self.buildServiceItem()
|
||||
if serviceItem:
|
||||
serviceItem.from_plugin = True
|
||||
self.parent.previewController.addServiceItem(serviceItem)
|
||||
|
||||
def onLiveClick(self):
|
||||
"""
|
||||
@ -459,10 +463,10 @@ class MediaManagerItem(QtGui.QWidget):
|
||||
'You must select one or more items to send live.'))
|
||||
else:
|
||||
log.debug(self.plugin.name + u' Live requested')
|
||||
service_item = self.buildServiceItem()
|
||||
if service_item:
|
||||
service_item.from_plugin = True
|
||||
self.parent.liveController.addServiceItem(service_item)
|
||||
serviceItem = self.buildServiceItem()
|
||||
if serviceItem:
|
||||
serviceItem.from_plugin = True
|
||||
self.parent.liveController.addServiceItem(serviceItem)
|
||||
|
||||
def onAddClick(self):
|
||||
"""
|
||||
@ -474,22 +478,22 @@ class MediaManagerItem(QtGui.QWidget):
|
||||
translate('OpenLP.MediaManagerItem',
|
||||
'You must select one or more items.'))
|
||||
else:
|
||||
# Is it posssible to process multiple list items to generate multiple
|
||||
# service items?
|
||||
# Is it posssible to process multiple list items to generate
|
||||
# multiple service items?
|
||||
if self.singleServiceItem or self.remoteTriggered:
|
||||
log.debug(self.plugin.name + u' Add requested')
|
||||
service_item = self.buildServiceItem()
|
||||
if service_item:
|
||||
service_item.from_plugin = False
|
||||
self.parent.serviceManager.addServiceItem(service_item,
|
||||
serviceItem = self.buildServiceItem(None, True)
|
||||
if serviceItem:
|
||||
serviceItem.from_plugin = False
|
||||
self.parent.serviceManager.addServiceItem(serviceItem,
|
||||
replace=self.remoteTriggered)
|
||||
else:
|
||||
items = self.listView.selectedIndexes()
|
||||
for item in items:
|
||||
service_item = self.buildServiceItem(item)
|
||||
if service_item:
|
||||
service_item.from_plugin = False
|
||||
self.parent.serviceManager.addServiceItem(service_item)
|
||||
serviceItem = self.buildServiceItem(item, True)
|
||||
if serviceItem:
|
||||
serviceItem.from_plugin = False
|
||||
self.parent.serviceManager.addServiceItem(serviceItem)
|
||||
|
||||
def onAddEditClick(self):
|
||||
"""
|
||||
@ -502,16 +506,16 @@ class MediaManagerItem(QtGui.QWidget):
|
||||
'You must select one or more items'))
|
||||
else:
|
||||
log.debug(self.plugin.name + u' Add requested')
|
||||
service_item = self.parent.serviceManager.getServiceItem()
|
||||
if not service_item:
|
||||
serviceItem = self.parent.serviceManager.getServiceItem()
|
||||
if not serviceItem:
|
||||
QtGui.QMessageBox.information(self,
|
||||
translate('OpenLP.MediaManagerItem',
|
||||
'No Service Item Selected'),
|
||||
translate('OpenLP.MediaManagerItem',
|
||||
'You must select an existing service item to add to.'))
|
||||
elif self.title.lower() == service_item.name.lower():
|
||||
self.generateSlideData(service_item)
|
||||
self.parent.serviceManager.addServiceItem(service_item,
|
||||
elif self.title.lower() == serviceItem.name.lower():
|
||||
self.generateSlideData(serviceItem)
|
||||
self.parent.serviceManager.addServiceItem(serviceItem,
|
||||
replace=True)
|
||||
else:
|
||||
# Turn off the remote edit update message indicator
|
||||
@ -521,17 +525,17 @@ class MediaManagerItem(QtGui.QWidget):
|
||||
unicode(translate('OpenLP.MediaManagerItem',
|
||||
'You must select a %s service item.')) % self.title)
|
||||
|
||||
def buildServiceItem(self, item=None):
|
||||
def buildServiceItem(self, item=None, xmlVersion=False):
|
||||
"""
|
||||
Common method for generating a service item
|
||||
"""
|
||||
service_item = ServiceItem(self.parent)
|
||||
serviceItem = ServiceItem(self.parent)
|
||||
if self.serviceItemIconName:
|
||||
service_item.add_icon(self.serviceItemIconName)
|
||||
serviceItem.add_icon(self.serviceItemIconName)
|
||||
else:
|
||||
service_item.add_icon(self.parent.icon_path)
|
||||
if self.generateSlideData(service_item, item):
|
||||
return service_item
|
||||
serviceItem.add_icon(self.parent.icon_path)
|
||||
if self.generateSlideData(serviceItem, item, xmlVersion):
|
||||
return serviceItem
|
||||
else:
|
||||
return None
|
||||
|
||||
|
@ -101,6 +101,7 @@ class ServiceItem(object):
|
||||
self.search_string = u''
|
||||
self.data_string = u''
|
||||
self.edit_id = None
|
||||
self.xml_version = None
|
||||
self._new_item()
|
||||
|
||||
def _new_item(self):
|
||||
@ -252,7 +253,8 @@ class ServiceItem(object):
|
||||
u'from_plugin': self.from_plugin,
|
||||
u'capabilities': self.capabilities,
|
||||
u'search': self.search_string,
|
||||
u'data': self.data_string
|
||||
u'data': self.data_string,
|
||||
u'xml_version': self.xml_version
|
||||
}
|
||||
service_data = []
|
||||
if self.service_item_type == ServiceItemType.Text:
|
||||
@ -294,6 +296,8 @@ class ServiceItem(object):
|
||||
if u'search' in header:
|
||||
self.search_string = header[u'search']
|
||||
self.data_string = header[u'data']
|
||||
if u'xml_version' in header:
|
||||
self.xml_version = header[u'xml_version']
|
||||
if self.service_item_type == ServiceItemType.Text:
|
||||
for slide in serviceitem[u'serviceitem'][u'data']:
|
||||
self._raw_frames.append(slide)
|
||||
|
@ -146,7 +146,7 @@ class AdvancedTab(SettingsTab):
|
||||
self.mediaPluginCheckBox.setText(translate('OpenLP.AdvancedTab',
|
||||
'Remember active media manager tab on startup'))
|
||||
self.doubleClickLiveCheckBox.setText(translate('OpenLP.AdvancedTab',
|
||||
'Double-click to send items straight to live (requires restart)'))
|
||||
'Double-click to send items straight to live'))
|
||||
self.expandServiceItemCheckBox.setText(translate('OpenLP.AdvancedTab',
|
||||
'Expand new service items on creation'))
|
||||
# self.sharedDirGroupBox.setTitle(
|
||||
|
@ -100,7 +100,7 @@ class MainDisplay(DisplayWidget):
|
||||
self.screens = screens
|
||||
self.isLive = live
|
||||
self.alertTab = None
|
||||
self.hide_mode = None
|
||||
self.hideMode = None
|
||||
self.setWindowTitle(u'OpenLP Display')
|
||||
self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;')
|
||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint |
|
||||
@ -381,8 +381,8 @@ class MainDisplay(DisplayWidget):
|
||||
if self.isLive:
|
||||
self.setVisible(True)
|
||||
# if was hidden keep it hidden
|
||||
if self.hide_mode and self.isLive:
|
||||
self.hideDisplay(self.hide_mode)
|
||||
if self.hideMode and self.isLive:
|
||||
self.hideDisplay(self.hideMode)
|
||||
preview = QtGui.QImage(self.screen[u'size'].width(),
|
||||
self.screen[u'size'].height(),
|
||||
QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||
@ -412,8 +412,8 @@ class MainDisplay(DisplayWidget):
|
||||
if serviceItem.foot_text and serviceItem.foot_text:
|
||||
self.footer(serviceItem.foot_text)
|
||||
# if was hidden keep it hidden
|
||||
if self.hide_mode and self.isLive:
|
||||
self.hideDisplay(self.hide_mode)
|
||||
if self.hideMode and self.isLive:
|
||||
self.hideDisplay(self.hideMode)
|
||||
|
||||
def footer(self, text):
|
||||
"""
|
||||
@ -444,7 +444,7 @@ class MainDisplay(DisplayWidget):
|
||||
self.setVisible(True)
|
||||
if self.phononActive:
|
||||
self.webView.setVisible(True)
|
||||
self.hide_mode = mode
|
||||
self.hideMode = mode
|
||||
|
||||
def showDisplay(self):
|
||||
"""
|
||||
@ -459,9 +459,9 @@ class MainDisplay(DisplayWidget):
|
||||
if self.phononActive:
|
||||
self.webView.setVisible(False)
|
||||
self.videoPlay()
|
||||
self.hideMode = None
|
||||
# Trigger actions when display is active again
|
||||
Receiver.send_message(u'maindisplay_active')
|
||||
self.hide_mode = None
|
||||
|
||||
class AudioPlayer(QtCore.QObject):
|
||||
"""
|
||||
|
@ -789,6 +789,8 @@ class ServiceManager(QtGui.QWidget):
|
||||
self.serviceName = name[len(name) - 1]
|
||||
self.parent.addRecentFile(filename)
|
||||
self.parent.serviceChanged(True, self.serviceName)
|
||||
# Refresh Plugin lists
|
||||
Receiver.send_message(u'plugin_list_refresh')
|
||||
|
||||
def validateItem(self, serviceItem):
|
||||
"""
|
||||
@ -1028,6 +1030,9 @@ class ServiceManager(QtGui.QWidget):
|
||||
# ServiceManager started the drag and drop
|
||||
if plugin == u'ServiceManager':
|
||||
startpos, startCount = self.findServiceItem()
|
||||
# If no items selected
|
||||
if startpos == -1:
|
||||
return
|
||||
if item is None:
|
||||
endpos = len(self.serviceItems)
|
||||
else:
|
||||
|
@ -331,10 +331,8 @@ class SlideController(QtGui.QWidget):
|
||||
QtCore.QObject.connect(self.PreviewListWidget,
|
||||
QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onSlideSelected)
|
||||
if not self.isLive:
|
||||
if QtCore.QSettings().value(u'advanced/double click live',
|
||||
QtCore.QVariant(False)).toBool():
|
||||
QtCore.QObject.connect(self.PreviewListWidget,
|
||||
QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.onGoLive)
|
||||
QtCore.QObject.connect(self.PreviewListWidget,
|
||||
QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.onGoLiveClick)
|
||||
if isLive:
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'slidecontroller_live_spin_delay'),
|
||||
@ -391,6 +389,8 @@ class SlideController(QtGui.QWidget):
|
||||
if self.isLive:
|
||||
QtCore.QObject.connect(self.volumeSlider,
|
||||
QtCore.SIGNAL(u'sliderReleased()'), self.mediaVolume)
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'maindisplay_active'), self.updatePreview)
|
||||
|
||||
def screenSizeChanged(self):
|
||||
"""
|
||||
@ -823,16 +823,15 @@ class SlideController(QtGui.QWidget):
|
||||
row)
|
||||
|
||||
def updatePreview(self):
|
||||
log.debug(u'updatePreview %s ' %self.screens.current[u'primary'])
|
||||
if not self.screens.current[u'primary']:
|
||||
# Grab now, but try again in a couple of seconds if slide change
|
||||
# is slow
|
||||
QtCore.QTimer.singleShot(0.5, self.grabMainDisplay)
|
||||
QtCore.QTimer.singleShot(2.5, self.grabMainDisplay)
|
||||
else:
|
||||
label = self.PreviewListWidget.cellWidget(
|
||||
self.PreviewListWidget.currentRow(), 1)
|
||||
if label:
|
||||
self.SlidePreview.setPixmap(label.pixmap())
|
||||
self.SlidePreview.setPixmap(
|
||||
QtGui.QPixmap.fromImage(self.display.preview()))
|
||||
|
||||
def grabMainDisplay(self):
|
||||
winid = QtGui.QApplication.desktop().winId()
|
||||
@ -944,6 +943,14 @@ class SlideController(QtGui.QWidget):
|
||||
Receiver.send_message(u'%s_edit' % self.serviceItem.name.lower(),
|
||||
u'P:%s' % self.serviceItem.edit_id)
|
||||
|
||||
def onGoLiveClick(self):
|
||||
"""
|
||||
triggered by clicking the Preview slide items
|
||||
"""
|
||||
if QtCore.QSettings().value(u'advanced/double click live',
|
||||
QtCore.QVariant(False)).toBool():
|
||||
self.onGoLive()
|
||||
|
||||
def onGoLive(self):
|
||||
"""
|
||||
If preview copy slide item to live
|
||||
|
@ -545,7 +545,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||
self.dual_search_results = self.parent.manager.get_verses(
|
||||
dual_bible, text)
|
||||
else:
|
||||
# We are doing a ' Text Search'.
|
||||
# We are doing a 'Text Search'.
|
||||
bibles = self.parent.manager.get_bibles()
|
||||
self.search_results = self.parent.manager.verse_search(bible, text)
|
||||
if dual_bible and self.search_results:
|
||||
@ -651,7 +651,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||
obj = obj.toPyObject()
|
||||
return unicode(obj)
|
||||
|
||||
def generateSlideData(self, service_item, item=None):
|
||||
def generateSlideData(self, service_item, item=None, xmlVersion=False):
|
||||
"""
|
||||
Generates and formats the slides for the service item as well as the
|
||||
service item's title.
|
||||
|
@ -46,7 +46,6 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog):
|
||||
Constructor
|
||||
"""
|
||||
QtGui.QDialog.__init__(self, parent)
|
||||
#self.parent = parent
|
||||
self.setupUi(self)
|
||||
# Connecting signals and slots
|
||||
self.previewButton = QtGui.QPushButton()
|
||||
@ -124,8 +123,9 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog):
|
||||
self.slideListView.addItem(slide[1])
|
||||
theme = self.customSlide.theme_name
|
||||
id = self.themeComboBox.findText(theme, QtCore.Qt.MatchExactly)
|
||||
# No theme match
|
||||
if id == -1:
|
||||
id = 0 # Not Found
|
||||
id = 0
|
||||
self.themeComboBox.setCurrentIndex(id)
|
||||
else:
|
||||
self.themeComboBox.setCurrentIndex(0)
|
||||
@ -264,7 +264,7 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog):
|
||||
self.titleEdit.setFocus()
|
||||
return False, translate('CustomPlugin.EditCustomForm',
|
||||
'You need to type in a title.')
|
||||
# We must have one slide.
|
||||
# We must have at least one slide.
|
||||
if self.slideListView.count() == 0:
|
||||
return False, translate('CustomPlugin.EditCustomForm',
|
||||
'You need to add at least one slide')
|
||||
|
@ -50,7 +50,7 @@ class EditCustomSlideForm(QtGui.QDialog, Ui_CustomSlideEditDialog):
|
||||
def setText(self, text):
|
||||
"""
|
||||
Set the text for slideTextEdit.
|
||||
|
||||
|
||||
``text``
|
||||
The text (unicode).
|
||||
"""
|
||||
@ -67,7 +67,7 @@ class EditCustomSlideForm(QtGui.QDialog, Ui_CustomSlideEditDialog):
|
||||
|
||||
def onSplitButtonPressed(self):
|
||||
"""
|
||||
Splits a slide in two slides.
|
||||
Adds a slide split at the cursor.
|
||||
"""
|
||||
if self.slideTextEdit.textCursor().columnNumber() != 0:
|
||||
self.slideTextEdit.insertPlainText(u'\n')
|
||||
|
@ -43,6 +43,7 @@ import logging
|
||||
|
||||
from xml.dom.minidom import Document
|
||||
from xml.etree.ElementTree import ElementTree, XML, dump
|
||||
from lxml import etree, objectify
|
||||
from xml.parsers.expat import ExpatError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -55,14 +56,14 @@ class CustomXMLBuilder(object):
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Set up the song builder.
|
||||
Set up the custom builder.
|
||||
"""
|
||||
# Create the minidom document
|
||||
self.custom_xml = Document()
|
||||
|
||||
def new_document(self):
|
||||
"""
|
||||
Create a new song XML document.
|
||||
Create a new custom XML document.
|
||||
"""
|
||||
# Create the <song> base element
|
||||
self.song = self.custom_xml.createElement(u'song')
|
||||
@ -72,7 +73,7 @@ class CustomXMLBuilder(object):
|
||||
def add_lyrics_to_song(self):
|
||||
"""
|
||||
Set up and add a ``<lyrics>`` tag which contains the lyrics of the
|
||||
song.
|
||||
custom item.
|
||||
"""
|
||||
# Create the main <lyrics> element
|
||||
self.lyrics = self.custom_xml.createElement(u'lyrics')
|
||||
@ -93,7 +94,6 @@ class CustomXMLBuilder(object):
|
||||
``content``
|
||||
The actual text of the verse to be stored.
|
||||
"""
|
||||
#log.debug(u'add_verse_to_lyrics %s, %s\n%s' % (type, number, content))
|
||||
verse = self.custom_xml.createElement(u'verse')
|
||||
verse.setAttribute(u'type', type)
|
||||
verse.setAttribute(u'label', number)
|
||||
@ -102,7 +102,7 @@ class CustomXMLBuilder(object):
|
||||
cds = self.custom_xml.createCDATASection(content)
|
||||
verse.appendChild(cds)
|
||||
|
||||
def dump_xml(self):
|
||||
def _dump_xml(self):
|
||||
"""
|
||||
Debugging aid to dump XML so that we can see what we have.
|
||||
"""
|
||||
@ -110,29 +110,30 @@ class CustomXMLBuilder(object):
|
||||
|
||||
def extract_xml(self):
|
||||
"""
|
||||
Extract our newly created XML song.
|
||||
Extract our newly created XML custom.
|
||||
"""
|
||||
return self.custom_xml.toxml(u'utf-8')
|
||||
|
||||
|
||||
class CustomXMLParser(object):
|
||||
"""
|
||||
A class to read in and parse a song's XML.
|
||||
A class to read in and parse a custom's XML.
|
||||
"""
|
||||
log.info(u'CustomXMLParser Loaded')
|
||||
|
||||
def __init__(self, xml):
|
||||
"""
|
||||
Set up our song XML parser.
|
||||
Set up our custom XML parser.
|
||||
|
||||
``xml``
|
||||
The XML of the song to be parsed.
|
||||
The XML of the custom to be parsed.
|
||||
"""
|
||||
self.custom_xml = None
|
||||
if xml[:5] == u'<?xml':
|
||||
xml = xml[38:]
|
||||
try:
|
||||
self.custom_xml = ElementTree(
|
||||
element=XML(unicode(xml).encode('unicode-escape')))
|
||||
except ExpatError:
|
||||
self.custom_xml = objectify.fromstring(xml)
|
||||
except etree.XMLSyntaxError:
|
||||
log.exception(u'Invalid xml %s', xml)
|
||||
|
||||
def get_verses(self):
|
||||
@ -146,11 +147,10 @@ class CustomXMLParser(object):
|
||||
if element.tag == u'verse':
|
||||
if element.text is None:
|
||||
element.text = u''
|
||||
verse_list.append([element.attrib,
|
||||
unicode(element.text).decode('unicode-escape')])
|
||||
verse_list.append([element.attrib, unicode(element.text)])
|
||||
return verse_list
|
||||
|
||||
def dump_xml(self):
|
||||
def _dump_xml(self):
|
||||
"""
|
||||
Debugging aid to dump XML so that we can see what we have.
|
||||
"""
|
||||
|
@ -74,9 +74,9 @@ class CustomMediaItem(MediaManagerItem):
|
||||
def initialise(self):
|
||||
self.loadCustomListView(self.manager.get_all_objects(
|
||||
CustomSlide, order_by_ref=CustomSlide.title))
|
||||
#Called to redisplay the song list screen edith from a search
|
||||
#or from the exit of the Song edit dialog. If remote editing is active
|
||||
#Trigger it and clean up so it will not update again.
|
||||
# Called to redisplay the custom list screen edith from a search
|
||||
# or from the exit of the Custom edit dialog. If remote editing is
|
||||
# active trigger it and clean up so it will not update again.
|
||||
if self.remoteTriggered == u'L':
|
||||
self.onAddClick()
|
||||
if self.remoteTriggered == u'P':
|
||||
@ -144,7 +144,7 @@ class CustomMediaItem(MediaManagerItem):
|
||||
for row in row_list:
|
||||
self.listView.takeItem(row)
|
||||
|
||||
def generateSlideData(self, service_item, item=None):
|
||||
def generateSlideData(self, service_item, item=None, xmlVersion=False):
|
||||
raw_slides = []
|
||||
raw_footer = []
|
||||
slide = None
|
||||
|
@ -154,7 +154,7 @@ class ImageMediaItem(MediaManagerItem):
|
||||
item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file))
|
||||
self.listView.addItem(item_name)
|
||||
|
||||
def generateSlideData(self, service_item, item=None):
|
||||
def generateSlideData(self, service_item, item=None, xmlVersion=False):
|
||||
items = self.listView.selectedIndexes()
|
||||
if items:
|
||||
service_item.title = unicode(
|
||||
@ -163,6 +163,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||
service_item.add_capability(ItemCapabilities.AllowsPreview)
|
||||
service_item.add_capability(ItemCapabilities.AllowsLoop)
|
||||
service_item.add_capability(ItemCapabilities.AllowsAdditions)
|
||||
# force a nonexistent theme
|
||||
service_item.theme = -1
|
||||
for item in items:
|
||||
bitem = self.listView.item(item.row())
|
||||
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
|
||||
|
@ -116,7 +116,7 @@ class MediaMediaItem(MediaManagerItem):
|
||||
self.parent.liveController.display.video(filename, 0, True)
|
||||
self.resetButton.setVisible(True)
|
||||
|
||||
def generateSlideData(self, service_item, item=None):
|
||||
def generateSlideData(self, service_item, item=None, xmlVersion=False):
|
||||
if item is None:
|
||||
item = self.listView.currentItem()
|
||||
if item is None:
|
||||
|
@ -38,7 +38,7 @@ log = logging.getLogger(__name__)
|
||||
class PresentationListView(BaseListWithDnD):
|
||||
"""
|
||||
Class for the list of Presentations
|
||||
|
||||
|
||||
We have to explicitly create separate classes for each plugin
|
||||
in order for DnD to the Service manager to work correctly.
|
||||
"""
|
||||
@ -67,7 +67,7 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
self.message_listener = MessageListener(self)
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'mediaitem_presentation_rebuild'), self.rebuild)
|
||||
|
||||
|
||||
def retranslateUi(self):
|
||||
"""
|
||||
The name of the plugin media displayed in UI
|
||||
@ -159,7 +159,7 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
if self.DisplayTypeComboBox.count() > 1:
|
||||
self.DisplayTypeComboBox.insertItem(0, self.Automatic)
|
||||
self.DisplayTypeComboBox.setCurrentIndex(0)
|
||||
if QtCore.QSettings().value(self.settingsSection + u'/override app',
|
||||
if QtCore.QSettings().value(self.settingsSection + u'/override app',
|
||||
QtCore.QVariant(QtCore.Qt.Unchecked)) == QtCore.Qt.Checked:
|
||||
self.PresentationWidget.show()
|
||||
else:
|
||||
@ -238,7 +238,7 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
SettingsManager.set_list(self.settingsSection,
|
||||
self.settingsSection, self.getFileList())
|
||||
|
||||
def generateSlideData(self, service_item, item=None):
|
||||
def generateSlideData(self, service_item, item=None, xmlVersion=False):
|
||||
"""
|
||||
Load the relevant information for displaying the presentation
|
||||
in the slidecontroller. In the case of powerpoints, an image
|
||||
@ -277,7 +277,7 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
def findControllerByType(self, filename):
|
||||
"""
|
||||
Determine the default application controller to use for the selected
|
||||
file type. This is used if "Automatic" is set as the preferred
|
||||
file type. This is used if "Automatic" is set as the preferred
|
||||
controller. Find the first (alphabetic) enabled controller which
|
||||
"supports" the extension. If none found, then look for a controller
|
||||
which "alsosupports" it instead.
|
||||
|
@ -152,6 +152,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
|
||||
def newSong(self):
|
||||
log.debug(u'New Song')
|
||||
self.initialise()
|
||||
self.SongTabWidget.setCurrentIndex(0)
|
||||
self.TitleEditItem.setText(u'')
|
||||
self.AlternativeEdit.setText(u'')
|
||||
@ -170,8 +171,18 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
# it's a new song to preview is not possible
|
||||
self.previewButton.setVisible(False)
|
||||
|
||||
def loadSong(self, id, preview):
|
||||
def loadSong(self, id, preview=False):
|
||||
"""
|
||||
Loads a song.
|
||||
|
||||
``id``
|
||||
The song id (int).
|
||||
|
||||
``preview``
|
||||
Should be ``True`` if the song is also previewed (boolean).
|
||||
"""
|
||||
log.debug(u'Load Song')
|
||||
self.initialise()
|
||||
self.SongTabWidget.setCurrentIndex(0)
|
||||
self.loadAuthors()
|
||||
self.loadTopics()
|
||||
@ -594,6 +605,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
"""
|
||||
Save and Preview button pressed.
|
||||
The Song is valid so as the plugin to add it to preview to see.
|
||||
|
||||
``button``
|
||||
A button (QPushButton).
|
||||
"""
|
||||
log.debug(u'onPreview')
|
||||
if unicode(button.objectName()) == u'previewButton' and \
|
||||
@ -631,13 +645,16 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
database.
|
||||
|
||||
``preview``
|
||||
Should be True if song is also previewed.
|
||||
Should be ``True`` if the song is also previewed (boolean).
|
||||
"""
|
||||
self.song.title = unicode(self.TitleEditItem.text())
|
||||
self.song.alternate_title = unicode(self.AlternativeEdit.text())
|
||||
self.song.copyright = unicode(self.CopyrightEditItem.text())
|
||||
self.song.search_title = self.song.title + u'@' + \
|
||||
self.song.alternate_title
|
||||
if self.song.alternate_title:
|
||||
self.song.search_title = self.song.title + u'@' + \
|
||||
self.song.alternate_title
|
||||
else:
|
||||
self.song.search_title = self.song.title
|
||||
self.song.comments = unicode(self.CommentsEdit.toPlainText())
|
||||
self.song.verse_order = unicode(self.VerseOrderEdit.text())
|
||||
self.song.ccli_number = unicode(self.CCLNumberEdit.text())
|
||||
@ -648,6 +665,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
Book.name == book_name)
|
||||
else:
|
||||
self.song.book = None
|
||||
theme_name = unicode(self.ThemeSelectionComboItem.currentText())
|
||||
if theme_name:
|
||||
self.song.theme_name = theme_name
|
||||
else:
|
||||
self.song.theme_name = None
|
||||
if self._validate_song():
|
||||
self.processLyrics()
|
||||
self.processTitle()
|
||||
|
@ -62,6 +62,36 @@ class VerseType(object):
|
||||
elif verse_type == VerseType.Other:
|
||||
return translate('SongsPlugin.VerseType', 'Other')
|
||||
|
||||
@staticmethod
|
||||
def expand_string(verse_type):
|
||||
"""
|
||||
Return the VerseType for a given string
|
||||
|
||||
``verse_type``
|
||||
The string to return a VerseType for
|
||||
"""
|
||||
verse_type = verse_type.lower()
|
||||
if verse_type == unicode(VerseType.to_string(VerseType.Verse)).lower()[0]:
|
||||
return translate('SongsPlugin.VerseType', 'Verse')
|
||||
elif verse_type == \
|
||||
unicode(VerseType.to_string(VerseType.Chorus)).lower()[0]:
|
||||
return translate('SongsPlugin.VerseType', 'Chorus')
|
||||
elif verse_type == \
|
||||
unicode(VerseType.to_string(VerseType.Bridge)).lower()[0]:
|
||||
return translate('SongsPlugin.VerseType', 'Bridge')
|
||||
elif verse_type == \
|
||||
unicode(VerseType.to_string(VerseType.PreChorus)).lower()[0]:
|
||||
return translate('SongsPlugin.VerseType', 'PreChorus')
|
||||
elif verse_type == \
|
||||
unicode(VerseType.to_string(VerseType.Intro)).lower()[0]:
|
||||
return translate('SongsPlugin.VerseType', 'Intro')
|
||||
elif verse_type == \
|
||||
unicode(VerseType.to_string(VerseType.Ending)).lower()[0]:
|
||||
return translate('SongsPlugin.VerseType', 'Ending')
|
||||
elif verse_type == \
|
||||
unicode(VerseType.to_string(VerseType.Other)).lower()[0]:
|
||||
return translate('SongsPlugin.VerseType', 'Other')
|
||||
|
||||
@staticmethod
|
||||
def from_string(verse_type):
|
||||
"""
|
||||
@ -92,7 +122,6 @@ class VerseType(object):
|
||||
unicode(VerseType.to_string(VerseType.Other)).lower():
|
||||
return VerseType.Other
|
||||
|
||||
|
||||
from xml import LyricsXML, SongXMLBuilder, SongXMLParser
|
||||
from xml import LyricsXML, SongXMLBuilder, SongXMLParser, OpenLyricsParser
|
||||
from songstab import SongsTab
|
||||
from mediaitem import SongMediaItem
|
||||
|
@ -32,7 +32,7 @@ from openlp.core.lib import MediaManagerItem, BaseListWithDnD, Receiver, \
|
||||
ItemCapabilities, translate, check_item_selected
|
||||
from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \
|
||||
SongImportForm
|
||||
from openlp.plugins.songs.lib import SongXMLParser
|
||||
from openlp.plugins.songs.lib import SongXMLParser, OpenLyricsParser
|
||||
from openlp.plugins.songs.lib.db import Author, Song
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -53,8 +53,8 @@ class SongMediaItem(MediaManagerItem):
|
||||
self.ListViewWithDnD_class = SongListView
|
||||
MediaManagerItem.__init__(self, parent, self, icon)
|
||||
self.edit_song_form = EditSongForm(self, self.parent.manager)
|
||||
self.openLyrics = OpenLyricsParser(self.parent.manager)
|
||||
self.singleServiceItem = False
|
||||
#self.edit_song_form = EditSongForm(self.parent.manager, self)
|
||||
self.song_maintenance_form = SongMaintenanceForm(
|
||||
self.parent.manager, self)
|
||||
# Holds information about whether the edit is remotly triggered and
|
||||
@ -114,6 +114,8 @@ class SongMediaItem(MediaManagerItem):
|
||||
self.SearchButtonLayout.addWidget(self.ClearTextButton)
|
||||
self.pageLayout.addLayout(self.SearchButtonLayout)
|
||||
# Signals and slots
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'plugin_list_refresh'), self.onSearchTextButtonClick)
|
||||
QtCore.QObject.connect(self.SearchTextEdit,
|
||||
QtCore.SIGNAL(u'returnPressed()'), self.onSearchTextButtonClick)
|
||||
QtCore.QObject.connect(self.SearchTextButton,
|
||||
@ -141,7 +143,7 @@ class SongMediaItem(MediaManagerItem):
|
||||
self.updateServiceOnEdit = QtCore.QSettings().value(
|
||||
self.settingsSection + u'/update service on edit',
|
||||
QtCore.QVariant(u'False')).toBool()
|
||||
self.AddSongFromServide = QtCore.QSettings().value(
|
||||
self.addSongFromService = QtCore.QSettings().value(
|
||||
self.settingsSection + u'/add song from service',
|
||||
QtCore.QVariant(u'True')).toBool()
|
||||
|
||||
@ -328,7 +330,7 @@ class SongMediaItem(MediaManagerItem):
|
||||
self.parent.manager.delete_object(Song, item_id)
|
||||
self.onSearchTextButtonClick()
|
||||
|
||||
def generateSlideData(self, service_item, item=None):
|
||||
def generateSlideData(self, service_item, item=None, xmlVersion=False):
|
||||
log.debug(u'generateSlideData (%s:%s)' % (service_item, item))
|
||||
raw_footer = []
|
||||
author_list = u''
|
||||
@ -355,7 +357,7 @@ class SongMediaItem(MediaManagerItem):
|
||||
if song.lyrics.startswith(u'<?xml version='):
|
||||
songXML = SongXMLParser(song.lyrics)
|
||||
verseList = songXML.get_verses()
|
||||
#no verse list or only 1 space (in error)
|
||||
# no verse list or only 1 space (in error)
|
||||
if not song.verse_order or not song.verse_order.strip():
|
||||
for verse in verseList:
|
||||
verseTag = u'%s:%s' % (
|
||||
@ -397,6 +399,7 @@ class SongMediaItem(MediaManagerItem):
|
||||
]
|
||||
service_item.data_string = {u'title':song.search_title,
|
||||
u'authors':author_list}
|
||||
service_item.xml_version = self.openLyrics.song_to_xml(song)
|
||||
return True
|
||||
|
||||
def serviceLoad(self, item):
|
||||
@ -406,21 +409,31 @@ class SongMediaItem(MediaManagerItem):
|
||||
log.debug(u'serviceLoad')
|
||||
if item.data_string:
|
||||
search_results = self.parent.manager.get_all_objects(Song,
|
||||
Song.search_title.like(u'%' +
|
||||
item.data_string[u'title'].split(u'@')[0] + u'%'),
|
||||
Song.search_title ==
|
||||
item.data_string[u'title'].split(u'@')[0].lower() ,
|
||||
Song.search_title.asc())
|
||||
author_list = item.data_string[u'authors'].split(u', ')
|
||||
editId = 0
|
||||
uuid = 0
|
||||
uuid = item._uuid
|
||||
if search_results:
|
||||
for song in search_results:
|
||||
count = 0
|
||||
for author in song.authors:
|
||||
if author.display_name in author_list:
|
||||
count += 1
|
||||
# All Authors the same
|
||||
if count == len(author_list):
|
||||
editId = song.id
|
||||
uuid = item._uuid
|
||||
else:
|
||||
# Authors different
|
||||
if self.addSongFromService:
|
||||
editId = self.openLyrics. \
|
||||
xml_to_song(item.xml_version)
|
||||
else:
|
||||
# Title does not match
|
||||
if self.addSongFromService:
|
||||
editId = self.openLyrics.xml_to_song(item.xml_version)
|
||||
# Update service with correct song id
|
||||
if editId != 0:
|
||||
Receiver.send_message(u'service_item_update',
|
||||
u'%s:%s' %(editId, uuid))
|
||||
|
@ -39,8 +39,11 @@ The basic XML is of the format::
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from lxml import etree, objectify
|
||||
from openlp.plugins.songs.lib import VerseType
|
||||
from openlp.plugins.songs.lib.db import Author, Song
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -77,7 +80,6 @@ class SongXMLBuilder(object):
|
||||
``content``
|
||||
The actual text of the verse to be stored.
|
||||
"""
|
||||
# log.debug(u'add_verse_to_lyrics %s, %s\n%s' % (type, number, content))
|
||||
verse = etree.Element(u'verse', type = unicode(type),
|
||||
label = unicode(number))
|
||||
verse.text = etree.CDATA(content)
|
||||
@ -239,3 +241,153 @@ class LyricsXML(object):
|
||||
song_output = u'<?xml version="1.0" encoding="UTF-8"?>' + \
|
||||
u'<song version="1.0">%s</song>' % lyrics_output
|
||||
return song_output
|
||||
|
||||
|
||||
class OpenLyricsParser(object):
|
||||
"""
|
||||
This class represents the converter for Song to/from OpenLyrics XML.
|
||||
"""
|
||||
def __init__(self, manager):
|
||||
self.manager = manager
|
||||
|
||||
def song_to_xml(self, song):
|
||||
"""
|
||||
Convert the song to OpenLyrics Format
|
||||
"""
|
||||
song_xml_parser = SongXMLParser(song.lyrics)
|
||||
verse_list = song_xml_parser.get_verses()
|
||||
song_xml = objectify.fromstring(
|
||||
u'<song version="0.7" createdIn="OpenLP 2.0"/>')
|
||||
properties = etree.SubElement(song_xml, u'properties')
|
||||
titles = etree.SubElement(properties, u'titles')
|
||||
self._add_text_to_element(u'title', titles, song.title)
|
||||
if song.alternate_title:
|
||||
self._add_text_to_element(u'title', titles, song.alternate_title)
|
||||
if song.theme_name:
|
||||
themes = etree.SubElement(properties, u'themes')
|
||||
self._add_text_to_element(u'theme', themes, song.theme_name)
|
||||
self._add_text_to_element(u'copyright', properties, song.copyright)
|
||||
self._add_text_to_element(u'verseOrder', properties, song.verse_order)
|
||||
if song.ccli_number:
|
||||
self._add_text_to_element(u'ccliNo', properties, song.ccli_number)
|
||||
authors = etree.SubElement(properties, u'authors')
|
||||
for author in song.authors:
|
||||
self._add_text_to_element(u'author', authors, author.display_name)
|
||||
lyrics = etree.SubElement(song_xml, u'lyrics')
|
||||
for verse in verse_list:
|
||||
verse_tag = u'%s%s' % (
|
||||
verse[0][u'type'][0].lower(), verse[0][u'label'])
|
||||
element = \
|
||||
self._add_text_to_element(u'verse', lyrics, None, verse_tag)
|
||||
element = self._add_text_to_element(u'lines', element)
|
||||
for line in unicode(verse[1]).split(u'\n'):
|
||||
self._add_text_to_element(u'line', element, line)
|
||||
return self._extract_xml(song_xml)
|
||||
|
||||
def xml_to_song(self, xml):
|
||||
"""
|
||||
Create a Song from OpenLyrics format xml
|
||||
"""
|
||||
# No xml get out of here
|
||||
if not xml:
|
||||
return 0
|
||||
song = Song()
|
||||
if xml[:5] == u'<?xml':
|
||||
xml = xml[38:]
|
||||
song_xml = objectify.fromstring(xml)
|
||||
properties = song_xml.properties
|
||||
song.copyright = unicode(properties.copyright.text)
|
||||
song.verse_order = unicode(properties.verseOrder.text)
|
||||
if song.verse_order == u'None':
|
||||
song.verse_order = u''
|
||||
song.topics = []
|
||||
song.book = None
|
||||
theme_name = None
|
||||
try:
|
||||
song.ccli_number = unicode(properties.ccliNo.text)
|
||||
except:
|
||||
song.ccli_number = u''
|
||||
try:
|
||||
theme_name = unicode(properties.themes.theme)
|
||||
except:
|
||||
pass
|
||||
if theme_name:
|
||||
song.theme_name = theme_name
|
||||
else:
|
||||
song.theme_name = u''
|
||||
# Process Titles
|
||||
for title in properties.titles.title:
|
||||
if not song.title:
|
||||
song.title = unicode(title.text)
|
||||
song.search_title = unicode(song.title)
|
||||
song.alternate_title = u''
|
||||
else:
|
||||
song.alternate_title = unicode(title.text)
|
||||
song.search_title += u'@' + song.alternate_title
|
||||
song.search_title = re.sub(r'[\'"`,;:(){}?]+', u'',
|
||||
unicode(song.search_title)).lower()
|
||||
# Process Lyrics
|
||||
sxml = SongXMLBuilder()
|
||||
search_text = u''
|
||||
for lyrics in song_xml.lyrics:
|
||||
for verse in song_xml.lyrics.verse:
|
||||
text = u''
|
||||
for line in verse.lines.line:
|
||||
line = unicode(line)
|
||||
if not text:
|
||||
text = line
|
||||
else:
|
||||
text += u'\n' + line
|
||||
type = VerseType.expand_string(verse.attrib[u'name'][0])
|
||||
sxml.add_verse_to_lyrics(type, verse.attrib[u'name'][1], text)
|
||||
search_text = search_text + text
|
||||
song.search_lyrics = search_text.lower()
|
||||
song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
|
||||
song.comments = u''
|
||||
song.song_number = u''
|
||||
# Process Authors
|
||||
for author in properties.authors.author:
|
||||
self._process_author(author.text, song)
|
||||
self.manager.save_object(song)
|
||||
return song.id
|
||||
|
||||
def _add_text_to_element(self, tag, parent, text=None, label=None):
|
||||
if label:
|
||||
element = etree.Element(tag, name = unicode(label))
|
||||
else:
|
||||
element = etree.Element(tag)
|
||||
if text:
|
||||
element.text = unicode(text)
|
||||
parent.append(element)
|
||||
return element
|
||||
|
||||
def _dump_xml(self, xml):
|
||||
"""
|
||||
Debugging aid to dump XML so that we can see what we have.
|
||||
"""
|
||||
return etree.tostring(xml, encoding=u'UTF-8',
|
||||
xml_declaration=True, pretty_print=True)
|
||||
|
||||
def _extract_xml(self, xml):
|
||||
"""
|
||||
Extract our newly created XML song.
|
||||
"""
|
||||
return etree.tostring(xml, encoding=u'UTF-8',
|
||||
xml_declaration=True)
|
||||
|
||||
def _process_author(self, name, song):
|
||||
"""
|
||||
Find or create an Author from display_name.
|
||||
"""
|
||||
name = unicode(name)
|
||||
author = self.manager.get_object_filtered(Author,
|
||||
Author.display_name == name)
|
||||
if author:
|
||||
# should only be one! so take the first
|
||||
song.authors.append(author)
|
||||
else:
|
||||
# Need a new author
|
||||
new_author = Author.populate(first_name=name.rsplit(u' ', 1)[0],
|
||||
last_name=name.rsplit(u' ', 1)[1], display_name=name)
|
||||
self.manager.save_object(new_author)
|
||||
song.authors.append(new_author)
|
||||
|