def fetch_scheduled_recipe(arg): # {{{ fmt = prefs['output_format'].lower() pt = PersistentTemporaryFile(suffix='_recipe_out.%s' % fmt.lower()) pt.close() recs = [] ps = load_defaults('page_setup') if 'output_profile' in ps: recs.append(('output_profile', ps['output_profile'], OptionRecommendation.HIGH)) lf = load_defaults('look_and_feel') if lf.get('base_font_size', 0.0) != 0.0: recs.append(('base_font_size', lf['base_font_size'], OptionRecommendation.HIGH)) recs.append( ('keep_ligatures', lf.get('keep_ligatures', False), OptionRecommendation.HIGH)) lr = load_defaults('lrf_output') if lr.get('header', False): recs.append(('header', True, OptionRecommendation.HIGH)) recs.append(('header_format', '%t', OptionRecommendation.HIGH)) epub = load_defaults('epub_output') if epub.get('epub_flatten', False): recs.append(('epub_flatten', True, OptionRecommendation.HIGH)) args = [arg['recipe'], pt.name, recs] if arg['username'] is not None: recs.append(('username', arg['username'], OptionRecommendation.HIGH)) if arg['password'] is not None: recs.append(('password', arg['password'], OptionRecommendation.HIGH)) return 'gui_convert', args, _( 'Fetch news from ') + arg['title'], fmt.upper(), [pt]
def fetch_scheduled_recipe(arg): # {{{ fmt = prefs['output_format'].lower() pt = PersistentTemporaryFile(suffix='_recipe_out.%s'%fmt.lower()) pt.close() recs = [] ps = load_defaults('page_setup') if 'output_profile' in ps: recs.append(('output_profile', ps['output_profile'], OptionRecommendation.HIGH)) lf = load_defaults('look_and_feel') if lf.get('base_font_size', 0.0) != 0.0: recs.append(('base_font_size', lf['base_font_size'], OptionRecommendation.HIGH)) recs.append(('keep_ligatures', lf.get('keep_ligatures', False), OptionRecommendation.HIGH)) lr = load_defaults('lrf_output') if lr.get('header', False): recs.append(('header', True, OptionRecommendation.HIGH)) recs.append(('header_format', '%t', OptionRecommendation.HIGH)) epub = load_defaults('epub_output') if epub.get('epub_flatten', False): recs.append(('epub_flatten', True, OptionRecommendation.HIGH)) args = [arg['recipe'], pt.name, recs] if arg['username'] is not None: recs.append(('username', arg['username'], OptionRecommendation.HIGH)) if arg['password'] is not None: recs.append(('password', arg['password'], OptionRecommendation.HIGH)) return 'gui_convert', args, _('Fetch news from ')+arg['title'], fmt.upper(), [pt]
def fetch_scheduled_recipe(arg): # {{{ fmt = prefs['output_format'].lower() # Never use AZW3 for periodicals... if fmt == 'azw3': fmt = 'mobi' pt = PersistentTemporaryFile(suffix='_recipe_out.%s' % fmt.lower()) pt.close() recs = [] ps = load_defaults('page_setup') if 'output_profile' in ps: recs.append(('output_profile', ps['output_profile'], OptionRecommendation.HIGH)) for edge in ('left', 'top', 'bottom', 'right'): edge = 'margin_' + edge if edge in ps: recs.append((edge, ps[edge], OptionRecommendation.HIGH)) lf = load_defaults('look_and_feel') if lf.get('base_font_size', 0.0) != 0.0: recs.append(('base_font_size', lf['base_font_size'], OptionRecommendation.HIGH)) recs.append( ('keep_ligatures', lf.get('keep_ligatures', False), OptionRecommendation.HIGH)) lr = load_defaults('lrf_output') if lr.get('header', False): recs.append(('header', True, OptionRecommendation.HIGH)) recs.append(('header_format', '%t', OptionRecommendation.HIGH)) epub = load_defaults('epub_output') if epub.get('epub_flatten', False): recs.append(('epub_flatten', True, OptionRecommendation.HIGH)) if fmt == 'pdf': pdf = load_defaults('pdf_output') from calibre.customize.ui import plugin_for_output_format p = plugin_for_output_format('pdf') for opt in p.options: recs.append( (opt.option.name, pdf.get(opt.option.name, opt.recommended_value), OptionRecommendation.HIGH)) args = [arg['recipe'], pt.name, recs] if arg['username'] is not None: recs.append(('username', arg['username'], OptionRecommendation.HIGH)) if arg['password'] is not None: recs.append(('password', arg['password'], OptionRecommendation.HIGH)) return 'gui_convert', args, _( 'Fetch news from %s') % arg['title'], fmt.upper(), [pt]
def bulk_defaults_for_input_format(fmt): plugin = plugin_for_input_format(fmt) if plugin is not None: w = config_widget_for_input_plugin(plugin) if w is not None: return load_defaults(w.COMMIT_NAME) return {}
def initialize_options(self, get_option, get_help, db=None, book_id=None): ''' :param get_option: A callable that takes one argument: the option name and returns the corresponding OptionRecommendation. :param get_help: A callable that takes the option name and return a help string. ''' defaults = load_defaults(self._name) defaults.merge_recommendations(get_option, OptionRecommendation.LOW, self._options) if db is not None: specifics = load_specifics(db, book_id) specifics.merge_recommendations(get_option, OptionRecommendation.HIGH, self._options, only_existing=True) defaults.update(specifics) self.apply_recommendations(defaults) self.setup_help(get_help) def process_child(child): for g in child.children(): if isinstance(g, QLabel): buddy = g.buddy() if buddy is not None and hasattr(buddy, '_help'): g._help = buddy._help htext = unicode(buddy.toolTip()).strip() g.setToolTip(htext) g.setWhatsThis(htext) g.__class__.enterEvent = lambda obj, event: self.set_help(getattr(obj, '_help', obj.toolTip())) else: process_child(g) process_child(self)
def options(self): # Save/return the current options # exclude_genre stores literally # Section switches store as True/False # others store as lists opts_dict = {} opt_value = unicode(self.library_url.text()) opts_dict['library_url'] = opt_value gprefs.set(self.name + '_' + 'library_url', opt_value) opt_value = unicode(self.excluded_tags.text()) opt_value = unicode([tag.strip() for tag in opt_value.split(',')]) gprefs.set(self.name + '_' + 'exclusion_tags', opt_value) opts_dict['exclusion_tags'] = opt_value opts_dict['generate_series'] = True opts_dict['generate_recently_added'] = False try: opts_dict['output_profile'] = [load_defaults('page_setup')['output_profile']] except: opts_dict['output_profile'] = ['default'] opts_dict['use_existing_cover'] = False opts_dict['thumb_width'] = 1.0 if self.DEBUG: print "opts_dict" for opt in sorted(opts_dict.keys(), key=sort_key): print " %s: %s" % (opt, repr(opts_dict[opt])) return opts_dict
def fetch_scheduled_recipe(arg): # {{{ fmt = prefs['output_format'].lower() # Never use AZW3 for periodicals... if fmt == 'azw3': fmt = 'mobi' pt = PersistentTemporaryFile(suffix='_recipe_out.%s'%fmt.lower()) pt.close() recs = [] ps = load_defaults('page_setup') if 'output_profile' in ps: recs.append(('output_profile', ps['output_profile'], OptionRecommendation.HIGH)) for edge in ('left', 'top', 'bottom', 'right'): edge = 'margin_' + edge if edge in ps: recs.append((edge, ps[edge], OptionRecommendation.HIGH)) lf = load_defaults('look_and_feel') if lf.get('base_font_size', 0.0) != 0.0: recs.append(('base_font_size', lf['base_font_size'], OptionRecommendation.HIGH)) recs.append(('keep_ligatures', lf.get('keep_ligatures', False), OptionRecommendation.HIGH)) lr = load_defaults('lrf_output') if lr.get('header', False): recs.append(('header', True, OptionRecommendation.HIGH)) recs.append(('header_format', '%t', OptionRecommendation.HIGH)) epub = load_defaults('epub_output') if epub.get('epub_flatten', False): recs.append(('epub_flatten', True, OptionRecommendation.HIGH)) if fmt == 'pdf': pdf = load_defaults('pdf_output') from calibre.customize.ui import plugin_for_output_format p = plugin_for_output_format('pdf') for opt in p.options: recs.append((opt.option.name, pdf.get(opt.option.name, opt.recommended_value), OptionRecommendation.HIGH)) args = [arg['recipe'], pt.name, recs] if arg['username'] is not None: recs.append(('username', arg['username'], OptionRecommendation.HIGH)) if arg['password'] is not None: recs.append(('password', arg['password'], OptionRecommendation.HIGH)) return 'gui_convert', args, _('Fetch news from ')+arg['title'], fmt.upper(), [pt]
def options(self): # Save/return the current options # exclude_genre stores literally # generate_titles, generate_recently_added store as True/False # others store as lists opts_dict = {} # Save values to gprefs for opt in self.OPTION_FIELDS: c_name, c_def, c_type = opt if c_type in ['check_box', 'radio_button']: opt_value = getattr(self, c_name).isChecked() elif c_type in ['combo_box']: opt_value = unicode(getattr(self, c_name).currentText()).strip() elif c_type in ['line_edit']: opt_value = unicode(getattr(self, c_name).text()).strip() elif c_type in ['spin_box']: opt_value = unicode(getattr(self, c_name).value()) gprefs.set(self.name + '_' + c_name, opt_value) # Construct opts object if c_name == 'exclude_tags': # store as list opts_dict[c_name] = opt_value.split(',') else: opts_dict[c_name] = opt_value # Generate markers for hybrids opts_dict['read_book_marker'] = "%s:%s" % (self.read_source_field_name, self.read_pattern.text()) opts_dict['exclude_book_marker'] = "%s:%s" % ( self.exclude_source_field_name, self.exclude_pattern.text()) # Generate specs for merge_comments, header_note_source_field checked = '' if self.merge_before.isChecked(): checked = 'before' elif self.merge_after.isChecked(): checked = 'after' include_hr = self.include_hr.isChecked() opts_dict['merge_comments'] = "%s:%s:%s" % \ (self.merge_source_field_name, checked, include_hr) opts_dict[ 'header_note_source_field'] = self.header_note_source_field_name # Append the output profile try: opts_dict['output_profile'] = [ load_defaults('page_setup')['output_profile'] ] except: opts_dict['output_profile'] = ['default'] if False: print "opts_dict" for opt in sorted(opts_dict.keys()): print " %s: %s" % (opt, repr(opts_dict[opt])) return opts_dict
def create_epub_cover(container, cover_path): from calibre.ebooks.conversion.config import load_defaults from calibre.ebooks.oeb.transforms.cover import CoverManager ext = cover_path.rpartition('.')[-1].lower() raster_cover_item = container.generate_item('cover.'+ext, id_prefix='cover') raster_cover = container.href_to_name(raster_cover_item.get('href'), container.opf_name) with open(cover_path, 'rb') as src, container.open(raster_cover, 'wb') as dest: shutil.copyfileobj(src, dest) opts = load_defaults('epub_output') keep_aspect = opts.get('preserve_cover_aspect_ratio', False) no_svg = opts.get('no_svg_cover', False) if no_svg: style = 'style="height: 100%%"' templ = CoverManager.NONSVG_TEMPLATE.replace('__style__', style) else: width, height = 600, 800 try: width, height = identify(cover_path)[:2] except: container.log.exception("Failed to get width and height of cover") ar = 'xMidYMid meet' if keep_aspect else 'none' templ = CoverManager.SVG_TEMPLATE.replace('__ar__', ar) templ = templ.replace('__viewbox__', '0 0 %d %d'%(width, height)) templ = templ.replace('__width__', str(width)) templ = templ.replace('__height__', str(height)) titlepage_item = container.generate_item('titlepage.xhtml', id_prefix='titlepage') titlepage = container.href_to_name(titlepage_item.get('href'), container.opf_name) raw = templ%container.name_to_href(raster_cover).encode('utf-8') with container.open(titlepage, 'wb') as f: f.write(raw) # We have to make sure the raster cover item has id="cover" for the moron # that wrote the Nook firmware if raster_cover_item.get('id') != 'cover': from calibre.ebooks.oeb.base import uuid_id newid = uuid_id() for item in container.opf_xpath('//*[@id="cover"]'): item.set('id', newid) for item in container.opf_xpath('//*[@idref="cover"]'): item.set('idref', newid) raster_cover_item.set('id', 'cover') spine = container.opf_xpath('//opf:spine')[0] ref = spine.makeelement(OPF('itemref'), idref=titlepage_item.get('id')) container.insert_into_xml(spine, ref, index=0) guide = container.opf_get_or_create('guide') container.insert_into_xml(guide, guide.makeelement( OPF('reference'), type='cover', title=_('Cover'), href=container.name_to_href(titlepage, base=container.opf_name))) metadata = container.opf_get_or_create('metadata') meta = metadata.makeelement(OPF('meta'), name='cover') meta.set('content', raster_cover_item.get('id')) container.insert_into_xml(metadata, meta) return raster_cover, titlepage
def get_recipe(urls, title): fmt = cprefs['output_format'].lower() pt = PersistentTemporaryFile(suffix='_recipe_out.%s' % fmt.lower()) pt.close() recs = [] ps = load_defaults('page_setup') if 'output_profile' in ps: recs.append(('output_profile', ps['output_profile'], OptionRecommendation.HIGH)) lf = load_defaults('look_and_feel') if lf.get('base_font_size', 0.0) != 0.0: recs.append(('base_font_size', lf['base_font_size'], OptionRecommendation.HIGH)) recs.append(('keep_ligatures', lf.get('keep_ligatures', False), OptionRecommendation.HIGH)) lr = load_defaults('lrf_output') if lr.get('header', False): recs.append(('header', True, OptionRecommendation.HIGH)) recs.append(('header_format', '%t', OptionRecommendation.HIGH)) epub = load_defaults('epub_output') if epub.get('epub_flatten', False): recs.append(('epub_flatten', True, OptionRecommendation.HIGH)) recipe = get_resources('wikipedia.recipe') url_line = bytes(repr(urls)) recipe = re.sub( br'^(\s+urls = ).+?# REPLACE_ME_URLS', br'\1' + url_line, recipe, flags=re.M) if title.strip(): title_line = bytes(repr(title)) recipe = re.sub( br'^(\s+title\s+=\s+)DEFAULT_TITLE', br'\1' + title_line, recipe, flags=re.M) logo = get_resources('images/icon.png') lf = PersistentTemporaryFile('_wiki_logo.png') lf.write(logo) recipe = recipe.replace(b'LOGO = None', b'LOGO = %r' % lf.name) lf.close() rf = PersistentTemporaryFile(suffix='_wikipedia.recipe') rf.write(recipe) rf.close() args = [rf.name, pt.name, recs] return args, fmt.upper(), [pt, rf, lf]
def commit(cls): cls.set_output_profile() cls.set_output_format() if cls.supports_color: from calibre.ebooks.conversion.config import load_defaults, save_defaults recs = load_defaults('comic_input') recs['dont_grayscale'] = True save_defaults('comic_input', recs)
def generate_masthead(title, output_path=None, width=600, height=60): from calibre.ebooks.conversion.config import load_defaults recs = load_defaults('mobi_output') masthead_font_family = recs.get('masthead_font', None) from calibre.ebooks.covers import generate_masthead return generate_masthead(title, output_path=output_path, width=width, height=height, font_family=masthead_font_family)
def options(self): # Save/return the current options # exclude_genre stores literally # generate_titles, generate_recently_added store as True/False # others store as lists opts_dict = {} # Save values to gprefs for opt in self.OPTION_FIELDS: c_name, c_def, c_type = opt if c_type in ['check_box', 'radio_button']: opt_value = getattr(self, c_name).isChecked() elif c_type in ['combo_box']: opt_value = unicode(getattr(self,c_name).currentText()).strip() elif c_type in ['line_edit']: opt_value = unicode(getattr(self, c_name).text()).strip() elif c_type in ['spin_box']: opt_value = unicode(getattr(self, c_name).value()) gprefs.set(self.name + '_' + c_name, opt_value) # Construct opts object if c_name == 'exclude_tags': # store as list opts_dict[c_name] = opt_value.split(',') else: opts_dict[c_name] = opt_value # Generate markers for hybrids opts_dict['read_book_marker'] = "%s:%s" % (self.read_source_field_name, self.read_pattern.text()) opts_dict['exclude_book_marker'] = "%s:%s" % (self.exclude_source_field_name, self.exclude_pattern.text()) # Generate specs for merge_comments, header_note_source_field checked = '' if self.merge_before.isChecked(): checked = 'before' elif self.merge_after.isChecked(): checked = 'after' include_hr = self.include_hr.isChecked() opts_dict['merge_comments'] = "%s:%s:%s" % \ (self.merge_source_field_name, checked, include_hr) opts_dict['header_note_source_field'] = self.header_note_source_field_name # Append the output profile try: opts_dict['output_profile'] = [load_defaults('page_setup')['output_profile']] except: opts_dict['output_profile'] = ['default'] if False: print "opts_dict" for opt in sorted(opts_dict.keys()): print " %s: %s" % (opt, repr(opts_dict[opt])) return opts_dict
def get_profile_values(self): from calibre.ebooks.conversion.config import load_defaults recs = load_defaults('page_setup') pfname = recs.get('output_profile', 'default') from calibre.customize.ui import output_profiles for profile in output_profiles(): if profile.short_name == pfname: break dbase = profile.fbase fsizes = profile.fkey return dbase, fsizes
def generate_masthead(title, output_path=None, width=600, height=60): from calibre.ebooks.conversion.config import load_defaults from calibre.utils.config import tweaks fp = tweaks["generate_cover_title_font"] if not fp: fp = P("fonts/liberation/LiberationSerif-Bold.ttf") font_path = default_font = fp recs = load_defaults("mobi_output") masthead_font_family = recs.get("masthead_font", "Default") if masthead_font_family != "Default": from calibre.utils.fonts.scanner import font_scanner, NoFonts try: faces = font_scanner.fonts_for_family(masthead_font_family) except NoFonts: faces = [] if faces: font_path = faces[0]["path"] if not font_path or not os.access(font_path, os.R_OK): font_path = default_font try: from PIL import Image, ImageDraw, ImageFont Image, ImageDraw, ImageFont except ImportError: import Image, ImageDraw, ImageFont img = Image.new("RGB", (width, height), "white") draw = ImageDraw.Draw(img) try: font = ImageFont.truetype(font_path, 48, encoding="unic") except: font = ImageFont.truetype(default_font, 48, encoding="unic") text = force_unicode(title) width, height = draw.textsize(text, font=font) left = max(int((width - width) / 2.0), 0) top = max(int((height - height) / 2.0), 0) draw.text((left, top), text, fill=(0, 0, 0), font=font) if output_path is None: f = StringIO() img.save(f, "JPEG") return f.getvalue() else: with open(output_path, "wb") as f: img.save(f, "JPEG")
def merge_group(group_name, option_names): if not group_name or group_name in ('debug', 'metadata'): return defs = load_defaults(group_name) defs.merge_recommendations(plumber.get_option_by_name, OptionRecommendation.LOW, option_names) specifics.merge_recommendations(plumber.get_option_by_name, OptionRecommendation.HIGH, option_names, only_existing=True) for k in defs: if k in specifics: defs[k] = specifics[k] defs = defs.as_dict() ans['options'].update(defs['options']) ans['disabled'] |= set(defs['disabled'])
def merge_group(group_name, option_names): if not group_name or group_name in ('debug', 'metadata'): return defs = load_defaults(group_name) defs.merge_recommendations( plumber.get_option_by_name, OptionRecommendation.LOW, option_names) specifics.merge_recommendations( plumber.get_option_by_name, OptionRecommendation.HIGH, option_names, only_existing=True) defaults = defs.as_dict()['options'] for k in defs: if k in specifics: defs[k] = specifics[k] defs = defs.as_dict() ans['options'].update(defs['options']) ans['disabled'] |= set(defs['disabled']) ans['defaults'].update(defaults) ans['help'] = plumber.get_all_help()
def generate_masthead(title, output_path=None, width=600, height=60): from calibre.ebooks.conversion.config import load_defaults from calibre.utils.config import tweaks fp = tweaks['generate_cover_title_font'] if not fp: fp = P('fonts/liberation/LiberationSerif-Bold.ttf') font_path = default_font = fp recs = load_defaults('mobi_output') masthead_font_family = recs.get('masthead_font', 'Default') if masthead_font_family != 'Default': from calibre.utils.fonts.scanner import font_scanner, NoFonts try: faces = font_scanner.fonts_for_family(masthead_font_family) except NoFonts: faces = [] if faces: font_path = faces[0]['path'] if not font_path or not os.access(font_path, os.R_OK): font_path = default_font try: from PIL import Image, ImageDraw, ImageFont Image, ImageDraw, ImageFont except ImportError: import Image, ImageDraw, ImageFont img = Image.new('RGB', (width, height), 'white') draw = ImageDraw.Draw(img) try: font = ImageFont.truetype(font_path, 48, encoding='unic') except: font = ImageFont.truetype(default_font, 48, encoding='unic') text = force_unicode(title) width, height = draw.textsize(text, font=font) left = max(int((width - width)/2.), 0) top = max(int((height - height)/2.), 0) draw.text((left, top), text, fill=(0,0,0), font=font) if output_path is None: f = StringIO() img.save(f, 'JPEG') return f.getvalue() else: with open(output_path, 'wb') as f: img.save(f, 'JPEG')
def generate_masthead(title, output_path=None, width=600, height=60): from calibre.ebooks.conversion.config import load_defaults from calibre.utils.fonts import fontconfig from calibre.utils.config import tweaks fp = tweaks['generate_cover_title_font'] if not fp: fp = P('fonts/liberation/LiberationSerif-Bold.ttf') font_path = default_font = fp recs = load_defaults('mobi_output') masthead_font_family = recs.get('masthead_font', 'Default') if masthead_font_family != 'Default': masthead_font = fontconfig.files_for_family(masthead_font_family) # Assume 'normal' always in dict, else use default # {'normal': (path_to_font, friendly name)} if 'normal' in masthead_font: font_path = masthead_font['normal'][0] if not font_path or not os.access(font_path, os.R_OK): font_path = default_font try: from PIL import Image, ImageDraw, ImageFont Image, ImageDraw, ImageFont except ImportError: import Image, ImageDraw, ImageFont img = Image.new('RGB', (width, height), 'white') draw = ImageDraw.Draw(img) try: font = ImageFont.truetype(font_path, 48, encoding='unic') except: font = ImageFont.truetype(default_font, 48, encoding='unic') text = force_unicode(title) width, height = draw.textsize(text, font=font) left = max(int((width - width)/2.), 0) top = max(int((height - height)/2.), 0) draw.text((left, top), text, fill=(0,0,0), font=font) if output_path is None: f = StringIO() img.save(f, 'JPEG') return f.getvalue() else: with open(output_path, 'wb') as f: img.save(f, 'JPEG')
def render_jacket(container, jacket): mi = container.mi ps = load_defaults('page_setup') op = ps.get('output_profile', 'default') opmap = {x.short_name:x for x in output_profiles()} output_profile = opmap.get(op, opmap['default']) root = render(mi, output_profile) for img, path in referenced_images(root): container.log('Embedding referenced image: %s into jacket' % path) ext = path.rpartition('.')[-1] jacket_item = container.generate_item('jacket_image.'+ext, id_prefix='jacket_img') name = container.href_to_name(jacket_item.get('href'), container.opf_name) with open(path, 'rb') as f: container.parsed_cache[name] = f.read() container.commit_item(name) href = container.name_to_href(name, jacket) img.set('src', href) return root
def set_image_settings(cls): from calibre.ebooks.conversion.config import load_defaults, save_defaults recs = load_defaults('comic_input') output_profile = "No output profile found." for profile in output_profiles(): if profile.short_name == cls.output_profile: output_profile = profile if getattr(output_profile, 'colors', 0): # Can also refer to grayscale shades recs['colors'] = getattr(output_profile, 'colors') if getattr(output_profile, 'supports_color', False): recs['dont_grayscale'] = True if getattr(output_profile, 'large_screen', False): recs['keep_aspect_ratio'] = True save_defaults('comic_input', recs)
def update(self, mi): # Collect the original metadata self.get_original_metadata() try: from calibre.ebooks.conversion.config import load_defaults prefs = load_defaults('mobi_output') pas = prefs.get('prefer_author_sort', False) except: pas = False if mi.author_sort and pas: authors = mi.author_sort self.metadata['Authors'] = authors.encode('utf-8') elif mi.authors: authors = '; '.join(mi.authors) self.metadata['Authors'] = authors.encode('utf-8') self.metadata['Title'] = mi.title.encode('utf-8') updated_metadata = self.generate_metadata_stream() # Skip tag_len, tag, extra prefix = len('metadata') + 2 um_buf_len = len(updated_metadata) - prefix head = self.regenerate_headers(um_buf_len) # Chunk1: self.base -> original metadata start # Chunk2: original metadata end -> eof chunk1 = self.data[self.base:self.original_md_start] chunk2 = self.data[prefix + self.original_md_start + self.original_md_len:] self.stream.seek(0) self.stream.truncate(0) # Write the revised stream self.stream.write(head) self.stream.write('d') self.stream.write(chunk1) self.stream.write(updated_metadata) self.stream.write(chunk2)
def update(self,mi): # Collect the original metadata self.get_original_metadata() try: from calibre.ebooks.conversion.config import load_defaults prefs = load_defaults('mobi_output') pas = prefs.get('prefer_author_sort', False) except: pas = False if mi.author_sort and pas: authors = mi.author_sort self.metadata['Authors'] = authors.encode('utf-8') elif mi.authors: authors = '; '.join(mi.authors) self.metadata['Authors'] = authors.encode('utf-8') self.metadata['Title'] = mi.title.encode('utf-8') updated_metadata = self.generate_metadata_stream() # Skip tag_len, tag, extra prefix = len('metadata') + 2 um_buf_len = len(updated_metadata) - prefix head = self.regenerate_headers(um_buf_len) # Chunk1: self.base -> original metadata start # Chunk2: original metadata end -> eof chunk1 = self.data[self.base:self.original_md_start] chunk2 = self.data[prefix + self.original_md_start + self.original_md_len:] self.stream.seek(0) self.stream.truncate(0) # Write the revised stream self.stream.write(head) self.stream.write('d') self.stream.write(chunk1) self.stream.write(updated_metadata) self.stream.write(chunk2)
def options(self): # Save/return the current options # exclude_genre stores literally # Section switches store as True/False # others store as lists opts_dict = {} prefix_rules_processed = False exclusion_rules_processed = False for opt in self.OPTION_FIELDS: c_name, c_def, c_type = opt if c_name == 'exclusion_rules_tw' and exclusion_rules_processed: continue if c_name == 'prefix_rules_tw' and prefix_rules_processed: continue if c_type in ['check_box', 'radio_button']: opt_value = getattr(self, c_name).isChecked() elif c_type in ['combo_box']: opt_value = unicode(getattr(self,c_name).currentText()).strip() elif c_type in ['line_edit']: opt_value = unicode(getattr(self, c_name).text()).strip() elif c_type in ['spin_box']: opt_value = unicode(getattr(self, c_name).value()) elif c_type in ['table_widget']: if c_name == 'prefix_rules_tw': opt_value = self.prefix_rules_table.get_data() prefix_rules_processed = True if c_name == 'exclusion_rules_tw': opt_value = self.exclusion_rules_table.get_data() exclusion_rules_processed = True # Store UI values to gui.json in config dir gprefs.set(self.name + '_' + c_name, opt_value) # Construct opts object for catalog builder if c_name in ['exclusion_rules_tw','prefix_rules_tw']: self.construct_tw_opts_object(c_name, opt_value, opts_dict) else: opts_dict[c_name] = opt_value # Generate specs for merge_comments, header_note_source_field checked = '' if self.merge_before.isChecked(): checked = 'before' elif self.merge_after.isChecked(): checked = 'after' include_hr = self.include_hr.isChecked() opts_dict['merge_comments_rule'] = "%s:%s:%s" % \ (self.merge_source_field_name, checked, include_hr) opts_dict['header_note_source_field'] = self.header_note_source_field_name # Fix up exclude_genre regex if blank. Assume blank = no exclusions if opts_dict['exclude_genre'] == '': opts_dict['exclude_genre'] = 'a^' # Append the output profile try: opts_dict['output_profile'] = [load_defaults('page_setup')['output_profile']] except: opts_dict['output_profile'] = ['default'] if self.DEBUG: print "opts_dict" for opt in sorted(opts_dict.keys(), key=sort_key): print " %s: %s" % (opt, repr(opts_dict[opt])) return opts_dict
def update(self, mi, asin=None): mi.title = normalize(mi.title) def update_exth_record(rec): recs.append(rec) if rec[0] in self.original_exth_records: self.original_exth_records.pop(rec[0]) if self.type != b"BOOKMOBI": raise MobiError( "Setting metadata only supported for MOBI files of type 'BOOK'.\n" "\tThis is a %r file of type %r" % (self.type[0:4], self.type[4:8])) recs = [] added_501 = False try: from calibre.ebooks.conversion.config import load_defaults prefs = load_defaults('mobi_output') pas = prefs.get('prefer_author_sort', False) kindle_pdoc = prefs.get('personal_doc', None) share_not_sync = prefs.get('share_not_sync', False) except: pas = False kindle_pdoc = None share_not_sync = False if mi.author_sort and pas: # We want an EXTH field per author... authors = mi.author_sort.split(' & ') for author in authors: update_exth_record( (100, normalize(author).encode(self.codec, 'replace'))) elif mi.authors: authors = mi.authors for author in authors: update_exth_record( (100, normalize(author).encode(self.codec, 'replace'))) if mi.publisher: update_exth_record( (101, normalize(mi.publisher).encode(self.codec, 'replace'))) if mi.comments: # Strip user annotations a_offset = mi.comments.find('<div class="user_annotations">') ad_offset = mi.comments.find('<hr class="annotations_divider" />') if a_offset >= 0: mi.comments = mi.comments[:a_offset] if ad_offset >= 0: mi.comments = mi.comments[:ad_offset] update_exth_record( (103, normalize(mi.comments).encode(self.codec, 'replace'))) if mi.isbn: update_exth_record((104, mi.isbn.encode(self.codec, 'replace'))) if mi.tags: # FIXME: Keep a single subject per EXTH field? subjects = '; '.join(mi.tags) update_exth_record( (105, normalize(subjects).encode(self.codec, 'replace'))) if kindle_pdoc and kindle_pdoc in mi.tags: added_501 = True update_exth_record((501, b'PDOC')) if mi.pubdate: update_exth_record( (106, unicode_type(mi.pubdate).encode(self.codec, 'replace'))) elif mi.timestamp: update_exth_record( (106, unicode_type(mi.timestamp).encode(self.codec, 'replace'))) elif self.timestamp: update_exth_record((106, self.timestamp)) else: update_exth_record( (106, nowf().isoformat().encode(self.codec, 'replace'))) if self.cover_record is not None: update_exth_record((201, pack('>I', self.cover_rindex))) update_exth_record((203, pack('>I', 0))) if self.thumbnail_record is not None: update_exth_record((202, pack('>I', self.thumbnail_rindex))) # Add a 113 record if not present to allow Amazon syncing if (113 not in self.original_exth_records and self.original_exth_records.get(501, None) == 'EBOK' and not added_501 and not share_not_sync): from uuid import uuid4 update_exth_record((113, unicode_type(uuid4()).encode(self.codec))) if asin is not None: update_exth_record((113, asin.encode(self.codec))) update_exth_record((504, asin.encode(self.codec))) # Add a 112 record with actual UUID if getattr(mi, 'uuid', None): update_exth_record( (112, ("calibre:%s" % mi.uuid).encode(self.codec, 'replace'))) if 503 in self.original_exth_records: update_exth_record((503, mi.title.encode(self.codec, 'replace'))) # Update book producer if getattr(mi, 'book_producer', False): update_exth_record( (108, mi.book_producer.encode(self.codec, 'replace'))) # Set langcode in EXTH header if not mi.is_null('language'): lang = canonicalize_lang(mi.language) lang = lang_as_iso639_1(lang) or lang if lang: update_exth_record((524, lang.encode(self.codec, 'replace'))) # Include remaining original EXTH fields for id in sorted(self.original_exth_records): recs.append((id, self.original_exth_records[id])) recs = sorted(recs, key=lambda x: (x[0], x[0])) exth = io.BytesIO() for code, data in recs: exth.write(pack('>II', code, len(data) + 8)) exth.write(data) exth = exth.getvalue() trail = len(exth) % 4 pad = b'\0' * (4 - trail) # Always pad w/ at least 1 byte exth = [b'EXTH', pack('>II', len(exth) + 12, len(recs)), exth, pad] exth = b''.join(exth) if getattr(self, 'exth', None) is None: raise MobiError('No existing EXTH record. Cannot update metadata.') if not mi.is_null('language'): self.record0[92:96] = iana2mobi(mi.language) self.create_exth(exth=exth, new_title=mi.title) # Fetch updated timestamp, cover_record, thumbnail_record self.fetchEXTHFields() if mi.cover_data[1] or mi.cover: try: data = mi.cover_data[1] if not data: with open(mi.cover, 'rb') as f: data = f.read() except: pass else: if is_image(self.cover_record): size = len(self.cover_record) cover = rescale_image(data, size) if len(cover) <= size: cover += b'\0' * (size - len(cover)) self.cover_record[:] = cover if is_image(self.thumbnail_record): size = len(self.thumbnail_record) thumbnail = rescale_image(data, size, dimen=MAX_THUMB_DIMEN) if len(thumbnail) <= size: thumbnail += b'\0' * (size - len(thumbnail)) self.thumbnail_record[:] = thumbnail return
def set_metadata(self, stream, mi, type_): from calibre_plugins.kfx_output.kfxlib import (set_logger, YJ_Book, YJ_Metadata) from calibre.ebooks import normalize as normalize_unicode from calibre.ebooks.metadata import author_to_author_sort from calibre.utils.config_base import tweaks from calibre.utils.date import (is_date_undefined, isoformat) from calibre.utils.logging import Log from calibre.utils.localization import (canonicalize_lang, lang_as_iso639_1) def mapped_author_to_author_sort(author): if hasattr(mi, "author_sort_map"): author_sort = mi.author_sort_map.get(author) # use mapping if provided if author_sort: return author_sort return author_to_author_sort(author) def normalize(s): if not isinstance(s, type("")): s = s.decode("utf8", "ignore") return normalize_unicode(s) log = set_logger(Log()) filename = stream.name if hasattr(stream, "name") else "stream" log.info("KFX metadata writer activated for %s" % filename) try: from calibre.ebooks.conversion.config import load_defaults prefs = load_defaults('kfx_output') except Exception: prefs = {} log.info("Failed to read default KFX Output preferences") md = YJ_Metadata(author_sort_fn=mapped_author_to_author_sort) md.title = normalize(mi.title) md.authors = [normalize(author) for author in mi.authors] if mi.publisher: md.publisher = normalize(mi.publisher) if mi.pubdate and not is_date_undefined(mi.pubdate): md.issue_date = str(isoformat(mi.pubdate)[:10]) if mi.comments: # Strip user annotations a_offset = mi.comments.find('<div class="user_annotations">') ad_offset = mi.comments.find('<hr class="annotations_divider" />') if a_offset >= 0: mi.comments = mi.comments[:a_offset] if ad_offset >= 0: mi.comments = mi.comments[:ad_offset] md.description = normalize(mi.comments) if not mi.is_null('language'): lang = canonicalize_lang(mi.language) lang = lang_as_iso639_1(lang) or lang if lang: md.language = normalize(lang) if mi.cover_data[1]: md.cover_image_data = mi.cover_data elif mi.cover: md.cover_image_data = ("jpg", open(mi.cover, 'rb').read()) if not tweaks.get("kfx_output_ignore_asin_metadata", False): value = mi.identifiers.get("mobi-asin") if value is not None and re.match(ASIN_RE, value): md.asin = value else: for ident, value in mi.identifiers.items(): if ident.startswith("amazon") and re.match(ASIN_RE, value): md.asin = value break else: value = mi.identifiers.get("asin") if value is not None and re.match(ASIN_RE, value): md.asin = value if md.asin: md.cde_content_type = "EBOK" if prefs.get("approximate_pages", False): page_count = 0 number_of_pages_field = prefs.get("number_of_pages_field", AUTO_PAGES) if number_of_pages_field and number_of_pages_field != AUTO_PAGES: number_of_pages = mi.get(number_of_pages_field, "") try: page_count = int(number_of_pages) except Exception: pass else: page_count = -1 book = YJ_Book(stream, log) book.decode_book(set_metadata=md, set_approximate_pages=page_count) new_data = book.convert_to_single_kfx() set_logger() stream.seek(0) stream.truncate() stream.write(new_data) stream.seek(0)
def set_output_profile(cls): if cls.output_profile: from calibre.ebooks.conversion.config import load_defaults, save_defaults recs = load_defaults('page_setup') recs['output_profile'] = cls.output_profile save_defaults('page_setup', recs)
def create_epub_cover(container, cover_path, existing_image, options=None): from calibre.ebooks.conversion.config import load_defaults from calibre.ebooks.oeb.transforms.cover import CoverManager try: ext = cover_path.rpartition(".")[-1].lower() except Exception: ext = "jpeg" cname, tname = "cover." + ext, "titlepage.xhtml" recommended_folders = get_recommended_folders(container, (cname, tname)) if existing_image: raster_cover = existing_image manifest_id = {v: k for k, v in container.manifest_id_map.iteritems()}[existing_image] raster_cover_item = container.opf_xpath('//opf:manifest/*[@id="%s"]' % manifest_id)[0] else: folder = recommended_folders[cname] if folder: cname = folder + "/" + cname raster_cover_item = container.generate_item(cname, id_prefix="cover") raster_cover = container.href_to_name(raster_cover_item.get("href"), container.opf_name) 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) if options is None: opts = load_defaults("epub_output") keep_aspect = opts.get("preserve_cover_aspect_ratio", False) no_svg = opts.get("no_svg_cover", False) else: keep_aspect = options.get("keep_aspect", False) no_svg = options.get("no_svg", False) if no_svg: style = 'style="height: 100%%"' templ = CoverManager.NONSVG_TEMPLATE.replace("__style__", style) else: if callable(cover_path): templ = (options or {}).get("template", CoverManager.SVG_TEMPLATE) else: width, height = 600, 800 try: if existing_image: width, height = identify(container.raw_data(existing_image, decode=False))[1:] else: with lopen(cover_path, "rb") as csrc: width, height = identify(csrc)[1:] except: container.log.exception("Failed to get width and height of cover") ar = "xMidYMid meet" if keep_aspect else "none" templ = CoverManager.SVG_TEMPLATE.replace("__ar__", ar) templ = templ.replace("__viewbox__", "0 0 %d %d" % (width, height)) templ = templ.replace("__width__", str(width)) templ = templ.replace("__height__", str(height)) folder = recommended_folders[tname] if folder: tname = folder + "/" + tname titlepage_item = container.generate_item(tname, id_prefix="titlepage") titlepage = container.href_to_name(titlepage_item.get("href"), container.opf_name) raw = templ % container.name_to_href(raster_cover, titlepage).encode("utf-8") with container.open(titlepage, "wb") as f: f.write(raw) # We have to make sure the raster cover item has id="cover" for the moron # that wrote the Nook firmware if raster_cover_item.get("id") != "cover": from calibre.ebooks.oeb.base import uuid_id newid = uuid_id() for item in container.opf_xpath('//*[@id="cover"]'): item.set("id", newid) for item in container.opf_xpath('//*[@idref="cover"]'): item.set("idref", newid) raster_cover_item.set("id", "cover") spine = container.opf_xpath("//opf:spine")[0] ref = spine.makeelement(OPF("itemref"), idref=titlepage_item.get("id")) container.insert_into_xml(spine, ref, index=0) guide = container.opf_get_or_create("guide") container.insert_into_xml( guide, guide.makeelement( OPF("reference"), type="cover", title=_("Cover"), href=container.name_to_href(titlepage, base=container.opf_name), ), ) metadata = container.opf_get_or_create("metadata") meta = metadata.makeelement(OPF("meta"), name="cover") meta.set("content", raster_cover_item.get("id")) container.insert_into_xml(metadata, meta) return raster_cover, titlepage
def create_epub_cover(container, cover_path, existing_image, options=None): from calibre.ebooks.conversion.config import load_defaults from calibre.ebooks.oeb.transforms.cover import CoverManager try: ext = cover_path.rpartition('.')[-1].lower() except Exception: ext = 'jpeg' cname, tname = 'cover.' + ext, 'titlepage.xhtml' recommended_folders = get_recommended_folders(container, (cname, tname)) if existing_image: raster_cover = existing_image manifest_id = {v:k for k, v in container.manifest_id_map.iteritems()}[existing_image] raster_cover_item = container.opf_xpath('//opf:manifest/*[@id="%s"]' % manifest_id)[0] else: folder = recommended_folders[cname] if folder: cname = folder + '/' + cname raster_cover_item = container.generate_item(cname, id_prefix='cover') raster_cover = container.href_to_name(raster_cover_item.get('href'), container.opf_name) 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) if options is None: opts = load_defaults('epub_output') keep_aspect = opts.get('preserve_cover_aspect_ratio', False) no_svg = opts.get('no_svg_cover', False) else: keep_aspect = options.get('keep_aspect', False) no_svg = options.get('no_svg', False) if no_svg: style = 'style="height: 100%%"' templ = CoverManager.NONSVG_TEMPLATE.replace('__style__', style) has_svg = False else: if callable(cover_path): templ = (options or {}).get('template', CoverManager.SVG_TEMPLATE) has_svg = 'xlink:href' in templ else: width, height = 600, 800 has_svg = True try: if existing_image: width, height = identify(container.raw_data(existing_image, decode=False))[1:] else: with lopen(cover_path, 'rb') as csrc: width, height = identify(csrc)[1:] except: container.log.exception("Failed to get width and height of cover") ar = 'xMidYMid meet' if keep_aspect else 'none' templ = CoverManager.SVG_TEMPLATE.replace('__ar__', ar) templ = templ.replace('__viewbox__', '0 0 %d %d'%(width, height)) templ = templ.replace('__width__', str(width)) templ = templ.replace('__height__', str(height)) folder = recommended_folders[tname] if folder: tname = folder + '/' + tname titlepage_item = container.generate_item(tname, id_prefix='titlepage') titlepage = container.href_to_name(titlepage_item.get('href'), container.opf_name) raw = templ%container.name_to_href(raster_cover, titlepage).encode('utf-8') with container.open(titlepage, 'wb') as f: f.write(raw) # We have to make sure the raster cover item has id="cover" for the moron # that wrote the Nook firmware if raster_cover_item.get('id') != 'cover': from calibre.ebooks.oeb.base import uuid_id newid = uuid_id() for item in container.opf_xpath('//*[@id="cover"]'): item.set('id', newid) for item in container.opf_xpath('//*[@idref="cover"]'): item.set('idref', newid) raster_cover_item.set('id', 'cover') spine = container.opf_xpath('//opf:spine')[0] ref = spine.makeelement(OPF('itemref'), idref=titlepage_item.get('id')) container.insert_into_xml(spine, ref, index=0) ver = container.opf_version_parsed if ver.major < 3: guide = container.opf_get_or_create('guide') container.insert_into_xml(guide, guide.makeelement( OPF('reference'), type='cover', title=_('Cover'), href=container.name_to_href(titlepage, base=container.opf_name))) metadata = container.opf_get_or_create('metadata') meta = metadata.makeelement(OPF('meta'), name='cover') meta.set('content', raster_cover_item.get('id')) container.insert_into_xml(metadata, meta) else: container.apply_unique_properties(raster_cover, 'cover-image') container.apply_unique_properties(titlepage, 'calibre:title-page') if has_svg: container.add_properties(titlepage, 'svg') return raster_cover, titlepage
def render_jacket(mi): ps = load_defaults('page_setup') op = ps.get('output_profile', 'default') opmap = {x.short_name:x for x in output_profiles()} output_profile = opmap.get(op, opmap['default']) return render(mi, output_profile)
def create_epub_cover(container, cover_path, existing_image, options=None): from calibre.ebooks.conversion.config import load_defaults from calibre.ebooks.oeb.transforms.cover import CoverManager try: ext = cover_path.rpartition('.')[-1].lower() except Exception: ext = 'jpeg' cname, tname = 'cover.' + ext, 'titlepage.xhtml' recommended_folders = get_recommended_folders(container, (cname, tname)) if existing_image: raster_cover = existing_image manifest_id = {v: k for k, v in iteritems(container.manifest_id_map) }[existing_image] raster_cover_item = container.opf_xpath('//opf:manifest/*[@id="%s"]' % manifest_id)[0] else: folder = recommended_folders[cname] if folder: cname = folder + '/' + cname raster_cover_item = container.generate_item(cname, id_prefix='cover') raster_cover = container.href_to_name(raster_cover_item.get('href'), container.opf_name) 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) if options is None: opts = load_defaults('epub_output') keep_aspect = opts.get('preserve_cover_aspect_ratio', False) no_svg = opts.get('no_svg_cover', False) else: keep_aspect = options.get('keep_aspect', False) no_svg = options.get('no_svg', False) if no_svg: style = 'style="height: 100%%"' templ = CoverManager.NONSVG_TEMPLATE.replace('__style__', style) has_svg = False else: if callable(cover_path): templ = (options or {}).get('template', CoverManager.SVG_TEMPLATE) has_svg = 'xlink:href' in templ else: width, height = 600, 800 has_svg = True try: if existing_image: width, height = identify( container.raw_data(existing_image, decode=False))[1:] else: with lopen(cover_path, 'rb') as csrc: width, height = identify(csrc)[1:] except: container.log.exception( "Failed to get width and height of cover") ar = 'xMidYMid meet' if keep_aspect else 'none' templ = CoverManager.SVG_TEMPLATE.replace('__ar__', ar) templ = templ.replace('__viewbox__', '0 0 %d %d' % (width, height)) templ = templ.replace('__width__', str(width)) templ = templ.replace('__height__', str(height)) folder = recommended_folders[tname] if folder: tname = folder + '/' + tname titlepage_item = container.generate_item(tname, id_prefix='titlepage') titlepage = container.href_to_name(titlepage_item.get('href'), container.opf_name) raw = templ % container.name_to_href(raster_cover, titlepage).encode('utf-8') with container.open(titlepage, 'wb') as f: f.write(raw) # We have to make sure the raster cover item has id="cover" for the moron # that wrote the Nook firmware if raster_cover_item.get('id') != 'cover': from calibre.ebooks.oeb.base import uuid_id newid = uuid_id() for item in container.opf_xpath('//*[@id="cover"]'): item.set('id', newid) for item in container.opf_xpath('//*[@idref="cover"]'): item.set('idref', newid) raster_cover_item.set('id', 'cover') spine = container.opf_xpath('//opf:spine')[0] ref = spine.makeelement(OPF('itemref'), idref=titlepage_item.get('id')) container.insert_into_xml(spine, ref, index=0) ver = container.opf_version_parsed if ver.major < 3: guide = container.opf_get_or_create('guide') container.insert_into_xml( guide, guide.makeelement(OPF('reference'), type='cover', title=_('Cover'), href=container.name_to_href( titlepage, base=container.opf_name))) metadata = container.opf_get_or_create('metadata') meta = metadata.makeelement(OPF('meta'), name='cover') meta.set('content', raster_cover_item.get('id')) container.insert_into_xml(metadata, meta) else: container.apply_unique_properties(raster_cover, 'cover-image') container.apply_unique_properties(titlepage, 'calibre:title-page') if has_svg: container.add_properties(titlepage, 'svg') return raster_cover, titlepage
def render_jacket(mi): ps = load_defaults('page_setup') op = ps.get('output_profile', 'default') opmap = {x.short_name: x for x in output_profiles()} output_profile = opmap.get(op, opmap['default']) return render(mi, output_profile)
def update(self, mi): mi.title = normalize(mi.title) def update_exth_record(rec): recs.append(rec) if rec[0] in self.original_exth_records: self.original_exth_records.pop(rec[0]) if self.type != "BOOKMOBI": raise MobiError("Setting metadata only supported for MOBI files of type 'BOOK'.\n" "\tThis is a %r file of type %r" % (self.type[0:4], self.type[4:8])) recs = [] added_501 = False try: from calibre.ebooks.conversion.config import load_defaults prefs = load_defaults('mobi_output') pas = prefs.get('prefer_author_sort', False) kindle_pdoc = prefs.get('personal_doc', None) share_not_sync = prefs.get('share_not_sync', False) except: pas = False kindle_pdoc = None share_not_sync = False if mi.author_sort and pas: # We want an EXTH field per author... authors = mi.author_sort.split(' & ') for author in authors: update_exth_record((100, normalize(author).encode(self.codec, 'replace'))) elif mi.authors: authors = mi.authors for author in authors: update_exth_record((100, normalize(author).encode(self.codec, 'replace'))) if mi.publisher: update_exth_record((101, normalize(mi.publisher).encode(self.codec, 'replace'))) if mi.comments: # Strip user annotations a_offset = mi.comments.find('<div class="user_annotations">') ad_offset = mi.comments.find('<hr class="annotations_divider" />') if a_offset >= 0: mi.comments = mi.comments[:a_offset] if ad_offset >= 0: mi.comments = mi.comments[:ad_offset] update_exth_record((103, normalize(mi.comments).encode(self.codec, 'replace'))) if mi.isbn: update_exth_record((104, mi.isbn.encode(self.codec, 'replace'))) if mi.tags: # FIXME: Keep a single subject per EXTH field? subjects = '; '.join(mi.tags) update_exth_record((105, normalize(subjects).encode(self.codec, 'replace'))) if kindle_pdoc and kindle_pdoc in mi.tags: added_501 = True update_exth_record((501, b'PDOC')) if mi.pubdate: update_exth_record((106, str(mi.pubdate).encode(self.codec, 'replace'))) elif mi.timestamp: update_exth_record((106, str(mi.timestamp).encode(self.codec, 'replace'))) elif self.timestamp: update_exth_record((106, self.timestamp)) else: update_exth_record((106, nowf().isoformat().encode(self.codec, 'replace'))) if self.cover_record is not None: update_exth_record((201, pack('>I', self.cover_rindex))) update_exth_record((203, pack('>I', 0))) if self.thumbnail_record is not None: update_exth_record((202, pack('>I', self.thumbnail_rindex))) # Add a 113 record if not present to allow Amazon syncing if (113 not in self.original_exth_records and self.original_exth_records.get(501, None) == 'EBOK' and not added_501 and not share_not_sync): from uuid import uuid4 update_exth_record((113, str(uuid4()))) # Add a 112 record with actual UUID if getattr(mi, 'uuid', None): update_exth_record((112, (u"calibre:%s" % mi.uuid).encode(self.codec, 'replace'))) if 503 in self.original_exth_records: update_exth_record((503, mi.title.encode(self.codec, 'replace'))) # Update book producer if getattr(mi, 'book_producer', False): update_exth_record((108, mi.book_producer.encode(self.codec, 'replace'))) # Set langcode in EXTH header if not mi.is_null('language'): lang = canonicalize_lang(mi.language) lang = lang_as_iso639_1(lang) or lang if lang: update_exth_record((524, lang.encode(self.codec, 'replace'))) # Include remaining original EXTH fields for id in sorted(self.original_exth_records): recs.append((id, self.original_exth_records[id])) recs = sorted(recs, key=lambda x:(x[0],x[0])) exth = StringIO() for code, data in recs: exth.write(pack('>II', code, len(data) + 8)) exth.write(data) exth = exth.getvalue() trail = len(exth) % 4 pad = '\0' * (4 - trail) # Always pad w/ at least 1 byte exth = ['EXTH', pack('>II', len(exth) + 12, len(recs)), exth, pad] exth = ''.join(exth) if getattr(self, 'exth', None) is None: raise MobiError('No existing EXTH record. Cannot update metadata.') if not mi.is_null('language'): self.record0[92:96] = iana2mobi(mi.language) self.create_exth(exth=exth, new_title=mi.title) # Fetch updated timestamp, cover_record, thumbnail_record self.fetchEXTHFields() if mi.cover_data[1] or mi.cover: try: data = mi.cover_data[1] if mi.cover_data[1] else open(mi.cover, 'rb').read() except: pass else: if is_image(self.cover_record): size = len(self.cover_record) cover = rescale_image(data, size) if len(cover) <= size: cover += b'\0' * (size - len(cover)) self.cover_record[:] = cover if is_image(self.thumbnail_record): size = len(self.thumbnail_record) thumbnail = rescale_image(data, size, dimen=MAX_THUMB_DIMEN) if len(thumbnail) <= size: thumbnail += b'\0' * (size - len(thumbnail)) self.thumbnail_record[:] = thumbnail return
def create_epub_cover(container, cover_path): from calibre.ebooks.conversion.config import load_defaults from calibre.ebooks.oeb.transforms.cover import CoverManager ext = cover_path.rpartition(".")[-1].lower() raster_cover_item = container.generate_item("cover." + ext, id_prefix="cover") raster_cover = container.href_to_name(raster_cover_item.get("href"), container.opf_name) with open(cover_path, "rb") as src, container.open(raster_cover, "wb") as dest: shutil.copyfileobj(src, dest) opts = load_defaults("epub_output") keep_aspect = opts.get("preserve_cover_aspect_ratio", False) no_svg = opts.get("no_svg_cover", False) if no_svg: style = 'style="height: 100%%"' templ = CoverManager.NONSVG_TEMPLATE.replace("__style__", style) else: width, height = 600, 800 try: width, height = identify(cover_path)[:2] except: container.log.exception("Failed to get width and height of cover") ar = "xMidYMid meet" if keep_aspect else "none" templ = CoverManager.SVG_TEMPLATE.replace("__ar__", ar) templ = templ.replace("__viewbox__", "0 0 %d %d" % (width, height)) templ = templ.replace("__width__", str(width)) templ = templ.replace("__height__", str(height)) titlepage_item = container.generate_item("titlepage.xhtml", id_prefix="titlepage") titlepage = container.href_to_name(titlepage_item.get("href"), container.opf_name) raw = templ % container.name_to_href(raster_cover).encode("utf-8") with container.open(titlepage, "wb") as f: f.write(raw) # We have to make sure the raster cover item has id="cover" for the moron # that wrote the Nook firmware if raster_cover_item.get("id") != "cover": from calibre.ebooks.oeb.base import uuid_id newid = uuid_id() for item in container.opf_xpath('//*[@id="cover"]'): item.set("id", newid) for item in container.opf_xpath('//*[@idref="cover"]'): item.set("idref", newid) raster_cover_item.set("id", "cover") spine = container.opf_xpath("//opf:spine")[0] ref = spine.makeelement(OPF("itemref"), idref=titlepage_item.get("id")) container.insert_into_xml(spine, ref, index=0) guide = container.opf_get_or_create("guide") container.insert_into_xml( guide, guide.makeelement( OPF("reference"), type="cover", title=_("Cover"), href=container.name_to_href(titlepage, base=container.opf_name), ), ) metadata = container.opf_get_or_create("metadata") meta = metadata.makeelement(OPF("meta"), name="cover") meta.set("content", raster_cover_item.get("id")) container.insert_into_xml(metadata, meta) return raster_cover, titlepage