This commit is contained in:
Tomas Groth 2019-06-07 22:25:30 +02:00
commit 3861d340a3
22 changed files with 338 additions and 219 deletions

View File

@ -1,27 +0,0 @@
[unittest]
verbose = true
plugins = nose2.plugins.mp
[log-capture]
always-on = true
clear-handlers = true
filter = -nose
log-level = ERROR
[test-result]
always-on = true
descriptions = true
[coverage]
always-on = true
coverage = openlp
coverage-report = html
[multiprocess]
always-on = false
processes = 4
[output-buffer]
always-on = true
stderr = true
stdout = true

View File

@ -23,7 +23,6 @@ from contextlib import suppress
from json import JSONDecoder, JSONEncoder from json import JSONDecoder, JSONEncoder
from pathlib import Path from pathlib import Path
_registered_classes = {} _registered_classes = {}

View File

@ -146,7 +146,7 @@ class Registry(object):
try: try:
log.debug('Running function {} for {}'.format(function, event)) log.debug('Running function {} for {}'.format(function, event))
result = function(*args, **kwargs) result = function(*args, **kwargs)
if result: if result is not None:
results.append(result) results.append(result)
except TypeError: except TypeError:
# Who has called me can help in debugging # Who has called me can help in debugging

View File

@ -589,7 +589,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
""" """
Add this item to the current service. Add this item to the current service.
:param item: Item to be processed :param QtWidgets.QListWidgetItem | QtWidgets.QTreeWidgetItem | None item: Item to be processed
:param replace: Replace the existing item :param replace: Replace the existing item
:param remote: Triggered from remote :param remote: Triggered from remote
:param position: Position to place item :param position: Position to place item
@ -627,7 +627,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
def build_service_item(self, item=None, remote=False, context=ServiceItemContext.Live): def build_service_item(self, item=None, remote=False, context=ServiceItemContext.Live):
""" """
Common method for generating a service item Common method for generating a service item
:param item: Service Item to be built. :param QtWidgets.QListWidgetItem | QtWidgets.QTreeWidgetItem | None item: Service Item to be built.
:param remote: Remote triggered (False) :param remote: Remote triggered (False)
:param context: The context on which this is called :param context: The context on which this is called
""" """

View File

@ -593,9 +593,11 @@ class ServiceItem(RegistryProperties):
""" """
return not bool(self.slides) return not bool(self.slides)
def validate_item(self, suffix_list=None): def validate_item(self, suffixes=None):
""" """
Validates a service item to make sure it is valid Validates a service item to make sure it is valid
:param set[str] suffixes: A set of vaild suffixes
""" """
self.is_valid = True self.is_valid = True
for slide in self.slides: for slide in self.slides:
@ -612,8 +614,8 @@ class ServiceItem(RegistryProperties):
if not os.path.exists(file_name): if not os.path.exists(file_name):
self.is_valid = False self.is_valid = False
break break
if suffix_list and not self.is_text(): if suffixes and not self.is_text():
file_suffix = slide['title'].split('.')[-1] file_suffix = slide['title'].split('.')[-1]
if file_suffix.lower() not in suffix_list: if file_suffix.lower() not in suffixes:
self.is_valid = False self.is_valid = False
break break

View File

@ -44,7 +44,7 @@ from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui import DisplayControllerType from openlp.core.ui import DisplayControllerType
from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path
from openlp.core.ui.media.endpoint import media_endpoint from openlp.core.ui.media.endpoint import media_endpoint
from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc from openlp.core.ui.media.vlcplayer import AUDIO_EXT, VIDEO_EXT, VlcPlayer, get_vlc
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -65,11 +65,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
current_media_players is an array of player instances keyed on ControllerType. current_media_players is an array of player instances keyed on ControllerType.
""" """
def __init__(self, parent=None):
"""
Constructor
"""
super(MediaController, self).__init__(parent)
def setup(self): def setup(self):
self.vlc_player = None self.vlc_player = None
@ -95,28 +90,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
Registry().register_function('songs_hide', self.media_hide) Registry().register_function('songs_hide', self.media_hide)
Registry().register_function('songs_blank', self.media_blank) Registry().register_function('songs_blank', self.media_blank)
Registry().register_function('songs_unblank', self.media_unblank) Registry().register_function('songs_unblank', self.media_unblank)
Registry().register_function('mediaitem_suffixes', self._generate_extensions_lists)
register_endpoint(media_endpoint) register_endpoint(media_endpoint)
def _generate_extensions_lists(self):
"""
Set the active players and available media files
"""
suffix_list = []
self.audio_extensions_list = []
if self.vlc_player.is_active:
for item in self.vlc_player.audio_extensions_list:
if item not in self.audio_extensions_list:
self.audio_extensions_list.append(item)
suffix_list.append(item[2:])
self.video_extensions_list = []
if self.vlc_player.is_active:
for item in self.vlc_player.video_extensions_list:
if item not in self.video_extensions_list:
self.video_extensions_list.append(item)
suffix_list.append(item[2:])
self.service_manager.supported_suffixes(suffix_list)
def bootstrap_initialise(self): def bootstrap_initialise(self):
""" """
Check to see if we have any media Player's available. Check to see if we have any media Player's available.
@ -131,7 +106,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
else: else:
State().missing_text('media_live', translate('OpenLP.SlideController', State().missing_text('media_live', translate('OpenLP.SlideController',
'VLC or pymediainfo are missing, so you are unable to play any media')) 'VLC or pymediainfo are missing, so you are unable to play any media'))
self._generate_extensions_lists()
return True return True
def bootstrap_post_set_up(self): def bootstrap_post_set_up(self):
@ -381,7 +355,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
if file.is_file: if file.is_file:
suffix = '*%s' % file.suffix.lower() suffix = '*%s' % file.suffix.lower()
file = str(file) file = str(file)
if suffix in self.vlc_player.video_extensions_list: if suffix in VIDEO_EXT:
if not controller.media_info.is_background or controller.media_info.is_background and \ if not controller.media_info.is_background or controller.media_info.is_background and \
self.vlc_player.can_background: self.vlc_player.can_background:
self.resize(display, self.vlc_player) self.resize(display, self.vlc_player)
@ -389,7 +363,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
self.current_media_players[controller.controller_type] = self.vlc_player self.current_media_players[controller.controller_type] = self.vlc_player
controller.media_info.media_type = MediaType.Video controller.media_info.media_type = MediaType.Video
return True return True
if suffix in self.vlc_player.audio_extensions_list: if suffix in AUDIO_EXT:
if self.vlc_player.load(display, file): if self.vlc_player.load(display, file):
self.current_media_players[controller.controller_type] = self.vlc_player self.current_media_players[controller.controller_type] = self.vlc_player
controller.media_info.media_type = MediaType.Audio controller.media_info.media_type = MediaType.Audio

View File

@ -38,13 +38,10 @@ class MediaPlayer(RegistryProperties):
self.parent = parent self.parent = parent
self.name = name self.name = name
self.available = self.check_available() self.available = self.check_available()
self.is_active = False
self.can_background = False self.can_background = False
self.can_folder = False self.can_folder = False
self.state = {0: MediaState.Off, 1: MediaState.Off} self.state = {0: MediaState.Off, 1: MediaState.Off}
self.has_own_widget = False self.has_own_widget = False
self.audio_extensions_list = []
self.video_extensions_list = []
def check_available(self): def check_available(self):
""" """
@ -166,12 +163,6 @@ class MediaPlayer(RegistryProperties):
""" """
return '' return ''
def get_info(self):
"""
Returns Information about the player
"""
return ''
def get_live_state(self): def get_live_state(self):
""" """
Get the state of the live player Get the state of the live player

View File

@ -32,7 +32,6 @@ from datetime import datetime
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from openlp.core.common import is_linux, is_macosx, is_win from openlp.core.common import is_linux, is_macosx, is_win
from openlp.core.common.i18n import translate
from openlp.core.common.settings import Settings from openlp.core.common.settings import Settings
from openlp.core.ui.media import MediaState, MediaType from openlp.core.ui.media import MediaState, MediaType
from openlp.core.ui.media.mediaplayer import MediaPlayer from openlp.core.ui.media.mediaplayer import MediaPlayer
@ -41,20 +40,18 @@ from openlp.core.ui.media.mediaplayer import MediaPlayer
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# Audio and video extensions copied from 'include/vlc_interface.h' from vlc 2.2.0 source # Audio and video extensions copied from 'include/vlc_interface.h' from vlc 2.2.0 source
AUDIO_EXT = ['*.3ga', '*.669', '*.a52', '*.aac', '*.ac3', '*.adt', '*.adts', '*.aif', '*.aifc', '*.aiff', '*.amr', AUDIO_EXT = ('3ga', '669', 'a52', 'aac', 'ac3', 'adt', 'adts', 'aif', 'aifc', 'aiff', 'amr', 'aob', 'ape', 'awb', 'caf',
'*.aob', '*.ape', '*.awb', '*.caf', '*.dts', '*.flac', '*.it', '*.kar', '*.m4a', '*.m4b', '*.m4p', '*.m5p', 'dts', 'flac', 'it', 'kar', 'm4a', 'm4b', 'm4p', 'm5p', 'mid', 'mka', 'mlp', 'mod', 'mpa', 'mp1', 'mp2',
'*.mid', '*.mka', '*.mlp', '*.mod', '*.mpa', '*.mp1', '*.mp2', '*.mp3', '*.mpc', '*.mpga', '*.mus', 'mp3', 'mpc', 'mpga', 'mus', 'oga', 'ogg', 'oma', 'opus', 'qcp', 'ra', 'rmi', 's3m', 'sid', 'spx', 'thd',
'*.oga', '*.ogg', '*.oma', '*.opus', '*.qcp', '*.ra', '*.rmi', '*.s3m', '*.sid', '*.spx', '*.thd', '*.tta', 'tta', 'voc', 'vqf', 'w64', 'wav', 'wma', 'wv', 'xa', 'xm')
'*.voc', '*.vqf', '*.w64', '*.wav', '*.wma', '*.wv', '*.xa', '*.xm']
VIDEO_EXT = ['*.3g2', '*.3gp', '*.3gp2', '*.3gpp', '*.amv', '*.asf', '*.avi', '*.bik', '*.divx', '*.drc', '*.dv', VIDEO_EXT = ('3g2', '3gp', '3gp2', '3gpp', 'amv', 'asf', 'avi', 'bik', 'divx', 'drc', 'dv', 'f4v', 'flv', 'gvi', 'gxf',
'*.f4v', '*.flv', '*.gvi', '*.gxf', '*.iso', '*.m1v', '*.m2v', '*.m2t', '*.m2ts', '*.m4v', '*.mkv', 'iso', 'm1v', 'm2v', 'm2t', 'm2ts', 'm4v', 'mkv', 'mov', 'mp2', 'mp2v', 'mp4', 'mp4v', 'mpe', 'mpeg',
'*.mov', '*.mp2', '*.mp2v', '*.mp4', '*.mp4v', '*.mpe', '*.mpeg', '*.mpeg1', '*.mpeg2', '*.mpeg4', '*.mpg', 'mpeg1', 'mpeg2', 'mpeg4', 'mpg', 'mpv2', 'mts', 'mtv', 'mxf', 'mxg', 'nsv', 'nuv', 'ogg', 'ogm', 'ogv',
'*.mpv2', '*.mts', '*.mtv', '*.mxf', '*.mxg', '*.nsv', '*.nuv', '*.ogg', '*.ogm', '*.ogv', '*.ogx', '*.ps', 'ogx', 'ps', 'rec', 'rm', 'rmvb', 'rpl', 'thp', 'tod', 'ts', 'tts', 'txd', 'vob', 'vro', 'webm', 'wm',
'*.rec', '*.rm', '*.rmvb', '*.rpl', '*.thp', '*.tod', '*.ts', '*.tts', '*.txd', '*.vob', '*.vro', '*.webm', 'wmv', 'wtv', 'xesc',
'*.wm', '*.wmv', '*.wtv', '*.xesc',
# These extensions was not in the official list, added manually. # These extensions was not in the official list, added manually.
'*.nut', '*.rv', '*.xvid'] 'nut', 'rv', 'xvid')
def get_vlc(): def get_vlc():
@ -110,8 +107,6 @@ class VlcPlayer(MediaPlayer):
self.display_name = '&VLC' self.display_name = '&VLC'
self.parent = parent self.parent = parent
self.can_folder = True self.can_folder = True
self.audio_extensions_list = AUDIO_EXT
self.video_extensions_list = VIDEO_EXT
def setup(self, output_display, live_display): def setup(self, output_display, live_display):
""" """
@ -375,14 +370,3 @@ class VlcPlayer(MediaPlayer):
else: else:
controller.seek_slider.setSliderPosition(output_display.vlc_media_player.get_time()) controller.seek_slider.setSliderPosition(output_display.vlc_media_player.get_time())
controller.seek_slider.blockSignals(False) controller.seek_slider.blockSignals(False)
def get_info(self):
"""
Return some information about this player
"""
return(translate('Media.player', 'VLC is an external player which '
'supports a number of different formats.') +
'<br/> <strong>' + translate('Media.player', 'Audio') +
'</strong><br/>' + str(AUDIO_EXT) + '<br/><strong>' +
translate('Media.player', 'Video') + '</strong><br/>' +
str(VIDEO_EXT) + '<br/>')

View File

@ -48,6 +48,7 @@ from openlp.core.lib.plugin import PluginStatus
from openlp.core.lib.serviceitem import ItemCapabilities, ServiceItem from openlp.core.lib.serviceitem import ItemCapabilities, ServiceItem
from openlp.core.lib.ui import create_widget_action, critical_error_message_box, find_and_set_in_combo_box from openlp.core.lib.ui import create_widget_action, critical_error_message_box, find_and_set_in_combo_box
from openlp.core.ui.icons import UiIcons from openlp.core.ui.icons import UiIcons
from openlp.core.ui.media.vlcplayer import AUDIO_EXT, VIDEO_EXT
from openlp.core.ui.serviceitemeditform import ServiceItemEditForm from openlp.core.ui.serviceitemeditform import ServiceItemEditForm
from openlp.core.ui.servicenoteform import ServiceNoteForm from openlp.core.ui.servicenoteform import ServiceNoteForm
from openlp.core.ui.starttimeform import StartTimeForm from openlp.core.ui.starttimeform import StartTimeForm
@ -320,7 +321,8 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
""" """
super().__init__(parent) super().__init__(parent)
self.service_items = [] self.service_items = []
self.suffixes = [] self.suffixes = set()
self.add_media_suffixes()
self.drop_position = -1 self.drop_position = -1
self.service_id = 0 self.service_id = 0
# is a new service and has not been saved # is a new service and has not been saved
@ -347,6 +349,13 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
self.service_item_edit_form = ServiceItemEditForm() self.service_item_edit_form = ServiceItemEditForm()
self.start_time_form = StartTimeForm() self.start_time_form = StartTimeForm()
def add_media_suffixes(self):
"""
Add the suffixes supported by :mod:`openlp.core.ui.media.vlcplayer`
"""
self.suffixes.update(AUDIO_EXT)
self.suffixes.update(VIDEO_EXT)
def set_modified(self, modified=True): def set_modified(self, modified=True):
""" """
Setter for property "modified". Sets whether or not the current service has been modified. Setter for property "modified". Sets whether or not the current service has been modified.
@ -401,22 +410,19 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
def reset_supported_suffixes(self): def reset_supported_suffixes(self):
""" """
Resets the Suffixes list. Resets the Suffixes list.
""" """
self.suffixes = [] self.suffixes.clear()
def supported_suffixes(self, suffix_list): def supported_suffixes(self, suffix_list):
""" """
Adds Suffixes supported to the master list. Called from Plugins. Adds Suffixes supported to the master list. Called from Plugins.
:param suffix_list: New Suffix's to be supported :param list[str] | str suffix_list: New suffix(s) to be supported
""" """
if isinstance(suffix_list, str): if isinstance(suffix_list, str):
self.suffixes.append(suffix_list) self.suffixes.add(suffix_list)
else: else:
for suffix in suffix_list: self.suffixes.update(suffix_list)
if suffix not in self.suffixes:
self.suffixes.append(suffix)
def on_new_service_clicked(self): def on_new_service_clicked(self):
""" """
@ -475,9 +481,11 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
QtWidgets.QMessageBox.Save | QtWidgets.QMessageBox.Discard | QtWidgets.QMessageBox.Save | QtWidgets.QMessageBox.Discard |
QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Save) QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Save)
def on_recent_service_clicked(self): def on_recent_service_clicked(self, checked):
""" """
Load a recent file as the service triggered by mainwindow recent service list. Load a recent file as the service triggered by mainwindow recent service list.
:param bool checked: Not used
""" """
if self.is_modified(): if self.is_modified():
result = self.save_modified_service() result = self.save_modified_service()
@ -976,8 +984,10 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
prev_item_last_slide = None prev_item_last_slide = None
service_iterator = QtWidgets.QTreeWidgetItemIterator(self.service_manager_list) service_iterator = QtWidgets.QTreeWidgetItemIterator(self.service_manager_list)
while service_iterator.value(): while service_iterator.value():
# Found the selected/current service item
if service_iterator.value() == selected: if service_iterator.value() == selected:
if last_slide and prev_item_last_slide: if last_slide and prev_item_last_slide:
# Go to the last slide of the previous service item
pos = prev_item.data(0, QtCore.Qt.UserRole) pos = prev_item.data(0, QtCore.Qt.UserRole)
check_expanded = self.service_items[pos - 1]['expanded'] check_expanded = self.service_items[pos - 1]['expanded']
self.service_manager_list.setCurrentItem(prev_item_last_slide) self.service_manager_list.setCurrentItem(prev_item_last_slide)
@ -986,13 +996,17 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
self.make_live() self.make_live()
self.service_manager_list.setCurrentItem(prev_item) self.service_manager_list.setCurrentItem(prev_item)
elif prev_item: elif prev_item:
# Go to the first slide of the previous service item
self.service_manager_list.setCurrentItem(prev_item) self.service_manager_list.setCurrentItem(prev_item)
self.make_live() self.make_live()
return return
# Found the previous service item root
if service_iterator.value().parent() is None: if service_iterator.value().parent() is None:
prev_item = service_iterator.value() prev_item = service_iterator.value()
# Found the last slide of the previous item
if service_iterator.value().parent() is prev_item: if service_iterator.value().parent() is prev_item:
prev_item_last_slide = service_iterator.value() prev_item_last_slide = service_iterator.value()
# Go to next item in the tree
service_iterator += 1 service_iterator += 1
def on_set_item(self, message): def on_set_item(self, message):

View File

@ -1261,9 +1261,18 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
if not self.service_item: if not self.service_item:
return return
if self.service_item.is_command(): if self.service_item.is_command():
Registry().execute('{text}_next'.format(text=self.service_item.name.lower()), past_end = Registry().execute('{text}_next'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live]) [self.service_item, self.is_live])
if self.is_live: # Check if we have gone past the end of the last slide
if self.is_live and past_end and past_end[0]:
if wrap is None:
if self.slide_limits == SlideLimits.Wrap:
self.on_slide_selected_index([0])
elif self.is_live and self.slide_limits == SlideLimits.Next:
self.service_next()
elif wrap:
self.on_slide_selected_index([0])
elif self.is_live:
self.update_preview() self.update_preview()
else: else:
row = self.preview_widget.current_slide_number() + 1 row = self.preview_widget.current_slide_number() + 1
@ -1290,9 +1299,16 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
if not self.service_item: if not self.service_item:
return return
if self.service_item.is_command(): if self.service_item.is_command():
Registry().execute('{text}_previous'.format(text=self.service_item.name.lower()), before_start = Registry().execute('{text}_previous'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live]) [self.service_item, self.is_live])
if self.is_live: # Check id we have tried to go before that start slide
if self.is_live and before_start and before_start[0]:
if self.slide_limits == SlideLimits.Wrap:
self.on_slide_selected_index([self.preview_widget.slide_count() - 1])
elif self.is_live and self.slide_limits == SlideLimits.Next:
self.keypress_queue.append(ServiceItemAction.PreviousLastSlide)
self._process_queue()
elif self.is_live:
self.update_preview() self.update_preview()
else: else:
row = self.preview_widget.current_slide_number() - 1 row = self.preview_widget.current_slide_number() - 1

View File

@ -32,7 +32,6 @@ from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.lib.theme import BackgroundGradientType, BackgroundType from openlp.core.lib.theme import BackgroundGradientType, BackgroundType
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
# TODO: Fix this. Use a "get_video_extensions" method which uses the current media player
from openlp.core.ui.media.vlcplayer import VIDEO_EXT from openlp.core.ui.media.vlcplayer import VIDEO_EXT
from openlp.core.ui.themelayoutform import ThemeLayoutForm from openlp.core.ui.themelayoutform import ThemeLayoutForm
from openlp.core.ui.themewizard import Ui_ThemeWizard from openlp.core.ui.themewizard import Ui_ThemeWizard
@ -76,9 +75,8 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
self.image_path_edit.filters = \ self.image_path_edit.filters = \
'{name};;{text} (*)'.format(name=get_images_filter(), text=UiStrings().AllFiles) '{name};;{text} (*)'.format(name=get_images_filter(), text=UiStrings().AllFiles)
self.image_path_edit.pathChanged.connect(self.on_image_path_edit_path_changed) self.image_path_edit.pathChanged.connect(self.on_image_path_edit_path_changed)
# TODO: Should work visible_formats = '(*.{name})'.format(name='; *.'.join(VIDEO_EXT))
visible_formats = '({name})'.format(name='; '.join(VIDEO_EXT)) actual_formats = '(*.{name})'.format(name=' *.'.join(VIDEO_EXT))
actual_formats = '({name})'.format(name=' '.join(VIDEO_EXT))
video_filter = '{trans} {visible} {actual}'.format(trans=translate('OpenLP', 'Video Files'), video_filter = '{trans} {visible} {actual}'.format(trans=translate('OpenLP', 'Video Files'),
visible=visible_formats, actual=actual_formats) visible=visible_formats, actual=actual_formats)
self.video_path_edit.filters = '{video};;{ui} (*)'.format(video=video_filter, ui=UiStrings().AllFiles) self.video_path_edit.filters = '{video};;{ui} (*)'.format(video=video_filter, ui=UiStrings().AllFiles)

View File

@ -38,7 +38,7 @@ from openlp.core.lib.serviceitem import ItemCapabilities
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.icons import UiIcons from openlp.core.ui.icons import UiIcons
from openlp.core.ui.media import parse_optical_path, format_milliseconds from openlp.core.ui.media import parse_optical_path, format_milliseconds
from openlp.core.ui.media.vlcplayer import get_vlc from openlp.core.ui.media.vlcplayer import AUDIO_EXT, VIDEO_EXT, get_vlc
if get_vlc() is not None: if get_vlc() is not None:
@ -232,9 +232,9 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
""" """
# self.populate_display_types() # self.populate_display_types()
self.on_new_file_masks = translate('MediaPlugin.MediaItem', self.on_new_file_masks = translate('MediaPlugin.MediaItem',
'Videos ({video});;Audio ({audio});;{files} ' 'Videos (*.{video});;Audio (*.{audio});;{files} '
'(*)').format(video=' '.join(self.media_controller.video_extensions_list), '(*)').format(video=' *.'.join(VIDEO_EXT),
audio=' '.join(self.media_controller.audio_extensions_list), audio=' *.'.join(AUDIO_EXT),
files=UiStrings().AllFiles) files=UiStrings().AllFiles)
def on_delete_click(self): def on_delete_click(self):
@ -302,9 +302,9 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
media_file_paths = Settings().value(self.settings_section + '/media files') media_file_paths = Settings().value(self.settings_section + '/media files')
media_file_paths.sort(key=lambda file_path: get_natural_key(file_path.name)) media_file_paths.sort(key=lambda file_path: get_natural_key(file_path.name))
if media_type == MediaType.Audio: if media_type == MediaType.Audio:
extension = self.media_controller.audio_extensions_list extension = AUDIO_EXT
else: else:
extension = self.media_controller.video_extensions_list extension = VIDEO_EXT
extension = [x[1:] for x in extension] extension = [x[1:] for x in extension]
media = [x for x in media_file_paths if x.suffix in extension] media = [x for x in media_file_paths if x.suffix in extension]
return media return media

View File

@ -36,31 +36,49 @@ import time
from PyQt5 import QtCore from PyQt5 import QtCore
from openlp.core.common import delete_file, get_uno_command, get_uno_instance, is_win from openlp.core.common import delete_file, get_uno_command, get_uno_instance, is_win, trace_error_handler
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.display.screens import ScreenList from openlp.core.display.screens import ScreenList
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \ from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
TextType TextType
# Load the XSlideShowListener class so we can inherit from it
if is_win(): if is_win():
from win32com.client import Dispatch from win32com.client import Dispatch
import pywintypes import pywintypes
uno_available = False uno_available = False
# Declare an empty exception to match the exception imported from UNO try:
service_manager = Dispatch('com.sun.star.ServiceManager')
service_manager._FlagAsMethod('Bridge_GetStruct')
XSlideShowListenerObj = service_manager.Bridge_GetStruct('com.sun.star.presentation.XSlideShowListener')
class SlideShowListenerImport(XSlideShowListenerObj.__class__):
pass
except (AttributeError, pywintypes.com_error):
class SlideShowListenerImport():
pass
# Declare an empty exception to match the exception imported from UNO
class ErrorCodeIOException(Exception): class ErrorCodeIOException(Exception):
pass pass
else: else:
try: try:
import uno import uno
import unohelper
from com.sun.star.beans import PropertyValue from com.sun.star.beans import PropertyValue
from com.sun.star.task import ErrorCodeIOException from com.sun.star.task import ErrorCodeIOException
from com.sun.star.presentation import XSlideShowListener
class SlideShowListenerImport(unohelper.Base, XSlideShowListener):
pass
uno_available = True uno_available = True
except ImportError: except ImportError:
uno_available = False uno_available = False
class SlideShowListenerImport():
pass
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -82,6 +100,8 @@ class ImpressController(PresentationController):
self.process = None self.process = None
self.desktop = None self.desktop = None
self.manager = None self.manager = None
self.conf_provider = None
self.presenter_screen_disabled_by_openlp = False
def check_available(self): def check_available(self):
""" """
@ -90,8 +110,7 @@ class ImpressController(PresentationController):
log.debug('check_available') log.debug('check_available')
if is_win(): if is_win():
return self.get_com_servicemanager() is not None return self.get_com_servicemanager() is not None
else: return uno_available
return uno_available
def start_process(self): def start_process(self):
""" """
@ -131,6 +150,7 @@ class ImpressController(PresentationController):
self.manager = uno_instance.ServiceManager self.manager = uno_instance.ServiceManager
log.debug('get UNO Desktop Openoffice - createInstanceWithContext - Desktop') log.debug('get UNO Desktop Openoffice - createInstanceWithContext - Desktop')
desktop = self.manager.createInstanceWithContext("com.sun.star.frame.Desktop", uno_instance) desktop = self.manager.createInstanceWithContext("com.sun.star.frame.Desktop", uno_instance)
self.toggle_presentation_screen(False)
return desktop return desktop
except Exception: except Exception:
log.warning('Failed to get UNO desktop') log.warning('Failed to get UNO desktop')
@ -148,6 +168,7 @@ class ImpressController(PresentationController):
desktop = self.manager.createInstance('com.sun.star.frame.Desktop') desktop = self.manager.createInstance('com.sun.star.frame.Desktop')
except (AttributeError, pywintypes.com_error): except (AttributeError, pywintypes.com_error):
log.warning('Failure to find desktop - Impress may have closed') log.warning('Failure to find desktop - Impress may have closed')
self.toggle_presentation_screen(False)
return desktop if desktop else None return desktop if desktop else None
def get_com_servicemanager(self): def get_com_servicemanager(self):
@ -166,6 +187,8 @@ class ImpressController(PresentationController):
Called at system exit to clean up any running presentations. Called at system exit to clean up any running presentations.
""" """
log.debug('Kill OpenOffice') log.debug('Kill OpenOffice')
if self.presenter_screen_disabled_by_openlp:
self.toggle_presentation_screen(True)
while self.docs: while self.docs:
self.docs[0].close_presentation() self.docs[0].close_presentation()
desktop = None desktop = None
@ -195,6 +218,60 @@ class ImpressController(PresentationController):
except Exception: except Exception:
log.warning('Failed to terminate OpenOffice') log.warning('Failed to terminate OpenOffice')
def toggle_presentation_screen(self, set_visible):
"""
Enable or disable the Presentation Screen/Console
:param bool set_visible: Should the presentation screen/console be set to be visible.
:rtype: None
"""
# Create Instance of ConfigurationProvider
if not self.conf_provider:
if is_win():
self.conf_provider = self.manager.createInstance('com.sun.star.configuration.ConfigurationProvider')
else:
self.conf_provider = self.manager.createInstanceWithContext(
'com.sun.star.configuration.ConfigurationProvider', uno.getComponentContext())
# Setup lookup properties to get Impress settings
properties = []
properties.append(self.create_property('nodepath', 'org.openoffice.Office.Impress'))
properties = tuple(properties)
try:
# Get an updateable configuration view
impress_conf_props = self.conf_provider.createInstanceWithArguments(
'com.sun.star.configuration.ConfigurationUpdateAccess', properties)
# Get the specific setting for presentation screen
presenter_screen_enabled = impress_conf_props.getHierarchicalPropertyValue(
'Misc/Start/EnablePresenterScreen')
# If the presentation screen is enabled we disable it
if presenter_screen_enabled != set_visible:
impress_conf_props.setHierarchicalPropertyValue('Misc/Start/EnablePresenterScreen', set_visible)
impress_conf_props.commitChanges()
# if set_visible is False this is an attempt to disable the Presenter Screen
# so we make a note that it has been disabled, so it can be enabled again on close.
if set_visible is False:
self.presenter_screen_disabled_by_openlp = True
except Exception as e:
log.exception(e)
trace_error_handler(log)
def create_property(self, name, value):
"""
Create an OOo style property object which are passed into some Uno methods.
:param str name: The name of the property
:param str value: The value of the property
:rtype: com.sun.star.beans.PropertyValue
"""
log.debug('create property OpenOffice')
if is_win():
property_object = self.manager.Bridge_GetStruct('com.sun.star.beans.PropertyValue')
else:
property_object = PropertyValue()
property_object.Name = name
property_object.Value = value
return property_object
class ImpressDocument(PresentationDocument): class ImpressDocument(PresentationDocument):
""" """
@ -213,6 +290,8 @@ class ImpressDocument(PresentationDocument):
self.document = None self.document = None
self.presentation = None self.presentation = None
self.control = None self.control = None
self.slide_ended = False
self.slide_ended_reverse = False
def load_presentation(self): def load_presentation(self):
""" """
@ -233,13 +312,16 @@ class ImpressDocument(PresentationDocument):
return False return False
self.desktop = desktop self.desktop = desktop
properties = [] properties = []
properties.append(self.create_property('Hidden', True)) properties.append(self.controller.create_property('Hidden', True))
properties = tuple(properties) properties = tuple(properties)
try: try:
self.document = desktop.loadComponentFromURL(url, '_blank', 0, properties) self.document = desktop.loadComponentFromURL(url, '_blank', 0, properties)
except Exception: except Exception:
log.warning('Failed to load presentation {url}'.format(url=url)) log.warning('Failed to load presentation {url}'.format(url=url))
return False return False
if self.document is None:
log.warning('Presentation {url} could not be loaded'.format(url=url))
return False
self.presentation = self.document.getPresentation() self.presentation = self.document.getPresentation()
self.presentation.Display = ScreenList().current.number + 1 self.presentation.Display = ScreenList().current.number + 1
self.control = None self.control = None
@ -257,7 +339,7 @@ class ImpressDocument(PresentationDocument):
temp_folder_path = self.get_temp_folder() temp_folder_path = self.get_temp_folder()
thumb_dir_url = temp_folder_path.as_uri() thumb_dir_url = temp_folder_path.as_uri()
properties = [] properties = []
properties.append(self.create_property('FilterName', 'impress_png_Export')) properties.append(self.controller.create_property('FilterName', 'impress_png_Export'))
properties = tuple(properties) properties = tuple(properties)
doc = self.document doc = self.document
pages = doc.getDrawPages() pages = doc.getDrawPages()
@ -279,19 +361,6 @@ class ImpressDocument(PresentationDocument):
except Exception: except Exception:
log.exception('{path} - Unable to store openoffice preview'.format(path=path)) log.exception('{path} - Unable to store openoffice preview'.format(path=path))
def create_property(self, name, value):
"""
Create an OOo style property object which are passed into some Uno methods.
"""
log.debug('create property OpenOffice')
if is_win():
property_object = self.controller.manager.Bridge_GetStruct('com.sun.star.beans.PropertyValue')
else:
property_object = PropertyValue()
property_object.Name = name
property_object.Value = value
return property_object
def close_presentation(self): def close_presentation(self):
""" """
Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being
@ -356,8 +425,7 @@ class ImpressDocument(PresentationDocument):
log.debug('is blank OpenOffice') log.debug('is blank OpenOffice')
if self.control and self.control.isRunning(): if self.control and self.control.isRunning():
return self.control.isPaused() return self.control.isPaused()
else: return False
return False
def stop_presentation(self): def stop_presentation(self):
""" """
@ -384,6 +452,8 @@ class ImpressDocument(PresentationDocument):
sleep_count += 1 sleep_count += 1
self.control = self.presentation.getController() self.control = self.presentation.getController()
window.setVisible(False) window.setVisible(False)
listener = SlideShowListener(self)
self.control.getSlideShow().addSlideShowListener(listener)
else: else:
self.control.activate() self.control.activate()
self.goto_slide(1) self.goto_slide(1)
@ -415,17 +485,33 @@ class ImpressDocument(PresentationDocument):
""" """
Triggers the next effect of slide on the running presentation. Triggers the next effect of slide on the running presentation.
""" """
# if we are at the presentations end don't go further, just return True
if self.slide_ended and self.get_slide_count() == self.get_slide_number():
return True
self.slide_ended = False
self.slide_ended_reverse = False
past_end = False
is_paused = self.control.isPaused() is_paused = self.control.isPaused()
self.control.gotoNextEffect() self.control.gotoNextEffect()
time.sleep(0.1) time.sleep(0.1)
# If for some reason the presentation end was not detected above, this will catch it.
# The presentation is set to paused when going past the end.
if not is_paused and self.control.isPaused(): if not is_paused and self.control.isPaused():
self.control.gotoPreviousEffect() self.control.gotoPreviousEffect()
past_end = True
return past_end
def previous_step(self): def previous_step(self):
""" """
Triggers the previous slide on the running presentation. Triggers the previous slide on the running presentation.
""" """
# if we are at the presentations start don't go further back, just return True
if self.slide_ended_reverse and self.get_slide_number() == 1:
return True
self.slide_ended = False
self.slide_ended_reverse = False
self.control.gotoPreviousEffect() self.control.gotoPreviousEffect()
return False
def get_slide_text(self, slide_no): def get_slide_text(self, slide_no):
""" """
@ -483,3 +569,100 @@ class ImpressDocument(PresentationDocument):
note = ' ' note = ' '
notes.append(note) notes.append(note)
self.save_titles_and_notes(titles, notes) self.save_titles_and_notes(titles, notes)
class SlideShowListener(SlideShowListenerImport):
"""
Listener interface to receive global slide show events.
"""
def __init__(self, document):
"""
Constructor
:param document: The ImpressDocument being presented
"""
self.document = document
def paused(self):
"""
Notify that the slide show is paused
"""
log.debug('LibreOffice SlideShowListener event: paused')
def resumed(self):
"""
Notify that the slide show is resumed from a paused state
"""
log.debug('LibreOffice SlideShowListener event: resumed')
def slideTransitionStarted(self):
"""
Notify that a new slide starts to become visible.
"""
log.debug('LibreOffice SlideShowListener event: slideTransitionStarted')
def slideTransitionEnded(self):
"""
Notify that the slide transtion of the current slide ended.
"""
log.debug('LibreOffice SlideShowListener event: slideTransitionEnded')
def slideAnimationsEnded(self):
"""
Notify that the last animation from the main sequence of the current slide has ended.
"""
log.debug('LibreOffice SlideShowListener event: slideAnimationsEnded')
if not Registry().get('main_window').isActiveWindow():
log.debug('main window is not in focus - should update slidecontroller')
Registry().execute('slidecontroller_live_change', self.document.control.getCurrentSlideIndex() + 1)
def slideEnded(self, reverse):
"""
Notify that the current slide has ended, e.g. the user has clicked on the slide. Calling displaySlide()
twice will not issue this event.
:param bool reverse: Whether or not the direction of the "slide movement" is reversed/backwards.
:rtype: None
"""
log.debug('LibreOffice SlideShowListener event: slideEnded %d' % reverse)
if reverse:
self.document.slide_ended = False
self.document.slide_ended_reverse = True
else:
self.document.slide_ended = True
self.document.slide_ended_reverse = False
def hyperLinkClicked(self, hyperLink):
"""
Notifies that a hyperlink has been clicked.
"""
log.debug('LibreOffice SlideShowListener event: hyperLinkClicked %s' % hyperLink)
def disposing(self, source):
"""
gets called when the broadcaster is about to be disposed.
:param source:
"""
log.debug('LibreOffice SlideShowListener event: disposing')
def beginEvent(self, node):
"""
This event is raised when the element local timeline begins to play.
:param node:
"""
log.debug('LibreOffice SlideShowListener event: beginEvent')
def endEvent(self, node):
"""
This event is raised at the active end of the element.
:param node:
"""
log.debug('LibreOffice SlideShowListener event: endEvent')
def repeat(self, node):
"""
This event is raised when the element local timeline repeats.
:param node:
"""
log.debug('LibreOffice SlideShowListener event: repeat')

View File

@ -169,24 +169,21 @@ class Controller(object):
""" """
log.debug('Live = {live}, next'.format(live=self.is_live)) log.debug('Live = {live}, next'.format(live=self.is_live))
if not self.doc: if not self.doc:
return return False
if not self.is_live: if not self.is_live:
return return False
if self.hide_mode: if self.hide_mode:
if not self.doc.is_active(): if not self.doc.is_active():
return return False
if self.doc.slidenumber < self.doc.get_slide_count(): if self.doc.slidenumber < self.doc.get_slide_count():
self.doc.slidenumber += 1 self.doc.slidenumber += 1
self.poll() self.poll()
return return False
if not self.activate(): if not self.activate():
return return False
# The "End of slideshow" screen is after the last slide. Note, we can't just stop on the last slide, since it ret = self.doc.next_step()
# may contain animations that need to be stepped through.
if self.doc.slidenumber > self.doc.get_slide_count():
return
self.doc.next_step()
self.poll() self.poll()
return ret
def previous(self): def previous(self):
""" """
@ -194,20 +191,21 @@ class Controller(object):
""" """
log.debug('Live = {live}, previous'.format(live=self.is_live)) log.debug('Live = {live}, previous'.format(live=self.is_live))
if not self.doc: if not self.doc:
return return False
if not self.is_live: if not self.is_live:
return return False
if self.hide_mode: if self.hide_mode:
if not self.doc.is_active(): if not self.doc.is_active():
return return False
if self.doc.slidenumber > 1: if self.doc.slidenumber > 1:
self.doc.slidenumber -= 1 self.doc.slidenumber -= 1
self.poll() self.poll()
return return False
if not self.activate(): if not self.activate():
return return False
self.doc.previous_step() ret = self.doc.previous_step()
self.poll() self.poll()
return ret
def shutdown(self): def shutdown(self):
""" """
@ -418,11 +416,12 @@ class MessageListener(object):
""" """
is_live = message[1] is_live = message[1]
if is_live: if is_live:
self.live_handler.next() ret = self.live_handler.next()
if Settings().value('core/click live slide to unblank'): if Settings().value('core/click live slide to unblank'):
Registry().execute('slidecontroller_live_unblank') Registry().execute('slidecontroller_live_unblank')
return ret
else: else:
self.preview_handler.next() return self.preview_handler.next()
def previous(self, message): def previous(self, message):
""" """
@ -432,11 +431,12 @@ class MessageListener(object):
""" """
is_live = message[1] is_live = message[1]
if is_live: if is_live:
self.live_handler.previous() ret = self.live_handler.previous()
if Settings().value('core/click live slide to unblank'): if Settings().value('core/click live slide to unblank'):
Registry().execute('slidecontroller_live_unblank') Registry().execute('slidecontroller_live_unblank')
return ret
else: else:
self.preview_handler.previous() return self.preview_handler.previous()
def shutdown(self, message): def shutdown(self, message):
""" """

View File

@ -145,8 +145,8 @@ class PowerpointDocument(PresentationDocument):
try: try:
if not self.controller.process: if not self.controller.process:
self.controller.start_process() self.controller.start_process()
self.controller.process.Presentations.Open(str(self.file_path), False, False, False) self.presentation = self.controller.process.Presentations.Open(str(self.file_path), False, False, False)
self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count) log.debug('Loaded presentation %s' % self.presentation.FullName)
self.create_thumbnails() self.create_thumbnails()
self.create_titles_and_notes() self.create_titles_and_notes()
# Make sure powerpoint doesn't steal focus, unless we're on a single screen setup # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
@ -170,14 +170,17 @@ class PowerpointDocument(PresentationDocument):
However, for the moment, we want a physical file since it makes life easier elsewhere. However, for the moment, we want a physical file since it makes life easier elsewhere.
""" """
log.debug('create_thumbnails') log.debug('create_thumbnails')
generate_thumbs = True
if self.check_thumbnails(): if self.check_thumbnails():
return # No need for thumbnails but we still need the index
generate_thumbs = False
key = 1 key = 1
for num in range(self.presentation.Slides.Count): for num in range(self.presentation.Slides.Count):
if not self.presentation.Slides(num + 1).SlideShowTransition.Hidden: if not self.presentation.Slides(num + 1).SlideShowTransition.Hidden:
self.index_map[key] = num + 1 self.index_map[key] = num + 1
self.presentation.Slides(num + 1).Export( if generate_thumbs:
str(self.get_thumbnail_folder() / 'slide{key:d}.png'.format(key=key)), 'png', 320, 240) self.presentation.Slides(num + 1).Export(
str(self.get_thumbnail_folder() / 'slide{key:d}.png'.format(key=key)), 'png', 320, 240)
key += 1 key += 1
self.slide_count = key - 1 self.slide_count = key - 1
@ -318,6 +321,9 @@ class PowerpointDocument(PresentationDocument):
size = ScreenList().current.display_geometry size = ScreenList().current.display_geometry
ppt_window = None ppt_window = None
try: try:
# Disable the presentation console
self.presentation.SlideShowSettings.ShowPresenterView = 0
# Start the presentation
ppt_window = self.presentation.SlideShowSettings.Run() ppt_window = self.presentation.SlideShowSettings.Run()
except (AttributeError, pywintypes.com_error): except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in start_presentation') log.exception('Caught exception while in start_presentation')
@ -437,6 +443,12 @@ class PowerpointDocument(PresentationDocument):
Triggers the next effect of slide on the running presentation. Triggers the next effect of slide on the running presentation.
""" """
log.debug('next_step') log.debug('next_step')
# if we are at the presentations end don't go further, just return True
if self.presentation.SlideShowWindow.View.GetClickCount() == \
self.presentation.SlideShowWindow.View.GetClickIndex() \
and self.get_slide_number() == self.get_slide_count():
return True
past_end = False
try: try:
self.presentation.SlideShowWindow.Activate() self.presentation.SlideShowWindow.Activate()
self.presentation.SlideShowWindow.View.Next() self.presentation.SlideShowWindow.View.Next()
@ -444,28 +456,35 @@ class PowerpointDocument(PresentationDocument):
log.exception('Caught exception while in next_step') log.exception('Caught exception while in next_step')
trace_error_handler(log) trace_error_handler(log)
self.show_error_msg() self.show_error_msg()
return return past_end
# If for some reason the presentation end was not detected above, this will catch it.
if self.get_slide_number() > self.get_slide_count(): if self.get_slide_number() > self.get_slide_count():
log.debug('past end, stepping back to previous') log.debug('past end, stepping back to previous')
self.previous_step() self.previous_step()
past_end = True
# Stop powerpoint from flashing in the taskbar # Stop powerpoint from flashing in the taskbar
if self.presentation_hwnd: if self.presentation_hwnd:
win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0) win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
# Make sure powerpoint doesn't steal focus, unless we're on a single screen setup # Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
if len(ScreenList()) > 1: if len(ScreenList()) > 1:
Registry().get('main_window').activateWindow() Registry().get('main_window').activateWindow()
return past_end
def previous_step(self): def previous_step(self):
""" """
Triggers the previous slide on the running presentation. Triggers the previous slide on the running presentation.
""" """
log.debug('previous_step') log.debug('previous_step')
# if we are at the presentations start we can't go further back, just return True
if self.presentation.SlideShowWindow.View.GetClickIndex() == 0 and self.get_slide_number() == 1:
return True
try: try:
self.presentation.SlideShowWindow.View.Previous() self.presentation.SlideShowWindow.View.Previous()
except (AttributeError, pywintypes.com_error): except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in previous_step') log.exception('Caught exception while in previous_step')
trace_error_handler(log) trace_error_handler(log)
self.show_error_msg() self.show_error_msg()
return False
def get_slide_text(self, slide_no): def get_slide_text(self, slide_no):
""" """

View File

@ -248,15 +248,17 @@ class PresentationDocument(object):
def next_step(self): def next_step(self):
""" """
Triggers the next effect of slide on the running presentation. This might be the next animation on the current Triggers the next effect of slide on the running presentation. This might be the next animation on the current
slide, or the next slide slide, or the next slide.
:rtype bool: True if we stepped beyond the slides of the presentation
""" """
pass return False
def previous_step(self): def previous_step(self):
""" """
Triggers the previous slide on the running presentation Triggers the previous slide on the running presentation
:rtype bool: True if we stepped beyond the slides of the presentation
""" """
pass return False
def convert_thumbnail(self, image_path, index): def convert_thumbnail(self, image_path, index):
""" """

View File

@ -100,12 +100,13 @@ OPTIONAL_MODULES = [
('pyodbc', '(ODBC support)'), ('pyodbc', '(ODBC support)'),
('psycopg2', '(PostgreSQL support)'), ('psycopg2', '(PostgreSQL support)'),
('enchant', '(spell checker)'), ('enchant', '(spell checker)'),
('fitz', '(executable-independent PDF support)'),
('pysword', '(import SWORD bibles)'), ('pysword', '(import SWORD bibles)'),
('uno', '(LibreOffice/OpenOffice support)'), ('uno', '(LibreOffice/OpenOffice support)'),
# development/testing modules # development/testing modules
('jenkins', '(access jenkins api)'), ('jenkins', '(access jenkins api)'),
('launchpadlib', '(launchpad script support)'), ('launchpadlib', '(launchpad script support)'),
('nose2', '(testing framework)'), ('pytest', '(testing framework)'),
('pylint', '(linter)') ('pylint', '(linter)')
] ]

View File

@ -2,6 +2,9 @@
# E722 do not use bare except, specify exception instead # E722 do not use bare except, specify exception instead
# F841 local variable '<variable>' is assigned to but never used # F841 local variable '<variable>' is assigned to but never used
[aliases]
test=pytest
[pep8] [pep8]
exclude=resources.py,vlc.py exclude=resources.py,vlc.py
max-line-length = 120 max-line-length = 120

View File

@ -178,6 +178,7 @@ using a computer and a data projector.""",
'pyobjc-framework-Cocoa; platform_system=="Darwin"', 'pyobjc-framework-Cocoa; platform_system=="Darwin"',
'PyQt5 >= 5.12', 'PyQt5 >= 5.12',
'PyQtWebEngine', 'PyQtWebEngine',
'python-vlc',
'pywin32; platform_system=="Windows"', 'pywin32; platform_system=="Windows"',
'QtAwesome', 'QtAwesome',
'requests', 'requests',
@ -199,13 +200,13 @@ using a computer and a data projector.""",
'launchpad': ['launchpadlib'] 'launchpad': ['launchpadlib']
}, },
tests_require=[ tests_require=[
'nose2',
'pylint', 'pylint',
'PyMuPDF', 'PyMuPDF',
'pyodbc', 'pyodbc',
'pysword', 'pysword',
'pytest',
'python-xlib; platform_system=="Linux"' 'python-xlib; platform_system=="Linux"'
], ],
test_suite='nose2.collector.collector', setup_requires=['pytest-runner'],
entry_points={'gui_scripts': ['openlp = run_openlp:start']} entry_points={'gui_scripts': ['openlp = run_openlp:start']}
) )

View File

@ -22,7 +22,6 @@
""" """
Package to test the openlp.core.common.path package. Package to test the openlp.core.common.path package.
""" """
# TODO: fix patches
import os import os
from pathlib import Path from pathlib import Path
from unittest import TestCase from unittest import TestCase

View File

@ -27,7 +27,6 @@ from unittest.mock import MagicMock, patch
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.ui.media.mediacontroller import MediaController from openlp.core.ui.media.mediacontroller import MediaController
from openlp.core.ui.media.vlcplayer import VlcPlayer
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
from tests.utils.constants import RESOURCE_PATH from tests.utils.constants import RESOURCE_PATH
@ -43,26 +42,6 @@ class TestMediaController(TestCase, TestMixin):
Registry.create() Registry.create()
Registry().register('service_manager', MagicMock()) Registry().register('service_manager', MagicMock())
def test_generate_extensions_lists(self):
"""
Test that the extensions are create correctly
"""
# GIVEN: A MediaController and an active player with audio and video extensions
media_controller = MediaController()
media_controller.vlc_player = VlcPlayer(None)
media_controller.vlc_player.is_active = True
media_controller.vlc_player.audio_extensions_list = ['*.mp3', '*.wav', '*.wma', '*.ogg']
media_controller.vlc_player.video_extensions_list = ['*.mp4', '*.mov', '*.avi', '*.ogm']
# WHEN: calling _generate_extensions_lists
media_controller._generate_extensions_lists()
# THEN: extensions list should have been copied from the player to the mediacontroller
assert media_controller.video_extensions_list == media_controller.video_extensions_list, \
'Video extensions should be the same'
assert media_controller.audio_extensions_list == media_controller.audio_extensions_list, \
'Audio extensions should be the same'
def test_resize(self): def test_resize(self):
""" """
Test that the resize method is called correctly Test that the resize method is called correctly

View File

@ -30,7 +30,7 @@ from unittest.mock import MagicMock, call, patch
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.ui.media import MediaState, MediaType from openlp.core.ui.media import MediaState, MediaType
from openlp.core.ui.media.vlcplayer import AUDIO_EXT, VIDEO_EXT, VlcPlayer, get_vlc from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc
from tests.helpers import MockDateTime from tests.helpers import MockDateTime
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
@ -95,8 +95,6 @@ class TestVLCPlayer(TestCase, TestMixin):
assert '&VLC' == vlc_player.display_name assert '&VLC' == vlc_player.display_name
assert vlc_player.parent is None assert vlc_player.parent is None
assert vlc_player.can_folder is True assert vlc_player.can_folder is True
assert AUDIO_EXT == vlc_player.audio_extensions_list
assert VIDEO_EXT == vlc_player.video_extensions_list
@patch('openlp.core.ui.media.vlcplayer.is_win') @patch('openlp.core.ui.media.vlcplayer.is_win')
@patch('openlp.core.ui.media.vlcplayer.is_macosx') @patch('openlp.core.ui.media.vlcplayer.is_macosx')
@ -958,20 +956,3 @@ class TestVLCPlayer(TestCase, TestMixin):
mocked_controller.seek_slider.setSliderPosition.assert_called_with(300) mocked_controller.seek_slider.setSliderPosition.assert_called_with(300)
expected_calls = [call(True), call(False)] expected_calls = [call(True), call(False)]
assert expected_calls == mocked_controller.seek_slider.blockSignals.call_args_list assert expected_calls == mocked_controller.seek_slider.blockSignals.call_args_list
@patch('openlp.core.ui.media.vlcplayer.translate')
def test_get_info(self, mocked_translate):
"""
Test that get_info() returns some information about the VLC player
"""
# GIVEN: A VlcPlayer
mocked_translate.side_effect = lambda *x: x[1]
vlc_player = VlcPlayer(None)
# WHEN: get_info() is run
info = vlc_player.get_info()
# THEN: The information should be correct
assert 'VLC is an external player which supports a number of different formats.<br/> ' \
'<strong>Audio</strong><br/>' + str(AUDIO_EXT) + '<br/><strong>Video</strong><br/>' + \
str(VIDEO_EXT) + '<br/>' == info