From f4411163f9ef0b085f1b6df4b02b1f9762f9ce41 Mon Sep 17 00:00:00 2001 From: ajurna Date: Thu, 21 May 2020 12:44:57 +0100 Subject: [PATCH] added pdf.js to add pdf support. --- Pipfile | 2 + Pipfile.lock | 30 +++- comic/models.py | 63 +++++--- comic/templates/comic/read_comic_pdf.html | 173 +++++++++++++++++++++ comic/urls.py | 1 + comic/util.py | 2 +- comic/views.py | 20 ++- static/js/hammer.js | 2 +- static/pdfjs/LICENSE | 177 ++++++++++++++++++++++ 9 files changed, 437 insertions(+), 33 deletions(-) create mode 100644 comic/templates/comic/read_comic_pdf.html create mode 100644 static/pdfjs/LICENSE diff --git a/Pipfile b/Pipfile index be0b557..1f67b36 100644 --- a/Pipfile +++ b/Pipfile @@ -11,6 +11,8 @@ gunicorn = "*" django-recaptcha2 = "*" django-bootstrap4 = "*" dj-database-url = "*" +pypdf2 = "*" +pypdf4 = "*" [requires] python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock index 3f749ab..5b21247 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "22569e29b4f8bd5fc837707e1c74a7425fadcc26ea7d25bdf000972446ea2f5e" + "sha256": "548d0f8003c2238f35176fd522fcca7d6108c2c9146f6cb0320660e6bb0212cc" }, "pipfile-spec": 6, "requires": { @@ -25,11 +25,11 @@ }, "beautifulsoup4": { "hashes": [ - "sha256:594ca51a10d2b3443cbac41214e12dbb2a1cd57e1a7344659849e2e20ba6a8d8", - "sha256:a4bbe77fd30670455c5296242967a123ec28c37e9702a8a81bd2f20a4baf0368", - "sha256:d4e96ac9b0c3a6d3f0caae2e4124e6055c5dcafde8e2f831ff194c104f0775a0" + "sha256:73cc4d115b96f79c7d77c1c7f7a0a8d4c57860d1041df407dd1aae7f07a77fd7", + "sha256:a6237df3c32ccfaee4fd201c8f5f9d9df619b93121d01353a64a73ce8c6ef9a8", + "sha256:e718f2342e2e099b640a34ab782407b7b676f47ee272d6739e60b8ea23829f2c" ], - "version": "==4.9.0" + "version": "==4.9.1" }, "certifi": { "hashes": [ @@ -92,6 +92,20 @@ ], "version": "==2.9" }, + "pypdf2": { + "hashes": [ + "sha256:e28f902f2f0a1603ea95ebe21dff311ef09be3d0f0ef29a3e44a932729564385" + ], + "index": "pypi", + "version": "==1.26.0" + }, + "pypdf4": { + "hashes": [ + "sha256:7c932441146d205572f96254d53c79ea2c30c9e11df55a5cf87e056c7b3d7f89" + ], + "index": "pypi", + "version": "==1.27.0" + }, "pytz": { "hashes": [ "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", @@ -108,10 +122,10 @@ }, "soupsieve": { "hashes": [ - "sha256:e914534802d7ffd233242b785229d5ba0766a7f487385e3f714446a07bf540ae", - "sha256:fcd71e08c0aee99aca1b73f45478549ee7e7fc006d51b37bec9e9def7dc22b69" + "sha256:1634eea42ab371d3d346309b93df7870a88610f0725d47528be902a0d95ecc55", + "sha256:a59dc181727e95d25f781f0eb4fd1825ff45590ec8ff49eadfd7f1a537cc0232" ], - "version": "==2.0" + "version": "==2.0.1" }, "sqlparse": { "hashes": [ diff --git a/comic/models.py b/comic/models.py index eadca5c..bd89e7b 100644 --- a/comic/models.py +++ b/comic/models.py @@ -7,6 +7,7 @@ from django.contrib.auth.models import User from django.db import models from django.db.transaction import atomic from django.utils.http import urlsafe_base64_encode +from PyPDF4 import PdfFileReader from comic import rarfile @@ -82,6 +83,10 @@ class ComicBook(models.Model): def selector_string(self): return urlsafe_base64_encode(self.selector.bytes) + def get_pdf(self): + base_dir = Setting.objects.get(name="BASE_DIR").value + return path.join(base_dir, self.directory.get_path(), self.file_name) + def get_image(self, page): base_dir = Setting.objects.get(name="BASE_DIR").value if self.directory: @@ -257,7 +262,14 @@ class ComicBook(models.Model): try: cbx = zipfile.ZipFile(comic_full_path) except zipfile.BadZipFile: - return comic_file_name + cbx = None + if not cbx: + pdf_file = PdfFileReader(comic_full_path) + else: + pdf_file = None + if not pdf_file and not cbx: + return comic_file_name + with atomic(): if directory: book = ComicBook(file_name=comic_file_name, directory=directory) @@ -265,27 +277,34 @@ class ComicBook(models.Model): book = ComicBook(file_name=comic_file_name) book.save() page_index = 0 - for page_file_name in sorted([str(x) for x in cbx.namelist()], key=str.lower): - try: - dot_index = page_file_name.rindex(".") + 1 - except ValueError: - continue - ext = page_file_name.lower()[dot_index:] - if ext in ["jpg", "jpeg"]: - content_type = "image/jpeg" - elif ext == "png": - content_type = "image/png" - elif ext == "bmp": - content_type = "image/bmp" - elif ext == "gif": - content_type = "image/gif" - else: - content_type = "text/plain" - page = ComicPage( - Comic=book, index=page_index, page_file_name=page_file_name, content_type=content_type - ) - page.save() - page_index += 1 + if cbx: + for page_file_name in sorted([str(x) for x in cbx.namelist()], key=str.lower): + try: + dot_index = page_file_name.rindex(".") + 1 + except ValueError: + continue + ext = page_file_name.lower()[dot_index:] + if ext in ["jpg", "jpeg"]: + content_type = "image/jpeg" + elif ext == "png": + content_type = "image/png" + elif ext == "bmp": + content_type = "image/bmp" + elif ext == "gif": + content_type = "image/gif" + else: + content_type = "text/plain" + page = ComicPage( + Comic=book, index=page_index, page_file_name=page_file_name, content_type=content_type + ) + page.save() + page_index += 1 + elif pdf_file: + for page_index in range(pdf_file.getNumPages()): + page = ComicPage( + Comic=book, index=page_index, page_file_name=page_index+1, content_type='application/pdf' + ) + page.save() return book @staticmethod diff --git a/comic/templates/comic/read_comic_pdf.html b/comic/templates/comic/read_comic_pdf.html new file mode 100644 index 0000000..ac6d8de --- /dev/null +++ b/comic/templates/comic/read_comic_pdf.html @@ -0,0 +1,173 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}{{ title }}{% endblock %} + +{% block content %} + +
+
+
+ + + + + + +
+
+
+ +
+
+ +{% endblock %} + +{% block script %} + + +{% endblock %} \ No newline at end of file diff --git a/comic/urls.py b/comic/urls.py index c304c45..8cab36e 100644 --- a/comic/urls.py +++ b/comic/urls.py @@ -13,6 +13,7 @@ urlpatterns = [ url(r"^read/(?P[\w-]+)/$", views.read_comic, name="read_comic"), url(r"^set_page/(?P[\w-]+)/(?P[0-9]+)/$", views.set_read_page, name="set_read_page"), url(r"^read/(?P[\w-]+)/(?P[0-9]+)/img$", views.get_image, name="get_image"), + url(r"^read/(?P[\w-]+)/pdf$", views.get_pdf, name="get_pdf"), url(r"^list_json/$", views.comic_list_json, name="comic_list_json1"), url(r"^list_json/(?P[\w-]+)/$", views.comic_list_json, name="comic_list_json2"), url(r"^recent/$", views.recent_comics, name="recent_comics"), diff --git a/comic/util.py b/comic/util.py index 522938a..35ca645 100644 --- a/comic/util.py +++ b/comic/util.py @@ -162,7 +162,7 @@ def generate_directory(user, directory=False): df.populate_directory(directory_obj, user) files.append(df) for file_name in file_list: - if file_name.lower()[-4:] in [".rar", ".zip", ".cbr", ".cbz"]: + if file_name.lower()[-4:] in [".rar", ".zip", ".cbr", ".cbz", ".pdf"]: book = ComicBook.process_comic_book(file_name, directory) df = DirFile() df.populate_comic(book, user) diff --git a/comic/views.py b/comic/views.py index 88b88cf..9d9e13c 100644 --- a/comic/views.py +++ b/comic/views.py @@ -293,6 +293,13 @@ def settings_page(request): @login_required def read_comic(request, comic_selector): selector = uuid.UUID(bytes=urlsafe_base64_decode(comic_selector)) + try: + book = ComicBook.objects.get(selector=selector) + except ComicBook.DoesNotExist: + Directory.objects.get(selector=selector) + return redirect('comic_list', directory_selector=comic_selector) + except Directory.DoesNotExist: + return HttpResponse(status=404) book = get_object_or_404(ComicBook, selector=selector) pages = ComicPage.objects.filter(Comic=book) @@ -308,7 +315,11 @@ def read_comic(request, comic_selector): "menu": Menu(request.user), "title": title, } - return render(request, "comic/read_comic.html", context) + if book.file_name.lower().endswith('pdf'): + context['status'].last_read_page += 1 + return render(request, "comic/read_comic_pdf.html", context) + else: + return render(request, "comic/read_comic.html", context) @login_required @@ -335,6 +346,13 @@ def get_image(_, comic_selector, page): return FileResponse(img, content_type=content) +@login_required +def get_pdf(_, comic_selector): + selector = uuid.UUID(bytes=urlsafe_base64_decode(comic_selector)) + book = ComicBook.objects.get(selector=selector) + return FileResponse(open(book.get_pdf(), 'rb'), content_type='application/pdf') + + def initial_setup(request): if User.objects.all().exists(): return redirect("/comic/") diff --git a/static/js/hammer.js b/static/js/hammer.js index 2b64009..00d2965 100644 --- a/static/js/hammer.js +++ b/static/js/hammer.js @@ -2132,7 +2132,7 @@ inherit(TapRecognizer, Recognizer, { /** * Simple way to create a manager with a default set of recognizers. - * @param {Document} element + * @param {HTMLElement} element * @param {Object} [options] * @constructor */ diff --git a/static/pdfjs/LICENSE b/static/pdfjs/LICENSE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/static/pdfjs/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS