From bf93c7beab7f6fd1019841b1358a0ca71afdbaf6 Mon Sep 17 00:00:00 2001 From: BasioMeusPuga Date: Thu, 14 Mar 2019 22:58:52 -0400 Subject: [PATCH] Preliminary DjVu support SideDock fade in animation --- TODO | 2 + lector/__main__.py | 9 ++-- lector/contentwidgets.py | 25 +++++++++- lector/dockwidgets.py | 7 +++ lector/logger.py | 2 +- lector/parsers/djvu.py | 105 +++++++++++++++++++++++++++++++++++++++ lector/sorter.py | 15 ++++-- 7 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 lector/parsers/djvu.py diff --git a/TODO b/TODO index 318df6e..7b2c80e 100644 --- a/TODO +++ b/TODO @@ -100,8 +100,10 @@ TODO Search results should ignore punctuation Keep text size for annotations Sort by new is not working + Drag and drop is acting out Secondary: + Navbar Text to speech Definitions dialog needs to respond to escape Zoom slider for comics diff --git a/lector/__main__.py b/lector/__main__.py index cfbdf03..bbade4d 100755 --- a/lector/__main__.py +++ b/lector/__main__.py @@ -891,9 +891,12 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): last_accessed_time = QtCore.QDateTime().currentDateTime() position_perc = 1 - self.lib_ref.libraryModel.setData(i, metadata, QtCore.Qt.UserRole + 3) - self.lib_ref.libraryModel.setData(i, position_perc, QtCore.Qt.UserRole + 7) - self.lib_ref.libraryModel.setData(i, last_accessed_time, QtCore.Qt.UserRole + 12) + self.lib_ref.libraryModel.setData( + i, metadata, QtCore.Qt.UserRole + 3) + self.lib_ref.libraryModel.setData( + i, position_perc, QtCore.Qt.UserRole + 7) + self.lib_ref.libraryModel.setData( + i, last_accessed_time, QtCore.Qt.UserRole + 12) self.lib_ref.update_proxymodels() database_dict = { diff --git a/lector/contentwidgets.py b/lector/contentwidgets.py index 5e483a2..bea5abd 100644 --- a/lector/contentwidgets.py +++ b/lector/contentwidgets.py @@ -25,6 +25,12 @@ try: except ImportError: pass +try: + import djvu.decode + from lector.parsers.djvu import render_djvu_page +except ImportError: + pass + from PyQt5 import QtWidgets, QtGui, QtCore from lector.rarfile import rarfile @@ -60,6 +66,11 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView): elif self.filetype == 'pdf': self.book = fitz.open(self.filepath) + elif self.filetype == 'djvu': + self.book = djvu.decode.Context().new_document( + djvu.decode.FileURI(self.filepath)) + self.book.decoding_job.wait() + self.common_functions = PliantWidgetsCommonFunctions( self, self.main_window) @@ -93,6 +104,10 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView): page_data = self.book.loadPage(page) pixmap = render_pdf_page(page_data) + elif self.filetype == 'djvu': + page_data = self.book.pages[page] + pixmap = render_djvu_page(page_data, '/tmp') + return pixmap firstPixmap = page_loader(current_page) @@ -168,7 +183,15 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView): # TODO # Get caching working for double page view - if not double_page_mode and self.main_window.settings['caching_enabled']: + # Get caching working for DjVu files + + # All of these must be True + caching_conditions = ( + not double_page_mode, + not self.filetype == 'djvu', + self.main_window.settings['caching_enabled']) + + if False not in caching_conditions: return_pixmap = None while not return_pixmap: return_pixmap = check_cache(current_page) diff --git a/lector/dockwidgets.py b/lector/dockwidgets.py index 0a91146..bce58d1 100644 --- a/lector/dockwidgets.py +++ b/lector/dockwidgets.py @@ -51,6 +51,12 @@ class PliantDockWidget(QtWidgets.QDockWidget): # Except this one self.sideDockTabWidget = None + # Animate appearance + self.animation = QtCore.QPropertyAnimation(self, b'windowOpacity') + self.animation.setStartValue(0) + self.animation.setEndValue(1) + self.animation.setDuration(200) + def showEvent(self, event=None): viewport_topRight = self.contentView.mapToGlobal( self.contentView.viewport().rect().topRight()) @@ -69,6 +75,7 @@ class PliantDockWidget(QtWidgets.QDockWidget): self.main_window.active_docks.append(self) self.setGeometry(dock_x, dock_y, dock_width, dock_height) + self.animation.start() def hideEvent(self, event=None): if self.notes_only: diff --git a/lector/logger.py b/lector/logger.py index 45f7128..953e014 100644 --- a/lector/logger.py +++ b/lector/logger.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -VERSION = '0.5.1' +VERSION = '0.5.GittyGittyBangBang' import os import logging diff --git a/lector/parsers/djvu.py b/lector/parsers/djvu.py new file mode 100644 index 0000000..faa00f7 --- /dev/null +++ b/lector/parsers/djvu.py @@ -0,0 +1,105 @@ +# This file is a part of Lector, a Qt based ebook reader +# Copyright (C) 2017-2019 BasioMeusPuga + +# 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 os +import collections + +import numpy +import djvu.decode +from PyQt5 import QtGui + +djvu_pixel_format = djvu.decode.PixelFormatRgbMask(0xFF0000, 0xFF00, 0xFF, bpp=32) +djvu_pixel_format.rows_top_to_bottom = 1 +djvu_pixel_format.y_top_to_bottom = 0 + + +class ParseDJVU: + def __init__(self, filename, temp_dir, file_md5): + self.book = None + self.filename = filename + + # Create the temporary directory where + # rendered pngs will be stored + # This may be skipped in case QImage to QPixmap conversion + # stops segfaulting + self.extract_dir = os.path.join(temp_dir, file_md5) + os.makedirs(self.extract_dir, exist_ok=True) + + def read_book(self): + self.book = djvu.decode.Context().new_document( + djvu.decode.FileURI(self.filename)) + self.book.decoding_job.wait() + + def generate_metadata(self): + title = os.path.basename(self.filename) + author = 'Unknown' + year = 9999 + isbn = None + tags = [] + + cover_page = self.book.pages[0] + cover = render_djvu_page(cover_page, self.extract_dir, True) + + Metadata = collections.namedtuple( + 'Metadata', ['title', 'author', 'year', 'isbn', 'tags', 'cover']) + return Metadata(title, author, year, isbn, tags, cover) + + def generate_content(self): + # TODO + # See if it's possible to generate a more involved ToC + + content = list(range(len(self.book.pages))) + toc = [(1, f'Page {i + 1}', i + 1) for i in content] + + # Return toc, content, images_only + return toc, content, True + +def render_djvu_page(page, extract_dir, for_cover=False): + + # TODO + # Figure out how to calculate image stride + bytes_per_line = 13200 + + # Yes, but why? + mode = 0 + + page_job = page.decode(wait=True) + width, height = page_job.size + rect = (0, 0, width, height) + color_buffer = numpy.zeros((height, bytes_per_line), dtype=numpy.uint32) + page_job.render( + mode, rect, rect, djvu_pixel_format, + row_alignment=bytes_per_line, + buffer=color_buffer) + color_buffer ^= 0xFF000000 + + imageFormat = QtGui.QImage.Format_RGB32 + pageQImage = QtGui.QImage(color_buffer, width, height, imageFormat) + + if for_cover: + return pageQImage + + # TODO + # Converting from the QImage to the QPixmap directly + # outright segfaults sometimes. + # This damages caching, speed and my ego + + outfile = os.path.join(extract_dir, 'temporaryPNG.png') + pageQImage.save(outfile) + pixmap = QtGui.QPixmap() + pixmap.load(outfile) + + return pixmap diff --git a/lector/sorter.py b/lector/sorter.py index 9789dd8..27fffd9 100644 --- a/lector/sorter.py +++ b/lector/sorter.py @@ -58,7 +58,18 @@ if mupdf_check: from lector.parsers.pdf import ParsePDF sorter['pdf'] = ParsePDF else: - error_string = 'pymupdf is not installed. Will be unable to load PDFs.' + error_string = 'pymupdf is not installed. Will be unable to load PDF files.' + print(error_string) + logger.error(error_string) + +# numpy and djvu - Optional +numpy_check = importlib.util.find_spec('numpy') +djvu_check = importlib.util.find_spec('djvu.decode') +if numpy_check and djvu_check: + from lector.parsers.djvu import ParseDJVU + sorter['djvu'] = ParseDJVU +else: + error_string = 'numpy / djvulibre is not installed. Will be unable to load Djvu files.' print(error_string) logger.error(error_string) @@ -211,8 +222,6 @@ class BookSorter: # None of the following have an exception type specified # This will keep everything from crashing, but will make # troubleshooting difficult - # TODO - # In application notifications try: book_ref.read_book()