openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #33888
[Merge] lp:~bastian-germann/openlp/pymupdf into lp:openlp
Bastian Germann has proposed merging lp:~bastian-germann/openlp/pymupdf into lp:openlp.
Commit message:
Add PyMuPDF as additional PDF controller and missing mupdf file formats
Requested reviews:
Tomas Groth (tomasgroth)
Phill (phill-ridout)
For more details, see:
https://code.launchpad.net/~bastian-germann/openlp/pymupdf/+merge/366851
PyMuPDF itself is GPLv3+ licenced. However, MuPDF is AGPLv3+ licenced. You can argue that calling the executable via subprocess does not make OpenLP a derivative work of MuPDF, however, using the library this argument does not hold. So if the new code is used, people running OpenLP with PyMuPDF have to comply with AGPLv3+. That means the source code of the actual running MuPDF has to be provided to remote users (if there are any). For the original MuPDF versions this is done by MuPDF's authors. If someone has changes to MuPDF, these changes would have to be provided via a network service to remote users.
People who have private changes to MuPDF will probably know about their obligations.
As a reminder I used agpl-pdf as a keyword for the dependency.
--
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/plugins/presentations/lib/messagelistener.py'
--- openlp/plugins/presentations/lib/messagelistener.py 2019-04-13 13:00:22 +0000
+++ openlp/plugins/presentations/lib/messagelistener.py 2019-05-02 17:33:11 +0000
@@ -333,7 +333,8 @@
# the conversion has already been done at this point.
file_type = file_path.suffix.lower()[1:]
if file_type in PDF_CONTROLLER_FILETYPES:
- log.debug('Converting from pdf/xps/oxps to images for serviceitem with file {name}'.format(name=file_path))
+ log.debug('Converting from pdf/xps/oxps/epub/cbz/fb2 to images for serviceitem with file {name}'
+ .format(name=file_path))
# 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)
=== modified file 'openlp/plugins/presentations/lib/pdfcontroller.py'
--- openlp/plugins/presentations/lib/pdfcontroller.py 2019-04-13 13:00:22 +0000
+++ openlp/plugins/presentations/lib/pdfcontroller.py 2019-05-02 17:33:11 +0000
@@ -34,9 +34,15 @@
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']
+PDF_CONTROLLER_FILETYPES = ['pdf', 'xps', 'oxps', 'epub', 'cbz', 'fb2']
class PdfController(PresentationController):
@@ -121,6 +127,9 @@
self.mudrawbin = program_path
elif program_type == 'mutool':
self.mutoolbin = program_path
+ elif PYMUPDF_AVAILABLE:
+ self.also_supports = ['xps', 'oxps', 'epub', 'cbz', 'fb2']
+ return True
else:
# Fallback to autodetection
application_path = AppLocation.get_directory(AppLocation.AppDir)
@@ -147,12 +156,11 @@
elif (application_path / 'mutool').is_file():
self.mutoolbin = application_path / 'mutool'
if self.mudrawbin or self.mutoolbin:
- self.also_supports = ['xps', 'oxps']
+ self.also_supports = ['xps', 'oxps', 'epub', 'cbz', 'fb2']
return True
elif self.gsbin:
return True
- else:
- return False
+ return False
def kill(self):
"""
@@ -276,6 +284,16 @@
'-r{res}'.format(res=resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
'-sOutputFile={output}'.format(output=temp_dir_path / 'mainslide%03d.png'),
str(self.file_path)], startupinfo=self.startupinfo)
+ elif PYMUPDF_AVAILABLE:
+ 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():
=== modified file 'scripts/appveyor.yml'
--- scripts/appveyor.yml 2019-04-02 00:05:46 +0000
+++ scripts/appveyor.yml 2019-05-02 17:33:11 +0000
@@ -16,11 +16,7 @@
install:
# Install dependencies from pypi
- - "%PYTHON%\\python.exe -m pip install sqlalchemy alembic appdirs chardet beautifulsoup4 lxml Mako mysql-connector-python pytest mock pyodbc psycopg2 pypiwin32 websockets asyncio waitress six webob requests QtAwesome PyQt5 PyQtWebEngine pymediainfo"
- # Download and unpack mupdf
- - appveyor DownloadFile https://mupdf.com/downloads/archive/mupdf-1.14.0-windows.zip
- - 7z x mupdf-1.14.0-windows.zip
- - cp mupdf-1.14.0-windows/mutool.exe openlp-branch/mutool.exe
+ - "%PYTHON%\\python.exe -m pip install sqlalchemy alembic appdirs chardet beautifulsoup4 lxml Mako mysql-connector-python pytest mock pyodbc psycopg2 pypiwin32 websockets asyncio waitress six webob requests QtAwesome PyQt5 PyQtWebEngine pymediainfo PyMuPDF"
build: off
=== modified file 'setup.py'
--- setup.py 2019-04-13 13:00:22 +0000
+++ setup.py 2019-05-02 17:33:11 +0000
@@ -187,6 +187,7 @@
'websockets'
],
extras_require={
+ 'agpl-pdf': ['PyMuPDF'],
'darkstyle': ['QDarkStyle'],
'mysql': ['mysql-connector-python'],
'odbc': ['pyodbc'],
@@ -200,6 +201,7 @@
tests_require=[
'nose2',
'pylint',
+ 'PyMuPDF',
'pyodbc',
'pysword',
'python-xlib; platform_system=="Linux"'
=== modified file 'tests/functional/openlp_plugins/presentations/test_mediaitem.py'
--- tests/functional/openlp_plugins/presentations/test_mediaitem.py 2019-04-13 13:00:22 +0000
+++ tests/functional/openlp_plugins/presentations/test_mediaitem.py 2019-05-02 17:33:11 +0000
@@ -65,7 +65,7 @@
pdf_controller = MagicMock()
pdf_controller.enabled.return_value = True
pdf_controller.supports = ['pdf']
- pdf_controller.also_supports = ['xps', 'oxps']
+ pdf_controller.also_supports = ['xps', 'oxps', 'epub', 'cbz', 'fb2']
# Mock the controllers.
self.media_item.controllers = {
'Impress': impress_controller,
@@ -85,6 +85,9 @@
assert '*.pdf' in self.media_item.on_new_file_masks, 'The file mask should contain the pdf extension'
assert '*.xps' in self.media_item.on_new_file_masks, 'The file mask should contain the xps extension'
assert '*.oxps' in self.media_item.on_new_file_masks, 'The file mask should contain the oxps extension'
+ assert '*.epub' in self.media_item.on_new_file_masks, 'The file mask should contain the epub extension'
+ assert '*.cbz' in self.media_item.on_new_file_masks, 'The file mask should contain the cbz extension'
+ assert '*.fb2' in self.media_item.on_new_file_masks, 'The file mask should contain the fb2 extension'
def test_clean_up_thumbnails(self):
"""
=== modified file 'tests/functional/openlp_plugins/presentations/test_pdfcontroller.py'
--- tests/functional/openlp_plugins/presentations/test_pdfcontroller.py 2019-04-13 13:00:22 +0000
+++ tests/functional/openlp_plugins/presentations/test_pdfcontroller.py 2019-05-02 17:33:11 +0000
@@ -23,8 +23,9 @@
This module contains tests for the PdfController
"""
import os
+from shutil import which
from tempfile import mkdtemp
-from unittest import SkipTest, TestCase
+from unittest import TestCase
from unittest.mock import MagicMock, patch
from PyQt5 import QtCore, QtGui
@@ -39,7 +40,8 @@
__default_settings__ = {
- 'presentations/enable_pdf_program': False,
+ 'presentations/enable_pdf_program': True,
+ 'presentations/pdf_program': None,
'presentations/thumbnail_scheme': ''
}
@@ -113,17 +115,16 @@
# THEN: The name of the presentation controller should be correct
assert 'Pdf' == controller.name, 'The name of the presentation controller should be correct'
- def test_load_pdf(self):
+ def load_pdf(self, exe_path):
"""
- Test loading of a Pdf using the PdfController
+ Test loading a Pdf using the PdfController
"""
# GIVEN: A Pdf-file
test_file_path = RESOURCE_PATH / 'presentations' / 'pdf_test1.pdf'
# WHEN: The Pdf is loaded
+ Settings().setValue('presentations/pdf_program', exe_path)
controller = PdfController(plugin=self.mock_plugin)
- if not controller.check_available():
- raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test')
controller.temp_folder = self.temp_folder_path
controller.thumbnail_folder = self.thumbnail_folder_path
document = PdfDocument(controller, test_file_path)
@@ -133,23 +134,22 @@
assert loaded is True, 'The loading of the PDF should succeed.'
assert 3 == document.get_slide_count(), 'The pagecount of the PDF should be 3.'
- def test_load_pdf_pictures(self):
+ def load_pdf_pictures(self, exe_path):
"""
- Test loading of a Pdf and check size of generate pictures
+ Test loading a Pdf and check the generated pictures' size
"""
# GIVEN: A Pdf-file
test_file_path = RESOURCE_PATH / 'presentations' / 'pdf_test1.pdf'
# WHEN: The Pdf is loaded
+ Settings().setValue('presentations/pdf_program', exe_path)
controller = PdfController(plugin=self.mock_plugin)
- if not controller.check_available():
- raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test')
controller.temp_folder = self.temp_folder_path
controller.thumbnail_folder = self.thumbnail_folder_path
document = PdfDocument(controller, test_file_path)
loaded = document.load_presentation()
- # THEN: The load should succeed and pictures should be created and have been scales to fit the screen
+ # THEN: The load should succeed and pictures should be created and have been scaled to fit the screen
assert loaded is True, 'The loading of the PDF should succeed.'
image = QtGui.QImage(os.path.join(str(self.temp_folder_path), 'pdf_test1.pdf', 'mainslide001.png'))
# Based on the converter used the resolution will differ a bit
@@ -163,6 +163,19 @@
assert image.height() == height, 'The height should be {height}'.format(height=height)
assert image.width() == width, 'The width should be {width}'.format(width=width)
+ def test_load_pdf(self):
+ """
+ Test loading a Pdf with each of the installed backends
+ """
+ for exe_name in ['gs', 'mutool', 'mudraw']:
+ exe_path = which(exe_name)
+ if exe_path:
+ self.load_pdf(exe_path)
+ self.load_pdf_pictures(exe_path)
+ # PyMuPDF
+ self.load_pdf(None)
+ self.load_pdf_pictures(None)
+
@patch('openlp.plugins.presentations.lib.pdfcontroller.check_binary_exists')
def test_process_check_binary_mudraw(self, mocked_check_binary_exists):
"""
Follow ups