Merge trunk updates

This commit is contained in:
john 2019-06-27 13:10:34 +01:00
commit 703eb0079b
18 changed files with 194 additions and 94 deletions

View File

@ -111,6 +111,8 @@ class OpenLPJSONDecoder(JSONDecoder):
:param dict obj: A decoded JSON object :param dict obj: A decoded JSON object
:return: The custom object from the serialized data if the custom object is registered, else obj :return: The custom object from the serialized data if the custom object is registered, else obj
""" """
if '__Path__' in obj:
return PathSerializer.encode_json(obj, **self.kwargs)
try: try:
key = obj['json_meta']['class'] key = obj['json_meta']['class']
except KeyError: except KeyError:
@ -150,8 +152,8 @@ class OpenLPJSONEncoder(JSONEncoder):
if isinstance(obj, JSONMixin): if isinstance(obj, JSONMixin):
return obj.json_object() return obj.json_object()
elif obj.__class__.__name__ in _registered_classes: elif obj.__class__.__name__ in _registered_classes:
return _registered_classes[obj.__class__.__name__].json_object(obj) return _registered_classes[obj.__class__.__name__].json_object(obj, **self.kwargs)
return super().default(obj) return super().default(obj, **self.kwargs)
def is_serializable(obj): def is_serializable(obj):
@ -174,17 +176,22 @@ class PathSerializer(JSONMixin, register_names=('Path', 'PosixPath', 'WindowsPat
:param kwargs: Contains any extra parameters. Not used! :param kwargs: Contains any extra parameters. Not used!
:return Path: The deserialized Path object :return Path: The deserialized Path object
""" """
path = Path(*obj['parts']) if '__Path__' in obj:
parts = obj['__Path__']
else:
parts = obj['parts']
path = Path(*parts)
if base_path and not path.is_absolute(): if base_path and not path.is_absolute():
return base_path / path return base_path / path
return path return path
@classmethod @classmethod
def json_object(cls, obj, base_path=None, **kwargs): def json_object(cls, obj, base_path=None, is_js=False, **kwargs):
""" """
Create a dictionary that can be JSON decoded. Create a dictionary that can be JSON decoded.
:param Path base_path: If specified, an absolute path to make a relative path from. :param Path base_path: If specified, an absolute path to make a relative path from.
:param bool is_js: Encode the path as a uri. For example for use in the js rendering code.
:param kwargs: Contains any extra parameters. Not used! :param kwargs: Contains any extra parameters. Not used!
:return: The dictionary representation of this Path object. :return: The dictionary representation of this Path object.
:rtype: dict[tuple] :rtype: dict[tuple]
@ -193,6 +200,8 @@ class PathSerializer(JSONMixin, register_names=('Path', 'PosixPath', 'WindowsPat
if base_path: if base_path:
with suppress(ValueError): with suppress(ValueError):
path = path.relative_to(base_path) path = path.relative_to(base_path)
if is_js is True:
return path.as_uri()
json_dict = {'parts': path.parts} json_dict = {'parts': path.parts}
cls.attach_meta(json_dict) cls.attach_meta(json_dict)
return json_dict return json_dict

View File

@ -210,6 +210,8 @@ class Settings(QtCore.QSettings):
'media/media auto start': QtCore.Qt.Unchecked, 'media/media auto start': QtCore.Qt.Unchecked,
'media/stream command': '', 'media/stream command': '',
'media/vlc arguments': '', 'media/vlc arguments': '',
'media/video': '',
'media/audio': '',
'remotes/download version': '0.0', 'remotes/download version': '0.0',
'players/background color': '#000000', 'players/background color': '#000000',
'servicemanager/last directory': None, 'servicemanager/last directory': None,
@ -610,7 +612,7 @@ class Settings(QtCore.QSettings):
elif isinstance(default_value, dict): elif isinstance(default_value, dict):
return {} return {}
elif isinstance(setting, str): elif isinstance(setting, str):
if 'json_meta' in setting or setting.startswith('{'): if 'json_meta' in setting or '__Path__' in setting or setting.startswith('{'):
return json.loads(setting, cls=OpenLPJSONDecoder) return json.loads(setting, cls=OpenLPJSONDecoder)
# Convert the setting to the correct type. # Convert the setting to the correct type.
if isinstance(default_value, bool): if isinstance(default_value, bool):

View File

@ -117,20 +117,6 @@ function _prepareText(text) {
return "<p>" + _nl2br(text) + "</p>"; return "<p>" + _nl2br(text) + "</p>";
} }
/**
* The paths we get are JSON versions of Python Path objects, so let's just fix that.
* @private
* @param {object} path - The Path object
* @returns {string} The actual file path
*/
function _pathToString(path) {
var filename = path.__Path__.join("/").replace("//", "/");
if (!filename.startsWith("/")) {
filename = "/" + filename;
}
return filename;
}
/** /**
* An audio player with a play list * An audio player with a play list
*/ */
@ -676,13 +662,13 @@ var Display = {
} }
break; break;
case BackgroundType.Image: case BackgroundType.Image:
background_filename = _pathToString(theme.background_filename); backgroundStyle["background-image"] = "url('" + theme.background_filename + "')";
backgroundStyle["background-image"] = "url('file://" + background_filename + "')"; console.warn(backgroundStyle["background-image"]);
break; break;
case BackgroundType.Video: case BackgroundType.Video:
background_filename = _pathToString(theme.background_filename);
backgroundStyle["background-color"] = theme.background_border_color; backgroundStyle["background-color"] = theme.background_border_color;
backgroundHtml = "<video loop autoplay muted><source src='file://" + background_filename + "'></video>"; backgroundHtml = "<video loop autoplay muted><source src='" + theme.background_filename + "'></video>";
console.warn(backgroundHtml);
break; break;
default: default:
backgroundStyle["background"] = "#000"; backgroundStyle["background"] = "#000";

View File

@ -332,9 +332,9 @@ class DisplayWindow(QtWidgets.QWidget):
theme_copy = copy.deepcopy(theme) theme_copy = copy.deepcopy(theme)
theme_copy.background_type = 'image' theme_copy.background_type = 'image'
theme_copy.background_filename = self.checkerboard_path theme_copy.background_filename = self.checkerboard_path
exported_theme = theme_copy.export_theme() exported_theme = theme_copy.export_theme(is_js=True)
else: else:
exported_theme = theme.export_theme() exported_theme = theme.export_theme(is_js=True)
self.run_javascript('Display.setTheme({theme});'.format(theme=exported_theme)) self.run_javascript('Display.setTheme({theme});'.format(theme=exported_theme))
def get_video_types(self): def get_video_types(self):

View File

@ -173,6 +173,7 @@ class ItemCapabilities(object):
HasNotes = 20 HasNotes = 20
HasThumbnails = 21 HasThumbnails = 21
HasMetaData = 22 HasMetaData = 22
CanStream = 23
def get_text_file_string(text_file_path): def get_text_file_string(text_file_path):

View File

@ -225,17 +225,18 @@ class Theme(object):
jsn = json.loads(theme, cls=OpenLPJSONDecoder) jsn = json.loads(theme, cls=OpenLPJSONDecoder)
self.expand_json(jsn) self.expand_json(jsn)
def export_theme(self, theme_path=None): def export_theme(self, theme_path=None, is_js=False):
""" """
Loop through the fields and build a dictionary of them Loop through the fields and build a dictionary of them
:param pathlib.Path | None theme_path:
:param bool is_js: For internal use, for example with the theme js code.
:return str: The json encoded theme object
""" """
theme_data = {} theme_data = {}
for attr, value in self.__dict__.items(): for attr, value in self.__dict__.items():
theme_data["{attr}".format(attr=attr)] = value theme_data["{attr}".format(attr=attr)] = value
if theme_path: return json.dumps(theme_data, cls=OpenLPJSONEncoder, base_path=theme_path, is_js=is_js)
return json.dumps(theme_data, cls=OpenLPJSONEncoder, base_path=theme_path)
return json.dumps(theme_data, cls=OpenLPJSONEncoder)
def parse(self, xml): def parse(self, xml):
""" """

View File

@ -26,6 +26,19 @@ import logging
log = logging.getLogger(__name__ + '.__init__') log = logging.getLogger(__name__ + '.__init__')
# Audio and video extensions copied from 'include/vlc_interface.h' from vlc 2.2.0 source
AUDIO_EXT = ['*.3ga', '*.669', '*.a52', '*.aac', '*.ac3', '*.adt', '*.adts', '*.aif', '*.aifc', '*.aiff', '*.amr',
'*.aob', '*.ape', '*.awb', '*.caf', '*.dts', '*.flac', '*.it', '*.kar', '*.m4a', '*.m4b', '*.m4p', '*.m5p',
'*.mid', '*.mka', '*.mlp', '*.mod', '*.mpa', '*.mp1', '*.mp2', '*.mp3', '*.mpc', '*.mpga', '*.mus',
'*.oga', '*.ogg', '*.oma', '*.opus', '*.qcp', '*.ra', '*.rmi', '*.s3m', '*.sid', '*.spx', '*.thd', '*.tta',
'*.voc', '*.vqf', '*.w64', '*.wav', '*.wma', '*.wv', '*.xa', '*.xm']
VIDEO_EXT = ['*.3g2', '*.3gp', '*.3gp2', '*.3gpp', '*.amv', '*.asf', '*.avi', '*.bik', '*.divx', '*.drc', '*.dv',
'*.f4v', '*.flv', '*.gvi', '*.gxf', '*.iso', '*.m1v', '*.m2v', '*.m2t', '*.m2ts', '*.m4v', '*.mkv',
'*.mov', '*.mp2', '*.mp2v', '*.mp4', '*.mp4v', '*.mpe', '*.mpeg', '*.mpeg1', '*.mpeg2', '*.mpeg4', '*.mpg',
'*.mpv2', '*.mts', '*.mtv', '*.mxf', '*.mxg', '*.nsv', '*.nuv', '*.ogg', '*.ogm', '*.ogv', '*.ogx', '*.ps',
'*.rec', '*.rm', '*.rmvb', '*.rpl', '*.thp', '*.tod', '*.ts', '*.tts', '*.txd', '*.vob', '*.vro', '*.webm',
'*.wm', '*.wmv', '*.wtv', '*.xesc', '*.nut', '*.rv', '*.xvid']
class MediaState(object): class MediaState(object):
""" """

View File

@ -42,9 +42,9 @@ from openlp.core.common.settings import Settings
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 from openlp.core.ui import DisplayControllerType
from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, VIDEO_EXT, AUDIO_EXT
from openlp.core.ui.media.endpoint import media_endpoint from openlp.core.ui.media.endpoint import media_endpoint
from openlp.core.ui.media.vlcplayer import AUDIO_EXT, VIDEO_EXT, VlcPlayer, get_vlc from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -184,7 +184,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
display.has_audio = False display.has_audio = False
self.vlc_player.setup(display, preview) self.vlc_player.setup(display, preview)
def set_controls_visible(self, controller, value): @staticmethod
def set_controls_visible(controller, value):
""" """
After a new display is configured, all media related widget will be created too After a new display is configured, all media related widget will be created too
@ -229,7 +230,10 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
display = self._define_display(controller) display = self._define_display(controller)
if controller.is_live: if controller.is_live:
# if this is an optical device use special handling # if this is an optical device use special handling
if service_item.is_capable(ItemCapabilities.IsOptical): if service_item.is_capable(ItemCapabilities.CanStream):
is_valid = self._check_file_type(controller, display, True)
controller.media_info.media_type = MediaType.Stream
elif service_item.is_capable(ItemCapabilities.IsOptical):
log.debug('video is optical and live') log.debug('video is optical and live')
path = service_item.get_frame_path() path = service_item.get_frame_path()
(name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(path) (name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(path)
@ -249,7 +253,10 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.media_info.start_time = service_item.start_time controller.media_info.start_time = service_item.start_time
controller.media_info.end_time = service_item.end_time controller.media_info.end_time = service_item.end_time
elif controller.preview_display: elif controller.preview_display:
if service_item.is_capable(ItemCapabilities.IsOptical): if service_item.is_capable(ItemCapabilities.CanStream):
controller.media_info.media_type = MediaType.Stream
is_valid = self._check_file_type(controller, display, True)
elif service_item.is_capable(ItemCapabilities.IsOptical):
log.debug('video is optical and preview') log.debug('video is optical and preview')
path = service_item.get_frame_path() path = service_item.get_frame_path()
(name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(path) (name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(path)
@ -270,6 +277,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
# display.frame.runJavaScript('show_video("setBackBoard", null, null,"visible");') # display.frame.runJavaScript('show_video("setBackBoard", null, null,"visible");')
# now start playing - Preview is autoplay! # now start playing - Preview is autoplay!
autoplay = False autoplay = False
if service_item.is_capable(ItemCapabilities.CanStream):
autoplay = True
# Preview requested # Preview requested
if not controller.is_live: if not controller.is_live:
autoplay = True autoplay = True
@ -346,13 +355,21 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.media_info.media_type = MediaType.DVD controller.media_info.media_type = MediaType.DVD
return True return True
def _check_file_type(self, controller, display): def _check_file_type(self, controller, display, stream=False):
""" """
Select the correct media Player type from the prioritized Player list Select the correct media Player type from the prioritized Player list
:param controller: First element is the controller which should be used :param controller: First element is the controller which should be used
:param display: Which display to use :param display: Which display to use
:param stream: Are we streaming or not
""" """
if stream:
self.resize(display, self.vlc_player)
display.media_info.media_type = MediaType.Stream
if self.vlc_player.load(display, None):
self.current_media_players[controller.controller_type] = self.vlc_player
return True
return True
for file in controller.media_info.file_info: for file in controller.media_info.file_info:
if file.is_file: if file.is_file:
suffix = '*%s' % file.suffix.lower() suffix = '*%s' % file.suffix.lower()

View File

@ -33,7 +33,7 @@ from openlp.core.common.settings import Settings
from openlp.core.lib.settingstab import SettingsTab from openlp.core.lib.settingstab import SettingsTab
from openlp.core.ui.icons import UiIcons from openlp.core.ui.icons import UiIcons
LINUX_STREAM = 'v4l2://{video} :v4l2-standard= :input-slave={audio} :live-caching=300' LINUX_STREAM = 'v4l2://{video}:v4l2-standard= :input-slave=alsa://{audio} :live-caching=300'
WIN_STREAM = 'dshow://:dshow-vdev={video} :dshow-adev={audio} :live-caching=300' WIN_STREAM = 'dshow://:dshow-vdev={video} :dshow-adev={audio} :live-caching=300'
OSX_STREAM = 'avcapture://{video}:qtsound://{audio} :live-caching=300' OSX_STREAM = 'avcapture://{video}:qtsound://{audio} :live-caching=300'
@ -68,11 +68,15 @@ class MediaTab(SettingsTab):
self.left_layout.addWidget(self.live_media_group_box) self.left_layout.addWidget(self.live_media_group_box)
self.stream_media_group_box = QtWidgets.QGroupBox(self.left_column) self.stream_media_group_box = QtWidgets.QGroupBox(self.left_column)
self.stream_media_group_box.setObjectName('stream_media_group_box') self.stream_media_group_box.setObjectName('stream_media_group_box')
self.stream_media_layout = QtWidgets.QHBoxLayout(self.stream_media_group_box) self.stream_media_layout = QtWidgets.QFormLayout(self.stream_media_group_box)
self.stream_media_layout.setObjectName('stream_media_layout') self.stream_media_layout.setObjectName('stream_media_layout')
self.stream_media_layout.setContentsMargins(0, 0, 0, 0) self.stream_media_layout.setContentsMargins(0, 0, 0, 0)
self.stream_edit = QtWidgets.QLabel(self) self.video_edit = QtWidgets.QLineEdit(self)
self.stream_media_layout.addWidget(self.stream_edit) self.stream_media_layout.addRow(translate('MediaPlugin.MediaTab', 'Video:'), self.video_edit)
self.audio_edit = QtWidgets.QLineEdit(self)
self.stream_media_layout.addRow(translate('MediaPlugin.MediaTab', 'Audio:'), self.audio_edit)
self.stream_cmd = QtWidgets.QLabel(self)
self.stream_media_layout.addWidget(self.stream_cmd)
self.left_layout.addWidget(self.stream_media_group_box) self.left_layout.addWidget(self.stream_media_group_box)
self.vlc_arguments_group_box = QtWidgets.QGroupBox(self.left_column) self.vlc_arguments_group_box = QtWidgets.QGroupBox(self.left_column)
self.vlc_arguments_group_box.setObjectName('vlc_arguments_group_box') self.vlc_arguments_group_box.setObjectName('vlc_arguments_group_box')
@ -84,7 +88,6 @@ class MediaTab(SettingsTab):
self.left_layout.addWidget(self.vlc_arguments_group_box) self.left_layout.addWidget(self.vlc_arguments_group_box)
self.left_layout.addStretch() self.left_layout.addStretch()
self.right_layout.addStretch() self.right_layout.addStretch()
# # Signals and slots
def retranslate_ui(self): def retranslate_ui(self):
""" """
@ -100,22 +103,28 @@ class MediaTab(SettingsTab):
Load the settings Load the settings
""" """
self.auto_start_check_box.setChecked(Settings().value(self.settings_section + '/media auto start')) self.auto_start_check_box.setChecked(Settings().value(self.settings_section + '/media auto start'))
self.stream_edit.setText(Settings().value(self.settings_section + '/stream command')) self.stream_cmd.setText(Settings().value(self.settings_section + '/stream command'))
if not self.stream_edit.text(): self.audio_edit.setText(Settings().value(self.settings_section + '/audio'))
if is_linux: self.video_edit.setText(Settings().value(self.settings_section + '/video'))
self.stream_edit.setText(LINUX_STREAM) if not self.stream_cmd.text():
elif is_win: self.set_base_stream()
self.stream_edit.setText(WIN_STREAM)
else:
self.stream_edit.setText(OSX_STREAM)
self.vlc_arguments_edit.setPlainText(Settings().value(self.settings_section + '/vlc arguments')) self.vlc_arguments_edit.setPlainText(Settings().value(self.settings_section + '/vlc arguments'))
if Settings().value('advanced/experimental'): if Settings().value('advanced/experimental'):
# vlc.MediaPlayer().audio_output_device_enum()
for cam in QCameraInfo.availableCameras(): for cam in QCameraInfo.availableCameras():
log.debug(cam.deviceName()) log.debug(cam.deviceName())
log.debug(cam.description()) log.debug(cam.description())
for au in QAudioDeviceInfo.availableDevices(QAudio.AudioInput): for au in QAudioDeviceInfo.availableDevices(QAudio.AudioInput):
log.debug(au.deviceName()) log.debug(au.deviceName())
def set_base_stream(self):
if is_linux:
self.stream_cmd.setText(LINUX_STREAM)
elif is_win:
self.stream_cmd.setText(WIN_STREAM)
else:
self.stream_cmd.setText(OSX_STREAM)
def save(self): def save(self):
""" """
Save the settings Save the settings
@ -123,8 +132,12 @@ class MediaTab(SettingsTab):
setting_key = self.settings_section + '/media auto start' setting_key = self.settings_section + '/media auto start'
if Settings().value(setting_key) != self.auto_start_check_box.checkState(): if Settings().value(setting_key) != self.auto_start_check_box.checkState():
Settings().setValue(setting_key, self.auto_start_check_box.checkState()) Settings().setValue(setting_key, self.auto_start_check_box.checkState())
Settings().setValue(self.settings_section + '/stream command', self.stream_edit.text()) Settings().setValue(self.settings_section + '/stream command', self.stream_cmd.text())
Settings().setValue(self.settings_section + '/vlc arguments', self.vlc_arguments_edit.toPlainText()) Settings().setValue(self.settings_section + '/vlc arguments', self.vlc_arguments_edit.toPlainText())
Settings().setValue(self.settings_section + '/video', self.video_edit.text())
Settings().setValue(self.settings_section + '/audio', self.audio_edit.text())
self.stream_cmd.setText(self.stream_cmd.text().format(video=self.video_edit.text(),
audio=self.audio_edit.text()))
def post_set_up(self, post_update=False): def post_set_up(self, post_update=False):
""" """

View File

@ -40,18 +40,6 @@ from openlp.core.ui.media.mediaplayer import MediaPlayer
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# Audio and video extensions copied from 'include/vlc_interface.h' from vlc 2.2.0 source # Audio and video extensions copied from 'include/vlc_interface.h' from vlc 2.2.0 source
AUDIO_EXT = ('3ga', '669', 'a52', 'aac', 'ac3', 'adt', 'adts', 'aif', 'aifc', 'aiff', 'amr', 'aob', 'ape', 'awb', 'caf',
'dts', 'flac', 'it', 'kar', 'm4a', 'm4b', 'm4p', 'm5p', 'mid', 'mka', 'mlp', 'mod', 'mpa', 'mp1', 'mp2',
'mp3', 'mpc', 'mpga', 'mus', 'oga', 'ogg', 'oma', 'opus', 'qcp', 'ra', 'rmi', 's3m', 'sid', 'spx', 'thd',
'tta', 'voc', 'vqf', 'w64', 'wav', 'wma', 'wv', 'xa', 'xm')
VIDEO_EXT = ('3g2', '3gp', '3gp2', '3gpp', 'amv', 'asf', 'avi', 'bik', 'divx', 'drc', 'dv', 'f4v', 'flv', 'gvi', 'gxf',
'iso', 'm1v', 'm2v', 'm2t', 'm2ts', 'm4v', 'mkv', 'mov', 'mp2', 'mp2v', 'mp4', 'mp4v', 'mpe', 'mpeg',
'mpeg1', 'mpeg2', 'mpeg4', 'mpg', 'mpv2', 'mts', 'mtv', 'mxf', 'mxg', 'nsv', 'nuv', 'ogg', 'ogm', 'ogv',
'ogx', 'ps', 'rec', 'rm', 'rmvb', 'rpl', 'thp', 'tod', 'ts', 'tts', 'txd', 'vob', 'vro', 'webm', 'wm',
'wmv', 'wtv', 'xesc',
# These extensions was not in the official list, added manually.
'nut', 'rv', 'xvid')
def get_vlc(): def get_vlc():
@ -159,16 +147,15 @@ class VlcPlayer(MediaPlayer):
Load a video into VLC Load a video into VLC
:param output_display: The display where the media is :param output_display: The display where the media is
:param file: file to be played :param file: file to be played or None for live streaming
:return: :return:
""" """
vlc = get_vlc() vlc = get_vlc()
log.debug('load vid in Vlc Controller') log.debug('load vid in Vlc Controller')
controller = output_display if file:
volume = controller.media_info.volume
path = os.path.normcase(file) path = os.path.normcase(file)
# create the media # create the media
if controller.media_info.media_type == MediaType.CD: if output_display.media_info.media_type == MediaType.CD:
if is_win(): if is_win():
path = '/' + path path = '/' + path
output_display.vlc_media = output_display.vlc_instance.media_new_location('cdda://' + path) output_display.vlc_media = output_display.vlc_instance.media_new_location('cdda://' + path)
@ -180,8 +167,8 @@ class VlcPlayer(MediaPlayer):
audio_cd_tracks = output_display.vlc_media.subitems() audio_cd_tracks = output_display.vlc_media.subitems()
if not audio_cd_tracks or audio_cd_tracks.count() < 1: if not audio_cd_tracks or audio_cd_tracks.count() < 1:
return False return False
output_display.vlc_media = audio_cd_tracks.item_at_index(controller.media_info.title_track) output_display.vlc_media = audio_cd_tracks.item_at_index(output_display.media_info.title_track)
elif controller.media_info.media_type == MediaType.Stream: elif output_display.media_info.media_type == MediaType.Stream:
stream_cmd = Settings().value('media/stream command') stream_cmd = Settings().value('media/stream command')
output_display.vlc_media = output_display.vlc_instance.media_new_location(stream_cmd) output_display.vlc_media = output_display.vlc_instance.media_new_location(stream_cmd)
else: else:
@ -190,7 +177,7 @@ class VlcPlayer(MediaPlayer):
output_display.vlc_media_player.set_media(output_display.vlc_media) output_display.vlc_media_player.set_media(output_display.vlc_media)
# parse the metadata of the file # parse the metadata of the file
output_display.vlc_media.parse() output_display.vlc_media.parse()
self.volume(output_display, volume) self.volume(output_display, output_display.media_info.volume)
return True return True
def media_state_wait(self, output_display, media_state): def media_state_wait(self, output_display, media_state):

View File

@ -48,7 +48,7 @@ from openlp.core.lib.plugin import PluginStatus
from openlp.core.lib.serviceitem import ItemCapabilities, ServiceItem from openlp.core.lib.serviceitem import ItemCapabilities, ServiceItem
from openlp.core.lib.ui import create_widget_action, critical_error_message_box, find_and_set_in_combo_box from openlp.core.lib.ui import create_widget_action, critical_error_message_box, find_and_set_in_combo_box
from openlp.core.ui.icons import UiIcons from openlp.core.ui.icons import UiIcons
from openlp.core.ui.media.vlcplayer import AUDIO_EXT, VIDEO_EXT from openlp.core.ui.media import AUDIO_EXT, VIDEO_EXT
from openlp.core.ui.serviceitemeditform import ServiceItemEditForm from openlp.core.ui.serviceitemeditform import ServiceItemEditForm
from openlp.core.ui.servicenoteform import ServiceNoteForm from openlp.core.ui.servicenoteform import ServiceNoteForm
from openlp.core.ui.starttimeform import StartTimeForm from openlp.core.ui.starttimeform import StartTimeForm

View File

@ -32,7 +32,7 @@ from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.lib.theme import BackgroundGradientType, BackgroundType from openlp.core.lib.theme import BackgroundGradientType, BackgroundType
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.media.vlcplayer import VIDEO_EXT from openlp.core.ui.media import VIDEO_EXT
from openlp.core.ui.themelayoutform import ThemeLayoutForm from openlp.core.ui.themelayoutform import ThemeLayoutForm
from openlp.core.ui.themewizard import Ui_ThemeWizard from openlp.core.ui.themewizard import Ui_ThemeWizard

View File

@ -103,8 +103,8 @@ class ProxyWidget(QtWidgets.QGroupBox):
:param QtWidgets.QRadioButton button: The button that has toggled :param QtWidgets.QRadioButton button: The button that has toggled
:param bool checked: The buttons new state :param bool checked: The buttons new state
""" """
id = self.radio_group.id(button) # The work around (see above comment) group_id = self.radio_group.id(button) # The work around (see above comment)
enable_manual_edits = id == ProxyMode.MANUAL_PROXY and checked enable_manual_edits = group_id == ProxyMode.MANUAL_PROXY and checked
self.http_edit.setEnabled(enable_manual_edits) self.http_edit.setEnabled(enable_manual_edits)
self.https_edit.setEnabled(enable_manual_edits) self.https_edit.setEnabled(enable_manual_edits)
self.username_edit.setEnabled(enable_manual_edits) self.username_edit.setEnabled(enable_manual_edits)

View File

@ -37,8 +37,8 @@ from openlp.core.lib.mediamanageritem import MediaManagerItem
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.icons import UiIcons from openlp.core.ui.icons import UiIcons
from openlp.core.ui.media import parse_optical_path, format_milliseconds from openlp.core.ui.media import parse_optical_path, format_milliseconds, AUDIO_EXT, VIDEO_EXT
from openlp.core.ui.media.vlcplayer import AUDIO_EXT, VIDEO_EXT, get_vlc from openlp.core.ui.media.vlcplayer import get_vlc
if get_vlc() is not None: if get_vlc() is not None:
@ -175,7 +175,11 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
return False return False
filename = str(item.data(QtCore.Qt.UserRole)) filename = str(item.data(QtCore.Qt.UserRole))
# Special handling if the filename is a optical clip # Special handling if the filename is a optical clip
if filename.startswith('optical:'): if filename == 'live':
service_item.processor = 'vlc'
service_item.title = filename
service_item.add_capability(ItemCapabilities.CanStream)
elif filename.startswith('optical:'):
(name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(filename) (name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(filename)
if not os.path.exists(name): if not os.path.exists(name):
if not remote: if not remote:
@ -232,9 +236,9 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
""" """
# self.populate_display_types() # self.populate_display_types()
self.on_new_file_masks = translate('MediaPlugin.MediaItem', self.on_new_file_masks = translate('MediaPlugin.MediaItem',
'Videos (*.{video});;Audio (*.{audio});;{files} ' 'Videos ({video});;Audio ({audio});;{files} '
'(*)').format(video=' *.'.join(VIDEO_EXT), '(*)').format(video=' '.join(VIDEO_EXT),
audio=' *.'.join(AUDIO_EXT), audio=' '.join(AUDIO_EXT),
files=UiStrings().AllFiles) files=UiStrings().AllFiles)
def on_delete_click(self): def on_delete_click(self):
@ -258,6 +262,12 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
""" """
# TODO needs to be fixed as no idea why this fails # TODO needs to be fixed as no idea why this fails
# media.sort(key=lambda file_path: get_natural_key(file_path.name)) # media.sort(key=lambda file_path: get_natural_key(file_path.name))
file_name = translate('MediaPlugin.MediaItem', 'Live Stream')
item_name = QtWidgets.QListWidgetItem(file_name)
item_name.setIcon(UiIcons().video)
item_name.setData(QtCore.Qt.UserRole, 'live')
item_name.setToolTip(translate('MediaPlugin.MediaItem', 'Show Live Stream'))
self.list_view.addItem(item_name)
for track in media: for track in media:
track_str = str(track) track_str = str(track)
track_info = QtCore.QFileInfo(track_str) track_info = QtCore.QFileInfo(track_str)

View File

@ -129,7 +129,7 @@ class PresentationDocument(object):
thumbnail_folder_path = self.get_thumbnail_folder() thumbnail_folder_path = self.get_thumbnail_folder()
temp_folder_path = self.get_temp_folder() temp_folder_path = self.get_temp_folder()
if thumbnail_folder_path.exists(): if thumbnail_folder_path.exists():
thumbnail_folder_path.rmtree() shutil.rmtree(thumbnail_folder_path)
if temp_folder_path.exists(): if temp_folder_path.exists():
shutil.rmtree(temp_folder_path) shutil.rmtree(temp_folder_path)
except OSError: except OSError:

View File

@ -88,52 +88,72 @@ class SongReviewWidget(QtWidgets.QWidget):
self.song_alternate_title_content.setText(self.song.alternate_title) self.song_alternate_title_content.setText(self.song.alternate_title)
self.song_alternate_title_content.setWordWrap(True) self.song_alternate_title_content.setWordWrap(True)
self.song_info_form_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.song_alternate_title_content) self.song_info_form_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.song_alternate_title_content)
# Add last modified date.
self.song_last_modified_label = QtWidgets.QLabel(self)
self.song_last_modified_label.setObjectName('last_modified_label')
self.song_last_modified_label.setText('Last Modified:')
self.song_info_form_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.song_last_modified_label)
self.song_last_modified_content = QtWidgets.QLabel(self)
self.song_last_modified_content.setObjectName('last_modified_content')
self.song_last_modified_content.setText(self.song.last_modified.strftime("%Y-%m-%d %H:%M:%S"))
self.song_last_modified_content.setWordWrap(True)
self.song_info_form_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.song_last_modified_content)
# Add Theme widget.
self.song_theme_label = QtWidgets.QLabel(self)
self.song_theme_label.setObjectName('song_theme_label')
self.song_theme_label.setText('Theme:')
self.song_info_form_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.song_theme_label)
self.song_theme_content = QtWidgets.QLabel(self)
self.song_theme_content.setObjectName('song_theme_content')
self.song_theme_content.setText(self.song.theme_name)
self.song_theme_content.setWordWrap(True)
self.song_info_form_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.song_theme_content)
# Add CCLI number widget. # Add CCLI number widget.
self.song_ccli_number_label = QtWidgets.QLabel(self) self.song_ccli_number_label = QtWidgets.QLabel(self)
self.song_ccli_number_label.setObjectName('song_ccli_number_label') self.song_ccli_number_label.setObjectName('song_ccli_number_label')
self.song_info_form_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.song_ccli_number_label) self.song_info_form_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.song_ccli_number_label)
self.song_ccli_number_content = QtWidgets.QLabel(self) self.song_ccli_number_content = QtWidgets.QLabel(self)
self.song_ccli_number_content.setObjectName('song_ccli_number_content') self.song_ccli_number_content.setObjectName('song_ccli_number_content')
self.song_ccli_number_content.setText(self.song.ccli_number) self.song_ccli_number_content.setText(self.song.ccli_number)
self.song_ccli_number_content.setWordWrap(True) self.song_ccli_number_content.setWordWrap(True)
self.song_info_form_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.song_ccli_number_content) self.song_info_form_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.song_ccli_number_content)
# Add copyright widget. # Add copyright widget.
self.song_copyright_label = QtWidgets.QLabel(self) self.song_copyright_label = QtWidgets.QLabel(self)
self.song_copyright_label.setObjectName('song_copyright_label') self.song_copyright_label.setObjectName('song_copyright_label')
self.song_info_form_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.song_copyright_label) self.song_info_form_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.song_copyright_label)
self.song_copyright_content = QtWidgets.QLabel(self) self.song_copyright_content = QtWidgets.QLabel(self)
self.song_copyright_content.setObjectName('song_copyright_content') self.song_copyright_content.setObjectName('song_copyright_content')
self.song_copyright_content.setWordWrap(True) self.song_copyright_content.setWordWrap(True)
self.song_copyright_content.setText(self.song.copyright) self.song_copyright_content.setText(self.song.copyright)
self.song_info_form_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.song_copyright_content) self.song_info_form_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.song_copyright_content)
# Add comments widget. # Add comments widget.
self.song_comments_label = QtWidgets.QLabel(self) self.song_comments_label = QtWidgets.QLabel(self)
self.song_comments_label.setObjectName('song_comments_label') self.song_comments_label.setObjectName('song_comments_label')
self.song_info_form_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.song_comments_label) self.song_info_form_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.song_comments_label)
self.song_comments_content = QtWidgets.QLabel(self) self.song_comments_content = QtWidgets.QLabel(self)
self.song_comments_content.setObjectName('song_comments_content') self.song_comments_content.setObjectName('song_comments_content')
self.song_comments_content.setText(self.song.comments) self.song_comments_content.setText(self.song.comments)
self.song_comments_content.setWordWrap(True) self.song_comments_content.setWordWrap(True)
self.song_info_form_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.song_comments_content) self.song_info_form_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.song_comments_content)
# Add authors widget. # Add authors widget.
self.song_authors_label = QtWidgets.QLabel(self) self.song_authors_label = QtWidgets.QLabel(self)
self.song_authors_label.setObjectName('song_authors_label') self.song_authors_label.setObjectName('song_authors_label')
self.song_info_form_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.song_authors_label) self.song_info_form_layout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.song_authors_label)
self.song_authors_content = QtWidgets.QLabel(self) self.song_authors_content = QtWidgets.QLabel(self)
self.song_authors_content.setObjectName('song_authors_content') self.song_authors_content.setObjectName('song_authors_content')
self.song_authors_content.setWordWrap(True) self.song_authors_content.setWordWrap(True)
authors_text = ', '.join([author.display_name for author in self.song.authors]) authors_text = ', '.join([author.display_name for author in self.song.authors])
self.song_authors_content.setText(authors_text) self.song_authors_content.setText(authors_text)
self.song_info_form_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.song_authors_content) self.song_info_form_layout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.song_authors_content)
# Add verse order widget. # Add verse order widget.
self.song_verse_order_label = QtWidgets.QLabel(self) self.song_verse_order_label = QtWidgets.QLabel(self)
self.song_verse_order_label.setObjectName('song_verse_order_label') self.song_verse_order_label.setObjectName('song_verse_order_label')
self.song_info_form_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.song_verse_order_label) self.song_info_form_layout.setWidget(8, QtWidgets.QFormLayout.LabelRole, self.song_verse_order_label)
self.song_verse_order_content = QtWidgets.QLabel(self) self.song_verse_order_content = QtWidgets.QLabel(self)
self.song_verse_order_content.setObjectName('song_verse_order_content') self.song_verse_order_content.setObjectName('song_verse_order_content')
self.song_verse_order_content.setText(self.song.verse_order) self.song_verse_order_content.setText(self.song.verse_order)
self.song_verse_order_content.setWordWrap(True) self.song_verse_order_content.setWordWrap(True)
self.song_info_form_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.song_verse_order_content) self.song_info_form_layout.setWidget(8, QtWidgets.QFormLayout.FieldRole, self.song_verse_order_content)
self.song_group_box_layout.addLayout(self.song_info_form_layout) self.song_group_box_layout.addLayout(self.song_info_form_layout)
# Add verses widget. # Add verses widget.
self.song_info_verse_list_widget = QtWidgets.QTableWidget(self.song_group_box) self.song_info_verse_list_widget = QtWidgets.QTableWidget(self.song_group_box)

View File

@ -423,7 +423,7 @@ class SongsPlugin(Plugin):
""" """
Remove temporary songs from the database Remove temporary songs from the database
""" """
songs = self.manager.get_all_objects(Song, Song.temporary is True) songs = self.manager.get_all_objects(Song, Song.temporary == True) # noqa: E712
for song in songs: for song in songs:
self.manager.delete_object(Song, song.id) self.manager.delete_object(Song, song.id)

View File

@ -27,6 +27,7 @@ from unittest.mock import MagicMock, patch
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
from openlp.core.ui.media.mediacontroller import MediaController from openlp.core.ui.media.mediacontroller import MediaController
from openlp.core.ui.media import ItemMediaInfo
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
from tests.utils.constants import RESOURCE_PATH from tests.utils.constants import RESOURCE_PATH
@ -57,7 +58,7 @@ class TestMediaController(TestCase, TestMixin):
# THEN: The player's resize method should be called correctly # THEN: The player's resize method should be called correctly
mocked_player.resize.assert_called_with(mocked_display) mocked_player.resize.assert_called_with(mocked_display)
def test_check_file_type(self): def test_check_file_type_null(self):
""" """
Test that we don't try to play media when no players available Test that we don't try to play media when no players available
""" """
@ -71,7 +72,47 @@ class TestMediaController(TestCase, TestMixin):
ret = media_controller._check_file_type(mocked_controller, mocked_display) ret = media_controller._check_file_type(mocked_controller, mocked_display)
# THEN: it should return False # THEN: it should return False
assert ret is False, '_check_file_type should return False when no mediaplayers are available.' assert ret is False, '_check_file_type should return False when no media file matches.'
def test_check_file_video(self):
"""
Test that we process a file that is valid
"""
# GIVEN: A mocked UiStrings, get_used_players, controller, display and service_item
media_controller = MediaController()
mocked_controller = MagicMock()
mocked_display = MagicMock()
media_controller.media_players = MagicMock()
mocked_controller.media_info = ItemMediaInfo()
mocked_controller.media_info.file_info = [TEST_PATH / 'mp3_file.mp3']
media_controller.current_media_players = {}
media_controller.vlc_player = MagicMock()
# WHEN: calling _check_file_type when no players exists
ret = media_controller._check_file_type(mocked_controller, mocked_display)
# THEN: it should return False
assert ret is True, '_check_file_type should return True when audio file is present and matches.'
def test_check_file_audio(self):
"""
Test that we process a file that is valid
"""
# GIVEN: A mocked UiStrings, get_used_players, controller, display and service_item
media_controller = MediaController()
mocked_controller = MagicMock()
mocked_display = MagicMock()
media_controller.media_players = MagicMock()
mocked_controller.media_info = ItemMediaInfo()
mocked_controller.media_info.file_info = [TEST_PATH / 'mp4_file.mp4']
media_controller.current_media_players = {}
media_controller.vlc_player = MagicMock()
# WHEN: calling _check_file_type when no players exists
ret = media_controller._check_file_type(mocked_controller, mocked_display)
# THEN: it should return False
assert ret is True, '_check_file_type should return True when media file is present and matches.'
def test_media_play_msg(self): def test_media_play_msg(self):
""" """