diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 67ac409df..0793e0644 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -144,12 +144,17 @@ def build_icon(icon): return button_icon -def image_to_byte(image): +def image_to_byte(image, base_64=True): """ Resize an image to fit on the current screen for the web and returns it as a byte stream. ``image`` The image to converted. + + ``base_64`` + If True returns the image as Base64 bytes, otherwise + the image is returned as a byte array + To preserve original intention, this defaults to True """ log.debug('image_to_byte - start') byte_array = QtCore.QByteArray() @@ -158,6 +163,8 @@ def image_to_byte(image): buffie.open(QtCore.QIODevice.WriteOnly) image.save(buffie, "PNG") 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') diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 043838f36..e82cdc695 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -815,7 +815,7 @@ class SlideController(DisplayController): self.update_preview() else: self.preview_widget.change_slide(index) - self.slide_selected() + self.slide_selected() def main_display_set_background(self): """ @@ -1046,7 +1046,7 @@ class SlideController(DisplayController): else: row = self.preview_widget.slide_count() - 1 self.preview_widget.change_slide(row) - self.slide_selected() + self.slide_selected() def on_slide_selected_previous(self): """ @@ -1069,7 +1069,7 @@ class SlideController(DisplayController): else: row = 0 self.preview_widget.change_slide(row) - self.slide_selected() + self.slide_selected() def on_toggle_loop(self): """ diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 08f2d68dc..7480d3b1b 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -485,11 +485,11 @@ class ImpressDocument(PresentationDocument): titles = [] notes = [] pages = self.document.getDrawPages() - for slide_no in range(pages.getCount()): + for slide_no in range(1, pages.getCount() + 1): titles.append( - self.__get_text_from_page(slide_no+1, TextType.Title). + self.__get_text_from_page(slide_no, TextType.Title). replace('\n', ' ') + '\n') - note = self.__get_text_from_page(slide_no+1, TextType.Notes) + note = self.__get_text_from_page(slide_no, TextType.Notes) if len(note) == 0: note = ' ' notes.append(note) diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index c31e98a21..5a037ec48 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -301,7 +301,7 @@ class PresentationDocument(object): if os.path.exists(titles_file): try: with open(titles_file) as fi: - titles = fi.read().splitlines(keepends=True) + titles = fi.read().splitlines() except: log.exception('Failed to open/read existing titles file') titles = [] diff --git a/openlp/plugins/remotes/html/openlp.js b/openlp/plugins/remotes/html/openlp.js index 3ca8806f2..4952878e3 100644 --- a/openlp/plugins/remotes/html/openlp.js +++ b/openlp/plugins/remotes/html/openlp.js @@ -98,7 +98,7 @@ window.OpenLP = { text += ("
" + slide["notes"] + "
"); text = text.replace(/\n/g, '
'); if (slide["img"]) - text += ""; + text += ""; var li = $("
  • ").append( $("").attr("value", parseInt(idx, 10)).html(text)); if (slide["selected"]) { diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index f3f8c8f7d..ac513a236 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -151,7 +151,7 @@ class HttpRouter(object): ('^/(stage)$', {'function': self.serve_file, 'secure': False}), ('^/(main)$', {'function': self.serve_file, 'secure': False}), (r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}), - (r'^/(.*)/thumbnails/(.*)$', {'function': self.serve_thumbnail, 'secure': False}), + (r'^/(.*)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}), (r'^/api/poll$', {'function': self.poll, 'secure': False}), (r'^/main/poll$', {'function': self.main_poll, 'secure': False}), (r'^/main/image$', {'function': self.main_image, 'secure': False}), @@ -394,24 +394,38 @@ class HttpRouter(object): self.send_header('Content-type', 'text/plain') return ext - def serve_thumbnail(self, controller_name=None, file_name=None): + def serve_thumbnail(self, controller_name=None, dimensions=None, file_name=None): """ Serve an image file. If not found return 404. """ - log.debug('serve thumbnail %s/thumbnails/%s' % (controller_name, file_name)) + log.debug('serve thumbnail %s/thumbnails%s/%s' % (controller_name, + dimensions, file_name)) supported_controllers = ['presentations'] content = '' if controller_name and file_name: if controller_name in supported_controllers: full_path = urllib.parse.unquote(file_name) - if not '..' in full_path: + if not '..' in full_path: # no hacking please + width = 80 + height = 80 + if dimensions: + match = re.search('(\d+)x(\d+)', + dimensions) + if match: + width = int(match.group(1)) + height = int(match.group(2)) + # let's make sure that the dimensions are within reason + width = min(width,1000) + width = max(width,10) + height = min(height,1000) + height = max(height,10) full_path = os.path.normpath(os.path.join( AppLocation.get_section_data_path(controller_name), 'thumbnails/' + full_path)) if os.path.exists(full_path): ext = self.send_appropriate_header(full_path) - file_handle = open(full_path, 'rb') - content = file_handle.read() + content = image_to_byte(resize_image(full_path, width, + height),False) if len(content)==0: content = self.do_not_found() return content @@ -514,7 +528,6 @@ class HttpRouter(object): dataPath = AppLocation.get_data_path() if frame['image'][0:len(dataPath)] == dataPath: item['img'] = frame['image'][len(dataPath):] - #'data:image/png;base64,' + str(image_to_byte(resize_image(frame['image'], 80, 80))) item['text'] = str(frame['title']) item['html'] = str(frame['title']) item['selected'] = (self.live_controller.selected_row == index) diff --git a/tests/functional/openlp_plugins/remotes/test_router.py b/tests/functional/openlp_plugins/remotes/test_router.py index f0c1a261b..e36c421b1 100644 --- a/tests/functional/openlp_plugins/remotes/test_router.py +++ b/tests/functional/openlp_plugins/remotes/test_router.py @@ -30,6 +30,7 @@ This module contains tests for the lib submodule of the Remotes plugin. """ import os +import urllib.request from unittest import TestCase from tempfile import mkstemp @@ -38,6 +39,7 @@ from PyQt4 import QtGui from openlp.core.common import Settings from openlp.plugins.remotes.lib.httpserver import HttpRouter from mock import MagicMock, patch, mock_open +from urllib.parse import urlparse __default_settings__ = { 'remotes/twelve hour': True, @@ -181,16 +183,30 @@ class TestRouter(TestCase): self.router.send_header = MagicMock() self.router.end_headers = MagicMock() self.router.wfile = MagicMock() + file_name = 'another%20test/slide1.png' + full_path = os.path.normpath(os.path.join('thumbnails',file_name)) + width = 120 + height = 90 with patch('openlp.core.lib.os.path.exists') as mocked_exists, \ patch('builtins.open', mock_open(read_data='123')), \ - patch('openlp.plugins.remotes.lib.httprouter.AppLocation') as mocked_location: + patch('openlp.plugins.remotes.lib.httprouter.AppLocation') \ + as mocked_location, \ + patch('openlp.plugins.remotes.lib.httprouter.resize_image') \ + as mocked_resize, \ + patch('openlp.plugins.remotes.lib.httprouter.image_to_byte')\ + as mocked_image_to_byte: mocked_exists.return_value = True + mocked_image_to_byte.return_value = '123' mocked_location.get_section_data_path.return_value = '' # WHEN: pass good controller and filename result = self.router.serve_thumbnail('presentations', - 'another%20test/slide1.png') + '{0}x{1}'.format(width, height), + file_name) # THEN: a file should be returned - self.assertEqual(len(self.router.send_header.mock_calls), 1, + self.assertEqual(self.router.send_header.call_count, 1, 'One header') self.assertEqual(result, '123', 'The content should match \'123\'') - mocked_exists.assert_called_with(os.path.normpath('thumbnails/another test/slide1.png')) + mocked_exists.assert_called_with(urllib.parse.unquote(full_path)) + self.assertEqual(mocked_image_to_byte.call_count, 1, 'Called once') + mocked_resize.assert_called_once_with( + urllib.parse.unquote(full_path), width, height)