Preliminary DjVu support
SideDock fade in animation
This commit is contained in:
2
TODO
2
TODO
@@ -100,8 +100,10 @@ TODO
|
|||||||
Search results should ignore punctuation
|
Search results should ignore punctuation
|
||||||
Keep text size for annotations
|
Keep text size for annotations
|
||||||
Sort by new is not working
|
Sort by new is not working
|
||||||
|
Drag and drop is acting out
|
||||||
|
|
||||||
Secondary:
|
Secondary:
|
||||||
|
Navbar
|
||||||
Text to speech
|
Text to speech
|
||||||
Definitions dialog needs to respond to escape
|
Definitions dialog needs to respond to escape
|
||||||
Zoom slider for comics
|
Zoom slider for comics
|
||||||
|
@@ -891,9 +891,12 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
last_accessed_time = QtCore.QDateTime().currentDateTime()
|
last_accessed_time = QtCore.QDateTime().currentDateTime()
|
||||||
position_perc = 1
|
position_perc = 1
|
||||||
|
|
||||||
self.lib_ref.libraryModel.setData(i, metadata, QtCore.Qt.UserRole + 3)
|
self.lib_ref.libraryModel.setData(
|
||||||
self.lib_ref.libraryModel.setData(i, position_perc, QtCore.Qt.UserRole + 7)
|
i, metadata, QtCore.Qt.UserRole + 3)
|
||||||
self.lib_ref.libraryModel.setData(i, last_accessed_time, QtCore.Qt.UserRole + 12)
|
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()
|
self.lib_ref.update_proxymodels()
|
||||||
|
|
||||||
database_dict = {
|
database_dict = {
|
||||||
|
@@ -25,6 +25,12 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
import djvu.decode
|
||||||
|
from lector.parsers.djvu import render_djvu_page
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||||
|
|
||||||
from lector.rarfile import rarfile
|
from lector.rarfile import rarfile
|
||||||
@@ -60,6 +66,11 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView):
|
|||||||
elif self.filetype == 'pdf':
|
elif self.filetype == 'pdf':
|
||||||
self.book = fitz.open(self.filepath)
|
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.common_functions = PliantWidgetsCommonFunctions(
|
||||||
self, self.main_window)
|
self, self.main_window)
|
||||||
|
|
||||||
@@ -93,6 +104,10 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView):
|
|||||||
page_data = self.book.loadPage(page)
|
page_data = self.book.loadPage(page)
|
||||||
pixmap = render_pdf_page(page_data)
|
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
|
return pixmap
|
||||||
|
|
||||||
firstPixmap = page_loader(current_page)
|
firstPixmap = page_loader(current_page)
|
||||||
@@ -168,7 +183,15 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView):
|
|||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# Get caching working for double page view
|
# 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
|
return_pixmap = None
|
||||||
while not return_pixmap:
|
while not return_pixmap:
|
||||||
return_pixmap = check_cache(current_page)
|
return_pixmap = check_cache(current_page)
|
||||||
|
@@ -51,6 +51,12 @@ class PliantDockWidget(QtWidgets.QDockWidget):
|
|||||||
# Except this one
|
# Except this one
|
||||||
self.sideDockTabWidget = None
|
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):
|
def showEvent(self, event=None):
|
||||||
viewport_topRight = self.contentView.mapToGlobal(
|
viewport_topRight = self.contentView.mapToGlobal(
|
||||||
self.contentView.viewport().rect().topRight())
|
self.contentView.viewport().rect().topRight())
|
||||||
@@ -69,6 +75,7 @@ class PliantDockWidget(QtWidgets.QDockWidget):
|
|||||||
|
|
||||||
self.main_window.active_docks.append(self)
|
self.main_window.active_docks.append(self)
|
||||||
self.setGeometry(dock_x, dock_y, dock_width, dock_height)
|
self.setGeometry(dock_x, dock_y, dock_width, dock_height)
|
||||||
|
self.animation.start()
|
||||||
|
|
||||||
def hideEvent(self, event=None):
|
def hideEvent(self, event=None):
|
||||||
if self.notes_only:
|
if self.notes_only:
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
VERSION = '0.5.1'
|
VERSION = '0.5.GittyGittyBangBang'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
105
lector/parsers/djvu.py
Normal file
105
lector/parsers/djvu.py
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
@@ -58,7 +58,18 @@ if mupdf_check:
|
|||||||
from lector.parsers.pdf import ParsePDF
|
from lector.parsers.pdf import ParsePDF
|
||||||
sorter['pdf'] = ParsePDF
|
sorter['pdf'] = ParsePDF
|
||||||
else:
|
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)
|
print(error_string)
|
||||||
logger.error(error_string)
|
logger.error(error_string)
|
||||||
|
|
||||||
@@ -211,8 +222,6 @@ class BookSorter:
|
|||||||
# None of the following have an exception type specified
|
# None of the following have an exception type specified
|
||||||
# This will keep everything from crashing, but will make
|
# This will keep everything from crashing, but will make
|
||||||
# troubleshooting difficult
|
# troubleshooting difficult
|
||||||
# TODO
|
|
||||||
# In application notifications
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
book_ref.read_book()
|
book_ref.read_book()
|
||||||
|
Reference in New Issue
Block a user