def _data_to_image(data): if isinstance(data, Image): img = data else: img = Image() img.load(data) return img
def convert_image(url, data, sizes, grayscale, removetrans, imgtype="jpg", background="#ffffff"): export = False img = Image.open(StringIO(data)) owidth, oheight = img.size nwidth, nheight = sizes scaled, nwidth, nheight = fit_image(owidth, oheight, nwidth, nheight) if scaled: img = img.resize((nwidth, nheight), Image.ANTIALIAS) export = True if normalize_format_name(img.format) != imgtype: if img.mode == "P": # convert pallete gifs to RGB so jpg save doesn't fail. img = img.convert("RGB") export = True if removetrans and img.mode == "RGBA": background = Image.new("RGBA", img.size, background) # Paste the image on top of the background background.paste(img, img) img = background.convert("RGB") export = True if grayscale and img.mode != "L": img = img.convert("L") export = True if export: outsio = StringIO() img.save(outsio, convtype[imgtype]) return (outsio.getvalue(), imgtype, imagetypes[imgtype]) else: logger.debug("image used unchanged") return (data, imgtype, imagetypes[imgtype])
def create_cover(report, icons=(), cols=5, size=60, padding=8): icons = icons or tuple(default_cover_icons(cols)) rows = int(math.ceil(len(icons) / cols)) canvas = create_canvas(cols * (size + padding), rows * (size + padding), '#eeeeee') y = -size - padding // 2 x = 0 for i, icon in enumerate(icons): if i % cols == 0: y += padding + size x = padding // 2 else: x += size + padding if report and icon in report.name_map: ipath = os.path.join(report.path, report.name_map[icon]) else: ipath = I(icon, allow_user_override=False) img = Image() with open(ipath, 'rb') as f: img.load(f.read()) scaled, nwidth, nheight = fit_image(img.size[0], img.size[1], size, size) img.size = nwidth, nheight dx = (size - nwidth) // 2 canvas.compose(img, x + dx, y) return canvas.export('JPEG')
def get_picture_size(self): from calibre.utils.magick import Image self.make_temp_cbz_file() zf = ZipFile(self.file) files = zf.namelist() size_x, size_y = 0, 0 index = 1 while index < 10 and index < len(files): fname = files[index] if fname.lower().rpartition('.')[-1] in IMG_EXTENSIONS: with zf.open(fname) as ffile: img = Image() try: img.open(ffile) size_x, size_y = img.size except: pass if size_x < size_y: break index += 1 zf.close() size = round(size_x * size_y / 1000000, 2) return size
def browse_icon(self, name="blank.png"): cherrypy.response.headers["Content-Type"] = "image/png" cherrypy.response.headers["Last-Modified"] = self.last_modified(self.build_time) if not hasattr(self, "__browse_icon_cache__"): self.__browse_icon_cache__ = {} if name not in self.__browse_icon_cache__: if name.startswith("_"): name = sanitize_file_name2(name[1:]) try: with open(os.path.join(config_dir, "tb_icons", name), "rb") as f: data = f.read() except: raise cherrypy.HTTPError(404, "no icon named: %r" % name) else: try: data = I(name, data=True) except: raise cherrypy.HTTPError(404, "no icon named: %r" % name) img = Image() img.load(data) width, height = img.size scaled, width, height = fit_image(width, height, 48, 48) if scaled: img.size = (width, height) self.__browse_icon_cache__[name] = img.export("png") return self.__browse_icon_cache__[name]
def browse_icon(self, name='blank.png'): cherrypy.response.headers['Content-Type'] = 'image/png' cherrypy.response.headers['Last-Modified'] = self.last_modified(self.build_time) if not hasattr(self, '__browse_icon_cache__'): self.__browse_icon_cache__ = {} if name not in self.__browse_icon_cache__: if name.startswith('_'): name = sanitize_file_name2(name[1:]) try: with open(os.path.join(config_dir, 'tb_icons', name), 'rb') as f: data = f.read() except: raise cherrypy.HTTPError(404, 'no icon named: %r'%name) else: try: data = I(name, data=True) except: raise cherrypy.HTTPError(404, 'no icon named: %r'%name) img = Image() img.load(data) width, height = img.size scaled, width, height = fit_image(width, height, 48, 48) if scaled: img.size = (width, height) self.__browse_icon_cache__[name] = img.export('png') return self.__browse_icon_cache__[name]
def add_borders_to_image(img_data, left=0, top=0, right=0, bottom=0, border_color="#ffffff", fmt="jpg"): img = Image() img.load(img_data) lwidth, lheight = img.size canvas = create_canvas(lwidth + left + right, lheight + top + bottom, border_color) canvas.compose(img, left, top) return canvas.export(fmt)
def thumbnail(data, width=120, height=120, bgcolor='#ffffff', fmt='jpg', preserve_aspect_ratio=True, compression_quality=70): img = Image() img.load(data) owidth, oheight = img.size if width is None: width = owidth if height is None: height = oheight if not preserve_aspect_ratio: scaled = owidth > width or oheight > height nwidth = width nheight = height else: scaled, nwidth, nheight = fit_image(owidth, oheight, width, height) if scaled: img.size = (nwidth, nheight) canvas = create_canvas(img.size[0], img.size[1], bgcolor) canvas.compose(img) data = image_to_data(canvas.img, compression_quality=compression_quality) return (canvas.size[0], canvas.size[1], data)
def identify_data(data): ''' Identify the image in data. Returns a 3-tuple (width, height, format) or raises an Exception if data is not an image. ''' img = Image() return img.identify(data)
def flip_image(img, flip): from calibre.utils.magick import Image im = Image() im.open(img) if b'x' in flip: im.flip(True) if b'y' in flip: im.flip() im.save(img)
def add_borders_to_image(img_data, left=0, top=0, right=0, bottom=0, border_color='#ffffff', fmt='jpg'): img = Image() img.load(img_data) lwidth, lheight = img.size canvas = create_canvas(lwidth+left+right, lheight+top+bottom, border_color) canvas.compose(img, left, top) return canvas.export(fmt)
def resize_image(self, raw, base, max_width, max_height): img = Image() img.load(raw) resized, nwidth, nheight = fit_image(img.size[0], img.size[1], max_width, max_height) if resized: img.size = (nwidth, nheight) base, ext = os.path.splitext(base) base = base + '-%dx%d%s' % (max_width, max_height, ext) raw = img.export(ext[1:]) return raw, base, resized
def check_raster_images(name, mt, raw): errors = [] i = Image() try: i.load(raw) except Exception as e: errors.append(InvalidImage(as_unicode(e.message), name)) else: if i.colorspace == 'CMYKColorspace': errors.append(CMYKImage(_('Image is in the CMYK colorspace'), name)) return errors
def qimage_to_magick(img): ans = Image() fmt = get_pixel_map() if not img.hasAlphaChannel(): if img.format() != img.Format_RGB32: img = img.convertToFormat(QImage.Format_RGB32) fmt = fmt.replace('A', 'P') else: if img.format() != img.Format_ARGB32: img = img.convertToFormat(QImage.Format_ARGB32) raw = img.constBits().ascapsule() ans.constitute(img.width(), img.height(), fmt, raw) return ans
def identify_data(data): ''' Identify the image in data. Returns a 3-tuple (width, height, format) or raises an Exception if data is not an image. ''' if data.startswith(b'<?xml'): # ImageMagick segfaults when trying to identify SVG images raise ValueError('Identifying svg images is not supported') img = Image() img.identify(data) width, height = img.size fmt = img.format return (width, height, fmt)
def identify_data(data): """ Identify the image in data. Returns a 3-tuple (width, height, format) or raises an Exception if data is not an image. """ if data.startswith(b"<?xml"): # ImageMagick segfaults when trying to identify SVG images raise ValueError("Identifying svg images is not supported") img = Image() img.identify(data) width, height = img.size fmt = img.format return (width, height, fmt)
def identify_data(data): ''' Identify the image in data. Returns a 3-tuple (width, height, format) or raises an Exception if data is not an image. ''' img = Image() if hasattr(img, 'identify'): img.identify(data) else: img.load(data) width, height = img.size fmt = img.format return (width, height, fmt)
def encode_thumbnail(thumbnail): ''' Encode the image part of a thumbnail, then return the 3 part tuple ''' from calibre.utils.magick import Image if thumbnail is None: return None if not isinstance(thumbnail, (tuple, list)): try: img = Image() img.load(thumbnail) width, height = img.size thumbnail = (width, height, thumbnail) except: return None return (thumbnail[0], thumbnail[1], b64encode(str(thumbnail[2])))
def identify_data(data): ''' Identify the image in data. Returns a 3-tuple (width, height, format) or raises an Exception if data is not an image. ''' if data.startswith(b'<?xml'): # ImageMagick segfaults when trying to identify SVG images raise ValueError('Identifying svg images is not supported') img = Image() if hasattr(img, 'identify'): img.identify(data) else: img.load(data) width, height = img.size fmt = img.format return (width, height, fmt)
def create_cover_page( top_lines, logo_path, width=590, height=750, bgcolor="#ffffff", output_format="jpg", texture_data=None, texture_opacity=1.0, ): """ Create the standard calibre cover page and return it as a byte string in the specified output_format. """ canvas = create_canvas(width, height, bgcolor) if texture_data and hasattr(canvas, "texture"): texture = Image() texture.load(texture_data) texture.set_opacity(texture_opacity) canvas.texture(texture) bottom = 10 for line in top_lines: twand = create_text_wand(line.font_size, font_path=line.font_path) bottom = draw_centered_text(canvas, twand, line.text, bottom) bottom += line.bottom_margin bottom -= top_lines[-1].bottom_margin foot_font = P("fonts/liberation/LiberationMono-Regular.ttf") vanity = create_text_arc(__appname__ + " " + __version__, 24, font=foot_font, bgcolor="#00000000") lwidth, lheight = vanity.size left = int(max(0, (width - lwidth) / 2.0)) top = height - lheight - 10 canvas.compose(vanity, left, top) available = (width, int(top - bottom) - 20) if available[1] > 40: logo = Image() logo.open(logo_path) lwidth, lheight = logo.size scaled, lwidth, lheight = fit_image(lwidth, lheight, *available) if scaled: logo.size = (lwidth, lheight) left = int(max(0, (width - lwidth) / 2.0)) top = bottom + 10 extra = int((available[1] - lheight) / 2.0) if extra > 0: top += extra canvas.compose(logo, left, top) return canvas.export(output_format)
def fb2mlize_images(self): ''' This function uses the self.image_hrefs dictionary mapping. It is populated by the dump_text function. ''' from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES images = [] for item in self.oeb_book.manifest: # Don't write the image if it's not referenced in the document's text. if item.href not in self.image_hrefs: continue if item.media_type in OEB_RASTER_IMAGES: try: if item.media_type != 'image/jpeg': im = Image() im.load(item.data) im.set_compression_quality(70) imdata = im.export('jpg') raw_data = b64encode(imdata) else: raw_data = b64encode(item.data) # Don't put the encoded image on a single line. data = '' col = 1 for char in raw_data: if col == 72: data += '\n' col = 1 col += 1 data += char images.append('<binary id="%s" content-type="image/jpeg">%s\n</binary>' % (self.image_hrefs[item.href], data)) except Exception as e: self.log.error('Error: Could not include file %s because ' '%s.' % (item.href, e)) return ''.join(images)
def extract_raster_image(wmf_data): try: wmf, wmf_err = plugins['wmf'] except KeyError: raise Unavailable('libwmf not available on this platform') if wmf_err: raise Unavailable(wmf_err) if iswindows: import sys, os appdir = sys.app_dir if isinstance(appdir, unicode): appdir = appdir.encode(filesystem_encoding) fdir = os.path.join(appdir, 'wmffonts') wmf.set_font_dir(fdir) data = '' with TemporaryDirectory('wmf2png') as tdir: with CurrentDir(tdir): wmf.render(wmf_data) images = list(sorted(glob.glob('*.png'))) if not images: raise NoRaster('No raster images in WMF') data = open(images[0], 'rb').read() im = Image() im.load(data) pw = PixelWand() pw.color = '#ffffff' im.rotate(pw, 180) return im.export('png')
def thumbnail(data, width=120, height=120, bgcolor='#ffffff', fmt='jpg', preserve_aspect_ratio=True, compression_quality=70): img = Image() img.load(data) owidth, oheight = img.size if not preserve_aspect_ratio: scaled = owidth > width or oheight > height nwidth = width nheight = height else: scaled, nwidth, nheight = fit_image(owidth, oheight, width, height) if scaled: img.size = (nwidth, nheight) canvas = create_canvas(img.size[0], img.size[1], bgcolor) canvas.compose(img) if fmt == 'jpg': canvas.set_compression_quality(compression_quality) return (canvas.size[0], canvas.size[1], canvas.export(fmt))
def to_png(bmp): # ImageMagick does not convert some bmp files correctly, while Qt does, # so try Qt first. See for instance: # https://bugs.launchpad.net/calibre/+bug/934167 # ImageMagick bug report: # http://www.imagemagick.org/discourse-server/viewtopic.php?f=3&t=20350 from PyQt5.Qt import QImage, QByteArray, QBuffer i = QImage() if i.loadFromData(bmp): ba = QByteArray() buf = QBuffer(ba) buf.open(QBuffer.WriteOnly) i.save(buf, 'png') return bytes(ba.data()) from calibre.utils.magick import Image img = Image() img.load(bmp) return img.export('png')
def to_png(bmp): # ImageMagick does not convert some bmp files correctly, while Qt does, # so try Qt first. See for instance: # https://bugs.launchpad.net/calibre/+bug/934167 # ImageMagick bug report: # http://www.imagemagick.org/discourse-server/viewtopic.php?f=3&t=20350 from PyQt4.Qt import QImage, QByteArray, QBuffer i = QImage() if i.loadFromData(bmp): ba = QByteArray() buf = QBuffer(ba) buf.open(QBuffer.WriteOnly) i.save(buf, 'png') return bytes(ba.data()) from calibre.utils.magick import Image img = Image() img.load(bmp) return img.export('png')
def convert_image(url, data, sizes, grayscale, removetrans, imgtype="jpg", background='#ffffff'): export = False img = Image.open(StringIO(data)) owidth, oheight = img.size nwidth, nheight = sizes scaled, nwidth, nheight = fit_image(owidth, oheight, nwidth, nheight) if scaled: img = img.resize((nwidth, nheight), Image.ANTIALIAS) export = True if normalize_format_name(img.format) != imgtype: if img.mode == "P": # convert pallete gifs to RGB so jpg save doesn't fail. img = img.convert("RGB") export = True if removetrans and img.mode == "RGBA": background = Image.new('RGBA', img.size, background) # Paste the image on top of the background background.paste(img, img) img = background.convert('RGB') export = True if grayscale and img.mode != "L": img = img.convert("L") export = True if export: outsio = StringIO() img.save(outsio, convtype[imgtype]) return (outsio.getvalue(), imgtype, imagetypes[imgtype]) else: logger.debug("image used unchanged") return (data, imgtype, imagetypes[imgtype])
def render(self): from calibre.utils.magick import Image img = Image() img.open(self.path_to_page) width, height = img.size if self.num == 0: # First image so create a thumbnail from it thumb = img.clone thumb.thumbnail(60, 80) thumb.save(os.path.join(self.dest, 'thumbnail.png')) self.pages = [img] if width > height: if self.opts.landscape: self.rotate = True else: split1, split2 = img.clone, img.clone half = int(width/2) split1.crop(half-1, height, 0, 0) split2.crop(half-1, height, half, 0) self.pages = [split2, split1] if self.opts.right2left else [split1, split2] self.process_pages()
def scramble_img(self, name, scramble_dgts=False): if self.eb.mime_map[name] in OEB_RASTER_IMAGES: data = self.eb.parsed(name) oldimg = Image() oldpath = self.eb.name_to_abspath(name) try: oldimg.load(data) wid, hgt = oldimg.size except: wid, hgt = (50, 50) try: fmt = oldimg.format except: x, x, fmt = get_nameparts(name) newimg = Image() newimg.load(self.dummyimg) newimg.size = (wid, hgt) self.eb.replace(name, newimg.export(fmt.upper()))
def browse_icon(self, name='blank.png'): cherrypy.response.headers['Content-Type'] = 'image/png' cherrypy.response.headers['Last-Modified'] = self.last_modified(self.build_time) if not hasattr(self, '__browse_icon_cache__'): self.__browse_icon_cache__ = {} if name not in self.__browse_icon_cache__: try: data = I(name, data=True) except: raise cherrypy.HTTPError(404, 'no icon named: %r'%name) img = Image() img.load(data) width, height = img.size scaled, width, height = fit_image(width, height, 48, 48) if scaled: img.size = (width, height) self.__browse_icon_cache__[name] = img.export('png') return self.__browse_icon_cache__[name]
def trim_cover(self, *args): from calibre.utils.magick import Image cdata = self.current_val if not cdata: return im = Image() im.load(cdata) im.trim(10) cdata = im.export('png') self.current_val = cdata
def browse_icon(self, name='blank.png'): cherrypy.response.headers['Content-Type'] = 'image/png' cherrypy.response.headers['Last-Modified'] = self.last_modified( self.build_time) if not hasattr(self, '__browse_icon_cache__'): self.__browse_icon_cache__ = {} if name not in self.__browse_icon_cache__: if name.startswith('_'): name = sanitize_file_name2(name[1:]) try: with open(os.path.join(config_dir, 'tb_icons', name), 'rb') as f: data = f.read() except: raise cherrypy.HTTPError(404, 'no icon named: %r' % name) else: try: data = I(name, data=True) except: raise cherrypy.HTTPError(404, 'no icon named: %r' % name) img = Image() img.load(data) width, height = img.size scaled, width, height = fit_image(width, height, 48, 48) if scaled: img.size = (width, height) self.__browse_icon_cache__[name] = img.export('png') return self.__browse_icon_cache__[name]
def qimage_to_magick(img): ans = Image() if isosx: # For some reson, on OSX MagickConstituteImage fails, and I can't be # bothered figuring out why. Dumping to uncompressed PNG is reasonably # fast. raw = pixmap_to_data(img, 'PNG', quality=100) ans.load(raw) return ans fmt = get_pixel_map() if not img.hasAlphaChannel(): if img.format() != img.Format_RGB32: img = QImage(img) img.setFormat(QImage.Format_RGB32) fmt = fmt.replace('A', 'P') else: if img.format() != img.Format_ARGB32: img = QImage(img) img.setFormat(img.Format_ARGB32) raw = img.constBits().ascapsule() ans.constitute(img.width(), img.height(), fmt, raw) return ans
def postprocess_html(self, soup, first): #process all the images for tag in soup.findAll(lambda tag: tag.name.lower()=='img' and tag.has_key('src')): iurl = tag['src'] img = Image() img.open(iurl) if img < 0: raise RuntimeError('Out of memory') img.type = "GrayscaleType" img.save(iurl) return soup
def rescale_image(self, data): orig_w, orig_h, ifmt = identify_data(data) orig_data = data # save it in case compression fails if self.scale_news_images is not None: wmax, hmax = self.scale_news_images scale, new_w, new_h = fit_image(orig_w, orig_h, wmax, hmax) if scale: data = thumbnail(data, new_w, new_h, compression_quality=95)[-1] orig_w = new_w orig_h = new_h if self.compress_news_images_max_size is None: if self.compress_news_images_auto_size is None: # not compressing return data else: maxsizeb = (orig_w * orig_h) / self.compress_news_images_auto_size else: maxsizeb = self.compress_news_images_max_size * 1024 scaled_data = data # save it in case compression fails if len(scaled_data) <= maxsizeb: # no compression required return scaled_data img = Image() quality = 95 img.load(data) while len(data) >= maxsizeb and quality >= 5: quality -= 5 img.set_compression_quality(quality) data = img.export('jpg') if len(data) >= len(scaled_data): # compression failed return orig_data if len(orig_data) <= len( scaled_data) else scaled_data if len(data) >= len(orig_data): # no improvement return orig_data return data
def HandleImage(self, imageData, imagePath): from calibre.utils.magick import Image img = Image() img.load(imageData) (x, y) = img.size if self.opts: if self.opts.snb_full_screen: SCREEN_X, SCREEN_Y = self.opts.output_profile.screen_size else: SCREEN_X, SCREEN_Y = self.opts.output_profile.comic_screen_size else: SCREEN_X = 540 SCREEN_Y = 700 # Handle big image only if x > SCREEN_X or y > SCREEN_Y: xScale = float(x) / SCREEN_X yScale = float(y) / SCREEN_Y scale = max(xScale, yScale) # TODO : intelligent image rotation # img = img.rotate(90) # x,y = y,x img.size = (x / scale, y / scale) img.save(imagePath)
def browse_icon(self, name='blank.png'): cherrypy.response.headers['Content-Type'] = 'image/png' cherrypy.response.headers['Last-Modified'] = self.last_modified( self.build_time) if not hasattr(self, '__browse_icon_cache__'): self.__browse_icon_cache__ = {} if name not in self.__browse_icon_cache__: try: data = I(name, data=True) except: raise cherrypy.HTTPError(404, 'no icon named: %r' % name) img = Image() img.load(data) width, height = img.size scaled, width, height = fit_image(width, height, 48, 48) if scaled: img.size = (width, height) self.__browse_icon_cache__[name] = img.export('png') return self.__browse_icon_cache__[name]
def HandleImage(self, imageData, imagePath): from calibre.utils.magick import Image img = Image() img.load(imageData) (x,y) = img.size if self.opts: if self.opts.snb_full_screen: SCREEN_X, SCREEN_Y = self.opts.output_profile.screen_size else: SCREEN_X, SCREEN_Y = self.opts.output_profile.comic_screen_size else: SCREEN_X = 540 SCREEN_Y = 700 # Handle big image only if x > SCREEN_X or y > SCREEN_Y: xScale = float(x) / SCREEN_X yScale = float(y) / SCREEN_Y scale = max(xScale, yScale) # TODO : intelligent image rotation # img = img.rotate(90) # x,y = y,x img.size = (x / scale, y / scale) img.save(imagePath)
def rescale_image(self, data): orig_w, orig_h, ifmt = identify_data(data) orig_data = data # save it in case compression fails if self.scale_news_images is not None: wmax, hmax = self.scale_news_images scale, new_w, new_h = fit_image(orig_w, orig_h, wmax, hmax) if scale: data = thumbnail(data, new_w, new_h, compression_quality=95)[-1] orig_w = new_w orig_h = new_h if self.compress_news_images_max_size is None: if self.compress_news_images_auto_size is None: # not compressing return data else: maxsizeb = (orig_w * orig_h)/self.compress_news_images_auto_size else: maxsizeb = self.compress_news_images_max_size * 1024 scaled_data = data # save it in case compression fails if len(scaled_data) <= maxsizeb: # no compression required return scaled_data img = Image() quality = 95 img.load(data) while len(data) >= maxsizeb and quality >= 5: quality -= 5 img.set_compression_quality(quality) data = img.export('jpg') if len(data) >= len(scaled_data): # compression failed return orig_data if len(orig_data) <= len(scaled_data) else scaled_data if len(data) >= len(orig_data): # no improvement return orig_data return data
def convert_image(url, data, sizes, grayscale, removetrans, imgtype="jpg", background="#ffffff"): export = False img = Image() img.load(data) owidth, oheight = img.size nwidth, nheight = sizes scaled, nwidth, nheight = fit_image(owidth, oheight, nwidth, nheight) if normalize_format_name(img.format) == "gif" and GifInfo(StringIO(data), CHECK_IS_ANIMATED).frameCount > 1: raise exceptions.RejectImage("Animated gifs come out purely--not going to use it.") if scaled: img.size = (nwidth, nheight) export = True if normalize_format_name(img.format) != imgtype: export = True if removetrans and img.has_transparent_pixels(): canvas = Image() canvas.create_canvas(int(img.size[0]), int(img.size[1]), unicode(background)) canvas.compose(img) img = canvas export = True if grayscale and img.type != "GrayscaleType": img.type = "GrayscaleType" export = True if export: return (img.export(convtype[imgtype]), imgtype, imagetypes[imgtype]) else: logger.debug("image used unchanged") return (data, imgtype, imagetypes[imgtype])
def convert_image(url, data, sizes, grayscale, removetrans, imgtype="jpg", background='#ffffff'): export = False img = Image() img.load(data) owidth, oheight = img.size nwidth, nheight = sizes scaled, nwidth, nheight = fit_image(owidth, oheight, nwidth, nheight) if normalize_format_name(img.format) == "gif" and GifInfo( StringIO(data), CHECK_IS_ANIMATED).frameCount > 1: raise exceptions.RejectImage( "Animated gifs come out purely--not going to use it.") if scaled: img.size = (nwidth, nheight) export = True if normalize_format_name(img.format) != imgtype: export = True if removetrans and img.has_transparent_pixels(): canvas = Image() canvas.create_canvas(int(img.size[0]), int(img.size[1]), unicode(background)) canvas.compose(img) img = canvas export = True if grayscale and img.type != "GrayscaleType": img.type = "GrayscaleType" export = True if export: return (img.export(convtype[imgtype]), imgtype, imagetypes[imgtype]) else: logger.debug("image used unchanged") return (data, imgtype, imagetypes[imgtype])
def process_images(self, soup, baseurl): diskpath = unicode_path(os.path.join(self.current_dir, 'images')) if not os.path.exists(diskpath): os.mkdir(diskpath) c = 0 for tag in soup.findAll( lambda tag: tag.name.lower() == 'img' and tag.has_key('src')): iurl = tag['src'] if iurl.startswith('data:image/'): try: data = b64decode(iurl.partition(',')[-1]) except: self.log.exception('Failed to decode embedded image') continue else: if callable(self.image_url_processor): iurl = self.image_url_processor(baseurl, iurl) if not urlparse.urlsplit(iurl).scheme: iurl = urlparse.urljoin(baseurl, iurl, False) with self.imagemap_lock: if self.imagemap.has_key(iurl): tag['src'] = self.imagemap[iurl] continue try: data = self.fetch_url(iurl) if data == 'GIF89a\x01': # Skip empty GIF files as PIL errors on them anyway continue except Exception: self.log.exception('Could not fetch image ', iurl) continue c += 1 fname = ascii_filename('img' + str(c)) if isinstance(fname, unicode): fname = fname.encode('ascii', 'replace') itype = what(None, data) if itype is None and b'<svg' in data[:1024]: # SVG image imgpath = os.path.join(diskpath, fname + '.svg') with self.imagemap_lock: self.imagemap[iurl] = imgpath with open(imgpath, 'wb') as x: x.write(data) tag['src'] = imgpath else: try: if itype not in {'png', 'jpg', 'jpeg'}: itype = 'png' if itype == 'gif' else 'jpg' im = Image() im.load(data) data = im.export(itype) if self.compress_news_images and itype in {'jpg', 'jpeg'}: try: data = self.rescale_image(data) except: self.log.exception('failed to compress image ' + iurl) identify_data(data) else: identify_data(data) # Moon+ apparently cannot handle .jpeg files if itype == 'jpeg': itype = 'jpg' imgpath = os.path.join(diskpath, fname + '.' + itype) with self.imagemap_lock: self.imagemap[iurl] = imgpath with open(imgpath, 'wb') as x: x.write(data) tag['src'] = imgpath except: traceback.print_exc() continue
def do_all(self): cache = self.db.new_api args = self.args # Title and authors if args.do_swap_ta: title_map = cache.all_field_for('title', self.ids) authors_map = cache.all_field_for('authors', self.ids) def new_title(authors): ans = authors_to_string(authors) return titlecase(ans) if args.do_title_case else ans new_title_map = {bid:new_title(authors) for bid, authors in authors_map.iteritems()} new_authors_map = {bid:string_to_authors(title) for bid, title in title_map.iteritems()} cache.set_field('authors', new_authors_map) cache.set_field('title', new_title_map) if args.do_title_case and not args.do_swap_ta: title_map = cache.all_field_for('title', self.ids) cache.set_field('title', {bid:titlecase(title) for bid, title in title_map.iteritems()}) if args.do_title_sort: lang_map = cache.all_field_for('languages', self.ids) title_map = cache.all_field_for('title', self.ids) def get_sort(book_id): if args.languages: lang = args.languages[0] else: try: lang = lang_map[book_id][0] except (KeyError, IndexError, TypeError, AttributeError): lang = 'eng' return title_sort(title_map[book_id], lang=lang) cache.set_field('sort', {bid:get_sort(bid) for bid in self.ids}) if args.au: authors = string_to_authors(args.au) cache.set_field('authors', {bid:authors for bid in self.ids}) if args.do_auto_author: aus_map = cache.author_sort_strings_for_books(self.ids) cache.set_field('author_sort', {book_id:' & '.join(aus_map[book_id]) for book_id in aus_map}) if args.aus and args.do_aus: cache.set_field('author_sort', {bid:args.aus for bid in self.ids}) # Covers if args.cover_action == 'remove': cache.set_cover({bid:None for bid in self.ids}) elif args.cover_action == 'generate': from calibre.ebooks import calibre_cover from calibre.ebooks.metadata import fmt_sidx from calibre.gui2 import config for book_id in self.ids: mi = self.db.get_metadata(book_id, index_is_id=True) series_string = None if mi.series: series_string = _('Book %(sidx)s of %(series)s')%dict( sidx=fmt_sidx(mi.series_index, use_roman=config['use_roman_numerals_for_series_number']), series=mi.series) cdata = calibre_cover(mi.title, mi.format_field('authors')[-1], series_string=series_string) cache.set_cover({book_id:cdata}) elif args.cover_action == 'fromfmt': for book_id in self.ids: fmts = cache.formats(book_id, verify_formats=False) if fmts: covers = [] for fmt in fmts: fmtf = cache.format(book_id, fmt, as_file=True) if fmtf is None: continue cdata, area = get_cover_data(fmtf, fmt) if cdata: covers.append((cdata, area)) covers.sort(key=lambda x: x[1]) if covers: cache.set_cover({book_id:covers[-1][0]}) elif args.cover_action == 'trim': from calibre.utils.magick import Image for book_id in self.ids: cdata = cache.cover(book_id) if cdata: im = Image() im.load(cdata) im.trim(tweaks['cover_trim_fuzz_value']) cdata = im.export('jpg') cache.set_cover({book_id:cdata}) elif args.cover_action == 'clone': cdata = None for book_id in self.ids: cdata = cache.cover(book_id) if cdata: break if cdata: cache.set_cover({bid:cdata for bid in self.ids if bid != book_id}) # Formats if args.do_remove_format: cache.remove_formats({bid:(args.remove_format,) for bid in self.ids}) if args.restore_original: for book_id in self.ids: formats = cache.formats(book_id) originals = tuple(x.upper() for x in formats if x.upper().startswith('ORIGINAL_')) for ofmt in originals: cache.restore_original_format(book_id, ofmt) # Various fields if args.rating != -1: cache.set_field('rating', {bid:args.rating*2 for bid in self.ids}) if args.clear_pub: cache.set_field('publisher', {bid:'' for bid in self.ids}) if args.pub: cache.set_field('publisher', {bid:args.pub for bid in self.ids}) if args.clear_series: cache.set_field('series', {bid:'' for bid in self.ids}) if args.pubdate is not None: cache.set_field('pubdate', {bid:args.pubdate for bid in self.ids}) if args.adddate is not None: cache.set_field('timestamp', {bid:args.adddate for bid in self.ids}) if args.do_series: sval = args.series_start_value if args.do_series_restart else cache.get_next_series_num_for(args.series, current_indices=True) cache.set_field('series', {bid:args.series for bid in self.ids}) if not args.series: cache.set_field('series_index', {bid:1.0 for bid in self.ids}) else: def next_series_num(bid, i): if args.do_series_restart: return sval + i next_num = _get_next_series_num_for_list(sorted(sval.itervalues()), unwrap=False) sval[bid] = next_num return next_num smap = {bid:next_series_num(bid, i) for i, bid in enumerate(self.ids)} if args.do_autonumber: cache.set_field('series_index', smap) elif tweaks['series_index_auto_increment'] != 'no_change': cache.set_field('series_index', {bid:1.0 for bid in self.ids}) if args.comments is not null: cache.set_field('comments', {bid:args.comments for bid in self.ids}) if args.do_remove_conv: cache.delete_conversion_options(self.ids) if args.clear_languages: cache.set_field('languages', {bid:() for bid in self.ids}) elif args.languages: cache.set_field('languages', {bid:args.languages for bid in self.ids}) if args.remove_all: cache.set_field('tags', {bid:() for bid in self.ids}) if args.add or args.remove: self.db.bulk_modify_tags(self.ids, add=args.add, remove=args.remove) if self.do_sr: for book_id in self.ids: self.s_r_func(book_id) if self.sr_calls: for field, book_id_val_map in self.sr_calls.iteritems(): self.refresh_books.update(self.db.new_api.set_field(field, book_id_val_map))
def extract_content(self, output_dir): # Each text record is independent (unless the continuation # value is set in the previous record). Put each converted # text recored into a separate file. We will reference the # home.html file as the first file and let the HTML input # plugin assemble the order based on hyperlinks. with CurrentDir(output_dir): for uid, num in self.uid_text_secion_number.items(): self.log.debug('Writing record with uid: %s as %s.html' % (uid, uid)) with open('%s.html' % uid, 'wb') as htmlf: html = u'<html><body>' section_header, section_data = self.sections[num] if section_header.type == DATATYPE_PHTML: html += self.process_phtml( section_data.data, section_data.header.paragraph_offsets) elif section_header.type == DATATYPE_PHTML_COMPRESSED: d = self.decompress_phtml(section_data.data) html += self.process_phtml( d, section_data.header.paragraph_offsets).decode( self.get_text_uid_encoding(section_header.uid), 'replace') html += '</body></html>' htmlf.write(html.encode('utf-8')) # Images. # Cache the image sizes in case they are used by a composite image. image_sizes = {} if not os.path.exists(os.path.join(output_dir, 'images/')): os.makedirs(os.path.join(output_dir, 'images/')) with CurrentDir(os.path.join(output_dir, 'images/')): # Single images. for uid, num in self.uid_image_section_number.items(): section_header, section_data = self.sections[num] if section_data: idata = None if section_header.type == DATATYPE_TBMP: idata = section_data elif section_header.type == DATATYPE_TBMP_COMPRESSED: if self.header_record.compression == 1: idata = decompress_doc(section_data) elif self.header_record.compression == 2: idata = zlib.decompress(section_data) try: with TemporaryFile(suffix='.palm') as itn: with open(itn, 'wb') as itf: itf.write(idata) im = Image() im.read(itn) image_sizes[uid] = im.size im.set_compression_quality(70) im.save('%s.jpg' % uid) self.log.debug( 'Wrote image with uid %s to images/%s.jpg' % (uid, uid)) except Exception as e: self.log.error( 'Failed to write image with uid %s: %s' % (uid, e)) else: self.log.error( 'Failed to write image with uid %s: No data.' % uid) # Composite images. # We're going to use the already compressed .jpg images here. for uid, num in self.uid_composite_image_section_number.items(): try: section_header, section_data = self.sections[num] # Get the final width and height. width = 0 height = 0 for row in section_data.layout: row_width = 0 col_height = 0 for col in row: if col not in image_sizes: raise Exception('Image with uid: %s missing.' % col) im = Image() im.read('%s.jpg' % col) w, h = im.size row_width += w if col_height < h: col_height = h if width < row_width: width = row_width height += col_height # Create a new image the total size of all image # parts. Put the parts into the new image. canvas = create_canvas(width, height) y_off = 0 for row in section_data.layout: x_off = 0 largest_height = 0 for col in row: im = Image() im.read('%s.jpg' % col) canvas.compose(im, x_off, y_off) w, h = im.size x_off += w if largest_height < h: largest_height = h y_off += largest_height canvas.set_compression_quality(70) canvas.save('%s.jpg' % uid) self.log.debug( 'Wrote composite image with uid %s to images/%s.jpg' % (uid, uid)) except Exception as e: self.log.error( 'Failed to write composite image with uid %s: %s' % (uid, e)) # Run the HTML through the html processing plugin. from calibre.customize.ui import plugin_for_input_format html_input = plugin_for_input_format('html') for opt in html_input.options: setattr(self.options, opt.option.name, opt.recommended_value) self.options.input_encoding = 'utf-8' odi = self.options.debug_pipeline self.options.debug_pipeline = None # Determine the home.html record uid. This should be set in the # reserved values in the metadata recored. home.html is the first # text record (should have hyper link references to other records) # in the document. try: home_html = self.header_record.home_html if not home_html: home_html = self.uid_text_secion_number.items()[0][0] except: raise Exception('Could not determine home.html') # Generate oeb from html conversion. oeb = html_input.convert(open('%s.html' % home_html, 'rb'), self.options, 'html', self.log, {}) self.options.debug_pipeline = odi return oeb
def create_cover_page(top_lines, bottom_lines, display_image, options, image_path, output_format='jpg'): from calibre.gui2 import ensure_app ensure_app() (width, height) = options.get(cfg.KEY_SIZE, (590, 750)) margins = options.get(cfg.KEY_MARGINS) (top_mgn, bottom_mgn, left_mgn, right_mgn, image_mgn) = ( margins['top'], margins['bottom'], margins['left'], margins['right'], margins['image']) left_mgn = min([left_mgn, (width / 2) - 10]) left_text_margin = left_mgn if left_mgn > 0 else 10 right_mgn = min([right_mgn, (width / 2) - 10]) right_text_margin = right_mgn if right_mgn > 0 else 10 colors = options[cfg.KEY_COLORS] bgcolor, border_color, fill_color, stroke_color = ( colors['background'], colors['border'], colors['fill'], colors['stroke']) if not options.get(cfg.KEY_COLOR_APPLY_STROKE, False): stroke_color = None auto_reduce_font = options.get(cfg.KEY_FONTS_AUTOREDUCED, False) borders = options[cfg.KEY_BORDERS] (cover_border_width, image_border_width) = ( borders['coverBorder'], borders['imageBorder']) is_background_image = options.get(cfg.KEY_BACKGROUND_IMAGE, False) if image_path: if not os.path.exists(image_path) or os.path.getsize(image_path) == 0: display_image = is_background_image = False canvas = create_canvas(width - cover_border_width * 2, height - cover_border_width * 2, bgcolor) if cover_border_width > 0: canvas = add_border(canvas, cover_border_width, border_color, bgcolor) if is_background_image: logo = Image() logo.open(image_path) outer_margin = 0 if cover_border_width == 0 else cover_border_width logo.size = (width - outer_margin * 2, height - outer_margin * 2) left = top = outer_margin canvas.compose(logo, int(left), int(top)) top = top_mgn if len(top_lines) > 0: for line in top_lines: twand = create_colored_text_wand(line, fill_color, stroke_color) top = draw_sized_text( canvas, twand, line, top, left_text_margin, right_text_margin, auto_reduce_font) top += line.bottom_margin top -= top_lines[-1].bottom_margin if len(bottom_lines) > 0: # Draw this on a fake canvas so can determine the space required fake_canvas = create_canvas(width, height, bgcolor) footer_height = 0 for line in bottom_lines: line.twand = create_colored_text_wand( line, fill_color, stroke_color) footer_height = draw_sized_text( fake_canvas, line.twand, line, footer_height, left_text_margin, right_text_margin, auto_reduce_font) footer_height += line.bottom_margin footer_height -= bottom_lines[-1].bottom_margin footer_top = height - footer_height - bottom_mgn bottom = footer_top # Re-use the text wand from previously which we will have adjusted the # font size on for line in bottom_lines: bottom = draw_sized_text( canvas, line.twand, line, bottom, left_text_margin, right_text_margin, auto_reduce_font=False) bottom += line.bottom_margin available = (width - (left_mgn + right_mgn), int(footer_top - top) - (image_mgn * 2)) else: available = (width - (left_mgn + right_mgn), int(height - top) - bottom_mgn - (image_mgn * 2)) if not is_background_image and display_image and available[1] > 40: logo = Image() logo.open(image_path) lwidth, lheight = logo.size available = (available[0] - image_border_width * 2, available[1] - image_border_width * 2) scaled, lwidth, lheight = fit_image(lwidth, lheight, *available) if not scaled and options.get(cfg.KEY_RESIZE_IMAGE_TO_FIT, False): scaled, lwidth, lheight = scaleup_image( lwidth, lheight, *available) if scaled: logo.size = (lwidth, lheight) if image_border_width > 0: logo = add_border(logo, image_border_width, border_color, bgcolor) left = int(max(0, (width - lwidth) / 2.)) top = top + image_mgn + ((available[1] - lheight) / 2.) canvas.compose(logo, int(left), int(top)) return canvas.export(output_format)