diff --git a/ebook/cover.py b/ebook/cover.py index 281b8a7..1d276b6 100644 --- a/ebook/cover.py +++ b/ebook/cover.py @@ -1,9 +1,10 @@ -from PIL import Image, ImageDraw, ImageFont +from PIL import Image, ImageDraw from io import BytesIO import textwrap import requests import logging +from . import image logger = logging.getLogger(__name__) @@ -15,14 +16,14 @@ def make_cover(title, author, width=600, height=800, fontname="Helvetica", fonts title = textwrap.fill(title, wrapat) author = textwrap.fill(author, wrapat) - font = _safe_font(fontname, size=fontsize) - title_size = textsize(draw, title, font=font) - draw_text_outlined(draw, ((width - title_size[0]) / 2, 100), title, textcolor, font=font) + font = image._safe_font(fontname, size=fontsize) + title_size = image.textsize(draw, title, font=font) + image.draw_text_outlined(draw, ((width - title_size[0]) / 2, 100), title, textcolor, font=font) # draw.text(((width - title_size[0]) / 2, 100), title, textcolor, font=font) - font = _safe_font(fontname, size=fontsize - 2) - author_size = textsize(draw, author, font=font) - draw_text_outlined(draw, ((width - author_size[0]) / 2, 100 + title_size[1] + 70), author, textcolor, font=font) + font = image._safe_font(fontname, size=fontsize - 2) + author_size = image.textsize(draw, author, font=font) + image.draw_text_outlined(draw, ((width - author_size[0]) / 2, 100 + title_size[1] + 70), author, textcolor, font=font) output = BytesIO() img.save(output, "PNG") @@ -44,7 +45,7 @@ def make_cover_from_url(url, title, author): cover.seek(0) if imgformat != "PNG": - cover = _convert_to_png(cover) + cover = image._convert_to_new_format(cover, "PNG") except Exception as e: logger.info("Encountered an error downloading cover: " + str(e)) cover = make_cover(title, author) @@ -52,46 +53,6 @@ def make_cover_from_url(url, title, author): return cover -def _convert_to_png(image_bytestream): - png_image = BytesIO() - Image.open(image_bytestream).save(png_image, format="PNG") - png_image.name = 'cover.png' - png_image.seek(0) - - return png_image - - -def _safe_font(preferred, *args, **kwargs): - for font in (preferred, "Helvetica", "FreeSans", "Arial"): - try: - return ImageFont.truetype(*args, font=font, **kwargs) - except IOError: - pass - - # This is pretty terrible, but it'll work regardless of what fonts the - # system has. Worst issue: can't set the size. - return ImageFont.load_default() - - -def textsize(draw, text, **kwargs): - left, top, right, bottom = draw.multiline_textbbox((0, 0), text, **kwargs) - width, height = right - left, bottom - top - return width, height - - -def draw_text_outlined(draw, xy, text, fill=None, font=None, anchor=None): - x, y = xy - - # Outline - draw.text((x - 1, y), text=text, fill=(0, 0, 0), font=font, anchor=anchor) - draw.text((x + 1, y), text=text, fill=(0, 0, 0), font=font, anchor=anchor) - draw.text((x, y - 1), text=text, fill=(0, 0, 0), font=font, anchor=anchor) - draw.text((x, y + 1), text=text, fill=(0, 0, 0), font=font, anchor=anchor) - - # Fill - draw.text(xy, text=text, fill=fill, font=font, anchor=anchor) - - if __name__ == '__main__': f = make_cover('Test of a Title which is quite long and will require multiple lines', 'Some Dude') with open('output.png', 'wb') as out: diff --git a/ebook/image.py b/ebook/image.py index b89b59b..37affab 100644 --- a/ebook/image.py +++ b/ebook/image.py @@ -13,38 +13,6 @@ from typing import Tuple logger = logging.getLogger(__name__) -def make_image( - message: str, - width=600, - height=300, - fontname="Helvetica", - font_size=40, - bg_color=(0, 0, 0), - textcolor=(255, 255, 255), - wrap_at=30 -): - """ - This function should only be called if get_image_from_url() fails - """ - img = Image.new("RGB", (width, height), bg_color) - draw = ImageDraw.Draw(img) - - message = textwrap.fill(message, wrap_at) - - font = _safe_font(fontname, size=font_size) - message_size = draw.textsize(message, font=font) - draw_text_outlined( - draw, ((width - message_size[0]) / 2, 100), message, textcolor, font=font) - # draw.text(((width - title_size[0]) / 2, 100), title, textcolor, font=font) - - output = BytesIO() - img.save(output, "JPEG") - output.name = 'cover.jpeg' - # writing left the cursor at the end of the file, so reset it - output.seek(0) - return output - - def get_size_format(b, factor=1000, suffix="B"): """ Scale bytes to its proper byte format @@ -134,8 +102,8 @@ def get_image_from_url( logger.warning("Filepicker.io image detected, converting to Fiction.live image. This might fail.") url = f"https://cdn3.fiction.live/fp/{url.split('/')[-1]}?&quality=95" elif url.startswith("https://cdn3.fiction.live/images/") or url.startswith("https://ddx5i92cqts4o.cloudfront.net/images/"): - logger.warning("Converting url to cdn6. This might fail.") - url = f"https://cdn6.fiction.live/file/fictionlive/images/{url.split('/images/')[-1]}" + logger.warning("Converting url to cdn6. This might fail.") + url = f"https://cdn6.fiction.live/file/fictionlive/images/{url.split('/images/')[-1]}" elif url.startswith("data:image") and 'base64' in url: logger.info("Base64 image detected") head, base64data = url.split(',') @@ -159,34 +127,63 @@ def get_image_from_url( image.seek(0) PIL_image = Image.open(image) - img_format = str(PIL_image.format) - if img_format.lower() == "gif": + if str(PIL_image.format).lower() == "gif": PIL_image = Image.open(image) if PIL_image.info['version'] not in [b"GIF89a", "GIF89a"]: PIL_image.info['version'] = b"GIF89a" return PIL_Image_to_bytes(PIL_image, "GIF"), "gif", "image/gif" if compress_images: - PIL_image = compress_image(image, max_image_size, img_format) + PIL_image = compress_image(image, max_image_size, str(PIL_image.format)) return PIL_Image_to_bytes(PIL_image, image_format), image_format, f"image/{image_format.lower()}" except Exception as e: logger.info("Encountered an error downloading image: " + str(e)) - cover = make_image("There was a problem downloading this image.").read() - return cover, "jpeg", "image/jpeg" + image = make_fallback_image("There was a problem downloading this image.").read() + return image, "jpeg", "image/jpeg" + + +def make_fallback_image( + message: str, + width=600, + height=300, + fontname="Helvetica", + font_size=40, + bg_color=(0, 0, 0), + textcolor=(255, 255, 255), + wrap_at=30 +): + """ + This function should only be called if get_image_from_url() fails + """ + img = Image.new("RGB", (width, height), bg_color) + draw = ImageDraw.Draw(img) + + message = textwrap.fill(message, wrap_at) + + font = _safe_font(fontname, size=font_size) + message_size = textsize(draw, message, font=font) + draw_text_outlined( + draw, ((width - message_size[0]) / 2, 100), message, textcolor, font=font) + # draw.text(((width - title_size[0]) / 2, 100), title, textcolor, font=font) + + output = BytesIO() + img.save(output, "JPEG") + # writing left the cursor at the end of the file, so reset it + output.seek(0) + return output def _convert_to_new_format(image_bytestream, image_format: str): new_image = BytesIO() try: Image.open(image_bytestream).save(new_image, format=image_format.upper()) - new_image.name = f'cover.{image_format.lower()}' new_image.seek(0) except Exception as e: logger.info(f"Encountered an error converting image to {image_format}\nError: {e}") - new_image = make_image("There was a problem converting this image.") + new_image = make_fallback_image("There was a problem converting this image.") return new_image @@ -202,6 +199,12 @@ def _safe_font(preferred, *args, **kwargs): return ImageFont.load_default() +def textsize(draw, text, **kwargs): + left, top, right, bottom = draw.multiline_textbbox((0, 0), text, **kwargs) + width, height = right - left, bottom - top + return width, height + + def draw_text_outlined(draw, xy, text, fill=None, font=None, anchor=None): x, y = xy @@ -216,7 +219,9 @@ def draw_text_outlined(draw, xy, text, fill=None, font=None, anchor=None): if __name__ == '__main__': - f = make_image( - 'Test of a Title which is quite long and will require multiple lines') + f = make_fallback_image( + 'Test of a Title which is quite long and will require multiple lines', + 'output.png' + ) with open('output.png', 'wb') as out: out.write(f.read()) diff --git a/leech.py b/leech.py index 29cc7cc..5f66045 100755 --- a/leech.py +++ b/leech.py @@ -100,7 +100,7 @@ def create_options(site, site_options, unused_flags): list(overridden_site_options.items()) + list(flag_specified_site_options.items()) + list(cover_options.items()) + - list({'image_bool': image_bool, 'image_format': image_format, 'compress_images': compress_images, 'max_image_size': max_image_size }.items()) + list({'image_bool': image_bool, 'image_format': image_format, 'compress_images': compress_images, 'max_image_size': max_image_size}.items()) ) return options, login