forked from openlp/openlp
Ignore transition speed when no transitions
Also add/fix tests
This commit is contained in:
parent
f09ce2fed0
commit
bd0308f1b4
@ -20,7 +20,6 @@
|
||||
##########################################################################
|
||||
from openlp.core.api.lib import old_auth, old_success_response
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.lib import image_to_byte
|
||||
from openlp.core.lib.plugin import PluginStatus, StringContent
|
||||
from openlp.core.state import State
|
||||
|
||||
@ -55,5 +54,5 @@ def plugin_list():
|
||||
|
||||
@core_views.route('/main/image')
|
||||
def main_image():
|
||||
img = 'data:image/png;base64,{}'.format(image_to_byte(Registry().get('live_controller').grab_maindisplay()))
|
||||
img = 'data:image/jpeg;base64,{}'.format(Registry().get('live_controller').grab_maindisplay())
|
||||
return jsonify({'slide_image': img})
|
||||
|
@ -21,7 +21,6 @@
|
||||
import logging
|
||||
from openlp.core.api.lib import login_required
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.lib import image_to_byte
|
||||
from openlp.core.lib.plugin import PluginStatus, StringContent
|
||||
from openlp.core.state import State
|
||||
|
||||
@ -87,5 +86,5 @@ def login():
|
||||
@core.route('/live-image')
|
||||
def main_image():
|
||||
controller = Registry().get('live_controller')
|
||||
img = 'data:image/png;base64,{}'.format(image_to_byte(controller.grab_maindisplay()))
|
||||
img = 'data:image/jpeg;base64,{}'.format(controller.grab_maindisplay())
|
||||
return jsonify({'binary_image': img})
|
||||
|
@ -542,7 +542,7 @@ var Display = {
|
||||
section.setAttribute("data-background", bg_color);
|
||||
section.setAttribute("style", "height: 100%; width: 100%;");
|
||||
var img = document.createElement('img');
|
||||
img.src = 'data:image/png;base64,' + image_data;
|
||||
img.src = 'data:image/jpeg;base64,' + image_data;
|
||||
img.setAttribute("style", "height: 100%; width: 100%");
|
||||
section.appendChild(img);
|
||||
Display._slidesContainer.appendChild(section);
|
||||
|
@ -271,12 +271,12 @@ def image_to_byte(image, base_64=True):
|
||||
# use buffer to store pixmap into byteArray
|
||||
buffer = QtCore.QBuffer(byte_array)
|
||||
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||
image.save(buffer, "PNG")
|
||||
image.save(buffer, "JPEG")
|
||||
log.debug('image_to_byte - end')
|
||||
if not base_64:
|
||||
return byte_array
|
||||
# convert to base64 encoding so does not get missed!
|
||||
return bytes(byte_array.toBase64()).decode('utf-8')
|
||||
return base64.b64encode(byte_array).decode('utf-8')
|
||||
|
||||
|
||||
def image_to_data_uri(image_path):
|
||||
|
@ -42,7 +42,7 @@ from openlp.core.common.registry import Registry
|
||||
from openlp.core.display.render import remove_tags, render_tags, render_chords_for_printing
|
||||
from openlp.core.lib import ItemCapabilities
|
||||
from openlp.core.lib import create_thumb
|
||||
from openlp.core.lib.theme import BackgroundType
|
||||
from openlp.core.lib.theme import BackgroundType, TransitionSpeed
|
||||
from openlp.core.state import State
|
||||
from openlp.core.ui.icons import UiIcons
|
||||
from openlp.core.ui.media import parse_stream_path
|
||||
@ -606,6 +606,24 @@ class ServiceItem(RegistryProperties):
|
||||
else:
|
||||
return self.slides[0]['title']
|
||||
|
||||
def get_transition_delay(self):
|
||||
"""
|
||||
Returns a approximate time in seconds for how long it will take to switch slides
|
||||
"""
|
||||
delay = 1
|
||||
if self.is_capable(ItemCapabilities.ProvidesOwnDisplay):
|
||||
delay = 0.5
|
||||
else:
|
||||
theme = self.get_theme_data()
|
||||
transition_speed = theme.display_slide_transition_speed
|
||||
if theme.display_slide_transition is False or transition_speed == TransitionSpeed.Fast:
|
||||
delay = 0.5
|
||||
elif transition_speed == TransitionSpeed.Normal:
|
||||
delay = 1
|
||||
elif transition_speed == TransitionSpeed.Slow:
|
||||
delay = 2
|
||||
return delay
|
||||
|
||||
def merge(self, other):
|
||||
"""
|
||||
Updates the unique_identifier with the value from the original one
|
||||
|
@ -208,7 +208,7 @@ class PrintServiceForm(QtWidgets.QDialog, Ui_PrintServiceDialog, RegistryPropert
|
||||
# Add the title of the service item.
|
||||
item_title = self._add_element('h2', parent=div, class_id='itemTitle')
|
||||
img = image_to_byte(item.icon.pixmap(20, 20).toImage())
|
||||
self._add_element('img', parent=item_title, attribute=('src', 'data:image/png;base64, ' + img))
|
||||
self._add_element('img', parent=item_title, attribute=('src', 'data:image/jpeg;base64, ' + img))
|
||||
self._add_element('span', ' ' + html.escape(item.get_display_title()), item_title)
|
||||
if self.slide_text_check_box.isChecked():
|
||||
# Add the text of the service item.
|
||||
|
@ -35,6 +35,7 @@ from openlp.core.common.actions import ActionList, CategoryOrder
|
||||
from openlp.core.common.i18n import UiStrings, translate
|
||||
from openlp.core.common.mixins import LogMixin, RegistryProperties
|
||||
from openlp.core.common.registry import Registry, RegistryBase
|
||||
from openlp.core.common.utils import wait_for
|
||||
from openlp.core.display.screens import ScreenList
|
||||
from openlp.core.display.window import DisplayWindow
|
||||
from openlp.core.lib import ServiceItemAction, image_to_byte
|
||||
@ -274,6 +275,9 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
||||
self.controller_type = DisplayControllerType.Preview
|
||||
if self.is_live:
|
||||
self.controller_type = DisplayControllerType.Live
|
||||
self.slide_changed_time = datetime.datetime.now()
|
||||
self.fetching_screenshot = False
|
||||
self.screen_capture = None
|
||||
# Hide Menu
|
||||
self.hide_menu = QtWidgets.QToolButton(self.toolbar)
|
||||
self.hide_menu.setObjectName('hide_menu')
|
||||
@ -927,6 +931,13 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
||||
self.selected_row = 0
|
||||
# take a copy not a link to the servicemanager copy.
|
||||
self.service_item = copy.copy(service_item)
|
||||
if self.is_live:
|
||||
# Reset screen capture before any api call can arrive
|
||||
self.screen_capture = None
|
||||
# If item transitions are on, make sure the delay is longer than the animation
|
||||
self.slide_changed_time = datetime.datetime.now()
|
||||
if self.settings.value('themes/item transitions') and self.service_item.get_transition_delay() < 1:
|
||||
self.slide_changed_time += datetime.timedelta(seconds=0.5)
|
||||
if self.service_item.is_command() and not self.service_item.is_media():
|
||||
Registry().execute(
|
||||
'{text}_start'.format(text=self.service_item.name.lower()),
|
||||
@ -1213,7 +1224,10 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
||||
"""
|
||||
This updates the preview frame, for example after changing a slide or using *Blank to Theme*.
|
||||
"""
|
||||
self.log_debug('update_preview {text} '.format(text=self.screens.current))
|
||||
self.log_debug('update_preview {text}'.format(text=self.screens.current))
|
||||
if self.is_live:
|
||||
self.screen_capture = None
|
||||
self.slide_changed_time = max(self.slide_changed_time, datetime.datetime.now())
|
||||
if self.service_item and self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
|
||||
if self.is_live:
|
||||
# If live, grab screen-cap of main display now
|
||||
@ -1223,6 +1237,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
||||
else:
|
||||
# If not live, use the slide's thumbnail/icon instead
|
||||
image_path = Path(self.service_item.get_rendered_frame(self.selected_row))
|
||||
self.screen_capture = image_path
|
||||
self.preview_display.set_single_image('#000', image_path)
|
||||
else:
|
||||
self.preview_display.go_to_slide(self.selected_row)
|
||||
@ -1231,14 +1246,16 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
||||
"""
|
||||
Gets an image of the display screen and updates the preview frame.
|
||||
"""
|
||||
display_image = self.grab_maindisplay()
|
||||
display_image = self._capture_maindisplay()
|
||||
base64_image = image_to_byte(display_image)
|
||||
self.screen_capture = base64_image
|
||||
self.preview_display.set_single_image_data('#000', base64_image)
|
||||
|
||||
def grab_maindisplay(self):
|
||||
def _capture_maindisplay(self):
|
||||
"""
|
||||
Creates an image of the current screen.
|
||||
"""
|
||||
self.log_debug('_capture_maindisplay {text}'.format(text=self.screens.current))
|
||||
win_id = QtWidgets.QApplication.desktop().winId()
|
||||
screen = QtWidgets.QApplication.primaryScreen()
|
||||
rect = ScreenList().current.display_geometry
|
||||
@ -1246,6 +1263,29 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
||||
win_image.setDevicePixelRatio(self.preview_display.devicePixelRatio())
|
||||
return win_image
|
||||
|
||||
def is_slide_loaded(self):
|
||||
"""
|
||||
Returns a boolean as to whether the slide should be fully visible.
|
||||
Takes transition time into consideration.
|
||||
"""
|
||||
slide_delay_time = 1
|
||||
if self.service_item:
|
||||
slide_delay_time = self.service_item.get_transition_delay()
|
||||
slide_ready_time = self.slide_changed_time + datetime.timedelta(seconds=slide_delay_time)
|
||||
return datetime.datetime.now() > slide_ready_time
|
||||
|
||||
def grab_maindisplay(self):
|
||||
"""
|
||||
Gets the last taken screenshot
|
||||
"""
|
||||
wait_for(lambda: not self.fetching_screenshot)
|
||||
if self.screen_capture is None:
|
||||
self.fetching_screenshot = True
|
||||
wait_for(self.is_slide_loaded)
|
||||
self.screen_capture = image_to_byte(self._capture_maindisplay())
|
||||
self.fetching_screenshot = False
|
||||
return self.screen_capture
|
||||
|
||||
def on_slide_selected_next_action(self, checked):
|
||||
"""
|
||||
Wrapper function from create_action so we can throw away the incorrect parameter
|
||||
|
@ -241,7 +241,7 @@ def test_image_to_byte():
|
||||
MockedQtCore.QByteArray.assert_called_with()
|
||||
MockedQtCore.QBuffer.assert_called_with(mocked_byte_array)
|
||||
mocked_buffer.open.assert_called_with('writeonly')
|
||||
mocked_image.save.assert_called_with(mocked_buffer, "PNG")
|
||||
mocked_image.save.assert_called_with(mocked_buffer, "JPEG")
|
||||
assert mocked_byte_array.toBase64.called is False
|
||||
assert mocked_byte_array == result, 'The mocked out byte array should be returned'
|
||||
|
||||
@ -250,11 +250,12 @@ def test_image_to_byte_base_64():
|
||||
"""
|
||||
Test the image_to_byte() function
|
||||
"""
|
||||
with patch('openlp.core.lib.QtCore') as MockedQtCore:
|
||||
with patch('openlp.core.lib.QtCore') as MockedQtCore, \
|
||||
patch('openlp.core.lib.base64.b64encode') as mocked_b64encode:
|
||||
# GIVEN: A set of mocked-out Qt classes
|
||||
mocked_byte_array = MagicMock()
|
||||
mocked_b64encode.side_effect = lambda x: MagicMock(decode=MagicMock(return_value='{} base64ified'.format(x)))
|
||||
mocked_byte_array = "byte_array"
|
||||
MockedQtCore.QByteArray.return_value = mocked_byte_array
|
||||
mocked_byte_array.toBase64.return_value = QtCore.QByteArray(b'base64mock')
|
||||
mocked_buffer = MagicMock()
|
||||
MockedQtCore.QBuffer.return_value = mocked_buffer
|
||||
MockedQtCore.QIODevice.WriteOnly = 'writeonly'
|
||||
@ -267,9 +268,9 @@ def test_image_to_byte_base_64():
|
||||
MockedQtCore.QByteArray.assert_called_with()
|
||||
MockedQtCore.QBuffer.assert_called_with(mocked_byte_array)
|
||||
mocked_buffer.open.assert_called_with('writeonly')
|
||||
mocked_image.save.assert_called_with(mocked_buffer, "PNG")
|
||||
mocked_byte_array.toBase64.assert_called_with()
|
||||
assert 'base64mock' == result, 'The result should be the return value of the mocked out base64 method'
|
||||
mocked_image.save.assert_called_with(mocked_buffer, "JPEG")
|
||||
mocked_b64encode.assert_called_with(mocked_byte_array)
|
||||
assert 'byte_array base64ified' == result, 'The result should be the return value of the mocked base64 method'
|
||||
|
||||
|
||||
def test_create_thumb_with_size():
|
||||
|
@ -31,6 +31,7 @@ from openlp.core.common.enum import ServiceItemType
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.lib.formattingtags import FormattingTags
|
||||
from openlp.core.lib.serviceitem import ItemCapabilities, ServiceItem
|
||||
from openlp.core.lib.theme import TransitionSpeed
|
||||
from tests.utils import convert_file_service_item
|
||||
from tests.utils.constants import RESOURCE_PATH
|
||||
|
||||
@ -572,6 +573,93 @@ def test_remove_capability(settings):
|
||||
assert ItemCapabilities.CanEdit not in service_item.capabilities, 'The capability should not be in the list'
|
||||
|
||||
|
||||
def test_get_transition_delay_own_display(settings):
|
||||
"""
|
||||
Test the service item - get approx transition delay from theme
|
||||
"""
|
||||
# GIVEN: A service item with a theme and theme level set to global
|
||||
service_item = ServiceItem(None)
|
||||
service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
|
||||
service_item.theme = 'song_theme'
|
||||
mocked_theme_manager = MagicMock()
|
||||
mocked_theme_manager.global_theme = 'global_theme'
|
||||
Registry().register('theme_manager', mocked_theme_manager)
|
||||
settings.setValue('servicemanager/service theme', 'service_theme')
|
||||
settings.setValue('themes/theme level', ThemeLevel.Global)
|
||||
|
||||
# WHEN: Get theme data is run
|
||||
delay = service_item.get_transition_delay()
|
||||
|
||||
# THEN: theme should be 0.5s
|
||||
assert delay == 0.5
|
||||
|
||||
|
||||
def test_get_transition_delay_no_transition(settings):
|
||||
"""
|
||||
Test the service item - get approx transition delay from theme
|
||||
"""
|
||||
# GIVEN: A service item with a theme and theme level set to global
|
||||
service_item = ServiceItem(None)
|
||||
mocked_theme_manager = MagicMock()
|
||||
mocked_theme_manager.global_theme = 'global_theme'
|
||||
mocked_theme_manager.get_theme_data = Mock(return_value=MagicMock(**{
|
||||
'display_slide_transition': False,
|
||||
'display_slide_transition_speed': TransitionSpeed.Normal
|
||||
}))
|
||||
Registry().register('theme_manager', mocked_theme_manager)
|
||||
settings.setValue('themes/theme level', ThemeLevel.Global)
|
||||
|
||||
# WHEN: Get theme data is run
|
||||
delay = service_item.get_transition_delay()
|
||||
|
||||
# THEN: theme should be 0.5s
|
||||
assert delay == 0.5
|
||||
|
||||
|
||||
def test_get_transition_delay_normal(settings):
|
||||
"""
|
||||
Test the service item - get approx transition delay from theme
|
||||
"""
|
||||
# GIVEN: A service item with a theme and theme level set to global
|
||||
service_item = ServiceItem(None)
|
||||
mocked_theme_manager = MagicMock()
|
||||
mocked_theme_manager.global_theme = 'global_theme'
|
||||
mocked_theme_manager.get_theme_data = Mock(return_value=MagicMock(**{
|
||||
'display_slide_transition': True,
|
||||
'display_slide_transition_speed': TransitionSpeed.Normal
|
||||
}))
|
||||
Registry().register('theme_manager', mocked_theme_manager)
|
||||
settings.setValue('themes/theme level', ThemeLevel.Global)
|
||||
|
||||
# WHEN: Get theme data is run
|
||||
delay = service_item.get_transition_delay()
|
||||
|
||||
# THEN: theme should be 1s
|
||||
assert delay == 1
|
||||
|
||||
|
||||
def test_get_transition_delay_slow(settings):
|
||||
"""
|
||||
Test the service item - get approx transition delay from theme
|
||||
"""
|
||||
# GIVEN: A service item with a theme and theme level set to global
|
||||
service_item = ServiceItem(None)
|
||||
mocked_theme_manager = MagicMock()
|
||||
mocked_theme_manager.global_theme = 'global_theme'
|
||||
mocked_theme_manager.get_theme_data = Mock(return_value=MagicMock(**{
|
||||
'display_slide_transition': True,
|
||||
'display_slide_transition_speed': TransitionSpeed.Slow
|
||||
}))
|
||||
Registry().register('theme_manager', mocked_theme_manager)
|
||||
settings.setValue('themes/theme level', ThemeLevel.Global)
|
||||
|
||||
# WHEN: Get theme data is run
|
||||
delay = service_item.get_transition_delay()
|
||||
|
||||
# THEN: theme should be 2s
|
||||
assert delay == 2
|
||||
|
||||
|
||||
def test_to_dict_text_item(state_media, settings, service_item_env):
|
||||
"""
|
||||
Test that the to_dict() method returns the correct data for the service item
|
||||
|
@ -21,6 +21,8 @@
|
||||
"""
|
||||
Package to test the openlp.core.ui.slidecontroller package.
|
||||
"""
|
||||
import datetime
|
||||
|
||||
from unittest.mock import MagicMock, patch, sentinel
|
||||
|
||||
from PyQt5 import QtCore, QtGui
|
||||
@ -1011,6 +1013,7 @@ def test_update_preview_live(mocked_singleShot, registry):
|
||||
slide_controller.display_maindisplay = MagicMock()
|
||||
slide_controller.slide_preview = MagicMock()
|
||||
slide_controller.slide_count = 0
|
||||
slide_controller.slide_changed_time = datetime.datetime.now()
|
||||
|
||||
# WHEN: update_preview is called
|
||||
slide_controller.update_preview()
|
||||
@ -1142,7 +1145,7 @@ def test_display_maindisplay(mocked_image_to_byte, registry):
|
||||
"""
|
||||
# GIVEN: A mocked slide controller, with mocked functions
|
||||
slide_controller = SlideController(None)
|
||||
slide_controller.grab_maindisplay = MagicMock(return_value='placeholder')
|
||||
slide_controller._capture_maindisplay = MagicMock(return_value='placeholder')
|
||||
slide_controller.preview_display = MagicMock()
|
||||
mocked_image_to_byte.side_effect = lambda x: '{} bytified'.format(x)
|
||||
|
||||
@ -1150,10 +1153,86 @@ def test_display_maindisplay(mocked_image_to_byte, registry):
|
||||
slide_controller.display_maindisplay()
|
||||
|
||||
# THEN: Should have grabbed the maindisplay and set to placeholder with a black background
|
||||
slide_controller.grab_maindisplay.assert_called_once()
|
||||
slide_controller._capture_maindisplay.assert_called_once()
|
||||
slide_controller.preview_display.set_single_image_data.assert_called_once_with('#000', 'placeholder bytified')
|
||||
|
||||
|
||||
@patch(u'openlp.core.ui.slidecontroller.image_to_byte')
|
||||
@patch(u'openlp.core.ui.slidecontroller.ScreenList')
|
||||
@patch(u'openlp.core.ui.slidecontroller.QtWidgets.QApplication')
|
||||
def test__capture_maindisplay(mocked_application, mocked_screenlist, mocked_image_to_byte, registry):
|
||||
"""
|
||||
Test the _capture_maindisplay method
|
||||
"""
|
||||
# GIVEN: A mocked slide controller, with mocked functions
|
||||
slide_controller = SlideController(None)
|
||||
mocked_display_geometry = MagicMock(
|
||||
x=MagicMock(return_value=34),
|
||||
y=MagicMock(return_value=67),
|
||||
width=MagicMock(return_value=77),
|
||||
height=MagicMock(return_value=42)
|
||||
)
|
||||
mocked_screenlist_instance = MagicMock()
|
||||
mocked_screenlist.return_value = mocked_screenlist_instance
|
||||
mocked_screenlist_instance.current = MagicMock(display_geometry=mocked_display_geometry)
|
||||
mocked_primary_screen = MagicMock()
|
||||
mocked_application.primaryScreen = MagicMock(return_value=mocked_primary_screen)
|
||||
mocked_application.desktop = MagicMock(return_value=MagicMock(
|
||||
winId=MagicMock(return_value=23)
|
||||
))
|
||||
slide_controller.preview_display = MagicMock()
|
||||
|
||||
# WHEN: _capture_maindisplay is called
|
||||
slide_controller._capture_maindisplay()
|
||||
|
||||
# THEN: Screenshot should have been taken with correct winId and dimensions
|
||||
mocked_primary_screen.grabWindow.assert_called_once_with(23, 34, 67, 77, 42)
|
||||
|
||||
|
||||
@patch(u'openlp.core.ui.slidecontroller.image_to_byte')
|
||||
def test_grab_maindisplay(mocked_image_to_byte, registry):
|
||||
"""
|
||||
Test the grab_maindisplay method
|
||||
"""
|
||||
# GIVEN: A mocked slide controller, with mocked functions
|
||||
slide_controller = SlideController(None)
|
||||
slide_controller._capture_maindisplay = MagicMock(return_value='placeholder')
|
||||
slide_controller.preview_display = MagicMock()
|
||||
slide_controller.fetching_screenshot = False
|
||||
slide_controller.screen_capture = None
|
||||
slide_controller.service_item = MagicMock(get_transition_delay=MagicMock(return_value=1))
|
||||
slide_controller.slide_changed_time = datetime.datetime.now() - datetime.timedelta(seconds=10)
|
||||
mocked_image_to_byte.side_effect = lambda x: '{} bytified'.format(x)
|
||||
|
||||
# WHEN: grab_maindisplay is called
|
||||
grabbed_stuff = slide_controller.grab_maindisplay()
|
||||
|
||||
# THEN: Should have grabbed the maindisplay and ran image_to_byte on it
|
||||
slide_controller._capture_maindisplay.assert_called_once()
|
||||
assert grabbed_stuff == 'placeholder bytified'
|
||||
|
||||
|
||||
@patch(u'openlp.core.ui.slidecontroller.image_to_byte')
|
||||
def test_grab_maindisplay_cached(mocked_image_to_byte, registry):
|
||||
"""
|
||||
Test the grab_maindisplay method with pre-cached screenshot
|
||||
"""
|
||||
# GIVEN: A mocked slide controller, with mocked functions
|
||||
slide_controller = SlideController(None)
|
||||
slide_controller._capture_maindisplay = MagicMock(return_value='placeholder')
|
||||
slide_controller.preview_display = MagicMock()
|
||||
slide_controller.fetching_screenshot = False
|
||||
slide_controller.screen_capture = 'cached screen_capture'
|
||||
mocked_image_to_byte.side_effect = lambda x: '{} bytified'.format(x)
|
||||
|
||||
# WHEN: grab_maindisplay is called
|
||||
grabbed_stuff = slide_controller.grab_maindisplay()
|
||||
|
||||
# THEN: Should have not grabbed the maindisplay and returned the cached image
|
||||
assert slide_controller._capture_maindisplay.call_count == 0
|
||||
assert grabbed_stuff == 'cached screen_capture'
|
||||
|
||||
|
||||
def test_paint_event_text_fits():
|
||||
"""
|
||||
Test the paintEvent method when text fits the label
|
||||
|
Loading…
Reference in New Issue
Block a user