From b23abd0378526ddd8a10d18b7e27dcc0138fb158 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 15 Jul 2013 21:20:00 +0200 Subject: [PATCH 01/16] First attempt to support pdf using mupdf or ghostscript --- openlp/plugins/presentations/lib/mediaitem.py | 2 +- .../presentations/lib/pdfcontroller.py | 427 ++++++++++++++++++ .../presentations/presentationplugin.py | 1 + ...lugins.presentations.presentationplugin.py | 3 +- 4 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 openlp/plugins/presentations/lib/pdfcontroller.py diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index cef30a498..0f1bd440d 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -85,7 +85,7 @@ class PresentationMediaItem(MediaManagerItem): if self.controllers[controller].enabled(): file_types = self.controllers[controller].supports + self.controllers[controller].also_supports for file_type in file_types: - if file_type.find(file_type) == -1: + if file_type_list.find(file_type) == -1: file_type_list += u'*.%s ' % file_type self.service_manager.supported_suffixes(file_type) self.on_new_file_masks = translate('PresentationPlugin.MediaItem', 'Presentations (%s)') % file_type_list diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py new file mode 100644 index 000000000..c01ef2e41 --- /dev/null +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -0,0 +1,427 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +import os +import logging +from tempfile import NamedTemporaryFile +import re +from subprocess import check_output, call +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import ScreenList +from presentationcontroller import PresentationController, PresentationDocument + + +log = logging.getLogger(__name__) + + +class PdfController(PresentationController): + """ + Class to control PDF presentations + """ + log.info(u'PdfController loaded') + + def __init__(self, plugin): + """ + Initialise the class + """ + log.debug(u'Initialising') + self.process = None + PresentationController.__init__(self, plugin, u'Pdf', PdfDocument) + self.supports = [u'pdf', u'xps'] + self.mudrawbin = u'' + self.gsbin = u'' + self.viewer = None + + def check_available(self): + """ + PdfController is able to run on this machine. + """ + log.debug(u'check_available Pdf') + return self.check_installed() + + def check_installed(self): + """ + Check the viewer is installed. + """ + log.debug(u'check_installed Pdf') + # First try to find mupdf + try: + self.mudrawbin = check_output([u'which', u'mudraw']).rstrip('\n') + except CalledProcessError: + self.mudrawbin = u'' + + # if mupdf isn't installed, fallback to ghostscript + if self.mudrawbin == u'': + try: + self.gsbin = check_output([u'which', u'gs']).rstrip('\n') + except CalledProcessError: + self.gsbin = u'' + + if self.mudrawbin == u'' and self.gsbin == u'': + return False + else: + return True + + def start_process(self): + log.debug(u'start_process pdf') + # Setup viewer + try: + size = ScreenList().current[u'size'] + self.viewer = PdfViewer(size) + except Exception as e: + log.debug(e) + + + def kill(self): + """ + Called at system exit to clean up any running presentations + """ + log.debug(u'Kill pdfviewer') + self.viewer.close() + while self.docs: + self.docs[0].close_presentation() + + +class PdfDocument(PresentationDocument): + """ + Class which holds information and controls a single presentation. + """ + def __init__(self, controller, presentation): + """ + Constructor, store information about the file and initialise. + """ + log.debug(u'Init Presentation Pdf') + PresentationDocument.__init__(self, controller, presentation) + self.presentation = None + self.blanked = False + self.hidden = False + self.image_files = [] + self.num_pages = -1 + + # Only used when using ghostscript + # Ghostscript can't scale automaticly while keeping aspect like mupdf, so we need + # to get the ratio bewteen the screen size and the PDF to scale + def gs_get_resolution(self, size): + # Use a postscript script to get size of the pdf. It is assumed that all pages have same size + postscript = u'%!PS \n\ +() = \n\ +File dup (r) file runpdfbegin \n\ +1 pdfgetpage dup \n\ +/MediaBox pget { \n\ +aload pop exch 4 1 roll exch sub 3 1 roll sub \n\ +( Size: x: ) print =print (, y: ) print =print (\n) print \n\ +} if \n\ +flush \n\ +quit \n\ +' + # Put postscript into tempfile + tmpfile = NamedTemporaryFile(delete=False) + tmpfile.write(postscript) + tmpfile.close() + + # Run the script on the pdf to get the size + runlog = check_output([self.controller.gsbin, u'-dNOPAUSE', u'-dNODISPLAY', u'-dBATCH', u'-sFile=' + self.filepath, tmpfile.name]) + os.unlink(tmpfile.name) + + # Extract the pdf resolution from output, the format is " Size: x: , y: " + width = 0 + height = 0 + for line in runlog.splitlines(): + try: + width = re.search(u'.*Size: x: (\d+), y: \d+.*', line).group(1) + height = re.search(u'.*Size: x: \d+, y: (\d+).*', line).group(1) + break; + except AttributeError: + pass + + # Calculate the ratio from pdf to screen + if width > 0 and height > 0: + width_ratio = size.right() / float(width) + heigth_ratio = size.bottom() / float(height) + + # return the resolution that should be used. 72 is default. + if width_ratio > heigth_ratio: + return int(heigth_ratio * 72) + else: + return int(width_ratio * 72) + else: + return 72 + + def load_presentation(self): + """ + Called when a presentation is added to the SlideController. It generates images from the PDF. + """ + log.debug(u'load_presentation pdf') + + # Check if the images has already been created, and if yes load them + if os.path.isfile(self.get_temp_folder() + u'/mainslide001.png'): + created_files = sorted(os.listdir(self.get_temp_folder())) + for fn in created_files: + if os.path.isfile(self.get_temp_folder() + u'/' + fn): + self.image_files.append(self.get_temp_folder()+ u'/' + fn) + self.num_pages = len(self.image_files) + return True + + size = ScreenList().current[u'size'] + # Generate images from PDF that will fit the frame. + runlog = u'' + try: + if not os.path.isdir(self.get_temp_folder()): + os.makedirs(self.get_temp_folder()) + if self.controller.mudrawbin != u'': + runlog = check_output([self.controller.mudrawbin, u'-w', str(size.right()), u'-h', str(size.bottom()), u'-o', self.get_temp_folder() + u'/mainslide%03d.png', self.filepath]) + elif self.controller.gsbin != u'': + resolution = self.gs_get_resolution(size) + runlog = check_output([self.controller.gsbin, u'-dSAFER', u'-dNOPAUSE', u'-dBATCH', u'-sDEVICE=png16m', u'-r' + str(resolution), u'-dTextAlphaBits=4', u'-dGraphicsAlphaBits=4', u'-sOutputFile=' + self.get_temp_folder() + u'/mainslide%03d.png', self.filepath]) + created_files = sorted(os.listdir(self.get_temp_folder())) + for fn in created_files: + if os.path.isfile(self.get_temp_folder() + u'/' + fn): + self.image_files.append(self.get_temp_folder()+ u'/' + fn) + except Exception as e: + log.debug(e) + log.debug(runlog) + return False + self.num_pages = len(self.image_files) + + # Create thumbnails + self.create_thumbnails() + return True + + def create_thumbnails(self): + """ + Generates thumbnails + """ + log.debug(u'create_thumbnails pdf') + if self.check_thumbnails(): + return + log.debug(u'create_thumbnails proceeding') + + # use builtin function to create thumbnails from generated images + index = 1 + for image in self.image_files: + self.convert_thumbnail(image, index) + index += 1 + + def close_presentation(self): + """ + Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being + shut down. + """ + log.debug(u'close_presentation pdf') + self.controller.remove_doc(self) + # TODO + + def is_loaded(self): + """ + Returns true if a presentation is loaded. + """ + log.debug(u'is_loaded pdf') + if self.num_pages < 0: + return False + return True + + def is_active(self): + """ + Returns true if a presentation is currently active. + """ + log.debug(u'is_active pdf') + return self.is_loaded() and not self.hidden + + def blank_screen(self): + """ + Blanks the screen. + """ + log.debug(u'blank_screen pdf') + self.blanked = True + self.controller.viewer.blank() + + def unblank_screen(self): + """ + Unblanks (restores) the presentation. + """ + log.debug(u'unblank_screen pdf') + self.blanked = False + self.controller.viewer.unblank() + + def is_blank(self): + """ + Returns true if screen is blank. + """ + log.debug(u'is blank pdf') + return self.blanked + + def stop_presentation(self): + """ + Stops the current presentation and hides the output. + """ + log.debug(u'stop_presentation pdf') + self.hidden = True + self.controller.viewer.stop() + + def start_presentation(self): + """ + Starts a presentation from the beginning. + """ + log.debug(u'start_presentation pdf') + if self.hidden: + self.hidden = False + self.controller.viewer.start(self.image_files) + + def get_slide_number(self): + """ + Return the current slide number on the screen, from 1. + """ + log.debug(u'get_slide_number pdf') + return self.controller.viewer.get_current_page() + 1 + + def get_slide_count(self): + """ + Returns total number of slides. + """ + log.debug(u'get_slide_count pdf') + return self.num_pages + + def goto_slide(self, slideno): + """ + Moves to a specific slide in the presentation. + """ + log.debug(u'goto_slide pdf' + str(slideno)) + self.controller.viewer.show_page(slideno - 1) + # TODO + + def next_step(self): + """ + Triggers the next effect of slide on the running presentation. + """ + log.debug(u'next_step pdf') + self.controller.viewer.next_page() + + def previous_step(self): + """ + Triggers the previous slide on the running presentation. + """ + log.debug(u'previous_step pdf') + self.controller.viewer.previous_page() + + +class PdfViewer(QtGui.QWidget): + def __init__(self, rect): + log.debug(u'initialised pdf viewer') + QtGui.QWidget.__init__(self, None) + self.setWindowTitle("PDF Viewer") + p = QtGui.QPalette() + p.setColor(QtGui.QPalette.Background, QtCore.Qt.black); + self.setPalette(p) + self.setGeometry(rect) # QtGui.QApplication.desktop().screenGeometry()) + self.hide() + self.num_pages = 0 + self.pdf_images = [] + self.image_files = [] + self.is_blanked = False + self.current_page = 0 + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Down: + self.next_page() + elif event.key() == QtCore.Qt.Key_Up: + self.previous_page() + elif event.key() == QtCore.Qt.Key_Escape: + self.stop() + + def paintEvent(self, event): + if self.is_blanked: + return + img = self.get_image(self.current_page) + if img is None: + return + x = (self.frameSize().width() - img.width()) / 2 + y = (self.frameSize().height() - img.height()) / 2 + painter = QtGui.QPainter(self) + painter.drawImage(x, y, img, 0, 0, 0, 0) + + def display(self): + self.update() + self.cache_image(self.current_page + 1) + + def start(self, images): + log.debug(u'start pdfviewer') + self.image_files = images + self.num_pages = len(self.image_files) + self.pdf_images = [None for i in range(self.num_pages)] + self.showFullScreen() + self.show() + + def stop(self): + log.debug(u'stop pdfviewer') + self.hide() + + def close(self): + log.debug(u'close pdfviewer') + self.stop() + self.pdf_images = None + self.image_files = None + + def blank(self): + self.is_blanked = True + self.update() + + def unblank(self): + self.is_blanked = False + self.update() + + def next_page(self): + if self.current_page + 1 < self.num_pages: + self.current_page += 1 + self.display() + + def previous_page(self): + if self.current_page > 0: + self.current_page -= 1 + self.display() + + def show_page(self, idx): + if idx < self.num_pages: + self.current_page = idx + self.display() + + def cache_image(self, idx): + if idx >= self.num_pages: + return + if self.image_files[idx] is None: + return + img = QtGui.QImage(self.image_files[idx]) + self.pdf_images[idx] = img + + def get_image(self, idx): + self.cache_image(idx) + return self.pdf_images[idx] + + def get_current_page(self): + return self.current_page diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index cc1516b69..b11975591 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -48,6 +48,7 @@ __default_settings__ = { u'presentations/Impress': QtCore.Qt.Checked, u'presentations/Powerpoint': QtCore.Qt.Checked, u'presentations/Powerpoint Viewer': QtCore.Qt.Checked, + u'presentations/Pdf': QtCore.Qt.Checked, u'presentations/presentations files': [] } diff --git a/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py b/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py index 4948a641f..0e7190b32 100644 --- a/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py +++ b/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py @@ -29,4 +29,5 @@ hiddenimports = ['openlp.plugins.presentations.lib.impresscontroller', 'openlp.plugins.presentations.lib.powerpointcontroller', - 'openlp.plugins.presentations.lib.pptviewcontroller'] + 'openlp.plugins.presentations.lib.pptviewcontroller', + 'openlp.plugins.presentations.lib.pdfcontroller'] From fa537260f627851f9f6e3ab773c9e9ab9f305a2a Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 16 Jul 2013 22:59:52 +0200 Subject: [PATCH 02/16] Various fixes for ghostscript. Tried to support windows+mupdf. --- .../presentations/lib/pdfcontroller.py | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index c01ef2e41..cec86f84a 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -31,9 +31,10 @@ import os import logging from tempfile import NamedTemporaryFile import re -from subprocess import check_output, call +from subprocess import check_output, CalledProcessError from PyQt4 import QtCore, QtGui +from openlp.core.utils import AppLocation from openlp.core.lib import ScreenList from presentationcontroller import PresentationController, PresentationDocument @@ -54,9 +55,11 @@ class PdfController(PresentationController): log.debug(u'Initialising') self.process = None PresentationController.__init__(self, plugin, u'Pdf', PdfDocument) - self.supports = [u'pdf', u'xps'] + self.supports = [u'pdf'] self.mudrawbin = u'' self.gsbin = u'' + if self.check_installed() and self.mudrawbin != u'': + self.also_supports = [u'xps'] self.viewer = None def check_available(self): @@ -70,20 +73,34 @@ class PdfController(PresentationController): """ Check the viewer is installed. """ + application_path = AppLocation.get_directory(AppLocation.AppDir) + print application_path log.debug(u'check_installed Pdf') - # First try to find mupdf - try: - self.mudrawbin = check_output([u'which', u'mudraw']).rstrip('\n') - except CalledProcessError: - self.mudrawbin = u'' - - # if mupdf isn't installed, fallback to ghostscript - if self.mudrawbin == u'': + if os.name != u'nt': + # First try to find mupdf try: - self.gsbin = check_output([u'which', u'gs']).rstrip('\n') + self.mudrawbin = check_output([u'which', u'mudraw']).rstrip('\n') except CalledProcessError: - self.gsbin = u'' + self.mudrawbin = u'' + + # if mupdf isn't installed, fallback to ghostscript + if self.mudrawbin == u'': + try: + self.gsbin = check_output([u'which', u'gs']).rstrip('\n') + except CalledProcessError: + self.gsbin = u'' + # Last option: check if mudraw is placed in OpenLP base folder + if self.mudrawbin == u'' and self.gsbin == u'': + application_path = AppLocation.get_directory(AppLocation.AppDir) + if os.path.isfile(application_path + u'/../mudraw'): + self.mudrawbin = application_path + u'/../mudraw' + else: + # for windows we only accept mudraw.exe in the base folder + application_path = AppLocation.get_directory(AppLocation.AppDir) + if os.path.isfile(application_path + u'/../mudraw.exe'): + self.mudrawbin = application_path + u'/../mudraw.exe' + if self.mudrawbin == u'' and self.gsbin == u'': return False else: @@ -155,8 +172,8 @@ quit \n\ height = 0 for line in runlog.splitlines(): try: - width = re.search(u'.*Size: x: (\d+), y: \d+.*', line).group(1) - height = re.search(u'.*Size: x: \d+, y: (\d+).*', line).group(1) + width = re.search(u'.*Size: x: (\d+\.?\d*), y: \d+.*', line).group(1) + height = re.search(u'.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line).group(1) break; except AttributeError: pass @@ -164,11 +181,11 @@ quit \n\ # Calculate the ratio from pdf to screen if width > 0 and height > 0: width_ratio = size.right() / float(width) - heigth_ratio = size.bottom() / float(height) + height_ratio = size.bottom() / float(height) # return the resolution that should be used. 72 is default. - if width_ratio > heigth_ratio: - return int(heigth_ratio * 72) + if width_ratio > height_ratio: + return int(height_ratio * 72) else: return int(width_ratio * 72) else: From 308d5ed5ee56ddcb21f9156caf3f34cabca13941 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sat, 27 Jul 2013 11:43:04 +0200 Subject: [PATCH 03/16] Instead of using a special PDF-player, now we present the images from PDF using the image-presentation features. --- openlp/plugins/presentations/lib/mediaitem.py | 117 +++++++--- .../presentations/lib/pdfcontroller.py | 209 ++---------------- 2 files changed, 99 insertions(+), 227 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 0f1bd440d..577f0cf62 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -244,45 +244,100 @@ class PresentationMediaItem(MediaManagerItem): items = self.list_view.selectedItems() if len(items) > 1: return False - service_item.processor = self.display_type_combo_box.currentText() - service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) + + filename = items[0].data(QtCore.Qt.UserRole) + file_type = os.path.splitext(filename)[1][1:] if not self.display_type_combo_box.currentText(): return False - for bitem in items: - filename = bitem.data(QtCore.Qt.UserRole) - (path, name) = os.path.split(filename) - service_item.title = name - if os.path.exists(filename): - if service_item.processor == self.Automatic: - service_item.processor = self.findControllerByType(filename) - if not service_item.processor: + + service_item.processor = self.display_type_combo_box.currentText() + + if context == ServiceItemContext.Live and (file_type == u'pdf' or file_type == u'xps'): + service_item.add_capability(ItemCapabilities.CanMaintain) + service_item.add_capability(ItemCapabilities.CanPreview) + service_item.add_capability(ItemCapabilities.CanLoop) + service_item.add_capability(ItemCapabilities.CanAppend) + # force a nonexistent theme + service_item.theme = -1 + + # Why the loop when we above return False if len(items) is > 1? + for bitem in items: + filename = bitem.data(QtCore.Qt.UserRole) + (path, name) = os.path.split(filename) + service_item.title = name + if os.path.exists(filename): + if service_item.processor == self.Automatic: + service_item.processor = self.findControllerByType(filename) + if not service_item.processor: + return False + controller = self.controllers[service_item.processor] + #service_item.processor = None + doc = controller.add_document(filename) + if doc.get_thumbnail_path(1, True) is None: + doc.load_presentation() + i = 1 + imagefile = u'mainslide%03d.png' % i + img = os.path.join(doc.get_temp_folder(), imagefile) + if os.path.isfile(img): + while os.path.isfile(img): + service_item.add_from_image(img, name) + i += 1 + imagefile = u'mainslide%03d.png' % i + img = os.path.join(doc.get_temp_folder(), imagefile) + doc.close_presentation() + return True + else: + # File is no longer present + if not remote: + critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), + translate('PresentationPlugin.MediaItem', + 'The presentation %s is incomplete, please reload.') % filename) return False - controller = self.controllers[service_item.processor] - doc = controller.add_document(filename) - if doc.get_thumbnail_path(1, True) is None: - doc.load_presentation() - i = 1 - img = doc.get_thumbnail_path(i, True) - if img: - while img: - service_item.add_from_command(path, name, img) - i += 1 - img = doc.get_thumbnail_path(i, True) - doc.close_presentation() - return True else: # File is no longer present if not remote: critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), - translate('PresentationPlugin.MediaItem', - 'The presentation %s is incomplete, please reload.') % filename) + translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) + return False + else: + service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) + + # Why the loop when we above return False if len(items) is > 1? + for bitem in items: + filename = bitem.data(QtCore.Qt.UserRole) + (path, name) = os.path.split(filename) + service_item.title = name + if os.path.exists(filename): + if service_item.processor == self.Automatic: + service_item.processor = self.findControllerByType(filename) + if not service_item.processor: + return False + controller = self.controllers[service_item.processor] + doc = controller.add_document(filename) + if doc.get_thumbnail_path(1, True) is None: + doc.load_presentation() + i = 1 + img = doc.get_thumbnail_path(i, True) + if img: + while img: + service_item.add_from_command(path, name, img) + i += 1 + img = doc.get_thumbnail_path(i, True) + doc.close_presentation() + return True + else: + # File is no longer present + if not remote: + critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), + translate('PresentationPlugin.MediaItem', + 'The presentation %s is incomplete, please reload.') % filename) + return False + else: + # File is no longer present + if not remote: + critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), + translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) return False - else: - # File is no longer present - if not remote: - critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), - translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) - return False def findControllerByType(self, filename): """ diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index cec86f84a..ff79c6231 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -60,7 +60,6 @@ class PdfController(PresentationController): self.gsbin = u'' if self.check_installed() and self.mudrawbin != u'': self.also_supports = [u'xps'] - self.viewer = None def check_available(self): """ @@ -74,7 +73,6 @@ class PdfController(PresentationController): Check the viewer is installed. """ application_path = AppLocation.get_directory(AppLocation.AppDir) - print application_path log.debug(u'check_installed Pdf') if os.name != u'nt': # First try to find mupdf @@ -105,30 +103,19 @@ class PdfController(PresentationController): return False else: return True - - def start_process(self): - log.debug(u'start_process pdf') - # Setup viewer - try: - size = ScreenList().current[u'size'] - self.viewer = PdfViewer(size) - except Exception as e: - log.debug(e) - def kill(self): """ Called at system exit to clean up any running presentations """ log.debug(u'Kill pdfviewer') - self.viewer.close() while self.docs: self.docs[0].close_presentation() class PdfDocument(PresentationDocument): """ - Class which holds information and controls a single presentation. + Class which holds information of a single presentation. """ def __init__(self, controller, presentation): """ @@ -142,10 +129,12 @@ class PdfDocument(PresentationDocument): self.image_files = [] self.num_pages = -1 - # Only used when using ghostscript - # Ghostscript can't scale automaticly while keeping aspect like mupdf, so we need - # to get the ratio bewteen the screen size and the PDF to scale def gs_get_resolution(self, size): + """ + Only used when using ghostscript + Ghostscript can't scale automaticly while keeping aspect like mupdf, so we need + to get the ratio bewteen the screen size and the PDF to scale + """ # Use a postscript script to get size of the pdf. It is assumed that all pages have same size postscript = u'%!PS \n\ () = \n\ @@ -198,11 +187,11 @@ quit \n\ log.debug(u'load_presentation pdf') # Check if the images has already been created, and if yes load them - if os.path.isfile(self.get_temp_folder() + u'/mainslide001.png'): + if os.path.isfile(os.path.join(self.get_temp_folder(), u'mainslide001.png')): created_files = sorted(os.listdir(self.get_temp_folder())) for fn in created_files: - if os.path.isfile(self.get_temp_folder() + u'/' + fn): - self.image_files.append(self.get_temp_folder()+ u'/' + fn) + if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): + self.image_files.append(os.path.join(self.get_temp_folder(), fn)) self.num_pages = len(self.image_files) return True @@ -213,14 +202,14 @@ quit \n\ if not os.path.isdir(self.get_temp_folder()): os.makedirs(self.get_temp_folder()) if self.controller.mudrawbin != u'': - runlog = check_output([self.controller.mudrawbin, u'-w', str(size.right()), u'-h', str(size.bottom()), u'-o', self.get_temp_folder() + u'/mainslide%03d.png', self.filepath]) + runlog = check_output([self.controller.mudrawbin, u'-w', str(size.right()), u'-h', str(size.bottom()), u'-o', os.path.join(self.get_temp_folder(), u'mainslide%03d.png'), self.filepath]) elif self.controller.gsbin != u'': resolution = self.gs_get_resolution(size) - runlog = check_output([self.controller.gsbin, u'-dSAFER', u'-dNOPAUSE', u'-dBATCH', u'-sDEVICE=png16m', u'-r' + str(resolution), u'-dTextAlphaBits=4', u'-dGraphicsAlphaBits=4', u'-sOutputFile=' + self.get_temp_folder() + u'/mainslide%03d.png', self.filepath]) + runlog = check_output([self.controller.gsbin, u'-dSAFER', u'-dNOPAUSE', u'-dBATCH', u'-sDEVICE=png16m', u'-r' + str(resolution), u'-dTextAlphaBits=4', u'-dGraphicsAlphaBits=4', u'-sOutputFile=' + os.path.join(self.get_temp_folder(), u'mainslide%03d.png'), self.filepath]) created_files = sorted(os.listdir(self.get_temp_folder())) for fn in created_files: - if os.path.isfile(self.get_temp_folder() + u'/' + fn): - self.image_files.append(self.get_temp_folder()+ u'/' + fn) + if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): + self.image_files.append(os.path.join(self.get_temp_folder(), fn)) except Exception as e: log.debug(e) log.debug(runlog) @@ -253,7 +242,6 @@ quit \n\ """ log.debug(u'close_presentation pdf') self.controller.remove_doc(self) - # TODO def is_loaded(self): """ @@ -271,174 +259,3 @@ quit \n\ log.debug(u'is_active pdf') return self.is_loaded() and not self.hidden - def blank_screen(self): - """ - Blanks the screen. - """ - log.debug(u'blank_screen pdf') - self.blanked = True - self.controller.viewer.blank() - - def unblank_screen(self): - """ - Unblanks (restores) the presentation. - """ - log.debug(u'unblank_screen pdf') - self.blanked = False - self.controller.viewer.unblank() - - def is_blank(self): - """ - Returns true if screen is blank. - """ - log.debug(u'is blank pdf') - return self.blanked - - def stop_presentation(self): - """ - Stops the current presentation and hides the output. - """ - log.debug(u'stop_presentation pdf') - self.hidden = True - self.controller.viewer.stop() - - def start_presentation(self): - """ - Starts a presentation from the beginning. - """ - log.debug(u'start_presentation pdf') - if self.hidden: - self.hidden = False - self.controller.viewer.start(self.image_files) - - def get_slide_number(self): - """ - Return the current slide number on the screen, from 1. - """ - log.debug(u'get_slide_number pdf') - return self.controller.viewer.get_current_page() + 1 - - def get_slide_count(self): - """ - Returns total number of slides. - """ - log.debug(u'get_slide_count pdf') - return self.num_pages - - def goto_slide(self, slideno): - """ - Moves to a specific slide in the presentation. - """ - log.debug(u'goto_slide pdf' + str(slideno)) - self.controller.viewer.show_page(slideno - 1) - # TODO - - def next_step(self): - """ - Triggers the next effect of slide on the running presentation. - """ - log.debug(u'next_step pdf') - self.controller.viewer.next_page() - - def previous_step(self): - """ - Triggers the previous slide on the running presentation. - """ - log.debug(u'previous_step pdf') - self.controller.viewer.previous_page() - - -class PdfViewer(QtGui.QWidget): - def __init__(self, rect): - log.debug(u'initialised pdf viewer') - QtGui.QWidget.__init__(self, None) - self.setWindowTitle("PDF Viewer") - p = QtGui.QPalette() - p.setColor(QtGui.QPalette.Background, QtCore.Qt.black); - self.setPalette(p) - self.setGeometry(rect) # QtGui.QApplication.desktop().screenGeometry()) - self.hide() - self.num_pages = 0 - self.pdf_images = [] - self.image_files = [] - self.is_blanked = False - self.current_page = 0 - - def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Down: - self.next_page() - elif event.key() == QtCore.Qt.Key_Up: - self.previous_page() - elif event.key() == QtCore.Qt.Key_Escape: - self.stop() - - def paintEvent(self, event): - if self.is_blanked: - return - img = self.get_image(self.current_page) - if img is None: - return - x = (self.frameSize().width() - img.width()) / 2 - y = (self.frameSize().height() - img.height()) / 2 - painter = QtGui.QPainter(self) - painter.drawImage(x, y, img, 0, 0, 0, 0) - - def display(self): - self.update() - self.cache_image(self.current_page + 1) - - def start(self, images): - log.debug(u'start pdfviewer') - self.image_files = images - self.num_pages = len(self.image_files) - self.pdf_images = [None for i in range(self.num_pages)] - self.showFullScreen() - self.show() - - def stop(self): - log.debug(u'stop pdfviewer') - self.hide() - - def close(self): - log.debug(u'close pdfviewer') - self.stop() - self.pdf_images = None - self.image_files = None - - def blank(self): - self.is_blanked = True - self.update() - - def unblank(self): - self.is_blanked = False - self.update() - - def next_page(self): - if self.current_page + 1 < self.num_pages: - self.current_page += 1 - self.display() - - def previous_page(self): - if self.current_page > 0: - self.current_page -= 1 - self.display() - - def show_page(self, idx): - if idx < self.num_pages: - self.current_page = idx - self.display() - - def cache_image(self, idx): - if idx >= self.num_pages: - return - if self.image_files[idx] is None: - return - img = QtGui.QImage(self.image_files[idx]) - self.pdf_images[idx] = img - - def get_image(self, idx): - self.cache_image(idx) - return self.pdf_images[idx] - - def get_current_page(self): - return self.current_page From d28d75a2ce8a6d77ab577212da9c8457b2da308f Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sun, 4 Aug 2013 20:51:22 +0100 Subject: [PATCH 04/16] Added possible to point to a binary to use for pdf-processing. --- openlp/plugins/presentations/lib/mediaitem.py | 14 ++- .../presentations/lib/messagelistener.py | 7 +- .../presentations/lib/pdfcontroller.py | 49 ++++++++- .../presentations/lib/presentationtab.py | 101 +++++++++++++++++- .../presentations/presentationplugin.py | 2 + 5 files changed, 158 insertions(+), 15 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 577f0cf62..dabab5c04 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -249,8 +249,6 @@ class PresentationMediaItem(MediaManagerItem): file_type = os.path.splitext(filename)[1][1:] if not self.display_type_combo_box.currentText(): return False - - service_item.processor = self.display_type_combo_box.currentText() if context == ServiceItemContext.Live and (file_type == u'pdf' or file_type == u'xps'): service_item.add_capability(ItemCapabilities.CanMaintain) @@ -266,12 +264,11 @@ class PresentationMediaItem(MediaManagerItem): (path, name) = os.path.split(filename) service_item.title = name if os.path.exists(filename): - if service_item.processor == self.Automatic: - service_item.processor = self.findControllerByType(filename) - if not service_item.processor: - return False - controller = self.controllers[service_item.processor] - #service_item.processor = None + processor = self.findControllerByType(filename) + if not processor: + return False + controller = self.controllers[processor] + service_item.processor = None doc = controller.add_document(filename) if doc.get_thumbnail_path(1, True) is None: doc.load_presentation() @@ -300,6 +297,7 @@ class PresentationMediaItem(MediaManagerItem): translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) return False else: + service_item.processor = self.display_type_combo_box.currentText() service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) # Why the loop when we above return False if len(items) is > 1? diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index cd7c654a2..5df013c2e 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -324,7 +324,12 @@ class MessageListener(object): controller = self.live_handler else: controller = self.preview_handler - controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3]) + # when presenting PDF, we're using the image presentation code, + # so handler & processor is set to None, and we skip adding the handler. + if self.handler == None: + self.controller = controller + else: + controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3]) def slide(self, message): """ diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index ff79c6231..d2ba47bef 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -31,16 +31,40 @@ import os import logging from tempfile import NamedTemporaryFile import re -from subprocess import check_output, CalledProcessError +from subprocess import check_output, CalledProcessError, STDOUT from PyQt4 import QtCore, QtGui from openlp.core.utils import AppLocation -from openlp.core.lib import ScreenList +from openlp.core.lib import ScreenList, Settings from presentationcontroller import PresentationController, PresentationDocument log = logging.getLogger(__name__) +def check_binary(program_path): + """ + Function that checks whether a binary is either ghostscript or mudraw or neither. + """ + program_type = None + runlog = u'' + try: + runlog = check_output([program_path, u'--help'], stderr=STDOUT) + except CalledProcessError as e: + runlog = e.output + except Exception: + runlog = u'' + + # Analyse the output to see it the program is mudraw, ghostscript or neither + for line in runlog.splitlines(): + found_mudraw = re.search(u'usage: mudraw.*', line) + if found_mudraw: + program_type = u'mudraw' + break + found_gs = re.search(u'GPL Ghostscript.*', line) + if found_gs: + program_type = u'gs' + break + return program_type class PdfController(PresentationController): """ @@ -72,8 +96,20 @@ class PdfController(PresentationController): """ Check the viewer is installed. """ - application_path = AppLocation.get_directory(AppLocation.AppDir) log.debug(u'check_installed Pdf') + # Use the user defined program if given + if (Settings().value(u'presentations/enable_given_pdf_program')): + given_pdf_program = Settings().value(u'presentations/given_pdf_program') + type = check_binary(given_pdf_program) + if type == u'gs': + self.gsbin = given_pdf_program + return True + elif type == u'mudraw': + self.mudrawbin = given_pdf_program + return True + + # Fallback to autodetection + application_path = AppLocation.get_directory(AppLocation.AppDir) if os.name != u'nt': # First try to find mupdf try: @@ -153,7 +189,12 @@ quit \n\ tmpfile.close() # Run the script on the pdf to get the size - runlog = check_output([self.controller.gsbin, u'-dNOPAUSE', u'-dNODISPLAY', u'-dBATCH', u'-sFile=' + self.filepath, tmpfile.name]) + runlog = [] + try: + runlog = check_output([self.controller.gsbin, u'-dNOPAUSE', u'-dNODISPLAY', u'-dBATCH', u'-sFile=' + self.filepath, tmpfile.name]) + except CalledProcessError as e: + log.debug(u' '.join(e.cmd)) + log.debug(e.output) os.unlink(tmpfile.name) # Extract the pdf resolution from output, the format is " Size: x: , y: " diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index e46467403..6ada5d496 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -29,8 +29,9 @@ from PyQt4 import QtGui -from openlp.core.lib import Settings, SettingsTab, UiStrings, translate - +from openlp.core.lib import Settings, SettingsTab, UiStrings, translate, build_icon +from openlp.core.lib.ui import critical_error_message_box +from pdfcontroller import check_binary class PresentationTab(SettingsTab): """ @@ -63,6 +64,7 @@ class PresentationTab(SettingsTab): self.presenter_check_boxes[controller.name] = checkbox self.controllers_layout.addWidget(checkbox) self.left_layout.addWidget(self.controllers_group_box) + # Advanced self.advanced_group_box = QtGui.QGroupBox(self.left_column) self.advanced_group_box.setObjectName(u'advanced_group_box') self.advanced_layout = QtGui.QVBoxLayout(self.advanced_group_box) @@ -70,7 +72,33 @@ class PresentationTab(SettingsTab): self.override_app_check_box = QtGui.QCheckBox(self.advanced_group_box) self.override_app_check_box.setObjectName(u'override_app_check_box') self.advanced_layout.addWidget(self.override_app_check_box) + + # Pdf options + self.pdf_group_box = QtGui.QGroupBox(self.left_column) + self.pdf_group_box.setObjectName(u'pdf_group_box') + self.pdf_layout = QtGui.QFormLayout(self.pdf_group_box) + self.pdf_layout.setObjectName(u'pdf_layout') + self.pdf_program_check_box = QtGui.QCheckBox(self.pdf_group_box) + self.pdf_program_check_box.setObjectName(u'pdf_program_check_box') + self.pdf_layout.addWidget(self.pdf_program_check_box) + self.pdf_program_path_layout = QtGui.QHBoxLayout() + self.pdf_program_path_layout.setObjectName(u'pdf_program_path_layout') + self.pdf_program_path = QtGui.QLineEdit(self.pdf_group_box) + self.pdf_program_path.setObjectName(u'pdf_program_path') + self.pdf_program_path.setReadOnly(True) + self.pdf_program_path.setPalette(self.get_grey_text_palette(True)) + self.pdf_program_path_layout.addWidget(self.pdf_program_path) + self.pdf_program_browse_button = QtGui.QToolButton(self.pdf_group_box) + self.pdf_program_browse_button.setObjectName(u'pdf_program_browse_button') + self.pdf_program_browse_button.setIcon(build_icon(u':/general/general_open.png')) + self.pdf_program_browse_button.setEnabled(False) + self.pdf_program_path_layout.addWidget(self.pdf_program_browse_button) + self.pdf_layout.addRow(self.pdf_program_path_layout) + self.pdf_program_path.editingFinished.connect(self.on_pdf_program_path_edit_finished) + self.pdf_program_browse_button.clicked.connect(self.on_pdf_program_browse_button_clicked) + self.pdf_program_check_box.clicked.connect(self.on_pdf_program_check_box_clicked) self.left_layout.addWidget(self.advanced_group_box) + self.left_layout.addWidget(self.pdf_group_box) self.left_layout.addStretch() self.right_layout.addStretch() @@ -84,8 +112,12 @@ class PresentationTab(SettingsTab): checkbox = self.presenter_check_boxes[controller.name] self.set_controller_text(checkbox, controller) self.advanced_group_box.setTitle(UiStrings().Advanced) + self.pdf_group_box.setTitle(translate('PresentationPlugin.PresentationTab', 'PDF options')) self.override_app_check_box.setText( translate('PresentationPlugin.PresentationTab', 'Allow presentation application to be overridden')) + self.pdf_program_check_box.setText( + translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:')) + def set_controller_text(self, checkbox, controller): if checkbox.isEnabled(): @@ -102,6 +134,15 @@ class PresentationTab(SettingsTab): checkbox = self.presenter_check_boxes[controller.name] checkbox.setChecked(Settings().value(self.settings_section + u'/' + controller.name)) self.override_app_check_box.setChecked(Settings().value(self.settings_section + u'/override app')) + # load pdf-program settings + enable_given_pdf_program = Settings().value(self.settings_section + u'/enable_given_pdf_program') + self.pdf_program_check_box.setChecked(enable_given_pdf_program) + self.pdf_program_path.setReadOnly(not enable_given_pdf_program) + self.pdf_program_path.setPalette(self.get_grey_text_palette(not enable_given_pdf_program)) + self.pdf_program_browse_button.setEnabled(enable_given_pdf_program) + pdf_program = Settings().value(self.settings_section + u'/given_pdf_program') + if pdf_program: + self.pdf_program_path.setText(pdf_program) def save(self): """ @@ -127,10 +168,25 @@ class PresentationTab(SettingsTab): if Settings().value(setting_key) != self.override_app_check_box.checkState(): Settings().setValue(setting_key, self.override_app_check_box.checkState()) changed = True + + # Save pdf-settings + pdf_program = self.pdf_program_path.text() + enable_given_pdf_program = self.pdf_program_check_box.checkState() + # If the given program is blank disable using the program + if pdf_program == u'': + enable_given_pdf_program = 0 + if pdf_program != Settings().value(self.settings_section + u'/given_pdf_program'): + Settings().setValue(self.settings_section + u'/given_pdf_program', pdf_program) + changed = True + if enable_given_pdf_program != Settings().value(self.settings_section + u'/enable_given_pdf_program'): + Settings().setValue(self.settings_section + u'/enable_given_pdf_program', enable_given_pdf_program) + changed = True + if changed: self.settings_form.register_post_process(u'mediaitem_suffix_reset') self.settings_form.register_post_process(u'mediaitem_presentation_rebuild') self.settings_form.register_post_process(u'mediaitem_suffixes') + def tab_visible(self): """ @@ -142,3 +198,44 @@ class PresentationTab(SettingsTab): checkbox = self.presenter_check_boxes[controller.name] checkbox.setEnabled(controller.is_available()) self.set_controller_text(checkbox, controller) + + def on_pdf_program_path_edit_finished(self): + """ + After selecting/typing in a program it is validated that it is a actually ghostscript or mudraw + """ + type = None + if self.pdf_program_path.text() != u'': + type = check_binary(self.pdf_program_path.text()) + if not type: + critical_error_message_box(UiStrings().Error, + translate('PresentationPlugin.PresentationTab', 'The program is not ghostscript or mudraw which is required.')) + self.pdf_program_path.setFocus() + + def on_pdf_program_browse_button_clicked(self): + """ + Select the mudraw or ghostscript binary that should be used. + """ + filename = QtGui.QFileDialog.getOpenFileName(self, translate('PresentationPlugin.PresentationTab', 'Select mudraw or ghostscript binary.')) + if filename: + self.pdf_program_path.setText(filename) + self.pdf_program_path.setFocus() + + def on_pdf_program_check_box_clicked(self, checked): + """ + When checkbox for manual entering pdf-program is clicked, + enable or disable the textbox for the programpath and the browse-button. + """ + self.pdf_program_path.setReadOnly(not checked) + self.pdf_program_path.setPalette(self.get_grey_text_palette(not checked)) + self.pdf_program_browse_button.setEnabled(checked) + + def get_grey_text_palette(self, greyed): + """ + Returns a QPalette with greyed out text as used for placeholderText. + """ + palette = QtGui.QPalette() + color = self.palette().color(QtGui.QPalette.Active, QtGui.QPalette.Text) + if greyed: + color.setAlpha(128) + palette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Text, color) + return palette diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index b11975591..d6019a46b 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -45,6 +45,8 @@ log = logging.getLogger(__name__) __default_settings__ = { u'presentations/override app': QtCore.Qt.Unchecked, + u'presentations/enable_given_pdf_program': QtCore.Qt.Unchecked, + u'presentations/given_pdf_program': u'', u'presentations/Impress': QtCore.Qt.Checked, u'presentations/Powerpoint': QtCore.Qt.Checked, u'presentations/Powerpoint Viewer': QtCore.Qt.Checked, From 968e30837fe39375eae80d6de4436672d561df4d Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 6 Aug 2013 17:15:06 +0100 Subject: [PATCH 05/16] Reload pdf-presentation if images needed is missing instead of displaying an error. --- openlp/plugins/presentations/lib/mediaitem.py | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index dabab5c04..bb5f39a12 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -270,26 +270,18 @@ class PresentationMediaItem(MediaManagerItem): controller = self.controllers[processor] service_item.processor = None doc = controller.add_document(filename) - if doc.get_thumbnail_path(1, True) is None: + if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(os.path.join(doc.get_temp_folder(), u'mainslide001.png')): doc.load_presentation() i = 1 imagefile = u'mainslide%03d.png' % i img = os.path.join(doc.get_temp_folder(), imagefile) - if os.path.isfile(img): - while os.path.isfile(img): - service_item.add_from_image(img, name) - i += 1 - imagefile = u'mainslide%03d.png' % i - img = os.path.join(doc.get_temp_folder(), imagefile) - doc.close_presentation() - return True - else: - # File is no longer present - if not remote: - critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), - translate('PresentationPlugin.MediaItem', - 'The presentation %s is incomplete, please reload.') % filename) - return False + while os.path.isfile(img): + service_item.add_from_image(img, name) + i += 1 + imagefile = u'mainslide%03d.png' % i + img = os.path.join(doc.get_temp_folder(), imagefile) + doc.close_presentation() + return True else: # File is no longer present if not remote: From a45dfd2d1b436c7bcaf1a435628badf745d8e37c Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 4 Oct 2013 21:06:31 +0200 Subject: [PATCH 06/16] Made branch work with python3. --- openlp/plugins/presentations/lib/mediaitem.py | 8 +- .../presentations/lib/pdfcontroller.py | 151 +++++++++--------- .../presentations/lib/presentationtab.py | 39 ++--- .../presentations/presentationplugin.py | 2 +- 4 files changed, 100 insertions(+), 100 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 1f7f863bf..f1d4106b7 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -250,7 +250,7 @@ class PresentationMediaItem(MediaManagerItem): if not self.display_type_combo_box.currentText(): return False - if context == ServiceItemContext.Live and (file_type == u'pdf' or file_type == u'xps'): + if context == ServiceItemContext.Live and (file_type == 'pdf' or file_type == 'xps'): service_item.add_capability(ItemCapabilities.CanMaintain) service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanLoop) @@ -270,15 +270,15 @@ class PresentationMediaItem(MediaManagerItem): controller = self.controllers[processor] service_item.processor = None doc = controller.add_document(filename) - if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(os.path.join(doc.get_temp_folder(), u'mainslide001.png')): + if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(os.path.join(doc.get_temp_folder(), 'mainslide001.png')): doc.load_presentation() i = 1 - imagefile = u'mainslide%03d.png' % i + imagefile = 'mainslide%03d.png' % i img = os.path.join(doc.get_temp_folder(), imagefile) while os.path.isfile(img): service_item.add_from_image(img, name) i += 1 - imagefile = u'mainslide%03d.png' % i + imagefile = 'mainslide%03d.png' % i img = os.path.join(doc.get_temp_folder(), imagefile) doc.close_presentation() return True diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index d2ba47bef..d907fa84b 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -32,110 +32,115 @@ import logging from tempfile import NamedTemporaryFile import re from subprocess import check_output, CalledProcessError, STDOUT -from PyQt4 import QtCore, QtGui +#from PyQt4 import QtCore, QtGui from openlp.core.utils import AppLocation from openlp.core.lib import ScreenList, Settings -from presentationcontroller import PresentationController, PresentationDocument +from .presentationcontroller import PresentationController, PresentationDocument log = logging.getLogger(__name__) -def check_binary(program_path): - """ - Function that checks whether a binary is either ghostscript or mudraw or neither. - """ - program_type = None - runlog = u'' - try: - runlog = check_output([program_path, u'--help'], stderr=STDOUT) - except CalledProcessError as e: - runlog = e.output - except Exception: - runlog = u'' - - # Analyse the output to see it the program is mudraw, ghostscript or neither - for line in runlog.splitlines(): - found_mudraw = re.search(u'usage: mudraw.*', line) - if found_mudraw: - program_type = u'mudraw' - break - found_gs = re.search(u'GPL Ghostscript.*', line) - if found_gs: - program_type = u'gs' - break - return program_type class PdfController(PresentationController): """ Class to control PDF presentations """ - log.info(u'PdfController loaded') + log.info('PdfController loaded') def __init__(self, plugin): """ Initialise the class """ - log.debug(u'Initialising') + log.debug('Initialising') self.process = None - PresentationController.__init__(self, plugin, u'Pdf', PdfDocument) - self.supports = [u'pdf'] - self.mudrawbin = u'' - self.gsbin = u'' - if self.check_installed() and self.mudrawbin != u'': - self.also_supports = [u'xps'] + PresentationController.__init__(self, plugin, 'Pdf', PdfDocument) + self.supports = ['pdf'] + self.mudrawbin = '' + self.gsbin = '' + if self.check_installed() and self.mudrawbin != '': + self.also_supports = ['xps'] + + def check_binary(program_path): + """ + Function that checks whether a binary is either ghostscript or mudraw or neither. + """ + + program_type = None + runlog = '' + try: + runlog = check_output([program_path, '--help'], stderr=STDOUT) + except CalledProcessError as e: + runlog = e.output + except Exception: + runlog = '' + + # Analyse the output to see it the program is mudraw, ghostscript or neither + for line in runlog.splitlines(): + found_mudraw = re.search('usage: mudraw.*', line) + if found_mudraw: + program_type = 'mudraw' + break + found_gs = re.search('GPL Ghostscript.*', line) + if found_gs: + program_type = 'gs' + break + log.info('in check_binary, found: ' + program_type) + return program_type + + def check_available(self): """ PdfController is able to run on this machine. """ - log.debug(u'check_available Pdf') + log.debug('check_available Pdf') return self.check_installed() def check_installed(self): """ Check the viewer is installed. """ - log.debug(u'check_installed Pdf') + log.debug('check_installed Pdf') # Use the user defined program if given - if (Settings().value(u'presentations/enable_given_pdf_program')): - given_pdf_program = Settings().value(u'presentations/given_pdf_program') - type = check_binary(given_pdf_program) - if type == u'gs': + if (Settings().value('presentations/enable_given_pdf_program')): + given_pdf_program = Settings().value('presentations/given_pdf_program') + type = self.check_binary(given_pdf_program) + if type == 'gs': self.gsbin = given_pdf_program return True - elif type == u'mudraw': + elif type == 'mudraw': self.mudrawbin = given_pdf_program return True # Fallback to autodetection application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.name != u'nt': + if os.name != 'nt': # First try to find mupdf try: - self.mudrawbin = check_output([u'which', u'mudraw']).rstrip('\n') + self.mudrawbin = check_output(['which', 'mudraw']).decode(encoding='UTF-8').rstrip('\n') except CalledProcessError: - self.mudrawbin = u'' + self.mudrawbin = '' # if mupdf isn't installed, fallback to ghostscript - if self.mudrawbin == u'': + if self.mudrawbin == '': try: - self.gsbin = check_output([u'which', u'gs']).rstrip('\n') + self.gsbin = check_output(['which', 'gs']).rstrip('\n') except CalledProcessError: - self.gsbin = u'' + self.gsbin = '' # Last option: check if mudraw is placed in OpenLP base folder - if self.mudrawbin == u'' and self.gsbin == u'': + if self.mudrawbin == '' and self.gsbin == '': application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.path.isfile(application_path + u'/../mudraw'): - self.mudrawbin = application_path + u'/../mudraw' + if os.path.isfile(application_path + '/../mudraw'): + self.mudrawbin = application_path + '/../mudraw' else: # for windows we only accept mudraw.exe in the base folder application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.path.isfile(application_path + u'/../mudraw.exe'): - self.mudrawbin = application_path + u'/../mudraw.exe' + if os.path.isfile(application_path + '/../mudraw.exe'): + self.mudrawbin = application_path + '/../mudraw.exe' - if self.mudrawbin == u'' and self.gsbin == u'': + if self.mudrawbin == '' and self.gsbin == '': return False else: return True @@ -144,7 +149,7 @@ class PdfController(PresentationController): """ Called at system exit to clean up any running presentations """ - log.debug(u'Kill pdfviewer') + log.debug('Kill pdfviewer') while self.docs: self.docs[0].close_presentation() @@ -157,7 +162,7 @@ class PdfDocument(PresentationDocument): """ Constructor, store information about the file and initialise. """ - log.debug(u'Init Presentation Pdf') + log.debug('Init Presentation Pdf') PresentationDocument.__init__(self, controller, presentation) self.presentation = None self.blanked = False @@ -172,7 +177,7 @@ class PdfDocument(PresentationDocument): to get the ratio bewteen the screen size and the PDF to scale """ # Use a postscript script to get size of the pdf. It is assumed that all pages have same size - postscript = u'%!PS \n\ + postscript = '%!PS \n\ () = \n\ File dup (r) file runpdfbegin \n\ 1 pdfgetpage dup \n\ @@ -191,9 +196,9 @@ quit \n\ # Run the script on the pdf to get the size runlog = [] try: - runlog = check_output([self.controller.gsbin, u'-dNOPAUSE', u'-dNODISPLAY', u'-dBATCH', u'-sFile=' + self.filepath, tmpfile.name]) + runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH', '-sFile=' + self.filepath, tmpfile.name]) except CalledProcessError as e: - log.debug(u' '.join(e.cmd)) + log.debug(' '.join(e.cmd)) log.debug(e.output) os.unlink(tmpfile.name) @@ -202,8 +207,8 @@ quit \n\ height = 0 for line in runlog.splitlines(): try: - width = re.search(u'.*Size: x: (\d+\.?\d*), y: \d+.*', line).group(1) - height = re.search(u'.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line).group(1) + width = re.search('.*Size: x: (\d+\.?\d*), y: \d+.*', line).group(1) + height = re.search('.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line).group(1) break; except AttributeError: pass @@ -225,10 +230,10 @@ quit \n\ """ Called when a presentation is added to the SlideController. It generates images from the PDF. """ - log.debug(u'load_presentation pdf') + log.debug('load_presentation pdf') # Check if the images has already been created, and if yes load them - if os.path.isfile(os.path.join(self.get_temp_folder(), u'mainslide001.png')): + if os.path.isfile(os.path.join(self.get_temp_folder(), 'mainslide001.png')): created_files = sorted(os.listdir(self.get_temp_folder())) for fn in created_files: if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): @@ -236,17 +241,17 @@ quit \n\ self.num_pages = len(self.image_files) return True - size = ScreenList().current[u'size'] + size = ScreenList().current['size'] # Generate images from PDF that will fit the frame. - runlog = u'' + runlog = '' try: if not os.path.isdir(self.get_temp_folder()): os.makedirs(self.get_temp_folder()) - if self.controller.mudrawbin != u'': - runlog = check_output([self.controller.mudrawbin, u'-w', str(size.right()), u'-h', str(size.bottom()), u'-o', os.path.join(self.get_temp_folder(), u'mainslide%03d.png'), self.filepath]) - elif self.controller.gsbin != u'': + if self.controller.mudrawbin != '': + runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()), '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) + elif self.controller.gsbin != '': resolution = self.gs_get_resolution(size) - runlog = check_output([self.controller.gsbin, u'-dSAFER', u'-dNOPAUSE', u'-dBATCH', u'-sDEVICE=png16m', u'-r' + str(resolution), u'-dTextAlphaBits=4', u'-dGraphicsAlphaBits=4', u'-sOutputFile=' + os.path.join(self.get_temp_folder(), u'mainslide%03d.png'), self.filepath]) + runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m', '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) created_files = sorted(os.listdir(self.get_temp_folder())) for fn in created_files: if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): @@ -265,10 +270,10 @@ quit \n\ """ Generates thumbnails """ - log.debug(u'create_thumbnails pdf') + log.debug('create_thumbnails pdf') if self.check_thumbnails(): return - log.debug(u'create_thumbnails proceeding') + log.debug('create_thumbnails proceeding') # use builtin function to create thumbnails from generated images index = 1 @@ -281,14 +286,14 @@ quit \n\ Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being shut down. """ - log.debug(u'close_presentation pdf') + log.debug('close_presentation pdf') self.controller.remove_doc(self) def is_loaded(self): """ Returns true if a presentation is loaded. """ - log.debug(u'is_loaded pdf') + log.debug('is_loaded pdf') if self.num_pages < 0: return False return True @@ -297,6 +302,6 @@ quit \n\ """ Returns true if a presentation is currently active. """ - log.debug(u'is_active pdf') + log.debug('is_active pdf') return self.is_loaded() and not self.hidden diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index e3cc46ae1..9ed2eb56e 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -31,7 +31,9 @@ from PyQt4 import QtGui from openlp.core.lib import Settings, SettingsTab, UiStrings, translate, build_icon from openlp.core.lib.ui import critical_error_message_box -from pdfcontroller import check_binary +from .pdfcontroller import PdfController +#from openlp.plugins.presentations.lib.pdfcontroller import PdfController +#from pdfcontroller import check_binary class PresentationTab(SettingsTab): """ @@ -75,22 +77,22 @@ class PresentationTab(SettingsTab): # Pdf options self.pdf_group_box = QtGui.QGroupBox(self.left_column) - self.pdf_group_box.setObjectName(u'pdf_group_box') + self.pdf_group_box.setObjectName('pdf_group_box') self.pdf_layout = QtGui.QFormLayout(self.pdf_group_box) - self.pdf_layout.setObjectName(u'pdf_layout') + self.pdf_layout.setObjectName('pdf_layout') self.pdf_program_check_box = QtGui.QCheckBox(self.pdf_group_box) - self.pdf_program_check_box.setObjectName(u'pdf_program_check_box') + self.pdf_program_check_box.setObjectName('pdf_program_check_box') self.pdf_layout.addWidget(self.pdf_program_check_box) self.pdf_program_path_layout = QtGui.QHBoxLayout() - self.pdf_program_path_layout.setObjectName(u'pdf_program_path_layout') + self.pdf_program_path_layout.setObjectName('pdf_program_path_layout') self.pdf_program_path = QtGui.QLineEdit(self.pdf_group_box) - self.pdf_program_path.setObjectName(u'pdf_program_path') + self.pdf_program_path.setObjectName('pdf_program_path') self.pdf_program_path.setReadOnly(True) self.pdf_program_path.setPalette(self.get_grey_text_palette(True)) self.pdf_program_path_layout.addWidget(self.pdf_program_path) self.pdf_program_browse_button = QtGui.QToolButton(self.pdf_group_box) - self.pdf_program_browse_button.setObjectName(u'pdf_program_browse_button') - self.pdf_program_browse_button.setIcon(build_icon(u':/general/general_open.png')) + self.pdf_program_browse_button.setObjectName('pdf_program_browse_button') + self.pdf_program_browse_button.setIcon(build_icon(':/general/general_open.png')) self.pdf_program_browse_button.setEnabled(False) self.pdf_program_path_layout.addWidget(self.pdf_program_browse_button) self.pdf_layout.addRow(self.pdf_program_path_layout) @@ -173,26 +175,19 @@ class PresentationTab(SettingsTab): pdf_program = self.pdf_program_path.text() enable_given_pdf_program = self.pdf_program_check_box.checkState() # If the given program is blank disable using the program - if pdf_program == u'': + if pdf_program == '': enable_given_pdf_program = 0 - if pdf_program != Settings().value(self.settings_section + u'/given_pdf_program'): - Settings().setValue(self.settings_section + u'/given_pdf_program', pdf_program) + if pdf_program != Settings().value(self.settings_section + '/given_pdf_program'): + Settings().setValue(self.settings_section + '/given_pdf_program', pdf_program) changed = True - if enable_given_pdf_program != Settings().value(self.settings_section + u'/enable_given_pdf_program'): - Settings().setValue(self.settings_section + u'/enable_given_pdf_program', enable_given_pdf_program) + if enable_given_pdf_program != Settings().value(self.settings_section + '/enable_given_pdf_program'): + Settings().setValue(self.settings_section + '/enable_given_pdf_program', enable_given_pdf_program) changed = True if changed: -<<<<<<< TREE - self.settings_form.register_post_process(u'mediaitem_suffix_reset') - self.settings_form.register_post_process(u'mediaitem_presentation_rebuild') - self.settings_form.register_post_process(u'mediaitem_suffixes') - -======= self.settings_form.register_post_process('mediaitem_suffix_reset') self.settings_form.register_post_process('mediaitem_presentation_rebuild') self.settings_form.register_post_process('mediaitem_suffixes') ->>>>>>> MERGE-SOURCE def tab_visible(self): """ @@ -210,8 +205,8 @@ class PresentationTab(SettingsTab): After selecting/typing in a program it is validated that it is a actually ghostscript or mudraw """ type = None - if self.pdf_program_path.text() != u'': - type = check_binary(self.pdf_program_path.text()) + if self.pdf_program_path.text() != '': + type = PdfController.check_binary(self.pdf_program_path.text()) if not type: critical_error_message_box(UiStrings().Error, translate('PresentationPlugin.PresentationTab', 'The program is not ghostscript or mudraw which is required.')) diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 824eef2ba..a599cea4e 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -46,7 +46,7 @@ log = logging.getLogger(__name__) __default_settings__ = { 'presentations/override app': QtCore.Qt.Unchecked, 'presentations/enable_given_pdf_program': QtCore.Qt.Unchecked, - 'presentations/given_pdf_program': u'', + 'presentations/given_pdf_program': '', 'presentations/Impress': QtCore.Qt.Checked, 'presentations/Powerpoint': QtCore.Qt.Checked, 'presentations/Powerpoint Viewer': QtCore.Qt.Checked, From 3acd18c102c88595b3ac652d17b4687a6bb15839 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Thu, 14 Nov 2013 17:42:21 +0100 Subject: [PATCH 07/16] Mostly code cleanup of PDF support code. --- openlp/plugins/presentations/lib/mediaitem.py | 19 +++---- .../presentations/lib/messagelistener.py | 13 ++++- .../presentations/lib/pdfcontroller.py | 51 +++++++------------ .../presentations/lib/presentationtab.py | 24 ++++----- .../presentations/presentationplugin.py | 4 +- 5 files changed, 50 insertions(+), 61 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index f1d4106b7..9add8c8a4 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -244,21 +244,17 @@ class PresentationMediaItem(MediaManagerItem): items = self.list_view.selectedItems() if len(items) > 1: return False - filename = items[0].data(QtCore.Qt.UserRole) file_type = os.path.splitext(filename)[1][1:] if not self.display_type_combo_box.currentText(): return False - - if context == ServiceItemContext.Live and (file_type == 'pdf' or file_type == 'xps'): + if (file_type == 'pdf' or file_type == 'xps') and context != ServiceItemContext.Service: service_item.add_capability(ItemCapabilities.CanMaintain) service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanLoop) service_item.add_capability(ItemCapabilities.CanAppend) # force a nonexistent theme service_item.theme = -1 - - # Why the loop when we above return False if len(items) is > 1? for bitem in items: filename = bitem.data(QtCore.Qt.UserRole) (path, name) = os.path.split(filename) @@ -270,16 +266,17 @@ class PresentationMediaItem(MediaManagerItem): controller = self.controllers[processor] service_item.processor = None doc = controller.add_document(filename) - if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(os.path.join(doc.get_temp_folder(), 'mainslide001.png')): + if doc.get_thumbnail_path(1, True) is None or not os.path.isfile( + os.path.join(doc.get_temp_folder(), 'mainslide001.png')): doc.load_presentation() i = 1 imagefile = 'mainslide%03d.png' % i - img = os.path.join(doc.get_temp_folder(), imagefile) - while os.path.isfile(img): - service_item.add_from_image(img, name) + image = os.path.join(doc.get_temp_folder(), imagefile) + while os.path.isfile(image): + service_item.add_from_image(image, name) i += 1 imagefile = 'mainslide%03d.png' % i - img = os.path.join(doc.get_temp_folder(), imagefile) + image = os.path.join(doc.get_temp_folder(), imagefile) doc.close_presentation() return True else: @@ -291,8 +288,6 @@ class PresentationMediaItem(MediaManagerItem): else: service_item.processor = self.display_type_combo_box.currentText() service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) - - # Why the loop when we above return False if len(items) is > 1? for bitem in items: filename = bitem.data(QtCore.Qt.UserRole) (path, name) = os.path.split(filename) diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index e5667bda4..bd9311673 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -33,6 +33,7 @@ from PyQt4 import QtCore from openlp.core.lib import Registry from openlp.core.ui import HideMode +from openlp.core.lib import ServiceItemContext log = logging.getLogger(__name__) @@ -69,6 +70,7 @@ class Controller(object): return self.doc.slidenumber = slide_no self.hide_mode = hide_mode + log.debug('add_handler, slidenumber: %d' % slide_no) if self.is_live: if hide_mode == HideMode.Screen: Registry().execute('live_display_hide', HideMode.Screen) @@ -316,6 +318,14 @@ class MessageListener(object): hide_mode = message[2] file = item.get_frame_path() self.handler = item.processor + #self.media_item.generate_slide_data(self, service_item, item=None, xml_version=False,remote=False, context=ServiceItemContext.Service) + if file.endswith('.pdf') or file.endswith('.xps'): + log.debug('Trying to convert from pdf to images for service-item') + if is_live: + self.media_item.generate_slide_data(self, item, None, False, False, ServiceItemContext.Live) + else: + self.media_item.generate_slide_data(self, item, None, False, False, ServiceItemContext.Preview) + if self.handler == self.media_item.Automatic: self.handler = self.media_item.findControllerByType(file) if not self.handler: @@ -326,9 +336,10 @@ class MessageListener(object): controller = self.preview_handler # when presenting PDF, we're using the image presentation code, # so handler & processor is set to None, and we skip adding the handler. + log.debug('file: %s' % file) if self.handler == None: self.controller = controller - else: + else: controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3]) def slide(self, message): diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index d907fa84b..c6fa7dc49 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -58,14 +58,13 @@ class PdfController(PresentationController): self.supports = ['pdf'] self.mudrawbin = '' self.gsbin = '' - if self.check_installed() and self.mudrawbin != '': + if self.check_installed() and self.mudrawbin: self.also_supports = ['xps'] def check_binary(program_path): """ Function that checks whether a binary is either ghostscript or mudraw or neither. """ - program_type = None runlog = '' try: @@ -74,7 +73,6 @@ class PdfController(PresentationController): runlog = e.output except Exception: runlog = '' - # Analyse the output to see it the program is mudraw, ghostscript or neither for line in runlog.splitlines(): found_mudraw = re.search('usage: mudraw.*', line) @@ -88,8 +86,6 @@ class PdfController(PresentationController): log.info('in check_binary, found: ' + program_type) return program_type - - def check_available(self): """ PdfController is able to run on this machine. @@ -103,44 +99,40 @@ class PdfController(PresentationController): """ log.debug('check_installed Pdf') # Use the user defined program if given - if (Settings().value('presentations/enable_given_pdf_program')): - given_pdf_program = Settings().value('presentations/given_pdf_program') - type = self.check_binary(given_pdf_program) + if (Settings().value('presentations/enable_pdf_program')): + pdf_program = Settings().value('presentations/pdf_program') + type = self.check_binary(pdf_program) if type == 'gs': - self.gsbin = given_pdf_program + self.gsbin = pdf_program return True elif type == 'mudraw': - self.mudrawbin = given_pdf_program + self.mudrawbin = pdf_program return True - # Fallback to autodetection application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.name != 'nt': + if os.name == 'nt': + # for windows we only accept mudraw.exe in the base folder + application_path = AppLocation.get_directory(AppLocation.AppDir) + if os.path.isfile(application_path + '/../mudraw.exe'): + self.mudrawbin = application_path + '/../mudraw.exe' + else: # First try to find mupdf try: self.mudrawbin = check_output(['which', 'mudraw']).decode(encoding='UTF-8').rstrip('\n') except CalledProcessError: self.mudrawbin = '' - # if mupdf isn't installed, fallback to ghostscript - if self.mudrawbin == '': + if not self.mudrawbin: try: self.gsbin = check_output(['which', 'gs']).rstrip('\n') except CalledProcessError: self.gsbin = '' - # Last option: check if mudraw is placed in OpenLP base folder - if self.mudrawbin == '' and self.gsbin == '': + if not self.mudrawbin and not self.gsbin: application_path = AppLocation.get_directory(AppLocation.AppDir) if os.path.isfile(application_path + '/../mudraw'): self.mudrawbin = application_path + '/../mudraw' - else: - # for windows we only accept mudraw.exe in the base folder - application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.path.isfile(application_path + '/../mudraw.exe'): - self.mudrawbin = application_path + '/../mudraw.exe' - - if self.mudrawbin == '' and self.gsbin == '': + if not self.mudrawbin and not self.gsbin: return False else: return True @@ -192,7 +184,6 @@ quit \n\ tmpfile = NamedTemporaryFile(delete=False) tmpfile.write(postscript) tmpfile.close() - # Run the script on the pdf to get the size runlog = [] try: @@ -201,7 +192,6 @@ quit \n\ log.debug(' '.join(e.cmd)) log.debug(e.output) os.unlink(tmpfile.name) - # Extract the pdf resolution from output, the format is " Size: x: , y: " width = 0 height = 0 @@ -212,12 +202,10 @@ quit \n\ break; except AttributeError: pass - # Calculate the ratio from pdf to screen if width > 0 and height > 0: width_ratio = size.right() / float(width) height_ratio = size.bottom() / float(height) - # return the resolution that should be used. 72 is default. if width_ratio > height_ratio: return int(height_ratio * 72) @@ -231,7 +219,6 @@ quit \n\ Called when a presentation is added to the SlideController. It generates images from the PDF. """ log.debug('load_presentation pdf') - # Check if the images has already been created, and if yes load them if os.path.isfile(os.path.join(self.get_temp_folder(), 'mainslide001.png')): created_files = sorted(os.listdir(self.get_temp_folder())) @@ -240,16 +227,15 @@ quit \n\ self.image_files.append(os.path.join(self.get_temp_folder(), fn)) self.num_pages = len(self.image_files) return True - size = ScreenList().current['size'] # Generate images from PDF that will fit the frame. runlog = '' try: if not os.path.isdir(self.get_temp_folder()): os.makedirs(self.get_temp_folder()) - if self.controller.mudrawbin != '': + if self.controller.mudrawbin: runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()), '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) - elif self.controller.gsbin != '': + elif self.controller.gsbin: resolution = self.gs_get_resolution(size) runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m', '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) created_files = sorted(os.listdir(self.get_temp_folder())) @@ -261,7 +247,6 @@ quit \n\ log.debug(runlog) return False self.num_pages = len(self.image_files) - # Create thumbnails self.create_thumbnails() return True @@ -273,8 +258,6 @@ quit \n\ log.debug('create_thumbnails pdf') if self.check_thumbnails(): return - log.debug('create_thumbnails proceeding') - # use builtin function to create thumbnails from generated images index = 1 for image in self.image_files: diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 9ed2eb56e..1d6d9a16e 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -137,12 +137,12 @@ class PresentationTab(SettingsTab): checkbox.setChecked(Settings().value(self.settings_section + '/' + controller.name)) self.override_app_check_box.setChecked(Settings().value(self.settings_section + '/override app')) # load pdf-program settings - enable_given_pdf_program = Settings().value(self.settings_section + '/enable_given_pdf_program') - self.pdf_program_check_box.setChecked(enable_given_pdf_program) - self.pdf_program_path.setReadOnly(not enable_given_pdf_program) - self.pdf_program_path.setPalette(self.get_grey_text_palette(not enable_given_pdf_program)) - self.pdf_program_browse_button.setEnabled(enable_given_pdf_program) - pdf_program = Settings().value(self.settings_section + '/given_pdf_program') + enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program') + self.pdf_program_check_box.setChecked(enable_pdf_program) + self.pdf_program_path.setReadOnly(not enable_pdf_program) + self.pdf_program_path.setPalette(self.get_grey_text_palette(not enable_pdf_program)) + self.pdf_program_browse_button.setEnabled(enable_pdf_program) + pdf_program = Settings().value(self.settings_section + '/pdf_program') if pdf_program: self.pdf_program_path.setText(pdf_program) @@ -173,15 +173,15 @@ class PresentationTab(SettingsTab): # Save pdf-settings pdf_program = self.pdf_program_path.text() - enable_given_pdf_program = self.pdf_program_check_box.checkState() + enable_pdf_program = self.pdf_program_check_box.checkState() # If the given program is blank disable using the program if pdf_program == '': - enable_given_pdf_program = 0 - if pdf_program != Settings().value(self.settings_section + '/given_pdf_program'): - Settings().setValue(self.settings_section + '/given_pdf_program', pdf_program) + enable_pdf_program = 0 + if pdf_program != Settings().value(self.settings_section + '/pdf_program'): + Settings().setValue(self.settings_section + '/pdf_program', pdf_program) changed = True - if enable_given_pdf_program != Settings().value(self.settings_section + '/enable_given_pdf_program'): - Settings().setValue(self.settings_section + '/enable_given_pdf_program', enable_given_pdf_program) + if enable_pdf_program != Settings().value(self.settings_section + '/enable_pdf_program'): + Settings().setValue(self.settings_section + '/enable_pdf_program', enable_pdf_program) changed = True if changed: diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index a599cea4e..91f4d6096 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -45,8 +45,8 @@ log = logging.getLogger(__name__) __default_settings__ = { 'presentations/override app': QtCore.Qt.Unchecked, - 'presentations/enable_given_pdf_program': QtCore.Qt.Unchecked, - 'presentations/given_pdf_program': '', + 'presentations/enable_pdf_program': QtCore.Qt.Unchecked, + 'presentations/pdf_program': '', 'presentations/Impress': QtCore.Qt.Checked, 'presentations/Powerpoint': QtCore.Qt.Checked, 'presentations/Powerpoint Viewer': QtCore.Qt.Checked, From 685258b744d2fa1c3a469b9a16f4f93bb93a1b85 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 27 Dec 2013 17:44:12 +0000 Subject: [PATCH 08/16] Made presenting PDF from the servicemanager work --- openlp/core/lib/serviceitem.py | 6 ++++- openlp/core/ui/slidecontroller.py | 4 +-- openlp/plugins/presentations/lib/mediaitem.py | 12 ++++++--- .../presentations/lib/messagelistener.py | 27 +++++++++++++------ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 789854c78..8078b5868 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -420,7 +420,11 @@ class ServiceItem(object): self._raw_frames.append(slide) elif self.service_item_type == ServiceItemType.Image: settings_section = serviceitem['serviceitem']['header']['name'] - background = QtGui.QColor(Settings().value(settings_section + '/background color')) + background = None + try: + background = QtGui.QColor(Settings().value(settings_section + '/background color')) + except Exception: + pass if path: self.has_original_files = False for text_image in serviceitem['serviceitem']['data']: diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 043838f36..dbf095e48 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -438,7 +438,7 @@ class SlideController(DisplayController): # "V1" was the slide we wanted to go. self.preview_widget.change_slide(self.slideList[self.current_shortcut]) self.slide_selected() - # Reset the shortcut. + # Reset the shortcut. self.current_shortcut = '' def set_live_hotkeys(self, parent=None): @@ -733,7 +733,7 @@ class SlideController(DisplayController): if old_item and self.is_live and old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay): self._reset_blank() Registry().execute( - '%s_start' % service_item.name.lower(), [service_item, self.is_live, self.hide_mode(), slideno]) + '%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slideno]) self.slideList = {} if self.is_live: self.song_menu.menu().clear() diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index a89877ac6..be48c10f6 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -238,7 +238,7 @@ class PresentationMediaItem(MediaManagerItem): Settings().setValue(self.settings_section + '/presentations files', self.get_file_list()) def generate_slide_data(self, service_item, item=None, xml_version=False, - remote=False, context=ServiceItemContext.Service): + remote=False, context=ServiceItemContext.Service, presentation_file=None): """ Load the relevant information for displaying the presentation in the slidecontroller. In the case of powerpoints, an image for each slide. @@ -249,7 +249,9 @@ class PresentationMediaItem(MediaManagerItem): items = self.list_view.selectedItems() if len(items) > 1: return False - filename = items[0].data(QtCore.Qt.UserRole) + filename = presentation_file + if filename is None: + filename = items[0].data(QtCore.Qt.UserRole) file_type = os.path.splitext(filename)[1][1:] if not self.display_type_combo_box.currentText(): return False @@ -261,7 +263,9 @@ class PresentationMediaItem(MediaManagerItem): # force a nonexistent theme service_item.theme = -1 for bitem in items: - filename = bitem.data(QtCore.Qt.UserRole) + filename = presentation_file + if filename is None: + filename = bitem.data(QtCore.Qt.UserRole) (path, name) = os.path.split(filename) service_item.title = name if os.path.exists(filename): @@ -298,7 +302,7 @@ class PresentationMediaItem(MediaManagerItem): (path, name) = os.path.split(filename) service_item.title = name if os.path.exists(filename): - if service_item.processor == self.Automatic: + if service_item.processor == self.automatic: service_item.processor = self.findControllerByType(filename) if not service_item.processor: return False diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index fba9c8b4d..68b7641cc 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -33,7 +33,7 @@ from PyQt4 import QtCore from openlp.core.lib import Registry from openlp.core.ui import HideMode -from openlp.core.lib import ServiceItemContext +from openlp.core.lib import ServiceItemContext, ServiceItem log = logging.getLogger(__name__) @@ -318,14 +318,26 @@ class MessageListener(object): hide_mode = message[2] file = item.get_frame_path() self.handler = item.processor - #self.media_item.generate_slide_data(self, service_item, item=None, xml_version=False,remote=False, context=ServiceItemContext.Service) + # When starting presentation from the servicemanager/slidecontroller we convert + # PDF/XPS-serviceitems into image-serviceitems. When started from the mediamanager + # the conversion has already been done. if file.endswith('.pdf') or file.endswith('.xps'): - log.debug('Trying to convert from pdf to images for service-item') + log.debug('Converting from pdf/xps to images for serviceitem with file %s', file) + # Create a new image-serviceitem which will overwrite the old one + new_item = ServiceItem() + new_item.name = 'images' if is_live: - self.media_item.generate_slide_data(self, item, None, False, False, ServiceItemContext.Live) + self.media_item.generate_slide_data(new_item, item, False, False, ServiceItemContext.Live, file) else: - self.media_item.generate_slide_data(self, item, None, False, False, ServiceItemContext.Preview) - + self.media_item.generate_slide_data(new_item, item, False, False, ServiceItemContext.Preview, file) + # We need to overwrite the current serviceitem to make the slidecontroller + # present the images, so we do some copying + service_repr = {'serviceitem': new_item.get_service_repr(True) } + item.set_from_service(service_repr) + item._raw_frames = new_item._raw_frames + # When presenting PDF or XPS, we are using the image presentation code, + # so handler & processor is set to None, and we skip adding the handler. + self.handler = None if self.handler == self.media_item.automatic: self.handler = self.media_item.findControllerByType(file) if not self.handler: @@ -334,9 +346,8 @@ class MessageListener(object): controller = self.live_handler else: controller = self.preview_handler - # when presenting PDF, we're using the image presentation code, + # When presenting PDF or XPS, we are using the image presentation code, # so handler & processor is set to None, and we skip adding the handler. - log.debug('file: %s' % file) if self.handler == None: self.controller = controller else: From 82aaf11860e785c258200c6905b42766f03a3158 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sat, 28 Dec 2013 18:36:33 +0000 Subject: [PATCH 09/16] Fixes for the presentation setting tab --- .../presentations/lib/pdfcontroller.py | 17 ++++++++++------- .../presentations/lib/presentationtab.py | 19 ++++++++++--------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index 6b2dccfa1..781bd0af4 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -41,7 +41,6 @@ from .presentationcontroller import PresentationController, PresentationDocument log = logging.getLogger(__name__) - class PdfController(PresentationController): """ Class to control PDF presentations @@ -61,12 +60,15 @@ class PdfController(PresentationController): if self.check_installed() and self.mudrawbin: self.also_supports = ['xps'] + @staticmethod def check_binary(program_path): """ Function that checks whether a binary is either ghostscript or mudraw or neither. + Is also used from presentationtab.py """ program_type = None runlog = '' + log.debug('testing program_path: %s', program_path) try: runlog = check_output([program_path, '--help'], stderr=STDOUT) except CalledProcessError as e: @@ -75,15 +77,16 @@ class PdfController(PresentationController): runlog = '' # Analyse the output to see it the program is mudraw, ghostscript or neither for line in runlog.splitlines(): - found_mudraw = re.search('usage: mudraw.*', line) + decoded_line = line.decode() + found_mudraw = re.search('usage: mudraw.*', decoded_line) if found_mudraw: program_type = 'mudraw' break - found_gs = re.search('GPL Ghostscript.*', line) + found_gs = re.search('GPL Ghostscript.*', decoded_line) if found_gs: program_type = 'gs' break - log.info('in check_binary, found: ' + program_type) + log.debug('in check_binary, found: %s', program_type) return program_type def check_available(self): @@ -101,11 +104,11 @@ class PdfController(PresentationController): # Use the user defined program if given if (Settings().value('presentations/enable_pdf_program')): pdf_program = Settings().value('presentations/pdf_program') - type = self.check_binary(pdf_program) - if type == 'gs': + program_type = check_binary(pdf_program) + if program_type == 'gs': self.gsbin = pdf_program return True - elif type == 'mudraw': + elif program_type == 'mudraw': self.mudrawbin = pdf_program return True # Fallback to autodetection diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index cc0cf81e9..5b93bac10 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -73,7 +73,7 @@ class PresentationTab(SettingsTab): self.override_app_check_box = QtGui.QCheckBox(self.advanced_group_box) self.override_app_check_box.setObjectName('override_app_check_box') self.advanced_layout.addWidget(self.override_app_check_box) - + self.left_layout.addWidget(self.advanced_group_box) # Pdf options self.pdf_group_box = QtGui.QGroupBox(self.left_column) self.pdf_group_box.setObjectName('pdf_group_box') @@ -81,7 +81,7 @@ class PresentationTab(SettingsTab): self.pdf_layout.setObjectName('pdf_layout') self.pdf_program_check_box = QtGui.QCheckBox(self.pdf_group_box) self.pdf_program_check_box.setObjectName('pdf_program_check_box') - self.pdf_layout.addWidget(self.pdf_program_check_box) + self.pdf_layout.addRow(self.pdf_program_check_box) self.pdf_program_path_layout = QtGui.QHBoxLayout() self.pdf_program_path_layout.setObjectName('pdf_program_path_layout') self.pdf_program_path = QtGui.QLineEdit(self.pdf_group_box) @@ -95,13 +95,14 @@ class PresentationTab(SettingsTab): self.pdf_program_browse_button.setEnabled(False) self.pdf_program_path_layout.addWidget(self.pdf_program_browse_button) self.pdf_layout.addRow(self.pdf_program_path_layout) + self.left_layout.addWidget(self.pdf_group_box) + self.left_layout.addStretch() + self.right_column.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) + self.right_layout.addStretch() + # Signals and slots self.pdf_program_path.editingFinished.connect(self.on_pdf_program_path_edit_finished) self.pdf_program_browse_button.clicked.connect(self.on_pdf_program_browse_button_clicked) self.pdf_program_check_box.clicked.connect(self.on_pdf_program_check_box_clicked) - self.left_layout.addWidget(self.advanced_group_box) - self.left_layout.addWidget(self.pdf_group_box) - self.left_layout.addStretch() - self.right_layout.addStretch() def retranslateUi(self): """ @@ -203,10 +204,10 @@ class PresentationTab(SettingsTab): """ After selecting/typing in a program it is validated that it is a actually ghostscript or mudraw """ - type = None + program_type = None if self.pdf_program_path.text() != '': - type = PdfController.check_binary(self.pdf_program_path.text()) - if not type: + program_type = PdfController.check_binary(self.pdf_program_path.text()) + if not program_type: critical_error_message_box(UiStrings().Error, translate('PresentationPlugin.PresentationTab', 'The program is not ghostscript or mudraw which is required.')) self.pdf_program_path.setFocus() From 7dd341721712e96ac503e86e27dc847b0794f0e8 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sun, 29 Dec 2013 19:46:04 +0000 Subject: [PATCH 10/16] Added tests for the PdfController. --- .../presentations/lib/pdfcontroller.py | 14 ++- .../presentations/test_mediaitem.py | 11 +- .../presentations/test_pdfcontroller.py | 108 ++++++++++++++++++ tests/resources/presentations/pdf_test1.pdf | Bin 0 -> 12471 bytes 4 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 tests/functional/openlp_plugins/presentations/test_pdfcontroller.py create mode 100644 tests/resources/presentations/pdf_test1.pdf diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index 781bd0af4..ef7e0dd1c 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -4,8 +4,8 @@ ############################################################################### # OpenLP - Open Source Lyrics Projection # # --------------------------------------------------------------------------- # -# Copyright (c) 2008-2013 Raoul Snyman # -# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Copyright (c) 2008-2014 Raoul Snyman # +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # # Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # # Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # # Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # @@ -104,7 +104,7 @@ class PdfController(PresentationController): # Use the user defined program if given if (Settings().value('presentations/enable_pdf_program')): pdf_program = Settings().value('presentations/pdf_program') - program_type = check_binary(pdf_program) + program_type = self.check_binary(pdf_program) if program_type == 'gs': self.gsbin = pdf_program return True @@ -152,6 +152,9 @@ class PdfController(PresentationController): class PdfDocument(PresentationDocument): """ Class which holds information of a single presentation. + This class is not actually used to present the PDF, instead we convert to + image-serviceitem on the fly and present as such. Therefore some of the 'playback' + functions is not implemented. """ def __init__(self, controller, presentation): """ @@ -291,3 +294,8 @@ quit \n\ log.debug('is_active pdf') return self.is_loaded() and not self.hidden + def get_slide_count(self): + """ + Returns total number of slides + """ + return self.num_pages \ No newline at end of file diff --git a/tests/functional/openlp_plugins/presentations/test_mediaitem.py b/tests/functional/openlp_plugins/presentations/test_mediaitem.py index 5b5c99e78..c9c8fbbf6 100644 --- a/tests/functional/openlp_plugins/presentations/test_mediaitem.py +++ b/tests/functional/openlp_plugins/presentations/test_mediaitem.py @@ -75,11 +75,16 @@ class TestMediaItem(TestCase): presentation_controller.also_supports = [] presentation_viewer_controller = MagicMock() presentation_viewer_controller.enabled.return_value = False + pdf_controller = MagicMock() + pdf_controller.enabled.return_value = True + pdf_controller.supports = ['pdf'] + pdf_controller.also_supports = ['xps'] # Mock the controllers. self.media_item.controllers = { 'Impress': impress_controller, 'Powerpoint': presentation_controller, - 'Powerpoint Viewer': presentation_viewer_controller + 'Powerpoint Viewer': presentation_viewer_controller, + 'Pdf': pdf_controller } # WHEN: Build the file mask. @@ -92,3 +97,7 @@ class TestMediaItem(TestCase): 'The file mask should contain the odp extension') self.assertIn('*.ppt', self.media_item.on_new_file_masks, 'The file mask should contain the ppt extension') + self.assertIn('*.pdf', self.media_item.on_new_file_masks, + 'The file mask should contain the pdf extension') + self.assertIn('*.xps', self.media_item.on_new_file_masks, + 'The file mask should contain the xps extension') diff --git a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py new file mode 100644 index 000000000..7739af3c8 --- /dev/null +++ b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2014 Raoul Snyman # +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +This module contains tests for the PdfController +""" +import os +import shutil +from unittest import TestCase, SkipTest +from tempfile import mkstemp, mkdtemp + +from PyQt4 import QtGui + +from openlp.plugins.presentations.lib.pdfcontroller import PdfController, PdfDocument +from tests.functional import MagicMock +from openlp.core.common import Settings +from openlp.core.lib import ScreenList +from tests.utils.constants import TEST_RESOURCES_PATH + +__default_settings__ = { + 'presentations/enable_pdf_program': False +} + +class TestPdfController(TestCase): + """ + Test the PdfController. + """ + def setUp(self): + """ + Set up the components need for all tests. + """ + self.fd, self.ini_file = mkstemp('.ini') + Settings().set_filename(self.ini_file) + self.application = QtGui.QApplication.instance() + ScreenList.create(self.application.desktop()) + Settings().extend_default_settings(__default_settings__) + self.temp_folder = mkdtemp() + self.thumbnail_folder = mkdtemp() + + def tearDown(self): + """ + Delete all the C++ objects at the end so that we don't have a segfault + """ + del self.application + try: + os.unlink(self.ini_file) + shutil.rmtree(self.thumbnail_folder) + shutil.rmtree(self.temp_folder) + except OSError: + pass + + def constructor_test(self): + """ + Test the Constructor + """ + # GIVEN: No presentation controller + controller = None + + # WHEN: The presentation controller object is created + controller = PdfController(plugin = MagicMock()) + + # THEN: The name of the presentation controller should be correct + self.assertEqual('Pdf', controller.name, 'The name of the presentation controller should be correct') + + def load_pdf_test(self): + """ + Test loading of a Pdf + """ + # GIVEN: A Pdf-file + test_file = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf') + + # WHEN: The Pdf is loaded + controller = PdfController(plugin = MagicMock()) + if not controller.check_available(): + raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test') + controller.temp_folder = self.temp_folder + controller.thumbnail_folder = self.thumbnail_folder + document = PdfDocument(controller, test_file) + loaded = document.load_presentation() + + # THEN: The load should succeed and we should be able to get a pagecount + self.assertTrue(loaded, 'The loading of the PDF should succeed.') + self.assertEqual(3, document.get_slide_count(), 'The pagecount of the PDF should be 3.') diff --git a/tests/resources/presentations/pdf_test1.pdf b/tests/resources/presentations/pdf_test1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..012dc95483300fd0338cb0fb303e7b0e7270d346 GIT binary patch literal 12471 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 literal 0 HcmV?d00001 From 3cc69cc9fde5b0d3fde639c642af4ade15c4f3fb Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 31 Dec 2013 12:15:18 +0000 Subject: [PATCH 11/16] Changed the way a serviceitem from the servicemanager is copied and converted. Also fixed for PEP8. --- openlp/core/lib/serviceitem.py | 6 +-- openlp/plugins/presentations/lib/mediaitem.py | 28 ++++++++------ .../presentations/lib/messagelistener.py | 33 +++++++++-------- .../presentations/lib/pdfcontroller.py | 37 +++++++++++-------- .../presentations/lib/presentationtab.py | 16 ++++---- .../presentations/test_pdfcontroller.py | 9 +++-- 6 files changed, 69 insertions(+), 60 deletions(-) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index b2569f0e5..83df4d338 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -420,11 +420,7 @@ class ServiceItem(object): self._raw_frames.append(slide) elif self.service_item_type == ServiceItemType.Image: settings_section = serviceitem['serviceitem']['header']['name'] - background = None - try: - background = QtGui.QColor(Settings().value(settings_section + '/background color')) - except Exception: - pass + background = QtGui.QColor(Settings().value(settings_section + '/background color')) if path: self.has_original_files = False for text_image in serviceitem['serviceitem']['data']: diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index fcaf5319e..93768594f 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -116,7 +116,7 @@ class PresentationMediaItem(MediaManagerItem): self.display_type_label = QtGui.QLabel(self.presentation_widget) self.display_type_label.setObjectName('display_type_label') self.display_type_combo_box = create_horizontal_adjusting_combo_box(self.presentation_widget, - 'display_type_combo_box') + 'display_type_combo_box') self.display_type_label.setBuddy(self.display_type_combo_box) self.display_layout.addRow(self.display_type_label, self.display_type_combo_box) # Add the Presentation widget to the page layout. @@ -177,9 +177,8 @@ class PresentationMediaItem(MediaManagerItem): if titles.count(filename) > 0: if not initial_load: critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'), - translate('PresentationPlugin.MediaItem', - 'A presentation with that filename already exists.') - ) + translate('PresentationPlugin.MediaItem', + 'A presentation with that filename already exists.')) continue controller_name = self.findControllerByType(filename) if controller_name: @@ -203,7 +202,8 @@ class PresentationMediaItem(MediaManagerItem): icon = build_icon(':/general/general_delete.png') else: critical_error_message_box(UiStrings().UnsupportedFile, - translate('PresentationPlugin.MediaItem', 'This type of presentation is not supported.')) + translate('PresentationPlugin.MediaItem', + 'This type of presentation is not supported.')) continue item_name = QtGui.QListWidgetItem(filename) item_name.setData(QtCore.Qt.UserRole, file) @@ -238,7 +238,7 @@ class PresentationMediaItem(MediaManagerItem): Settings().setValue(self.settings_section + '/presentations files', self.get_file_list()) def generate_slide_data(self, service_item, item=None, xml_version=False, - remote=False, context=ServiceItemContext.Service, presentation_file=None): + remote=False, context=ServiceItemContext.Service, presentation_file=None): """ Load the relevant information for displaying the presentation in the slidecontroller. In the case of powerpoints, an image for each slide. @@ -251,7 +251,7 @@ class PresentationMediaItem(MediaManagerItem): return False filename = presentation_file if filename is None: - filename = items[0].data(QtCore.Qt.UserRole) + filename = items[0].data(QtCore.Qt.UserRole) file_type = os.path.splitext(filename)[1][1:] if not self.display_type_combo_box.currentText(): return False @@ -292,7 +292,8 @@ class PresentationMediaItem(MediaManagerItem): # File is no longer present if not remote: critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), - translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) + translate('PresentationPlugin.MediaItem', + 'The presentation %s no longer exists.') % filename) return False else: service_item.processor = self.display_type_combo_box.currentText() @@ -322,15 +323,18 @@ class PresentationMediaItem(MediaManagerItem): else: # File is no longer present if not remote: - critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), - translate('PresentationPlugin.MediaItem', - 'The presentation %s is incomplete, please reload.') % filename) + critical_error_message_box(translate('PresentationPlugin.MediaItem', + 'Missing Presentation'), + translate('PresentationPlugin.MediaItem', + 'The presentation %s is incomplete, please reload.') + % filename) return False else: # File is no longer present if not remote: critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), - translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) + translate('PresentationPlugin.MediaItem', + 'The presentation %s no longer exists.') % filename) return False def findControllerByType(self, filename): diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 6640fa9bb..1ad7ae860 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -28,6 +28,7 @@ ############################################################################### import logging +import copy from PyQt4 import QtCore @@ -318,24 +319,26 @@ class MessageListener(object): hide_mode = message[2] file = item.get_frame_path() self.handler = item.processor - # When starting presentation from the servicemanager/slidecontroller we convert + # When starting presentation from the servicemanager we convert # PDF/XPS-serviceitems into image-serviceitems. When started from the mediamanager - # the conversion has already been done. + # the conversion has already been done at this point. if file.endswith('.pdf') or file.endswith('.xps'): log.debug('Converting from pdf/xps to images for serviceitem with file %s', file) - # Create a new image-serviceitem which will overwrite the old one - new_item = ServiceItem() - new_item.name = 'images' + # Create a copy of the original item, and then clear the original item so it can be filled with images + item_cpy = copy.copy(item) + item.__init__(None) if is_live: - self.media_item.generate_slide_data(new_item, item, False, False, ServiceItemContext.Live, file) + self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Live, file) else: - self.media_item.generate_slide_data(new_item, item, False, False, ServiceItemContext.Preview, file) - # We need to overwrite the current serviceitem to make the slidecontroller - # present the images, so we do some copying - service_repr = {'serviceitem': new_item.get_service_repr(True) } - item.set_from_service(service_repr) - item._raw_frames = new_item._raw_frames - # When presenting PDF or XPS, we are using the image presentation code, + self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Preview, file) + # Some of the original serviceitem attributes is needed in the new serviceitem + item.footer = item_cpy.footer + item.from_service = item_cpy.from_service + item.iconic_representation = item_cpy.iconic_representation + item.image_border = item_cpy.image_border + item.main = item_cpy.main + item.theme_data = item_cpy.theme_data + # When presenting PDF or XPS, we are using the image presentation code, # so handler & processor is set to None, and we skip adding the handler. self.handler = None if self.handler == self.media_item.automatic: @@ -346,9 +349,9 @@ class MessageListener(object): controller = self.live_handler else: controller = self.preview_handler - # When presenting PDF or XPS, we are using the image presentation code, + # When presenting PDF or XPS, we are using the image presentation code, # so handler & processor is set to None, and we skip adding the handler. - if self.handler == None: + if self.handler is None: self.controller = controller else: controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3]) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index ef7e0dd1c..d403d2edd 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -38,9 +38,9 @@ from openlp.core.common import Settings from openlp.core.lib import ScreenList from .presentationcontroller import PresentationController, PresentationDocument - log = logging.getLogger(__name__) + class PdfController(PresentationController): """ Class to control PDF presentations @@ -139,7 +139,7 @@ class PdfController(PresentationController): return False else: return True - + def kill(self): """ Called at system exit to clean up any running presentations @@ -153,7 +153,7 @@ class PdfDocument(PresentationDocument): """ Class which holds information of a single presentation. This class is not actually used to present the PDF, instead we convert to - image-serviceitem on the fly and present as such. Therefore some of the 'playback' + image-serviceitem on the fly and present as such. Therefore some of the 'playback' functions is not implemented. """ def __init__(self, controller, presentation): @@ -169,7 +169,7 @@ class PdfDocument(PresentationDocument): self.num_pages = -1 def gs_get_resolution(self, size): - """ + """ Only used when using ghostscript Ghostscript can't scale automaticly while keeping aspect like mupdf, so we need to get the ratio bewteen the screen size and the PDF to scale @@ -187,17 +187,18 @@ flush \n\ quit \n\ ' # Put postscript into tempfile - tmpfile = NamedTemporaryFile(delete=False) - tmpfile.write(postscript) - tmpfile.close() + tmp_file = NamedTemporaryFile(delete=False) + tmp_file.write(postscript) + tmp_file.close() # Run the script on the pdf to get the size runlog = [] try: - runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH', '-sFile=' + self.filepath, tmpfile.name]) + runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH', + '-sFile=' + self.filepath, tmp_file.name]) except CalledProcessError as e: log.debug(' '.join(e.cmd)) log.debug(e.output) - os.unlink(tmpfile.name) + os.unlink(tmp_file.name) # Extract the pdf resolution from output, the format is " Size: x: , y: " width = 0 height = 0 @@ -205,7 +206,7 @@ quit \n\ try: width = re.search('.*Size: x: (\d+\.?\d*), y: \d+.*', line).group(1) height = re.search('.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line).group(1) - break; + break except AttributeError: pass # Calculate the ratio from pdf to screen @@ -240,18 +241,22 @@ quit \n\ if not os.path.isdir(self.get_temp_folder()): os.makedirs(self.get_temp_folder()) if self.controller.mudrawbin: - runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()), '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) + runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()), + '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) elif self.controller.gsbin: resolution = self.gs_get_resolution(size) - runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m', '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) + runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m', + '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', + '-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), + self.filepath]) created_files = sorted(os.listdir(self.get_temp_folder())) for fn in created_files: if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): self.image_files.append(os.path.join(self.get_temp_folder(), fn)) - except Exception as e: + except Exception as e: log.debug(e) log.debug(runlog) - return False + return False self.num_pages = len(self.image_files) # Create thumbnails self.create_thumbnails() @@ -277,7 +282,7 @@ quit \n\ """ log.debug('close_presentation pdf') self.controller.remove_doc(self) - + def is_loaded(self): """ Returns true if a presentation is loaded. @@ -298,4 +303,4 @@ quit \n\ """ Returns total number of slides """ - return self.num_pages \ No newline at end of file + return self.num_pages diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 4c35de1a8..5b93c10a9 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -34,6 +34,7 @@ from openlp.core.lib import SettingsTab, build_icon from openlp.core.lib.ui import critical_error_message_box from .pdfcontroller import PdfController + class PresentationTab(SettingsTab): """ PresentationsTab is the Presentations settings tab in the settings dialog. @@ -120,7 +121,6 @@ class PresentationTab(SettingsTab): self.pdf_program_check_box.setText( translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:')) - def set_controller_text(self, checkbox, controller): if checkbox.isEnabled(): checkbox.setText(controller.name) @@ -170,7 +170,6 @@ class PresentationTab(SettingsTab): if Settings().value(setting_key) != self.override_app_check_box.checkState(): Settings().setValue(setting_key, self.override_app_check_box.checkState()) changed = True - # Save pdf-settings pdf_program = self.pdf_program_path.text() enable_pdf_program = self.pdf_program_check_box.checkState() @@ -183,7 +182,6 @@ class PresentationTab(SettingsTab): if enable_pdf_program != Settings().value(self.settings_section + '/enable_pdf_program'): Settings().setValue(self.settings_section + '/enable_pdf_program', enable_pdf_program) changed = True - if changed: self.settings_form.register_post_process('mediaitem_suffix_reset') self.settings_form.register_post_process('mediaitem_presentation_rebuild') @@ -199,24 +197,26 @@ class PresentationTab(SettingsTab): checkbox = self.presenter_check_boxes[controller.name] checkbox.setEnabled(controller.is_available()) self.set_controller_text(checkbox, controller) - + def on_pdf_program_path_edit_finished(self): """ After selecting/typing in a program it is validated that it is a actually ghostscript or mudraw """ - program_type = None + program_type = None if self.pdf_program_path.text() != '': program_type = PdfController.check_binary(self.pdf_program_path.text()) if not program_type: - critical_error_message_box(UiStrings().Error, - translate('PresentationPlugin.PresentationTab', 'The program is not ghostscript or mudraw which is required.')) + critical_error_message_box(UiStrings().Error, + translate('PresentationPlugin.PresentationTab', + 'The program is not ghostscript or mudraw which is required.')) self.pdf_program_path.setFocus() def on_pdf_program_browse_button_clicked(self): """ Select the mudraw or ghostscript binary that should be used. """ - filename = QtGui.QFileDialog.getOpenFileName(self, translate('PresentationPlugin.PresentationTab', 'Select mudraw or ghostscript binary.')) + filename = QtGui.QFileDialog.getOpenFileName(self, translate('PresentationPlugin.PresentationTab', + 'Select mudraw or ghostscript binary.')) if filename: self.pdf_program_path.setText(filename) self.pdf_program_path.setFocus() diff --git a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py index 7739af3c8..cd2e4f744 100644 --- a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py +++ b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py @@ -46,6 +46,7 @@ __default_settings__ = { 'presentations/enable_pdf_program': False } + class TestPdfController(TestCase): """ Test the PdfController. @@ -82,7 +83,7 @@ class TestPdfController(TestCase): controller = None # WHEN: The presentation controller object is created - controller = PdfController(plugin = MagicMock()) + controller = PdfController(plugin=MagicMock()) # THEN: The name of the presentation controller should be correct self.assertEqual('Pdf', controller.name, 'The name of the presentation controller should be correct') @@ -93,16 +94,16 @@ class TestPdfController(TestCase): """ # GIVEN: A Pdf-file test_file = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf') - + # WHEN: The Pdf is loaded - controller = PdfController(plugin = MagicMock()) + controller = PdfController(plugin=MagicMock()) if not controller.check_available(): raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test') controller.temp_folder = self.temp_folder controller.thumbnail_folder = self.thumbnail_folder document = PdfDocument(controller, test_file) loaded = document.load_presentation() - + # THEN: The load should succeed and we should be able to get a pagecount self.assertTrue(loaded, 'The loading of the PDF should succeed.') self.assertEqual(3, document.get_slide_count(), 'The pagecount of the PDF should be 3.') From b242343d5996d6b57fb492fdec019e25497bc1c7 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Wed, 1 Jan 2014 15:31:06 +0000 Subject: [PATCH 12/16] Added some docstring documentation. --- .../presentations/lib/pdfcontroller.py | 24 +++++++++++++++++-- .../presentations/lib/presentationtab.py | 5 ++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index d403d2edd..be6a03c35 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -50,6 +50,8 @@ class PdfController(PresentationController): def __init__(self, plugin): """ Initialise the class + + :param plugin: The plugin that creates the controller. """ log.debug('Initialising') self.process = None @@ -65,6 +67,9 @@ class PdfController(PresentationController): """ Function that checks whether a binary is either ghostscript or mudraw or neither. Is also used from presentationtab.py + + :param program_path:The full path to the binary to check. + :return: Type of the binary, 'gs' if ghostscript, 'mudraw' if mudraw, None if invalid. """ program_type = None runlog = '' @@ -92,6 +97,8 @@ class PdfController(PresentationController): def check_available(self): """ PdfController is able to run on this machine. + + :return: True if program to open PDF-files was found, otherwise False. """ log.debug('check_available Pdf') return self.check_installed() @@ -99,6 +106,8 @@ class PdfController(PresentationController): def check_installed(self): """ Check the viewer is installed. + + :return: True if program to open PDF-files was found, otherwise False. """ log.debug('check_installed Pdf') # Use the user defined program if given @@ -171,8 +180,11 @@ class PdfDocument(PresentationDocument): def gs_get_resolution(self, size): """ Only used when using ghostscript - Ghostscript can't scale automaticly while keeping aspect like mupdf, so we need - to get the ratio bewteen the screen size and the PDF to scale + Ghostscript can't scale automatically while keeping aspect like mupdf, so we need + to get the ratio between the screen size and the PDF to scale + + :param size: Size struct containing the screen size. + :return: The resolution dpi to be used. """ # Use a postscript script to get size of the pdf. It is assumed that all pages have same size postscript = '%!PS \n\ @@ -224,6 +236,8 @@ quit \n\ def load_presentation(self): """ Called when a presentation is added to the SlideController. It generates images from the PDF. + + :return: True is loading succeeded, otherwise False. """ log.debug('load_presentation pdf') # Check if the images has already been created, and if yes load them @@ -286,6 +300,8 @@ quit \n\ def is_loaded(self): """ Returns true if a presentation is loaded. + + :return: True if loaded, False if not. """ log.debug('is_loaded pdf') if self.num_pages < 0: @@ -295,6 +311,8 @@ quit \n\ def is_active(self): """ Returns true if a presentation is currently active. + + :return: True if active, False if not. """ log.debug('is_active pdf') return self.is_loaded() and not self.hidden @@ -302,5 +320,7 @@ quit \n\ def get_slide_count(self): """ Returns total number of slides + + :return: The number of pages in the presentation.. """ return self.num_pages diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 5b93c10a9..8c5997a61 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -225,6 +225,8 @@ class PresentationTab(SettingsTab): """ When checkbox for manual entering pdf-program is clicked, enable or disable the textbox for the programpath and the browse-button. + + :param checked: If the box is checked or not. """ self.pdf_program_path.setReadOnly(not checked) self.pdf_program_path.setPalette(self.get_grey_text_palette(not checked)) @@ -233,6 +235,9 @@ class PresentationTab(SettingsTab): def get_grey_text_palette(self, greyed): """ Returns a QPalette with greyed out text as used for placeholderText. + + :param greyed: Determines whether the palette should be grayed. + :return: The created palette. """ palette = QtGui.QPalette() color = self.palette().color(QtGui.QPalette.Active, QtGui.QPalette.Text) From d02377f74f1a1df58f067c82c822192a6039918f Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 6 Jan 2014 10:51:53 +0000 Subject: [PATCH 13/16] Moved PS script into separate file. --- .../lib/ghostscript_get_resolution.ps | 10 ++++++++ .../presentations/lib/pdfcontroller.py | 23 ++++--------------- 2 files changed, 14 insertions(+), 19 deletions(-) create mode 100644 openlp/plugins/presentations/lib/ghostscript_get_resolution.ps diff --git a/openlp/plugins/presentations/lib/ghostscript_get_resolution.ps b/openlp/plugins/presentations/lib/ghostscript_get_resolution.ps new file mode 100644 index 000000000..67c14e1b2 --- /dev/null +++ b/openlp/plugins/presentations/lib/ghostscript_get_resolution.ps @@ -0,0 +1,10 @@ +%!PS +() = +File dup (r) file runpdfbegin +1 pdfgetpage dup +/MediaBox pget { +aload pop exch 4 1 roll exch sub 3 1 roll sub +( Size: x: ) print =print (, y: ) print =print (\n) print +} if +flush +quit diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index be6a03c35..4b8cf5aab 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -187,37 +187,22 @@ class PdfDocument(PresentationDocument): :return: The resolution dpi to be used. """ # Use a postscript script to get size of the pdf. It is assumed that all pages have same size - postscript = '%!PS \n\ -() = \n\ -File dup (r) file runpdfbegin \n\ -1 pdfgetpage dup \n\ -/MediaBox pget { \n\ -aload pop exch 4 1 roll exch sub 3 1 roll sub \n\ -( Size: x: ) print =print (, y: ) print =print (\n) print \n\ -} if \n\ -flush \n\ -quit \n\ -' - # Put postscript into tempfile - tmp_file = NamedTemporaryFile(delete=False) - tmp_file.write(postscript) - tmp_file.close() + gs_resolution_script = AppLocation.get_directory(AppLocation.PluginsDir) + '/presentations/lib/ghostscript_get_resolution.ps' # Run the script on the pdf to get the size runlog = [] try: runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH', - '-sFile=' + self.filepath, tmp_file.name]) + '-sFile=' + self.filepath, gs_resolution_script]) except CalledProcessError as e: log.debug(' '.join(e.cmd)) log.debug(e.output) - os.unlink(tmp_file.name) # Extract the pdf resolution from output, the format is " Size: x: , y: " width = 0 height = 0 for line in runlog.splitlines(): try: - width = re.search('.*Size: x: (\d+\.?\d*), y: \d+.*', line).group(1) - height = re.search('.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line).group(1) + width = int(re.search('.*Size: x: (\d+\.?\d*), y: \d+.*', line.decode()).group(1)) + height = int(re.search('.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line.decode()).group(1)) break except AttributeError: pass From e958c0465948a7c34bcdc6af9e05bdb14cf29f83 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 6 Jan 2014 12:06:22 +0000 Subject: [PATCH 14/16] Make the PdfController reload backend if setting changes. --- openlp/plugins/presentations/lib/mediaitem.py | 3 + .../presentations/lib/pdfcontroller.py | 66 ++++++++++--------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 5eb041ba8..b5bc492c1 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -138,6 +138,9 @@ class PresentationMediaItem(MediaManagerItem): """ self.display_type_combo_box.clear() for item in self.controllers: + # For PDF reload backend, since it can have changed + if self.controllers[item].name == 'Pdf': + self.controllers[item].check_available() # load the drop down selection if self.controllers[item].enabled(): self.display_type_combo_box.addItem(item) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index 4b8cf5aab..42dd5960d 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -57,10 +57,9 @@ class PdfController(PresentationController): self.process = None PresentationController.__init__(self, plugin, 'Pdf', PdfDocument) self.supports = ['pdf'] - self.mudrawbin = '' - self.gsbin = '' - if self.check_installed() and self.mudrawbin: - self.also_supports = ['xps'] + self.also_supports = [] + # Determine whether mudraw or ghostscript is used + self.check_installed() @staticmethod def check_binary(program_path): @@ -110,44 +109,49 @@ class PdfController(PresentationController): :return: True if program to open PDF-files was found, otherwise False. """ log.debug('check_installed Pdf') + self.mudrawbin = '' + self.gsbin = '' + self.also_supports = [] # Use the user defined program if given if (Settings().value('presentations/enable_pdf_program')): pdf_program = Settings().value('presentations/pdf_program') program_type = self.check_binary(pdf_program) if program_type == 'gs': self.gsbin = pdf_program - return True elif program_type == 'mudraw': self.mudrawbin = pdf_program - return True - # Fallback to autodetection - application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.name == 'nt': - # for windows we only accept mudraw.exe in the base folder + else: + # Fallback to autodetection application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.path.isfile(application_path + '/../mudraw.exe'): - self.mudrawbin = application_path + '/../mudraw.exe' - else: - # First try to find mupdf - try: - self.mudrawbin = check_output(['which', 'mudraw']).decode(encoding='UTF-8').rstrip('\n') - except CalledProcessError: - self.mudrawbin = '' - # if mupdf isn't installed, fallback to ghostscript - if not self.mudrawbin: - try: - self.gsbin = check_output(['which', 'gs']).rstrip('\n') - except CalledProcessError: - self.gsbin = '' - # Last option: check if mudraw is placed in OpenLP base folder - if not self.mudrawbin and not self.gsbin: + if os.name == 'nt': + # for windows we only accept mudraw.exe in the base folder application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.path.isfile(application_path + '/../mudraw'): - self.mudrawbin = application_path + '/../mudraw' - if not self.mudrawbin and not self.gsbin: - return False - else: + if os.path.isfile(application_path + '/../mudraw.exe'): + self.mudrawbin = application_path + '/../mudraw.exe' + else: + # First try to find mupdf + try: + self.mudrawbin = check_output(['which', 'mudraw']).decode(encoding='UTF-8').rstrip('\n') + except CalledProcessError: + self.mudrawbin = '' + # if mupdf isn't installed, fallback to ghostscript + if not self.mudrawbin: + try: + self.gsbin = check_output(['which', 'gs']).rstrip('\n') + except CalledProcessError: + self.gsbin = '' + # Last option: check if mudraw is placed in OpenLP base folder + if not self.mudrawbin and not self.gsbin: + application_path = AppLocation.get_directory(AppLocation.AppDir) + if os.path.isfile(application_path + '/../mudraw'): + self.mudrawbin = application_path + '/../mudraw' + if self.mudrawbin: + self.also_supports = ['xps'] return True + elif self.gsbin: + return True + else: + return False def kill(self): """ From c3af8d5d14a5eddff03c099429ddcf390b227074 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 14 Jan 2014 21:57:43 +0000 Subject: [PATCH 15/16] Only allow pdf-program selection using filedialog. --- .../presentations/lib/presentationtab.py | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 8c5997a61..7d5c81366 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -101,7 +101,6 @@ class PresentationTab(SettingsTab): self.right_column.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) self.right_layout.addStretch() # Signals and slots - self.pdf_program_path.editingFinished.connect(self.on_pdf_program_path_edit_finished) self.pdf_program_browse_button.clicked.connect(self.on_pdf_program_browse_button_clicked) self.pdf_program_check_box.clicked.connect(self.on_pdf_program_check_box_clicked) @@ -139,7 +138,6 @@ class PresentationTab(SettingsTab): # load pdf-program settings enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program') self.pdf_program_check_box.setChecked(enable_pdf_program) - self.pdf_program_path.setReadOnly(not enable_pdf_program) self.pdf_program_path.setPalette(self.get_grey_text_palette(not enable_pdf_program)) self.pdf_program_browse_button.setEnabled(enable_pdf_program) pdf_program = Settings().value(self.settings_section + '/pdf_program') @@ -198,28 +196,21 @@ class PresentationTab(SettingsTab): checkbox.setEnabled(controller.is_available()) self.set_controller_text(checkbox, controller) - def on_pdf_program_path_edit_finished(self): - """ - After selecting/typing in a program it is validated that it is a actually ghostscript or mudraw - """ - program_type = None - if self.pdf_program_path.text() != '': - program_type = PdfController.check_binary(self.pdf_program_path.text()) - if not program_type: - critical_error_message_box(UiStrings().Error, - translate('PresentationPlugin.PresentationTab', - 'The program is not ghostscript or mudraw which is required.')) - self.pdf_program_path.setFocus() - def on_pdf_program_browse_button_clicked(self): """ Select the mudraw or ghostscript binary that should be used. """ filename = QtGui.QFileDialog.getOpenFileName(self, translate('PresentationPlugin.PresentationTab', - 'Select mudraw or ghostscript binary.')) + 'Select mudraw or ghostscript binary.'), + self.pdf_program_path.text()) if filename: - self.pdf_program_path.setText(filename) - self.pdf_program_path.setFocus() + program_type = PdfController.check_binary(filename) + if not program_type: + critical_error_message_box(UiStrings().Error, + translate('PresentationPlugin.PresentationTab', + 'The program is not ghostscript or mudraw which is required.')) + else: + self.pdf_program_path.setText(filename) def on_pdf_program_check_box_clicked(self, checked): """ @@ -228,7 +219,6 @@ class PresentationTab(SettingsTab): :param checked: If the box is checked or not. """ - self.pdf_program_path.setReadOnly(not checked) self.pdf_program_path.setPalette(self.get_grey_text_palette(not checked)) self.pdf_program_browse_button.setEnabled(checked) From 21eeac70ebea2a4e72ccd65f441558799fa65e2e Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 21 Feb 2014 23:27:00 +0100 Subject: [PATCH 16/16] fixed an exception --- openlp/plugins/presentations/lib/pdfcontroller.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index 42dd5960d..36eddac2f 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -129,15 +129,16 @@ class PdfController(PresentationController): if os.path.isfile(application_path + '/../mudraw.exe'): self.mudrawbin = application_path + '/../mudraw.exe' else: + DEVNULL = open(os.devnull, 'wb') # First try to find mupdf try: - self.mudrawbin = check_output(['which', 'mudraw']).decode(encoding='UTF-8').rstrip('\n') + self.mudrawbin = check_output(['which', 'mudraw'], stderr=DEVNULL).decode(encoding='UTF-8').rstrip('\n') except CalledProcessError: self.mudrawbin = '' # if mupdf isn't installed, fallback to ghostscript if not self.mudrawbin: try: - self.gsbin = check_output(['which', 'gs']).rstrip('\n') + self.gsbin = check_output(['which', 'gs'], stderr=DEVNULL).decode(encoding='UTF-8').rstrip('\n') except CalledProcessError: self.gsbin = '' # Last option: check if mudraw is placed in OpenLP base folder