From 111854f686820960dedf4b12dfb89d44456e1419 Mon Sep 17 00:00:00 2001 From: gryf Date: Sun, 5 Jul 2020 16:37:03 +0200 Subject: [PATCH] Cleaned up cover module --- ebook_converter/ebooks/__init__.py | 2 + ebook_converter/ebooks/conversion/cli.py | 6 +- ebook_converter/ebooks/covers.py | 775 ++---------------- ebook_converter/ebooks/metadata/opf3.py | 53 +- ebook_converter/ebooks/oeb/parse_utils.py | 8 +- ebook_converter/library/catalogs/epub_mobi.py | 16 +- ebook_converter/utils/config_base.py | 1 - 7 files changed, 105 insertions(+), 756 deletions(-) diff --git a/ebook_converter/ebooks/__init__.py b/ebook_converter/ebooks/__init__.py index b4dfce0..c09732c 100644 --- a/ebook_converter/ebooks/__init__.py +++ b/ebook_converter/ebooks/__init__.py @@ -165,6 +165,8 @@ def normalize(x): def calibre_cover(title, author_string, series_string=None, output_format='jpg', title_size=46, author_size=36, logo_path=None): + # TODO(gryf): generate cover using pillow + return None title = normalize(title) author_string = normalize(author_string) series_string = normalize(series_string) diff --git a/ebook_converter/ebooks/conversion/cli.py b/ebook_converter/ebooks/conversion/cli.py index a62d773..d4703ae 100644 --- a/ebook_converter/ebooks/conversion/cli.py +++ b/ebook_converter/ebooks/conversion/cli.py @@ -51,13 +51,13 @@ HEURISTIC_OPTIONS = ['markup_chapter_headings', 'italicize_common_cases', DEFAULT_TRUE_OPTIONS = HEURISTIC_OPTIONS + ['remove_fake_margins'] -def print_help(parser, log): +def print_help(parser): parser.print_help() def check_command_line_options(parser, args, log): if len(args) < 3 or args[1].startswith('-') or args[2].startswith('-'): - print_help(parser, log) + print_help(parser) log.error('\n\nYou must specify the input AND output files') raise SystemExit(1) @@ -292,7 +292,7 @@ def create_option_parser(args, log): parser = option_parser() if len(args) < 3: - print_help(parser, log) + print_help(parser) if any(x in args for x in ('-h', '--help')): raise SystemExit(0) else: diff --git a/ebook_converter/ebooks/covers.py b/ebook_converter/ebooks/covers.py index e5011d8..7fd7aaf 100644 --- a/ebook_converter/ebooks/covers.py +++ b/ebook_converter/ebooks/covers.py @@ -1,692 +1,69 @@ -import re, random, unicodedata, numbers -from collections import namedtuple -from contextlib import contextmanager -from math import ceil, sqrt, cos, sin, atan2 -from itertools import chain - -from PyQt5.Qt import ( - QImage, Qt, QFont, QPainter, QPointF, QTextLayout, QTextOption, - QFontMetrics, QTextCharFormat, QColor, QRect, QBrush, QLinearGradient, - QPainterPath, QPen, QRectF, QTransform, QRadialGradient -) - -from ebook_converter import force_unicode, fit_image +from ebook_converter import fit_image from ebook_converter.constants_old import __appname__, __version__ -from ebook_converter.ebooks.metadata import fmt_sidx -from ebook_converter.ebooks.metadata.book.base import Metadata -from ebook_converter.ebooks.metadata.book.formatter import SafeFormat from ebook_converter.gui2 import ensure_app, config, load_builtin_fonts, pixmap_to_data from ebook_converter.utils.cleantext import clean_ascii_chars, clean_xml_chars from ebook_converter.utils.config import JSONConfig - -__license__ = 'GPL v3' -__copyright__ = '2014, Kovid Goyal ' - -# Default settings {{{ -cprefs = JSONConfig('cover_generation') -cprefs.defaults['title_font_size'] = 120 # px -cprefs.defaults['subtitle_font_size'] = 80 # px -cprefs.defaults['footer_font_size'] = 80 # px -cprefs.defaults['cover_width'] = 1200 # px -cprefs.defaults['cover_height'] = 1600 # px -cprefs.defaults['title_font_family'] = None -cprefs.defaults['subtitle_font_family'] = None -cprefs.defaults['footer_font_family'] = None -cprefs.defaults['color_themes'] = {} -cprefs.defaults['disabled_color_themes'] = [] -cprefs.defaults['disabled_styles'] = [] -cprefs.defaults['title_template'] = '{title}' -cprefs.defaults['subtitle_template'] = '''{series:'test($, strcat("", $, " - ", raw_field("formatted_series_index")), "")'}''' -cprefs.defaults['footer_template'] = r'''program: -# Show at most two authors, on separate lines. -authors = field('authors'); -num = count(authors, ' & '); -authors = sublist(authors, 0, 2, ' & '); -authors = list_re(authors, ' & ', '(.+)', '\1'); -authors = re(authors, ' & ', '
'); -re(authors, '&&', '&') -''' -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 {{{ -Point = namedtuple('Point', 'x y') - - -def parse_text_formatting(text): - pos = 0 - tokens = [] - for m in re.finditer(r'', text): - q = text[pos:m.start()] - if q: - tokens.append((False, q)) - tokens.append((True, (m.group(1).lower(), '/' in m.group()[:2]))) - pos = m.end() - if tokens: - if text[pos:]: - tokens.append((False, text[pos:])) - else: - tokens = [(False, text)] - - ranges, open_ranges, text = [], [], [] - offset = 0 - for is_tag, tok in tokens: - if is_tag: - tag, closing = tok - if closing: - if open_ranges: - r = open_ranges.pop() - r[-1] = offset - r[-2] - if r[-1] > 0: - ranges.append(r) - else: - if tag in {'b', 'strong', 'i', 'em'}: - open_ranges.append([tag, offset, -1]) - else: - offset += len(tok.replace('&', '&')) - text.append(tok) - text = ''.join(text) - formats = [] - for tag, start, length in chain(ranges, open_ranges): - fmt = QTextCharFormat() - if tag in {'b', 'strong'}: - fmt.setFontWeight(QFont.Bold) - elif tag in {'i', 'em'}: - fmt.setFontItalic(True) - else: - continue - if length == -1: - length = len(text) - start - if length > 0: - r = QTextLayout.FormatRange() - r.format = fmt - r.start, r.length = start, length - formats.append(r) - return text, formats - - -class Block(object): - - def __init__(self, text='', width=0, font=None, img=None, max_height=100, align=Qt.AlignCenter): - self.layouts = [] - self._position = Point(0, 0) - self.leading = self.line_spacing = 0 - if font is not None: - fm = QFontMetrics(font, img) - self.leading = fm.leading() - self.line_spacing = fm.lineSpacing() - for text in text.split('
') if text else (): - text, formats = parse_text_formatting(sanitize(text)) - l = QTextLayout(unescape_formatting(text), font, img) - l.setAdditionalFormats(formats) - to = QTextOption(align) - to.setWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere) - l.setTextOption(to) - - l.beginLayout() - height = 0 - while height + 3*self.leading < max_height: - line = l.createLine() - if not line.isValid(): - break - line.setLineWidth(width) - height += self.leading - line.setPosition(QPointF(0, height)) - height += line.height() - max_height -= height - l.endLayout() - if self.layouts: - self.layouts.append(self.leading) - else: - self._position = Point(l.position().x(), l.position().y()) - self.layouts.append(l) - if self.layouts: - self.layouts.append(self.leading) - - @property - def height(self): - return int(ceil(sum(l if isinstance(l, numbers.Number) else l.boundingRect().height() for l in self.layouts))) - - @property - def position(self): - return self._position - - @position.setter - def position(self, new_pos): - (x, y) = new_pos - self._position = Point(x, y) - if self.layouts: - self.layouts[0].setPosition(QPointF(x, y)) - y += self.layouts[0].boundingRect().height() - for l in self.layouts[1:]: - if isinstance(l, numbers.Number): - y += l - else: - l.setPosition(QPointF(x, y)) - y += l.boundingRect().height() - - def draw(self, painter): - for l in self.layouts: - if hasattr(l, 'draw'): - # Etch effect for the text - painter.save() - painter.setRenderHints(QPainter.TextAntialiasing | QPainter.Antialiasing) - painter.save() - painter.setPen(QColor(255, 255, 255, 125)) - l.draw(painter, QPointF(1, 1)) - painter.restore() - l.draw(painter, QPointF()) - painter.restore() - - -def layout_text(prefs, img, title, subtitle, footer, max_height, style): - width = img.width() - 2 * style.hmargin - title, subtitle, footer = title, subtitle, footer - title_font = QFont(prefs.title_font_family or 'Liberation Serif') - title_font.setPixelSize(prefs.title_font_size) - title_font.setStyleStrategy(QFont.PreferAntialias) - title_block = Block(title, width, title_font, img, max_height, style.TITLE_ALIGN) - title_block.position = style.hmargin, style.vmargin - subtitle_block = Block() - if subtitle: - subtitle_font = QFont(prefs.subtitle_font_family or 'Liberation Sans') - subtitle_font.setPixelSize(prefs.subtitle_font_size) - subtitle_font.setStyleStrategy(QFont.PreferAntialias) - gap = 2 * title_block.leading - mh = max_height - title_block.height - gap - subtitle_block = Block(subtitle, width, subtitle_font, img, mh, style.SUBTITLE_ALIGN) - subtitle_block.position = style.hmargin, title_block.position.y + title_block.height + gap - - footer_font = QFont(prefs.footer_font_family or 'Liberation Serif') - footer_font.setStyleStrategy(QFont.PreferAntialias) - footer_font.setPixelSize(prefs.footer_font_size) - footer_block = Block(footer, width, footer_font, img, max_height, style.FOOTER_ALIGN) - footer_block.position = style.hmargin, img.height() - style.vmargin - footer_block.height - - return title_block, subtitle_block, footer_block - -# }}} - -# Format text using templates {{{ - - -def sanitize(s): - return unicodedata.normalize('NFC', clean_xml_chars(clean_ascii_chars(force_unicode(s or '')))) - - -_formatter = None -_template_cache = {} - - -def escape_formatting(val): - return val.replace('&', '&').replace('<', '<').replace('>', '>') - - -def unescape_formatting(val): - return val.replace('<', '<').replace('>', '>').replace('&', '&') - - -class Formatter(SafeFormat): - - def get_value(self, orig_key, args, kwargs): - ans = SafeFormat.get_value(self, orig_key, args, kwargs) - return escape_formatting(ans) - - -def formatter(): - global _formatter - if _formatter is None: - _formatter = Formatter() - return _formatter - - -def format_fields(mi, prefs): - f = formatter() - - def safe_format(field): - return f.safe_format(getattr(prefs, field), mi, 'Template error', mi, - template_cache=_template_cache) - return map(safe_format, ('title_template', 'subtitle_template', 'footer_template')) - - -@contextmanager -def preserve_fields(obj, fields): - if isinstance(fields, (str, bytes)): - fields = fields.split() - null = object() - mem = {f:getattr(obj, f, null) for f in fields} - try: - yield - finally: - for f, val in mem.items(): - if val is null: - delattr(obj, f) - else: - setattr(obj, f, val) - - -def format_text(mi, prefs): - with preserve_fields(mi, 'authors formatted_series_index'): - mi.authors = [a for a in mi.authors if a != 'Unknown'] - mi.formatted_series_index = fmt_sidx(mi.series_index or 0, use_roman=get_use_roman()) - return tuple(format_fields(mi, prefs)) -# }}} - - -# Colors {{{ -ColorTheme = namedtuple('ColorTheme', 'color1 color2 contrast_color1 contrast_color2') - - -def to_theme(x): - return {k:v for k, v in zip(ColorTheme._fields[:4], x.split())} - - -fallback_colors = to_theme('ffffff 000000 000000 ffffff') - -default_color_themes = { - 'Earth' : to_theme('e8d9ac c7b07b 564628 382d1a'), - 'Grass' : to_theme('d8edb5 abc8a4 375d3b 183128'), - 'Water' : to_theme('d3dcf2 829fe4 00448d 00305a'), - 'Silver': to_theme('e6f1f5 aab3b6 6e7476 3b3e40'), -} - - -def theme_to_colors(theme): - colors = {k:QColor('#' + theme[k]) for k in ColorTheme._fields} - return ColorTheme(**colors) - - -def load_color_themes(prefs): - t = default_color_themes.copy() - t.update(prefs.color_themes) - disabled = frozenset(prefs.disabled_color_themes) - ans = [theme_to_colors(v) for k, v in t.items() if k not in disabled] - if not ans: - # Ignore disabled and return only the builtin color themes - ans = [theme_to_colors(v) for k, v in default_color_themes.items()] - return ans - - -def color(color_theme, name): - ans = getattr(color_theme, name) - if not ans.isValid(): - ans = QColor('#' + fallback_colors[name]) - return ans - -# }}} - -# Styles {{{ - - -class Style(object): - - TITLE_ALIGN = SUBTITLE_ALIGN = FOOTER_ALIGN = Qt.AlignHCenter | Qt.AlignTop - - def __init__(self, color_theme, prefs): - self.load_colors(color_theme) - self.calculate_margins(prefs) - - def calculate_margins(self, prefs): - self.hmargin = int((50 / 600) * prefs.cover_width) - self.vmargin = int((50 / 800) * prefs.cover_height) - - def load_colors(self, color_theme): - self.color1 = color(color_theme, 'color1') - self.color2 = color(color_theme, 'color2') - self.ccolor1 = color(color_theme, 'contrast_color1') - self.ccolor2 = color(color_theme, 'contrast_color2') - - -class Cross(Style): - - NAME = 'The Cross' - - def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block): - painter.fillRect(rect, self.color1) - r = QRect(0, int(title_block.position.y), rect.width(), - title_block.height + subtitle_block.height + subtitle_block.line_spacing // 2 + title_block.leading) - painter.save() - p = QPainterPath() - p.addRoundedRect(QRectF(r), 10, 10 * r.width()/r.height(), Qt.RelativeSize) - painter.setClipPath(p) - painter.setRenderHint(QPainter.Antialiasing) - painter.fillRect(r, self.color2) - painter.restore() - r = QRect(0, 0, int(title_block.position.x), rect.height()) - painter.fillRect(r, self.color2) - return self.ccolor2, self.ccolor2, self.ccolor1 - - -class Half(Style): - - NAME = 'Half and Half' - - def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block): - g = QLinearGradient(QPointF(0, 0), QPointF(0, rect.height())) - g.setStops([(0, self.color1), (0.7, self.color2), (1, self.color1)]) - painter.fillRect(rect, QBrush(g)) - return self.ccolor1, self.ccolor1, self.ccolor1 - - -def rotate_vector(angle, x, y): - return x * cos(angle) - y * sin(angle), x * sin(angle) + y * cos(angle) - - -def draw_curved_line(painter_path, dx, dy, c1_frac, c1_amp, c2_frac, c2_amp): - length = sqrt(dx * dx + dy * dy) - angle = atan2(dy, dx) - c1 = QPointF(*rotate_vector(angle, c1_frac * length, c1_amp * length)) - c2 = QPointF(*rotate_vector(angle, c2_frac * length, c2_amp * length)) - pos = painter_path.currentPosition() - painter_path.cubicTo(pos + c1, pos + c2, pos + QPointF(dx, dy)) - - -class Banner(Style): - - NAME = 'Banner' - GRADE = 0.07 - - def calculate_margins(self, prefs): - Style.calculate_margins(self, prefs) - self.hmargin = int(0.15 * prefs.cover_width) - self.fold_width = int(0.1 * prefs.cover_width) - - def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block): - painter.fillRect(rect, self.color1) - top = title_block.position.y + 2 - extra_spacing = subtitle_block.line_spacing // 2 if subtitle_block.line_spacing else title_block.line_spacing // 3 - height = title_block.height + subtitle_block.height + extra_spacing + title_block.leading - right = rect.right() - self.hmargin - width = right - self.hmargin - - # Draw main banner - p = main = QPainterPath(QPointF(self.hmargin, top)) - draw_curved_line(p, rect.width() - 2 * self.hmargin, 0, 0.1, -0.1, 0.9, -0.1) - deltax = self.GRADE * height - p.lineTo(right + deltax, top + height) - right_corner = p.currentPosition() - draw_curved_line(p, - width - 2 * deltax, 0, 0.1, 0.05, 0.9, 0.05) - left_corner = p.currentPosition() - p.closeSubpath() - - # Draw fold rectangles - rwidth = self.fold_width - yfrac = 0.1 - width23 = int(0.67 * rwidth) - rtop = top + height * yfrac - - def draw_fold(x, m=1, corner=left_corner): - ans = p = QPainterPath(QPointF(x, rtop)) - draw_curved_line(p, rwidth*m, 0, 0.1, 0.1*m, 0.5, -0.2*m) - fold_upper = p.currentPosition() - p.lineTo(p.currentPosition() + QPointF(-deltax*m, height)) - fold_corner = p.currentPosition() - draw_curved_line(p, -rwidth*m, 0, 0.2, -0.1*m, 0.8, -0.1*m) - draw_curved_line(p, deltax*m, -height, 0.2, 0.1*m, 0.8, 0.1*m) - p = inner_fold = QPainterPath(corner) - dp = fold_corner - p.currentPosition() - draw_curved_line(p, dp.x(), dp.y(), 0.5, 0.3*m, 1, 0*m) - p.lineTo(fold_upper), p.closeSubpath() - return ans, inner_fold - - left_fold, left_inner = draw_fold(self.hmargin - width23) - right_fold, right_inner = draw_fold(right + width23, m=-1, corner=right_corner) - - painter.save() - painter.setRenderHint(QPainter.Antialiasing) - pen = QPen(self.ccolor2) - pen.setWidth(3) - pen.setJoinStyle(Qt.RoundJoin) - painter.setPen(pen) - for r in (left_fold, right_fold): - painter.fillPath(r, QBrush(self.color2)) - painter.drawPath(r) - for r in (left_inner, right_inner): - painter.fillPath(r, QBrush(self.color2.darker())) - painter.drawPath(r) - painter.fillPath(main, QBrush(self.color2)) - painter.drawPath(main) - painter.restore() - return self.ccolor2, self.ccolor2, self.ccolor1 - - -class Ornamental(Style): - - NAME = 'Ornamental' - - # SVG vectors {{{ - CORNER_VECTOR = "m 67.791903,64.260958 c -4.308097,-2.07925 -4.086719,-8.29575 0.334943,-9.40552 4.119758,-1.03399 8.732363,5.05239 5.393055,7.1162 -0.55,0.33992 -1,1.04147 -1,1.55902 0,1.59332 2.597425,1.04548 5.365141,-1.1316 1.999416,-1.57274 2.634859,-2.96609 2.634859,-5.7775 0,-9.55787 -9.827495,-13.42961 -24.43221,-9.62556 -3.218823,0.83839 -5.905663,1.40089 -5.970755,1.25 -0.06509,-0.1509 -0.887601,-1.19493 -1.827799,-2.32007 -1.672708,-2.00174 -1.636693,-2.03722 1.675668,-1.65052 1.861815,0.21736 6.685863,-0.35719 10.720107,-1.27678 12.280767,-2.79934 20.195487,-0.0248 22.846932,8.0092 3.187273,9.65753 -6.423297,17.7497 -15.739941,13.25313 z m 49.881417,-20.53932 c -3.19204,-2.701 -3.72967,-6.67376 -1.24009,-9.16334 2.48236,-2.48236 5.35141,-2.67905 7.51523,-0.51523 1.85966,1.85966 2.07045,6.52954 0.37143,8.22857 -2.04025,2.04024 3.28436,1.44595 6.92316,-0.77272 9.66959,-5.89579 0.88581,-18.22422 -13.0777,-18.35516 -5.28594,-0.0496 -10.31098,1.88721 -14.26764,5.4991 -1.98835,1.81509 -2.16454,1.82692 -2.7936,0.18763 -0.40973,-1.06774 0.12141,-2.82197 1.3628,-4.50104 2.46349,-3.33205 1.67564,-4.01299 -2.891784,-2.49938 -2.85998,0.94777 -3.81038,2.05378 -5.59837,6.51495 -1.184469,2.95536 -3.346819,6.86882 -4.805219,8.69657 -1.4584,1.82776 -2.65164,4.02223 -2.65164,4.87662 0,3.24694 -4.442667,0.59094 -5.872557,-3.51085 -1.361274,-3.90495 0.408198,-8.63869 4.404043,-11.78183 5.155844,-4.05558 1.612374,-3.42079 -9.235926,1.65457 -12.882907,6.02725 -16.864953,7.18038 -24.795556,7.18038 -8.471637,0 -13.38802,-1.64157 -17.634617,-5.88816 -2.832233,-2.83224 -3.849773,-4.81378 -4.418121,-8.6038 -1.946289,-12.9787795 8.03227,-20.91713135 19.767685,-15.7259993 5.547225,2.4538018 6.993631,6.1265383 3.999564,10.1557393 -5.468513,7.35914 -15.917883,-0.19431 -10.657807,-7.7041155 1.486298,-2.1219878 1.441784,-2.2225068 -0.984223,-2.2225068 -1.397511,0 -4.010527,1.3130878 -5.806704,2.9179718 -2.773359,2.4779995 -3.265777,3.5977995 -3.265777,7.4266705 0,5.10943 2.254112,8.84197 7.492986,12.40748 8.921325,6.07175 19.286666,5.61396 37.12088,-1.63946 15.35037,-6.24321 21.294999,-7.42408 34.886123,-6.92999 11.77046,0.4279 19.35803,3.05537 24.34054,8.42878 4.97758,5.3681 2.53939,13.58271 -4.86733,16.39873 -4.17361,1.58681 -11.00702,1.19681 -13.31978,-0.76018 z m 26.50156,-0.0787 c -2.26347,-2.50111 -2.07852,-7.36311 0.39995,-10.51398 2.68134,-3.40877 10.49035,-5.69409 18.87656,-5.52426 l 6.5685,0.13301 -7.84029,0.82767 c -8.47925,0.89511 -12.76997,2.82233 -16.03465,7.20213 -1.92294,2.57976 -1.96722,3.00481 -0.57298,5.5 1.00296,1.79495 2.50427,2.81821 4.46514,3.04333 2.92852,0.33623 2.93789,0.32121 1.08045,-1.73124 -1.53602,-1.69728 -1.64654,-2.34411 -0.61324,-3.58916 2.84565,-3.4288 7.14497,-0.49759 5.03976,3.43603 -1.86726,3.48903 -8.65528,4.21532 -11.3692,1.21647 z m -4.17462,-14.20302 c -0.38836,-0.62838 -0.23556,-1.61305 0.33954,-2.18816 1.3439,-1.34389 4.47714,-0.17168 3.93038,1.47045 -0.5566,1.67168 -3.38637,2.14732 -4.26992,0.71771 z m -8.48037,-9.1829 c -12.462,-4.1101 -12.53952,-4.12156 -25.49998,-3.7694 -24.020921,0.65269 -32.338219,0.31756 -37.082166,-1.49417 -5.113999,-1.95305 -8.192504,-6.3647405 -6.485463,-9.2940713 0.566827,-0.972691 1.020091,-1.181447 1.037211,-0.477701 0.01685,0.692606 1.268676,1.2499998 2.807321,1.2499998 1.685814,0 4.868609,1.571672 8.10041,4.0000015 4.221481,3.171961 6.182506,3.999221 9.473089,3.996261 l 4.149585,-0.004 -3.249996,-1.98156 c -3.056252,-1.863441 -4.051566,-3.8760635 -2.623216,-5.3044145 0.794,-0.794 6.188222,1.901516 9.064482,4.5295635 1.858669,1.698271 3.461409,1.980521 10.559493,1.859621 11.30984,-0.19266 20.89052,1.29095 31.97905,4.95208 7.63881,2.52213 11.51931,3.16471 22.05074,3.65141 7.02931,0.32486 13.01836,0.97543 13.30902,1.44571 0.29065,0.47029 -5.2356,0.83436 -12.28056,0.80906 -12.25942,-0.044 -13.34537,-0.2229 -25.30902,-4.16865 z" # noqa - # }}} - PATH_CACHE = {} - VIEWPORT = (400, 500) - - def calculate_margins(self, prefs): - self.hmargin = int((51 / self.VIEWPORT[0]) * prefs.cover_width) - self.vmargin = int((83 / self.VIEWPORT[1]) * prefs.cover_height) - - def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block): - if not self.PATH_CACHE: - from ebook_converter.utils.speedups import svg_path_to_painter_path - try: - self.__class__.PATH_CACHE['corner'] = svg_path_to_painter_path(self.CORNER_VECTOR) - except Exception: - import traceback - traceback.print_exc() - p = painter - painter.setRenderHint(QPainter.Antialiasing) - g = QRadialGradient(QPointF(rect.center()), rect.width()) - g.setColorAt(0, self.color1), g.setColorAt(1, self.color2) - painter.fillRect(rect, QBrush(g)) - painter.save() - painter.setWindow(0, 0, *self.VIEWPORT) - try: - path = self.PATH_CACHE['corner'] - except KeyError: - path = QPainterPath() - pen = p.pen() - pen.setColor(self.ccolor1) - p.setPen(pen) - - def corner(): - b = QBrush(self.ccolor1) - p.fillPath(path, b) - p.rotate(90), p.translate(100, -100), p.scale(1, -1), p.translate(-103, -97) - p.fillPath(path, b) - p.setWorldTransform(QTransform()) - # Top-left corner - corner() - # Top right corner - p.scale(-1, 1), p.translate(-400, 0), corner() - # Bottom left corner - p.scale(1, -1), p.translate(0, -500), corner() - # Bottom right corner - p.scale(-1, -1), p.translate(-400, -500), corner() - for y in (28.4, 471.7): - p.drawLine(QPointF(160, y), QPointF(240, y)) - for x in (31.3, 368.7): - p.drawLine(QPointF(x, 155), QPointF(x, 345)) - pen.setWidthF(1.8) - p.setPen(pen) - for y in (23.8, 476.7): - p.drawLine(QPointF(160, y), QPointF(240, y)) - for x in (26.3, 373.7): - p.drawLine(QPointF(x, 155), QPointF(x, 345)) - painter.restore() - - return self.ccolor2, self.ccolor2, self.ccolor1 - - -class Blocks(Style): - - NAME = 'Blocks' - FOOTER_ALIGN = Qt.AlignRight | Qt.AlignTop - - def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block): - painter.fillRect(rect, self.color1) - y = rect.height() - rect.height() // 3 - r = QRect(rect) - r.setBottom(y) - painter.fillRect(rect, self.color1) - r = QRect(rect) - r.setTop(y) - painter.fillRect(r, self.color2) - return self.ccolor1, self.ccolor1, self.ccolor2 - - -def all_styles(): - return set( - x.NAME for x in globals().values() if - isinstance(x, type) and issubclass(x, Style) and x is not Style - ) - - -def load_styles(prefs, respect_disabled=True): - disabled = frozenset(prefs.disabled_styles) if respect_disabled else () - ans = tuple(x for x in globals().values() if - isinstance(x, type) and issubclass(x, Style) and x is not Style and x.NAME not in disabled) - if not ans and disabled: - # If all styles have been disabled, ignore the disabling and return all - # the styles - ans = load_styles(prefs, respect_disabled=False) - return ans - -# }}} - - -def init_environment(): - ensure_app() - load_builtin_fonts() - - -def generate_cover(mi, prefs=None, as_qimage=False): - init_environment() - prefs = prefs or cprefs - prefs = {k:prefs.get(k) for k in cprefs.defaults} - prefs = Prefs(**prefs) - color_theme = random.choice(load_color_themes(prefs)) - style = random.choice(load_styles(prefs))(color_theme, prefs) - title, subtitle, footer = format_text(mi, prefs) - img = QImage(prefs.cover_width, prefs.cover_height, QImage.Format_ARGB32) - title_block, subtitle_block, footer_block = layout_text( - prefs, img, title, subtitle, footer, img.height() // 3, style) - p = QPainter(img) - rect = QRect(0, 0, img.width(), img.height()) - colors = style(p, rect, color_theme, title_block, subtitle_block, footer_block) - for block, color in zip((title_block, subtitle_block, footer_block), colors): - p.setPen(color) - block.draw(p) - p.end() - img.setText('Generated cover', '%s %s' % (__appname__, __version__)) - if as_qimage: - return img - return pixmap_to_data(img) - - -def override_prefs(base_prefs, **overrides): - ans = {k:overrides.get(k, base_prefs[k]) for k in cprefs.defaults} - override_color_theme = overrides.get('override_color_theme') - if override_color_theme is not None: - all_themes = set(default_color_themes) | set(ans['color_themes']) - if override_color_theme in all_themes: - all_themes.discard(override_color_theme) - ans['disabled_color_themes'] = all_themes - override_style = overrides.get('override_style') - if override_style is not None: - styles = all_styles() - if override_style in styles: - styles.discard(override_style) - ans['disabled_styles'] = styles - - return ans - - -def create_cover(title, authors, series=None, series_index=1, prefs=None, as_qimage=False): - ' Create a cover from the specified title, author and series. Any user set' - ' templates are ignored, to ensure that the specified metadata is used. ' - mi = Metadata(title, authors) - if series: - mi.series, mi.series_index = series, series_index - d = cprefs.defaults - prefs = override_prefs( - prefs or cprefs, title_template=d['title_template'], subtitle_template=d['subtitle_template'], footer_template=d['footer_template']) - return generate_cover(mi, prefs=prefs, as_qimage=as_qimage) - - -def calibre_cover2(title, author_string='', series_string='', prefs=None, as_qimage=False, logo_path=None): - init_environment() - title, subtitle, footer = '' + escape_formatting(title), '' + escape_formatting(series_string), '' + escape_formatting(author_string) - prefs = prefs or cprefs - prefs = {k:prefs.get(k) for k in cprefs.defaults} - scale = 800. / prefs['cover_height'] - scale_cover(prefs, scale) - prefs = Prefs(**prefs) - img = QImage(prefs.cover_width, prefs.cover_height, QImage.Format_ARGB32) - img.fill(Qt.white) - # colors = to_theme('ffffff ffffff 000000 000000') - color_theme = theme_to_colors(fallback_colors) - - class CalibeLogoStyle(Style): - NAME = GUI_NAME = 'calibre' - - def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block): - top = title_block.position.y + 10 - extra_spacing = subtitle_block.line_spacing // 2 if subtitle_block.line_spacing else title_block.line_spacing // 3 - height = title_block.height + subtitle_block.height + extra_spacing + title_block.leading - top += height + 25 - bottom = footer_block.position.y - 50 - logo = QImage(logo_path or I('library.png')) - pwidth, pheight = rect.width(), bottom - top - scaled, width, height = fit_image(logo.width(), logo.height(), pwidth, pheight) - x, y = (pwidth - width) // 2, (pheight - height) // 2 - rect = QRect(x, top + y, width, height) - painter.setRenderHint(QPainter.SmoothPixmapTransform) - painter.drawImage(rect, logo) - return self.ccolor1, self.ccolor1, self.ccolor1 - style = CalibeLogoStyle(color_theme, prefs) - title_block, subtitle_block, footer_block = layout_text( - prefs, img, title, subtitle, footer, img.height() // 3, style) - p = QPainter(img) - rect = QRect(0, 0, img.width(), img.height()) - colors = style(p, rect, color_theme, title_block, subtitle_block, footer_block) - for block, color in zip((title_block, subtitle_block, footer_block), colors): - p.setPen(color) - block.draw(p) - p.end() - img.setText('Generated cover', '%s %s' % (__appname__, __version__)) - if as_qimage: - return img - return pixmap_to_data(img) +#def calibre_cover2(title, author_string='', series_string='', prefs=None, as_qimage=False, logo_path=None): +# init_environment() +# title, subtitle, footer = '' + escape_formatting(title), '' + escape_formatting(series_string), '' + escape_formatting(author_string) +# prefs = prefs or cprefs +# prefs = {k:prefs.get(k) for k in cprefs.defaults} +# scale = 800. / prefs['cover_height'] +# scale_cover(prefs, scale) +# prefs = Prefs(**prefs) +# img = QImage(prefs.cover_width, prefs.cover_height, QImage.Format_ARGB32) +# img.fill(Qt.white) +# # colors = to_theme('ffffff ffffff 000000 000000') +# color_theme = theme_to_colors(fallback_colors) +# +# class CalibeLogoStyle(Style): +# NAME = GUI_NAME = 'calibre' +# +# def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block): +# top = title_block.position.y + 10 +# extra_spacing = subtitle_block.line_spacing // 2 if subtitle_block.line_spacing else title_block.line_spacing // 3 +# height = title_block.height + subtitle_block.height + extra_spacing + title_block.leading +# top += height + 25 +# bottom = footer_block.position.y - 50 +# logo = QImage(logo_path or I('library.png')) +# pwidth, pheight = rect.width(), bottom - top +# scaled, width, height = fit_image(logo.width(), logo.height(), pwidth, pheight) +# x, y = (pwidth - width) // 2, (pheight - height) // 2 +# rect = QRect(x, top + y, width, height) +# painter.setRenderHint(QPainter.SmoothPixmapTransform) +# painter.drawImage(rect, logo) +# return self.ccolor1, self.ccolor1, self.ccolor1 +# style = CalibeLogoStyle(color_theme, prefs) +# title_block, subtitle_block, footer_block = layout_text( +# prefs, img, title, subtitle, footer, img.height() // 3, style) +# p = QPainter(img) +# rect = QRect(0, 0, img.width(), img.height()) +# colors = style(p, rect, color_theme, title_block, subtitle_block, footer_block) +# for block, color in zip((title_block, subtitle_block, footer_block), colors): +# p.setPen(color) +# block.draw(p) +# p.end() +# img.setText('Generated cover', '%s %s' % (__appname__, __version__)) +# if as_qimage: +# return img +# return pixmap_to_data(img) def message_image(text, width=500, height=400, font_size=20): - init_environment() - img = QImage(width, height, QImage.Format_ARGB32) - img.fill(Qt.white) - p = QPainter(img) - f = QFont() - f.setPixelSize(font_size) - p.setFont(f) - r = img.rect().adjusted(10, 10, -10, -10) - p.drawText(r, Qt.AlignJustify | Qt.AlignVCenter | Qt.TextWordWrap, text) - p.end() - return pixmap_to_data(img) + # init_environment() + # img = QImage(width, height, QImage.Format_ARGB32) + # img.fill(Qt.white) + # p = QPainter(img) + # f = QFont() + # f.setPixelSize(font_size) + # p.setFont(f) + # r = img.rect().adjusted(10, 10, -10, -10) + # p.drawText(r, Qt.AlignJustify | Qt.AlignVCenter | Qt.TextWordWrap, text) + # p.end() + # return pixmap_to_data(img) + # TODO(gryf): geenrate image using pillow. + return None def scale_cover(prefs, scale): @@ -714,39 +91,3 @@ def generate_masthead(title, output_path=None, width=600, height=60, as_qimage=F return data with open(output_path, 'wb') as f: f.write(data) - - -def test(scale=0.25): - from PyQt5.Qt import QLabel, QPixmap, QMainWindow, QWidget, QScrollArea, QGridLayout - from ebook_converter.gui2 import Application - app = Application([]) - mi = Metadata('Unknown', ['Kovid Goyal', 'John & Doe', 'Author']) - mi.series = 'A series & styles' - m = QMainWindow() - sa = QScrollArea(m) - w = QWidget(m) - sa.setWidget(w) - l = QGridLayout(w) - w.setLayout(l), l.setSpacing(30) - scale *= w.devicePixelRatioF() - labels = [] - for r, color in enumerate(sorted(default_color_themes)): - for c, style in enumerate(sorted(all_styles())): - mi.series_index = c + 1 - mi.title = 'An algorithmic cover [%s]' % color - prefs = override_prefs(cprefs, override_color_theme=color, override_style=style) - scale_cover(prefs, scale) - img = generate_cover(mi, prefs=prefs, as_qimage=True) - img.setDevicePixelRatio(w.devicePixelRatioF()) - la = QLabel() - la.setPixmap(QPixmap.fromImage(img)) - l.addWidget(la, r, c) - labels.append(la) - m.setCentralWidget(sa) - w.resize(w.sizeHint()) - m.show() - app.exec_() - - -if __name__ == '__main__': - test() diff --git a/ebook_converter/ebooks/metadata/opf3.py b/ebook_converter/ebooks/metadata/opf3.py index 0f4fd1d..5c0831d 100644 --- a/ebook_converter/ebooks/metadata/opf3.py +++ b/ebook_converter/ebooks/metadata/opf3.py @@ -10,6 +10,7 @@ from ebook_converter import prints from ebook_converter.ebooks.metadata import authors_to_string from ebook_converter.ebooks.metadata import check_isbn from ebook_converter.ebooks.metadata import string_to_authors +from ebook_converter.ebooks.oeb import base as oeb_base from ebook_converter.ebooks.metadata.book import base from ebook_converter.ebooks.metadata.book.json_codec import ( decode_is_multiple, encode_is_multiple, object_to_unicode @@ -218,7 +219,7 @@ def set_refines(elem, existing_refines, *new_refines): remove_refines(elem, existing_refines) for ref in reversed(new_refines): prop, val, scheme = ref - r = elem.makeelement(base.tag('opf', 'meta')) + r = elem.makeelement(oeb_base.tag('opf', 'meta')) r.set('refines', '#' + eid), r.set('property', prop) r.text = val.strip() if scheme: @@ -254,7 +255,7 @@ def parse_identifier(ident, val, refines): # Try the OPF 2 style opf:scheme attribute, which will be present, for # example, in EPUB 3 files that have had their metadata set by an # application that only understands EPUB 2. - scheme = ident.get(base.tag('opf', 'scheme')) + scheme = ident.get(oeb_base.tag('opf', 'scheme')) if scheme and not lval.startswith('urn:'): return finalize(scheme, val) @@ -303,7 +304,7 @@ def set_identifiers(root, prefixes, refines, new_identifiers, continue metadata = XPath('./opf:metadata')(root)[0] for scheme, val in new_identifiers.items(): - ident = metadata.makeelement(base.tag('dc', 'ident')) + ident = metadata.makeelement(oeb_base.tag('dc', 'ident')) ident.text = '%s:%s' % (scheme, val) if package_identifier is None: metadata.append(ident) @@ -322,11 +323,11 @@ def identifier_writer(name): package_identifier = ident val = (ident.text or '').strip() if (val.startswith(name + ':') or - ident.get(base.tag('opf', 'scheme')) == name) and not is_package_id: + ident.get(oeb_base.tag('opf', 'scheme')) == name) and not is_package_id: remove_element(ident, refines) metadata = XPath('./opf:metadata')(root)[0] if ival: - ident = metadata.makeelement(base.tag('dc', 'ident')) + ident = metadata.makeelement(oeb_base.tag('dc', 'ident')) ident.text = '%s:%s' % (name, ival) if package_identifier is None: metadata.append(ident) @@ -387,7 +388,7 @@ def set_title(root, prefixes, refines, title, title_sort=None): main_title = find_main_title(root, refines, remove_blanks=True) if main_title is None: m = XPath('./opf:metadata')(root)[0] - main_title = m.makeelement(base.tag('dc', 'title')) + main_title = m.makeelement(oeb_base.tag('dc', 'title')) m.insert(0, main_title) main_title.text = title or None ts = [refdef('file-as', title_sort)] if title_sort else () @@ -424,7 +425,7 @@ def set_languages(root, prefixes, refines, languages): languages = ['und'] metadata = XPath('./opf:metadata')(root)[0] for lang in uniq(languages): - dc_lang = metadata.makeelement(base.tag('dc', 'lang')) + dc_lang = metadata.makeelement(oeb_base.tag('dc', 'lang')) dc_lang.text = lang metadata.append(dc_lang) # }}} @@ -456,7 +457,7 @@ def read_authors(root, prefixes, refines): if file_as: aus = file_as[0][-1] else: - aus = item.get(base.tag('opf', 'file_as')) or None + aus = item.get(oeb_base.tag('opf', 'file_as')) or None return Author(normalize_whitespace(val), normalize_whitespace(aus)) for item in XPath('./opf:metadata/dc:creator')(root): @@ -465,7 +466,7 @@ def read_authors(root, prefixes, refines): props = properties_for_id_with_scheme(item.get('id'), prefixes, refines) role = props.get('role') - opf_role = item.get(base.tag('opf', 'role')) + opf_role = item.get(oeb_base.tag('opf', 'role')) if role: if is_relators_role(props, 'aut'): roled_authors.append(author(item, props, val)) @@ -483,7 +484,7 @@ def set_authors(root, prefixes, refines, authors): for item in XPath('./opf:metadata/dc:creator')(root): props = properties_for_id_with_scheme(item.get('id'), prefixes, refines) - opf_role = item.get(base.tag('opf', 'role')) + opf_role = item.get(oeb_base.tag('opf', 'role')) if ((opf_role and opf_role.lower() != 'aut') or (props.get('role') and not is_relators_role(props, 'aut'))): continue @@ -491,18 +492,18 @@ def set_authors(root, prefixes, refines, authors): metadata = XPath('./opf:metadata')(root)[0] for author in authors: if author.name: - a = metadata.makeelement(base.tag('dc', 'creator')) + a = metadata.makeelement(oeb_base.tag('dc', 'creator')) aid = ensure_id(a) a.text = author.name metadata.append(a) - m = metadata.makeelement(base.tag('opf', 'meta'), + m = metadata.makeelement(oeb_base.tag('opf', 'meta'), attrib={'refines': '#' + aid, 'property': 'role', 'scheme': 'marc:relators'}) m.text = 'aut' metadata.append(m) if author.sort: - m = metadata.makeelement(base.tag('opf', 'meta'), + m = metadata.makeelement(oeb_base.tag('opf', 'meta'), attrib={'refines': '#' + aid, 'property': 'file-as'}) m.text = author.sort @@ -517,7 +518,7 @@ def read_book_producers(root, prefixes, refines): props = properties_for_id_with_scheme(item.get('id'), prefixes, refines) role = props.get('role') - opf_role = item.get(base.tag('opf', 'role')) + opf_role = item.get(oeb_base.tag('opf', 'role')) if role: if is_relators_role(props, 'bkp'): ans.append(normalize_whitespace(val)) @@ -530,7 +531,7 @@ def set_book_producers(root, prefixes, refines, producers): for item in XPath('./opf:metadata/dc:contributor')(root): props = properties_for_id_with_scheme(item.get('id'), prefixes, refines) - opf_role = item.get(base.tag('opf', 'role')) + opf_role = item.get(oeb_base.tag('opf', 'role')) if ((opf_role and opf_role.lower() != 'bkp') or (props.get('role') and not is_relators_role(props, 'bkp'))): continue @@ -538,11 +539,11 @@ def set_book_producers(root, prefixes, refines, producers): metadata = XPath('./opf:metadata')(root)[0] for bkp in producers: if bkp: - a = metadata.makeelement(base.tag('dc', 'contributor')) + a = metadata.makeelement(oeb_base.tag('dc', 'contributor')) aid = ensure_id(a) a.text = bkp metadata.append(a) - m = metadata.makeelement(base.tag('opf', 'meta'), + m = metadata.makeelement(oeb_base.tag('opf', 'meta'), attrib={'refines': '#' + aid, 'property': 'role', 'scheme': 'marc:relators'}) @@ -584,7 +585,7 @@ def set_pubdate(root, prefixes, refines, val): if not is_date_undefined(val): val = isoformat(val) m = XPath('./opf:metadata')(root)[0] - d = m.makeelement(base.tag('dc', 'date')) + d = m.makeelement(oeb_base.tag('dc', 'date')) d.text = val m.append(d) @@ -617,7 +618,7 @@ def create_timestamp(root, prefixes, m, val): ensure_prefix(root, prefixes, 'calibre', CALIBRE_PREFIX) ensure_prefix(root, prefixes, 'dcterms') val = w3cdtf(val) - d = m.makeelement(base.tag('opf', 'meta'), + d = m.makeelement(oeb_base.tag('opf', 'meta'), attrib={'property': 'calibre:timestamp', 'scheme': 'dcterms:W3CDTF'}) d.text = val @@ -660,7 +661,7 @@ def set_last_modified(root, prefixes, refines, val=None): else: ensure_prefix(root, prefixes, 'dcterms') m = XPath('./opf:metadata')(root)[0] - meta = m.makeelement(base.tag('opf', 'meta'), + meta = m.makeelement(oeb_base.tag('opf', 'meta'), attrib={'property': 'dcterms:modified', 'scheme': 'dcterms:W3CDTF'}) m.append(meta) @@ -685,7 +686,7 @@ def set_comments(root, prefixes, refines, val): if val: val = val.strip() if val: - c = m.makeelement(base.tag('dc', 'desc')) + c = m.makeelement(oeb_base.tag('dc', 'desc')) c.text = val m.append(c) # }}} @@ -707,7 +708,7 @@ def set_publisher(root, prefixes, refines, val): if val: val = val.strip() if val: - c = m.makeelement(base.tag('dc', 'publisher')) + c = m.makeelement(oeb_base.tag('dc', 'publisher')) c.text = normalize_whitespace(val) m.append(c) # }}} @@ -730,7 +731,7 @@ def set_tags(root, prefixes, refines, val): if val: val = uniq(list(filter(None, val))) for x in val: - c = m.makeelement(base.tag('dc', 'subj')) + c = m.makeelement(oeb_base.tag('dc', 'subj')) c.text = normalize_whitespace(x) if c.text: m.append(c) @@ -762,7 +763,7 @@ def read_rating(root, prefixes, refines): def create_rating(root, prefixes, val): ensure_prefix(root, prefixes, 'calibre', CALIBRE_PREFIX) m = XPath('./opf:metadata')(root)[0] - d = m.makeelement(base.tag('opf', 'meta'), attrib={'property': 'calibre:rating'}) + d = m.makeelement(oeb_base.tag('opf', 'meta'), attrib={'property': 'calibre:rating'}) d.text = val m.append(d) @@ -812,7 +813,7 @@ def read_series(root, prefixes, refines): def create_series(root, refines, series, series_index): m = XPath('./opf:metadata')(root)[0] - d = m.makeelement(base.tag('opf', 'meta'), + d = m.makeelement(oeb_base.tag('opf', 'meta'), attrib={'property': 'belongs-to-collection'}) d.text = series m.append(d) @@ -882,7 +883,7 @@ def dict_writer(name, serialize=dump_dict, remove2=True): if val: ensure_prefix(root, prefixes, 'calibre', CALIBRE_PREFIX) m = XPath('./opf:metadata')(root)[0] - d = m.makeelement(base.tag('opf', 'meta'), + d = m.makeelement(oeb_base.tag('opf', 'meta'), attrib={'property': 'calibre:%s' % name}) d.text = serialize(val) m.append(d) diff --git a/ebook_converter/ebooks/oeb/parse_utils.py b/ebook_converter/ebooks/oeb/parse_utils.py index ef1337b..a9e7651 100644 --- a/ebook_converter/ebooks/oeb/parse_utils.py +++ b/ebook_converter/ebooks/oeb/parse_utils.py @@ -1,3 +1,4 @@ +import logging import re from lxml import etree @@ -13,6 +14,10 @@ RECOVER_PARSER = etree.XMLParser(recover=True, no_network=True, resolve_entities=False) +# TODO(gryf): replace this with real logging setup. +LOG = logging + + class NotHTML(Exception): def __init__(self, root_tag): @@ -152,8 +157,7 @@ def check_for_html5(prefix, root): def parse_html(data, log=None, decoder=None, preprocessor=None, filename='', non_html_file_tags=frozenset()): if log is None: - from ebook_converter.utils.logging import default_log - log = default_log + log = LOG filename = force_unicode(filename, enc=filesystem_encoding) diff --git a/ebook_converter/library/catalogs/epub_mobi.py b/ebook_converter/library/catalogs/epub_mobi.py index 8cad0ed..5a562c8 100644 --- a/ebook_converter/library/catalogs/epub_mobi.py +++ b/ebook_converter/library/catalogs/epub_mobi.py @@ -484,13 +484,15 @@ class EPUB_MOBI(CatalogPlugin): recommendations.append(('cover', cpath, OptionRecommendation.HIGH)) log.info("using existing catalog cover") else: - from ebook_converter.ebooks.covers import calibre_cover2 - log.info("replacing catalog cover") - new_cover_path = PersistentTemporaryFile(suffix='.jpg') - new_cover = calibre_cover2(opts.catalog_title, 'calibre') - new_cover_path.write(new_cover) - new_cover_path.close() - recommendations.append(('cover', new_cover_path.name, OptionRecommendation.HIGH)) + # TODO(gryf): feature: generating cover with pillow. + pass + # from ebook_converter.ebooks.covers import calibre_cover2 + # log.info("replacing catalog cover") + # new_cover_path = PersistentTemporaryFile(suffix='.jpg') + # # new_cover = calibre_cover2(opts.catalog_title, 'calibre') + # new_cover_path.write('') + # new_cover_path.close() + # recommendations.append(('cover', new_cover_path.name, OptionRecommendation.HIGH)) # Run ebook-convert from ebook_converter.ebooks.conversion.plumber import Plumber diff --git a/ebook_converter/utils/config_base.py b/ebook_converter/utils/config_base.py index eb7b68e..e24bfa6 100644 --- a/ebook_converter/utils/config_base.py +++ b/ebook_converter/utils/config_base.py @@ -410,7 +410,6 @@ class Config(ConfigInterface): src = self.option_set.serialize(opts) f.seek(0) f.truncate() - __import__('pdb').set_trace() if isinstance(src, str): src = src.encode('utf-8') f.write(src)