CS: Dynamically generate covers for books lacking a cover

This commit is contained in:
Kovid Goyal 2016-02-21 10:13:01 +05:30
parent 7c9cd55b19
commit d67b951c59
4 changed files with 71 additions and 22 deletions

View File

@ -53,6 +53,18 @@ authors = re(authors, ' &amp; ', '<br>');
re(authors, '&amp;&amp;', '&amp;') re(authors, '&amp;&amp;', '&amp;')
''' '''
Prefs = namedtuple('Prefs', ' '.join(sorted(cprefs.defaults))) Prefs = namedtuple('Prefs', ' '.join(sorted(cprefs.defaults)))
_use_roman = None
def get_use_roman():
global _use_roman
if _use_roman is None:
return config['use_roman_numerals_for_series_number']
return _use_roman
def set_use_roman(val):
global _use_roman
_use_roman = bool(val)
# }}} # }}}
# Draw text {{{ # Draw text {{{
@ -255,7 +267,7 @@ def preserve_fields(obj, fields):
def format_text(mi, prefs): def format_text(mi, prefs):
with preserve_fields(mi, 'authors formatted_series_index'): with preserve_fields(mi, 'authors formatted_series_index'):
mi.authors = [a for a in mi.authors if a != _('Unknown')] mi.authors = [a for a in mi.authors if a != _('Unknown')]
mi.formatted_series_index = fmt_sidx(mi.series_index or 0, use_roman=config['use_roman_numerals_for_series_number']) mi.formatted_series_index = fmt_sidx(mi.series_index or 0, use_roman=get_use_roman())
return tuple(format_fields(mi, prefs)) return tuple(format_fields(mi, prefs))
# }}} # }}}

View File

@ -4,20 +4,19 @@
from __future__ import (unicode_literals, division, absolute_import, from __future__ import (unicode_literals, division, absolute_import,
print_function) print_function)
import re, hashlib, random, os import re, hashlib, random
from functools import partial from functools import partial
from threading import Lock from threading import Lock
from json import load as load_json_file, dumps as json_dumps from json import load as load_json_file, dumps as json_dumps
from calibre import prepare_string_for_xml, as_unicode from calibre import prepare_string_for_xml, as_unicode
from calibre.constants import config_dir
from calibre.customize.ui import available_input_formats from calibre.customize.ui import available_input_formats
from calibre.db.view import sanitize_sort_field_name from calibre.db.view import sanitize_sort_field_name
from calibre.srv.ajax import search_result from calibre.srv.ajax import search_result
from calibre.srv.errors import HTTPNotFound, HTTPBadRequest from calibre.srv.errors import HTTPNotFound, HTTPBadRequest
from calibre.srv.metadata import book_as_json, categories_as_json, icon_map from calibre.srv.metadata import book_as_json, categories_as_json, icon_map
from calibre.srv.routes import endpoint, json from calibre.srv.routes import endpoint, json
from calibre.srv.utils import get_library_data from calibre.srv.utils import get_library_data, get_use_roman
from calibre.utils.config import prefs, tweaks from calibre.utils.config import prefs, tweaks
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.search_query_parser import ParseException from calibre.utils.search_query_parser import ParseException
@ -82,20 +81,6 @@ def get_basic_query_data(ctx, rd):
sorts, orders = ['timestamp'], ['desc'] sorts, orders = ['timestamp'], ['desc']
return library_id, db, sorts, orders return library_id, db, sorts, orders
_use_roman = None
def get_use_roman():
global _use_roman
if _use_roman is None:
try:
with lopen(os.path.join(config_dir, 'gui.py'), 'rb') as f:
raw = f.read()
except EnvironmentError:
_use_roman = False
else:
m = re.search(br'use_roman_numerals_for_series_number\s*=\s*(True|False)', raw)
_use_roman = m is not None and m.group(1) == b'True'
return _use_roman
DEFAULT_NUMBER_OF_BOOKS = 50 DEFAULT_NUMBER_OF_BOOKS = 50

View File

@ -11,6 +11,7 @@ from binascii import hexlify
from io import BytesIO from io import BytesIO
from threading import Lock from threading import Lock
from future_builtins import map from future_builtins import map
from functools import partial
from calibre import fit_image from calibre import fit_image
from calibre.constants import config_dir, iswindows from calibre.constants import config_dir, iswindows
@ -21,12 +22,13 @@ from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre.library.save_to_disk import find_plugboard from calibre.library.save_to_disk import find_plugboard
from calibre.srv.errors import HTTPNotFound from calibre.srv.errors import HTTPNotFound
from calibre.srv.routes import endpoint, json from calibre.srv.routes import endpoint, json
from calibre.srv.utils import http_date, get_db from calibre.srv.utils import http_date, get_db, get_use_roman
from calibre.utils.config_base import tweaks from calibre.utils.config_base import tweaks
from calibre.utils.date import timestampfromdt from calibre.utils.date import timestampfromdt
from calibre.utils.img import scale_image, image_from_data from calibre.utils.img import scale_image, image_from_data
from calibre.utils.filenames import ascii_filename, atomic_rename from calibre.utils.filenames import ascii_filename, atomic_rename
from calibre.utils.shared_file import share_open from calibre.utils.shared_file import share_open
from calibre.utils.ipc.simple_worker import fork_job, WorkerError
plugboard_content_server_value = 'content_server' plugboard_content_server_value = 'content_server'
plugboard_content_server_formats = ['epub', 'mobi', 'azw3'] plugboard_content_server_formats = ['epub', 'mobi', 'azw3']
@ -94,10 +96,45 @@ def create_file_copy(ctx, rd, prefix, library_id, book_id, ext, mtime, copy_func
rd.outheaders['Tempfile'] = hexlify(fname.encode('utf-8')) rd.outheaders['Tempfile'] = hexlify(fname.encode('utf-8'))
return rd.filesystem_file_with_custom_etag(ans, prefix, library_id, book_id, mtime, extra_etag_data) return rd.filesystem_file_with_custom_etag(ans, prefix, library_id, book_id, mtime, extra_etag_data)
def generate_cover_worker(width, height, opf, file_name, use_roman):
# We have to generate the cover in a worker as it depends on Qt and needs
# QApplication
from calibre.ebooks.covers import cprefs, override_prefs, scale_cover, generate_cover, set_use_roman
from calibre.ebooks.metadata.opf2 import OPF
set_use_roman(use_roman)
mi = OPF(BytesIO(opf), try_to_guess_cover=False, populate_spine=False).to_book_metadata()
if height is None:
prefs = cprefs
else:
ratio = height / float(cprefs['cover_height'])
prefs = override_prefs(cprefs)
scale_cover(prefs, ratio)
cdata = generate_cover(mi, prefs=prefs)
with share_open(file_name, 'w+b') as f:
f.write(cdata)
def write_generated_cover(db, book_id, width, height, destf):
from calibre.ebooks.metadata.opf2 import metadata_to_opf
mi = metadata_to_opf(db.get_metadata(book_id))
try:
fork_job('calibre.srv.content', 'generate_cover_worker', args=(width, height, mi, destf.name, get_use_roman()), no_output=True)
except WorkerError as err:
raise Exception(err.orig_tb)
def generated_cover(ctx, rd, library_id, db, book_id, width=None, height=None):
prefix = 'generated-cover'
if height is not None:
prefix += '-%sx%s' % (width, height)
mtime = timestampfromdt(db.field_for('last_modified', book_id))
return create_file_copy(ctx, rd, prefix, library_id, book_id, 'jpg', mtime, partial(write_generated_cover, db, book_id, width, height))
def cover(ctx, rd, library_id, db, book_id, width=None, height=None): def cover(ctx, rd, library_id, db, book_id, width=None, height=None):
mtime = db.cover_last_modified(book_id) mtime = db.cover_last_modified(book_id)
if mtime is None: if mtime is None:
raise HTTPNotFound('No cover for book: %r' % book_id) return generated_cover(ctx, rd, library_id, db, book_id, width, height)
prefix = 'cover' prefix = 'cover'
if width is None and height is None: if width is None and height is None:
def copy_func(dest): def copy_func(dest):

View File

@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import errno, socket, select, os import errno, socket, select, os, re
from Cookie import SimpleCookie from Cookie import SimpleCookie
from contextlib import closing from contextlib import closing
from urlparse import parse_qs from urlparse import parse_qs
@ -18,7 +18,7 @@ from urllib import quote as urlquote
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from calibre import prints from calibre import prints
from calibre.constants import iswindows from calibre.constants import iswindows, config_dir
from calibre.srv.errors import HTTPNotFound from calibre.srv.errors import HTTPNotFound
from calibre.utils.config_base import tweaks from calibre.utils.config_base import tweaks
from calibre.utils.localization import get_translator from calibre.utils.localization import get_translator
@ -505,3 +505,18 @@ class Offsets(object):
self.last_offset = last_allowed_index - delta self.last_offset = last_allowed_index - delta
if self.last_offset < 0: if self.last_offset < 0:
self.last_offset = 0 self.last_offset = 0
_use_roman = None
def get_use_roman():
global _use_roman
if _use_roman is None:
try:
with lopen(os.path.join(config_dir, 'gui.py'), 'rb') as f:
raw = f.read()
except EnvironmentError:
_use_roman = False
else:
m = re.search(br'use_roman_numerals_for_series_number\s*=\s*(True|False)', raw)
_use_roman = m is not None and m.group(1) == b'True'
return _use_roman