From 57b84fc50398d7eb92f1db59de1e7854212015e0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 17:03:38 +0200 Subject: [PATCH] [Fixes #10134] New simple renderer to generate thumbnails for PDFs (#10135) (#10136) * PDF thumbnail renderer * - add unit tests * command to generate thumbnails for docs * flake fix * renamed management command * add requirement to setup.cfg * make command similar to other sync commands * removed unused import * fix flake8 Co-authored-by: marthamareal Co-authored-by: Giovanni Allegri Co-authored-by: marthamareal --- .../commands/sync_geonode_documents.py | 48 ++++++++++++++++++ geonode/documents/tasks.py | 40 +++++++++++++++ geonode/documents/tests.py | 18 +++++++ geonode/documents/tests/data/pdf_doc.pdf | Bin 0 -> 10919 bytes requirements.txt | 1 + setup.cfg | 1 + 6 files changed, 108 insertions(+) create mode 100644 geonode/documents/management/commands/sync_geonode_documents.py create mode 100644 geonode/documents/tests/data/pdf_doc.pdf diff --git a/geonode/documents/management/commands/sync_geonode_documents.py b/geonode/documents/management/commands/sync_geonode_documents.py new file mode 100644 index 00000000000..58a8e548a61 --- /dev/null +++ b/geonode/documents/management/commands/sync_geonode_documents.py @@ -0,0 +1,48 @@ +######################################################################### +# +# Copyright (C) 2022 OSGeo +# +# 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 . +# +######################################################################### + +import logging + +from django.core.management.base import BaseCommand +from geonode.documents.models import Document +from geonode.documents.tasks import create_document_thumbnail + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = ("Update documents. For the moment only thumbnails can be updated") + + def add_arguments(self, parser): + parser.add_argument( + '--updatethumbnails', + action='store_true', + dest="updatethumbnails", + default=False, + help="Update the document thumbnails.") + + def handle(self, *args, **options): + updatethumbnails = options.get('updatethumbnails') + for doc in Document.objects.all(): + if updatethumbnails: + if doc.thumbnail_url is None or doc.thumbnail_url == '': + try: + create_document_thumbnail(doc.id) + except Exception: + logger.error(f"[ERROR] Thumbnail for [{doc.name}] couldn't be created") diff --git a/geonode/documents/tasks.py b/geonode/documents/tasks.py index 2f293d03547..99e125e5649 100644 --- a/geonode/documents/tasks.py +++ b/geonode/documents/tasks.py @@ -16,9 +16,11 @@ # along with this program. If not, see . # ######################################################################### +import os import io from PIL import Image +import fitz from celery.utils.log import get_task_logger @@ -31,6 +33,39 @@ logger = get_task_logger(__name__) +class DocumentRenderer(): + FILETYPES = ['pdf'] + + def __init__(self) -> None: + pass + + def supports(self, filename): + return self._get_filetype(filename) in self.FILETYPES + + def render(self, filename): + content = None + if self.supports(filename): + filetype = self._get_filetype(filename) + render = getattr(self, f'render_{filetype}') + content = render(filename) + return content + + def render_pdf(self, filename): + try: + doc = fitz.open(filename) + pix = doc[0].get_pixmap(matrix=fitz.Matrix(2.0, 2.0)) + return pix.pil_tobytes(format="PNG") + except Exception as e: + logger.warning(f'Cound not generate thumbnail for {filename}: {e}') + return None + + def _get_filetype(self, filname): + return os.path.splitext(filname)[1][1:] + + +doc_renderer = DocumentRenderer() + + @app.task( bind=True, name='geonode.documents.tasks.create_document_thumbnail', @@ -75,6 +110,11 @@ def create_document_thumbnail(self, object_id): if image_file is not None: image_file.close() + elif doc_renderer.supports(document.files[0]): + try: + thumbnail_content = doc_renderer.render(document.files[0]) + except Exception as e: + print(e) if not thumbnail_content: logger.warning(f"Thumbnail for document #{object_id} empty.") ResourceBase.objects.filter(id=document.id).update(thumbnail_url=None) diff --git a/geonode/documents/tests.py b/geonode/documents/tests.py index 8640a01e40a..3ad6c36fbf7 100644 --- a/geonode/documents/tests.py +++ b/geonode/documents/tests.py @@ -286,6 +286,7 @@ def test_non_image_documents_thumbnail(self): def test_image_documents_thumbnail(self): self.client.login(username='admin', password='admin') try: + # test image doc with open(os.path.join(f"{self.project_root}", "tests/data/img.gif"), "rb") as f: data = { 'title': "img File Doc", @@ -303,8 +304,25 @@ def test_image_documents_thumbnail(self): self.assertEqual(file.size, (400, 200)) # check thumbnail qualty and extention self.assertEqual(file.format, 'JPEG') + # test pdf doc + with open(os.path.join(f"{self.project_root}", "tests/data/pdf_doc.pdf"), "rb") as f: + data = { + 'title': "Pdf File Doc", + 'doc_file': f, + 'extension': 'pdf', + } + self.client.post(reverse('document_upload'), data=data) + d = Document.objects.get(title='Pdf File Doc') + self.assertIsNotNone(d.thumbnail_url) + thumb_file = os.path.join( + settings.MEDIA_ROOT, f"thumbs/{os.path.basename(urlparse(d.thumbnail_url).path)}" + ) + file = Image.open(thumb_file) + # check thumbnail qualty and extention + self.assertEqual(file.format, 'JPEG') finally: Document.objects.filter(title='img File Doc').delete() + Document.objects.filter(title='Pdf File Doc').delete() def test_upload_document_form_size_limit(self): form_data = { diff --git a/geonode/documents/tests/data/pdf_doc.pdf b/geonode/documents/tests/data/pdf_doc.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0158787c4f292b4e598eb50c40f27122f4594cd4 GIT binary patch literal 10919 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 literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt index 8918b9d9b0b..8baa223eb79 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,6 +25,7 @@ zipstream-new==1.1.8 schema==0.7.5 rdflib==6.1.1 smart_open==6.2.0 +PyMuPDF==1.20.2 # Django Apps django-allauth==0.51.0 diff --git a/setup.cfg b/setup.cfg index eb41aac53bd..679af840812 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,6 +51,7 @@ install_requires = schema==0.7.5 rdflib==6.1.1 smart_open==6.2.0 + PyMuPDF==1.20.2 # Django Apps django-allauth==0.51.0