← Back to team overview

openlp-core team mailing list archive

Re: [Merge] lp:~tomasgroth/openlp/dvd into lp:openlp

 

Review: Needs Fixing

Much better and allowed me to play and rip Top Gun.
media clips allow the slide to skip through the track but in this case the ripped tracks could not be skipped through.
This is a bug.

Media items missed an ICON.

Diff comments:

> === modified file 'openlp/core/lib/serviceitem.py'
> --- openlp/core/lib/serviceitem.py	2014-04-14 18:28:04 +0000
> +++ openlp/core/lib/serviceitem.py	2014-07-01 17:51:13 +0000
> @@ -111,6 +111,9 @@
>      ``CanEditTitle``
>              The capability to edit the title of the item
>  
> +    ``IsOptical``
> +            .Determines is the service_item is based on an optical device
> +
>      """
>      CanPreview = 1
>      CanEdit = 2
> @@ -129,6 +132,7 @@
>      HasBackgroundAudio = 15
>      CanAutoStartForLive = 16
>      CanEditTitle = 17
> +    IsOptical = 18
>  
>  
>  class ServiceItem(RegistryProperties):
> @@ -416,7 +420,10 @@
>              for text_image in service_item['serviceitem']['data']:
>                  if not self.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.add_from_command(path, text_image['title'], text_image['image'])
>                  else:
> @@ -427,7 +434,8 @@
>          """
>          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
>          else:
>              if len(self._raw_frames) > 1:
> @@ -495,7 +503,8 @@
>          """
>          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):
>          """
> @@ -553,7 +562,7 @@
>                  frame = self._raw_frames[row]
>              except IndexError:
>                  return ''
> -        if self.is_image():
> +        if self.is_image() or self.is_capable(ItemCapabilities.IsOptical):
>              path_from = frame['path']
>          else:
>              path_from = os.path.join(frame['path'], frame['title'])
> @@ -623,12 +632,17 @@
>                  self.is_valid = False
>                  break
>              elif self.is_command():
> -                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
> +                if self.is_capable(ItemCapabilities.IsOptical):
> +                    if not os.path.exists(frame['title']):
> +                        self.is_valid = False
> +                        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
> 
> === modified file 'openlp/core/ui/media/__init__.py'
> --- openlp/core/ui/media/__init__.py	2014-04-20 22:23:26 +0000
> +++ openlp/core/ui/media/__init__.py	2014-07-01 17:51:13 +0000
> @@ -72,6 +72,9 @@
>      length = 0
>      start_time = 0
>      end_time = 0
> +    title_track = 0
> +    audio_track = 0
> +    subtitle_track = 0
>      media_type = MediaType()
>  
>  
> @@ -107,6 +110,40 @@
>          players = players.replace(overridden_player, '[%s]' % overridden_player)
>      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 .playertab import PlayerTab
>  
> 
> === modified file 'openlp/core/ui/media/mediacontroller.py'
> --- openlp/core/ui/media/mediacontroller.py	2014-04-14 19:44:35 +0000
> +++ openlp/core/ui/media/mediacontroller.py	2014-07-01 17:51:13 +0000
> @@ -36,9 +36,10 @@
>  from PyQt4 import QtCore, QtGui
>  
>  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.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.common import AppLocation
>  from openlp.core.ui import DisplayControllerType
> @@ -368,7 +369,16 @@
>          controller.media_info.file_info = QtCore.QFileInfo(service_item.get_frame_path())
>          display = self._define_display(controller)
>          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['video'] = True
>              if controller.media_info.is_background:
> @@ -379,12 +389,21 @@
>                  controller.media_info.start_time = service_item.start_time
>                  controller.media_info.end_time = service_item.end_time
>          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:
>              # Media could not be loaded correctly
>              critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File'),
>                                         translate('MediaPlugin.MediaItem', 'Unsupported File'))
>              return False
> +        log.debug('video mediatype: ' + str(controller.media_info.media_type))
>          # dont care about actual theme, set a black background
>          if controller.is_live and not controller.media_info.is_background:
>              display.frame.evaluateJavaScript('show_video( "setBackBoard", null, null, null,"visible");')
> @@ -436,6 +455,62 @@
>          log.debug('use %s controller' % self.current_media_players[controller.controller_type])
>          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):
>          """
>          Select the correct media Player type from the prioritized Player list
> 
> === modified file 'openlp/core/ui/media/vlcplayer.py'
> --- openlp/core/ui/media/vlcplayer.py	2014-05-07 22:52:06 +0000
> +++ openlp/core/ui/media/vlcplayer.py	2014-07-01 17:51:13 +0000
> @@ -40,7 +40,7 @@
>  
>  from openlp.core.common import Settings
>  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
>  
>  log = logging.getLogger(__name__)
> @@ -166,7 +166,19 @@
>          file_path = str(controller.media_info.file_info.absoluteFilePath())
>          path = os.path.normcase(file_path)
>          # 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
>          display.vlc_media_player.set_media(display.vlc_media)
>          # parse the metadata of the file
> @@ -206,14 +218,37 @@
>          """
>          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
>          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
> +        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
>          self.volume(display, controller.media_info.volume)
>          if start_time > 0:
> -            self.seek(display, controller.media_info.start_time * 1000)
> +            self.seek(display, 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)
>          self.state = MediaState.Playing
> 
> === added directory 'openlp/plugins/media/forms'
> === added file 'openlp/plugins/media/forms/__init__.py'
> --- openlp/plugins/media/forms/__init__.py	1970-01-01 00:00:00 +0000
> +++ openlp/plugins/media/forms/__init__.py	2014-07-01 17:51:13 +0000
> @@ -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                          #
> +###############################################################################
> 
> === added file 'openlp/plugins/media/forms/mediaclipselectordialog.py'
> --- openlp/plugins/media/forms/mediaclipselectordialog.py	1970-01-01 00:00:00 +0000
> +++ openlp/plugins/media/forms/mediaclipselectordialog.py	2014-07-01 17:51:13 +0000
> @@ -0,0 +1,207 @@
> +# -*- 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
> +
> +
> +class Ui_MediaClipSelector(object):
> +    def setupUi(self, MediaClipSelector):
> +        MediaClipSelector.setObjectName("MediaClipSelector")
> +        MediaClipSelector.resize(683, 739)
> +        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.MinimumExpanding)
> +        sizePolicy.setHorizontalStretch(0)
> +        sizePolicy.setVerticalStretch(0)
> +        sizePolicy.setHeightForWidth(MediaClipSelector.sizePolicy().hasHeightForWidth())
> +        MediaClipSelector.setSizePolicy(sizePolicy)
> +        MediaClipSelector.setMinimumSize(QtCore.QSize(683, 686))
> +        MediaClipSelector.setFocusPolicy(QtCore.Qt.NoFocus)
> +        MediaClipSelector.setAutoFillBackground(False)
> +        MediaClipSelector.setInputMethodHints(QtCore.Qt.ImhNone)
> +        self.centralwidget = QtGui.QWidget(MediaClipSelector)
> +        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.MinimumExpanding)
> +        sizePolicy.setHorizontalStretch(0)
> +        sizePolicy.setVerticalStretch(0)
> +        sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
> +        self.centralwidget.setSizePolicy(sizePolicy)
> +        self.centralwidget.setObjectName("centralwidget")
> +        self.gridLayout = QtGui.QGridLayout(self.centralwidget)
> +        self.gridLayout.setObjectName("gridLayout")
> +        self.media_path_combobox = QtGui.QComboBox(self.centralwidget)
> +        self.media_path_combobox.setEnabled(True)
> +        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
> +        sizePolicy.setHorizontalStretch(0)
> +        sizePolicy.setVerticalStretch(0)
> +        sizePolicy.setHeightForWidth(self.media_path_combobox.sizePolicy().hasHeightForWidth())
> +        self.media_path_combobox.setSizePolicy(sizePolicy)
> +        self.media_path_combobox.setEditable(True)
> +        self.media_path_combobox.setObjectName("media_path_combobox")
> +        self.gridLayout.addWidget(self.media_path_combobox, 0, 2, 1, 2)
> +        self.start_timeedit = QtGui.QTimeEdit(self.centralwidget)
> +        self.start_timeedit.setEnabled(True)
> +        self.start_timeedit.setObjectName("start_timeedit")
> +        self.gridLayout.addWidget(self.start_timeedit, 7, 2, 1, 1)
> +        self.end_timeedit = QtGui.QTimeEdit(self.centralwidget)
> +        self.end_timeedit.setEnabled(True)
> +        self.end_timeedit.setObjectName("end_timeedit")
> +        self.gridLayout.addWidget(self.end_timeedit, 8, 2, 1, 1)
> +        self.set_start_pushbutton = QtGui.QPushButton(self.centralwidget)
> +        self.set_start_pushbutton.setEnabled(True)
> +        self.set_start_pushbutton.setObjectName("set_start_pushbutton")
> +        self.gridLayout.addWidget(self.set_start_pushbutton, 7, 3, 1, 1)
> +        self.load_disc_pushbutton = QtGui.QPushButton(self.centralwidget)
> +        self.load_disc_pushbutton.setEnabled(True)
> +        self.load_disc_pushbutton.setObjectName("load_disc_pushbutton")
> +        self.gridLayout.addWidget(self.load_disc_pushbutton, 0, 4, 1, 1)
> +        spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
> +        self.gridLayout.addItem(spacerItem, 9, 3, 1, 1)
> +        self.play_pushbutton = QtGui.QPushButton(self.centralwidget)
> +        self.play_pushbutton.setEnabled(True)
> +        self.play_pushbutton.setText("")
> +        icon = QtGui.QIcon()
> +        icon.addPixmap(QtGui.QPixmap(":/slides/media_playback_start.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
> +        self.play_pushbutton.setIcon(icon)
> +        self.play_pushbutton.setObjectName("play_pushbutton")
> +        self.gridLayout.addWidget(self.play_pushbutton, 6, 0, 1, 1)
> +        self.end_point_label = QtGui.QLabel(self.centralwidget)
> +        self.end_point_label.setEnabled(True)
> +        self.end_point_label.setObjectName("end_point_label")
> +        self.gridLayout.addWidget(self.end_point_label, 8, 0, 1, 1)
> +        self.subtitle_tracks_combobox = QtGui.QComboBox(self.centralwidget)
> +        self.subtitle_tracks_combobox.setEnabled(True)
> +        self.subtitle_tracks_combobox.setObjectName("subtitle_tracks_combobox")
> +        self.gridLayout.addWidget(self.subtitle_tracks_combobox, 4, 2, 1, 2)
> +        self.title_label = QtGui.QLabel(self.centralwidget)
> +        self.title_label.setEnabled(True)
> +        self.title_label.setObjectName("title_label")
> +        self.gridLayout.addWidget(self.title_label, 2, 0, 1, 1)
> +        self.audio_tracks_combobox = QtGui.QComboBox(self.centralwidget)
> +        self.audio_tracks_combobox.setEnabled(True)
> +        self.audio_tracks_combobox.setObjectName("audio_tracks_combobox")
> +        self.gridLayout.addWidget(self.audio_tracks_combobox, 3, 2, 1, 2)
> +        self.set_end_pushbutton = QtGui.QPushButton(self.centralwidget)
> +        self.set_end_pushbutton.setEnabled(True)
> +        self.set_end_pushbutton.setObjectName("set_end_pushbutton")
> +        self.gridLayout.addWidget(self.set_end_pushbutton, 8, 3, 1, 1)
> +        self.save_pushbutton = QtGui.QPushButton(self.centralwidget)
> +        self.save_pushbutton.setEnabled(True)
> +        self.save_pushbutton.setObjectName("save_pushbutton")
> +        self.gridLayout.addWidget(self.save_pushbutton, 10, 3, 1, 1)
> +        self.close_pushbutton = QtGui.QPushButton(self.centralwidget)
> +        self.close_pushbutton.setEnabled(True)
> +        self.close_pushbutton.setObjectName("close_pushbutton")
> +        self.gridLayout.addWidget(self.close_pushbutton, 10, 4, 1, 1)
> +        self.start_point_label = QtGui.QLabel(self.centralwidget)
> +        self.start_point_label.setEnabled(True)
> +        self.start_point_label.setObjectName("start_point_label")
> +        self.gridLayout.addWidget(self.start_point_label, 7, 0, 1, 2)
> +        self.jump_start_pushbutton = QtGui.QPushButton(self.centralwidget)
> +        self.jump_start_pushbutton.setEnabled(True)
> +        self.jump_start_pushbutton.setObjectName("jump_start_pushbutton")
> +        self.gridLayout.addWidget(self.jump_start_pushbutton, 7, 4, 1, 1)
> +        self.audio_track_label = QtGui.QLabel(self.centralwidget)
> +        self.audio_track_label.setEnabled(True)
> +        self.audio_track_label.setObjectName("audio_track_label")
> +        self.gridLayout.addWidget(self.audio_track_label, 3, 0, 1, 2)
> +        self.media_position_timeedit = QtGui.QTimeEdit(self.centralwidget)
> +        self.media_position_timeedit.setEnabled(True)
> +        self.media_position_timeedit.setReadOnly(True)
> +        self.media_position_timeedit.setObjectName("media_position_timeedit")
> +        self.gridLayout.addWidget(self.media_position_timeedit, 6, 4, 1, 1)
> +        self.media_view_frame = QtGui.QFrame(self.centralwidget)
> +        self.media_view_frame.setMinimumSize(QtCore.QSize(665, 375))
> +        self.media_view_frame.setStyleSheet("background-color:black;")
> +        self.media_view_frame.setFrameShape(QtGui.QFrame.StyledPanel)
> +        self.media_view_frame.setFrameShadow(QtGui.QFrame.Raised)
> +        self.media_view_frame.setObjectName("media_view_frame")
> +        self.gridLayout.addWidget(self.media_view_frame, 5, 0, 1, 5)
> +        self.subtitle_track_label = QtGui.QLabel(self.centralwidget)
> +        self.subtitle_track_label.setEnabled(True)
> +        self.subtitle_track_label.setObjectName("subtitle_track_label")
> +        self.gridLayout.addWidget(self.subtitle_track_label, 4, 0, 1, 2)
> +        self.jump_end_pushbutton = QtGui.QPushButton(self.centralwidget)
> +        self.jump_end_pushbutton.setEnabled(True)
> +        self.jump_end_pushbutton.setObjectName("jump_end_pushbutton")
> +        self.gridLayout.addWidget(self.jump_end_pushbutton, 8, 4, 1, 1)
> +        self.media_path_label = QtGui.QLabel(self.centralwidget)
> +        self.media_path_label.setEnabled(True)
> +        self.media_path_label.setObjectName("media_path_label")
> +        self.gridLayout.addWidget(self.media_path_label, 0, 0, 1, 2)
> +        self.title_combo_box = QtGui.QComboBox(self.centralwidget)
> +        self.title_combo_box.setEnabled(True)
> +        self.title_combo_box.setProperty("currentText", "")
> +        self.title_combo_box.setObjectName("title_combo_box")
> +        self.gridLayout.addWidget(self.title_combo_box, 2, 2, 1, 2)
> +        self.position_horizontalslider = QtGui.QSlider(self.centralwidget)
> +        self.position_horizontalslider.setEnabled(True)
> +        self.position_horizontalslider.setTracking(False)
> +        self.position_horizontalslider.setOrientation(QtCore.Qt.Horizontal)
> +        self.position_horizontalslider.setInvertedAppearance(False)
> +        self.position_horizontalslider.setObjectName("position_horizontalslider")
> +        self.gridLayout.addWidget(self.position_horizontalslider, 6, 1, 1, 3)
> +

blank line

> +        self.retranslateUi(MediaClipSelector)
> +        QtCore.QMetaObject.connectSlotsByName(MediaClipSelector)
> +        MediaClipSelector.setTabOrder(self.media_path_combobox, self.load_disc_pushbutton)
> +        MediaClipSelector.setTabOrder(self.load_disc_pushbutton, self.title_combo_box)
> +        MediaClipSelector.setTabOrder(self.title_combo_box, self.audio_tracks_combobox)
> +        MediaClipSelector.setTabOrder(self.audio_tracks_combobox, self.subtitle_tracks_combobox)
> +        MediaClipSelector.setTabOrder(self.subtitle_tracks_combobox, self.play_pushbutton)
> +        MediaClipSelector.setTabOrder(self.play_pushbutton, self.position_horizontalslider)
> +        MediaClipSelector.setTabOrder(self.position_horizontalslider, self.media_position_timeedit)
> +        MediaClipSelector.setTabOrder(self.media_position_timeedit, self.start_timeedit)
> +        MediaClipSelector.setTabOrder(self.start_timeedit, self.set_start_pushbutton)
> +        MediaClipSelector.setTabOrder(self.set_start_pushbutton, self.jump_start_pushbutton)
> +        MediaClipSelector.setTabOrder(self.jump_start_pushbutton, self.end_timeedit)
> +        MediaClipSelector.setTabOrder(self.end_timeedit, self.set_end_pushbutton)
> +        MediaClipSelector.setTabOrder(self.set_end_pushbutton, self.jump_end_pushbutton)
> +        MediaClipSelector.setTabOrder(self.jump_end_pushbutton, self.save_pushbutton)
> +        MediaClipSelector.setTabOrder(self.save_pushbutton, self.close_pushbutton)
> +
> +    def retranslateUi(self, MediaClipSelector):
> +        MediaClipSelector.setWindowTitle(translate("MediaPlugin.MediaClipSelector", "Select media clip", None))
> +        self.start_timeedit.setDisplayFormat(translate("MediaPlugin.MediaClipSelector", "HH:mm:ss.z", None))
> +        self.end_timeedit.setDisplayFormat(translate("MediaPlugin.MediaClipSelector", "HH:mm:ss.z", None))
> +        self.set_start_pushbutton.setText(translate("MediaPlugin.MediaClipSelector",
> +                                                    "Set current position as start point", None))
> +        self.load_disc_pushbutton.setText(translate("MediaPlugin.MediaClipSelector", "Load disc", None))
> +        self.end_point_label.setText(translate("MediaPlugin.MediaClipSelector", "End point", None))
> +        self.title_label.setText(translate("MediaPlugin.MediaClipSelector", "Title", None))
> +        self.set_end_pushbutton.setText(translate("MediaPlugin.MediaClipSelector",
> +                                                  "Set current position as end point", None))
> +        self.save_pushbutton.setText(translate("MediaPlugin.MediaClipSelector", "Save current clip", None))
> +        self.close_pushbutton.setText(translate("MediaPlugin.MediaClipSelector", "Close", None))
> +        self.start_point_label.setText(translate("MediaPlugin.MediaClipSelector", "Start point", None))
> +        self.jump_start_pushbutton.setText(translate("MediaPlugin.MediaClipSelector", "Jump to start point", None))
> +        self.audio_track_label.setText(translate("MediaPlugin.MediaClipSelector", "Audio track", None))
> +        self.media_position_timeedit.setDisplayFormat(translate("MediaPlugin.MediaClipSelector", "HH:mm:ss.z", None))
> +        self.subtitle_track_label.setText(translate("MediaPlugin.MediaClipSelector", "Subtitle track", None))
> +        self.jump_end_pushbutton.setText(translate("MediaPlugin.MediaClipSelector", "Jump to end point", None))
> +        self.media_path_label.setText(translate("MediaPlugin.MediaClipSelector", "Media path", None))
> 
> === added file 'openlp/plugins/media/forms/mediaclipselectorform.py'
> --- openlp/plugins/media/forms/mediaclipselectorform.py	1970-01-01 00:00:00 +0000
> +++ openlp/plugins/media/forms/mediaclipselectorform.py	2014-07-01 17:51:13 +0000
> @@ -0,0 +1,644 @@
> +# -*- 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 ctypes import windll
> +    import string
> +import sys
> +if sys.platform.startswith('linux'):
> +    import dbus
> +import logging
> +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.media_item = media_item
> +        self.setupUi(self)
> +        # most actions auto-connect due to the functions name, so only a few left to do
> +        self.close_pushbutton.clicked.connect(self.reject)
> +        # 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
> +        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_horizontalslider.setMinimum(0)
> +        self.disable_all()
> +        self.toggle_disable_load_media(False)
> +        self.subtitle_tracks_combobox.clear()
> +        self.audio_tracks_combobox.clear()
> +        self.title_combo_box.clear()
> +        time = QtCore.QTime()
> +        self.start_timeedit.setTime(time)
> +        self.end_timeedit.setTime(time)
> +        self.media_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.media_view_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 title_combo_box
> +        self.title_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.title_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.title_combo_box.setDisabled(False)
> +        self.title_combo_box.setCurrentIndex(0)
> +        self.on_title_combo_box_currentIndexChanged(0)
> +
> +        return True
> +
> +    @QtCore.pyqtSlot(bool)
> +    def on_load_disc_pushbutton_clicked(self, clicked):
> +        """
> +        Load the media when the load-button has been clicked
> +
> +        :param clicked: Given from signal, not used.
> +        """
> +        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
> +        self.vlc_media = self.vlc_instance.media_new_location('file://' + 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.set_pause(1)
> +        self.vlc_media_player.set_time(0)
> +        if not self.audio_cd:
> +            # Get titles, insert in combobox
> +            titles = self.vlc_media_player.video_get_title_description()
> +            self.title_combo_box.clear()
> +            for title in titles:
> +                self.title_combo_box.addItem(title[1].decode(), title[0])
> +            # Main title is usually title #1
> +            if len(titles) > 1:
> +                self.title_combo_box.setCurrentIndex(1)
> +            else:
> +                self.title_combo_box.setCurrentIndex(0)
> +            # Enable audio track combobox if anything is in it
> +            if len(titles) > 0:
> +                self.title_combo_box.setDisabled(False)
> +        self.vlc_media_player.set_pause(1)
> +        self.toggle_disable_load_media(False)
> +
> +    @QtCore.pyqtSlot(bool)
> +    def on_play_pushbutton_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_pushbutton.setIcon(self.play_icon)
> +        else:
> +            self.vlc_media_player.play()
> +            self.media_state_wait(vlc.State.Playing)
> +            self.play_pushbutton.setIcon(self.pause_icon)
> +
> +    @QtCore.pyqtSlot(bool)
> +    def on_set_start_pushbutton_clicked(self, clicked):
> +        """
> +        Copy the current player position to start_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.start_timeedit.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_pushbutton_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_timeedit.time()
> +        if start_time > new_pos_time:
> +            self.start_timeedit.setTime(new_pos_time)
> +
> +    @QtCore.pyqtSlot(QtCore.QTime)
> +    def on_start_timeedit_timeChanged(self, new_time):
> +        """
> +        Called when start_timeedit 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_timeedit.time()
> +        if start_time > new_time:
> +            self.start_timeedit.setTime(new_time)
> +
> +    @QtCore.pyqtSlot(bool)
> +    def on_jump_end_pushbutton_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_pushbutton_clicked(self, clicked):
> +        """
> +        Set the player position to the position stored in start_timeedit
> +
> +        :param clicked: Given from signal, not used.
> +        """
> +        start_time = self.start_timeedit.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_title_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_title_combo_box_changed, index: %d', index)
> +        if not self.vlc_media_player:
> +            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()
> +            self.vlc_media_player.audio_set_mute(True)
> +            if not self.media_state_wait(vlc.State.Playing):
> +                return
> +            # pause
> +            self.vlc_media_player.set_pause(1)
> +            self.vlc_media_player.set_time(0)
> +            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()
> +            self.vlc_media_player.audio_set_mute(True)
> +            if not self.media_state_wait(vlc.State.Playing):
> +                return
> +            # pause
> +            self.vlc_media_player.set_pause(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)
> +            # If a title or audio track is available the player is enabled
> +            if self.title_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()
> +        self.position_horizontalslider.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_timeedit.setMaximumTime(playback_length_time)
> +        self.end_timeedit.setMaximumTime(playback_length_time)
> +        self.end_timeedit.setTime(playback_length_time)
> +
> +    @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_horizontalslider_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.media_position_timeedit.setTime(new_pos_time)
> +            self.position_horizontalslider.setSliderPosition(vlc_ms_pos)
> +
> +    def disable_all(self):
> +        """
> +        Disable all elements in the dialog
> +        """
> +        self.toggle_disable_load_media(True)
> +        self.title_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_pushbutton.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_pushbutton.setDisabled(action)
> +        self.position_horizontalslider.setDisabled(action)
> +        self.media_position_timeedit.setDisabled(action)
> +        self.start_timeedit.setDisabled(action)
> +        self.set_start_pushbutton.setDisabled(action)
> +        self.jump_start_pushbutton.setDisabled(action)
> +        self.end_timeedit.setDisabled(action)
> +        self.set_end_pushbutton.setDisabled(action)
> +        self.jump_end_pushbutton.setDisabled(action)
> +        self.save_pushbutton.setDisabled(action)
> +
> +    @QtCore.pyqtSlot(bool)
> +    def on_save_pushbutton_clicked(self, clicked):
> +        """
> +        Saves the current media and trackinfo as a clip to the mediamanager
> +
> +        :param clicked: Given from signal, not used.
> +        """
> +        log.debug('in on_save_pushbutton_clicked')
> +        start_time = self.start_timeedit.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.title_combo_box.itemData(self.title_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 > 15:
> +                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()
> +        # insert empty string as first item
> +        self.media_path_combobox.addItem('')
> +        if os.name == 'nt':
> +            # use win api to fine optical drives
> +            bitmask = windll.kernel32.GetLogicalDrives()
> +            for letter in string.uppercase:
> +                if bitmask & 1:
> +                    try:
> +                        type = windll.kernel32.GetDriveTypeW('%s:\\' % letter)
> +                        # if type is 5, it is a cd-rom drive
> +                        if type == 5:
> +                            self.media_path_combobox.addItem('%s:\\' % letter)
> +                    except Exception as e:
> +                        log.debug('Exception while looking for optical drives: ', e)
> +                bitmask >>= 1
> +        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
> 
> === modified file 'openlp/plugins/media/lib/mediaitem.py'
> --- openlp/plugins/media/lib/mediaitem.py	2014-05-03 20:00:17 +0000
> +++ openlp/plugins/media/lib/mediaitem.py	2014-07-01 17:51:13 +0000
> @@ -29,6 +29,7 @@
>  
>  import logging
>  import os
> +from datetime import time
>  
>  from PyQt4 import QtCore, QtGui
>  
> @@ -38,17 +39,21 @@
>      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.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.ui.media.vlcplayer import VLC_AVAILABLE
> +if VLC_AVAILABLE:
> +    from openlp.plugins.media.forms.mediaclipselectorform import MediaClipSelectorForm
>  
>  
>  log = logging.getLogger(__name__)
>  
>  
>  CLAPPERBOARD = ':/media/slidecontroller_multimedia.png'
> +OPTICAL = ':/media/media_optical.png'
>  VIDEO_ICON = build_icon(':/media/media_video.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')
>  
>  
> @@ -88,6 +93,10 @@
>          self.list_view.activateDnD()
>  
>      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.replace_action.setText(UiStrings().ReplaceBG)
>          self.replace_action.setToolTip(UiStrings().ReplaceLiveBG)
> @@ -106,10 +115,35 @@
>          self.has_edit_icon = False
>  
>      def add_list_view_to_toolbar(self):
> +        """
> +        Creates the main widget for listing items.
> +        """
>          MediaManagerItem.add_list_view_to_toolbar(self)
>          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):
> +        """
> +        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',
>                                                                triggers=self.on_replace_click)
> @@ -198,22 +232,42 @@
>              if item is None:
>                  return False
>          filename = item.data(QtCore.Qt.UserRole)
> -        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
> +        # Special handling if the filename is a optical clip
> +        if filename.startswith('optical:'):
> +            (name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(filename)
> +            if not os.path.exists(name):
> +                if not remote:
> +                    # Optical disc is no longer present
> +                    critical_error_message_box(
> +                        translate('MediaPlugin.MediaItem', 'Missing Media File'),
> +                        translate('MediaPlugin.MediaItem', 'The optical disc %s is no longer available.') % name)
> +                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.CanEditTitle)
>          service_item.add_capability(ItemCapabilities.RequiresMedia)
> @@ -224,12 +278,17 @@
>          return True
>  
>      def initialise(self):
> +        """
> +        Initialize media item.
> +        """
>          self.list_view.clear()
>          self.list_view.setIconSize(QtCore.QSize(88, 50))
>          self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
>          check_directory_exists(self.service_path)
>          self.load_list(Settings().value(self.settings_section + '/media files'))
>          self.rebuild_players()
> +        if VLC_AVAILABLE:
> +            self.media_clip_selector_form = MediaClipSelectorForm(self, self.main_window, None)
>  
>      def rebuild_players(self):
>          """
> @@ -241,6 +300,9 @@
>              ' '.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):
> @@ -280,7 +342,6 @@
>              Settings().setValue(self.settings_section + '/media files', self.get_file_list())
>  
>      def load_list(self, media, target_group=None):
> -        # Sort the media by its filename considering language specific characters.
>          """
>          Load the media list
>  
> @@ -290,12 +351,22 @@
>          media.sort(key=lambda file_name: get_locale_key(os.path.split(str(file_name))[1]))
>          for track in media:
>              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]
>                  item_name = QtGui.QListWidgetItem(file_name)
>                  item_name.setIcon(ERROR_ICON)
>                  item_name.setData(QtCore.Qt.UserRole, track)
> +                item_name.setToolTip(track)
>              elif track_info.isFile():
> +                # Normal media file handling.
>                  file_name = os.path.split(str(track))[1]
>                  item_name = QtGui.QListWidgetItem(file_name)
>                  if '*.%s' % (file_name.split('.')[-1].lower()) in self.media_controller.audio_extensions_list:
> @@ -303,15 +374,16 @@
>                  else:
>                      item_name.setIcon(VIDEO_ICON)
>                  item_name.setData(QtCore.Qt.UserRole, track)
> -            else:
> -                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)
> +                item_name.setToolTip(track)
>              self.list_view.addItem(item_name)
>  
>      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.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1]))
>          if type == MediaType.Audio:
> @@ -323,6 +395,13 @@
>          return media
>  
>      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')
>          results = []
>          string = string.lower()
> @@ -331,3 +410,26 @@
>              if filename.lower().find(string) > -1:
>                  results.append([file, filename])
>          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_()
> +
> +    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())
> 
> === added file 'resources/forms/mediaclipselector.ui'
> --- resources/forms/mediaclipselector.ui	1970-01-01 00:00:00 +0000
> +++ resources/forms/mediaclipselector.ui	2014-07-01 17:51:13 +0000
> @@ -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>
> 
> === added file 'resources/images/media_optical.png'
> Binary files resources/images/media_optical.png	1970-01-01 00:00:00 +0000 and resources/images/media_optical.png	2014-07-01 17:51:13 +0000 differ
> === modified file 'resources/images/openlp-2.qrc'
> --- resources/images/openlp-2.qrc	2014-04-14 18:09:47 +0000
> +++ resources/images/openlp-2.qrc	2014-07-01 17:51:13 +0000
> @@ -139,6 +139,7 @@
>      <file>media_stop.png</file>
>      <file>media_audio.png</file>
>      <file>media_video.png</file>
> +    <file>media_optical.png</file>
>      <file>slidecontroller_multimedia.png</file>
>      <file>auto-start_active.png</file>
>      <file>auto-start_inactive.png</file>
> 
> === modified file 'tests/functional/openlp_core_lib/test_serviceitem.py'
> --- tests/functional/openlp_core_lib/test_serviceitem.py	2014-03-13 20:59:10 +0000
> +++ tests/functional/openlp_core_lib/test_serviceitem.py	2014-07-01 17:51:13 +0000
> @@ -206,3 +206,24 @@
>                          'This service item should be able to be run in a can be made to Loop')
>          self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend),
>                          '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')
> 
> === added directory 'tests/interfaces/openlp_plugins/media'
> === added file 'tests/interfaces/openlp_plugins/media/__init__.py'
> === added directory 'tests/interfaces/openlp_plugins/media/forms'
> === added file 'tests/interfaces/openlp_plugins/media/forms/test_mediaclipselectorform.py'
> --- tests/interfaces/openlp_plugins/media/forms/test_mediaclipselectorform.py	1970-01-01 00:00:00 +0000
> +++ tests/interfaces/openlp_plugins/media/forms/test_mediaclipselectorform.py	2014-07-01 17:51:13 +0000
> @@ -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_pushbutton, 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_pushbutton, 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_pushbutton, 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.title_combo_box.insertItem(0, 'Test Title 0')
> +            self.form.title_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.title_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)
> 
> === added file 'tests/resources/serviceitem-dvd.osj'
> --- tests/resources/serviceitem-dvd.osj	1970-01-01 00:00:00 +0000
> +++ tests/resources/serviceitem-dvd.osj	2014-07-01 17:51:13 +0000
> @@ -0,0 +1,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"}]}}]
> 


-- 
https://code.launchpad.net/~tomasgroth/openlp/dvd/+merge/225214
Your team OpenLP Core is subscribed to branch lp:openlp.


Follow ups

References