forked from openlp/openlp
Merge branch 'features_june_1' into 'master'
Fix up a number of bugs from 2.9.1 Closes #294, #587, #584, and #295 See merge request openlp/openlp!204
This commit is contained in:
commit
af1dd70589
@ -57,7 +57,7 @@ def add(plugin_name, id):
|
|||||||
@plugins.route('/<plugin>/search')
|
@plugins.route('/<plugin>/search')
|
||||||
@login_required
|
@login_required
|
||||||
def search_view(plugin):
|
def search_view(plugin):
|
||||||
log.debug(f'{plugin}/search search called')
|
log.debug(f'{plugin}/search called')
|
||||||
text = request.args.get('text', '')
|
text = request.args.get('text', '')
|
||||||
result = search(plugin, text)
|
result = search(plugin, text)
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
@ -66,7 +66,7 @@ def search_view(plugin):
|
|||||||
@plugins.route('/<plugin>/add', methods=['POST'])
|
@plugins.route('/<plugin>/add', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def add_view(plugin):
|
def add_view(plugin):
|
||||||
log.debug(f'{plugin}/add search called')
|
log.debug(f'{plugin}/add called')
|
||||||
data = request.json
|
data = request.json
|
||||||
if not data:
|
if not data:
|
||||||
abort(400)
|
abort(400)
|
||||||
@ -78,7 +78,7 @@ def add_view(plugin):
|
|||||||
@plugins.route('/<plugin>/live', methods=['POST'])
|
@plugins.route('/<plugin>/live', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def live_view(plugin):
|
def live_view(plugin):
|
||||||
log.debug(f'{plugin}/live search called')
|
log.debug(f'{plugin}/live called')
|
||||||
data = request.json
|
data = request.json
|
||||||
if not data:
|
if not data:
|
||||||
abort(400)
|
abort(400)
|
||||||
|
@ -120,11 +120,11 @@ class Registry(metaclass=Singleton):
|
|||||||
:param args: Parameters to be passed to the function.
|
:param args: Parameters to be passed to the function.
|
||||||
:param kwargs: Parameters to be passed to the function.
|
:param kwargs: Parameters to be passed to the function.
|
||||||
"""
|
"""
|
||||||
|
log.debug(f'Running function {event}')
|
||||||
results = []
|
results = []
|
||||||
if event in self.functions_list:
|
if event in self.functions_list:
|
||||||
for function in self.functions_list[event]:
|
for function in self.functions_list[event]:
|
||||||
try:
|
try:
|
||||||
log.debug('Running function {} for {}'.format(function, event))
|
|
||||||
result = function(*args, **kwargs)
|
result = function(*args, **kwargs)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
@ -573,8 +573,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
|||||||
"""
|
"""
|
||||||
process the bootstrap post setup request
|
process the bootstrap post setup request
|
||||||
"""
|
"""
|
||||||
# self.preview_controller.panel.setVisible(self.settings.value('user interface/preview panel'))
|
|
||||||
# self.live_controller.panel.setVisible(self.settings.value('user interface/live panel'))
|
|
||||||
self.load_settings()
|
self.load_settings()
|
||||||
self.restore_current_media_manager_item()
|
self.restore_current_media_manager_item()
|
||||||
Registry().execute('theme_update_global')
|
Registry().execute('theme_update_global')
|
||||||
@ -637,8 +635,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
|||||||
Show the main form, as well as the display form
|
Show the main form, as well as the display form
|
||||||
"""
|
"""
|
||||||
QtWidgets.QWidget.show(self)
|
QtWidgets.QWidget.show(self)
|
||||||
# if self.live_controller.display.isVisible():
|
|
||||||
# self.live_controller.display.setFocus()
|
|
||||||
self.activateWindow()
|
self.activateWindow()
|
||||||
# We have -disable-web-security added by our code.
|
# We have -disable-web-security added by our code.
|
||||||
# If a file is passed in we will have that as well so count of 2
|
# If a file is passed in we will have that as well so count of 2
|
||||||
|
@ -411,13 +411,13 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||||||
:param msg: First element is the controller which should be used
|
:param msg: First element is the controller which should be used
|
||||||
:param status:
|
:param status:
|
||||||
"""
|
"""
|
||||||
self.media_play(msg[0], status)
|
return self.media_play(msg[0], status)
|
||||||
|
|
||||||
def on_media_play(self):
|
def on_media_play(self):
|
||||||
"""
|
"""
|
||||||
Responds to the request to play a loaded video from the web.
|
Responds to the request to play a loaded video from the web.
|
||||||
"""
|
"""
|
||||||
self.media_play(Registry().get('live_controller'), False)
|
return self.media_play(Registry().get('live_controller'), False)
|
||||||
|
|
||||||
def media_play(self, controller, first_time=True):
|
def media_play(self, controller, first_time=True):
|
||||||
"""
|
"""
|
||||||
@ -495,13 +495,13 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||||||
|
|
||||||
:param msg: First element is the controller which should be used
|
:param msg: First element is the controller which should be used
|
||||||
"""
|
"""
|
||||||
self.media_pause(msg[0])
|
return self.media_pause(msg[0])
|
||||||
|
|
||||||
def on_media_pause(self):
|
def on_media_pause(self):
|
||||||
"""
|
"""
|
||||||
Responds to the request to pause a loaded video from the web.
|
Responds to the request to pause a loaded video from the web.
|
||||||
"""
|
"""
|
||||||
self.media_pause(Registry().get('live_controller'))
|
return self.media_pause(Registry().get('live_controller'))
|
||||||
|
|
||||||
def media_pause(self, controller):
|
def media_pause(self, controller):
|
||||||
"""
|
"""
|
||||||
@ -515,6 +515,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||||||
controller.mediabar.actions['playbackStop'].setDisabled(False)
|
controller.mediabar.actions['playbackStop'].setDisabled(False)
|
||||||
controller.mediabar.actions['playbackPause'].setVisible(False)
|
controller.mediabar.actions['playbackPause'].setVisible(False)
|
||||||
controller.media_info.is_playing = False
|
controller.media_info.is_playing = False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def media_loop_msg(self, msg):
|
def media_loop_msg(self, msg):
|
||||||
"""
|
"""
|
||||||
@ -540,13 +542,13 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||||||
|
|
||||||
:param msg: First element is the controller which should be used
|
:param msg: First element is the controller which should be used
|
||||||
"""
|
"""
|
||||||
self.media_stop(msg[0])
|
return self.media_stop(msg[0])
|
||||||
|
|
||||||
def on_media_stop(self):
|
def on_media_stop(self):
|
||||||
"""
|
"""
|
||||||
Responds to the request to stop a loaded video from the web.
|
Responds to the request to stop a loaded video from the web.
|
||||||
"""
|
"""
|
||||||
self.media_stop(Registry().get('live_controller'))
|
return self.media_stop(Registry().get('live_controller'))
|
||||||
|
|
||||||
def media_stop(self, controller):
|
def media_stop(self, controller):
|
||||||
"""
|
"""
|
||||||
@ -570,6 +572,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||||||
controller.media_timer = 0
|
controller.media_timer = 0
|
||||||
display = self._define_display(controller)
|
display = self._define_display(controller)
|
||||||
display.show_display()
|
display.show_display()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def media_volume_msg(self, msg):
|
def media_volume_msg(self, msg):
|
||||||
"""
|
"""
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# OpenLP - Open Source Lyrics Projection #
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
@ -20,79 +19,58 @@
|
|||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
"""
|
"""
|
||||||
The :mod:`~openlp.core.api.endpoint` module contains various API endpoints
|
The :mod:`~openlp.core.ui.media` module contains various API endpoints
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from flask import abort, jsonify, Blueprint
|
from flask import abort, Blueprint
|
||||||
|
|
||||||
from openlp.core.api import app
|
from openlp.core.api import app
|
||||||
from openlp.core.api.lib import login_required, old_auth
|
from openlp.core.api.lib import login_required
|
||||||
from openlp.core.common.registry import Registry
|
from openlp.core.common.registry import Registry
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
v1_media = Blueprint('v1-media-controller', __name__)
|
|
||||||
v2_media = Blueprint('v2-media-controller', __name__)
|
v2_media = Blueprint('v2-media-controller', __name__)
|
||||||
|
|
||||||
|
|
||||||
@v2_media.route('/play', methods=['POST'])
|
@v2_media.route('/play', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def media_play():
|
def media_play():
|
||||||
media = Registry().get('media_controller')
|
log.debug('media_play')
|
||||||
live = Registry().get('live_controller')
|
live = Registry().get('live_controller')
|
||||||
try:
|
if live.service_item.name != 'media':
|
||||||
status = media.media_play(live, True)
|
|
||||||
except Exception:
|
|
||||||
# The current item probably isn't a media item
|
|
||||||
abort(400)
|
abort(400)
|
||||||
|
status = live.mediacontroller_live_play.emit()
|
||||||
if status:
|
if status:
|
||||||
return '', 204
|
return '', 202
|
||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
|
|
||||||
@v2_media.route('/pause', methods=['POST'])
|
@v2_media.route('/pause', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def media_pause():
|
def media_pause():
|
||||||
media = Registry().get('media_controller')
|
log.debug('media_pause')
|
||||||
live = Registry().get('live_controller')
|
live = Registry().get('live_controller')
|
||||||
media.media_pause(live)
|
if live.service_item.name != 'media':
|
||||||
return '', 204
|
abort(400)
|
||||||
|
status = live.mediacontroller_live_pause.emit()
|
||||||
|
if status:
|
||||||
|
return '', 202
|
||||||
|
abort(400)
|
||||||
|
|
||||||
|
|
||||||
@v2_media.route('/stop', methods=['POST'])
|
@v2_media.route('/stop', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def media_stop():
|
def media_stop():
|
||||||
Registry().get('live_controller').mediacontroller_live_stop.emit()
|
log.debug('media_stop')
|
||||||
return '', 204
|
|
||||||
|
|
||||||
|
|
||||||
# -------------- DEPRECATED ------------------------
|
|
||||||
@v1_media.route('/play')
|
|
||||||
@old_auth
|
|
||||||
def v1_media_play():
|
|
||||||
media = Registry().get('media_controller')
|
|
||||||
live = Registry().get('live_controller')
|
live = Registry().get('live_controller')
|
||||||
status = media.media_play(live, False)
|
if live.service_item.name != 'media':
|
||||||
return jsonify({'success': status})
|
abort(400)
|
||||||
|
status = live.mediacontroller_live_stop.emit()
|
||||||
|
if status:
|
||||||
@v1_media.route('/pause')
|
return '', 202
|
||||||
@old_auth
|
abort(400)
|
||||||
def v1_media_pause():
|
|
||||||
media = Registry().get('media_controller')
|
|
||||||
live = Registry().get('live_controller')
|
|
||||||
status = media.media_pause(live)
|
|
||||||
return jsonify({'success': status})
|
|
||||||
|
|
||||||
|
|
||||||
@v1_media.route('/stop')
|
|
||||||
@old_auth
|
|
||||||
def v1_media_stop():
|
|
||||||
Registry().get('live_controller').mediacontroller_live_stop.emit()
|
|
||||||
return ''
|
|
||||||
# -------------- END OF DEPRECATED ------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def register_views():
|
def register_views():
|
||||||
app.register_blueprint(v2_media, url_prefix='/api/v2/media/')
|
app.register_blueprint(v2_media, url_prefix='/api/v2/media/')
|
||||||
app.register_blueprint(v1_media, url_prefix='/api/media/')
|
|
||||||
|
@ -287,42 +287,26 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
|||||||
icon=UiIcons().live_presentation,
|
icon=UiIcons().live_presentation,
|
||||||
checked=False, can_shortcuts=True, category=self.category,
|
checked=False, can_shortcuts=True, category=self.category,
|
||||||
triggers=self.on_show_display)
|
triggers=self.on_show_display)
|
||||||
self.theme_screen = create_action(self, 'setThemeScreen',
|
self.theme_screen = create_action(self, 'themeScreen',
|
||||||
text=translate('OpenLP.SlideController', 'Show Theme'),
|
text=translate('OpenLP.SlideController', 'Show Theme'),
|
||||||
icon=UiIcons().live_theme,
|
icon=UiIcons().live_theme,
|
||||||
checked=False, can_shortcuts=False, category=self.category,
|
checked=False, can_shortcuts=True, category=self.category,
|
||||||
triggers=self.on_theme_display)
|
triggers=self.on_theme_display)
|
||||||
self.blank_screen = create_action(self, 'setBlankScreen',
|
self.blank_screen = create_action(self, 'blankScreen',
|
||||||
text=translate('OpenLP.SlideController', 'Show Black'),
|
text=translate('OpenLP.SlideController', 'Show Black'),
|
||||||
icon=UiIcons().live_black,
|
icon=UiIcons().live_black,
|
||||||
checked=False, can_shortcuts=False, category=self.category,
|
checked=False, can_shortcuts=True, category=self.category,
|
||||||
triggers=self.on_blank_display)
|
triggers=self.on_blank_display)
|
||||||
self.desktop_screen = create_action(self, 'setDesktopScreen',
|
self.desktop_screen = create_action(self, 'desktopScreen',
|
||||||
text=translate('OpenLP.SlideController', 'Show Desktop'),
|
text=translate('OpenLP.SlideController', 'Show Desktop'),
|
||||||
icon=UiIcons().live_desktop,
|
icon=UiIcons().live_desktop,
|
||||||
checked=False, can_shortcuts=False, category=self.category,
|
checked=False, can_shortcuts=True, category=self.category,
|
||||||
triggers=self.on_hide_display)
|
triggers=self.on_hide_display)
|
||||||
self.hide_menu.setDefaultAction(self.show_screen)
|
self.hide_menu.setDefaultAction(self.show_screen)
|
||||||
self.hide_menu.menu().addAction(self.show_screen)
|
self.hide_menu.menu().addAction(self.show_screen)
|
||||||
self.hide_menu.menu().addAction(self.theme_screen)
|
self.hide_menu.menu().addAction(self.theme_screen)
|
||||||
self.hide_menu.menu().addAction(self.blank_screen)
|
self.hide_menu.menu().addAction(self.blank_screen)
|
||||||
self.hide_menu.menu().addAction(self.desktop_screen)
|
self.hide_menu.menu().addAction(self.desktop_screen)
|
||||||
# Add togglable actions for keyboard shortcuts
|
|
||||||
self.controller.addAction(create_action(self, 'desktopScreen',
|
|
||||||
can_shortcuts=True,
|
|
||||||
context=QtCore.Qt.WidgetWithChildrenShortcut,
|
|
||||||
category=self.category,
|
|
||||||
triggers=self.on_toggle_desktop))
|
|
||||||
self.controller.addAction(create_action(self, 'themeScreen',
|
|
||||||
can_shortcuts=True,
|
|
||||||
context=QtCore.Qt.WidgetWithChildrenShortcut,
|
|
||||||
category=self.category,
|
|
||||||
triggers=self.on_toggle_theme))
|
|
||||||
self.controller.addAction(create_action(self, 'blankScreen',
|
|
||||||
can_shortcuts=True,
|
|
||||||
context=QtCore.Qt.WidgetWithChildrenShortcut,
|
|
||||||
category=self.category,
|
|
||||||
triggers=self.on_toggle_blank))
|
|
||||||
# Wide menu of display control buttons.
|
# Wide menu of display control buttons.
|
||||||
self.show_screen_button = QtWidgets.QToolButton(self.toolbar)
|
self.show_screen_button = QtWidgets.QToolButton(self.toolbar)
|
||||||
self.show_screen_button.setObjectName('show_screen_button')
|
self.show_screen_button.setObjectName('show_screen_button')
|
||||||
@ -922,7 +906,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
|||||||
self.selected_row = 0
|
self.selected_row = 0
|
||||||
# take a copy not a link to the servicemanager copy.
|
# take a copy not a link to the servicemanager copy.
|
||||||
self.service_item = copy.copy(service_item)
|
self.service_item = copy.copy(service_item)
|
||||||
if self.service_item.is_command():
|
if self.service_item.is_command() and not self.service_item.is_media():
|
||||||
Registry().execute(
|
Registry().execute(
|
||||||
'{text}_start'.format(text=self.service_item.name.lower()),
|
'{text}_start'.format(text=self.service_item.name.lower()),
|
||||||
[self.service_item, self.is_live, self.get_hide_mode(), slide_no])
|
[self.service_item, self.is_live, self.get_hide_mode(), slide_no])
|
||||||
@ -967,7 +951,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
|||||||
self.preview_display.load_images(self.service_item.slides)
|
self.preview_display.load_images(self.service_item.slides)
|
||||||
for display in self.displays:
|
for display in self.displays:
|
||||||
display.load_images(self.service_item.slides)
|
display.load_images(self.service_item.slides)
|
||||||
for slide_index, slide in enumerate(self.service_item.slides):
|
for _, _ in enumerate(self.service_item.slides):
|
||||||
row += 1
|
row += 1
|
||||||
self.slide_list[str(row)] = row - 1
|
self.slide_list[str(row)] = row - 1
|
||||||
self.preview_widget.replace_service_item(self.service_item, width, slide_no)
|
self.preview_widget.replace_service_item(self.service_item, width, slide_no)
|
||||||
@ -987,7 +971,10 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
|||||||
# close the previous, so make sure we don't close the new one.
|
# close the previous, so make sure we don't close the new one.
|
||||||
if old_item.is_command() and not self.service_item.is_command() or \
|
if old_item.is_command() and not self.service_item.is_command() or \
|
||||||
old_item.is_command() and not old_item.is_media() and self.service_item.is_media():
|
old_item.is_command() and not old_item.is_media() and self.service_item.is_media():
|
||||||
Registry().execute('{name}_stop'.format(name=old_item.name.lower()), [old_item, self.is_live])
|
if old_item.is_media():
|
||||||
|
self.on_media_close()
|
||||||
|
else:
|
||||||
|
Registry().execute('{name}_stop'.format(name=old_item.name.lower()), [old_item, self.is_live])
|
||||||
if old_item.is_media() and not self.service_item.is_media():
|
if old_item.is_media() and not self.service_item.is_media():
|
||||||
self.on_media_close()
|
self.on_media_close()
|
||||||
if self.is_live:
|
if self.is_live:
|
||||||
|
@ -314,12 +314,21 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||||||
:param show_error: Should the error be shown (True)
|
:param show_error: Should the error be shown (True)
|
||||||
:return: The search result.
|
:return: The search result.
|
||||||
"""
|
"""
|
||||||
|
from pathlib import Path
|
||||||
results = []
|
results = []
|
||||||
string = string.lower()
|
string = string.lower()
|
||||||
for file_path in self.settings.value('media/media files'):
|
for file_path in self.settings.value('media/media files'):
|
||||||
file_name = file_path.name
|
if isinstance(file_path, Path):
|
||||||
if file_name.lower().find(string) > -1:
|
file_name = file_path.name
|
||||||
results.append([str(file_path), file_name])
|
if file_name.lower().find(string) > -1:
|
||||||
|
results.append([str(file_path), file_name])
|
||||||
|
else:
|
||||||
|
if file_path.lower().find(string) > -1:
|
||||||
|
if file_path.startswith('device'):
|
||||||
|
(name, _, _) = parse_stream_path(file_path)
|
||||||
|
results.append([str(file_path), name])
|
||||||
|
else:
|
||||||
|
results.append([str(file_path), file_path])
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def on_load_optical(self):
|
def on_load_optical(self):
|
||||||
|
@ -160,6 +160,8 @@ class PresentationMediaItem(MediaManagerItem):
|
|||||||
existing files, and when the user adds new files via the media manager.
|
existing files, and when the user adds new files via the media manager.
|
||||||
|
|
||||||
:param list[pathlib.Path] file_paths: List of file paths to add to the media manager.
|
:param list[pathlib.Path] file_paths: List of file paths to add to the media manager.
|
||||||
|
:param list[pathlib.Path] target_group: Group to load.
|
||||||
|
:param boolean initial_load: Is this the initial load of the list at start up
|
||||||
"""
|
"""
|
||||||
current_paths = self.get_file_list()
|
current_paths = self.get_file_list()
|
||||||
titles = [file_path.name for file_path in current_paths]
|
titles = [file_path.name for file_path in current_paths]
|
||||||
|
@ -739,8 +739,8 @@ def test_process_item(mocked_execute, registry):
|
|||||||
slide_controller._process_item(mocked_media_item, 0)
|
slide_controller._process_item(mocked_media_item, 0)
|
||||||
|
|
||||||
# THEN: Registry.execute should have been called to stop the presentation
|
# THEN: Registry.execute should have been called to stop the presentation
|
||||||
assert 2 == mocked_execute.call_count, 'Execute should have been called 2 times'
|
assert 1 == mocked_execute.call_count, 'Execute should have been called 2 times'
|
||||||
assert 'mocked_presentation_item_stop' == mocked_execute.call_args_list[1][0][0], \
|
assert 'mocked_presentation_item_stop' == mocked_execute.call_args_list[0][0][0], \
|
||||||
'The presentation should have been stopped.'
|
'The presentation should have been stopped.'
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,6 +55,18 @@ def test_search_found(media_item):
|
|||||||
assert result == [['test.mp4', 'test.mp4']], 'The result file contain the file name'
|
assert result == [['test.mp4', 'test.mp4']], 'The result file contain the file name'
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_found_mixed(media_item):
|
||||||
|
"""
|
||||||
|
Media Remote Search Successful find with Paths and Strings
|
||||||
|
"""
|
||||||
|
# GIVEN: The Mediaitem set up a list of media
|
||||||
|
media_item.settings.setValue('media/media files', [Path('test.mp3'), 'test.mp4'])
|
||||||
|
# WHEN: Retrieving the test file
|
||||||
|
result = media_item.search('test.mp4', False)
|
||||||
|
# THEN: a file should be found
|
||||||
|
assert result == [['test.mp4', 'test.mp4']], 'The result file contain the file name'
|
||||||
|
|
||||||
|
|
||||||
def test_search_not_found(media_item):
|
def test_search_not_found(media_item):
|
||||||
"""
|
"""
|
||||||
Media Remote Search not find
|
Media Remote Search not find
|
||||||
|
Loading…
Reference in New Issue
Block a user