forked from openlp/openlp
Add support for network streams.
This commit is contained in:
parent
b51829d4d6
commit
127ee05ac3
@ -37,6 +37,7 @@ from openlp.core.ui.media.vlcplayer import get_vlc
|
|||||||
|
|
||||||
if get_vlc() is not None:
|
if get_vlc() is not None:
|
||||||
from openlp.plugins.media.forms.streamselectorform import StreamSelectorForm
|
from openlp.plugins.media.forms.streamselectorform import StreamSelectorForm
|
||||||
|
from openlp.plugins.media.forms.networkstreamselectorform import NetworkStreamSelectorForm
|
||||||
|
|
||||||
|
|
||||||
class BackgroundPage(GridLayoutPage):
|
class BackgroundPage(GridLayoutPage):
|
||||||
@ -130,11 +131,16 @@ class BackgroundPage(GridLayoutPage):
|
|||||||
self.stream_lineedit.setObjectName('stream_lineedit')
|
self.stream_lineedit.setObjectName('stream_lineedit')
|
||||||
self.stream_lineedit.setReadOnly(True)
|
self.stream_lineedit.setReadOnly(True)
|
||||||
self.stream_layout.addWidget(self.stream_lineedit)
|
self.stream_layout.addWidget(self.stream_lineedit)
|
||||||
# button to open select stream forms
|
# button to open select device stream form
|
||||||
self.stream_select_button = QtWidgets.QToolButton(self)
|
self.device_stream_select_button = QtWidgets.QToolButton(self)
|
||||||
self.stream_select_button.setObjectName('stream_select_button')
|
self.device_stream_select_button.setObjectName('device_stream_select_button')
|
||||||
self.stream_select_button.setIcon(UiIcons().device_stream)
|
self.device_stream_select_button.setIcon(UiIcons().device_stream)
|
||||||
self.stream_layout.addWidget(self.stream_select_button)
|
self.stream_layout.addWidget(self.device_stream_select_button)
|
||||||
|
# button to open select network stream form
|
||||||
|
self.network_stream_select_button = QtWidgets.QToolButton(self)
|
||||||
|
self.network_stream_select_button.setObjectName('network_stream_select_button')
|
||||||
|
self.network_stream_select_button.setIcon(UiIcons().network_stream)
|
||||||
|
self.stream_layout.addWidget(self.network_stream_select_button)
|
||||||
self.layout.addLayout(self.stream_layout, 6, 1, 1, 3)
|
self.layout.addLayout(self.stream_layout, 6, 1, 1, 3)
|
||||||
self.stream_color_label = FormLabel(self)
|
self.stream_color_label = FormLabel(self)
|
||||||
self.stream_color_label.setObjectName('stream_color_label')
|
self.stream_color_label.setObjectName('stream_color_label')
|
||||||
@ -143,14 +149,15 @@ class BackgroundPage(GridLayoutPage):
|
|||||||
self.stream_color_button.color = '#000000'
|
self.stream_color_button.color = '#000000'
|
||||||
self.stream_color_button.setObjectName('stream_color_button')
|
self.stream_color_button.setObjectName('stream_color_button')
|
||||||
self.layout.addWidget(self.stream_color_button, 7, 1)
|
self.layout.addWidget(self.stream_color_button, 7, 1)
|
||||||
self.stream_widgets = [self.stream_label, self.stream_lineedit, self.stream_select_button,
|
self.stream_widgets = [self.stream_label, self.stream_lineedit, self.device_stream_select_button,
|
||||||
self.stream_color_label, self.stream_color_button]
|
self.stream_color_label, self.stream_color_button]
|
||||||
# Force everything up
|
# Force everything up
|
||||||
self.layout_spacer = QtWidgets.QSpacerItem(1, 1)
|
self.layout_spacer = QtWidgets.QSpacerItem(1, 1)
|
||||||
self.layout.addItem(self.layout_spacer, 8, 0, 1, 4)
|
self.layout.addItem(self.layout_spacer, 8, 0, 1, 4)
|
||||||
# Connect slots
|
# Connect slots
|
||||||
self.background_combo_box.currentIndexChanged.connect(self._on_background_type_index_changed)
|
self.background_combo_box.currentIndexChanged.connect(self._on_background_type_index_changed)
|
||||||
self.stream_select_button.clicked.connect(self._on_stream_select_button_triggered)
|
self.device_stream_select_button.clicked.connect(self._on_device_stream_select_button_triggered)
|
||||||
|
self.network_stream_select_button.clicked.connect(self._on_network_stream_select_button_triggered)
|
||||||
# Force the first set of widgets to show
|
# Force the first set of widgets to show
|
||||||
self._on_background_type_index_changed(0)
|
self._on_background_type_index_changed(0)
|
||||||
|
|
||||||
@ -208,7 +215,7 @@ class BackgroundPage(GridLayoutPage):
|
|||||||
for widget in widget_sets[index]:
|
for widget in widget_sets[index]:
|
||||||
widget.show()
|
widget.show()
|
||||||
|
|
||||||
def _on_stream_select_button_triggered(self):
|
def _on_device_stream_select_button_triggered(self):
|
||||||
"""
|
"""
|
||||||
Open the Stream selection form.
|
Open the Stream selection form.
|
||||||
"""
|
"""
|
||||||
@ -222,6 +229,20 @@ class BackgroundPage(GridLayoutPage):
|
|||||||
critical_error_message_box(translate('MediaPlugin.MediaItem', 'VLC is not available'),
|
critical_error_message_box(translate('MediaPlugin.MediaItem', 'VLC is not available'),
|
||||||
translate('MediaPlugin.MediaItem', 'Device streaming support requires VLC.'))
|
translate('MediaPlugin.MediaItem', 'Device streaming support requires VLC.'))
|
||||||
|
|
||||||
|
def _on_network_stream_select_button_triggered(self):
|
||||||
|
"""
|
||||||
|
Open the Stream selection form.
|
||||||
|
"""
|
||||||
|
if get_vlc():
|
||||||
|
stream_selector_form = NetworkStreamSelectorForm(self, self.set_stream, True)
|
||||||
|
if self.stream_lineedit.text():
|
||||||
|
stream_selector_form.set_mrl(self.stream_lineedit.text())
|
||||||
|
stream_selector_form.exec()
|
||||||
|
del stream_selector_form
|
||||||
|
else:
|
||||||
|
critical_error_message_box(translate('MediaPlugin.MediaItem', 'VLC is not available'),
|
||||||
|
translate('MediaPlugin.MediaItem', 'Network streaming support requires VLC.'))
|
||||||
|
|
||||||
def set_stream(self, stream_str):
|
def set_stream(self, stream_str):
|
||||||
"""
|
"""
|
||||||
callback method used to get the stream mrl and options
|
callback method used to get the stream mrl and options
|
||||||
|
@ -99,6 +99,7 @@ class UiIcons(metaclass=Singleton):
|
|||||||
'media': {'icon': 'fa.fax'},
|
'media': {'icon': 'fa.fax'},
|
||||||
'minus': {'icon': 'fa.minus'},
|
'minus': {'icon': 'fa.minus'},
|
||||||
'music': {'icon': 'fa.music'},
|
'music': {'icon': 'fa.music'},
|
||||||
|
'network_stream': {'icon': 'fa.link'},
|
||||||
'new': {'icon': 'fa.file'},
|
'new': {'icon': 'fa.file'},
|
||||||
'new_group': {'icon': 'fa.folder'},
|
'new_group': {'icon': 'fa.folder'},
|
||||||
'notes': {'icon': 'fa.sticky-note'},
|
'notes': {'icon': 'fa.sticky-note'},
|
||||||
|
@ -104,16 +104,18 @@ def parse_optical_path(input_string):
|
|||||||
return filename, title, audio_track, subtitle_track, start, end, clip_name
|
return filename, title, audio_track, subtitle_track, start, end, clip_name
|
||||||
|
|
||||||
|
|
||||||
def parse_devicestream_path(input_string):
|
def parse_stream_path(input_string):
|
||||||
"""
|
"""
|
||||||
Split the device stream path info.
|
Split the device stream path info.
|
||||||
|
|
||||||
:param input_string: The string to parse
|
:param input_string: The string to parse
|
||||||
:return: The elements extracted from the string: streamname, MRL, VLC-options
|
:return: The elements extracted from the string: streamname, MRL, VLC-options
|
||||||
"""
|
"""
|
||||||
log.debug('parse_devicestream_path, about to parse: "{text}"'.format(text=input_string))
|
log.debug('parse_stream_path, about to parse: "{text}"'.format(text=input_string))
|
||||||
# skip the header: 'devicestream:', split at '&&'
|
# skip the header: 'devicestream:' or 'networkstream:'
|
||||||
stream_info = input_string[len('devicestream:'):].split('&&')
|
header, data = input_string.split(':', 1)
|
||||||
|
# split at '&&'
|
||||||
|
stream_info = data.split('&&')
|
||||||
name = stream_info[0]
|
name = stream_info[0]
|
||||||
mrl = stream_info[1]
|
mrl = stream_info[1]
|
||||||
options = stream_info[2]
|
options = stream_info[2]
|
||||||
|
@ -41,7 +41,7 @@ from openlp.core.common.registry import Registry, RegistryBase
|
|||||||
from openlp.core.lib.serviceitem import ItemCapabilities
|
from openlp.core.lib.serviceitem import ItemCapabilities
|
||||||
from openlp.core.lib.ui import critical_error_message_box
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
from openlp.core.ui import DisplayControllerType, HideMode
|
from openlp.core.ui import DisplayControllerType, HideMode
|
||||||
from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, parse_devicestream_path, \
|
from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, parse_stream_path, \
|
||||||
VIDEO_EXT, AUDIO_EXT
|
VIDEO_EXT, AUDIO_EXT
|
||||||
from openlp.core.ui.media.remote import register_views
|
from openlp.core.ui.media.remote import register_views
|
||||||
from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc
|
from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc
|
||||||
@ -231,7 +231,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||||||
controller.media_info.file_info = service_item.background_audio
|
controller.media_info.file_info = service_item.background_audio
|
||||||
else:
|
else:
|
||||||
if service_item.is_capable(ItemCapabilities.HasBackgroundStream):
|
if service_item.is_capable(ItemCapabilities.HasBackgroundStream):
|
||||||
(name, mrl, options) = parse_devicestream_path(service_item.stream_mrl)
|
(name, mrl, options) = parse_stream_path(service_item.stream_mrl)
|
||||||
controller.media_info.file_info = (mrl, options)
|
controller.media_info.file_info = (mrl, options)
|
||||||
controller.media_info.is_background = True
|
controller.media_info.is_background = True
|
||||||
controller.media_info.media_type = MediaType.Stream
|
controller.media_info.media_type = MediaType.Stream
|
||||||
@ -255,7 +255,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||||||
log.debug('video is stream and live')
|
log.debug('video is stream and live')
|
||||||
path = service_item.get_frames()[0]['path']
|
path = service_item.get_frames()[0]['path']
|
||||||
controller.media_info.media_type = MediaType.Stream
|
controller.media_info.media_type = MediaType.Stream
|
||||||
(name, mrl, options) = parse_devicestream_path(path)
|
(name, mrl, options) = parse_stream_path(path)
|
||||||
controller.media_info.file_info = (mrl, options)
|
controller.media_info.file_info = (mrl, options)
|
||||||
is_valid = self._check_file_type(controller, display)
|
is_valid = self._check_file_type(controller, display)
|
||||||
else:
|
else:
|
||||||
@ -274,7 +274,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||||||
elif service_item.is_capable(ItemCapabilities.CanStream):
|
elif service_item.is_capable(ItemCapabilities.CanStream):
|
||||||
path = service_item.get_frames()[0]['path']
|
path = service_item.get_frames()[0]['path']
|
||||||
controller.media_info.media_type = MediaType.Stream
|
controller.media_info.media_type = MediaType.Stream
|
||||||
(name, mrl, options) = parse_devicestream_path(path)
|
(name, mrl, options) = parse_stream_path(path)
|
||||||
controller.media_info.file_info = (mrl, options)
|
controller.media_info.file_info = (mrl, options)
|
||||||
is_valid = self._check_file_type(controller, display)
|
is_valid = self._check_file_type(controller, display)
|
||||||
else:
|
else:
|
||||||
|
@ -18,3 +18,149 @@
|
|||||||
# You should have received a copy of the GNU General Public License #
|
# You should have received a copy of the GNU General Public License #
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
|
from openlp.core.common.i18n import translate
|
||||||
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
|
from openlp.core.ui.media import parse_stream_path
|
||||||
|
|
||||||
|
|
||||||
|
class StreamSelectorFormBase(QtWidgets.QDialog):
|
||||||
|
"""
|
||||||
|
Class to manage the clip selection
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent, callback, theme_stream=False):
|
||||||
|
"""
|
||||||
|
Constructor
|
||||||
|
"""
|
||||||
|
super(StreamSelectorFormBase, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint |
|
||||||
|
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
|
||||||
|
self.callback = callback
|
||||||
|
self.theme_stream = theme_stream
|
||||||
|
self.setup_base_ui()
|
||||||
|
self.type = ''
|
||||||
|
|
||||||
|
def setup_base_ui(self):
|
||||||
|
self.setObjectName('stream_selector')
|
||||||
|
self.combobox_size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
|
||||||
|
QtWidgets.QSizePolicy.Fixed)
|
||||||
|
self.setSizePolicy(
|
||||||
|
QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
|
||||||
|
self.main_layout = QtWidgets.QVBoxLayout(self)
|
||||||
|
self.main_layout.setObjectName('main_layout')
|
||||||
|
|
||||||
|
self.top_widget = QtWidgets.QWidget(self)
|
||||||
|
self.top_widget.setObjectName('top_widget')
|
||||||
|
self.top_layout = QtWidgets.QFormLayout(self.top_widget)
|
||||||
|
self.top_layout.setObjectName('top_layout')
|
||||||
|
# Stream name
|
||||||
|
if not self.theme_stream:
|
||||||
|
self.stream_name_label = QtWidgets.QLabel(self.top_widget)
|
||||||
|
self.stream_name_label.setObjectName('stream_name_label')
|
||||||
|
self.stream_name_edit = QtWidgets.QLineEdit(self.top_widget)
|
||||||
|
self.stream_name_edit.setObjectName('stream_name_edit')
|
||||||
|
self.top_layout.addRow(self.stream_name_label, self.stream_name_edit)
|
||||||
|
|
||||||
|
def exec(self):
|
||||||
|
"""
|
||||||
|
Start dialog
|
||||||
|
"""
|
||||||
|
return QtWidgets.QDialog.exec(self)
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
"""
|
||||||
|
Saves the current stream as a clip to the mediamanager
|
||||||
|
"""
|
||||||
|
if not self.theme_stream:
|
||||||
|
# Verify that a stream name exists
|
||||||
|
if not self.stream_name_edit.text().strip():
|
||||||
|
critical_error_message_box(message=translate('MediaPlugin.StreamSelector', 'A Stream name is needed.'))
|
||||||
|
return
|
||||||
|
stream_name = self.stream_name_edit.text().strip()
|
||||||
|
else:
|
||||||
|
stream_name = ' '
|
||||||
|
# Verify that a MRL exists
|
||||||
|
if not self.more_options_group.mrl_lineedit.text().strip():
|
||||||
|
critical_error_message_box(message=translate('MediaPlugin.StreamSelector', 'A MRL is needed.'), parent=self)
|
||||||
|
return
|
||||||
|
stream_string = '{type}:{name}&&{mrl}&&{options}'.format(
|
||||||
|
type=self.type, name=stream_name, mrl=self.more_options_group.mrl_lineedit.text().strip(),
|
||||||
|
options=self.more_options_group.vlc_options_lineedit.text().strip())
|
||||||
|
self.callback(stream_string)
|
||||||
|
return QtWidgets.QDialog.accept(self)
|
||||||
|
|
||||||
|
def update_mrl_options(self, mrl, options):
|
||||||
|
"""
|
||||||
|
Callback method used to fill the MRL and Options text fields
|
||||||
|
"""
|
||||||
|
options += ' :live-caching={cache}'.format(cache=self.more_options_group.caching.value())
|
||||||
|
self.more_options_group.mrl_lineedit.setText(mrl)
|
||||||
|
self.more_options_group.vlc_options_lineedit.setText(options)
|
||||||
|
|
||||||
|
def set_mrl(self, stream_str):
|
||||||
|
"""
|
||||||
|
Setup the stream widgets based on the saved stream string. This is best effort as the string is
|
||||||
|
editable for the user.
|
||||||
|
"""
|
||||||
|
(name, mrl, options) = parse_stream_path(stream_str)
|
||||||
|
cache = re.search(r'live-caching=(\d+)', options)
|
||||||
|
if cache:
|
||||||
|
self.more_options_group.caching.setValue(int(cache.group(1)))
|
||||||
|
|
||||||
|
self.more_options_group.mrl_lineedit.setText(mrl)
|
||||||
|
self.more_options_group.vlc_options_lineedit.setText(options)
|
||||||
|
|
||||||
|
|
||||||
|
class VLCOptionsWidget(QtWidgets.QGroupBox):
|
||||||
|
"""
|
||||||
|
Groupbox widget for VLC options: caching, mrl and VLC options
|
||||||
|
"""
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
"""
|
||||||
|
Initialise the widget.
|
||||||
|
|
||||||
|
:param QtWidgets.QWidget | None parent: The widgets parent
|
||||||
|
"""
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setup_ui()
|
||||||
|
self.retranslate_ui()
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
"""
|
||||||
|
Create the widget layout and sub widgets
|
||||||
|
"""
|
||||||
|
# Groupbox for VLC options
|
||||||
|
self.vlc_options_group_layout = QtWidgets.QFormLayout(self)
|
||||||
|
self.vlc_options_group_layout.setObjectName('more_options_group_layout')
|
||||||
|
# Caching spinbox
|
||||||
|
self.caching_label = QtWidgets.QLabel(self)
|
||||||
|
self.caching_label.setObjectName('caching_label')
|
||||||
|
self.caching = QtWidgets.QSpinBox(self)
|
||||||
|
self.caching.setAlignment(QtCore.Qt.AlignRight)
|
||||||
|
self.caching.setSuffix(' ms')
|
||||||
|
self.caching.setSingleStep(100)
|
||||||
|
self.caching.setMaximum(65535)
|
||||||
|
self.caching.setValue(300)
|
||||||
|
self.vlc_options_group_layout.addRow(self.caching_label, self.caching)
|
||||||
|
# MRL
|
||||||
|
self.mrl_label = QtWidgets.QLabel(self)
|
||||||
|
self.mrl_label.setObjectName('mrl_label')
|
||||||
|
self.mrl_lineedit = QtWidgets.QLineEdit(self)
|
||||||
|
self.mrl_lineedit.setObjectName('mrl_lineedit')
|
||||||
|
self.vlc_options_group_layout.addRow(self.mrl_label, self.mrl_lineedit)
|
||||||
|
# VLC options
|
||||||
|
self.vlc_options_label = QtWidgets.QLabel(self)
|
||||||
|
self.vlc_options_label.setObjectName('vlc_options_label')
|
||||||
|
self.vlc_options_lineedit = QtWidgets.QLineEdit(self)
|
||||||
|
self.vlc_options_lineedit.setObjectName('vlc_options_lineedit')
|
||||||
|
self.vlc_options_group_layout.addRow(self.vlc_options_label, self.vlc_options_lineedit)
|
||||||
|
|
||||||
|
def retranslate_ui(self):
|
||||||
|
self.setTitle(translate('MediaPlugin.StreamSelector', 'More options'))
|
||||||
|
self.caching_label.setText(translate('MediaPlugin.StreamSelector', 'Caching'))
|
||||||
|
self.mrl_label.setText(translate('MediaPlugin.StreamSelector', 'MRL'))
|
||||||
|
self.vlc_options_label.setText(translate('MediaPlugin.StreamSelector', 'VLC options'))
|
||||||
|
81
openlp/plugins/media/forms/networkstreamselectorform.py
Normal file
81
openlp/plugins/media/forms/networkstreamselectorform.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# ---------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2020 OpenLP Developers #
|
||||||
|
# ---------------------------------------------------------------------- #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# 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, see <https://www.gnu.org/licenses/>. #
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from openlp.core.common.i18n import translate
|
||||||
|
from openlp.plugins.media.forms import StreamSelectorFormBase, VLCOptionsWidget
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkStreamSelectorForm(StreamSelectorFormBase):
|
||||||
|
"""
|
||||||
|
Class to manage the network stream selection
|
||||||
|
"""
|
||||||
|
log.info('{name} NetworkStreamSelectorForm loaded'.format(name=__name__))
|
||||||
|
|
||||||
|
def __init__(self, parent, callback, theme_stream=False):
|
||||||
|
"""
|
||||||
|
Constructor
|
||||||
|
"""
|
||||||
|
super(NetworkStreamSelectorForm, self).__init__(parent, callback, theme_stream)
|
||||||
|
self.type = 'networkstream'
|
||||||
|
self.setup_ui()
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
self.net_mrl_label = QtWidgets.QLabel(self)
|
||||||
|
self.net_mrl_label.setObjectName('net_mrl_label')
|
||||||
|
self.net_mrl_lineedit = QtWidgets.QLineEdit(self)
|
||||||
|
self.net_mrl_lineedit.setObjectName('net_mrl_lineedit')
|
||||||
|
self.top_layout.addRow(self.net_mrl_label, self.net_mrl_lineedit)
|
||||||
|
self.main_layout.addWidget(self.top_widget)
|
||||||
|
|
||||||
|
# Add groupbox for VLC options
|
||||||
|
self.more_options_group = VLCOptionsWidget(self)
|
||||||
|
# Add groupbox for more options to main layout
|
||||||
|
self.main_layout.addWidget(self.more_options_group)
|
||||||
|
# Save and close buttons
|
||||||
|
self.button_box = QtWidgets.QDialogButtonBox(self)
|
||||||
|
self.button_box.addButton(QtWidgets.QDialogButtonBox.Save)
|
||||||
|
self.button_box.addButton(QtWidgets.QDialogButtonBox.Close)
|
||||||
|
self.close_button = self.button_box.button(QtWidgets.QDialogButtonBox.Close)
|
||||||
|
self.save_button = self.button_box.button(QtWidgets.QDialogButtonBox.Save)
|
||||||
|
self.main_layout.addWidget(self.button_box)
|
||||||
|
|
||||||
|
# translate
|
||||||
|
self.retranslate_ui()
|
||||||
|
# connect
|
||||||
|
self.net_mrl_lineedit.editingFinished.connect(self.on_updates)
|
||||||
|
self.more_options_group.caching.valueChanged.connect(self.on_updates)
|
||||||
|
self.button_box.accepted.connect(self.accept)
|
||||||
|
self.button_box.rejected.connect(self.reject)
|
||||||
|
|
||||||
|
def retranslate_ui(self):
|
||||||
|
self.setWindowTitle(translate('MediaPlugin.StreamSelector', 'Insert Input Stream'))
|
||||||
|
if not self.theme_stream:
|
||||||
|
self.stream_name_label.setText(translate('MediaPlugin.StreamSelector', 'Stream name'))
|
||||||
|
self.net_mrl_label.setText(translate('MediaPlugin.StreamSelector', 'Network URL'))
|
||||||
|
|
||||||
|
def on_updates(self):
|
||||||
|
self.update_mrl_options(self.net_mrl_lineedit.text(), '')
|
@ -34,6 +34,7 @@ from PyQt5.QtMultimedia import QCameraInfo, QAudioDeviceInfo, QAudio
|
|||||||
|
|
||||||
from openlp.core.common import is_linux, is_macosx, is_win
|
from openlp.core.common import is_linux, is_macosx, is_win
|
||||||
from openlp.core.common.i18n import translate
|
from openlp.core.common.i18n import translate
|
||||||
|
from openlp.plugins.media.forms import VLCOptionsWidget
|
||||||
|
|
||||||
# Copied from VLC source code: modules/access/v4l2/v4l2.c
|
# Copied from VLC source code: modules/access/v4l2/v4l2.c
|
||||||
VIDEO_STANDARDS_VLC = [
|
VIDEO_STANDARDS_VLC = [
|
||||||
@ -651,25 +652,6 @@ class CaptureVideoDirectShowWidget(CaptureVideoQtDetectWidget):
|
|||||||
|
|
||||||
class Ui_StreamSelector(object):
|
class Ui_StreamSelector(object):
|
||||||
def setup_ui(self, stream_selector):
|
def setup_ui(self, stream_selector):
|
||||||
stream_selector.setObjectName('stream_selector')
|
|
||||||
self.combobox_size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
|
|
||||||
QtWidgets.QSizePolicy.Fixed)
|
|
||||||
stream_selector.setSizePolicy(
|
|
||||||
QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
|
|
||||||
self.main_layout = QtWidgets.QVBoxLayout(stream_selector)
|
|
||||||
self.main_layout.setObjectName('main_layout')
|
|
||||||
|
|
||||||
self.top_widget = QtWidgets.QWidget(stream_selector)
|
|
||||||
self.top_widget.setObjectName('top_widget')
|
|
||||||
self.top_layout = QtWidgets.QFormLayout(self.top_widget)
|
|
||||||
self.top_layout.setObjectName('top_layout')
|
|
||||||
# Stream name
|
|
||||||
if not self.theme_stream:
|
|
||||||
self.stream_name_label = QtWidgets.QLabel(self.top_widget)
|
|
||||||
self.stream_name_label.setObjectName('stream_name_label')
|
|
||||||
self.stream_name_edit = QtWidgets.QLineEdit(self.top_widget)
|
|
||||||
self.stream_name_edit.setObjectName('stream_name_edit')
|
|
||||||
self.top_layout.addRow(self.stream_name_label, self.stream_name_edit)
|
|
||||||
# Mode combobox
|
# Mode combobox
|
||||||
self.capture_mode_label = QtWidgets.QLabel(self.top_widget)
|
self.capture_mode_label = QtWidgets.QLabel(self.top_widget)
|
||||||
self.capture_mode_label.setObjectName('capture_mode_label')
|
self.capture_mode_label.setObjectName('capture_mode_label')
|
||||||
@ -719,33 +701,8 @@ class Ui_StreamSelector(object):
|
|||||||
for i in range(self.stacked_modes_layout.count()):
|
for i in range(self.stacked_modes_layout.count()):
|
||||||
self.stacked_modes_layout.widget(i).find_devices()
|
self.stacked_modes_layout.widget(i).find_devices()
|
||||||
self.stacked_modes_layout.widget(i).retranslate_ui()
|
self.stacked_modes_layout.widget(i).retranslate_ui()
|
||||||
# Groupbox for more options
|
# Add groupbox for VLC options
|
||||||
self.more_options_group = QtWidgets.QGroupBox(self)
|
self.more_options_group = VLCOptionsWidget(self)
|
||||||
self.more_options_group.setObjectName('more_options_group')
|
|
||||||
self.more_options_group_layout = QtWidgets.QFormLayout(self.more_options_group)
|
|
||||||
self.more_options_group_layout.setObjectName('more_options_group_layout')
|
|
||||||
# Caching spinbox
|
|
||||||
self.caching_label = QtWidgets.QLabel(self)
|
|
||||||
self.caching_label.setObjectName('caching_label')
|
|
||||||
self.caching = QtWidgets.QSpinBox(self)
|
|
||||||
self.caching.setAlignment(QtCore.Qt.AlignRight)
|
|
||||||
self.caching.setSuffix(' ms')
|
|
||||||
self.caching.setSingleStep(100)
|
|
||||||
self.caching.setMaximum(65535)
|
|
||||||
self.caching.setValue(300)
|
|
||||||
self.more_options_group_layout.addRow(self.caching_label, self.caching)
|
|
||||||
# MRL
|
|
||||||
self.mrl_label = QtWidgets.QLabel(self)
|
|
||||||
self.mrl_label.setObjectName('mrl_label')
|
|
||||||
self.mrl_lineedit = QtWidgets.QLineEdit(self)
|
|
||||||
self.mrl_lineedit.setObjectName('mrl_lineedit')
|
|
||||||
self.more_options_group_layout.addRow(self.mrl_label, self.mrl_lineedit)
|
|
||||||
# VLC options
|
|
||||||
self.vlc_options_label = QtWidgets.QLabel(self)
|
|
||||||
self.vlc_options_label.setObjectName('vlc_options_label')
|
|
||||||
self.vlc_options_lineedit = QtWidgets.QLineEdit(self)
|
|
||||||
self.vlc_options_lineedit.setObjectName('vlc_options_lineedit')
|
|
||||||
self.more_options_group_layout.addRow(self.vlc_options_label, self.vlc_options_lineedit)
|
|
||||||
# Add groupbox for more options to main layout
|
# Add groupbox for more options to main layout
|
||||||
self.main_layout.addWidget(self.more_options_group)
|
self.main_layout.addWidget(self.more_options_group)
|
||||||
# Save and close buttons
|
# Save and close buttons
|
||||||
@ -760,7 +717,7 @@ class Ui_StreamSelector(object):
|
|||||||
self.retranslate_ui(stream_selector)
|
self.retranslate_ui(stream_selector)
|
||||||
# connect
|
# connect
|
||||||
self.capture_mode_combo_box.currentIndexChanged.connect(stream_selector.on_capture_mode_combo_box)
|
self.capture_mode_combo_box.currentIndexChanged.connect(stream_selector.on_capture_mode_combo_box)
|
||||||
self.caching.valueChanged.connect(stream_selector.on_capture_mode_combo_box)
|
self.more_options_group.caching.valueChanged.connect(stream_selector.on_capture_mode_combo_box)
|
||||||
self.button_box.accepted.connect(stream_selector.accept)
|
self.button_box.accepted.connect(stream_selector.accept)
|
||||||
self.button_box.rejected.connect(stream_selector.reject)
|
self.button_box.rejected.connect(stream_selector.reject)
|
||||||
|
|
||||||
@ -769,7 +726,3 @@ class Ui_StreamSelector(object):
|
|||||||
if not self.theme_stream:
|
if not self.theme_stream:
|
||||||
self.stream_name_label.setText(translate('MediaPlugin.StreamSelector', 'Stream name'))
|
self.stream_name_label.setText(translate('MediaPlugin.StreamSelector', 'Stream name'))
|
||||||
self.capture_mode_label.setText(translate('MediaPlugin.StreamSelector', 'Capture Mode'))
|
self.capture_mode_label.setText(translate('MediaPlugin.StreamSelector', 'Capture Mode'))
|
||||||
self.more_options_group.setTitle(translate('MediaPlugin.StreamSelector', 'More options'))
|
|
||||||
self.caching_label.setText(translate('MediaPlugin.StreamSelector', 'Caching'))
|
|
||||||
self.mrl_label.setText(translate('MediaPlugin.StreamSelector', 'MRL'))
|
|
||||||
self.vlc_options_label.setText(translate('MediaPlugin.StreamSelector', 'VLC options'))
|
|
||||||
|
@ -20,18 +20,16 @@
|
|||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
from PyQt5 import QtCore, QtWidgets
|
|
||||||
|
|
||||||
from openlp.plugins.media.forms.streamselectordialog import Ui_StreamSelector
|
from openlp.plugins.media.forms.streamselectordialog import Ui_StreamSelector
|
||||||
from openlp.core.ui.media import parse_devicestream_path
|
from openlp.core.ui.media import parse_stream_path
|
||||||
from openlp.core.lib.ui import critical_error_message_box
|
from openlp.plugins.media.forms import StreamSelectorFormBase
|
||||||
from openlp.core.common.i18n import translate
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class StreamSelectorForm(QtWidgets.QDialog, Ui_StreamSelector):
|
class StreamSelectorForm(StreamSelectorFormBase, Ui_StreamSelector):
|
||||||
"""
|
"""
|
||||||
Class to manage the clip selection
|
Class to manage the clip selection
|
||||||
"""
|
"""
|
||||||
@ -41,53 +39,14 @@ class StreamSelectorForm(QtWidgets.QDialog, Ui_StreamSelector):
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(StreamSelectorForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint |
|
super(StreamSelectorForm, self).__init__(parent, callback, theme_stream)
|
||||||
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
|
self.type = 'devicestream'
|
||||||
self.callback = callback
|
|
||||||
self.theme_stream = theme_stream
|
|
||||||
self.setup_ui(self)
|
self.setup_ui(self)
|
||||||
# setup callbacks
|
# setup callbacks
|
||||||
for i in range(self.stacked_modes_layout.count()):
|
for i in range(self.stacked_modes_layout.count()):
|
||||||
self.stacked_modes_layout.widget(i).set_callback(self.update_mrl_options)
|
self.stacked_modes_layout.widget(i).set_callback(self.update_mrl_options)
|
||||||
self.stacked_modes_layout.currentWidget().update_mrl()
|
self.stacked_modes_layout.currentWidget().update_mrl()
|
||||||
|
|
||||||
def exec(self):
|
|
||||||
"""
|
|
||||||
Start dialog
|
|
||||||
"""
|
|
||||||
return QtWidgets.QDialog.exec(self)
|
|
||||||
|
|
||||||
def accept(self):
|
|
||||||
"""
|
|
||||||
Saves the current stream as a clip to the mediamanager
|
|
||||||
"""
|
|
||||||
log.debug('in StreamSelectorForm.accept')
|
|
||||||
if not self.theme_stream:
|
|
||||||
# Verify that a stream name exists
|
|
||||||
if not self.stream_name_edit.text().strip():
|
|
||||||
critical_error_message_box(message=translate('MediaPlugin.StreamSelector', 'A Stream name is needed.'))
|
|
||||||
return
|
|
||||||
stream_name = self.stream_name_edit.text().strip()
|
|
||||||
else:
|
|
||||||
stream_name = ' '
|
|
||||||
# Verify that a MRL exists
|
|
||||||
if not self.mrl_lineedit.text().strip():
|
|
||||||
critical_error_message_box(message=translate('MediaPlugin.StreamSelector', 'A MRL is needed.'), parent=self)
|
|
||||||
return
|
|
||||||
stream_string = 'devicestream:{name}&&{mrl}&&{options}'.format(name=stream_name,
|
|
||||||
mrl=self.mrl_lineedit.text().strip(),
|
|
||||||
options=self.vlc_options_lineedit.text().strip())
|
|
||||||
self.callback(stream_string)
|
|
||||||
return QtWidgets.QDialog.accept(self)
|
|
||||||
|
|
||||||
def update_mrl_options(self, mrl, options):
|
|
||||||
"""
|
|
||||||
Callback method used to fill the MRL and Options text fields
|
|
||||||
"""
|
|
||||||
options += ' :live-caching={cache}'.format(cache=self.caching.value())
|
|
||||||
self.mrl_lineedit.setText(mrl)
|
|
||||||
self.vlc_options_lineedit.setText(options)
|
|
||||||
|
|
||||||
def on_capture_mode_combo_box(self):
|
def on_capture_mode_combo_box(self):
|
||||||
self.stacked_modes_layout.setCurrentIndex(self.capture_mode_combo_box.currentIndex())
|
self.stacked_modes_layout.setCurrentIndex(self.capture_mode_combo_box.currentIndex())
|
||||||
self.stacked_modes_layout.currentWidget().update_mrl()
|
self.stacked_modes_layout.currentWidget().update_mrl()
|
||||||
@ -97,12 +56,14 @@ class StreamSelectorForm(QtWidgets.QDialog, Ui_StreamSelector):
|
|||||||
Setup the stream widgets based on the saved devicestream. This is best effort as the string is
|
Setup the stream widgets based on the saved devicestream. This is best effort as the string is
|
||||||
editable for the user.
|
editable for the user.
|
||||||
"""
|
"""
|
||||||
(name, mrl, options) = parse_devicestream_path(device_stream_str)
|
(name, mrl, options) = parse_stream_path(device_stream_str)
|
||||||
for i in range(self.stacked_modes_layout.count()):
|
for i in range(self.stacked_modes_layout.count()):
|
||||||
if self.stacked_modes_layout.widget(i).has_support_for_mrl(mrl, options):
|
if self.stacked_modes_layout.widget(i).has_support_for_mrl(mrl, options):
|
||||||
self.stacked_modes_layout.setCurrentIndex(i)
|
self.stacked_modes_layout.setCurrentIndex(i)
|
||||||
self.stacked_modes_layout.widget(i).set_mrl(mrl, options)
|
self.stacked_modes_layout.widget(i).set_mrl(mrl, options)
|
||||||
break
|
break
|
||||||
|
cache = re.search(r'live-caching=(\d+)', options)
|
||||||
self.mrl_lineedit.setText(mrl)
|
if cache:
|
||||||
self.vlc_options_lineedit.setText(options)
|
self.more_options_group.caching.setValue(int(cache.group(1)))
|
||||||
|
self.more_options_group.mrl_lineedit.setText(mrl)
|
||||||
|
self.more_options_group.vlc_options_lineedit.setText(options)
|
||||||
|
@ -35,12 +35,13 @@ from openlp.core.lib.serviceitem import ItemCapabilities
|
|||||||
from openlp.core.lib.ui import critical_error_message_box
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
from openlp.core.state import State
|
from openlp.core.state import State
|
||||||
from openlp.core.ui.icons import UiIcons
|
from openlp.core.ui.icons import UiIcons
|
||||||
from openlp.core.ui.media import parse_optical_path, parse_devicestream_path, format_milliseconds, AUDIO_EXT, VIDEO_EXT
|
from openlp.core.ui.media import parse_optical_path, parse_stream_path, format_milliseconds, AUDIO_EXT, VIDEO_EXT
|
||||||
from openlp.core.ui.media.vlcplayer import get_vlc
|
from openlp.core.ui.media.vlcplayer import get_vlc
|
||||||
|
|
||||||
if get_vlc() is not None:
|
if get_vlc() is not None:
|
||||||
from openlp.plugins.media.forms.mediaclipselectorform import MediaClipSelectorForm
|
from openlp.plugins.media.forms.mediaclipselectorform import MediaClipSelectorForm
|
||||||
from openlp.plugins.media.forms.streamselectorform import StreamSelectorForm
|
from openlp.plugins.media.forms.streamselectorform import StreamSelectorForm
|
||||||
|
from openlp.plugins.media.forms.networkstreamselectorform import NetworkStreamSelectorForm
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -129,6 +130,13 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||||||
text=device_stream_button_text,
|
text=device_stream_button_text,
|
||||||
tooltip=device_stream_button_tooltip,
|
tooltip=device_stream_button_tooltip,
|
||||||
triggers=self.on_open_device_stream)
|
triggers=self.on_open_device_stream)
|
||||||
|
network_stream_button_text = translate('MediaPlugin.MediaItem', 'Open network stream')
|
||||||
|
network_stream_button_tooltip = translate('MediaPlugin.MediaItem', 'Open network stream')
|
||||||
|
self.open_network_stream = self.toolbar.add_toolbar_action('open_network_stream',
|
||||||
|
icon=UiIcons().network_stream,
|
||||||
|
text=network_stream_button_text,
|
||||||
|
tooltip=network_stream_button_tooltip,
|
||||||
|
triggers=self.on_open_network_stream)
|
||||||
|
|
||||||
def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
|
def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
@ -165,9 +173,9 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||||||
service_item.start_time = start
|
service_item.start_time = start
|
||||||
service_item.end_time = end
|
service_item.end_time = end
|
||||||
service_item.add_capability(ItemCapabilities.IsOptical)
|
service_item.add_capability(ItemCapabilities.IsOptical)
|
||||||
elif filename.startswith('devicestream:'):
|
elif filename.startswith('devicestream:') or filename.startswith('networkstream:'):
|
||||||
# Special handling if the filename is a devicestream
|
# Special handling if the filename is a devicestream
|
||||||
(name, mrl, options) = parse_devicestream_path(filename)
|
(name, mrl, options) = parse_stream_path(filename)
|
||||||
service_item.processor = 'vlc'
|
service_item.processor = 'vlc'
|
||||||
service_item.add_from_command(filename, name, self.clapperboard)
|
service_item.add_from_command(filename, name, self.clapperboard)
|
||||||
service_item.title = name
|
service_item.title = name
|
||||||
@ -249,10 +257,13 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||||||
item_name.setToolTip('{name}@{start}-{end}'.format(name=file_name,
|
item_name.setToolTip('{name}@{start}-{end}'.format(name=file_name,
|
||||||
start=format_milliseconds(start),
|
start=format_milliseconds(start),
|
||||||
end=format_milliseconds(end)))
|
end=format_milliseconds(end)))
|
||||||
elif track_str.startswith('devicestream:'):
|
elif track_str.startswith('devicestream:') or track_str.startswith('networkstream:'):
|
||||||
(name, mrl, options) = parse_devicestream_path(track_str)
|
(name, mrl, options) = parse_stream_path(track_str)
|
||||||
item_name = QtWidgets.QListWidgetItem(name)
|
item_name = QtWidgets.QListWidgetItem(name)
|
||||||
item_name.setIcon(UiIcons().device_stream)
|
if track_str.startswith('devicestream:'):
|
||||||
|
item_name.setIcon(UiIcons().device_stream)
|
||||||
|
else:
|
||||||
|
item_name.setIcon(UiIcons().network_stream)
|
||||||
item_name.setData(QtCore.Qt.UserRole, track)
|
item_name.setData(QtCore.Qt.UserRole, track)
|
||||||
item_name.setToolTip(mrl)
|
item_name.setToolTip(mrl)
|
||||||
elif not os.path.exists(track):
|
elif not os.path.exists(track):
|
||||||
@ -366,3 +377,32 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||||||
file_paths.append(stream)
|
file_paths.append(stream)
|
||||||
self.load_list([str(stream)])
|
self.load_list([str(stream)])
|
||||||
self.settings.setValue(self.settings_section + '/media files', file_paths)
|
self.settings.setValue(self.settings_section + '/media files', file_paths)
|
||||||
|
|
||||||
|
def on_open_network_stream(self):
|
||||||
|
"""
|
||||||
|
When the open network stream button is clicked, open the stream selector window.
|
||||||
|
"""
|
||||||
|
if get_vlc():
|
||||||
|
stream_selector_form = NetworkStreamSelectorForm(self.main_window, self.add_network_stream)
|
||||||
|
stream_selector_form.exec()
|
||||||
|
del stream_selector_form
|
||||||
|
else:
|
||||||
|
critical_error_message_box(translate('MediaPlugin.MediaItem', 'VLC is not available'),
|
||||||
|
translate('MediaPlugin.MediaItem', 'Network streaming support requires VLC.'))
|
||||||
|
|
||||||
|
def add_network_stream(self, stream):
|
||||||
|
"""
|
||||||
|
Add a network stream based clip to the mediamanager, called from stream_selector_form.
|
||||||
|
|
||||||
|
:param stream: The clip to add.
|
||||||
|
"""
|
||||||
|
file_paths = self.get_file_list()
|
||||||
|
# If the clip already is in the media list it isn't added and an error message is displayed.
|
||||||
|
if stream in file_paths:
|
||||||
|
critical_error_message_box(translate('MediaPlugin.MediaItem', 'Stream already saved'),
|
||||||
|
translate('MediaPlugin.MediaItem', 'This stream has already been saved'))
|
||||||
|
return
|
||||||
|
# Append the device stream string to the media list
|
||||||
|
file_paths.append(stream)
|
||||||
|
self.load_list([str(stream)])
|
||||||
|
self.settings.setValue(self.settings_section + '/media files', file_paths)
|
||||||
|
Loading…
Reference in New Issue
Block a user