Improve DjVu support

This commit is contained in:
BasioMeusPuga
2019-03-15 19:07:07 -04:00
parent eb49ca92a4
commit 38de0dcd13
7 changed files with 45 additions and 44 deletions

View File

@@ -30,7 +30,6 @@ Bitcoin: 17jaxj26vFJNqQ2hEVerbBV5fpTusfqFro
| Package | Version tested | Required for | | Package | Version tested | Required for |
| --- | --- | --- | | --- | --- | --- |
| python-pymupdf | 1.14.5 | PDF support | | python-pymupdf | 1.14.5 | PDF support |
| python-numpy | 1.16.2 | DjVu support |
| python-djvulibre | 0.8.4 | DjVu support | | python-djvulibre | 0.8.4 | DjVu support |
## Support ## Support

4
TODO
View File

@@ -89,6 +89,7 @@ TODO
Have them save to memory Have them save to memory
✓ fb2 support ✓ fb2 support
✓ Images need to show up in their placeholders ✓ Images need to show up in their placeholders
✓ djvu support
Other: Other:
✓ Define every widget in code ✓ Define every widget in code
Bugs: Bugs:
@@ -121,10 +122,11 @@ TODO
Goodreads API: Ratings, Read, Recommendations Goodreads API: Ratings, Read, Recommendations
Get ISBN using python-isbnlib Get ISBN using python-isbnlib
Use embedded fonts + CSS Use embedded fonts + CSS
txt, doc, chm, djvu support txt, doc, chm, markdown support
Include icons for filetype emblems Include icons for filetype emblems
Comic view modes Comic view modes
Continuous paging Continuous paging
Image rotation
Ignore a / the / numbers for sorting purposes Ignore a / the / numbers for sorting purposes
? Add only one file type if multiple are present ? Add only one file type if multiple are present
? Create emblem per filetype ? Create emblem per filetype

View File

@@ -106,7 +106,7 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView):
elif self.filetype == 'djvu': elif self.filetype == 'djvu':
page_data = self.book.pages[page] page_data = self.book.pages[page]
pixmap = render_djvu_page(page_data, '/tmp') pixmap = render_djvu_page(page_data)
return pixmap return pixmap
@@ -183,12 +183,10 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView):
# TODO # TODO
# Get caching working for double page view # Get caching working for double page view
# Get caching working for DjVu files
# All of these must be True # All of these must be True
caching_conditions = ( caching_conditions = (
not double_page_mode, not double_page_mode,
not self.filetype == 'djvu',
self.main_window.settings['caching_enabled']) self.main_window.settings['caching_enabled'])
if False not in caching_conditions: if False not in caching_conditions:

View File

@@ -17,33 +17,23 @@
import os import os
import collections import collections
import numpy
import djvu.decode import djvu.decode
from PyQt5 import QtGui 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: class ParseDJVU:
def __init__(self, filename, temp_dir, file_md5): def __init__(self, filename, *args):
self.book = None self.book = None
self.filename = filename 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): def read_book(self):
self.book = djvu.decode.Context().new_document( self.book = djvu.decode.Context().new_document(
djvu.decode.FileURI(self.filename)) djvu.decode.FileURI(self.filename))
self.book.decoding_job.wait() self.book.decoding_job.wait()
def generate_metadata(self): def generate_metadata(self):
# TODO
# What even is this?
title = os.path.basename(self.filename) title = os.path.basename(self.filename)
author = 'Unknown' author = 'Unknown'
year = 9999 year = 9999
@@ -51,7 +41,7 @@ class ParseDJVU:
tags = [] tags = []
cover_page = self.book.pages[0] cover_page = self.book.pages[0]
cover = render_djvu_page(cover_page, self.extract_dir, True) cover = render_djvu_page(cover_page, True)
Metadata = collections.namedtuple( Metadata = collections.namedtuple(
'Metadata', ['title', 'author', 'year', 'isbn', 'tags', 'cover']) 'Metadata', ['title', 'author', 'year', 'isbn', 'tags', 'cover'])
@@ -60,46 +50,49 @@ class ParseDJVU:
def generate_content(self): def generate_content(self):
# TODO # TODO
# See if it's possible to generate a more involved ToC # See if it's possible to generate a more involved ToC
content = list(range(len(self.book.pages))) content = list(range(len(self.book.pages)))
toc = [(1, f'Page {i + 1}', i + 1) for i in content] toc = [(1, f'Page {i + 1}', i + 1) for i in content]
# Return toc, content, images_only # Return toc, content, images_only
return toc, content, True return toc, content, True
def render_djvu_page(page, extract_dir, for_cover=False):
def render_djvu_page(page, for_cover=False):
# TODO # TODO
# Figure out how to calculate image stride # Figure out how to calculate image stride
bytes_per_line = 13200 # and if it impacts row_alignment in the render
# method below
# bytes_per_line = 13200
# Yes, but why? 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
# ¯\_(ツ)_/¯
mode = 0 mode = 0
page_job = page.decode(wait=True) page_job = page.decode(wait=True)
width, height = page_job.size width, height = page_job.size
rect = (0, 0, width, height) rect = (0, 0, width, height)
color_buffer = numpy.zeros((height, bytes_per_line), dtype=numpy.uint32) output = page_job.render(
page_job.render( mode, rect, rect, djvu_pixel_format)
mode, rect, rect, djvu_pixel_format, # row_alignment=bytes_per_line)
row_alignment=bytes_per_line,
buffer=color_buffer)
color_buffer ^= 0xFF000000
imageFormat = QtGui.QImage.Format_RGB32 imageFormat = QtGui.QImage.Format_RGB32
pageQImage = QtGui.QImage(color_buffer, width, height, imageFormat) pageQImage = QtGui.QImage(output, width, height, imageFormat)
# Format conversion not only keeps the damn thing from
# segfaulting when converting from QImage to QPixmap,
# but it also allows for the double page mode to keep
# working properly. We like format conversion.
pageQImage = pageQImage.convertToFormat(
QtGui.QImage.Format_ARGB32_Premultiplied)
if for_cover: if for_cover:
return pageQImage 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 = QtGui.QPixmap()
pixmap.load(outfile) pixmap.convertFromImage(pageQImage)
return pixmap return pixmap

View File

@@ -89,7 +89,7 @@ class EPUB:
return i return i
# If the file isn't found # If the file isn't found
logger.error(filename + ' not found in ' + self.book_filename) logger.warning(filename + ' not found in ' + self.book_filename)
return False return False
def generate_toc(self): def generate_toc(self):
@@ -109,7 +109,7 @@ class EPUB:
if not toc_filename: if not toc_filename:
if not toc_filename_alternative: if not toc_filename_alternative:
logger.error('No ToC found for: ' + self.book_filename) logger.warning('No ToC found for: ' + self.book_filename)
else: else:
toc_filename = toc_filename_alternative toc_filename = toc_filename_alternative

View File

@@ -62,14 +62,13 @@ else:
print(error_string) print(error_string)
logger.error(error_string) logger.error(error_string)
# numpy and djvu - Optional # djvu - Optional
numpy_check = importlib.util.find_spec('numpy')
djvu_check = importlib.util.find_spec('djvu.decode') djvu_check = importlib.util.find_spec('djvu.decode')
if numpy_check and djvu_check: if djvu_check:
from lector.parsers.djvu import ParseDJVU from lector.parsers.djvu import ParseDJVU
sorter['djvu'] = ParseDJVU sorter['djvu'] = ParseDJVU
else: else:
error_string = 'numpy / djvulibre is not installed. Will be unable to load Djvu files.' error_string = '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)

View File

@@ -25,11 +25,17 @@ from PyQt5 import QtCore, QtGui
from lector import sorter from lector import sorter
from lector import database from lector import database
# The following have to be separate
try: try:
from lector.parsers.pdf import render_pdf_page from lector.parsers.pdf import render_pdf_page
except ImportError: except ImportError:
pass pass
try:
from lector.parsers.djvu import render_djvu_page
except ImportError:
pass
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -165,6 +171,10 @@ class BackGroundCacheRefill(QtCore.QThread):
page_data = self.book.loadPage(current_page) page_data = self.book.loadPage(current_page)
pixmap = render_pdf_page(page_data) pixmap = render_pdf_page(page_data)
elif self.filetype == 'djvu':
page_data = self.book.pages[current_page]
pixmap = render_djvu_page(page_data)
return pixmap return pixmap
remove_index = self.image_cache.index(self.remove_value) remove_index = self.image_cache.index(self.remove_value)