mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Use the cover in the db rather than the one in the file when rendering books
This commit is contained in:
parent
1c8058fff6
commit
c41e4b00fc
@ -258,7 +258,10 @@ def create_epub_cover(container, cover_path, existing_image, options=None):
|
|||||||
from calibre.ebooks.conversion.config import load_defaults
|
from calibre.ebooks.conversion.config import load_defaults
|
||||||
from calibre.ebooks.oeb.transforms.cover import CoverManager
|
from calibre.ebooks.oeb.transforms.cover import CoverManager
|
||||||
|
|
||||||
|
try:
|
||||||
ext = cover_path.rpartition('.')[-1].lower()
|
ext = cover_path.rpartition('.')[-1].lower()
|
||||||
|
except Exception:
|
||||||
|
ext = 'jpeg'
|
||||||
cname, tname = 'cover.' + ext, 'titlepage.xhtml'
|
cname, tname = 'cover.' + ext, 'titlepage.xhtml'
|
||||||
recommended_folders = get_recommended_folders(container, (cname, tname))
|
recommended_folders = get_recommended_folders(container, (cname, tname))
|
||||||
|
|
||||||
@ -273,7 +276,11 @@ def create_epub_cover(container, cover_path, existing_image, options=None):
|
|||||||
raster_cover_item = container.generate_item(cname, id_prefix='cover')
|
raster_cover_item = container.generate_item(cname, id_prefix='cover')
|
||||||
raster_cover = container.href_to_name(raster_cover_item.get('href'), container.opf_name)
|
raster_cover = container.href_to_name(raster_cover_item.get('href'), container.opf_name)
|
||||||
|
|
||||||
with lopen(cover_path, 'rb') as src, container.open(raster_cover, 'wb') as dest:
|
with container.open(raster_cover, 'wb') as dest:
|
||||||
|
if callable(cover_path):
|
||||||
|
cover_path('write_image', dest)
|
||||||
|
else:
|
||||||
|
with lopen(cover_path, 'rb') as src:
|
||||||
shutil.copyfileobj(src, dest)
|
shutil.copyfileobj(src, dest)
|
||||||
if options is None:
|
if options is None:
|
||||||
opts = load_defaults('epub_output')
|
opts = load_defaults('epub_output')
|
||||||
@ -285,6 +292,9 @@ def create_epub_cover(container, cover_path, existing_image, options=None):
|
|||||||
if no_svg:
|
if no_svg:
|
||||||
style = 'style="height: 100%%"'
|
style = 'style="height: 100%%"'
|
||||||
templ = CoverManager.NONSVG_TEMPLATE.replace('__style__', style)
|
templ = CoverManager.NONSVG_TEMPLATE.replace('__style__', style)
|
||||||
|
else:
|
||||||
|
if callable(cover_path):
|
||||||
|
templ = CoverManager.SVG_TEMPLATE
|
||||||
else:
|
else:
|
||||||
width, height = 600, 800
|
width, height = 600, 800
|
||||||
try:
|
try:
|
||||||
@ -412,5 +422,6 @@ def set_epub_cover(container, cover_path, report, options=None):
|
|||||||
if s is not None and s != d}
|
if s is not None and s != d}
|
||||||
if link_sub:
|
if link_sub:
|
||||||
replace_links(container, link_sub, frag_map=lambda x, y:None)
|
replace_links(container, link_sub, frag_map=lambda x, y:None)
|
||||||
|
return raster_cover, titlepage
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,15 +12,18 @@ from urlparse import urlparse
|
|||||||
from cssutils import replaceUrls
|
from cssutils import replaceUrls
|
||||||
from lxml.etree import Comment, tostring
|
from lxml.etree import Comment, tostring
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import OEB_DOCS, escape_cdata, OEB_STYLES, rewrite_links, XPath, urlunquote, XLINK
|
from calibre.ebooks.oeb.base import (
|
||||||
|
OEB_DOCS, escape_cdata, OEB_STYLES, rewrite_links, XPath, urlunquote, XLINK, XHTML)
|
||||||
from calibre.ebooks.oeb.iterator.book import extract_book
|
from calibre.ebooks.oeb.iterator.book import extract_book
|
||||||
from calibre.ebooks.oeb.polish.container import Container as ContainerBase
|
from calibre.ebooks.oeb.polish.container import Container as ContainerBase
|
||||||
|
from calibre.ebooks.oeb.polish.cover import set_epub_cover, find_cover_image
|
||||||
from calibre.ebooks.oeb.polish.toc import get_toc
|
from calibre.ebooks.oeb.polish.toc import get_toc
|
||||||
from calibre.ebooks.oeb.polish.utils import guess_type
|
from calibre.ebooks.oeb.polish.utils import guess_type
|
||||||
from calibre.utils.short_uuid import uuid4
|
from calibre.utils.short_uuid import uuid4
|
||||||
from calibre.utils.logging import default_log
|
from calibre.utils.logging import default_log
|
||||||
|
|
||||||
RENDER_VERSION = 1 # Also change this in read_book.ui.pyj
|
RENDER_VERSION = 1 # Also change this in read_book.ui.pyj
|
||||||
|
BLANK_JPEG = b'\xff\xd8\xff\xdb\x00C\x00\x03\x02\x02\x02\x02\x02\x03\x02\x02\x02\x03\x03\x03\x03\x04\x06\x04\x04\x04\x04\x04\x08\x06\x06\x05\x06\t\x08\n\n\t\x08\t\t\n\x0c\x0f\x0c\n\x0b\x0e\x0b\t\t\r\x11\r\x0e\x0f\x10\x10\x11\x10\n\x0c\x12\x13\x12\x10\x13\x0f\x10\x10\x10\xff\xc9\x00\x0b\x08\x00\x01\x00\x01\x01\x01\x11\x00\xff\xcc\x00\x06\x00\x10\x10\x05\xff\xda\x00\x08\x01\x01\x00\x00?\x00\xd2\xcf \xff\xd9' # noqa
|
||||||
|
|
||||||
def encode_component(x):
|
def encode_component(x):
|
||||||
return x.replace(',', ',c').replace('|', ',p')
|
return x.replace(',', ',c').replace('|', ',p')
|
||||||
@ -53,6 +56,7 @@ class Container(ContainerBase):
|
|||||||
name == self.opf_name or mt == guess_type('a.ncx') or name.startswith('META-INF/') or
|
name == self.opf_name or mt == guess_type('a.ncx') or name.startswith('META-INF/') or
|
||||||
name == 'mimetype'
|
name == 'mimetype'
|
||||||
}
|
}
|
||||||
|
raster_cover_name, titlepage_name = self.create_cover_page(input_fmt.lower())
|
||||||
|
|
||||||
self.book_render_data = data = {
|
self.book_render_data = data = {
|
||||||
'version': RENDER_VERSION,
|
'version': RENDER_VERSION,
|
||||||
@ -61,10 +65,13 @@ class Container(ContainerBase):
|
|||||||
'link_uid': uuid4(),
|
'link_uid': uuid4(),
|
||||||
'book_hash': book_hash,
|
'book_hash': book_hash,
|
||||||
'is_comic': input_fmt.lower() in {'cbc', 'cbz', 'cbr', 'cb7'},
|
'is_comic': input_fmt.lower() in {'cbc', 'cbz', 'cbr', 'cb7'},
|
||||||
|
'raster_cover_name': raster_cover_name,
|
||||||
|
'title_page_name': titlepage_name,
|
||||||
}
|
}
|
||||||
# Mark the spine as dirty since we have to ensure it is normalized
|
# Mark the spine as dirty since we have to ensure it is normalized
|
||||||
for name in data['spine']:
|
for name in data['spine']:
|
||||||
self.parsed(name), self.dirty(name)
|
self.parsed(name), self.dirty(name)
|
||||||
|
self.inject_script(data['spine'])
|
||||||
self.virtualized_names = set()
|
self.virtualized_names = set()
|
||||||
self.virtualize_resources()
|
self.virtualize_resources()
|
||||||
def manifest_data(name):
|
def manifest_data(name):
|
||||||
@ -76,6 +83,37 @@ class Container(ContainerBase):
|
|||||||
with lopen(os.path.join(self.root, 'calibre-book-manifest.json'), 'wb') as f:
|
with lopen(os.path.join(self.root, 'calibre-book-manifest.json'), 'wb') as f:
|
||||||
f.write(json.dumps(self.book_render_data, ensure_ascii=False).encode('utf-8'))
|
f.write(json.dumps(self.book_render_data, ensure_ascii=False).encode('utf-8'))
|
||||||
|
|
||||||
|
def create_cover_page(self, input_fmt):
|
||||||
|
if input_fmt == 'epub':
|
||||||
|
def cover_path(action, data):
|
||||||
|
if action == 'write_image':
|
||||||
|
data.write(BLANK_JPEG)
|
||||||
|
return set_epub_cover(self, cover_path, (lambda *a: None))
|
||||||
|
raster_cover_name = find_cover_image(self, strict=True)
|
||||||
|
if raster_cover_name is None:
|
||||||
|
item = self.generate_item(name='cover.jpeg', id_prefix='cover')
|
||||||
|
raster_cover_name = self.href_to_name(item.get('href'), self.opf_name)
|
||||||
|
with self.open(raster_cover_name, 'wb') as dest:
|
||||||
|
dest.write(BLANK_JPEG)
|
||||||
|
item = self.generate_item(name='titlepage.html', id_prefix='titlepage')
|
||||||
|
titlepage_name = self.href_to_name(item.get('href'), self.opf_name)
|
||||||
|
self.dirty(self.opf_name)
|
||||||
|
return raster_cover_name, titlepage_name
|
||||||
|
|
||||||
|
def inject_script(self, spine):
|
||||||
|
src = 'injected-script-' + self.book_render_data['link_uid']
|
||||||
|
for name in spine:
|
||||||
|
root = self.parsed(name)
|
||||||
|
head = tuple(root.iterchildren(XHTML('head')))
|
||||||
|
head = head[0] if head else root.makeelement(XHTML('head'))
|
||||||
|
root.insert(0, head)
|
||||||
|
script = root.makeelement(XHTML('script'))
|
||||||
|
script.set('type', 'text/javascript')
|
||||||
|
script.set('src', src)
|
||||||
|
script.set('data-secret', 'secret-key-' + self.book_render_data['link_uid'])
|
||||||
|
head.insert(0, script)
|
||||||
|
self.dirty(name)
|
||||||
|
|
||||||
def virtualize_resources(self):
|
def virtualize_resources(self):
|
||||||
|
|
||||||
changed = set()
|
changed = set()
|
||||||
|
@ -82,6 +82,8 @@ class DB:
|
|||||||
'last_read': Date(),
|
'last_read': Date(),
|
||||||
'metadata': metadata,
|
'metadata': metadata,
|
||||||
'manifest': None,
|
'manifest': None,
|
||||||
|
'cover_width': None,
|
||||||
|
'cover_height': None
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -94,12 +96,42 @@ class DB:
|
|||||||
v'delete manifest["metadata"]'
|
v'delete manifest["metadata"]'
|
||||||
self.do_op(['books'], book, _('Failed to write to the books database'), proceed, op='put')
|
self.do_op(['books'], book, _('Failed to write to the books database'), proceed, op='put')
|
||||||
|
|
||||||
def store_file(self, book, name, xhr, proceed):
|
def store_file(self, book, name, xhr, proceed, is_cover):
|
||||||
store_as_text = xhr.responseType is 'text'
|
store_as_text = xhr.responseType is 'text'
|
||||||
fname = file_store_name(book, name)
|
fname = file_store_name(book, name)
|
||||||
needs_encoding = not store_as_text and not self.supports_blobs
|
needs_encoding = not store_as_text and not self.supports_blobs
|
||||||
book.stored_files[fname] = {'encoded':needs_encoding, 'mimetype':book.manifest.files[name].mimetype, 'store_as_text':store_as_text}
|
book.stored_files[fname] = {'encoded':needs_encoding, 'mimetype':book.manifest.files[name].mimetype, 'store_as_text':store_as_text}
|
||||||
data = xhr.response
|
if is_cover:
|
||||||
|
self.store_cover(book, needs_encoding, xhr.response, name, fname, proceed)
|
||||||
|
else:
|
||||||
|
self.store_file_stage2(needs_encoding, xhr.response, name, fname, proceed)
|
||||||
|
|
||||||
|
def store_cover(self, book, needs_encoding, data, name, fname, proceed):
|
||||||
|
blob = data
|
||||||
|
if needs_encoding:
|
||||||
|
blob = Blob([data], {'type':'image/jpeg'})
|
||||||
|
url = window.URL.createObjectURL(blob)
|
||||||
|
img = new Image()
|
||||||
|
proceeded = False
|
||||||
|
|
||||||
|
def done():
|
||||||
|
nonlocal proceeded
|
||||||
|
if not proceeded:
|
||||||
|
proceeded = True
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
self.store_file_stage2(needs_encoding, data, name, fname, proceed)
|
||||||
|
|
||||||
|
img.onload = def():
|
||||||
|
book.cover_width = this.width
|
||||||
|
book.cover_height = this.height
|
||||||
|
done()
|
||||||
|
|
||||||
|
img.onerror = def():
|
||||||
|
print('WARNING: Failed to read dimensions of cover')
|
||||||
|
done()
|
||||||
|
img.src = url
|
||||||
|
|
||||||
|
def store_file_stage2(self, needs_encoding, data, name, fname, proceed):
|
||||||
if needs_encoding:
|
if needs_encoding:
|
||||||
data = base64encode(Uint8Array(data))
|
data = base64encode(Uint8Array(data))
|
||||||
req = self.idb.transaction(['files'], 'readwrite').objectStore('files').put(data, fname)
|
req = self.idb.transaction(['files'], 'readwrite').objectStore('files').put(data, fname)
|
||||||
|
@ -8,6 +8,7 @@ from book_list.globals import get_boss
|
|||||||
from modals import error_dialog
|
from modals import error_dialog
|
||||||
from utils import human_readable
|
from utils import human_readable
|
||||||
from read_book.db import create_db
|
from read_book.db import create_db
|
||||||
|
from read_book.view import View
|
||||||
|
|
||||||
RENDER_VERSION = 1 # Also change this in render_book.py
|
RENDER_VERSION = 1 # Also change this in render_book.py
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ class ReadUI:
|
|||||||
container.appendChild(E.div(
|
container.appendChild(E.div(
|
||||||
id=self.display_id, style='display:none',
|
id=self.display_id, style='display:none',
|
||||||
))
|
))
|
||||||
|
self.view = View(container.lastChild)
|
||||||
|
|
||||||
def show_stack(self, name):
|
def show_stack(self, name):
|
||||||
ans = None
|
ans = None
|
||||||
@ -145,6 +147,7 @@ class ReadUI:
|
|||||||
def download_book(self, book):
|
def download_book(self, book):
|
||||||
files = book.manifest.files
|
files = book.manifest.files
|
||||||
total = 0
|
total = 0
|
||||||
|
cover_total_updated = False
|
||||||
for name in files:
|
for name in files:
|
||||||
total += files[name].size
|
total += files[name].size
|
||||||
files_left = set(book.manifest.files)
|
files_left = set(book.manifest.files)
|
||||||
@ -161,6 +164,7 @@ class ReadUI:
|
|||||||
query = {'library_id': library_id}
|
query = {'library_id': library_id}
|
||||||
progress_track = {}
|
progress_track = {}
|
||||||
pbar.setAttribute('max', total + '')
|
pbar.setAttribute('max', total + '')
|
||||||
|
raster_cover_name = book.manifest.raster_cover_name
|
||||||
|
|
||||||
def update_progress():
|
def update_progress():
|
||||||
x = 0
|
x = 0
|
||||||
@ -190,22 +194,34 @@ class ReadUI:
|
|||||||
files_left.discard(this)
|
files_left.discard(this)
|
||||||
return
|
return
|
||||||
if end_type is 'load':
|
if end_type is 'load':
|
||||||
self.db.store_file(book, this, xhr, on_stored.bind(this))
|
self.db.store_file(book, this, xhr, on_stored.bind(this), this is raster_cover_name)
|
||||||
else:
|
else:
|
||||||
failed_files.append([this, xhr.error_html])
|
failed_files.append([this, xhr.error_html])
|
||||||
files_left.discard(this)
|
files_left.discard(this)
|
||||||
|
|
||||||
def on_progress(loaded):
|
def on_progress(loaded, ftotal):
|
||||||
|
nonlocal total, cover_total_updated
|
||||||
|
if this is raster_cover_name and not cover_total_updated:
|
||||||
|
cover_total_updated = True
|
||||||
|
total = total - files[raster_cover_name].size + ftotal
|
||||||
|
pbar.setAttribute('max', total + '')
|
||||||
progress_track[this] = loaded
|
progress_track[this] = loaded
|
||||||
update_progress()
|
update_progress()
|
||||||
|
|
||||||
for fname in files_left:
|
def start_download(fname, path):
|
||||||
xhr = ajax(base_path + encodeURIComponent(fname), on_complete.bind(fname), on_progress=on_progress.bind(fname), query=query, progress_totals_needed=False)
|
xhr = ajax(path, on_complete.bind(fname), on_progress=on_progress.bind(fname), query=query, progress_totals_needed=False)
|
||||||
xhr.responseType = 'text'
|
xhr.responseType = 'text'
|
||||||
if not book.manifest.files[name].is_virtualized:
|
if not book.manifest.files[name].is_virtualized:
|
||||||
xhr.responseType = 'blob' if self.db.supports_blobs else 'arraybuffer'
|
xhr.responseType = 'blob' if self.db.supports_blobs else 'arraybuffer'
|
||||||
xhr.send()
|
xhr.send()
|
||||||
self.downloads_in_progress.append(xhr)
|
self.downloads_in_progress.append(xhr)
|
||||||
|
|
||||||
|
if raster_cover_name:
|
||||||
|
start_download(raster_cover_name, 'get/cover/' + book_id + '/' + encodeURIComponent(library_id))
|
||||||
|
|
||||||
|
for fname in files_left:
|
||||||
|
if fname is not raster_cover_name:
|
||||||
|
start_download(fname, base_path + encodeURIComponent(fname))
|
||||||
|
|
||||||
def display_book(self, book):
|
def display_book(self, book):
|
||||||
self.show_stack(self.display_id)
|
self.show_stack(self.display_id)
|
||||||
|
30
src/pyj/read_book/view.pyj
Normal file
30
src/pyj/read_book/view.pyj
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
from elementmaker import E
|
||||||
|
from gettext import gettext as _
|
||||||
|
|
||||||
|
LOADING_DOC = '''
|
||||||
|
<p>{}</p>
|
||||||
|
'''
|
||||||
|
class View:
|
||||||
|
|
||||||
|
def __init__(self, container):
|
||||||
|
self.iframe_id = 'read-book-iframe'
|
||||||
|
container.appendChild(
|
||||||
|
E.iframe(
|
||||||
|
id=self.iframe_id,
|
||||||
|
seamless=True,
|
||||||
|
sandbox='allow-popups allow-scripts',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.show_loading()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def iframe(self):
|
||||||
|
return document.getElementById(self.iframe_id)
|
||||||
|
|
||||||
|
def show_loading(self):
|
||||||
|
iframe = self.iframe
|
||||||
|
iframe.setAttribute('srcdoc', str.format(LOADING_DOC, _(
|
||||||
|
'Loading, please wait...')))
|
Loading…
x
Reference in New Issue
Block a user