This commit is contained in:
Raoul Snyman 2014-09-05 22:15:44 +02:00
commit 09be73cc26
17 changed files with 1799 additions and 48 deletions

View File

@ -111,6 +111,9 @@ class ItemCapabilities(object):
``CanEditTitle`` ``CanEditTitle``
The capability to edit the title of the item The capability to edit the title of the item
``IsOptical``
.Determines is the service_item is based on an optical device
""" """
CanPreview = 1 CanPreview = 1
CanEdit = 2 CanEdit = 2
@ -129,6 +132,7 @@ class ItemCapabilities(object):
HasBackgroundAudio = 15 HasBackgroundAudio = 15
CanAutoStartForLive = 16 CanAutoStartForLive = 16
CanEditTitle = 17 CanEditTitle = 17
IsOptical = 18
class ServiceItem(RegistryProperties): class ServiceItem(RegistryProperties):
@ -416,7 +420,10 @@ class ServiceItem(RegistryProperties):
for text_image in service_item['serviceitem']['data']: for text_image in service_item['serviceitem']['data']:
if not self.title: if not self.title:
self.title = text_image['title'] self.title = text_image['title']
if path: if self.is_capable(ItemCapabilities.IsOptical):
self.has_original_files = False
self.add_from_command(text_image['path'], text_image['title'], text_image['image'])
elif path:
self.has_original_files = False self.has_original_files = False
self.add_from_command(path, text_image['title'], text_image['image']) self.add_from_command(path, text_image['title'], text_image['image'])
else: else:
@ -427,7 +434,8 @@ class ServiceItem(RegistryProperties):
""" """
Returns the title of the service item. Returns the title of the service item.
""" """
if self.is_text() or ItemCapabilities.CanEditTitle in self.capabilities: if self.is_text() or self.is_capable(ItemCapabilities.IsOptical) \
or self.is_capable(ItemCapabilities.CanEditTitle):
return self.title return self.title
else: else:
if len(self._raw_frames) > 1: if len(self._raw_frames) > 1:
@ -495,7 +503,8 @@ class ServiceItem(RegistryProperties):
""" """
Confirms if the ServiceItem uses a file Confirms if the ServiceItem uses a file
""" """
return self.service_item_type == ServiceItemType.Image or self.service_item_type == ServiceItemType.Command return self.service_item_type == ServiceItemType.Image or \
(self.service_item_type == ServiceItemType.Command and not self.is_capable(ItemCapabilities.IsOptical))
def is_text(self): def is_text(self):
""" """
@ -553,7 +562,7 @@ class ServiceItem(RegistryProperties):
frame = self._raw_frames[row] frame = self._raw_frames[row]
except IndexError: except IndexError:
return '' return ''
if self.is_image(): if self.is_image() or self.is_capable(ItemCapabilities.IsOptical):
path_from = frame['path'] path_from = frame['path']
else: else:
path_from = os.path.join(frame['path'], frame['title']) path_from = os.path.join(frame['path'], frame['title'])
@ -623,12 +632,17 @@ class ServiceItem(RegistryProperties):
self.is_valid = False self.is_valid = False
break break
elif self.is_command(): elif self.is_command():
file_name = os.path.join(frame['path'], frame['title']) if self.is_capable(ItemCapabilities.IsOptical):
if not os.path.exists(file_name): if not os.path.exists(frame['title']):
self.is_valid = False
break
if suffix_list and not self.is_text():
file_suffix = frame['title'].split('.')[-1]
if file_suffix.lower() not in suffix_list:
self.is_valid = False self.is_valid = False
break break
else:
file_name = os.path.join(frame['path'], frame['title'])
if not os.path.exists(file_name):
self.is_valid = False
break
if suffix_list and not self.is_text():
file_suffix = frame['title'].split('.')[-1]
if file_suffix.lower() not in suffix_list:
self.is_valid = False
break

View File

@ -72,6 +72,9 @@ class MediaInfo(object):
length = 0 length = 0
start_time = 0 start_time = 0
end_time = 0 end_time = 0
title_track = 0
audio_track = 0
subtitle_track = 0
media_type = MediaType() media_type = MediaType()
@ -107,6 +110,40 @@ def set_media_players(players_list, overridden_player='auto'):
players = players.replace(overridden_player, '[%s]' % overridden_player) players = players.replace(overridden_player, '[%s]' % overridden_player)
Settings().setValue('media/players', players) Settings().setValue('media/players', players)
def parse_optical_path(input):
"""
Split the optical path info.
:param input: 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=':')
title = int(clip_info[1])
audio_track = int(clip_info[2])
subtitle_track = int(clip_info[3])
start = float(clip_info[4])
end = float(clip_info[5])
clip_name = clip_info[6]
filename = clip_info[7]
# Windows path usually contains a colon after the drive letter
if len(clip_info) > 8:
filename += ':' + clip_info[8]
return filename, title, audio_track, subtitle_track, start, end, clip_name
def format_milliseconds(milliseconds):
"""
Format milliseconds into a human readable time string.
:param milliseconds: Milliseconds to format
:return: Time string in format: hh.mm.ss,ttt
"""
seconds, millis = divmod(milliseconds, 1000)
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
return "%02d:%02d:%02d,%03d" % (hours, minutes, seconds, millis)
from .mediacontroller import MediaController from .mediacontroller import MediaController
from .playertab import PlayerTab from .playertab import PlayerTab

View File

@ -36,9 +36,10 @@ import datetime
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties, Settings, UiStrings, translate from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties, Settings, UiStrings, translate
from openlp.core.lib import OpenLPToolbar from openlp.core.lib import OpenLPToolbar, 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.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\
parse_optical_path
from openlp.core.ui.media.mediaplayer import MediaPlayer from openlp.core.ui.media.mediaplayer import MediaPlayer
from openlp.core.common import AppLocation from openlp.core.common import AppLocation
from openlp.core.ui import DisplayControllerType from openlp.core.ui import DisplayControllerType
@ -368,7 +369,16 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
controller.media_info.file_info = QtCore.QFileInfo(service_item.get_frame_path()) controller.media_info.file_info = QtCore.QFileInfo(service_item.get_frame_path())
display = self._define_display(controller) display = self._define_display(controller)
if controller.is_live: if controller.is_live:
is_valid = self._check_file_type(controller, display, service_item) # if this is an optical device use special handling
if service_item.is_capable(ItemCapabilities.IsOptical):
log.debug('video is optical and live')
path = service_item.get_frame_path()
(name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(path)
is_valid = self.media_setup_optical(name, title, audio_track, subtitle_track, start, end, display,
controller)
else:
log.debug('video is not optical and live')
is_valid = self._check_file_type(controller, display, service_item)
display.override['theme'] = '' display.override['theme'] = ''
display.override['video'] = True display.override['video'] = True
if controller.media_info.is_background: if controller.media_info.is_background:
@ -379,12 +389,21 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
controller.media_info.start_time = service_item.start_time controller.media_info.start_time = service_item.start_time
controller.media_info.end_time = service_item.end_time controller.media_info.end_time = service_item.end_time
elif controller.preview_display: elif controller.preview_display:
is_valid = self._check_file_type(controller, display, service_item) if service_item.is_capable(ItemCapabilities.IsOptical):
log.debug('video is optical and preview')
path = service_item.get_frame_path()
(name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(path)
is_valid = self.media_setup_optical(name, title, audio_track, subtitle_track, start, end, display,
controller)
else:
log.debug('video is not optical and preview')
is_valid = self._check_file_type(controller, display, service_item)
if not is_valid: if not is_valid:
# Media could not be loaded correctly # Media could not be loaded correctly
critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File'), critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File'),
translate('MediaPlugin.MediaItem', 'Unsupported File')) translate('MediaPlugin.MediaItem', 'Unsupported File'))
return False return False
log.debug('video mediatype: ' + str(controller.media_info.media_type))
# dont care about actual theme, set a black background # dont care about actual theme, set a black background
if controller.is_live and not controller.media_info.is_background: if controller.is_live and not controller.media_info.is_background:
display.frame.evaluateJavaScript('show_video( "setBackBoard", null, null, null,"visible");') display.frame.evaluateJavaScript('show_video( "setBackBoard", null, null, null,"visible");')
@ -436,6 +455,62 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
log.debug('use %s controller' % self.current_media_players[controller.controller_type]) log.debug('use %s controller' % self.current_media_players[controller.controller_type])
return True return True
def media_setup_optical(self, filename, title, audio_track, subtitle_track, start, end, display, controller):
"""
Setup playback of optical media
:param filename: Path of the optical device/drive.
: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 display: The display to play the media.
:param controller: The media contraoller.
:return: True if setup succeded else False.
"""
log.debug('media_setup_optical')
if controller is None:
controller = self.display_controllers[DisplayControllerType.Plugin]
# stop running videos
self.media_reset(controller)
# Setup media info
controller.media_info = MediaInfo()
controller.media_info.file_info = QtCore.QFileInfo(filename)
if audio_track == -1 and subtitle_track == -1:
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.title_track = title
controller.media_info.audio_track = audio_track
controller.media_info.subtitle_track = subtitle_track
# When called from mediaitem display is None
if display is None:
display = controller.preview_display
# Find vlc player
used_players = get_media_players()[0]
vlc_player = None
for title in used_players:
player = self.media_players[title]
if player.name == 'vlc':
vlc_player = player
if vlc_player is None:
critical_error_message_box(translate('MediaPlugin.MediaItem', 'VLC player required'),
translate('MediaPlugin.MediaItem',
'VLC player required for playback of optical devices'))
return False
vlc_player.load(display)
self.resize(display, vlc_player)
self.current_media_players[controller.controller_type] = vlc_player
if audio_track == -1 and subtitle_track == -1:
controller.media_info.media_type = MediaType.CD
else:
controller.media_info.media_type = MediaType.DVD
return True
def _check_file_type(self, controller, display, service_item): def _check_file_type(self, controller, display, service_item):
""" """
Select the correct media Player type from the prioritized Player list Select the correct media Player type from the prioritized Player list

View File

@ -40,7 +40,7 @@ from PyQt4 import QtGui
from openlp.core.common import Settings, is_win, is_macosx from openlp.core.common import Settings, is_win, is_macosx
from openlp.core.lib import translate from openlp.core.lib import translate
from openlp.core.ui.media import MediaState from openlp.core.ui.media import MediaState, MediaType
from openlp.core.ui.media.mediaplayer import MediaPlayer from openlp.core.ui.media.mediaplayer import MediaPlayer
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -166,7 +166,19 @@ class VlcPlayer(MediaPlayer):
file_path = str(controller.media_info.file_info.absoluteFilePath()) file_path = str(controller.media_info.file_info.absoluteFilePath())
path = os.path.normcase(file_path) path = os.path.normcase(file_path)
# create the media # create the media
display.vlc_media = display.vlc_instance.media_new_path(path) if controller.media_info.media_type == MediaType.CD:
display.vlc_media = display.vlc_instance.media_new_location('cdda://' + path)
display.vlc_media_player.set_media(display.vlc_media)
display.vlc_media_player.play()
# Wait for media to start playing. In this case VLC actually returns an error.
self.media_state_wait(display, vlc.State.Playing)
# If subitems exists, this is a CD
audio_cd_tracks = display.vlc_media.subitems()
if not audio_cd_tracks or audio_cd_tracks.count() < 1:
return False
display.vlc_media = audio_cd_tracks.item_at_index(controller.media_info.title_track)
else:
display.vlc_media = display.vlc_instance.media_new_path(path)
# put the media in the media player # put the media in the media player
display.vlc_media_player.set_media(display.vlc_media) display.vlc_media_player.set_media(display.vlc_media)
# parse the metadata of the file # parse the metadata of the file
@ -206,15 +218,40 @@ class VlcPlayer(MediaPlayer):
""" """
controller = display.controller controller = display.controller
start_time = 0 start_time = 0
log.debug('vlc play')
if self.state != MediaState.Paused and controller.media_info.start_time > 0: if self.state != MediaState.Paused and controller.media_info.start_time > 0:
start_time = controller.media_info.start_time start_time = controller.media_info.start_time
threading.Thread(target=display.vlc_media_player.play).start() threading.Thread(target=display.vlc_media_player.play).start()
if not self.media_state_wait(display, vlc.State.Playing): if not self.media_state_wait(display, vlc.State.Playing):
return False 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
log.debug('mediatype: ' + str(controller.media_info.media_type))
# Set tracks for the optical device
if controller.media_info.media_type == MediaType.DVD:
log.debug('vlc play, playing started')
if controller.media_info.title_track > 0:
log.debug('vlc play, title_track set: ' + str(controller.media_info.title_track))
display.vlc_media_player.set_title(controller.media_info.title_track)
display.vlc_media_player.play()
if not self.media_state_wait(display, vlc.State.Playing):
return False
if controller.media_info.audio_track > 0:
display.vlc_media_player.audio_set_track(controller.media_info.audio_track)
log.debug('vlc play, audio_track set: ' + str(controller.media_info.audio_track))
if controller.media_info.subtitle_track > 0:
display.vlc_media_player.video_set_spu(controller.media_info.subtitle_track)
log.debug('vlc play, subtitle_track set: ' + str(controller.media_info.subtitle_track))
if controller.media_info.start_time > 0:
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) self.volume(display, controller.media_info.volume)
if start_time > 0: if start_time > 0 and display.vlc_media_player.is_seekable():
self.seek(display, controller.media_info.start_time * 1000) display.vlc_media_player.set_time(int(start_time * 1000))
controller.media_info.length = int(display.vlc_media_player.get_media().get_duration() / 1000)
controller.seek_slider.setMaximum(controller.media_info.length * 1000) controller.seek_slider.setMaximum(controller.media_info.length * 1000)
self.state = MediaState.Playing self.state = MediaState.Playing
display.vlc_widget.raise_() display.vlc_widget.raise_()
@ -248,6 +285,9 @@ class VlcPlayer(MediaPlayer):
""" """
Go to a particular position Go to a particular position
""" """
if display.controller.media_info.media_type == MediaType.CD \
or display.controller.media_info.media_type == MediaType.DVD:
seek_value += int(display.controller.media_info.start_time * 1000)
if display.vlc_media_player.is_seekable(): if display.vlc_media_player.is_seekable():
display.vlc_media_player.set_time(seek_value) display.vlc_media_player.set_time(seek_value)
@ -280,7 +320,12 @@ class VlcPlayer(MediaPlayer):
self.set_visible(display, False) self.set_visible(display, False)
if not controller.seek_slider.isSliderDown(): if not controller.seek_slider.isSliderDown():
controller.seek_slider.blockSignals(True) controller.seek_slider.blockSignals(True)
controller.seek_slider.setSliderPosition(display.vlc_media_player.get_time()) if display.controller.media_info.media_type == MediaType.CD \
or display.controller.media_info.media_type == MediaType.DVD:
controller.seek_slider.setSliderPosition(display.vlc_media_player.get_time() -
int(display.controller.media_info.start_time * 1000))
else:
controller.seek_slider.setSliderPosition(display.vlc_media_player.get_time())
controller.seek_slider.blockSignals(False) controller.seek_slider.blockSignals(False)
def get_info(self): def get_info(self):

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# 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 #
# --------------------------------------------------------------------------- #
# 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 #
###############################################################################

View File

@ -0,0 +1,209 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# 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 #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt4 import QtCore, QtGui
from openlp.core.common import translate
from openlp.core.lib import build_icon
class Ui_MediaClipSelector(object):
def setupUi(self, media_clip_selector):
media_clip_selector.setObjectName('media_clip_selector')
media_clip_selector.resize(554, 654)
self.combobox_size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed)
media_clip_selector.setSizePolicy(
QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.MinimumExpanding))
self.main_layout = QtGui.QVBoxLayout(media_clip_selector)
self.main_layout.setContentsMargins(8, 8, 8, 8)
self.main_layout.setObjectName('main_layout')
# Source groupbox
self.source_groupbox = QtGui.QGroupBox(media_clip_selector)
self.source_groupbox.setObjectName('source_groupbox')
self.source_layout = QtGui.QHBoxLayout()
self.source_layout.setContentsMargins(8, 8, 8, 8)
self.source_layout.setObjectName('source_layout')
self.source_groupbox.setLayout(self.source_layout)
# Media path label
self.media_path_label = QtGui.QLabel(self.source_groupbox)
self.media_path_label.setObjectName('media_path_label')
self.source_layout.addWidget(self.media_path_label)
# Media path combobox
self.media_path_combobox = QtGui.QComboBox(self.source_groupbox)
# Make the combobox expand
self.media_path_combobox.setSizePolicy(self.combobox_size_policy)
self.media_path_combobox.setEditable(True)
self.media_path_combobox.setObjectName('media_path_combobox')
self.source_layout.addWidget(self.media_path_combobox)
# Load disc button
self.load_disc_button = QtGui.QPushButton(media_clip_selector)
self.load_disc_button.setEnabled(True)
self.load_disc_button.setObjectName('load_disc_button')
self.source_layout.addWidget(self.load_disc_button)
self.main_layout.addWidget(self.source_groupbox)
# Track details group box
self.track_groupbox = QtGui.QGroupBox(media_clip_selector)
self.track_groupbox.setObjectName('track_groupbox')
self.track_layout = QtGui.QFormLayout()
self.track_layout.setContentsMargins(8, 8, 8, 8)
self.track_layout.setObjectName('track_layout')
self.label_alignment = self.track_layout.labelAlignment()
self.track_groupbox.setLayout(self.track_layout)
# Title track
self.title_label = QtGui.QLabel(self.track_groupbox)
self.title_label.setObjectName('title_label')
self.titles_combo_box = QtGui.QComboBox(self.track_groupbox)
self.titles_combo_box.setSizePolicy(self.combobox_size_policy)
self.titles_combo_box.setEditText('')
self.titles_combo_box.setObjectName('titles_combo_box')
self.track_layout.addRow(self.title_label, self.titles_combo_box)
# Audio track
self.audio_track_label = QtGui.QLabel(self.track_groupbox)
self.audio_track_label.setObjectName('audio_track_label')
self.audio_tracks_combobox = QtGui.QComboBox(self.track_groupbox)
self.audio_tracks_combobox.setSizePolicy(self.combobox_size_policy)
self.audio_tracks_combobox.setObjectName('audio_tracks_combobox')
self.track_layout.addRow(self.audio_track_label, self.audio_tracks_combobox)
self.main_layout.addWidget(self.track_groupbox)
# Subtitle track
self.subtitle_track_label = QtGui.QLabel(self.track_groupbox)
self.subtitle_track_label.setObjectName('subtitle_track_label')
self.subtitle_tracks_combobox = QtGui.QComboBox(self.track_groupbox)
self.subtitle_tracks_combobox.setSizePolicy(self.combobox_size_policy)
self.subtitle_tracks_combobox.setObjectName('subtitle_tracks_combobox')
self.track_layout.addRow(self.subtitle_track_label, self.subtitle_tracks_combobox)
# Preview frame
self.preview_frame = QtGui.QFrame(media_clip_selector)
self.preview_frame.setMinimumSize(QtCore.QSize(320, 240))
self.preview_frame.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding,
QtGui.QSizePolicy.MinimumExpanding))
self.preview_frame.setStyleSheet('background-color:black;')
self.preview_frame.setFrameShape(QtGui.QFrame.NoFrame)
self.preview_frame.setObjectName('preview_frame')
self.main_layout.addWidget(self.preview_frame)
# player controls
self.controls_layout = QtGui.QHBoxLayout()
self.controls_layout.setObjectName('controls_layout')
self.play_button = QtGui.QToolButton(media_clip_selector)
self.play_button.setIcon(build_icon(':/slides/media_playback_start.png'))
self.play_button.setObjectName('play_button')
self.controls_layout.addWidget(self.play_button)
self.position_slider = QtGui.QSlider(media_clip_selector)
self.position_slider.setTracking(False)
self.position_slider.setOrientation(QtCore.Qt.Horizontal)
self.position_slider.setObjectName('position_slider')
self.controls_layout.addWidget(self.position_slider)
self.position_timeedit = QtGui.QTimeEdit(media_clip_selector)
self.position_timeedit.setReadOnly(True)
self.position_timeedit.setObjectName('position_timeedit')
self.controls_layout.addWidget(self.position_timeedit)
self.main_layout.addLayout(self.controls_layout)
# Range
self.range_groupbox = QtGui.QGroupBox(media_clip_selector)
self.range_groupbox.setObjectName('range_groupbox')
self.range_layout = QtGui.QGridLayout()
self.range_layout.setContentsMargins(8, 8, 8, 8)
self.range_layout.setObjectName('range_layout')
self.range_groupbox.setLayout(self.range_layout)
# Start position
self.start_position_label = QtGui.QLabel(self.range_groupbox)
self.start_position_label.setObjectName('start_position_label')
self.range_layout.addWidget(self.start_position_label, 0, 0, self.label_alignment)
self.start_position_edit = QtGui.QTimeEdit(self.range_groupbox)
self.start_position_edit.setObjectName('start_position_edit')
self.range_layout.addWidget(self.start_position_edit, 0, 1)
self.set_start_button = QtGui.QPushButton(self.range_groupbox)
self.set_start_button.setObjectName('set_start_button')
self.range_layout.addWidget(self.set_start_button, 0, 2)
self.jump_start_button = QtGui.QPushButton(self.range_groupbox)
self.jump_start_button.setObjectName('jump_start_button')
self.range_layout.addWidget(self.jump_start_button, 0, 3)
# End position
self.end_position_label = QtGui.QLabel(self.range_groupbox)
self.end_position_label.setObjectName('end_position_label')
self.range_layout.addWidget(self.end_position_label, 1, 0, self.label_alignment)
self.end_timeedit = QtGui.QTimeEdit(self.range_groupbox)
self.end_timeedit.setObjectName('end_timeedit')
self.range_layout.addWidget(self.end_timeedit, 1, 1)
self.set_end_button = QtGui.QPushButton(self.range_groupbox)
self.set_end_button.setObjectName('set_end_button')
self.range_layout.addWidget(self.set_end_button, 1, 2)
self.jump_end_button = QtGui.QPushButton(self.range_groupbox)
self.jump_end_button.setObjectName('jump_end_button')
self.range_layout.addWidget(self.jump_end_button, 1, 3)
self.main_layout.addWidget(self.range_groupbox)
# Save and close buttons
self.button_box = QtGui.QDialogButtonBox(media_clip_selector)
self.button_box.addButton(QtGui.QDialogButtonBox.Save)
self.button_box.addButton(QtGui.QDialogButtonBox.Close)
self.close_button = self.button_box.button(QtGui.QDialogButtonBox.Close)
self.save_button = self.button_box.button(QtGui.QDialogButtonBox.Save)
self.main_layout.addWidget(self.button_box)
self.retranslateUi(media_clip_selector)
self.button_box.accepted.connect(media_clip_selector.accept)
self.button_box.rejected.connect(media_clip_selector.reject)
QtCore.QMetaObject.connectSlotsByName(media_clip_selector)
media_clip_selector.setTabOrder(self.media_path_combobox, self.load_disc_button)
media_clip_selector.setTabOrder(self.load_disc_button, self.titles_combo_box)
media_clip_selector.setTabOrder(self.titles_combo_box, self.audio_tracks_combobox)
media_clip_selector.setTabOrder(self.audio_tracks_combobox, self.subtitle_tracks_combobox)
media_clip_selector.setTabOrder(self.subtitle_tracks_combobox, self.play_button)
media_clip_selector.setTabOrder(self.play_button, self.position_slider)
media_clip_selector.setTabOrder(self.position_slider, self.position_timeedit)
media_clip_selector.setTabOrder(self.position_timeedit, self.start_position_edit)
media_clip_selector.setTabOrder(self.start_position_edit, self.set_start_button)
media_clip_selector.setTabOrder(self.set_start_button, self.jump_start_button)
media_clip_selector.setTabOrder(self.jump_start_button, self.end_timeedit)
media_clip_selector.setTabOrder(self.end_timeedit, self.set_end_button)
media_clip_selector.setTabOrder(self.set_end_button, self.jump_end_button)
media_clip_selector.setTabOrder(self.jump_end_button, self.save_button)
media_clip_selector.setTabOrder(self.save_button, self.close_button)
def retranslateUi(self, media_clip_selector):
media_clip_selector.setWindowTitle(translate('MediaPlugin.MediaClipSelector', 'Select Media Clip'))
self.source_groupbox.setTitle(translate('MediaPlugin.MediaClipSelector', 'Source'))
self.media_path_label.setText(translate('MediaPlugin.MediaClipSelector', 'Media path:'))
self.media_path_combobox.lineEdit().setPlaceholderText(translate('MediaPlugin.MediaClipSelector',
'Select drive from list'))
self.load_disc_button.setText(translate('MediaPlugin.MediaClipSelector', 'Load disc'))
self.track_groupbox.setTitle(translate('MediaPlugin.MediaClipSelector', 'Track Details'))
self.title_label.setText(translate('MediaPlugin.MediaClipSelector', 'Title:'))
self.audio_track_label.setText(translate('MediaPlugin.MediaClipSelector', 'Audio track:'))
self.subtitle_track_label.setText(translate('MediaPlugin.MediaClipSelector', 'Subtitle track:'))
self.position_timeedit.setDisplayFormat(translate('MediaPlugin.MediaClipSelector', 'HH:mm:ss.z'))
self.range_groupbox.setTitle(translate('MediaPlugin.MediaClipSelector', 'Clip Range'))
self.start_position_label.setText(translate('MediaPlugin.MediaClipSelector', 'Start point:'))
self.start_position_edit.setDisplayFormat(translate('MediaPlugin.MediaClipSelector', 'HH:mm:ss.z'))
self.set_start_button.setText(translate('MediaPlugin.MediaClipSelector', 'Set start point'))
self.jump_start_button.setText(translate('MediaPlugin.MediaClipSelector', 'Jump to start point'))
self.end_position_label.setText(translate('MediaPlugin.MediaClipSelector', 'End point:'))
self.end_timeedit.setDisplayFormat(translate('MediaPlugin.MediaClipSelector', 'HH:mm:ss.z'))
self.set_end_button.setText(translate('MediaPlugin.MediaClipSelector', 'Set end point'))
self.jump_end_button.setText(translate('MediaPlugin.MediaClipSelector', 'Jump to end point'))

View File

@ -0,0 +1,665 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# 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 #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import os
if os.name == 'nt':
from win32com.client import Dispatch
import string
import sys
if sys.platform.startswith('linux'):
import dbus
import logging
import re
from time import sleep
from datetime import datetime
from PyQt4 import QtCore, QtGui
from openlp.core.common import translate
from openlp.plugins.media.forms.mediaclipselectordialog import Ui_MediaClipSelector
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.media import format_milliseconds
try:
from openlp.core.ui.media.vendor import vlc
except (ImportError, NameError, NotImplementedError):
pass
except OSError as e:
if sys.platform.startswith('win'):
if not isinstance(e, WindowsError) and e.winerror != 126:
raise
else:
raise
log = logging.getLogger(__name__)
class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
"""
Class to manage the clip selection
"""
log.info('%s MediaClipSelectorForm loaded', __name__)
def __init__(self, media_item, parent, manager):
"""
Constructor
"""
super(MediaClipSelectorForm, self).__init__(parent)
self.vlc_instance = None
self.vlc_media_player = None
self.vlc_media = None
self.timer = None
self.audio_cd_tracks = None
self.audio_cd = False
self.playback_length = 0
self.media_item = media_item
self.setupUi(self)
# setup play/pause icon
self.play_icon = QtGui.QIcon()
self.play_icon.addPixmap(QtGui.QPixmap(":/slides/media_playback_start.png"), QtGui.QIcon.Normal,
QtGui.QIcon.Off)
self.pause_icon = QtGui.QIcon()
self.pause_icon.addPixmap(QtGui.QPixmap(":/slides/media_playback_pause.png"), QtGui.QIcon.Normal,
QtGui.QIcon.Off)
def reject(self):
"""
Exit Dialog and do not save
"""
log.debug('MediaClipSelectorForm.reject')
# Tear down vlc
if self.vlc_media_player:
self.vlc_media_player.stop()
self.vlc_media_player.release()
self.vlc_media_player = None
if self.vlc_instance:
self.vlc_instance.release()
self.vlc_instance = None
if self.vlc_media:
self.vlc_media.release()
self.vlc_media = None
return QtGui.QDialog.reject(self)
def exec_(self):
"""
Start dialog
"""
self.reset_ui()
self.setup_vlc()
return QtGui.QDialog.exec_(self)
def reset_ui(self):
"""
Reset the UI to default values
"""
self.playback_length = 0
self.position_slider.setMinimum(0)
self.disable_all()
self.toggle_disable_load_media(False)
self.subtitle_tracks_combobox.clear()
self.audio_tracks_combobox.clear()
self.titles_combo_box.clear()
time = QtCore.QTime()
self.start_position_edit.setTime(time)
self.end_timeedit.setTime(time)
self.position_timeedit.setTime(time)
def setup_vlc(self):
"""
Setup VLC instance and mediaplayer
"""
self.vlc_instance = vlc.Instance()
# creating an empty vlc media player
self.vlc_media_player = self.vlc_instance.media_player_new()
# The media player has to be 'connected' to the QFrame.
# (otherwise a video would be displayed in it's own window)
# This is platform specific!
# You have to give the id of the QFrame (or similar object)
# to vlc, different platforms have different functions for this.
win_id = int(self.preview_frame.winId())
if sys.platform == "win32":
self.vlc_media_player.set_hwnd(win_id)
elif sys.platform == "darwin":
# We have to use 'set_nsobject' since Qt4 on OSX uses Cocoa
# framework and not the old Carbon.
self.vlc_media_player.set_nsobject(win_id)
else:
# for Linux using the X Server
self.vlc_media_player.set_xwindow(win_id)
self.vlc_media = None
# Setup timer every 100 ms to update position
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.update_position)
self.timer.start(100)
self.find_optical_devices()
self.audio_cd = False
self.audio_cd_tracks = None
def detect_audio_cd(self, path):
"""
Detects is the given path is an audio CD
:param path: Path to the device to be tested.
:return: True if it was an audio CD else False.
"""
# Detect by trying to play it as a CD
self.vlc_media = self.vlc_instance.media_new_location('cdda://' + path)
self.vlc_media_player.set_media(self.vlc_media)
self.vlc_media_player.play()
# Wait for media to start playing. In this case VLC actually returns an error.
self.media_state_wait(vlc.State.Playing)
self.vlc_media_player.set_pause(1)
# If subitems exists, this is a CD
self.audio_cd_tracks = self.vlc_media.subitems()
if not self.audio_cd_tracks or self.audio_cd_tracks.count() < 1:
return False
# Insert into titles_combo_box
self.titles_combo_box.clear()
for i in range(self.audio_cd_tracks.count()):
item = self.audio_cd_tracks.item_at_index(i)
item_title = item.get_meta(vlc.Meta.Title)
self.titles_combo_box.addItem(item_title, i)
self.vlc_media_player.set_media(self.audio_cd_tracks.item_at_index(0))
self.audio_cd = True
self.titles_combo_box.setDisabled(False)
self.titles_combo_box.setCurrentIndex(0)
self.on_title_combo_box_currentIndexChanged(0)
return True
@QtCore.pyqtSlot(bool)
def on_load_disc_button_clicked(self, clicked):
"""
Load the media when the load-button has been clicked
:param clicked: Given from signal, not used.
"""
log.debug('on_load_disc_button_clicked')
self.disable_all()
path = self.media_path_combobox.currentText()
# Check if given path is non-empty and exists before starting VLC
if not path:
log.debug('no given path')
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm', 'No path was given'))
self.toggle_disable_load_media(False)
return
if not os.path.exists(path):
log.debug('Given path does not exists')
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
'Given path does not exists'))
self.toggle_disable_load_media(False)
return
# VLC behaves a bit differently on windows and linux when loading, which creates problems when trying to
# detect if we're dealing with a DVD or CD, so we use different loading approaches depending on the OS.
if os.name == 'nt':
# If the given path is in the format "D:\" or "D:", prefix it with "/" to make VLC happy
pattern = re.compile('^\w:\\\\*$')
if pattern.match(path):
path = '/' + path
self.vlc_media = self.vlc_instance.media_new_location('dvd://' + path)
else:
self.vlc_media = self.vlc_instance.media_new_path(path)
if not self.vlc_media:
log.debug('vlc media player is none')
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
'An error happened during initialization of VLC player'))
self.toggle_disable_load_media(False)
return
# put the media in the media player
self.vlc_media_player.set_media(self.vlc_media)
self.vlc_media_player.audio_set_mute(True)
# start playback to get vlc to parse the media
if self.vlc_media_player.play() < 0:
log.debug('vlc play returned error')
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
'VLC player failed playing the media'))
self.toggle_disable_load_media(False)
return
self.vlc_media_player.audio_set_mute(True)
if not self.media_state_wait(vlc.State.Playing):
# Tests if this is an audio CD
if not self.detect_audio_cd(path):
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
'VLC player failed playing the media'))
self.toggle_disable_load_media(False)
return
self.vlc_media_player.audio_set_mute(True)
if not self.audio_cd:
# Get titles, insert in combobox
titles = self.vlc_media_player.video_get_title_description()
self.titles_combo_box.clear()
for title in titles:
self.titles_combo_box.addItem(title[1].decode(), title[0])
# Main title is usually title #1
if len(titles) > 1:
self.titles_combo_box.setCurrentIndex(1)
else:
self.titles_combo_box.setCurrentIndex(0)
# Enable audio track combobox if anything is in it
if len(titles) > 0:
self.titles_combo_box.setDisabled(False)
self.toggle_disable_load_media(False)
log.debug('load_disc_button end - vlc_media_player state: %s' % self.vlc_media_player.get_state())
@QtCore.pyqtSlot(bool)
def on_play_button_clicked(self, clicked):
"""
Toggle the playback
:param clicked: Given from signal, not used.
"""
if self.vlc_media_player.get_state() == vlc.State.Playing:
self.vlc_media_player.pause()
self.play_button.setIcon(self.play_icon)
else:
self.vlc_media_player.play()
self.media_state_wait(vlc.State.Playing)
self.play_button.setIcon(self.pause_icon)
@QtCore.pyqtSlot(bool)
def on_set_start_button_clicked(self, clicked):
"""
Copy the current player position to start_position_edit
:param clicked: Given from signal, not used.
"""
vlc_ms_pos = self.vlc_media_player.get_time()
time = QtCore.QTime()
new_pos_time = time.addMSecs(vlc_ms_pos)
self.start_position_edit.setTime(new_pos_time)
# If start time is after end time, update end time.
end_time = self.end_timeedit.time()
if end_time < new_pos_time:
self.end_timeedit.setTime(new_pos_time)
@QtCore.pyqtSlot(bool)
def on_set_end_button_clicked(self, clicked):
"""
Copy the current player position to end_timeedit
:param clicked: Given from signal, not used.
"""
vlc_ms_pos = self.vlc_media_player.get_time()
time = QtCore.QTime()
new_pos_time = time.addMSecs(vlc_ms_pos)
self.end_timeedit.setTime(new_pos_time)
# If start time is after end time, update start time.
start_time = self.start_position_edit.time()
if start_time > new_pos_time:
self.start_position_edit.setTime(new_pos_time)
@QtCore.pyqtSlot(QtCore.QTime)
def on_start_timeedit_timeChanged(self, new_time):
"""
Called when start_position_edit is changed manually
:param new_time: The new time
"""
# If start time is after end time, update end time.
end_time = self.end_timeedit.time()
if end_time < new_time:
self.end_timeedit.setTime(new_time)
@QtCore.pyqtSlot(QtCore.QTime)
def on_end_timeedit_timeChanged(self, new_time):
"""
Called when end_timeedit is changed manually
:param new_time: The new time
"""
# If start time is after end time, update start time.
start_time = self.start_position_edit.time()
if start_time > new_time:
self.start_position_edit.setTime(new_time)
@QtCore.pyqtSlot(bool)
def on_jump_end_button_clicked(self, clicked):
"""
Set the player position to the position stored in end_timeedit
:param clicked: Given from signal, not used.
"""
end_time = self.end_timeedit.time()
end_time_ms = end_time.hour() * 60 * 60 * 1000 + \
end_time.minute() * 60 * 1000 + \
end_time.second() * 1000 + \
end_time.msec()
self.vlc_media_player.set_time(end_time_ms)
@QtCore.pyqtSlot(bool)
def on_jump_start_button_clicked(self, clicked):
"""
Set the player position to the position stored in start_position_edit
:param clicked: Given from signal, not used.
"""
start_time = self.start_position_edit.time()
start_time_ms = start_time.hour() * 60 * 60 * 1000 + \
start_time.minute() * 60 * 1000 + \
start_time.second() * 1000 + \
start_time.msec()
self.vlc_media_player.set_time(start_time_ms)
@QtCore.pyqtSlot(int)
def on_titles_combo_box_currentIndexChanged(self, index):
"""
When a new title is chosen, it is loaded by VLC and info about audio and subtitle tracks is reloaded
:param index: The index of the newly chosen title track.
"""
log.debug('in on_titles_combo_box_changed, index: %d', index)
if not self.vlc_media_player:
log.error('vlc_media_player was None')
return
if self.audio_cd:
self.vlc_media = self.audio_cd_tracks.item_at_index(index)
self.vlc_media_player.set_media(self.vlc_media)
self.vlc_media_player.set_time(0)
self.vlc_media_player.play()
if not self.media_state_wait(vlc.State.Playing):
log.error('Could not start playing audio cd, needed to get track info')
return
self.vlc_media_player.audio_set_mute(True)
# Sleep 1 second to make sure VLC has the needed metadata
sleep(1)
# pause
self.vlc_media_player.set_time(0)
self.vlc_media_player.set_pause(1)
self.vlc_media_player.audio_set_mute(False)
self.toggle_disable_player(False)
else:
self.vlc_media_player.set_title(index)
self.vlc_media_player.set_time(0)
self.vlc_media_player.play()
if not self.media_state_wait(vlc.State.Playing):
log.error('Could not start playing dvd, needed to get track info')
return
self.vlc_media_player.audio_set_mute(True)
# Sleep 1 second to make sure VLC has the needed metadata
sleep(1)
self.vlc_media_player.set_time(0)
# Get audio tracks, insert in combobox
audio_tracks = self.vlc_media_player.audio_get_track_description()
self.audio_tracks_combobox.clear()
for audio_track in audio_tracks:
self.audio_tracks_combobox.addItem(audio_track[1].decode(), audio_track[0])
# Enable audio track combobox if anything is in it
if len(audio_tracks) > 0:
self.audio_tracks_combobox.setDisabled(False)
# First track is "deactivated", so set to next if it exists
if len(audio_tracks) > 1:
self.audio_tracks_combobox.setCurrentIndex(1)
# Get subtitle tracks, insert in combobox
subtitles_tracks = self.vlc_media_player.video_get_spu_description()
self.subtitle_tracks_combobox.clear()
for subtitle_track in subtitles_tracks:
self.subtitle_tracks_combobox.addItem(subtitle_track[1].decode(), subtitle_track[0])
# Enable subtitle track combobox is anything in it
if len(subtitles_tracks) > 0:
self.subtitle_tracks_combobox.setDisabled(False)
self.vlc_media_player.audio_set_mute(False)
self.vlc_media_player.set_pause(1)
# If a title or audio track is available the player is enabled
if self.titles_combo_box.count() > 0 or len(audio_tracks) > 0:
self.toggle_disable_player(False)
# Set media length info
self.playback_length = self.vlc_media_player.get_length()
log.debug('playback_length: %d ms' % self.playback_length)
self.position_slider.setMaximum(self.playback_length)
# setup start and end time
rounded_vlc_ms_length = int(round(self.playback_length / 100.0) * 100.0)
time = QtCore.QTime()
playback_length_time = time.addMSecs(rounded_vlc_ms_length)
self.start_position_edit.setMaximumTime(playback_length_time)
self.end_timeedit.setMaximumTime(playback_length_time)
self.end_timeedit.setTime(playback_length_time)
# Pause once again, just to make sure
loop_count = 0
while self.vlc_media_player.get_state() == vlc.State.Playing and loop_count < 20:
sleep(0.1)
self.vlc_media_player.set_pause(1)
loop_count += 1
log.debug('titles_combo_box end - vlc_media_player state: %s' % self.vlc_media_player.get_state())
@QtCore.pyqtSlot(int)
def on_audio_tracks_combobox_currentIndexChanged(self, index):
"""
When a new audio track is chosen update audio track bing played by VLC
:param index: The index of the newly chosen audio track.
"""
if not self.vlc_media_player:
return
audio_track = self.audio_tracks_combobox.itemData(index)
log.debug('in on_audio_tracks_combobox_currentIndexChanged, index: %d audio_track: %s' % (index, audio_track))
if audio_track and int(audio_track) > 0:
self.vlc_media_player.audio_set_track(int(audio_track))
@QtCore.pyqtSlot(int)
def on_subtitle_tracks_combobox_currentIndexChanged(self, index):
"""
When a new subtitle track is chosen update subtitle track bing played by VLC
:param index: The index of the newly chosen subtitle.
"""
if not self.vlc_media_player:
return
subtitle_track = self.subtitle_tracks_combobox.itemData(index)
if subtitle_track:
self.vlc_media_player.video_set_spu(int(subtitle_track))
def on_position_slider_sliderMoved(self, position):
"""
Set player position according to new slider position.
:param position: Position to seek to.
"""
self.vlc_media_player.set_time(position)
def update_position(self):
"""
Update slider position and displayed time according to VLC player position.
"""
if self.vlc_media_player:
vlc_ms_pos = self.vlc_media_player.get_time()
rounded_vlc_ms_pos = int(round(vlc_ms_pos / 100.0) * 100.0)
time = QtCore.QTime()
new_pos_time = time.addMSecs(rounded_vlc_ms_pos)
self.position_timeedit.setTime(new_pos_time)
self.position_slider.setSliderPosition(vlc_ms_pos)
def disable_all(self):
"""
Disable all elements in the dialog
"""
self.toggle_disable_load_media(True)
self.titles_combo_box.setDisabled(True)
self.audio_tracks_combobox.setDisabled(True)
self.subtitle_tracks_combobox.setDisabled(True)
self.toggle_disable_player(True)
def toggle_disable_load_media(self, action):
"""
Enable/disable load media combobox and button.
:param action: If True elements are disabled, if False they are enabled.
"""
self.media_path_combobox.setDisabled(action)
self.load_disc_button.setDisabled(action)
def toggle_disable_player(self, action):
"""
Enable/disable player elements.
:param action: If True elements are disabled, if False they are enabled.
"""
self.play_button.setDisabled(action)
self.position_slider.setDisabled(action)
self.position_timeedit.setDisabled(action)
self.start_position_edit.setDisabled(action)
self.set_start_button.setDisabled(action)
self.jump_start_button.setDisabled(action)
self.end_timeedit.setDisabled(action)
self.set_end_button.setDisabled(action)
self.jump_end_button.setDisabled(action)
self.save_button.setDisabled(action)
def accept(self):
"""
Saves the current media and trackinfo as a clip to the mediamanager
"""
log.debug('in on_save_button_clicked')
start_time = self.start_position_edit.time()
start_time_ms = start_time.hour() * 60 * 60 * 1000 + \
start_time.minute() * 60 * 1000 + \
start_time.second() * 1000 + \
start_time.msec()
end_time = self.end_timeedit.time()
end_time_ms = end_time.hour() * 60 * 60 * 1000 + \
end_time.minute() * 60 * 1000 + \
end_time.second() * 1000 + \
end_time.msec()
title = self.titles_combo_box.itemData(self.titles_combo_box.currentIndex())
path = self.media_path_combobox.currentText()
optical = ''
if self.audio_cd:
optical = 'optical:%d:-1:-1:%d:%d:' % (title, start_time_ms, end_time_ms)
else:
audio_track = self.audio_tracks_combobox.itemData(self.audio_tracks_combobox.currentIndex())
subtitle_track = self.subtitle_tracks_combobox.itemData(self.subtitle_tracks_combobox.currentIndex())
optical = 'optical:%d:%d:%d:%d:%d:' % (title, audio_track, subtitle_track, start_time_ms, end_time_ms)
# Ask for an alternative name for the mediaclip
while True:
new_optical_name, ok = QtGui.QInputDialog.getText(self, translate('MediaPlugin.MediaClipSelectorForm',
'Set name of mediaclip'),
translate('MediaPlugin.MediaClipSelectorForm',
'Name of mediaclip:'),
QtGui.QLineEdit.Normal)
# User pressed cancel, don't save the clip
if not ok:
return
# User pressed ok, but the input text is blank
if not new_optical_name:
critical_error_message_box(translate('MediaPlugin.MediaClipSelectorForm',
'Enter a valid name or cancel'),
translate('MediaPlugin.MediaClipSelectorForm',
'Enter a valid name or cancel'))
# The entered new name contains a colon, which we don't allow because colons is used to seperate clip info
elif new_optical_name.find(':') >= 0:
critical_error_message_box(translate('MediaPlugin.MediaClipSelectorForm', 'Invalid character'),
translate('MediaPlugin.MediaClipSelectorForm',
'The name of the mediaclip must not contain the character ":"'))
# New name entered and we use it
else:
break
# Append the new name to the optical string and the path
optical += new_optical_name + ':' + path
self.media_item.add_optical_clip(optical)
def media_state_wait(self, media_state):
"""
Wait for the video to change its state
Wait no longer than 15 seconds. (loading an optical disc takes some time)
:param media_state: VLC media state to wait for.
:return: True if state was reached within 15 seconds, False if not or error occurred.
"""
start = datetime.now()
while media_state != self.vlc_media_player.get_state():
if self.vlc_media_player.get_state() == vlc.State.Error:
return False
if (datetime.now() - start).seconds > 30:
return False
return True
def find_optical_devices(self):
"""
Attempt to autodetect optical devices on the computer, and add them to the media-dropdown
:return:
"""
# Clear list first
self.media_path_combobox.clear()
if os.name == 'nt':
# use win api to find optical drives
fso = Dispatch('scripting.filesystemobject')
for drive in fso.Drives:
log.debug('Drive %s has type %d' % (drive.DriveLetter, drive.DriveType))
# if type is 4, it is a cd-rom drive
if drive.DriveType == 4:
self.media_path_combobox.addItem('%s:\\' % drive.DriveLetter)
elif sys.platform.startswith('linux'):
# Get disc devices from dbus and find the ones that are optical
bus = dbus.SystemBus()
try:
udev_manager_obj = bus.get_object('org.freedesktop.UDisks', '/org/freedesktop/UDisks')
udev_manager = dbus.Interface(udev_manager_obj, 'org.freedesktop.UDisks')
for dev in udev_manager.EnumerateDevices():
device_obj = bus.get_object("org.freedesktop.UDisks", dev)
device_props = dbus.Interface(device_obj, dbus.PROPERTIES_IFACE)
if device_props.Get('org.freedesktop.UDisks.Device', 'DeviceIsDrive'):
drive_props = device_props.Get('org.freedesktop.UDisks.Device', 'DriveMediaCompatibility')
if any('optical' in prop for prop in drive_props):
self.media_path_combobox.addItem(device_props.Get('org.freedesktop.UDisks.Device',
'DeviceFile'))
return
except dbus.exceptions.DBusException:
log.debug('could not use udisks, will try udisks2')
udev_manager_obj = bus.get_object('org.freedesktop.UDisks2', '/org/freedesktop/UDisks2')
udev_manager = dbus.Interface(udev_manager_obj, 'org.freedesktop.DBus.ObjectManager')
for k, v in udev_manager.GetManagedObjects().items():
drive_info = v.get('org.freedesktop.UDisks2.Drive', {})
drive_props = drive_info.get('MediaCompatibility')
if drive_props and any('optical' in prop for prop in drive_props):
for device in udev_manager.GetManagedObjects().values():
if dbus.String('org.freedesktop.UDisks2.Block') in device:
if device[dbus.String('org.freedesktop.UDisks2.Block')][dbus.String('Drive')] == k:
block_file = ''
for c in device[dbus.String('org.freedesktop.UDisks2.Block')][
dbus.String('PreferredDevice')]:
if chr(c) != '\x00':
block_file += chr(c)
self.media_path_combobox.addItem(block_file)
elif sys.platform.startswith('darwin'):
# Look for DVD folders in devices to find optical devices
volumes = os.listdir('/Volumes')
candidates = list()
for volume in volumes:
if volume.startswith('.'):
continue
dirs = os.listdir('/Volumes/' + volume)
# Detect DVD
if 'VIDEO_TS' in dirs:
self.media_path_combobox.addItem('/Volumes/' + volume)
# Detect audio cd
files = [f for f in dirs if os.path.isfile(f)]
for file in files:
if file.endswith('aiff'):
self.media_path_combobox.addItem('/Volumes/' + volume)
break

View File

@ -29,6 +29,7 @@
import logging import logging
import os import os
from datetime import time
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
@ -38,17 +39,21 @@ from openlp.core.lib import ItemCapabilities, MediaManagerItem, MediaType, Servi
build_icon, check_item_selected build_icon, check_item_selected
from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box 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.ui import DisplayController, Display, DisplayControllerType
from openlp.core.ui.media import get_media_players, set_media_players from openlp.core.ui.media import get_media_players, set_media_players, parse_optical_path, format_milliseconds
from openlp.core.utils import get_locale_key from openlp.core.utils import get_locale_key
from openlp.core.ui.media.vlcplayer import VLC_AVAILABLE
if VLC_AVAILABLE:
from openlp.plugins.media.forms.mediaclipselectorform import MediaClipSelectorForm
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
CLAPPERBOARD = ':/media/slidecontroller_multimedia.png' CLAPPERBOARD = ':/media/slidecontroller_multimedia.png'
OPTICAL = ':/media/media_optical.png'
VIDEO_ICON = build_icon(':/media/media_video.png') VIDEO_ICON = build_icon(':/media/media_video.png')
AUDIO_ICON = build_icon(':/media/media_audio.png') AUDIO_ICON = build_icon(':/media/media_audio.png')
DVD_ICON = build_icon(':/media/media_video.png') OPTICAL_ICON = build_icon(OPTICAL)
ERROR_ICON = build_icon(':/general/general_delete.png') ERROR_ICON = build_icon(':/general/general_delete.png')
@ -88,6 +93,10 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
self.list_view.activateDnD() self.list_view.activateDnD()
def retranslateUi(self): def retranslateUi(self):
"""
This method is called automatically to provide OpenLP with the opportunity to translate the ``MediaManagerItem``
to another language.
"""
self.on_new_prompt = translate('MediaPlugin.MediaItem', 'Select Media') self.on_new_prompt = translate('MediaPlugin.MediaItem', 'Select Media')
self.replace_action.setText(UiStrings().ReplaceBG) self.replace_action.setText(UiStrings().ReplaceBG)
self.replace_action.setToolTip(UiStrings().ReplaceLiveBG) self.replace_action.setToolTip(UiStrings().ReplaceLiveBG)
@ -106,10 +115,35 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
self.has_edit_icon = False self.has_edit_icon = False
def add_list_view_to_toolbar(self): def add_list_view_to_toolbar(self):
"""
Creates the main widget for listing items.
"""
MediaManagerItem.add_list_view_to_toolbar(self) MediaManagerItem.add_list_view_to_toolbar(self)
self.list_view.addAction(self.replace_action) self.list_view.addAction(self.replace_action)
def add_start_header_bar(self):
"""
Adds buttons to the start of the header bar.
"""
if 'vlc' in get_media_players()[0]:
diable_optical_button_text = False
optical_button_text = translate('MediaPlugin.MediaItem', 'Load CD/DVD')
optical_button_tooltip = translate('MediaPlugin.MediaItem', 'Load CD/DVD')
else:
diable_optical_button_text = True
optical_button_text = translate('MediaPlugin.MediaItem', 'Load CD/DVD')
optical_button_tooltip = translate('MediaPlugin.MediaItem',
'Load CD/DVD - only supported when VLC is installed and enabled')
self.load_optical = self.toolbar.add_toolbar_action('load_optical', icon=OPTICAL_ICON, text=optical_button_text,
tooltip=optical_button_tooltip,
triggers=self.on_load_optical)
if diable_optical_button_text:
self.load_optical.setDisabled(True)
def add_end_header_bar(self): def add_end_header_bar(self):
"""
Adds buttons to the end of the header bar.
"""
# Replace backgrounds do not work at present so remove functionality. # 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_blank.png',
triggers=self.on_replace_click) triggers=self.on_replace_click)
@ -198,22 +232,42 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
if item is None: if item is None:
return False return False
filename = item.data(QtCore.Qt.UserRole) filename = item.data(QtCore.Qt.UserRole)
if not os.path.exists(filename): # Special handling if the filename is a optical clip
if not remote: if filename.startswith('optical:'):
# File is no longer present (name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(filename)
critical_error_message_box( if not os.path.exists(name):
translate('MediaPlugin.MediaItem', 'Missing Media File'), if not remote:
translate('MediaPlugin.MediaItem', 'The file %s no longer exists.') % filename) # Optical disc is no longer present
return False critical_error_message_box(
(path, name) = os.path.split(filename) translate('MediaPlugin.MediaItem', 'Missing Media File'),
service_item.title = name translate('MediaPlugin.MediaItem', 'The optical disc %s is no longer available.') % name)
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 return False
service_item.processor = self.display_type_combo_box.currentText()
service_item.add_from_command(filename, name, CLAPPERBOARD)
service_item.title = clip_name
# Set the length
self.media_controller.media_setup_optical(name, title, audio_track, subtitle_track, start, end, None, None)
service_item.set_media_length((end - start) / 1000)
service_item.start_time = start / 1000
service_item.end_time = end / 1000
service_item.add_capability(ItemCapabilities.IsOptical)
else:
if not os.path.exists(filename):
if not remote:
# File is no longer present
critical_error_message_box(
translate('MediaPlugin.MediaItem', 'Missing Media File'),
translate('MediaPlugin.MediaItem', 'The file %s no longer exists.') % filename)
return False
(path, name) = os.path.split(filename)
service_item.title = name
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
service_item.add_capability(ItemCapabilities.CanAutoStartForLive) service_item.add_capability(ItemCapabilities.CanAutoStartForLive)
service_item.add_capability(ItemCapabilities.CanEditTitle) service_item.add_capability(ItemCapabilities.CanEditTitle)
service_item.add_capability(ItemCapabilities.RequiresMedia) service_item.add_capability(ItemCapabilities.RequiresMedia)
@ -224,6 +278,9 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
return True return True
def initialise(self): def initialise(self):
"""
Initialize media item.
"""
self.list_view.clear() self.list_view.clear()
self.list_view.setIconSize(QtCore.QSize(88, 50)) self.list_view.setIconSize(QtCore.QSize(88, 50))
self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails') self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
@ -241,6 +298,9 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
' '.join(self.media_controller.audio_extensions_list), UiStrings().AllFiles) ' '.join(self.media_controller.audio_extensions_list), UiStrings().AllFiles)
def display_setup(self): def display_setup(self):
"""
Setup media controller display.
"""
self.media_controller.setup_display(self.display_controller.preview_display, False) self.media_controller.setup_display(self.display_controller.preview_display, False)
def populate_display_types(self): def populate_display_types(self):
@ -280,7 +340,6 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
Settings().setValue(self.settings_section + '/media files', self.get_file_list()) Settings().setValue(self.settings_section + '/media files', self.get_file_list())
def load_list(self, media, target_group=None): def load_list(self, media, target_group=None):
# Sort the media by its filename considering language specific characters.
""" """
Load the media list Load the media list
@ -290,12 +349,22 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
media.sort(key=lambda file_name: get_locale_key(os.path.split(str(file_name))[1])) media.sort(key=lambda file_name: get_locale_key(os.path.split(str(file_name))[1]))
for track in media: for track in media:
track_info = QtCore.QFileInfo(track) track_info = QtCore.QFileInfo(track)
if not os.path.exists(track): if track.startswith('optical:'):
# Handle optical based item
(file_name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(track)
item_name = QtGui.QListWidgetItem(clip_name)
item_name.setIcon(OPTICAL_ICON)
item_name.setData(QtCore.Qt.UserRole, track)
item_name.setToolTip('%s@%s-%s' % (file_name, format_milliseconds(start), format_milliseconds(end)))
elif not os.path.exists(track):
# File doesn't exist, mark as error.
file_name = os.path.split(str(track))[1] file_name = os.path.split(str(track))[1]
item_name = QtGui.QListWidgetItem(file_name) item_name = QtGui.QListWidgetItem(file_name)
item_name.setIcon(ERROR_ICON) item_name.setIcon(ERROR_ICON)
item_name.setData(QtCore.Qt.UserRole, track) item_name.setData(QtCore.Qt.UserRole, track)
item_name.setToolTip(track)
elif track_info.isFile(): elif track_info.isFile():
# Normal media file handling.
file_name = os.path.split(str(track))[1] file_name = os.path.split(str(track))[1]
item_name = QtGui.QListWidgetItem(file_name) item_name = QtGui.QListWidgetItem(file_name)
if '*.%s' % (file_name.split('.')[-1].lower()) in self.media_controller.audio_extensions_list: if '*.%s' % (file_name.split('.')[-1].lower()) in self.media_controller.audio_extensions_list:
@ -303,15 +372,16 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
else: else:
item_name.setIcon(VIDEO_ICON) item_name.setIcon(VIDEO_ICON)
item_name.setData(QtCore.Qt.UserRole, track) item_name.setData(QtCore.Qt.UserRole, track)
else: item_name.setToolTip(track)
file_name = os.path.split(str(track))[1]
item_name = QtGui.QListWidgetItem(file_name)
item_name.setIcon(build_icon(DVD_ICON))
item_name.setData(QtCore.Qt.UserRole, track)
item_name.setToolTip(track)
self.list_view.addItem(item_name) self.list_view.addItem(item_name)
def get_list(self, type=MediaType.Audio): def get_list(self, type=MediaType.Audio):
"""
Get the list of media, optional select media type.
:param type: Type to get, defaults to audio.
:return: The media list
"""
media = Settings().value(self.settings_section + '/media files') media = Settings().value(self.settings_section + '/media files')
media.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1])) media.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1]))
if type == MediaType.Audio: if type == MediaType.Audio:
@ -323,6 +393,13 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
return media return media
def search(self, string, show_error): def search(self, string, show_error):
"""
Performs a search for items containing ``string``
:param string: String to be displayed
:param show_error: Should the error be shown (True)
:return: The search result.
"""
files = Settings().value(self.settings_section + '/media files') files = Settings().value(self.settings_section + '/media files')
results = [] results = []
string = string.lower() string = string.lower()
@ -331,3 +408,32 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
if filename.lower().find(string) > -1: if filename.lower().find(string) > -1:
results.append([file, filename]) results.append([file, filename])
return results return results
def on_load_optical(self):
"""
When the load optical button is clicked, open the clip selector window.
"""
# self.media_clip_selector_form.exec_()
if VLC_AVAILABLE:
media_clip_selector_form = MediaClipSelectorForm(self, self.main_window, None)
media_clip_selector_form.exec_()
del media_clip_selector_form
else:
QtGui.QMessageBox.critical(self, 'VLC is not available', 'VLC is not available')
def add_optical_clip(self, optical):
"""
Add a optical based clip to the mediamanager, called from media_clip_selector_form.
:param optical: The clip to add.
"""
full_list = self.get_file_list()
# If the clip already is in the media list it isn't added and an error message is displayed.
if optical in full_list:
critical_error_message_box(translate('MediaPlugin.MediaItem', 'Mediaclip already saved'),
translate('MediaPlugin.MediaItem', 'This mediaclip has already been saved'))
return
# Append the optical string to the media list
full_list.append(optical)
self.load_list([optical])
Settings().setValue(self.settings_section + '/media files', self.get_file_list())

View File

@ -124,7 +124,7 @@ class SongExportForm(OpenLPWizard):
self.export_song_layout = QtGui.QHBoxLayout(self.export_song_page) self.export_song_layout = QtGui.QHBoxLayout(self.export_song_page)
self.export_song_layout.setObjectName('export_song_layout') self.export_song_layout.setObjectName('export_song_layout')
self.grid_layout = QtGui.QGridLayout() self.grid_layout = QtGui.QGridLayout()
self.grid_layout.setObjectName('grid_layout') self.grid_layout.setObjectName('range_layout')
self.selected_list_widget = QtGui.QListWidget(self.export_song_page) self.selected_list_widget = QtGui.QListWidget(self.export_song_page)
self.selected_list_widget.setObjectName('selected_list_widget') self.selected_list_widget.setObjectName('selected_list_widget')
self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 1) self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 1)

View File

@ -0,0 +1,336 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MediaClipSelector</class>
<widget class="QMainWindow" name="MediaClipSelector">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>683</width>
<height>739</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>683</width>
<height>686</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="windowTitle">
<string>Select media clip</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="inputMethodHints">
<set>Qt::ImhNone</set>
</property>
<widget class="QWidget" name="centralwidget">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2" colspan="2">
<widget class="QComboBox" name="media_path_combobox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="2">
<widget class="QTimeEdit" name="start_timeedit">
<property name="enabled">
<bool>true</bool>
</property>
<property name="displayFormat">
<string>HH:mm:ss.z</string>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QTimeEdit" name="end_timeedit">
<property name="enabled">
<bool>true</bool>
</property>
<property name="displayFormat">
<string>HH:mm:ss.z</string>
</property>
</widget>
</item>
<item row="7" column="3">
<widget class="QPushButton" name="set_start_pushbutton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Set current position as start point</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QPushButton" name="load_disc_pushbutton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Load disc</string>
</property>
</widget>
</item>
<item row="9" column="3">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="0">
<widget class="QPushButton" name="play_pushbutton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>../images/media_playback_start.png</normaloff>../images/media_playback_start.png</iconset>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="end_point_label">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>End point</string>
</property>
</widget>
</item>
<item row="4" column="2" colspan="2">
<widget class="QComboBox" name="subtitle_tracks_combobox">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="title_label">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Title</string>
</property>
</widget>
</item>
<item row="3" column="2" colspan="2">
<widget class="QComboBox" name="audio_tracks_combobox">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="3">
<widget class="QPushButton" name="set_end_pushbutton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Set current position as end point</string>
</property>
</widget>
</item>
<item row="10" column="3">
<widget class="QPushButton" name="save_pushbutton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Save current clip</string>
</property>
</widget>
</item>
<item row="10" column="4">
<widget class="QPushButton" name="close_pushbutton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QLabel" name="start_point_label">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Start point</string>
</property>
</widget>
</item>
<item row="7" column="4">
<widget class="QPushButton" name="jump_start_pushbutton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Jump to start point</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="audio_track_label">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Audio track</string>
</property>
</widget>
</item>
<item row="6" column="4">
<widget class="QTimeEdit" name="media_position_timeedit">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="displayFormat">
<string>HH:mm:ss.z</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="5">
<widget class="QFrame" name="media_view_frame">
<property name="minimumSize">
<size>
<width>665</width>
<height>375</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">background-color:black;</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QLabel" name="subtitle_track_label">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Subtitle track</string>
</property>
</widget>
</item>
<item row="8" column="4">
<widget class="QPushButton" name="jump_end_pushbutton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Jump to end point</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="media_path_label">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Media path</string>
</property>
</widget>
</item>
<item row="2" column="2" colspan="2">
<widget class="QComboBox" name="title_combo_box">
<property name="enabled">
<bool>true</bool>
</property>
<property name="currentText" stdset="0">
<string/>
</property>
</widget>
</item>
<item row="6" column="1" colspan="3">
<widget class="QSlider" name="position_horizontalslider">
<property name="enabled">
<bool>true</bool>
</property>
<property name="tracking">
<bool>false</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="invertedAppearance">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<tabstops>
<tabstop>media_path_combobox</tabstop>
<tabstop>load_disc_pushbutton</tabstop>
<tabstop>title_combo_box</tabstop>
<tabstop>audio_tracks_combobox</tabstop>
<tabstop>subtitle_tracks_combobox</tabstop>
<tabstop>play_pushbutton</tabstop>
<tabstop>position_horizontalslider</tabstop>
<tabstop>media_position_timeedit</tabstop>
<tabstop>start_timeedit</tabstop>
<tabstop>set_start_pushbutton</tabstop>
<tabstop>jump_start_pushbutton</tabstop>
<tabstop>end_timeedit</tabstop>
<tabstop>set_end_pushbutton</tabstop>
<tabstop>jump_end_pushbutton</tabstop>
<tabstop>save_pushbutton</tabstop>
<tabstop>close_pushbutton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -140,6 +140,7 @@
<file>media_stop.png</file> <file>media_stop.png</file>
<file>media_audio.png</file> <file>media_audio.png</file>
<file>media_video.png</file> <file>media_video.png</file>
<file>media_optical.png</file>
<file>slidecontroller_multimedia.png</file> <file>slidecontroller_multimedia.png</file>
<file>auto-start_active.png</file> <file>auto-start_active.png</file>
<file>auto-start_inactive.png</file> <file>auto-start_inactive.png</file>

View File

@ -206,3 +206,24 @@ class TestServiceItem(TestCase):
'This service item should be able to be run in a can be made to Loop') 'This service item should be able to be run in a can be made to Loop')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend), self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend),
'This service item should be able to have new items added to it') 'This service item should be able to have new items added to it')
def service_item_load_optical_media_from_service_test(self):
"""
Test the Service Item - load an optical media item
"""
# GIVEN: A new service item and a mocked add icon function
service_item = ServiceItem(None)
service_item.add_icon = MagicMock()
# WHEN: We load a serviceitem with optical media
line = convert_file_service_item(TEST_PATH, 'serviceitem-dvd.osj')
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
mocked_exists.return_value = True
service_item.set_from_service(line)
# THEN: We should get back a valid service item with optical media info
self.assertTrue(service_item.is_valid, 'The service item should be valid')
self.assertTrue(service_item.is_capable(ItemCapabilities.IsOptical), 'The item should be Optical')
self.assertEqual(service_item.start_time, 654.375, 'Start time should be 654.375')
self.assertEqual(service_item.end_time, 672.069, 'End time should be 672.069')
self.assertEqual(service_item.media_length, 17.694, 'Media length should be 17.694')

View File

@ -32,7 +32,7 @@ Package to test the openlp.core.ui package.
from PyQt4 import QtCore from PyQt4 import QtCore
from unittest import TestCase from unittest import TestCase
from openlp.core.ui.media import get_media_players from openlp.core.ui.media import get_media_players, parse_optical_path
from tests.functional import MagicMock, patch from tests.functional import MagicMock, patch
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
@ -126,3 +126,59 @@ class TestMedia(TestCase, TestMixin):
# THEN: the used_players should be an empty list, and the overridden player should be an empty string # THEN: the used_players should be an empty list, and the overridden player should be an empty string
self.assertEqual(['vlc', 'webkit', 'phonon'], used_players, 'Used players should be correct') self.assertEqual(['vlc', 'webkit', 'phonon'], used_players, 'Used players should be correct')
self.assertEqual('vlc,webkit,phonon', overridden_player, 'Overridden player should be a string of players') self.assertEqual('vlc,webkit,phonon', overridden_player, 'Overridden player should be a string of players')
def test_parse_optical_path_linux(self):
"""
Test that test_parse_optical_path() parses a optical path with linux device path correctly
"""
# GIVEN: An optical formatted path
org_title_track = 1
org_audio_track = 2
org_subtitle_track = -1
org_start = 1234
org_end = 4321
org_name = 'test name'
org_device_path = '/dev/dvd'
path = 'optical:%d:%d:%d:%d:%d:%s:%s' % (org_title_track, org_audio_track, org_subtitle_track,
org_start, org_end, org_name, org_device_path)
# WHEN: parsing the path
(device_path, title_track, audio_track, subtitle_track, start, end, name) = parse_optical_path(path)
# THEN: The return values should match the original values
self.assertEqual(org_title_track, title_track, 'Returned title_track should match the original')
self.assertEqual(org_audio_track, audio_track, 'Returned audio_track should match the original')
self.assertEqual(org_subtitle_track, subtitle_track, 'Returned subtitle_track should match the original')
self.assertEqual(org_start, start, 'Returned start should match the original')
self.assertEqual(org_end, end, 'Returned end should match the original')
self.assertEqual(org_name, name, 'Returned end should match the original')
self.assertEqual(org_device_path, device_path, 'Returned device_path should match the original')
def test_parse_optical_path_win(self):
"""
Test that test_parse_optical_path() parses a optical path with windows device path correctly
"""
# GIVEN: An optical formatted path
org_title_track = 1
org_audio_track = 2
org_subtitle_track = -1
org_start = 1234
org_end = 4321
org_name = 'test name'
org_device_path = 'D:'
path = 'optical:%d:%d:%d:%d:%d:%s:%s' % (org_title_track, org_audio_track, org_subtitle_track,
org_start, org_end, org_name, org_device_path)
# WHEN: parsing the path
(device_path, title_track, audio_track, subtitle_track, start, end, name) = parse_optical_path(path)
# THEN: The return values should match the original values
self.assertEqual(org_title_track, title_track, 'Returned title_track should match the original')
self.assertEqual(org_audio_track, audio_track, 'Returned audio_track should match the original')
self.assertEqual(org_subtitle_track, subtitle_track, 'Returned subtitle_track should match the original')
self.assertEqual(org_start, start, 'Returned start should match the original')
self.assertEqual(org_end, end, 'Returned end should match the original')
self.assertEqual(org_name, name, 'Returned end should match the original')
self.assertEqual(org_device_path, device_path, 'Returned device_path should match the original')

View File

@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# 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 #
# --------------------------------------------------------------------------- #
# 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 #
###############################################################################
"""
Module to test the MediaClipSelectorForm.
"""
import os
from unittest import TestCase, SkipTest
from openlp.core.ui.media.vlcplayer import VLC_AVAILABLE
if os.name == 'nt' and not VLC_AVAILABLE:
raise SkipTest('Windows without VLC, skipping this test since it cannot run without vlc')
from PyQt4 import QtGui, QtTest, QtCore
from openlp.core.common import Registry
from openlp.plugins.media.forms.mediaclipselectorform import MediaClipSelectorForm
from tests.interfaces import MagicMock, patch
from tests.helpers.testmixin import TestMixin
class TestMediaClipSelectorForm(TestCase, TestMixin):
"""
Test the EditCustomSlideForm.
"""
def setUp(self):
"""
Create the UI
"""
Registry.create()
self.get_application()
self.main_window = QtGui.QMainWindow()
Registry().register('main_window', self.main_window)
# Mock VLC so we don't actually use it
self.vlc_patcher = patch('openlp.plugins.media.forms.mediaclipselectorform.vlc')
self.vlc_patcher.start()
# Mock the media item
self.mock_media_item = MagicMock()
# create form to test
self.form = MediaClipSelectorForm(self.mock_media_item, self.main_window, None)
mock_media_state_wait = MagicMock()
mock_media_state_wait.return_value = True
self.form.media_state_wait = mock_media_state_wait
def tearDown(self):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
self.vlc_patcher.stop()
del self.form
del self.main_window
def basic_test(self):
"""
Test if the dialog is correctly set up.
"""
# GIVEN: A mocked QDialog.exec_() method
with patch('PyQt4.QtGui.QDialog.exec_') as mocked_exec:
# WHEN: Show the dialog.
self.form.exec_()
# THEN: The media path should be empty.
assert self.form.media_path_combobox.currentText() == '', 'There should not be any text in the media path.'
def click_load_button_test(self):
"""
Test that the correct function is called when load is clicked, and that it behaves as expected.
"""
# GIVEN: Mocked methods.
with patch('openlp.plugins.media.forms.mediaclipselectorform.critical_error_message_box') as \
mocked_critical_error_message_box,\
patch('openlp.plugins.media.forms.mediaclipselectorform.os.path.exists') as mocked_os_path_exists,\
patch('PyQt4.QtGui.QDialog.exec_') as mocked_exec:
self.form.exec_()
# WHEN: The load button is clicked with no path set
QtTest.QTest.mouseClick(self.form.load_disc_button, QtCore.Qt.LeftButton)
# THEN: we should get an error
mocked_critical_error_message_box.assert_called_with(message='No path was given')
# WHEN: The load button is clicked with a non-existing path
mocked_os_path_exists.return_value = False
self.form.media_path_combobox.insertItem(0, '/non-existing/test-path.test')
self.form.media_path_combobox.setCurrentIndex(0)
QtTest.QTest.mouseClick(self.form.load_disc_button, QtCore.Qt.LeftButton)
# THEN: we should get an error
assert self.form.media_path_combobox.currentText() == '/non-existing/test-path.test',\
'The media path should be the given one.'
mocked_critical_error_message_box.assert_called_with(message='Given path does not exists')
# WHEN: The load button is clicked with a mocked existing path
mocked_os_path_exists.return_value = True
self.form.vlc_media_player = MagicMock()
self.form.vlc_media_player.play.return_value = -1
self.form.media_path_combobox.insertItem(0, '/existing/test-path.test')
self.form.media_path_combobox.setCurrentIndex(0)
QtTest.QTest.mouseClick(self.form.load_disc_button, QtCore.Qt.LeftButton)
# THEN: we should get an error
assert self.form.media_path_combobox.currentText() == '/existing/test-path.test',\
'The media path should be the given one.'
mocked_critical_error_message_box.assert_called_with(message='VLC player failed playing the media')
def title_combobox_test(self):
"""
Test the behavior when the title combobox is updated
"""
# GIVEN: Mocked methods and some entries in the title combobox.
with patch('PyQt4.QtGui.QDialog.exec_') as mocked_exec:
self.form.exec_()
self.form.vlc_media_player.get_length.return_value = 1000
self.form.audio_tracks_combobox.itemData = MagicMock()
self.form.subtitle_tracks_combobox.itemData = MagicMock()
self.form.audio_tracks_combobox.itemData.return_value = None
self.form.subtitle_tracks_combobox.itemData.return_value = None
self.form.titles_combo_box.insertItem(0, 'Test Title 0')
self.form.titles_combo_box.insertItem(1, 'Test Title 1')
# WHEN: There exists audio and subtitle tracks and the index is updated.
self.form.vlc_media_player.audio_get_track_description.return_value = [(-1, b'Disabled'),
(0, b'Audio Track 1')]
self.form.vlc_media_player.video_get_spu_description.return_value = [(-1, b'Disabled'),
(0, b'Subtitle Track 1')]
self.form.titles_combo_box.setCurrentIndex(1)
# THEN: The subtitle and audio track comboboxes should be updated and get signals and call itemData.
self.form.audio_tracks_combobox.itemData.assert_any_call(0)
self.form.audio_tracks_combobox.itemData.assert_any_call(1)
self.form.subtitle_tracks_combobox.itemData.assert_any_call(0)

View File

@ -0,0 +1 @@
[{"serviceitem": {"header": {"auto_play_slides_once": false, "data": "", "processor": "Automatic", "theme": -1, "theme_overwritten": false, "end_time": 672.069, "start_time": 654.375, "capabilities": [12, 18, 16, 4], "media_length": 17.694, "audit": "", "xml_version": null, "title": "First DVD Clip", "auto_play_slides_loop": false, "notes": "", "icon": ":/plugins/plugin_media.png", "type": 3, "background_audio": [], "plugin": "media", "from_plugin": false, "search": "", "will_auto_start": false, "name": "media", "footer": [], "timed_slide_interval": 0}, "data": [{"image": ":/media/slidecontroller_multimedia.png", "path": "optical:1:5:3:654375:672069:First DVD Clip:/dev/sr0", "title": "/dev/sr0"}]}}]