diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 8410d4d28..e45aa6e61 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -610,7 +610,7 @@ class ServiceItem(RegistryProperties): str(datetime.timedelta(seconds=self.start_time)) if self.media_length != 0: end = translate('OpenLP.ServiceItem', 'Length: %s') % \ - str(datetime.timedelta(seconds=self.media_length)) + str(datetime.timedelta(seconds=self.media_length // 1000)) if not start and not end: return '' elif start and not end: diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 82426539b..c6777c756 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -68,7 +68,6 @@ class DisplayControllerType(object): """ Live = 0 Preview = 1 - Plugin = 2 class SingleColumnTableWidget(QtWidgets.QTableWidget): diff --git a/openlp/core/ui/media/__init__.py b/openlp/core/ui/media/__init__.py index ecd4b98bd..07e2a73fb 100644 --- a/openlp/core/ui/media/__init__.py +++ b/openlp/core/ui/media/__init__.py @@ -60,12 +60,14 @@ class MediaInfo(object): """ file_info = None volume = 100 - is_flash = False is_background = False + can_loop_playback = False length = 0 start_time = 0 end_time = 0 title_track = 0 + is_playing = False + timer = 1000 audio_track = 0 subtitle_track = 0 media_type = MediaType() @@ -104,15 +106,15 @@ def set_media_players(players_list, overridden_player='auto'): Settings().setValue('media/players', players) -def parse_optical_path(input): +def parse_optical_path(input_string): """ Split the optical path info. - :param input: The string to parse + :param input_string: The string to parse :return: The elements extracted from the string: filename, title, audio_track, subtitle_track, start, end """ - log.debug('parse_optical_path, about to parse: "%s"' % input) - clip_info = input.split(sep=':') + log.debug('parse_optical_path, about to parse: "%s"' % input_string) + clip_info = input_string.split(sep=':') title = int(clip_info[1]) audio_track = int(clip_info[2]) subtitle_track = int(clip_info[3]) diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index 343ce0dd4..cf116e861 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -33,12 +33,15 @@ from openlp.core.lib import OpenLPToolbar, ItemCapabilities from openlp.core.lib.ui import critical_error_message_box from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\ parse_optical_path +from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper from openlp.core.ui.media.mediaplayer import MediaPlayer from openlp.core.common import AppLocation from openlp.core.ui import DisplayControllerType log = logging.getLogger(__name__) +TICK_TIME = 200 + class MediaSlider(QtWidgets.QSlider): """ @@ -51,10 +54,13 @@ class MediaSlider(QtWidgets.QSlider): super(MediaSlider, self).__init__(direction) self.manager = manager self.controller = controller + self.no_matching_player = translate('MediaPlugin.MediaItem', 'File %s not supported using player %s') def mouseMoveEvent(self, event): """ Override event to allow hover time to be displayed. + + :param event: The triggering event """ time_value = QtWidgets.QStyle.sliderValueFromPosition(self.minimum(), self.maximum(), event.x(), self.width()) self.setToolTip('%s' % datetime.timedelta(seconds=int(time_value / 1000))) @@ -63,12 +69,16 @@ class MediaSlider(QtWidgets.QSlider): def mousePressEvent(self, event): """ Mouse Press event no new functionality + + :param event: The triggering event """ QtWidgets.QSlider.mousePressEvent(self, event) def mouseReleaseEvent(self, event): """ Set the slider position when the mouse is clicked and released on the slider. + + :param event: The triggering event """ self.setValue(QtWidgets.QStyle.sliderValueFromPosition(self.minimum(), self.maximum(), event.x(), self.width())) QtWidgets.QSlider.mouseReleaseEvent(self, event) @@ -96,13 +106,17 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): self.display_controllers = {} self.current_media_players = {} # Timer for video state - self.timer = QtCore.QTimer() - self.timer.setInterval(200) + self.live_timer = QtCore.QTimer() + self.live_timer.setInterval(TICK_TIME) + self.preview_timer = QtCore.QTimer() + self.preview_timer.setInterval(TICK_TIME) # Signals - self.timer.timeout.connect(self.media_state) + self.live_timer.timeout.connect(self.media_state_live) + self.preview_timer.timeout.connect(self.media_state_preview) Registry().register_function('playbackPlay', self.media_play_msg) Registry().register_function('playbackPause', self.media_pause_msg) Registry().register_function('playbackStop', self.media_stop_msg) + Registry().register_function('playbackLoop', self.media_loop_msg) Registry().register_function('seek_slider', self.media_seek_msg) Registry().register_function('volume_slider', self.media_volume_msg) Registry().register_function('media_hide', self.media_hide) @@ -172,8 +186,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): log.warning('Failed to import %s on path %s', module_name, path) player_classes = MediaPlayer.__subclasses__() for player_class in player_classes: - player = player_class(self) - self.register_players(player) + self.register_players(player_class(self)) if not self.media_players: return False saved_players, overridden_player = get_media_players() @@ -188,31 +201,39 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): self._generate_extensions_lists() return True - def media_state(self): + def media_state_live(self): """ - Check if there is a running media Player and do updating stuff (e.g. update the UI) + Check if there is a running Live media Player and do updating stuff (e.g. update the UI) """ - if not list(self.current_media_players.keys()): - self.timer.stop() + display = self._define_display(self.display_controllers[DisplayControllerType.Live]) + if DisplayControllerType.Live in self.current_media_players: + self.current_media_players[DisplayControllerType.Live].resize(display) + self.current_media_players[DisplayControllerType.Live].update_ui(display) + self.tick(self.display_controllers[DisplayControllerType.Live]) + if self.current_media_players[DisplayControllerType.Live].get_live_state() is not MediaState.Playing: + self.live_timer.stop() else: - any_active = False - for source in list(self.current_media_players.keys()): - display = self._define_display(self.display_controllers[source]) - self.current_media_players[source].resize(display) - self.current_media_players[source].update_ui(display) - if self.current_media_players[source].state == MediaState.Playing: - any_active = True - # There are still any active players - no need to stop timer. - if any_active: - return - # no players are active anymore - for source in list(self.current_media_players.keys()): - if self.current_media_players[source].state != MediaState.Paused: - display = self._define_display(self.display_controllers[source]) - display.controller.seek_slider.setSliderPosition(0) - display.controller.mediabar.actions['playbackPlay'].setVisible(True) - display.controller.mediabar.actions['playbackPause'].setVisible(False) - self.timer.stop() + self.live_timer.stop() + self.media_stop(self.display_controllers[DisplayControllerType.Live]) + if self.display_controllers[DisplayControllerType.Live].media_info.can_loop_playback: + self.media_play(self.display_controllers[DisplayControllerType.Live], True) + + def media_state_preview(self): + """ + Check if there is a running Preview media Player and do updating stuff (e.g. update the UI) + """ + display = self._define_display(self.display_controllers[DisplayControllerType.Preview]) + if DisplayControllerType.Preview in self.current_media_players: + self.current_media_players[DisplayControllerType.Preview].resize(display) + self.current_media_players[DisplayControllerType.Preview].update_ui(display) + self.tick(self.display_controllers[DisplayControllerType.Preview]) + if self.current_media_players[DisplayControllerType.Preview].get_preview_state() is not MediaState.Playing: + self.preview_timer.stop() + else: + self.preview_timer.stop() + self.media_stop(self.display_controllers[DisplayControllerType.Preview]) + if self.display_controllers[DisplayControllerType.Preview].media_info.can_loop_playback: + self.media_play(self.display_controllers[DisplayControllerType.Preview], True) def get_media_display_css(self): """ @@ -274,6 +295,15 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): icon=':/slides/media_playback_stop.png', tooltip=translate('OpenLP.SlideController', 'Stop playing media.'), triggers=controller.send_to_plugins) + controller.mediabar.add_toolbar_action('playbackLoop', text='media_playback_loop', + icon=':/slides/media_playback_stop.png', checked=False, + tooltip=translate('OpenLP.SlideController', 'Loop playing media.'), + triggers=controller.send_to_plugins) + controller.position_label = QtWidgets.QLabel() + controller.position_label.setText(' 00:00 / 00:00') + controller.position_label.setToolTip(translate('OpenLP.SlideController', 'Video timer.')) + controller.position_label.setObjectName('position_label') + controller.mediabar.add_toolbar_widget(controller.position_label) # Build the seek_slider. controller.seek_slider = MediaSlider(QtCore.Qt.Horizontal, self, controller) controller.seek_slider.setMaximum(1000) @@ -297,6 +327,8 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): controller.mediabar.add_toolbar_widget(controller.volume_slider) controller.controller_layout.addWidget(controller.mediabar) controller.mediabar.setVisible(False) + if not controller.is_live: + controller.volume_slider.setEnabled(False) # Signals controller.seek_slider.valueChanged.connect(controller.send_to_plugins) controller.volume_slider.valueChanged.connect(controller.send_to_plugins) @@ -335,7 +367,8 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): if self.current_media_players[controller.controller_type] != self.media_players['webkit']: controller.display.set_transparency(False) - def resize(self, display, player): + @staticmethod + def resize(display, player): """ After Mainwindow changes or Splitter moved all related media widgets have to be resized @@ -353,7 +386,6 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): :param hidden: The player which is doing the playing :param video_behind_text: Is the video to be played behind text. """ - log.debug('video') is_valid = False controller = self.display_controllers[source] # stop running videos @@ -361,6 +393,8 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): controller.media_info = MediaInfo() controller.media_info.volume = controller.volume_slider.value() controller.media_info.is_background = video_behind_text + # background will always loop video. + controller.media_info.can_loop_playback = video_behind_text controller.media_info.file_info = QtCore.QFileInfo(service_item.get_frame_path()) display = self._define_display(controller) if controller.is_live: @@ -373,6 +407,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): controller) else: log.debug('video is not optical and live') + controller.media_info.length = service_item.media_length is_valid = self._check_file_type(controller, display, service_item) display.override['theme'] = '' display.override['video'] = True @@ -392,6 +427,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): controller) else: log.debug('video is not optical and preview') + controller.media_info.length = service_item.media_length is_valid = self._check_file_type(controller, display, service_item) if not is_valid: # Media could not be loaded correctly @@ -428,26 +464,22 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): :param service_item: The ServiceItem containing the details to be played. """ - controller = self.display_controllers[DisplayControllerType.Plugin] - log.debug('media_length') - # stop running videos - self.media_reset(controller) - controller.media_info = MediaInfo() - controller.media_info.volume = 0 - controller.media_info.file_info = QtCore.QFileInfo(service_item.get_frame_path()) - display = controller.preview_display - if not self._check_file_type(controller, display, service_item): + media_info = MediaInfo() + media_info.volume = 0 + media_info.file_info = QtCore.QFileInfo(service_item.get_frame_path()) + # display = controller.preview_display + suffix = '*.%s' % media_info.file_info.suffix().lower() + used_players = get_media_players()[0] + player = self.media_players[used_players[0]] + if suffix not in player.video_extensions_list and suffix not in player.audio_extensions_list: # Media could not be loaded correctly - critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File'), - translate('MediaPlugin.MediaItem', 'Unsupported File')) + critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported Media File'), + translate('MediaPlugin.MediaItem', 'File %s not supported using player %s') % + (service_item.get_frame_path(), used_players[0])) return False - if not self.media_play(controller): - critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File'), - translate('MediaPlugin.MediaItem', 'Unsupported File')) - return False - service_item.set_media_length(controller.media_info.length) - self.media_stop(controller) - log.debug('use %s controller' % self.current_media_players[controller.controller_type]) + media_data = MediaInfoWrapper.parse(service_item.get_frame_path()) + # duration returns in milli seconds + service_item.set_media_length(media_data.tracks[0].duration) return True def media_setup_optical(self, filename, title, audio_track, subtitle_track, start, end, display, controller): @@ -458,13 +490,12 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): :param title: The main/title track to play. :param audio_track: The audio track to play. :param subtitle_track: The subtitle track to play. - :param start: Start position in miliseconds. - :param end: End position in miliseconds. + :param start: Start position in milliseconds. + :param end: End position in milliseconds. :param display: The display to play the media. - :param controller: The media contraoller. - :return: True if setup succeded else False. + :param controller: The media controller. + :return: True if setup succeeded else False. """ - log.debug('media_setup_optical') if controller is None: controller = self.display_controllers[DisplayControllerType.Plugin] # stop running videos @@ -476,9 +507,9 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): controller.media_info.media_type = MediaType.CD else: controller.media_info.media_type = MediaType.DVD - controller.media_info.start_time = start / 1000 - controller.media_info.end_time = end / 1000 - controller.media_info.length = (end - start) / 1000 + controller.media_info.start_time = start // 1000 + controller.media_info.end_time = end // 1000 + controller.media_info.length = (end - start) // 1000 controller.media_info.title_track = title controller.media_info.audio_track = audio_track controller.media_info.subtitle_track = subtitle_track @@ -506,13 +537,13 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): controller.media_info.media_type = MediaType.DVD return True - def _check_file_type(self, controller, display, service_item): + @staticmethod + def _get_used_players(service_item): """ - Select the correct media Player type from the prioritized Player list + Find the player for a given service item - :param controller: First element is the controller which should be used - :param display: Which display to use - :param service_item: The ServiceItem containing the details to be played. + :param service_item: where the information is about the media and required player + :return: player description """ used_players = get_media_players()[0] # If no player, we can't play @@ -525,6 +556,17 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): used_players = default_player else: used_players = [service_item.processor.lower()] + return used_players + + def _check_file_type(self, controller, display, service_item): + """ + Select the correct media Player type from the prioritized Player list + + :param controller: First element is the controller which should be used + :param display: Which display to use + :param service_item: The ServiceItem containing the details to be played. + """ + used_players = self._get_used_players(service_item) if controller.media_info.file_info.isFile(): suffix = '*.%s' % controller.media_info.file_info.suffix().lower() for title in used_players: @@ -573,17 +615,15 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): :param msg: First element is the controller which should be used :param status: """ - log.debug('media_play_msg') self.media_play(msg[0], status) - def media_play(self, controller, status=True): + def media_play(self, controller, first_time=True): """ Responds to the request to play a loaded video :param controller: The controller to be played - :param status: + :param first_time: """ - log.debug('media_play') controller.seek_slider.blockSignals(True) controller.volume_slider.blockSignals(True) display = self._define_display(controller) @@ -595,35 +635,60 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): self.media_volume(controller, 0) else: self.media_volume(controller, controller.media_info.volume) - if status: + if first_time: if not controller.media_info.is_background: display.frame.evaluateJavaScript('show_blank("desktop");') self.current_media_players[controller.controller_type].set_visible(display, True) - # Flash needs to be played and will not AutoPlay - if controller.media_info.is_flash: - controller.mediabar.actions['playbackPlay'].setVisible(True) - controller.mediabar.actions['playbackPause'].setVisible(False) - else: - controller.mediabar.actions['playbackPlay'].setVisible(False) - controller.mediabar.actions['playbackPause'].setVisible(True) + controller.mediabar.actions['playbackPlay'].setVisible(False) + controller.mediabar.actions['playbackPause'].setVisible(True) controller.mediabar.actions['playbackStop'].setDisabled(False) - if controller.is_live: - if controller.hide_menu.defaultAction().isChecked() and not controller.media_info.is_background: - controller.hide_menu.defaultAction().trigger() - # Start Timer for ui updates - if not self.timer.isActive(): - self.timer.start() + if controller.is_live: + if controller.hide_menu.defaultAction().isChecked() and not controller.media_info.is_background: + controller.hide_menu.defaultAction().trigger() + # Start Timer for ui updates + if not self.live_timer.isActive(): + self.live_timer.start() + else: + # Start Timer for ui updates + if not self.preview_timer.isActive(): + self.preview_timer.start() controller.seek_slider.blockSignals(False) controller.volume_slider.blockSignals(False) + controller.media_info.is_playing = True + display = self._define_display(controller) + display.setVisible(True) return True + def tick(self, controller): + """ + Add a tick while the media is playing but only count if not paused + + :param controller: The Controller to be processed + """ + start_again = False + if controller.media_info.is_playing and controller.media_info.length > 0: + if controller.media_info.timer > controller.media_info.length: + self.media_stop(controller, True) + if controller.media_info.can_loop_playback: + start_again = True + controller.media_info.timer += TICK_TIME + seconds = controller.media_info.timer // 1000 + minutes = seconds // 60 + seconds %= 60 + total_seconds = controller.media_info.length // 1000 + total_minutes = total_seconds // 60 + total_seconds %= 60 + controller.position_label.setText(' %02d:%02d / %02d:%02d' % + (minutes, seconds, total_minutes, total_seconds)) + if start_again: + self.media_play(controller, True) + def media_pause_msg(self, msg): """ Responds to the request to pause a loaded video :param msg: First element is the controller which should be used """ - log.debug('media_pause_msg') self.media_pause(msg[0]) def media_pause(self, controller): @@ -632,12 +697,31 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): :param controller: The Controller to be paused """ - log.debug('media_pause') display = self._define_display(controller) - self.current_media_players[controller.controller_type].pause(display) - controller.mediabar.actions['playbackPlay'].setVisible(True) - controller.mediabar.actions['playbackStop'].setDisabled(False) - controller.mediabar.actions['playbackPause'].setVisible(False) + if controller.controller_type in self.current_media_players: + self.current_media_players[controller.controller_type].pause(display) + controller.mediabar.actions['playbackPlay'].setVisible(True) + controller.mediabar.actions['playbackStop'].setDisabled(False) + controller.mediabar.actions['playbackPause'].setVisible(False) + controller.media_info.is_playing = False + + def media_loop_msg(self, msg): + """ + Responds to the request to loop a loaded video + + :param msg: First element is the controller which should be used + """ + self.media_loop(msg[0]) + + @staticmethod + def media_loop(controller): + """ + Responds to the request to loop a loaded video + + :param controller: The controller that needs to be stopped + """ + controller.media_info.can_loop_playback = not controller.media_info.can_loop_playback + controller.mediabar.actions['playbackLoop'].setChecked(controller.media_info.can_loop_playback) def media_stop_msg(self, msg): """ @@ -645,25 +729,28 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): :param msg: First element is the controller which should be used """ - log.debug('media_stop_msg') self.media_stop(msg[0]) - def media_stop(self, controller): + def media_stop(self, controller, looping_background=False): """ Responds to the request to stop a loaded video :param controller: The controller that needs to be stopped + :param looping_background: The background is looping so do not blank. """ - log.debug('media_stop') display = self._define_display(controller) if controller.controller_type in self.current_media_players: - display.frame.evaluateJavaScript('show_blank("black");') + if not looping_background: + display.frame.evaluateJavaScript('show_blank("black");') self.current_media_players[controller.controller_type].stop(display) self.current_media_players[controller.controller_type].set_visible(display, False) controller.seek_slider.setSliderPosition(0) controller.mediabar.actions['playbackPlay'].setVisible(True) controller.mediabar.actions['playbackStop'].setDisabled(True) controller.mediabar.actions['playbackPause'].setVisible(False) + controller.media_info.is_playing = False + controller.media_info.timer = 1000 + controller.media_timer = 0 def media_volume_msg(self, msg): """ @@ -694,7 +781,6 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): :param msg: First element is the controller which should be used Second element is a list with the seek value as first element """ - log.debug('media_seek') controller = msg[0] seek_value = msg[1][0] self.media_seek(controller, seek_value) @@ -706,15 +792,15 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): :param controller: The controller to use. :param seek_value: The value to set. """ - log.debug('media_seek') display = self._define_display(controller) self.current_media_players[controller.controller_type].seek(display, seek_value) + controller.media_info.timer = seek_value def media_reset(self, controller): """ Responds to the request to reset a loaded video + :param controller: The controller to use. """ - log.debug('media_reset') self.set_controls_visible(controller, False) display = self._define_display(controller) if controller.controller_type in self.current_media_players: @@ -735,7 +821,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): return display = self._define_display(self.live_controller) if self.live_controller.controller_type in self.current_media_players and \ - self.current_media_players[self.live_controller.controller_type].state == MediaState.Playing: + self.current_media_players[self.live_controller.controller_type].get_live_state() == MediaState.Playing: self.current_media_players[self.live_controller.controller_type].pause(display) self.current_media_players[self.live_controller.controller_type].set_visible(display, False) @@ -753,7 +839,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): Registry().execute('live_display_hide', hide_mode) display = self._define_display(self.live_controller) if self.live_controller.controller_type in self.current_media_players and \ - self.current_media_players[self.live_controller.controller_type].state == MediaState.Playing: + self.current_media_players[self.live_controller.controller_type].get_live_state() == MediaState.Playing: self.current_media_players[self.live_controller.controller_type].pause(display) self.current_media_players[self.live_controller.controller_type].set_visible(display, False) @@ -770,22 +856,25 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): return display = self._define_display(self.live_controller) if self.live_controller.controller_type in self.current_media_players and \ - self.current_media_players[self.live_controller.controller_type].state != MediaState.Playing: + self.current_media_players[self.live_controller.controller_type].get_live_state() != \ + MediaState.Playing: if self.current_media_players[self.live_controller.controller_type].play(display): self.current_media_players[self.live_controller.controller_type].set_visible(display, True) # Start Timer for ui updates - if not self.timer.isActive(): - self.timer.start() + if not self.live_timer.isActive(): + self.live_timer.start() def finalise(self): """ Reset all the media controllers when OpenLP shuts down """ - self.timer.stop() + self.live_timer.stop() + self.preview_timer.stop() for controller in self.display_controllers: self.media_reset(self.display_controllers[controller]) - def _define_display(self, controller): + @staticmethod + def _define_display(controller): """ Extract the correct display for a given controller diff --git a/openlp/core/ui/media/mediaplayer.py b/openlp/core/ui/media/mediaplayer.py index b25916372..d9c7ad321 100644 --- a/openlp/core/ui/media/mediaplayer.py +++ b/openlp/core/ui/media/mediaplayer.py @@ -41,7 +41,7 @@ class MediaPlayer(RegistryProperties): self.is_active = False self.can_background = False self.can_folder = False - self.state = MediaState.Off + self.state = {0: MediaState.Off, 1: MediaState.Off} self.has_own_widget = False self.audio_extensions_list = [] self.video_extensions_list = [] @@ -55,12 +55,16 @@ class MediaPlayer(RegistryProperties): def setup(self, display): """ Create the related widgets for the current display + + :param display: The display to be updated. """ pass def load(self, display): """ Load a new media file and check if it is valid + + :param display: The display to be updated. """ return True @@ -68,54 +72,75 @@ class MediaPlayer(RegistryProperties): """ If the main display size or position is changed, the media widgets should also resized + + :param display: The display to be updated. """ pass def play(self, display): """ Starts playing of current Media File + + :param display: The display to be updated. """ pass def pause(self, display): """ Pause of current Media File + + :param display: The display to be updated. """ pass def stop(self, display): """ Stop playing of current Media File + + :param display: The display to be updated. """ pass - def volume(self, display, vol): + def volume(self, display, volume): """ Change volume of current Media File + + :param display: The display to be updated. + :param volume: The volume to set. """ pass def seek(self, display, seek_value): """ Change playing position of current Media File + + :param display: The display to be updated. + :param seek_value: The where to seek to. """ pass def reset(self, display): """ Remove the current loaded video + + :param display: The display to be updated. """ pass def set_visible(self, display, status): """ Show/Hide the media widgets + + :param display: The display to be updated. + :param status: The status to be set. """ pass def update_ui(self, display): """ Do some ui related stuff (e.g. update the seek slider) + + :param display: The display to be updated. """ pass @@ -142,3 +167,45 @@ class MediaPlayer(RegistryProperties): Returns Information about the player """ return '' + + def get_live_state(self): + """ + Get the state of the live player + :return: Live state + """ + return self.state[0] + + def set_live_state(self, state): + """ + Set the State of the Live player + :param state: State to be set + :return: None + """ + self.state[0] = state + + def get_preview_state(self): + """ + Get the state of the preview player + :return: Preview State + """ + return self.state[1] + + def set_preview_state(self, state): + """ + Set the state of the Preview Player + :param state: State to be set + :return: None + """ + self.state[1] = state + + def set_state(self, state, display): + """ + Set the State based on the display being processed + :param state: State to be set + :param display: Identify the Display type + :return: None + """ + if display.controller.is_live: + self.set_live_state(state) + else: + self.set_preview_state(state) diff --git a/openlp/core/ui/media/playertab.py b/openlp/core/ui/media/playertab.py index 3db5e06b4..ed34993ca 100644 --- a/openlp/core/ui/media/playertab.py +++ b/openlp/core/ui/media/playertab.py @@ -133,12 +133,16 @@ class PlayerTab(SettingsTab): def on_background_color_changed(self, color): """ Set the background color + + :param color: The color to be set. """ self.background_color = color def on_player_check_box_changed(self, check_state): """ Add or remove players depending on their status + + :param check_state: The requested status. """ player = self.sender().player_name if check_state == QtCore.Qt.Checked: diff --git a/openlp/core/ui/media/systemplayer.py b/openlp/core/ui/media/systemplayer.py index 79069f9c9..ad1907044 100644 --- a/openlp/core/ui/media/systemplayer.py +++ b/openlp/core/ui/media/systemplayer.py @@ -4,14 +4,7 @@ ############################################################################### # OpenLP - Open Source Lyrics Projection # # --------------------------------------------------------------------------- # -# Copyright (c) 2008-2014 Raoul Snyman # -# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # -# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # -# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # -# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # -# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # -# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # -# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# Copyright (c) 2008-2016 OpenLP Developers # # --------------------------------------------------------------------------- # # 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 # @@ -124,7 +117,8 @@ class SystemPlayer(MediaPlayer): def load(self, display): """ Load a video into the display - :param display: + + :param display: The display where the media is """ log.debug('load vid in System Controller') controller = display.controller @@ -141,93 +135,122 @@ class SystemPlayer(MediaPlayer): def resize(self, display): """ Resize the display - :param display: + + :param display: The display where the media is """ display.video_widget.resize(display.size()) def play(self, display): """ Play the current media item - :param display: + + :param display: The display where the media is """ log.info('Play the current item') controller = display.controller start_time = 0 - if display.media_player.state() != QtMultimedia.QMediaPlayer.PausedState and \ - controller.media_info.start_time > 0: - start_time = controller.media_info.start_time + if display.controller.is_live: + if self.get_live_state() != QtMultimedia.QMediaPlayer.PausedState and controller.media_info.start_time > 0: + start_time = controller.media_info.start_time + else: + if self.get_preview_state() != QtMultimedia.QMediaPlayer.PausedState and \ + controller.media_info.start_time > 0: + start_time = controller.media_info.start_time display.media_player.play() if start_time > 0: self.seek(display, controller.media_info.start_time * 1000) self.volume(display, controller.media_info.volume) display.media_player.durationChanged.connect(functools.partial(self.set_duration, controller)) - self.state = MediaState.Playing + self.set_state(MediaState.Playing, display) display.video_widget.raise_() return True def pause(self, display): """ Pause the current media item + + :param display: The display where the media is """ display.media_player.pause() - if display.media_player.state() == QtMultimedia.QMediaPlayer.PausedState: - self.state = MediaState.Paused + if display.controller.is_live: + if self.get_live_state() == QtMultimedia.QMediaPlayer.PausedState: + self.set_state(MediaState.Paused, display) + else: + if self.get_preview_state() == QtMultimedia.QMediaPlayer.PausedState: + self.set_state(MediaState.Paused, display) def stop(self, display): """ Stop the current media item + + :param display: The display where the media is """ - display.media_player.blockSignals(True) - display.media_player.durationChanged.disconnect() - display.media_player.blockSignals(False) display.media_player.stop() self.set_visible(display, False) - self.state = MediaState.Stopped + self.set_state(MediaState.Stopped, display) - def volume(self, display, vol): + def volume(self, display, volume): """ Set the volume + + :param display: The display where the media is + :param volume: The volume to be set """ if display.has_audio: - display.media_player.setVolume(vol) + display.media_player.setVolume(volume) def seek(self, display, seek_value): """ Go to a particular point in the current media item + + :param display: The display where the media is + :param seek_value: The where to seek to """ display.media_player.setPosition(seek_value) def reset(self, display): """ Reset the media player + + :param display: The display where the media is """ display.media_player.stop() display.media_player.setMedia(QtMultimedia.QMediaContent()) self.set_visible(display, False) display.video_widget.setVisible(False) - self.state = MediaState.Off + self.set_state(MediaState.Off, display) def set_visible(self, display, status): """ Set the visibility of the widget + + :param display: The display where the media is + :param status: The visibility status to be set """ if self.has_own_widget: display.video_widget.setVisible(status) @staticmethod def set_duration(controller, duration): - controller.media_info.length = int(duration / 1000) - controller.seek_slider.setMaximum(controller.media_info.length * 1000) + """ + + :param controller: the controller displaying the media + :param duration: how long is the media + :return: + """ + controller.seek_slider.setMaximum(controller.media_info.length) def update_ui(self, display): """ Update the UI + + :param display: The display where the media is """ if display.media_player.state() == QtMultimedia.QMediaPlayer.PausedState and self.state != MediaState.Paused: self.stop(display) controller = display.controller if controller.media_info.end_time > 0: - if display.media_player.position() > controller.media_info.end_time * 1000: + if display.media_player.position() > controller.media_info.end_time: self.stop(display) self.set_visible(display, False) if not controller.seek_slider.isSliderDown(): diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py new file mode 100644 index 000000000..35f16667d --- /dev/null +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2016 OpenLP Developers # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### +""" +The :mod:`~openlp.core.ui.media.mediainfo` module contains code to run mediainfo on a media file and obtain +information related to the rwquested media. +""" +import json +import os +from subprocess import Popen +from tempfile import mkstemp + +import six +from bs4 import BeautifulSoup, NavigableString + +ENV_DICT = os.environ + + +class Track(object): + + def __getattribute__(self, name): + try: + return object.__getattribute__(self, name) + except: + pass + return None + + def __init__(self, xml_dom_fragment): + self.xml_dom_fragment = xml_dom_fragment + self.track_type = xml_dom_fragment.attrs['type'] + for el in self.xml_dom_fragment.children: + if not isinstance(el, NavigableString): + node_name = el.name.lower().strip().strip('_') + if node_name == 'id': + node_name = 'track_id' + node_value = el.string + other_node_name = "other_%s" % node_name + if getattr(self, node_name) is None: + setattr(self, node_name, node_value) + else: + if getattr(self, other_node_name) is None: + setattr(self, other_node_name, [node_value, ]) + else: + getattr(self, other_node_name).append(node_value) + + for o in [d for d in self.__dict__.keys() if d.startswith('other_')]: + try: + primary = o.replace('other_', '') + setattr(self, primary, int(getattr(self, primary))) + except: + for v in getattr(self, o): + try: + current = getattr(self, primary) + setattr(self, primary, int(v)) + getattr(self, o).append(current) + break + except: + pass + + def __repr__(self): + return "".format(self.track_id, self.track_type) + + def to_data(self): + data = {} + for k, v in six.iteritems(self.__dict__): + if k != 'xml_dom_fragment': + data[k] = v + return data + + +class MediaInfoWrapper(object): + + def __init__(self, xml): + self.xml_dom = xml + xml_types = (str,) # no unicode type in python3 + if isinstance(xml, xml_types): + self.xml_dom = MediaInfoWrapper.parse_xml_data_into_dom(xml) + + @staticmethod + def parse_xml_data_into_dom(xml_data): + return BeautifulSoup(xml_data, "xml") + + @staticmethod + def parse(filename, environment=ENV_DICT): + command = ["mediainfo", "-f", "--Output=XML", filename] + fileno_out, fname_out = mkstemp(suffix=".xml", prefix="media-") + fileno_err, fname_err = mkstemp(suffix=".err", prefix="media-") + fp_out = os.fdopen(fileno_out, 'r+b') + fp_err = os.fdopen(fileno_err, 'r+b') + p = Popen(command, stdout=fp_out, stderr=fp_err, env=environment) + p.wait() + fp_out.seek(0) + + xml_dom = MediaInfoWrapper.parse_xml_data_into_dom(fp_out.read()) + fp_out.close() + fp_err.close() + os.unlink(fname_out) + os.unlink(fname_err) + return MediaInfoWrapper(xml_dom) + + def _populate_tracks(self): + if self.xml_dom is None: + return + for xml_track in self.xml_dom.Mediainfo.File.find_all("track"): + self._tracks.append(Track(xml_track)) + + @property + def tracks(self): + if not hasattr(self, "_tracks"): + self._tracks = [] + if len(self._tracks) == 0: + self._populate_tracks() + return self._tracks + + def to_data(self): + data = {'tracks': []} + for track in self.tracks: + data['tracks'].append(track.to_data()) + return data + + def to_json(self): + return json.dumps(self.to_data()) diff --git a/openlp/core/ui/media/vlcplayer.py b/openlp/core/ui/media/vlcplayer.py index 99f8c37fd..9c2110e22 100644 --- a/openlp/core/ui/media/vlcplayer.py +++ b/openlp/core/ui/media/vlcplayer.py @@ -144,6 +144,9 @@ class VlcPlayer(MediaPlayer): def setup(self, display): """ Set up the media player + + :param display: The display where the media is + :return: """ vlc = get_vlc() display.vlc_widget = QtWidgets.QFrame(display) @@ -186,6 +189,9 @@ class VlcPlayer(MediaPlayer): def load(self, display): """ Load a video into VLC + + :param display: The display where the media is + :return: """ vlc = get_vlc() log.debug('load vid in Vlc Controller') @@ -214,18 +220,16 @@ class VlcPlayer(MediaPlayer): # parse the metadata of the file display.vlc_media.parse() self.volume(display, volume) - # We need to set media_info.length during load because we want - # to avoid start and stop the video twice. Once for real playback - # and once to just get media length. - # - # Media plugin depends on knowing media length before playback. - controller.media_info.length = int(display.vlc_media_player.get_media().get_duration() / 1000) return True def media_state_wait(self, display, media_state): """ Wait for the video to change its state Wait no longer than 60 seconds. (loading an iso file needs a long time) + + :param media_state: The state of the playing media + :param display: The display where the media is + :return: """ vlc = get_vlc() start = datetime.now() @@ -240,25 +244,40 @@ class VlcPlayer(MediaPlayer): def resize(self, display): """ Resize the player + + :param display: The display where the media is + :return: """ display.vlc_widget.resize(display.size()) def play(self, display): """ Play the current item + + :param display: The display where the media is + :return: """ vlc = get_vlc() controller = display.controller start_time = 0 log.debug('vlc play') - if self.state != MediaState.Paused and controller.media_info.start_time > 0: - start_time = controller.media_info.start_time + if display.controller.is_live: + if self.get_live_state() != MediaState.Paused and controller.media_info.start_time > 0: + start_time = controller.media_info.start_time + else: + if self.get_preview_state() != MediaState.Paused and controller.media_info.start_time > 0: + start_time = controller.media_info.start_time threading.Thread(target=display.vlc_media_player.play).start() if not self.media_state_wait(display, vlc.State.Playing): return False - if self.state != MediaState.Paused and controller.media_info.start_time > 0: - log.debug('vlc play, starttime set') - start_time = controller.media_info.start_time + if display.controller.is_live: + if self.get_live_state() != MediaState.Paused and controller.media_info.start_time > 0: + log.debug('vlc play, start time set') + start_time = controller.media_info.start_time + else: + if self.get_preview_state() != MediaState.Paused and controller.media_info.start_time > 0: + log.debug('vlc play, start time set') + start_time = controller.media_info.start_time log.debug('mediatype: ' + str(controller.media_info.media_type)) # Set tracks for the optical device if controller.media_info.media_type == MediaType.DVD: @@ -279,37 +298,45 @@ class VlcPlayer(MediaPlayer): log.debug('vlc play, starttime set: ' + str(controller.media_info.start_time)) start_time = controller.media_info.start_time controller.media_info.length = controller.media_info.end_time - controller.media_info.start_time - else: - controller.media_info.length = int(display.vlc_media_player.get_media().get_duration() / 1000) self.volume(display, controller.media_info.volume) if start_time > 0 and display.vlc_media_player.is_seekable(): - display.vlc_media_player.set_time(int(start_time * 1000)) - controller.seek_slider.setMaximum(controller.media_info.length * 1000) - self.state = MediaState.Playing + display.vlc_media_player.set_time(int(start_time)) + controller.seek_slider.setMaximum(controller.media_info.length) + self.set_state(MediaState.Playing, display) display.vlc_widget.raise_() return True def pause(self, display): """ Pause the current item + + :param display: The display where the media is + :return: """ vlc = get_vlc() if display.vlc_media.get_state() != vlc.State.Playing: return display.vlc_media_player.pause() if self.media_state_wait(display, vlc.State.Paused): - self.state = MediaState.Paused + self.set_state(MediaState.Paused, display) def stop(self, display): """ Stop the current item + + :param display: The display where the media is + :return: """ threading.Thread(target=display.vlc_media_player.stop).start() - self.state = MediaState.Stopped + self.set_state(MediaState.Stopped, display) def volume(self, display, vol): """ Set the volume + + :param vol: The volume to be sets + :param display: The display where the media is + :return: """ if display.has_audio: display.vlc_media_player.audio_set_volume(vol) @@ -317,6 +344,9 @@ class VlcPlayer(MediaPlayer): def seek(self, display, seek_value): """ Go to a particular position + + :param seek_value: The position of where a seek goes to + :param display: The display where the media is """ if display.controller.media_info.media_type == MediaType.CD \ or display.controller.media_info.media_type == MediaType.DVD: @@ -327,14 +357,19 @@ class VlcPlayer(MediaPlayer): def reset(self, display): """ Reset the player + + :param display: The display where the media is """ display.vlc_media_player.stop() display.vlc_widget.setVisible(False) - self.state = MediaState.Off + self.set_state(MediaState.Off, display) def set_visible(self, display, status): """ Set the visibility + + :param display: The display where the media is + :param status: The visibility status """ if self.has_own_widget: display.vlc_widget.setVisible(status) @@ -342,6 +377,8 @@ class VlcPlayer(MediaPlayer): def update_ui(self, display): """ Update the UI + + :param display: The display where the media is """ vlc = get_vlc() # Stop video if playback is finished. diff --git a/openlp/core/ui/media/webkitplayer.py b/openlp/core/ui/media/webkitplayer.py index 0a35fe085..cc8c7f55c 100644 --- a/openlp/core/ui/media/webkitplayer.py +++ b/openlp/core/ui/media/webkitplayer.py @@ -99,74 +99,6 @@ VIDEO_HTML = """ """ -FLASH_CSS = """ -#flash { - z-index:5; -} -""" - -FLASH_JS = """ - function getFlashMovieObject(movieName) - { - if (window.document[movieName]){ - return window.document[movieName]; - } - if (document.embeds && document.embeds[movieName]){ - return document.embeds[movieName]; - } - } - - function show_flash(state, path, volume, variable_value){ - var text = document.getElementById('flash'); - var flashMovie = getFlashMovieObject("OpenLPFlashMovie"); - var src = "src = 'file:///" + path + "'"; - var view_parm = " wmode='opaque'" + " width='100%%'" + " height='100%%'"; - var swf_parm = " name='OpenLPFlashMovie'" + " autostart='true' loop='false' play='true'" + - " hidden='false' swliveconnect='true' allowscriptaccess='always'" + " volume='" + volume + "'"; - - switch(state){ - case 'load': - text.innerHTML = ""; - flashMovie = getFlashMovieObject("OpenLPFlashMovie"); - flashMovie.Play(); - break; - case 'play': - flashMovie.Play(); - break; - case 'pause': - flashMovie.StopPlay(); - break; - case 'stop': - flashMovie.StopPlay(); - tempHtml = text.innerHTML; - text.innerHTML = ''; - text.innerHTML = tempHtml; - break; - case 'close': - flashMovie.StopPlay(); - text.innerHTML = ''; - break; - case 'length': - return flashMovie.TotalFrames(); - case 'current_time': - return flashMovie.CurrentFrame(); - case 'seek': -// flashMovie.GotoFrame(variable_value); - break; - case 'isEnded': - //TODO check flash end - return false; - case 'setVisible': - text.style.visibility = variable_value; - break; - } - } -""" - -FLASH_HTML = """ - -""" - VIDEO_EXT = ['*.3gp', '*.3gpp', '*.3g2', '*.3gpp2', '*.aac', '*.flv', '*.f4a', '*.f4b', '*.f4p', '*.f4v', '*.mov', '*.m4a', '*.m4b', '*.m4p', '*.m4v', '*.mkv', '*.mp4', '*.ogv', '*.webm', '*.mpg', '*.wmv', '*.mpeg', '*.avi', '*.swf'] @@ -198,23 +130,25 @@ class WebkitPlayer(MediaPlayer): """ background = QtGui.QColor(Settings().value('players/background color')).name() css = VIDEO_CSS % {'bgcolor': background} - return css + FLASH_CSS + return css def get_media_display_javascript(self): """ Add javascript functions to htmlbuilder """ - return VIDEO_JS + FLASH_JS + return VIDEO_JS def get_media_display_html(self): """ Add html code to htmlbuilder """ - return VIDEO_HTML + FLASH_HTML + return VIDEO_HTML def setup(self, display): """ Set up the player + + :param display: The display to be updated. """ display.web_view.resize(display.size()) display.web_view.raise_() @@ -235,6 +169,8 @@ class WebkitPlayer(MediaPlayer): def load(self, display): """ Load a video + + :param display: The display to be updated. """ log.debug('load vid in Webkit Controller') controller = display.controller @@ -249,132 +185,120 @@ class WebkitPlayer(MediaPlayer): else: loop = 'false' display.web_view.setVisible(True) - if controller.media_info.file_info.suffix() == 'swf': - controller.media_info.is_flash = True - js = 'show_flash("load","%s");' % (path.replace('\\', '\\\\')) - else: - js = 'show_video("load", "%s", %s, %s);' % (path.replace('\\', '\\\\'), str(vol), loop) + js = 'show_video("load", "%s", %s, %s);' % (path.replace('\\', '\\\\'), str(vol), loop) display.frame.evaluateJavaScript(js) return True def resize(self, display): """ Resize the player + + :param display: The display to be updated. """ display.web_view.resize(display.size()) def play(self, display): """ Play a video + + :param display: The display to be updated. """ controller = display.controller display.web_loaded = True - length = 0 start_time = 0 - if self.state != MediaState.Paused and controller.media_info.start_time > 0: - start_time = controller.media_info.start_time - self.set_visible(display, True) - if controller.media_info.is_flash: - display.frame.evaluateJavaScript('show_flash("play");') + if display.controller.is_live: + if self.get_live_state() != MediaState.Paused and controller.media_info.start_time > 0: + start_time = controller.media_info.start_time else: - display.frame.evaluateJavaScript('show_video("play");') + if self.get_preview_state() != MediaState.Paused and controller.media_info.start_time > 0: + start_time = controller.media_info.start_time + self.set_visible(display, True) + display.frame.evaluateJavaScript('show_video("play");') if start_time > 0: self.seek(display, controller.media_info.start_time * 1000) - # TODO add playing check and get the correct media length - controller.media_info.length = length - self.state = MediaState.Playing + self.set_state(MediaState.Playing, display) display.web_view.raise_() return True def pause(self, display): """ Pause a video + + :param display: The display to be updated. """ - controller = display.controller - if controller.media_info.is_flash: - display.frame.evaluateJavaScript('show_flash("pause");') - else: - display.frame.evaluateJavaScript('show_video("pause");') - self.state = MediaState.Paused + display.frame.evaluateJavaScript('show_video("pause");') + self.set_state(MediaState.Paused, display) def stop(self, display): """ Stop a video + + :param display: The display to be updated. """ - controller = display.controller - if controller.media_info.is_flash: - display.frame.evaluateJavaScript('show_flash("stop");') - else: - display.frame.evaluateJavaScript('show_video("stop");') - self.state = MediaState.Stopped + display.frame.evaluateJavaScript('show_video("stop");') + self.set_state(MediaState.Stopped, display) def volume(self, display, volume): """ Set the volume + + :param display: The display to be updated. + :param volume: The volume to be set. """ - controller = display.controller # 1.0 is the highest value if display.has_audio: vol = float(volume) / float(100) - if not controller.media_info.is_flash: - display.frame.evaluateJavaScript('show_video(null, null, %s);' % str(vol)) + display.frame.evaluateJavaScript('show_video(null, null, %s);' % str(vol)) def seek(self, display, seek_value): """ Go to a position in the video + + :param display: The display to be updated. + :param seek_value: The value to be set. """ - controller = display.controller - if controller.media_info.is_flash: - seek = seek_value - display.frame.evaluateJavaScript('show_flash("seek", null, null, "%s");' % seek) - else: - seek = float(seek_value) / 1000 - display.frame.evaluateJavaScript('show_video("seek", null, null, null, "%f");' % seek) + seek = float(seek_value) / 1000 + display.frame.evaluateJavaScript('show_video("seek", null, null, null, "%f");' % seek) def reset(self, display): """ Reset the player - """ - controller = display.controller - if controller.media_info.is_flash: - display.frame.evaluateJavaScript('show_flash("close");') - else: - display.frame.evaluateJavaScript('show_video("close");') - self.state = MediaState.Off - def set_visible(self, display, status): + :param display: The display to be updated. + """ + display.frame.evaluateJavaScript('show_video("close");') + self.set_state(MediaState.Off, display) + + def set_visible(self, display, visibility): """ Set the visibility + + :param display: The display to be updated. + :param visibility: The visibility to be set. """ - controller = display.controller - if status: + if visibility: is_visible = "visible" else: is_visible = "hidden" - if controller.media_info.is_flash: - display.frame.evaluateJavaScript('show_flash("setVisible", null, null, "%s");' % is_visible) - else: - display.frame.evaluateJavaScript('show_video("setVisible", null, null, null, "%s");' % is_visible) + display.frame.evaluateJavaScript('show_video("setVisible", null, null, null, "%s");' % is_visible) def update_ui(self, display): """ Update the UI + + :param display: The display to be updated. """ controller = display.controller - if controller.media_info.is_flash: - current_time = display.frame.evaluateJavaScript('show_flash("current_time");') - length = display.frame.evaluateJavaScript('show_flash("length");') - else: - if display.frame.evaluateJavaScript('show_video("isEnded");'): - self.stop(display) - current_time = display.frame.evaluateJavaScript('show_video("current_time");') - # check if conversion was ok and value is not 'NaN' - if current_time and current_time != float('inf'): - current_time = int(current_time * 1000) - length = display.frame.evaluateJavaScript('show_video("length");') - # check if conversion was ok and value is not 'NaN' - if length and length != float('inf'): - length = int(length * 1000) + if display.frame.evaluateJavaScript('show_video("isEnded");'): + self.stop(display) + current_time = display.frame.evaluateJavaScript('show_video("current_time");') + # check if conversion was ok and value is not 'NaN' + if current_time and current_time != float('inf'): + current_time = int(current_time * 1000) + length = display.frame.evaluateJavaScript('show_video("length");') + # check if conversion was ok and value is not 'NaN' + if length and length != float('inf'): + length = int(length * 1000) if current_time and length: controller.media_info.length = length controller.seek_slider.setMaximum(length) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index fd88e67ee..96ce82868 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -84,7 +84,7 @@ class DisplayController(QtWidgets.QWidget): super(DisplayController, self).__init__(parent) self.is_live = False self.display = None - self.controller_type = DisplayControllerType.Plugin + self.controller_type = None def send_to_plugins(self, *args): """ diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index d620a0f79..20143ddaa 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -147,6 +147,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): def update_lines_text(self, lines): """ Updates the lines on a page on the wizard + :param lines: then number of lines to be displayed """ self.main_line_count_label.setText( translate('OpenLP.ThemeForm', '(approximately %d lines per slide)') % int(lines)) @@ -186,6 +187,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): def on_current_id_changed(self, page_id): """ Detects Page changes and updates as appropriate. + :param page_id: current page number """ enabled = self.page(page_id) == self.area_position_page self.setOption(QtWidgets.QWizard.HaveCustomButton1, enabled) diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 4a35f909c..3835056fb 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.py @@ -111,6 +111,7 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties): def setupUi(self, image): """ Set up the wizard UI. + :param image: path to start up image """ self.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) self.setModal(True) @@ -210,6 +211,7 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties): def on_current_id_changed(self, page_id): """ Perform necessary functions depending on which wizard page is active. + :param page_id: current page number """ if self.with_progress_page and self.page(page_id) == self.progress_page: self.pre_wizard() @@ -221,6 +223,7 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties): def custom_page_changed(self, page_id): """ Called when changing to a page other than the progress page + :param page_id: current page number """ pass diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index f35fd48c7..80a49f81c 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -195,7 +195,7 @@ class ImageMediaItem(MediaManagerItem): Add custom buttons to the end of the toolbar """ self.replace_action = self.toolbar.add_toolbar_action('replace_action', - icon=':/slides/slide_blank.png', + icon=':/slides/slide_theme.png', triggers=self.on_replace_click) self.reset_action = self.toolbar.add_toolbar_action('reset_action', icon=':/system/system_close.png', diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index dfe6f1fa4..507196395 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -29,8 +29,8 @@ from openlp.core.common import Registry, RegistryProperties, AppLocation, Settin translate from openlp.core.lib import ItemCapabilities, MediaManagerItem, MediaType, ServiceItem, ServiceItemContext, \ build_icon, check_item_selected -from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box -from openlp.core.ui import DisplayController, Display, DisplayControllerType +from openlp.core.lib.ui import create_widget_action, critical_error_message_box, create_horizontal_adjusting_combo_box +from openlp.core.ui import DisplayControllerType from openlp.core.ui.media import get_media_players, set_media_players, parse_optical_path, format_milliseconds from openlp.core.common.languagemanager import get_locale_key from openlp.core.ui.media.vlcplayer import get_vlc @@ -78,19 +78,9 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): self.single_service_item = False self.has_search = True self.media_object = None - self.display_controller = DisplayController(self.parent()) - self.display_controller.controller_layout = QtWidgets.QVBoxLayout() - self.media_controller.register_controller(self.display_controller) - self.media_controller.set_controls_visible(self.display_controller, False) - self.display_controller.preview_display = Display(self.display_controller) - self.display_controller.preview_display.hide() - self.display_controller.preview_display.setGeometry(QtCore.QRect(0, 0, 300, 300)) - self.display_controller.preview_display.screen = {'size': self.display_controller.preview_display.geometry()} - self.display_controller.preview_display.setup() - self.media_controller.setup_display(self.display_controller.preview_display, False) + # self.display_controller = DisplayController(self.parent()) Registry().register_function('video_background_replaced', self.video_background_replaced) Registry().register_function('mediaitem_media_rebuild', self.rebuild_players) - Registry().register_function('config_screen_changed', self.display_setup) # Allow DnD from the desktop self.list_view.activateDnD() @@ -101,12 +91,17 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): """ self.on_new_prompt = translate('MediaPlugin.MediaItem', 'Select Media') self.replace_action.setText(UiStrings().ReplaceBG) + self.replace_action_context.setText(UiStrings().ReplaceBG) if 'webkit' in get_media_players()[0]: self.replace_action.setToolTip(UiStrings().ReplaceLiveBG) + self.replace_action_context.setToolTip(UiStrings().ReplaceLiveBG) else: self.replace_action.setToolTip(UiStrings().ReplaceLiveBGDisabled) + self.replace_action_context.setToolTip(UiStrings().ReplaceLiveBGDisabled) self.reset_action.setText(UiStrings().ResetBG) self.reset_action.setToolTip(UiStrings().ResetLiveBG) + self.reset_action_context.setText(UiStrings().ResetBG) + self.reset_action_context.setToolTip(UiStrings().ResetLiveBG) self.automatic = UiStrings().Automatic self.display_type_label.setText(translate('MediaPlugin.MediaItem', 'Use Player:')) @@ -151,10 +146,11 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): Adds buttons to the end of the header bar. """ # Replace backgrounds do not work at present so remove functionality. - self.replace_action = self.toolbar.add_toolbar_action('replace_action', icon=':/slides/slide_blank.png', + self.replace_action = self.toolbar.add_toolbar_action('replace_action', icon=':/slides/slide_theme.png', triggers=self.on_replace_click) if 'webkit' not in get_media_players()[0]: self.replace_action.setDisabled(True) + self.replace_action_context.setDisabled(True) self.reset_action = self.toolbar.add_toolbar_action('reset_action', icon=':/system/system_close.png', visible=False, triggers=self.on_reset_click) self.media_widget = QtWidgets.QWidget(self) @@ -173,7 +169,17 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): self.page_layout.addWidget(self.media_widget) self.display_type_combo_box.currentIndexChanged.connect(self.override_player_changed) - def override_player_changed(self, index): + def add_custom_context_actions(self): + create_widget_action(self.list_view, separator=True) + self.replace_action_context = create_widget_action( + self.list_view, text=UiStrings().ReplaceBG, icon=':/slides/slide_blank.png', + triggers=self.on_replace_click) + self.reset_action_context = create_widget_action( + self.list_view, text=UiStrings().ReplaceLiveBG, icon=':/system/system_close.png', + visible=False, triggers=self.on_reset_click) + + @staticmethod + def override_player_changed(index): """ The Player has been overridden @@ -191,12 +197,14 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): """ self.media_controller.media_reset(self.live_controller) self.reset_action.setVisible(False) + self.reset_action_context.setVisible(False) def video_background_replaced(self): """ Triggered by main display on change of serviceitem. """ self.reset_action.setVisible(False) + self.reset_action_context.setVisible(False) def on_replace_click(self): """ @@ -215,6 +223,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): service_item.add_from_command(path, name, CLAPPERBOARD) if self.media_controller.video(DisplayControllerType.Live, service_item, video_behind_text=True): self.reset_action.setVisible(True) + self.reset_action_context.setVisible(True) else: critical_error_message_box(UiStrings().LiveBGError, translate('MediaPlugin.MediaItem', @@ -273,16 +282,14 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): service_item.processor = self.display_type_combo_box.currentText() service_item.add_from_command(path, name, CLAPPERBOARD) # Only get start and end times if going to a service - if context == ServiceItemContext.Service: - # Start media and obtain the length - if not self.media_controller.media_length(service_item): - return False + if not self.media_controller.media_length(service_item): + return False service_item.add_capability(ItemCapabilities.CanAutoStartForLive) service_item.add_capability(ItemCapabilities.CanEditTitle) service_item.add_capability(ItemCapabilities.RequiresMedia) if Settings().value(self.settings_section + '/media auto start') == QtCore.Qt.Checked: service_item.will_auto_start = True - # force a non-existent theme + # force a non-existent theme service_item.theme = -1 return True @@ -305,12 +312,6 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): ' '.join(self.media_controller.video_extensions_list), ' '.join(self.media_controller.audio_extensions_list), UiStrings().AllFiles) - def display_setup(self): - """ - Setup media controller display. - """ - self.media_controller.setup_display(self.display_controller.preview_display, False) - def populate_display_types(self): """ Load the combobox with the enabled media players, allowing user to select a specific player if settings allow. @@ -385,16 +386,16 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): if item_name: self.list_view.addItem(item_name) - def get_list(self, type=MediaType.Audio): + def get_list(self, media_type=MediaType.Audio): """ Get the list of media, optional select media type. - :param type: Type to get, defaults to audio. + :param media_type: Type to get, defaults to audio. :return: The media list """ media = Settings().value(self.settings_section + '/media files') media.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1])) - if type == MediaType.Audio: + if media_type == MediaType.Audio: extension = self.media_controller.audio_extensions_list else: extension = self.media_controller.video_extensions_list diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index a1e54a132..daeb4dc2c 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -60,13 +60,6 @@ class MediaPlugin(Plugin): """ Override the inherited initialise() method in order to upgrade the media before trying to load it """ - # FIXME: Remove after 2.2 release. - # This is needed to load the list of media from the config saved before the settings rewrite. - if self.media_item_class is not None: - loaded_list = Settings().get_files_from_config(self) - # Now save the list to the config using our Settings class. - if loaded_list: - Settings().setValue('%s/%s files' % (self.settings_section, self.name), loaded_list) super().initialise() def app_startup(self): diff --git a/resources/images/general_preview.png b/resources/images/general_preview.png index d196792bb..2d6b7b631 100644 Binary files a/resources/images/general_preview.png and b/resources/images/general_preview.png differ diff --git a/tests/functional/openlp_core_ui/test_mainwindow.py b/tests/functional/openlp_core_ui/test_mainwindow.py index a49ded25c..8dcfd0518 100644 --- a/tests/functional/openlp_core_ui/test_mainwindow.py +++ b/tests/functional/openlp_core_ui/test_mainwindow.py @@ -148,7 +148,7 @@ class TestMainWindow(TestCase, TestMixin): # THEN: the following registry functions should have been registered self.assertEqual(len(self.registry.service_list), 6, 'The registry should have 6 services.') - self.assertEqual(len(self.registry.functions_list), 16, 'The registry should have 16 functions') + self.assertEqual(len(self.registry.functions_list), 17, 'The registry should have 17 functions') self.assertTrue('application' in self.registry.service_list, 'The application should have been registered.') self.assertTrue('main_window' in self.registry.service_list, 'The main_window should have been registered.') self.assertTrue('media_controller' in self.registry.service_list, 'The media_controller should have been ' diff --git a/tests/functional/openlp_core_ui_media/test_mediacontroller.py b/tests/functional/openlp_core_ui_media/test_mediacontroller.py index 567ee9847..a37961b7a 100644 --- a/tests/functional/openlp_core_ui_media/test_mediacontroller.py +++ b/tests/functional/openlp_core_ui_media/test_mediacontroller.py @@ -78,10 +78,11 @@ class TestMediaController(TestCase, TestMixin): """ Test that we don't try to play media when no players available """ - # GIVEN: A mocked UiStrings, get_media_players, controller, display and service_item - with patch('openlp.core.ui.media.mediacontroller.get_media_players') as mocked_get_media_players,\ + # GIVEN: A mocked UiStrings, get_used_players, controller, display and service_item + with patch('openlp.core.ui.media.mediacontroller.MediaController._get_used_players') as \ + mocked_get_used_players,\ patch('openlp.core.ui.media.mediacontroller.UiStrings') as mocked_uistrings: - mocked_get_media_players.return_value = ([], '') + mocked_get_used_players.return_value = ([]) mocked_ret_uistrings = MagicMock() mocked_ret_uistrings.Automatic = 1 mocked_uistrings.return_value = mocked_ret_uistrings @@ -97,14 +98,14 @@ class TestMediaController(TestCase, TestMixin): # THEN: it should return False self.assertFalse(ret, '_check_file_type should return False when no mediaplayers are available.') - @patch('openlp.core.ui.media.mediacontroller.get_media_players') + @patch('openlp.core.ui.media.mediacontroller.MediaController._get_used_players') @patch('openlp.core.ui.media.mediacontroller.UiStrings') - def check_file_type_no_processor_test(self, mocked_uistrings, mocked_get_media_players): + def check_file_type_no_processor_test(self, mocked_uistrings, mocked_get_used_players): """ Test that we don't try to play media when the processor for the service item is None """ # GIVEN: A mocked UiStrings, get_media_players, controller, display and service_item - mocked_get_media_players.return_value = ([], '') + mocked_get_used_players.return_value = ([], '') mocked_ret_uistrings = MagicMock() mocked_ret_uistrings.Automatic = 1 mocked_uistrings.return_value = mocked_ret_uistrings @@ -120,14 +121,14 @@ class TestMediaController(TestCase, TestMixin): # THEN: it should return False self.assertFalse(ret, '_check_file_type should return False when the processor for service_item is None.') - @patch('openlp.core.ui.media.mediacontroller.get_media_players') + @patch('openlp.core.ui.media.mediacontroller.MediaController._get_used_players') @patch('openlp.core.ui.media.mediacontroller.UiStrings') - def check_file_type_automatic_processor_test(self, mocked_uistrings, mocked_get_media_players): + def check_file_type_automatic_processor_test(self, mocked_uistrings, mocked_get_used_players): """ Test that we can play media when players are available and we have a automatic processor from the service item """ # GIVEN: A mocked UiStrings, get_media_players, controller, display and service_item - mocked_get_media_players.return_value = (['vlc', 'webkit'], '') + mocked_get_used_players.return_value = (['vlc', 'webkit']) mocked_ret_uistrings = MagicMock() mocked_ret_uistrings.Automatic = 1 mocked_uistrings.return_value = mocked_ret_uistrings @@ -150,21 +151,21 @@ class TestMediaController(TestCase, TestMixin): self.assertTrue(ret, '_check_file_type should return True when mediaplayers are available and ' 'the service item has an automatic processor.') - @patch('openlp.core.ui.media.mediacontroller.get_media_players') + @patch('openlp.core.ui.media.mediacontroller.MediaController._get_used_players') @patch('openlp.core.ui.media.mediacontroller.UiStrings') - def check_file_type_processor_different_from_available_test(self, mocked_uistrings, mocked_get_media_players): + def check_file_type_processor_different_from_available_test(self, mocked_uistrings, mocked_get_used_players): """ Test that we can play media when players available are different from the processor from the service item """ # GIVEN: A mocked UiStrings, get_media_players, controller, display and service_item - mocked_get_media_players.return_value = (['phonon'], '') + mocked_get_used_players.return_value = (['system']) mocked_ret_uistrings = MagicMock() mocked_ret_uistrings.Automatic = 'automatic' mocked_uistrings.return_value = mocked_ret_uistrings media_controller = MediaController() mocked_phonon = MagicMock() mocked_phonon.video_extensions_list = ['*.mp4'] - media_controller.media_players = {'phonon': mocked_phonon} + media_controller.media_players = {'system': mocked_phonon} mocked_controller = MagicMock() mocked_suffix = MagicMock() mocked_suffix.return_value = 'mp4' diff --git a/tests/functional/openlp_core_ui_media/test_vlcplayer.py b/tests/functional/openlp_core_ui_media/test_vlcplayer.py index 98ab16ca7..8e49fe4d9 100644 --- a/tests/functional/openlp_core_ui_media/test_vlcplayer.py +++ b/tests/functional/openlp_core_ui_media/test_vlcplayer.py @@ -380,7 +380,6 @@ class TestVLCPlayer(TestCase, TestMixin): mocked_display.vlc_media_player.set_media.assert_called_with(mocked_vlc_media) mocked_vlc_media.parse.assert_called_with() mocked_volume.assert_called_with(mocked_display, 100) - self.assertEqual(10, mocked_controller.media_info.length) self.assertTrue(result) @patch('openlp.core.ui.media.vlcplayer.is_win') @@ -426,7 +425,6 @@ class TestVLCPlayer(TestCase, TestMixin): mocked_display.vlc_media_player.set_media.assert_called_with(mocked_vlc_media) mocked_vlc_media.parse.assert_called_with() mocked_volume.assert_called_with(mocked_display, 100) - self.assertEqual(10, mocked_controller.media_info.length) self.assertTrue(result) @patch('openlp.core.ui.media.vlcplayer.is_win') @@ -472,7 +470,6 @@ class TestVLCPlayer(TestCase, TestMixin): mocked_display.vlc_media_player.set_media.assert_called_with(mocked_vlc_media) mocked_vlc_media.parse.assert_called_with() mocked_volume.assert_called_with(mocked_display, 100) - self.assertEqual(10, mocked_controller.media_info.length) self.assertTrue(result) @patch('openlp.core.ui.media.vlcplayer.is_win') @@ -628,7 +625,7 @@ class TestVLCPlayer(TestCase, TestMixin): mocked_display.controller = mocked_controller mocked_display.vlc_media_player.get_media.return_value = mocked_media vlc_player = VlcPlayer(None) - vlc_player.state = MediaState.Paused + vlc_player.set_state(MediaState.Paused, mocked_display) # WHEN: play() is called with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait, \ @@ -638,10 +635,8 @@ class TestVLCPlayer(TestCase, TestMixin): # THEN: A bunch of things should happen to play the media mocked_thread.start.assert_called_with() - self.assertEqual(50, mocked_controller.media_info.length) mocked_volume.assert_called_with(mocked_display, 100) - mocked_controller.seek_slider.setMaximum.assert_called_with(50000) - self.assertEqual(MediaState.Playing, vlc_player.state) + self.assertEqual(MediaState.Playing, vlc_player.get_live_state()) mocked_display.vlc_widget.raise_.assert_called_with() self.assertTrue(result, 'The value returned from play() should be True') @@ -661,7 +656,7 @@ class TestVLCPlayer(TestCase, TestMixin): mocked_display = MagicMock() mocked_display.controller = mocked_controller vlc_player = VlcPlayer(None) - vlc_player.state = MediaState.Paused + vlc_player.set_state(MediaState.Paused, mocked_display) # WHEN: play() is called with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait, \ @@ -695,7 +690,7 @@ class TestVLCPlayer(TestCase, TestMixin): mocked_display = MagicMock() mocked_display.controller = mocked_controller vlc_player = VlcPlayer(None) - vlc_player.state = MediaState.Paused + vlc_player.set_state(MediaState.Paused, mocked_display) # WHEN: play() is called with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait, \ @@ -709,10 +704,8 @@ class TestVLCPlayer(TestCase, TestMixin): mocked_display.vlc_media_player.play.assert_called_with() mocked_display.vlc_media_player.audio_set_track.assert_called_with(1) mocked_display.vlc_media_player.video_set_spu.assert_called_with(1) - self.assertEqual(50, mocked_controller.media_info.length) mocked_volume.assert_called_with(mocked_display, 100) - mocked_controller.seek_slider.setMaximum.assert_called_with(50000) - self.assertEqual(MediaState.Playing, vlc_player.state) + self.assertEqual(MediaState.Playing, vlc_player.get_live_state()) mocked_display.vlc_widget.raise_.assert_called_with() self.assertTrue(result, 'The value returned from play() should be True') @@ -739,7 +732,7 @@ class TestVLCPlayer(TestCase, TestMixin): mocked_display.vlc_media.get_state.assert_called_with() mocked_display.vlc_media_player.pause.assert_called_with() mocked_media_state_wait.assert_called_with(mocked_display, 2) - self.assertEqual(MediaState.Paused, vlc_player.state) + self.assertEqual(MediaState.Paused, vlc_player.get_live_state()) @patch('openlp.core.ui.media.vlcplayer.get_vlc') def pause_not_playing_test(self, mocked_get_vlc): @@ -805,7 +798,7 @@ class TestVLCPlayer(TestCase, TestMixin): # THEN: A thread should have been started to stop VLC mocked_threading.Thread.assert_called_with(target=mocked_stop) mocked_thread.start.assert_called_with() - self.assertEqual(MediaState.Stopped, vlc_player.state) + self.assertEqual(MediaState.Stopped, vlc_player.get_live_state()) def volume_test(self): """ @@ -900,10 +893,10 @@ class TestVLCPlayer(TestCase, TestMixin): # WHEN: reset() is called vlc_player.reset(mocked_display) - # THEN: The media should be stopped and invsibile + # THEN: The media should be stopped and invisible mocked_display.vlc_media_player.stop.assert_called_with() mocked_display.vlc_widget.setVisible.assert_called_with(False) - self.assertEqual(MediaState.Off, vlc_player.state) + self.assertEqual(MediaState.Off, vlc_player.get_live_state()) def set_visible_has_own_widget_test(self): """ diff --git a/tests/functional/openlp_plugins/media/test_mediaplugin.py b/tests/functional/openlp_plugins/media/test_mediaplugin.py index 9dbab37cf..1e11de4fa 100644 --- a/tests/functional/openlp_plugins/media/test_mediaplugin.py +++ b/tests/functional/openlp_plugins/media/test_mediaplugin.py @@ -54,8 +54,6 @@ class MediaPluginTest(TestCase, TestMixin): media_plugin.initialise() # THEN: The settings should be upgraded and the base initialise() method should be called - mocked_settings.get_files_from_config.assert_called_with(media_plugin) - mocked_settings.setValue.assert_called_with('media/media files', True) mocked_initialise.assert_called_with() def test_about_text(self): diff --git a/tests/interfaces/openlp_core_ul_media_vendor/__init__.py b/tests/interfaces/openlp_core_ul_media_vendor/__init__.py new file mode 100644 index 000000000..02bded5b0 --- /dev/null +++ b/tests/interfaces/openlp_core_ul_media_vendor/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2016 OpenLP Developers # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### diff --git a/tests/interfaces/openlp_core_ul_media_vendor/test_mediainfoWrapper.py b/tests/interfaces/openlp_core_ul_media_vendor/test_mediainfoWrapper.py new file mode 100644 index 000000000..acf17f581 --- /dev/null +++ b/tests/interfaces/openlp_core_ul_media_vendor/test_mediainfoWrapper.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2016 OpenLP Developers # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### +""" +Package to test the openlp.core.ui.media package. +""" + +import os +from unittest import TestCase + +from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper + +TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources', 'media')) + +TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg', 9404], ['mp4_file.mp4', 188336]] + + +class TestMediainfoWrapper(TestCase): + + def media_length_test(self): + """ + Test the Media Info basic functionality + """ + for test_data in TEST_MEDIA: + # GIVEN: a media file + full_path = os.path.normpath(os.path.join(TEST_PATH, test_data[0])) + + # WHEN the media data is retrieved + results = MediaInfoWrapper.parse(full_path) + + # THEN you can determine the run time + self.assertEqual(results.tracks[0].duration, test_data[1], 'The correct duration is returned for ' + + test_data[0]) diff --git a/tests/resources/media/avi_file.avi b/tests/resources/media/avi_file.avi new file mode 100644 index 000000000..9a89932fe Binary files /dev/null and b/tests/resources/media/avi_file.avi differ diff --git a/tests/resources/media/mp3_file.mp3 b/tests/resources/media/mp3_file.mp3 new file mode 100644 index 000000000..2b5cc44f7 Binary files /dev/null and b/tests/resources/media/mp3_file.mp3 differ diff --git a/tests/resources/media/mp4_file.mp4 b/tests/resources/media/mp4_file.mp4 new file mode 100644 index 000000000..73abf10a0 Binary files /dev/null and b/tests/resources/media/mp4_file.mp4 differ diff --git a/tests/resources/media/mpg_file.mpg b/tests/resources/media/mpg_file.mpg new file mode 100644 index 000000000..af768c542 Binary files /dev/null and b/tests/resources/media/mpg_file.mpg differ