diff --git a/openlp/__init__.py b/openlp/__init__.py index 9038b48cc..5f7608770 100644 --- a/openlp/__init__.py +++ b/openlp/__init__.py @@ -27,3 +27,9 @@ """ The :mod:`openlp` module contains all the project produced OpenLP functionality """ + +import core +import plugins + +__all__ = [u'core', u'plugins'] + diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 01d34956e..a5347edeb 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -25,7 +25,12 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -__all__ = ('OpenLP', 'main') +""" +The :mod:`core` module provides all core application functions + +All the core functions of the OpenLP application including the GUI, settings, +logging and a plugin framework are contained within the openlp.core module. +""" import os import sys @@ -46,16 +51,11 @@ from openlp.core.ui import SplashScreen, ScreenList from openlp.core.utils import AppLocation, LanguageManager, VersionThread, \ get_application_version, DelayStartThread + +__all__ = [u'OpenLP', u'main'] + + log = logging.getLogger() - - -""" -The :mod:`core` module provides all core application functions - -All the core functions of the OpenLP application including the GUI, settings, -logging and a plugin framework are contained within the openlp.core module. -""" - application_stylesheet = u""" QMainWindow::separator { diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index d9d29efab..2c0b53a95 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -36,6 +36,13 @@ from PyQt4 import QtCore, QtGui log = logging.getLogger(__name__) +class MediaType(object): + """ + An enumeration class for types of media. + """ + Audio = 1 + Video = 2 + def translate(context, text, comment=None, encoding=QtCore.QCoreApplication.CodecForTr, n=-1, translate=QtCore.QCoreApplication.translate): @@ -241,9 +248,7 @@ from settingsmanager import SettingsManager from plugin import PluginStatus, StringContent, Plugin from pluginmanager import PluginManager from settingstab import SettingsTab -from serviceitem import ServiceItem -from serviceitem import ServiceItemType -from serviceitem import ItemCapabilities +from serviceitem import ServiceItem, ServiceItemType, ItemCapabilities from htmlbuilder import build_html, build_lyrics_format_css, \ build_lyrics_outline_css from toolbar import OpenLPToolbar diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index 2e5d011cf..1b8d086df 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -82,7 +82,7 @@ def upgrade_db(url, upgrade): load_changes = True try: tables = upgrade.upgrade_setup(metadata) - except SQLAlchemyError, DBAPIError: + except (SQLAlchemyError, DBAPIError): load_changes = False metadata_table = Table(u'metadata', metadata, Column(u'key', types.Unicode(64), primary_key=True), @@ -106,7 +106,7 @@ def upgrade_db(url, upgrade): getattr(upgrade, u'upgrade_%d' % version) \ (session, metadata, tables) version_meta.value = unicode(version) - except SQLAlchemyError, DBAPIError: + except (SQLAlchemyError, DBAPIError): log.exception(u'Could not run database upgrade script ' '"upgrade_%s", upgrade process has been halted.', version) break @@ -213,7 +213,8 @@ class Manager(object): return try: self.session = init_schema(self.db_url) - except: + except (SQLAlchemyError, DBAPIError): + log.exception(u'Error loading database: %s', self.db_url) critical_error_message_box( translate('OpenLP.Manager', 'Database Error'), unicode(translate('OpenLP.Manager', 'OpenLP cannot load your ' diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index f21d8df50..55cb2faeb 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -111,7 +111,7 @@ class MediaManagerItem(QtGui.QWidget): self.requiredIcons() self.setupUi() self.retranslateUi() - self.auto_select_id = -1 + self.autoSelectId = -1 QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'%s_service_load' % self.plugin.name), self.serviceLoad) @@ -506,7 +506,7 @@ class MediaManagerItem(QtGui.QWidget): if QtCore.QSettings().value(u'advanced/single click preview', QtCore.QVariant(False)).toBool() and self.quickPreviewAllowed \ and self.listView.selectedIndexes() \ - and self.auto_select_id == -1: + and self.autoSelectId == -1: self.onPreviewClick(True) def onPreviewClick(self, keepFocus=False): @@ -626,7 +626,7 @@ class MediaManagerItem(QtGui.QWidget): """ pass - def check_search_result(self): + def checkSearchResult(self): """ Checks if the listView is empty and adds a "No Search Results" item. """ @@ -662,15 +662,15 @@ class MediaManagerItem(QtGui.QWidget): item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] return item_id - def save_auto_select_id(self): + def saveAutoSelectId(self): """ Sorts out, what item to select after loading a list. """ # The item to select has not been set. - if self.auto_select_id == -1: + if self.autoSelectId == -1: item = self.listView.currentItem() if item: - self.auto_select_id = item.data(QtCore.Qt.UserRole).toInt()[0] + self.autoSelectId = item.data(QtCore.Qt.UserRole).toInt()[0] def search(self, string): """ diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index 5b9f2185e..db7d4845b 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -368,3 +368,4 @@ class Plugin(QtCore.QObject): after this has been set. """ self.textStrings[name] = {u'title': title, u'tooltip': tooltip} + diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 185d74878..8353ddc19 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -222,14 +222,14 @@ class Renderer(object): if item.is_capable(ItemCapabilities.NoLineBreaks): line_end = u' ' # Bibles - if item.is_capable(ItemCapabilities.AllowsWordSplit): + if item.is_capable(ItemCapabilities.CanWordSplit): pages = self._paginate_slide_words(text.split(u'\n'), line_end) else: # Clean up line endings. lines = self._lines_split(text) pages = self._paginate_slide(lines, line_end) # Songs and Custom - if item.is_capable(ItemCapabilities.AllowsVirtualSplit) and \ + if item.is_capable(ItemCapabilities.CanSoftBreak) and \ len(pages) > 1 and u'[---]' in text: pages = [] while True: diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 7be28520c..05a1128b1 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -52,20 +52,21 @@ class ItemCapabilities(object): """ Provides an enumeration of a serviceitem's capabilities """ - AllowsPreview = 1 - AllowsEdit = 2 - AllowsMaintain = 3 + CanPreview = 1 + CanEdit = 2 + CanMaintain = 3 RequiresMedia = 4 - AllowsLoop = 5 - AllowsAdditions = 6 + CanLoop = 5 + CanAppend = 6 NoLineBreaks = 7 OnLoadUpdate = 8 AddIfNewItem = 9 ProvidesOwnDisplay = 10 - AllowsDetailedTitleDisplay = 11 - AllowsVariableStartTime = 12 - AllowsVirtualSplit = 13 - AllowsWordSplit = 14 + HasDetailedTitleDisplay = 11 + HasVariableStartTime = 12 + CanSoftBreak = 13 + CanWordSplit = 14 + HasBackgroundAudio = 15 class ServiceItem(object): @@ -116,6 +117,7 @@ class ServiceItem(object): self.media_length = 0 self.from_service = False self.image_border = u'#000000' + self.background_audio = [] self._new_item() def _new_item(self): @@ -159,7 +161,7 @@ class ServiceItem(object): """ The render method is what generates the frames for the screen and obtains the display information from the renderemanager. - At this point all the slides are build for the given + At this point all the slides are built for the given display size. """ log.debug(u'Render called') @@ -272,7 +274,8 @@ class ServiceItem(object): u'xml_version': self.xml_version, u'start_time': self.start_time, u'end_time': self.end_time, - u'media_length': self.media_length + u'media_length': self.media_length, + u'background_audio': self.background_audio } service_data = [] if self.service_item_type == ServiceItemType.Text: @@ -320,6 +323,8 @@ class ServiceItem(object): self.end_time = header[u'end_time'] if u'media_length' in header: self.media_length = header[u'media_length'] + if u'background_audio' in header: + self.background_audio = header[u'background_audio'] if self.service_item_type == ServiceItemType.Text: for slide in serviceitem[u'serviceitem'][u'data']: self._raw_frames.append(slide) @@ -341,7 +346,7 @@ class ServiceItem(object): if self.is_text(): return self.title else: - if ItemCapabilities.AllowsDetailedTitleDisplay in self.capabilities: + if ItemCapabilities.HasDetailedTitleDisplay in self.capabilities: return self._raw_frames[0][u'title'] elif len(self._raw_frames) > 1: return self.title @@ -359,6 +364,8 @@ class ServiceItem(object): """ self._uuid = other._uuid self.notes = other.notes + if self.is_capable(ItemCapabilities.HasBackgroundAudio): + log.debug(self.background_audio) def __eq__(self, other): """ diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 77f2e2f7c..297f5430b 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -62,6 +62,10 @@ class MainDisplay(QtGui.QGraphicsView): self.override = {} self.retranslateUi() self.mediaObject = None + if live: + self.audioPlayer = AudioPlayer(self) + else: + self.audioPlayer = None self.firstTime = True self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;') self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | @@ -587,61 +591,76 @@ class AudioPlayer(QtCore.QObject): """ log.debug(u'AudioPlayer Initialisation started') QtCore.QObject.__init__(self, parent) - self.message = None + self.currentIndex = -1 + self.playlist = [] self.mediaObject = Phonon.MediaObject() self.audioObject = Phonon.AudioOutput(Phonon.VideoCategory) Phonon.createPath(self.mediaObject, self.audioObject) + QtCore.QObject.connect(self.mediaObject, + QtCore.SIGNAL(u'aboutToFinish()'), self.onAboutToFinish) - def setup(self): - """ - Sets up the Audio Player for use - """ - log.debug(u'AudioPlayer Setup') - - def close(self): + def __del__(self): """ Shutting down so clean up connections """ - self.onMediaStop() + self.stop() for path in self.mediaObject.outputPaths(): path.disconnect() + QtCore.QObject.__del__(self) - def onMediaQueue(self, message): + def onAboutToFinish(self): """ - Set up a video to play from the serviceitem. + Just before the audio player finishes the current track, queue the next + item in the playlist, if there is one. """ - log.debug(u'AudioPlayer Queue new media message %s' % message) - mfile = os.path.join(message[0].get_frame_path(), - message[0].get_frame_title()) - self.mediaObject.setCurrentSource(Phonon.MediaSource(mfile)) - self.onMediaPlay() + self.currentIndex += 1 + if len(self.playlist) > self.currentIndex: + self.mediaObject.enqueue(self.playlist[self.currentIndex]) - def onMediaPlay(self): + def connectVolumeSlider(self, slider): + slider.setAudioOutput(self.audioObject) + + def reset(self): """ - We want to play the play so start it + Reset the audio player, clearing the playlist and the queue. """ - log.debug(u'AudioPlayer _play called') + self.currentIndex = -1 + self.playlist = [] + self.stop() + self.mediaObject.clear() + + def play(self): + """ + We want to play the file so start it + """ + log.debug(u'AudioPlayer.play() called') + if self.currentIndex == -1: + self.onAboutToFinish() self.mediaObject.play() - def onMediaPause(self): + def pause(self): """ Pause the Audio """ - log.debug(u'AudioPlayer Media paused by user') + log.debug(u'AudioPlayer.pause() called') self.mediaObject.pause() - def onMediaStop(self): + def stop(self): """ Stop the Audio and clean up """ - log.debug(u'AudioPlayer Media stopped by user') - self.message = None + log.debug(u'AudioPlayer.stop() called') self.mediaObject.stop() - self.onMediaFinish() - def onMediaFinish(self): + def addToPlaylist(self, filenames): """ - Clean up the Object queue + Add another file to the playlist. + + ``filename`` + The file to add to the playlist. """ - log.debug(u'AudioPlayer Reached end of media playlist') - self.mediaObject.clearQueue() + if not isinstance(filenames, list): + filenames = [filenames] + for filename in filenames: + self.playlist.append(Phonon.MediaSource(filename)) + diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 20edab792..d2d7450ca 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -28,6 +28,7 @@ import cgi import cPickle import logging import os +import shutil import zipfile log = logging.getLogger(__name__) @@ -471,23 +472,34 @@ class ServiceManager(QtGui.QWidget): if not self.fileName(): return self.saveFileAs() path_file_name = unicode(self.fileName()) - (path, file_name) = os.path.split(path_file_name) - (basename, extension) = os.path.splitext(file_name) - service_file_name = basename + '.osd' + path, file_name = os.path.split(path_file_name) + basename, extension = os.path.splitext(file_name) + service_file_name = '%s.osd' % basename log.debug(u'ServiceManager.saveFile - %s' % path_file_name) SettingsManager.set_last_dir( self.mainwindow.servicemanagerSettingsSection, path) service = [] write_list = [] + audio_files = [] total_size = 0 Receiver.send_message(u'cursor_busy') # Number of items + 1 to zip it self.mainwindow.displayProgressBar(len(self.serviceItems) + 1) for item in self.serviceItems: self.mainwindow.incrementProgressBar() - service.append({u'serviceitem': - item[u'service_item'].get_service_repr()}) + service_item = item[u'service_item'].get_service_repr() + # Get all the audio files, and ready them for embedding in the + # service file. + if len(service_item[u'header'][u'background_audio']) > 0: + for i, filename in \ + enumerate(service_item[u'header'][u'background_audio']): + new_file = os.path.join(u'audio', item[u'service_item']._uuid, + os.path.split(filename)[1]) + audio_files.append((filename, new_file)) + service_item[u'header'][u'background_audio'][i] = new_file + # Add the service item to the service. + service.append({u'serviceitem': service_item}) if not item[u'service_item'].uses_file(): continue skipMissing = False @@ -541,6 +553,8 @@ class ServiceManager(QtGui.QWidget): # Finally add all the listed media files. for path_from in write_list: zip.write(path_from, path_from.encode(u'utf-8')) + for path_from, path_to in audio_files: + zip.write(path_from, path_to.encode(u'utf-8')) except IOError: log.exception(u'Failed to save service to disk') success = False @@ -595,11 +609,12 @@ class ServiceManager(QtGui.QWidget): 'The content encoding is not UTF-8.')) continue osfile = unicode(QtCore.QDir.toNativeSeparators(ucsfile)) - filename_only = os.path.split(osfile)[1] - zipinfo.filename = filename_only + if not osfile.startswith(u'audio'): + osfile = os.path.split(osfile)[1] + zipinfo.filename = osfile zip.extract(zipinfo, self.servicePath) - if filename_only.endswith(u'osd'): - p_file = os.path.join(self.servicePath, filename_only) + if osfile.endswith(u'osd'): + p_file = os.path.join(self.servicePath, osfile) if 'p_file' in locals(): Receiver.send_message(u'cursor_busy') fileTo = open(p_file, u'r') @@ -630,10 +645,10 @@ class ServiceManager(QtGui.QWidget): 'File is not a valid service.')) log.exception(u'File contains no service data') except (IOError, NameError, zipfile.BadZipfile): + log.exception(u'Problem loading service file %s' % fileName) critical_error_message_box( message=translate('OpenLP.ServiceManager', 'File could not be opened because it is corrupt.')) - log.exception(u'Problem loading service file %s' % fileName) except zipfile.BadZipfile: if os.path.getsize(fileName) == 0: log.exception(u'Service file is zero sized: %s' % fileName) @@ -682,16 +697,16 @@ class ServiceManager(QtGui.QWidget): self.maintainAction.setVisible(False) self.notesAction.setVisible(False) self.timeAction.setVisible(False) - if serviceItem[u'service_item'].is_capable(ItemCapabilities.AllowsEdit)\ + if serviceItem[u'service_item'].is_capable(ItemCapabilities.CanEdit)\ and serviceItem[u'service_item'].edit_id: self.editAction.setVisible(True) if serviceItem[u'service_item']\ - .is_capable(ItemCapabilities.AllowsMaintain): + .is_capable(ItemCapabilities.CanMaintain): self.maintainAction.setVisible(True) if item.parent() is None: self.notesAction.setVisible(True) if serviceItem[u'service_item']\ - .is_capable(ItemCapabilities.AllowsVariableStartTime): + .is_capable(ItemCapabilities.HasVariableStartTime): self.timeAction.setVisible(True) self.themeMenu.menuAction().setVisible(False) # Set up the theme menu. @@ -962,7 +977,7 @@ class ServiceManager(QtGui.QWidget): (unicode(translate('OpenLP.ServiceManager', 'Notes')), cgi.escape(unicode(serviceitem.notes)))) if item[u'service_item'] \ - .is_capable(ItemCapabilities.AllowsVariableStartTime): + .is_capable(ItemCapabilities.HasVariableStartTime): tips.append(item[u'service_item'].get_media_time()) treewidgetitem.setToolTip(0, u'
'.join(tips)) treewidgetitem.setData(0, QtCore.Qt.UserRole, @@ -998,6 +1013,8 @@ class ServiceManager(QtGui.QWidget): for file in os.listdir(self.servicePath): file_path = os.path.join(self.servicePath, file) delete_file(file_path) + if os.path.exists(os.path.join(self.servicePath, u'audio')): + shutil.rmtree(os.path.join(self.servicePath, u'audio'), False) def onThemeComboBoxSelected(self, currentIndex): """ @@ -1196,7 +1213,7 @@ class ServiceManager(QtGui.QWidget): item += 1 if self.serviceItems and item < len(self.serviceItems) and \ self.serviceItems[item][u'service_item'].is_capable( - ItemCapabilities.AllowsPreview): + ItemCapabilities.CanPreview): self.mainwindow.previewController.addServiceManagerItem( self.serviceItems[item][u'service_item'], 0) self.mainwindow.liveController.previewListWidget.setFocus() @@ -1214,7 +1231,7 @@ class ServiceManager(QtGui.QWidget): """ item = self.findServiceItem()[0] if self.serviceItems[item][u'service_item']\ - .is_capable(ItemCapabilities.AllowsEdit): + .is_capable(ItemCapabilities.CanEdit): Receiver.send_message(u'%s_edit' % self.serviceItems[item][u'service_item'].name.lower(), u'L:%s' % self.serviceItems[item][u'service_item'].edit_id) @@ -1297,7 +1314,7 @@ class ServiceManager(QtGui.QWidget): serviceItem = self.serviceItems[pos] if (plugin == serviceItem[u'service_item'].name and serviceItem[u'service_item'].is_capable( - ItemCapabilities.AllowsAdditions)): + ItemCapabilities.CanAppend)): action = self.dndMenu.exec_(QtGui.QCursor.pos()) # New action required if action == self.newAction: diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 0f83cbc30..3000bb617 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -256,6 +256,12 @@ class SlideController(QtGui.QWidget): self.songMenu.setMenu(QtGui.QMenu( translate('OpenLP.SlideController', 'Go To'), self.toolbar)) self.toolbar.makeWidgetsInvisible([u'Song Menu']) + # Stuff for items with background audio. + self.audioPauseItem = self.toolbar.addToolbarButton( + u'Pause Audio', u':/slides/media_playback_pause.png', + translate('OpenLP.SlideController', 'Pause audio.'), + self.onAudioPauseClicked, True) + self.audioPauseItem.setVisible(False) # Build the volumeSlider. self.volumeSlider = QtGui.QSlider(QtCore.Qt.Horizontal) self.volumeSlider.setTickInterval(1) @@ -512,13 +518,13 @@ class SlideController(QtGui.QWidget): self.playSlidesOnce.setChecked(False) self.playSlidesOnce.setIcon(build_icon(u':/media/media_time.png')) self.playSlidesLoop.setChecked(False) - self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png')) + self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png')) if item.is_text(): if QtCore.QSettings().value( self.parent().songsSettingsSection + u'/display songbar', QtCore.QVariant(True)).toBool() and len(self.slideList) > 0: self.toolbar.makeWidgetsVisible([u'Song Menu']) - if item.is_capable(ItemCapabilities.AllowsLoop) and \ + if item.is_capable(ItemCapabilities.CanLoop) and \ len(item.get_frames()) > 1: self.toolbar.makeWidgetsVisible(self.loopList) if item.is_media(): @@ -538,7 +544,7 @@ class SlideController(QtGui.QWidget): self.toolbar.hide() self.mediabar.setVisible(False) self.toolbar.makeWidgetsInvisible(self.songEditList) - if item.is_capable(ItemCapabilities.AllowsEdit) and item.from_plugin: + if item.is_capable(ItemCapabilities.CanEdit) and item.from_plugin: self.toolbar.makeWidgetsVisible(self.songEditList) elif item.is_media(): self.toolbar.setVisible(False) @@ -576,7 +582,7 @@ class SlideController(QtGui.QWidget): """ Replacement item following a remote edit """ - if item.__eq__(self.serviceItem): + if item == self.serviceItem: self._processItem(item, self.previewListWidget.currentRow()) def addServiceManagerItem(self, item, slideno): @@ -586,15 +592,17 @@ class SlideController(QtGui.QWidget): Called by ServiceManager """ log.debug(u'addServiceManagerItem live = %s' % self.isLive) - # If no valid slide number is specified we take the first one. + # If no valid slide number is specified we take the first one, but we + # remember the initial value to see if we should reload the song or not + slidenum = slideno if slideno == -1: - slideno = 0 - # If service item is the same as the current on only change slide - if item.__eq__(self.serviceItem): - self.__checkUpdateSelectedSlide(slideno) + slidenum = 0 + # If service item is the same as the current one, only change slide + if slideno >= 0 and item == self.serviceItem: + self.__checkUpdateSelectedSlide(slidenum) self.slideSelected() - return - self._processItem(item, slideno) + else: + self._processItem(item, slidenum) def _processItem(self, serviceItem, slideno): """ @@ -618,6 +626,15 @@ class SlideController(QtGui.QWidget): self.previewListWidget.setColumnWidth(0, width) if self.isLive: self.songMenu.menu().clear() + self.display.audioPlayer.reset() + self.setAudioItemsVisibility(False) + self.audioPauseItem.setChecked(False) + if self.serviceItem.is_capable(ItemCapabilities.HasBackgroundAudio): + log.debug(u'Starting to play...') + self.display.audioPlayer.addToPlaylist( + self.serviceItem.background_audio) + self.display.audioPlayer.play() + self.setAudioItemsVisibility(True) row = 0 text = [] for framenumber, frame in enumerate(self.serviceItem.get_frames()): @@ -1097,6 +1114,17 @@ class SlideController(QtGui.QWidget): self.playSlidesLoop.setChecked(False) self.onToggleLoop() + def setAudioItemsVisibility(self, visible): + self.audioPauseItem.setVisible(visible) + + def onAudioPauseClicked(self, checked): + if not self.audioPauseItem.isVisible(): + return + if checked: + self.display.audioPlayer.pause() + else: + self.display.audioPlayer.play() + def timerEvent(self, event): """ If the timer event is for this window select next slide diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 9083b18a2..2872740db 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -67,7 +67,7 @@ class BibleMediaItem(MediaManagerItem): self.hasSearch = True self.search_results = {} self.second_search_results = {} - self.check_search_result() + self.checkSearchResult() QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'bibles_load_list'), self.reloadBibles) @@ -651,7 +651,7 @@ class BibleMediaItem(MediaManagerItem): elif self.search_results: self.displayResults(bible, second_bible) self.advancedSearchButton.setEnabled(True) - self.check_search_result() + self.checkSearchResult() Receiver.send_message(u'cursor_normal') Receiver.send_message(u'openlp_process_events') @@ -715,7 +715,7 @@ class BibleMediaItem(MediaManagerItem): elif self.search_results: self.displayResults(bible, second_bible) self.quickSearchButton.setEnabled(True) - self.check_search_result() + self.checkSearchResult() Receiver.send_message(u'cursor_normal') Receiver.send_message(u'openlp_process_events') @@ -863,9 +863,9 @@ class BibleMediaItem(MediaManagerItem): not second_bible: # Split the line but do not replace line breaks in renderer. service_item.add_capability(ItemCapabilities.NoLineBreaks) - service_item.add_capability(ItemCapabilities.AllowsPreview) - service_item.add_capability(ItemCapabilities.AllowsLoop) - service_item.add_capability(ItemCapabilities.AllowsWordSplit) + service_item.add_capability(ItemCapabilities.CanPreview) + service_item.add_capability(ItemCapabilities.CanLoop) + service_item.add_capability(ItemCapabilities.CanWordSplit) # Service Item: Title service_item.title = u', '.join(raw_title) # Service Item: Theme diff --git a/openlp/plugins/custom/forms/editcustomform.py b/openlp/plugins/custom/forms/editcustomform.py index a3a80caf9..0eadf6021 100644 --- a/openlp/plugins/custom/forms/editcustomform.py +++ b/openlp/plugins/custom/forms/editcustomform.py @@ -135,7 +135,7 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): self.customSlide.credits = unicode(self.creditEdit.text()) self.customSlide.theme_name = unicode(self.themeComboBox.currentText()) success = self.manager.save_object(self.customSlide) - self.mediaitem.auto_select_id = self.customSlide.id + self.mediaitem.autoSelectId = self.customSlide.id return success def onUpButtonClicked(self): diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index 693e1ef8d..ce6463741 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -132,7 +132,7 @@ class CustomMediaItem(MediaManagerItem): def loadList(self, custom_slides): # Sort out what custom we want to select after loading the list. - self.save_auto_select_id() + self.saveAutoSelectId() self.listView.clear() # Sort the customs by its title considering language specific # characters. lower() is needed for windows! @@ -144,9 +144,9 @@ class CustomMediaItem(MediaManagerItem): QtCore.Qt.UserRole, QtCore.QVariant(custom_slide.id)) self.listView.addItem(custom_name) # Auto-select the custom. - if custom_slide.id == self.auto_select_id: + if custom_slide.id == self.autoSelectId: self.listView.setCurrentItem(custom_name) - self.auto_select_id = -1 + self.autoSelectId = -1 # 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. @@ -180,7 +180,7 @@ class CustomMediaItem(MediaManagerItem): self.remoteTriggered = remote_type self.edit_custom_form.loadCustom(custom_id, (remote_type == u'P')) self.edit_custom_form.exec_() - self.auto_select_id = -1 + self.autoSelectId = -1 self.onSearchTextButtonClick() def onEditClick(self): @@ -192,7 +192,7 @@ class CustomMediaItem(MediaManagerItem): item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] self.edit_custom_form.loadCustom(item_id, False) self.edit_custom_form.exec_() - self.auto_select_id = -1 + self.autoSelectId = -1 self.onSearchTextButtonClick() def onDeleteClick(self): @@ -227,10 +227,10 @@ class CustomMediaItem(MediaManagerItem): slide = None theme = None item_id = self._getIdOfItemToGenerate(item, self.remoteCustom) - service_item.add_capability(ItemCapabilities.AllowsEdit) - service_item.add_capability(ItemCapabilities.AllowsPreview) - service_item.add_capability(ItemCapabilities.AllowsLoop) - service_item.add_capability(ItemCapabilities.AllowsVirtualSplit) + service_item.add_capability(ItemCapabilities.CanEdit) + service_item.add_capability(ItemCapabilities.CanPreview) + service_item.add_capability(ItemCapabilities.CanLoop) + service_item.add_capability(ItemCapabilities.CanSoftBreak) customSlide = self.plugin.manager.get_object(CustomSlide, item_id) title = customSlide.title credit = customSlide.credits @@ -273,7 +273,7 @@ class CustomMediaItem(MediaManagerItem): CustomSlide.theme_name.like(u'%' + self.whitespace.sub(u' ', search_keywords) + u'%'), order_by_ref=CustomSlide.title) self.loadList(search_results) - self.check_search_result() + self.checkSearchResult() def onSearchTextEditChanged(self, text): """ diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 18d5d2a1c..1073ac6ca 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -149,10 +149,10 @@ class ImageMediaItem(MediaManagerItem): if not items: return False service_item.title = unicode(self.plugin.nameStrings[u'plural']) - service_item.add_capability(ItemCapabilities.AllowsMaintain) - service_item.add_capability(ItemCapabilities.AllowsPreview) - service_item.add_capability(ItemCapabilities.AllowsLoop) - service_item.add_capability(ItemCapabilities.AllowsAdditions) + service_item.add_capability(ItemCapabilities.CanMaintain) + service_item.add_capability(ItemCapabilities.CanPreview) + service_item.add_capability(ItemCapabilities.CanLoop) + service_item.add_capability(ItemCapabilities.CanAppend) # force a nonexistent theme service_item.theme = -1 missing_items = [] diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index e3c36bd77..6bc22afba 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -31,11 +31,11 @@ import os import locale from PyQt4 import QtCore, QtGui +from PyQt4.phonon import Phonon from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \ - SettingsManager, translate, check_item_selected, Receiver + SettingsManager, translate, check_item_selected, Receiver, MediaType from openlp.core.lib.ui import UiStrings, critical_error_message_box -from PyQt4.phonon import Phonon log = logging.getLogger(__name__) @@ -48,9 +48,9 @@ class MediaMediaItem(MediaManagerItem): log.info(u'%s MediaMediaItem loaded', __name__) def __init__(self, parent, plugin, icon): - self.IconPath = u'images/image' + self.iconPath = u'images/image' self.background = False - self.PreviewFunction = CLAPPERBOARD + self.previewFunction = CLAPPERBOARD MediaManagerItem.__init__(self, parent, plugin, icon) self.singleServiceItem = False self.hasSearch = True @@ -139,8 +139,8 @@ class MediaMediaItem(MediaManagerItem): # File is no longer present critical_error_message_box( translate('MediaPlugin.MediaItem', 'Missing Media File'), - unicode(translate('MediaPlugin.MediaItem', - 'The file %s no longer exists.')) % filename) + unicode(translate('MediaPlugin.MediaItem', + 'The file %s no longer exists.')) % filename) return False self.mediaObject.stop() self.mediaObject.clearQueue() @@ -156,13 +156,16 @@ class MediaMediaItem(MediaManagerItem): or self.mediaObject.currentSource().type() \ == Phonon.MediaSource.Invalid: self.mediaObject.stop() - critical_error_message_box(UiStrings().UnsupportedFile, - UiStrings().UnsupportedFile) + critical_error_message_box( + translate('MediaPlugin.MediaItem', 'File Too Big'), + translate('MediaPlugin.MediaItem', 'The file you are ' + 'trying to load is too big. Please reduce it to less ' + 'than 50MiB.')) return False self.mediaObject.stop() service_item.media_length = self.mediaObject.totalTime() / 1000 service_item.add_capability( - ItemCapabilities.AllowsVariableStartTime) + ItemCapabilities.HasVariableStartTime) service_item.title = unicode(self.plugin.nameStrings[u'singular']) service_item.add_capability(ItemCapabilities.RequiresMedia) # force a non-existent theme @@ -217,6 +220,19 @@ class MediaMediaItem(MediaManagerItem): item_name.setToolTip(track) self.listView.addItem(item_name) + def getList(self, type=MediaType.Audio): + media = SettingsManager.load_list(self.settingsSection, u'media') + media.sort(cmp=locale.strcoll, + key=lambda filename: os.path.split(unicode(filename))[1].lower()) + ext = [] + if type == MediaType.Audio: + ext = self.plugin.audio_extensions_list + else: + ext = self.plugin.video_extensions_list + ext = map(lambda x: x[1:], ext) + media = filter(lambda x: os.path.splitext(x)[1] in ext, media) + return media + def createPhonon(self): log.debug(u'CreatePhonon') if not self.mediaObject: diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 85721c65d..f3752fd73 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -248,7 +248,7 @@ class PresentationMediaItem(MediaManagerItem): service_item.title = unicode(self.displayTypeComboBox.currentText()) service_item.shortname = unicode(self.displayTypeComboBox.currentText()) service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) - service_item.add_capability(ItemCapabilities.AllowsDetailedTitleDisplay) + service_item.add_capability(ItemCapabilities.HasDetailedTitleDisplay) shortname = service_item.shortname if shortname: for bitem in items: diff --git a/openlp/plugins/songs/forms/__init__.py b/openlp/plugins/songs/forms/__init__.py index 0c2434275..d82e3dea3 100644 --- a/openlp/plugins/songs/forms/__init__.py +++ b/openlp/plugins/songs/forms/__init__.py @@ -52,6 +52,7 @@ them separate from the functionality, so that it is easier to recreate the GUI from the .ui files later if necessary. """ +from mediafilesform import MediaFilesForm from authorsform import AuthorsForm from topicsform import TopicsForm from songbookform import SongBookForm diff --git a/openlp/plugins/songs/forms/editsongdialog.py b/openlp/plugins/songs/forms/editsongdialog.py index 26c799c00..469716f6b 100644 --- a/openlp/plugins/songs/forms/editsongdialog.py +++ b/openlp/plugins/songs/forms/editsongdialog.py @@ -28,7 +28,8 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import build_icon, translate -from openlp.core.lib.ui import UiStrings, create_accept_reject_button_box +from openlp.core.lib.ui import UiStrings, create_accept_reject_button_box, \ + create_up_down_push_button_set from openlp.plugins.songs.lib.ui import SongStrings class Ui_EditSongDialog(object): @@ -36,9 +37,11 @@ class Ui_EditSongDialog(object): editSongDialog.setObjectName(u'editSongDialog') editSongDialog.resize(650, 400) editSongDialog.setWindowIcon( - build_icon(u':/icon/openlp.org-icon-32.bmp')) + build_icon(u':/icon/openlp-logo-16x16.png')) editSongDialog.setModal(True) self.dialogLayout = QtGui.QVBoxLayout(editSongDialog) + self.dialogLayout.setSpacing(8) + self.dialogLayout.setContentsMargins(8, 8, 8, 8) self.dialogLayout.setObjectName(u'dialogLayout') self.songTabWidget = QtGui.QTabWidget(editSongDialog) self.songTabWidget.setObjectName(u'songTabWidget') @@ -246,6 +249,36 @@ class Ui_EditSongDialog(object): self.commentsLayout.addWidget(self.commentsEdit) self.themeTabLayout.addWidget(self.commentsGroupBox) self.songTabWidget.addTab(self.themeTab, u'') + # audio tab + self.audioTab = QtGui.QWidget() + self.audioTab.setObjectName(u'audioTab') + self.audioLayout = QtGui.QHBoxLayout(self.audioTab) + self.audioLayout.setObjectName(u'audioLayout') + self.audioListWidget = QtGui.QListWidget(self.audioTab) + self.audioListWidget.setObjectName(u'audioListWidget') + self.audioLayout.addWidget(self.audioListWidget) + self.audioButtonsLayout = QtGui.QVBoxLayout() + self.audioButtonsLayout.setObjectName(u'audioButtonsLayout') + self.audioAddFromFileButton = QtGui.QPushButton(self.audioTab) + self.audioAddFromFileButton.setObjectName(u'audioAddFromFileButton') + self.audioButtonsLayout.addWidget(self.audioAddFromFileButton) + self.audioAddFromMediaButton = QtGui.QPushButton(self.audioTab) + self.audioAddFromMediaButton.setObjectName(u'audioAddFromMediaButton') + self.audioButtonsLayout.addWidget(self.audioAddFromMediaButton) + self.audioRemoveButton = QtGui.QPushButton(self.audioTab) + self.audioRemoveButton.setObjectName(u'audioRemoveButton') + self.audioButtonsLayout.addWidget(self.audioRemoveButton) + self.audioRemoveAllButton = QtGui.QPushButton(self.audioTab) + self.audioRemoveAllButton.setObjectName(u'audioRemoveAllButton') + self.audioButtonsLayout.addWidget(self.audioRemoveAllButton) + self.audioButtonsLayout.addStretch(1) + self.upButton, self.downButton = \ + create_up_down_push_button_set(self) + self.audioButtonsLayout.addWidget(self.upButton) + self.audioButtonsLayout.addWidget(self.downButton) + self.audioLayout.addLayout(self.audioButtonsLayout) + self.songTabWidget.addTab(self.audioTab, u'') + # Last few bits self.dialogLayout.addWidget(self.songTabWidget) self.buttonBox = create_accept_reject_button_box(editSongDialog) self.dialogLayout.addWidget(self.buttonBox) @@ -305,6 +338,17 @@ class Ui_EditSongDialog(object): self.songTabWidget.indexOf(self.themeTab), translate('SongsPlugin.EditSongForm', 'Theme, Copyright Info && Comments')) + self.songTabWidget.setTabText( + self.songTabWidget.indexOf(self.audioTab), + translate('SongsPlugin.EditSongForm', 'Linked Audio')) + self.audioAddFromFileButton.setText( + translate('SongsPlugin.EditSongForm', 'Add &File(s)')) + self.audioAddFromMediaButton.setText( + translate('SongsPlugin.EditSongForm', 'Add &Media')) + self.audioRemoveButton.setText( + translate('SongsPlugin.EditSongForm', '&Remove')) + self.audioRemoveAllButton.setText( + translate('SongsPlugin.EditSongForm', 'Remove &All')) def editSongDialogComboBox(parent, name): """ diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index c7dbf85cf..146ef0422 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -27,15 +27,18 @@ import logging import re +import os +import shutil from PyQt4 import QtCore, QtGui -from openlp.core.lib import Receiver, translate +from openlp.core.lib import PluginStatus, Receiver, MediaType, translate from openlp.core.lib.ui import UiStrings, add_widget_completer, \ critical_error_message_box, find_and_set_in_combo_box -from openlp.plugins.songs.forms import EditVerseForm +from openlp.core.utils import AppLocation +from openlp.plugins.songs.forms import EditVerseForm, MediaFilesForm from openlp.plugins.songs.lib import SongXML, VerseType, clean_song -from openlp.plugins.songs.lib.db import Book, Song, Author, Topic +from openlp.plugins.songs.lib.db import Book, Song, Author, Topic, MediaFile from openlp.plugins.songs.lib.ui import SongStrings from editsongdialog import Ui_EditSongDialog @@ -93,6 +96,14 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.mediaitem.plugin.renderer.themeManager.onAddTheme) QtCore.QObject.connect(self.maintenanceButton, QtCore.SIGNAL(u'clicked()'), self.onMaintenanceButtonClicked) + QtCore.QObject.connect(self.audioAddFromFileButton, + QtCore.SIGNAL(u'clicked()'), self.onAudioAddFromFileButtonClicked) + QtCore.QObject.connect(self.audioAddFromMediaButton, + QtCore.SIGNAL(u'clicked()'), self.onAudioAddFromMediaButtonClicked) + QtCore.QObject.connect(self.audioRemoveButton, + QtCore.SIGNAL(u'clicked()'), self.onAudioRemoveButtonClicked) + QtCore.QObject.connect(self.audioRemoveAllButton, + QtCore.SIGNAL(u'clicked()'), self.onAudioRemoveAllButtonClicked) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'theme_update_list'), self.loadThemes) self.previewButton = QtGui.QPushButton() @@ -104,12 +115,14 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): QtCore.SIGNAL(u'clicked(QAbstractButton*)'), self.onPreview) # Create other objects and forms self.manager = manager - self.verse_form = EditVerseForm(self) + self.verseForm = EditVerseForm(self) + self.mediaForm = MediaFilesForm(self) self.initialise() self.authorsListView.setSortingEnabled(False) self.authorsListView.setAlternatingRowColors(True) self.topicsListView.setSortingEnabled(False) self.topicsListView.setAlternatingRowColors(True) + self.audioListWidget.setAlternatingRowColors(True) self.findVerseSplit = re.compile(u'---\[\]---\n', re.UNICODE) self.whitespace = re.compile(r'\W+', re.UNICODE) @@ -161,6 +174,16 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.themes.append(theme) add_widget_completer(self.themes, self.themeComboBox) + def loadMediaFiles(self): + self.audioAddFromMediaButton.setVisible(False) + for plugin in self.parent().pluginManager.plugins: + if plugin.name == u'media' and \ + plugin.status == PluginStatus.Active: + self.audioAddFromMediaButton.setVisible(True) + self.mediaForm.populateFiles( + plugin.getMediaManagerItem().getList(MediaType.Audio)) + break + def newSong(self): log.debug(u'New Song') self.song = None @@ -176,11 +199,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.verseListWidget.setRowCount(0) self.authorsListView.clear() self.topicsListView.clear() + self.audioListWidget.clear() self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason) self.songBookNumberEdit.setText(u'') self.loadAuthors() self.loadTopics() self.loadBooks() + self.loadMediaFiles() self.themeComboBox.setCurrentIndex(0) # it's a new song to preview is not possible self.previewButton.setVisible(False) @@ -201,6 +226,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.loadAuthors() self.loadTopics() self.loadBooks() + self.loadMediaFiles() self.song = self.manager.get_object(Song, id) self.titleEdit.setText(self.song.title) if self.song.alternate_title: @@ -303,6 +329,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): topic_name = QtGui.QListWidgetItem(unicode(topic.name)) topic_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(topic.id)) self.topicsListView.addItem(topic_name) + self.audioListWidget.clear() + for media in self.song.media_files: + media_file = QtGui.QListWidgetItem(os.path.split(media.file_name)[1]) + media_file.setData(QtCore.Qt.UserRole, QtCore.QVariant(media.file_name)) + self.audioListWidget.addItem(media_file) self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason) # Hide or show the preview button. self.previewButton.setVisible(preview) @@ -436,9 +467,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.verseDeleteButton.setEnabled(True) def onVerseAddButtonClicked(self): - self.verse_form.setVerse(u'', True) - if self.verse_form.exec_(): - after_text, verse_tag, verse_num = self.verse_form.getVerse() + self.verseForm.setVerse(u'', True) + if self.verseForm.exec_(): + after_text, verse_tag, verse_num = self.verseForm.getVerse() verse_def = u'%s%s' % (verse_tag, verse_num) item = QtGui.QTableWidgetItem(after_text) item.setData(QtCore.Qt.UserRole, QtCore.QVariant(verse_def)) @@ -454,20 +485,21 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): if item: tempText = item.text() verseId = unicode(item.data(QtCore.Qt.UserRole).toString()) - self.verse_form.setVerse(tempText, True, verseId) - if self.verse_form.exec_(): - after_text, verse_tag, verse_num = self.verse_form.getVerse() + self.verseForm.setVerse(tempText, True, verseId) + if self.verseForm.exec_(): + after_text, verse_tag, verse_num = self.verseForm.getVerse() verse_def = u'%s%s' % (verse_tag, verse_num) item.setData(QtCore.Qt.UserRole, QtCore.QVariant(verse_def)) item.setText(after_text) - # number of lines has change so repaint the list moving the data + # number of lines has changed, repaint the list moving the data if len(tempText.split(u'\n')) != len(after_text.split(u'\n')): tempList = {} tempId = {} for row in range(0, self.verseListWidget.rowCount()): - tempList[row] = self.verseListWidget.item(row, 0).text() - tempId[row] = self.verseListWidget.item(row, 0).\ - data(QtCore.Qt.UserRole) + tempList[row] = self.verseListWidget.item(row, 0)\ + .text() + tempId[row] = self.verseListWidget.item(row, 0)\ + .data(QtCore.Qt.UserRole) self.verseListWidget.clear() for row in range (0, len(tempList)): item = QtGui.QTableWidgetItem(tempList[row], 0) @@ -486,12 +518,12 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): verse_list += u'---[%s:%s]---\n' % (verse_tag, verse_num) verse_list += item.text() verse_list += u'\n' - self.verse_form.setVerse(verse_list) + self.verseForm.setVerse(verse_list) else: - self.verse_form.setVerse(u'') - if not self.verse_form.exec_(): + self.verseForm.setVerse(u'') + if not self.verseForm.exec_(): return - verse_list = self.verse_form.getVerseAll() + verse_list = self.verseForm.getVerseAll() verse_list = unicode(verse_list.replace(u'\r\n', u'\n')) self.verseListWidget.clear() self.verseListWidget.setRowCount(0) @@ -670,6 +702,66 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.saveSong(True) Receiver.send_message(u'songs_preview') + def onAudioAddFromFileButtonClicked(self): + """ + Loads file(s) from the filesystem. + """ + filters = u'%s (*)' % UiStrings().AllFiles + filenames = QtGui.QFileDialog.getOpenFileNames(self, + translate('SongsPlugin.EditSongForm', 'Open File(s)'), + QtCore.QString(), filters) + for filename in filenames: + item = QtGui.QListWidgetItem(os.path.split(unicode(filename))[1]) + item.setData(QtCore.Qt.UserRole, filename) + self.audioListWidget.addItem(item) + + def onAudioAddFromMediaButtonClicked(self): + """ + Loads file(s) from the media plugin. + """ + if self.mediaForm.exec_(): + for filename in self.mediaForm.getSelectedFiles(): + item = QtGui.QListWidgetItem(os.path.split(unicode(filename))[1]) + item.setData(QtCore.Qt.UserRole, filename) + self.audioListWidget.addItem(item) + + def onAudioRemoveButtonClicked(self): + """ + Removes a file from the list. + """ + row = self.audioListWidget.currentRow() + if row == -1: + return + self.audioListWidget.takeItem(row) + + def onAudioRemoveAllButtonClicked(self): + """ + Removes all files from the list. + """ + self.audioListWidget.clear() + + def onUpButtonClicked(self): + """ + Moves a file up when the user clicks the up button on the audio tab. + """ + row = self.audioListWidget.currentRow() + if row <= 0: + return + item = self.audioListWidget.takeItem(row) + self.audioListWidget.insertItem(row - 1, item) + self.audioListWidget.setCurrentRow(row - 1) + + def onDownButtonClicked(self): + """ + Moves a file down when the user clicks the up button on the audio tab. + """ + row = self.audioListWidget.currentRow() + if row == -1 or row > self.audioListWidget.count() - 1: + return + item = self.audioListWidget.takeItem(row) + self.audioListWidget.insertItem(row + 1, item) + self.audioListWidget.setCurrentRow(row + 1) + def clearCaches(self): """ Free up autocompletion memory on dialog exit @@ -744,18 +836,55 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.song.theme_name = None self._processLyrics() self.song.authors = [] - for row in range(self.authorsListView.count()): + for row in xrange(self.authorsListView.count()): item = self.authorsListView.item(row) authorId = (item.data(QtCore.Qt.UserRole)).toInt()[0] self.song.authors.append(self.manager.get_object(Author, authorId)) self.song.topics = [] - for row in range(self.topicsListView.count()): + for row in xrange(self.topicsListView.count()): item = self.topicsListView.item(row) topicId = (item.data(QtCore.Qt.UserRole)).toInt()[0] self.song.topics.append(self.manager.get_object(Topic, topicId)) + # 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) - self.mediaitem.auto_select_id = self.song.id + audio_files = map(lambda a: a.file_name, self.song.media_files) + log.debug(audio_files) + save_path = os.path.join( + AppLocation.get_section_data_path(self.mediaitem.plugin.name), + 'audio', str(self.song.id)) + if not os.path.exists(save_path): + os.makedirs(save_path) + self.song.media_files = [] + files = [] + for row in xrange(self.audioListWidget.count()): + item = self.audioListWidget.item(row) + filename = unicode(item.data(QtCore.Qt.UserRole).toString()) + if not filename.startswith(save_path): + oldfile, filename = filename, os.path.join(save_path, + os.path.split(filename)[1]) + shutil.copyfile(oldfile, filename) + files.append(filename) + media_file = MediaFile() + media_file.file_name = filename + media_file.type = u'audio' + media_file.weight = row + self.song.media_files.append(media_file) + for audio in audio_files: + if audio not in files: + try: + os.remove(audio) + except: + log.exception('Could not remove file: %s', audio) + pass + if not files: + try: + os.rmdir(save_path) + except OSError: + log.exception(u'Could not remove directory: %s', save_path) + clean_song(self.manager, self.song) + self.manager.save_object(self.song) + self.mediaitem.autoSelectId = self.song.id def _processLyrics(self): """ @@ -783,3 +912,4 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): except: log.exception(u'Problem processing song Lyrics \n%s', sxml.dump_xml()) + diff --git a/openlp/plugins/songs/forms/mediafilesdialog.py b/openlp/plugins/songs/forms/mediafilesdialog.py new file mode 100644 index 000000000..252dac5ff --- /dev/null +++ b/openlp/plugins/songs/forms/mediafilesdialog.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import translate, build_icon + +class Ui_MediaFilesDialog(object): + def setupUi(self, mediaFilesDialog): + mediaFilesDialog.setObjectName(u'mediaFilesDialog') + mediaFilesDialog.setWindowModality(QtCore.Qt.ApplicationModal) + mediaFilesDialog.resize(400, 300) + mediaFilesDialog.setModal(True) + mediaFilesDialog.setWindowIcon( + build_icon(u':/icon/openlp-logo-16x16.png')) + self.filesVerticalLayout = QtGui.QVBoxLayout(mediaFilesDialog) + self.filesVerticalLayout.setSpacing(8) + self.filesVerticalLayout.setMargin(8) + self.filesVerticalLayout.setObjectName(u'filesVerticalLayout') + self.selectLabel = QtGui.QLabel(mediaFilesDialog) + self.selectLabel.setWordWrap(True) + self.selectLabel.setObjectName(u'selectLabel') + self.filesVerticalLayout.addWidget(self.selectLabel) + self.fileListWidget = QtGui.QListWidget(mediaFilesDialog) + self.fileListWidget.setAlternatingRowColors(True) + self.fileListWidget.setSelectionMode( + QtGui.QAbstractItemView.ExtendedSelection) + self.fileListWidget.setObjectName(u'fileListWidget') + self.filesVerticalLayout.addWidget(self.fileListWidget) + self.buttonBox = QtGui.QDialogButtonBox(mediaFilesDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons( + QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(u'buttonBox') + self.filesVerticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(mediaFilesDialog) + QtCore.QObject.connect(self.buttonBox, + QtCore.SIGNAL(u'accepted()'), mediaFilesDialog.accept) + QtCore.QObject.connect(self.buttonBox, + QtCore.SIGNAL(u'rejected()'), mediaFilesDialog.reject) + QtCore.QMetaObject.connectSlotsByName(mediaFilesDialog) + + def retranslateUi(self, mediaFilesDialog): + mediaFilesDialog.setWindowTitle( + translate('SongsPlugin.MediaFilesForm', 'Select Media File(s)')) + self.selectLabel.setText( + translate('SongsPlugin.MediaFilesForm', u'Select one or more ' + 'audio files from the list below, and click OK to import them ' + 'into this song.')) + diff --git a/openlp/plugins/songs/forms/mediafilesform.py b/openlp/plugins/songs/forms/mediafilesform.py new file mode 100644 index 000000000..34dac0390 --- /dev/null +++ b/openlp/plugins/songs/forms/mediafilesform.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +import logging +import os + +from PyQt4 import QtCore, QtGui + +from mediafilesdialog import Ui_MediaFilesDialog + +log = logging.getLogger(__name__) + +class MediaFilesForm(QtGui.QDialog, Ui_MediaFilesDialog): + """ + Class to show a list of files from the + """ + log.info(u'%s MediaFilesForm loaded', __name__) + + def __init__(self, parent): + QtGui.QDialog.__init__(self) + self.setupUi(self) + + def populateFiles(self, files): + self.fileListWidget.clear() + for file in files: + item = QtGui.QListWidgetItem(os.path.split(file)[1]) + item.setData(QtCore.Qt.UserRole, file) + self.fileListWidget.addItem(item) + + def getSelectedFiles(self): + return map(lambda x: unicode(x.data(QtCore.Qt.UserRole).toString()), + self.fileListWidget.selectedItems()) + diff --git a/openlp/plugins/songs/lib/db.py b/openlp/plugins/songs/lib/db.py index 5bfa0c830..37ee42451 100644 --- a/openlp/plugins/songs/lib/db.py +++ b/openlp/plugins/songs/lib/db.py @@ -232,7 +232,8 @@ def init_schema(url): 'authors': relation(Author, backref='songs', secondary=authors_songs_table, lazy=False), 'book': relation(Book, backref='songs'), - 'media_files': relation(MediaFile, backref='songs'), + 'media_files': relation(MediaFile, backref='songs', + order_by=media_files_table.c.weight), 'topics': relation(Topic, backref='songs', secondary=songs_topics_table) }) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index a2814a1df..f00f7e9be 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -28,6 +28,8 @@ import logging import locale import re +import os +import shutil from PyQt4 import QtCore, QtGui from sqlalchemy.sql import or_ @@ -37,11 +39,12 @@ from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.ui import UiStrings, context_menu_action, \ context_menu_separator +from openlp.core.utils import AppLocation from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \ SongImportForm, SongExportForm from openlp.plugins.songs.lib import OpenLyrics, SongXML, VerseType, \ clean_string -from openlp.plugins.songs.lib.db import Author, Song +from openlp.plugins.songs.lib.db import Author, Song, MediaFile from openlp.plugins.songs.lib.ui import SongStrings log = logging.getLogger(__name__) @@ -79,6 +82,22 @@ class SongMediaItem(MediaManagerItem): self.quickPreviewAllowed = True self.hasSearch = True + def _updateBackgroundAudio(self, song, item): + song.media_files = [] + for i, bga in enumerate(item.background_audio): + dest_file = os.path.join( + AppLocation.get_section_data_path(self.plugin.name), + u'audio', str(song.id), os.path.split(bga)[1]) + if not os.path.exists(os.path.split(dest_file)[0]): + os.makedirs(os.path.split(dest_file)[0]) + shutil.copyfile(os.path.join( + AppLocation.get_section_data_path( + u'servicemanager'), bga), + dest_file) + song.media_files.append(MediaFile.populate( + weight=i, file_name=dest_file)) + self.plugin.manager.save_object(song, True) + def addEndHeaderBar(self): self.addToolbarSeparator() ## Song Maintenance Button ## @@ -210,7 +229,7 @@ class SongMediaItem(MediaManagerItem): search_results = self.plugin.manager.get_all_objects(Song, Song.theme_name.like(u'%' + search_keywords + u'%')) self.displayResultsSong(search_results) - self.check_search_result() + self.checkSearchResult() def searchEntire(self, search_keywords): return self.plugin.manager.get_all_objects(Song, @@ -244,7 +263,7 @@ class SongMediaItem(MediaManagerItem): def displayResultsSong(self, searchresults): log.debug(u'display results Song') - self.save_auto_select_id() + self.saveAutoSelectId() self.listView.clear() # Sort the songs by its title considering language specific characters. # lower() is needed for windows! @@ -258,9 +277,9 @@ class SongMediaItem(MediaManagerItem): song_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(song.id)) self.listView.addItem(song_name) # Auto-select the item if name has been set - if song.id == self.auto_select_id: + if song.id == self.autoSelectId: self.listView.setCurrentItem(song_name) - self.auto_select_id = -1 + self.autoSelectId = -1 def displayResultsAuthor(self, searchresults): log.debug(u'display results Author') @@ -312,7 +331,7 @@ class SongMediaItem(MediaManagerItem): self.edit_song_form.exec_() self.onClearTextButtonClick() self.onSelectionChange() - self.auto_select_id = -1 + self.autoSelectId = -1 def onSongMaintenanceClick(self): self.song_maintenance_form.exec_() @@ -335,9 +354,9 @@ class SongMediaItem(MediaManagerItem): if valid: self.remoteSong = song_id self.remoteTriggered = remote_type - self.edit_song_form.loadSong(song_id, (remote_type == u'P')) + self.edit_song_form.loadSong(song_id, remote_type == u'P') self.edit_song_form.exec_() - self.auto_select_id = -1 + self.autoSelectId = -1 self.onSongListLoad() def onEditClick(self): @@ -350,7 +369,7 @@ class SongMediaItem(MediaManagerItem): item_id = (self.editItem.data(QtCore.Qt.UserRole)).toInt()[0] self.edit_song_form.loadSong(item_id, False) self.edit_song_form.exec_() - self.auto_select_id = -1 + self.autoSelectId = -1 self.onSongListLoad() self.editItem = None @@ -395,12 +414,12 @@ class SongMediaItem(MediaManagerItem): def generateSlideData(self, service_item, item=None, xmlVersion=False): log.debug(u'generateSlideData (%s:%s)' % (service_item, item)) item_id = self._getIdOfItemToGenerate(item, self.remoteSong) - service_item.add_capability(ItemCapabilities.AllowsEdit) - service_item.add_capability(ItemCapabilities.AllowsPreview) - service_item.add_capability(ItemCapabilities.AllowsLoop) + service_item.add_capability(ItemCapabilities.CanEdit) + service_item.add_capability(ItemCapabilities.CanPreview) + service_item.add_capability(ItemCapabilities.CanLoop) service_item.add_capability(ItemCapabilities.OnLoadUpdate) service_item.add_capability(ItemCapabilities.AddIfNewItem) - service_item.add_capability(ItemCapabilities.AllowsVirtualSplit) + service_item.add_capability(ItemCapabilities.CanSoftBreak) song = self.plugin.manager.get_object(Song, item_id) service_item.theme = song.theme_name service_item.edit_id = item_id @@ -471,6 +490,10 @@ class SongMediaItem(MediaManagerItem): service_item.data_string = {u'title': song.search_title, u'authors': u', '.join(author_list)} service_item.xml_version = self.openLyrics.song_to_xml(song) + # Add the audio file to the service item. + if len(song.media_files) > 0: + service_item.add_capability(ItemCapabilities.HasBackgroundAudio) + service_item.background_audio = [m.file_name for m in song.media_files] return True def serviceLoad(self, item): @@ -510,8 +533,15 @@ class SongMediaItem(MediaManagerItem): add_song = False editId = song.id break + # If there's any backing tracks, copy them over. + if len(item.background_audio) > 0: + self._updateBackgroundAudio(song, item) if add_song and self.addSongFromService: - editId = self.openLyrics.xml_to_song(item.xml_version) + song = self.openLyrics.xml_to_song(item.xml_version) + # If there's any backing tracks, copy them over. + if len(item.background_audio) > 0: + self._updateBackgroundAudio(song, item) + editId = song.id self.onSearchTextButtonClick() # Update service with correct song id. if editId: diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index 193a823d5..9da7a0a65 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -343,7 +343,7 @@ class OpenLyrics(object): self._process_topics(properties, song) clean_song(self.manager, song) self.manager.save_object(song) - return song.id + return song def _add_text_to_element(self, tag, parent, text=None, label=None): if label: diff --git a/resources/forms/mediafilesdialog.ui b/resources/forms/mediafilesdialog.ui new file mode 100644 index 000000000..427e27548 --- /dev/null +++ b/resources/forms/mediafilesdialog.ui @@ -0,0 +1,95 @@ + + + MediaFilesDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 400 + 300 + + + + Select Media File(s) + + + true + + + + 8 + + + 8 + + + + + Select one or more audio files from the list below, and click OK to import them into this song. + + + true + + + + + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + MediaFilesDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MediaFilesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +