Added resizing capabilities to the thumbnails

Fixed a problem with setting the presentation slides from the remote
This commit is contained in:
Felipe Polo-Wood 2013-10-29 11:38:28 -04:00
parent 9a07d6887b
commit 6d464dec4b
7 changed files with 56 additions and 20 deletions

View File

@ -144,12 +144,17 @@ def build_icon(icon):
return button_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. Resize an image to fit on the current screen for the web and returns it as a byte stream.
``image`` ``image``
The image to converted. 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') log.debug('image_to_byte - start')
byte_array = QtCore.QByteArray() byte_array = QtCore.QByteArray()
@ -158,6 +163,8 @@ def image_to_byte(image):
buffie.open(QtCore.QIODevice.WriteOnly) buffie.open(QtCore.QIODevice.WriteOnly)
image.save(buffie, "PNG") image.save(buffie, "PNG")
log.debug('image_to_byte - end') log.debug('image_to_byte - end')
if not base_64:
return byte_array
# convert to base64 encoding so does not get missed! # convert to base64 encoding so does not get missed!
return bytes(byte_array.toBase64()).decode('utf-8') return bytes(byte_array.toBase64()).decode('utf-8')

View File

@ -815,7 +815,7 @@ class SlideController(DisplayController):
self.update_preview() self.update_preview()
else: else:
self.preview_widget.change_slide(index) self.preview_widget.change_slide(index)
self.slide_selected() self.slide_selected()
def main_display_set_background(self): def main_display_set_background(self):
""" """
@ -1046,7 +1046,7 @@ class SlideController(DisplayController):
else: else:
row = self.preview_widget.slide_count() - 1 row = self.preview_widget.slide_count() - 1
self.preview_widget.change_slide(row) self.preview_widget.change_slide(row)
self.slide_selected() self.slide_selected()
def on_slide_selected_previous(self): def on_slide_selected_previous(self):
""" """
@ -1069,7 +1069,7 @@ class SlideController(DisplayController):
else: else:
row = 0 row = 0
self.preview_widget.change_slide(row) self.preview_widget.change_slide(row)
self.slide_selected() self.slide_selected()
def on_toggle_loop(self): def on_toggle_loop(self):
""" """

View File

@ -485,11 +485,11 @@ class ImpressDocument(PresentationDocument):
titles = [] titles = []
notes = [] notes = []
pages = self.document.getDrawPages() pages = self.document.getDrawPages()
for slide_no in range(pages.getCount()): for slide_no in range(1, pages.getCount() + 1):
titles.append( titles.append(
self.__get_text_from_page(slide_no+1, TextType.Title). self.__get_text_from_page(slide_no, TextType.Title).
replace('\n', ' ') + '\n') 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: if len(note) == 0:
note = ' ' note = ' '
notes.append(note) notes.append(note)

View File

@ -301,7 +301,7 @@ class PresentationDocument(object):
if os.path.exists(titles_file): if os.path.exists(titles_file):
try: try:
with open(titles_file) as fi: with open(titles_file) as fi:
titles = fi.read().splitlines(keepends=True) titles = fi.read().splitlines()
except: except:
log.exception('Failed to open/read existing titles file') log.exception('Failed to open/read existing titles file')
titles = [] titles = []

View File

@ -98,7 +98,7 @@ window.OpenLP = {
text += ("<div style='font-size:smaller;font-weight:normal'>" + slide["notes"] + "</div>"); text += ("<div style='font-size:smaller;font-weight:normal'>" + slide["notes"] + "</div>");
text = text.replace(/\n/g, '<br />'); text = text.replace(/\n/g, '<br />');
if (slide["img"]) if (slide["img"])
text += "<img src='" + slide["img"] + "'>"; text += "<img src='" + slide["img"].replace("/thumbnails/", "/thumbnails80x80/") + "'>";
var li = $("<li data-icon=\"false\">").append( var li = $("<li data-icon=\"false\">").append(
$("<a href=\"#\">").attr("value", parseInt(idx, 10)).html(text)); $("<a href=\"#\">").attr("value", parseInt(idx, 10)).html(text));
if (slide["selected"]) { if (slide["selected"]) {

View File

@ -151,7 +151,7 @@ class HttpRouter(object):
('^/(stage)$', {'function': self.serve_file, 'secure': False}), ('^/(stage)$', {'function': self.serve_file, 'secure': False}),
('^/(main)$', {'function': self.serve_file, 'secure': False}), ('^/(main)$', {'function': self.serve_file, 'secure': False}),
(r'^/files/(.*)$', {'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'^/api/poll$', {'function': self.poll, 'secure': False}),
(r'^/main/poll$', {'function': self.main_poll, 'secure': False}), (r'^/main/poll$', {'function': self.main_poll, 'secure': False}),
(r'^/main/image$', {'function': self.main_image, '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') self.send_header('Content-type', 'text/plain')
return ext 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. 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'] supported_controllers = ['presentations']
content = '' content = ''
if controller_name and file_name: if controller_name and file_name:
if controller_name in supported_controllers: if controller_name in supported_controllers:
full_path = urllib.parse.unquote(file_name) 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( full_path = os.path.normpath(os.path.join(
AppLocation.get_section_data_path(controller_name), AppLocation.get_section_data_path(controller_name),
'thumbnails/' + full_path)) 'thumbnails/' + full_path))
if os.path.exists(full_path): if os.path.exists(full_path):
ext = self.send_appropriate_header(full_path) ext = self.send_appropriate_header(full_path)
file_handle = open(full_path, 'rb') content = image_to_byte(resize_image(full_path, width,
content = file_handle.read() height),False)
if len(content)==0: if len(content)==0:
content = self.do_not_found() content = self.do_not_found()
return content return content
@ -514,7 +528,6 @@ class HttpRouter(object):
dataPath = AppLocation.get_data_path() dataPath = AppLocation.get_data_path()
if frame['image'][0:len(dataPath)] == dataPath: if frame['image'][0:len(dataPath)] == dataPath:
item['img'] = frame['image'][len(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['text'] = str(frame['title'])
item['html'] = str(frame['title']) item['html'] = str(frame['title'])
item['selected'] = (self.live_controller.selected_row == index) item['selected'] = (self.live_controller.selected_row == index)

View File

@ -30,6 +30,7 @@
This module contains tests for the lib submodule of the Remotes plugin. This module contains tests for the lib submodule of the Remotes plugin.
""" """
import os import os
import urllib.request
from unittest import TestCase from unittest import TestCase
from tempfile import mkstemp from tempfile import mkstemp
@ -38,6 +39,7 @@ from PyQt4 import QtGui
from openlp.core.common import Settings from openlp.core.common import Settings
from openlp.plugins.remotes.lib.httpserver import HttpRouter from openlp.plugins.remotes.lib.httpserver import HttpRouter
from mock import MagicMock, patch, mock_open from mock import MagicMock, patch, mock_open
from urllib.parse import urlparse
__default_settings__ = { __default_settings__ = {
'remotes/twelve hour': True, 'remotes/twelve hour': True,
@ -181,16 +183,30 @@ class TestRouter(TestCase):
self.router.send_header = MagicMock() self.router.send_header = MagicMock()
self.router.end_headers = MagicMock() self.router.end_headers = MagicMock()
self.router.wfile = 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, \ with patch('openlp.core.lib.os.path.exists') as mocked_exists, \
patch('builtins.open', mock_open(read_data='123')), \ 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_exists.return_value = True
mocked_image_to_byte.return_value = '123'
mocked_location.get_section_data_path.return_value = '' mocked_location.get_section_data_path.return_value = ''
# WHEN: pass good controller and filename # WHEN: pass good controller and filename
result = self.router.serve_thumbnail('presentations', result = self.router.serve_thumbnail('presentations',
'another%20test/slide1.png') '{0}x{1}'.format(width, height),
file_name)
# THEN: a file should be returned # 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') 'One header')
self.assertEqual(result, '123', 'The content should match \'123\'') 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)