forked from openlp/openlp
212 lines
7.5 KiB
Python
212 lines
7.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
##########################################################################
|
|
# OpenLP - Open Source Lyrics Projection #
|
|
# ---------------------------------------------------------------------- #
|
|
# Copyright (c) 2008-2020 OpenLP Developers #
|
|
# ---------------------------------------------------------------------- #
|
|
# 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, either version 3 of the License, or #
|
|
# (at your option) any later version. #
|
|
# #
|
|
# 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, see <https://www.gnu.org/licenses/>. #
|
|
##########################################################################
|
|
import logging
|
|
|
|
from openlp.core.common import is_win
|
|
from openlp.core.display.screens import ScreenList
|
|
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
|
|
|
|
|
|
if is_win():
|
|
from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
|
|
|
|
try:
|
|
import fitz
|
|
PYMUPDF_AVAILABLE = True
|
|
except ImportError:
|
|
PYMUPDF_AVAILABLE = False
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
PDF_CONTROLLER_FILETYPES = ['pdf', 'xps', 'oxps', 'epub', 'cbz', 'fb2']
|
|
|
|
|
|
class PdfController(PresentationController):
|
|
"""
|
|
Class to control PDF presentations
|
|
"""
|
|
log.info('PdfController loaded')
|
|
|
|
def __init__(self, plugin):
|
|
"""
|
|
Initialise the class
|
|
|
|
:param plugin: The plugin that creates the controller.
|
|
"""
|
|
log.debug('Initialising')
|
|
super(PdfController, self).__init__(plugin, 'Pdf', PdfDocument)
|
|
self.process = None
|
|
self.supports = ['pdf']
|
|
self.also_supports = []
|
|
# Determine whether mudraw or ghostscript is used
|
|
self.check_installed()
|
|
|
|
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()
|
|
|
|
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')
|
|
self.also_supports = []
|
|
if PYMUPDF_AVAILABLE:
|
|
self.also_supports = ['xps', 'oxps', 'epub', 'cbz', 'fb2']
|
|
return True
|
|
return False
|
|
|
|
def kill(self):
|
|
"""
|
|
Called at system exit to clean up any running presentations
|
|
"""
|
|
log.debug('Kill pdfviewer')
|
|
while self.docs:
|
|
self.docs[0].close_presentation()
|
|
|
|
|
|
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, document_path):
|
|
"""
|
|
Constructor, store information about the file and initialise.
|
|
|
|
:param pathlib.Path document_path: Path to the document to load
|
|
:rtype: None
|
|
"""
|
|
log.debug('Init Presentation Pdf')
|
|
super().__init__(controller, document_path)
|
|
self.presentation = None
|
|
self.blanked = False
|
|
self.hidden = False
|
|
self.image_files = []
|
|
self.num_pages = -1
|
|
# Setup startupinfo options for check_output to avoid console popping up on windows
|
|
if is_win():
|
|
self.startupinfo = STARTUPINFO()
|
|
self.startupinfo.dwFlags |= STARTF_USESHOWWINDOW
|
|
else:
|
|
self.startupinfo = None
|
|
|
|
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')
|
|
temp_dir_path = self.get_temp_folder()
|
|
# Check if the images has already been created, and if yes load them
|
|
if (temp_dir_path / 'mainslide001.png').is_file():
|
|
created_files = sorted(temp_dir_path.glob('*'))
|
|
for image_path in created_files:
|
|
if image_path.is_file():
|
|
self.image_files.append(image_path)
|
|
self.num_pages = len(self.image_files)
|
|
return True
|
|
size = ScreenList().current.display_geometry
|
|
# Generate images from PDF that will fit the frame.
|
|
runlog = ''
|
|
if not temp_dir_path.is_dir():
|
|
temp_dir_path.mkdir(parents=True)
|
|
try:
|
|
log.debug('loading presentation using PyMuPDF')
|
|
pdf = fitz.open(str(self.file_path))
|
|
for i, page in enumerate(pdf, start=1):
|
|
src_size = page.bound().round()
|
|
# keep aspect ratio
|
|
scale = min(size.width() / src_size.width, size.height() / src_size.height)
|
|
m = fitz.Matrix(scale, scale)
|
|
page.getPixmap(m, alpha=False).writeImage(str(temp_dir_path / 'mainslide{:03d}.png'.format(i)))
|
|
pdf.close()
|
|
created_files = sorted(temp_dir_path.glob('*'))
|
|
for image_path in created_files:
|
|
if image_path.is_file():
|
|
self.image_files.append(image_path)
|
|
except Exception:
|
|
log.exception(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('create_thumbnails pdf')
|
|
if self.check_thumbnails():
|
|
return
|
|
# 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('close_presentation pdf')
|
|
self.controller.remove_doc(self)
|
|
|
|
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:
|
|
return False
|
|
return True
|
|
|
|
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
|
|
|
|
def get_slide_count(self):
|
|
"""
|
|
Returns total number of slides
|
|
|
|
:return: The number of pages in the presentation..
|
|
"""
|
|
return self.num_pages
|