Improve DjVu support
This commit is contained in:
@@ -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
4
TODO
@@ -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
|
||||||
|
@@ -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:
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
Reference in New Issue
Block a user