mirror of
https://github.com/kemayo/leech
synced 2025-12-06 16:33:16 +01:00
Code style fixups, duplication cleanup, PIL textsize removal
This commit is contained in:
parent
f557a48ac4
commit
542774543a
3 changed files with 58 additions and 92 deletions
|
|
@ -1,9 +1,10 @@
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import textwrap
|
import textwrap
|
||||||
import requests
|
import requests
|
||||||
import logging
|
import logging
|
||||||
|
from . import image
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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)
|
title = textwrap.fill(title, wrapat)
|
||||||
author = textwrap.fill(author, wrapat)
|
author = textwrap.fill(author, wrapat)
|
||||||
|
|
||||||
font = _safe_font(fontname, size=fontsize)
|
font = image._safe_font(fontname, size=fontsize)
|
||||||
title_size = textsize(draw, title, font=font)
|
title_size = image.textsize(draw, title, font=font)
|
||||||
draw_text_outlined(draw, ((width - title_size[0]) / 2, 100), title, textcolor, 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)
|
# draw.text(((width - title_size[0]) / 2, 100), title, textcolor, font=font)
|
||||||
|
|
||||||
font = _safe_font(fontname, size=fontsize - 2)
|
font = image._safe_font(fontname, size=fontsize - 2)
|
||||||
author_size = textsize(draw, author, font=font)
|
author_size = image.textsize(draw, author, font=font)
|
||||||
draw_text_outlined(draw, ((width - author_size[0]) / 2, 100 + title_size[1] + 70), author, textcolor, font=font)
|
image.draw_text_outlined(draw, ((width - author_size[0]) / 2, 100 + title_size[1] + 70), author, textcolor, font=font)
|
||||||
|
|
||||||
output = BytesIO()
|
output = BytesIO()
|
||||||
img.save(output, "PNG")
|
img.save(output, "PNG")
|
||||||
|
|
@ -44,7 +45,7 @@ def make_cover_from_url(url, title, author):
|
||||||
cover.seek(0)
|
cover.seek(0)
|
||||||
|
|
||||||
if imgformat != "PNG":
|
if imgformat != "PNG":
|
||||||
cover = _convert_to_png(cover)
|
cover = image._convert_to_new_format(cover, "PNG")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.info("Encountered an error downloading cover: " + str(e))
|
logger.info("Encountered an error downloading cover: " + str(e))
|
||||||
cover = make_cover(title, author)
|
cover = make_cover(title, author)
|
||||||
|
|
@ -52,46 +53,6 @@ def make_cover_from_url(url, title, author):
|
||||||
return cover
|
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__':
|
if __name__ == '__main__':
|
||||||
f = make_cover('Test of a Title which is quite long and will require multiple lines', 'Some Dude')
|
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:
|
with open('output.png', 'wb') as out:
|
||||||
|
|
|
||||||
|
|
@ -13,38 +13,6 @@ from typing import Tuple
|
||||||
logger = logging.getLogger(__name__)
|
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"):
|
def get_size_format(b, factor=1000, suffix="B"):
|
||||||
"""
|
"""
|
||||||
Scale bytes to its proper byte format
|
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.")
|
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"
|
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/"):
|
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.")
|
logger.warning("Converting url to cdn6. This might fail.")
|
||||||
url = f"https://cdn6.fiction.live/file/fictionlive/images/{url.split('/images/')[-1]}"
|
url = f"https://cdn6.fiction.live/file/fictionlive/images/{url.split('/images/')[-1]}"
|
||||||
elif url.startswith("data:image") and 'base64' in url:
|
elif url.startswith("data:image") and 'base64' in url:
|
||||||
logger.info("Base64 image detected")
|
logger.info("Base64 image detected")
|
||||||
head, base64data = url.split(',')
|
head, base64data = url.split(',')
|
||||||
|
|
@ -159,34 +127,63 @@ def get_image_from_url(
|
||||||
image.seek(0)
|
image.seek(0)
|
||||||
|
|
||||||
PIL_image = Image.open(image)
|
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)
|
PIL_image = Image.open(image)
|
||||||
if PIL_image.info['version'] not in [b"GIF89a", "GIF89a"]:
|
if PIL_image.info['version'] not in [b"GIF89a", "GIF89a"]:
|
||||||
PIL_image.info['version'] = b"GIF89a"
|
PIL_image.info['version'] = b"GIF89a"
|
||||||
return PIL_Image_to_bytes(PIL_image, "GIF"), "gif", "image/gif"
|
return PIL_Image_to_bytes(PIL_image, "GIF"), "gif", "image/gif"
|
||||||
|
|
||||||
if compress_images:
|
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()}"
|
return PIL_Image_to_bytes(PIL_image, image_format), image_format, f"image/{image_format.lower()}"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.info("Encountered an error downloading image: " + str(e))
|
logger.info("Encountered an error downloading image: " + str(e))
|
||||||
cover = make_image("There was a problem downloading this image.").read()
|
image = make_fallback_image("There was a problem downloading this image.").read()
|
||||||
return cover, "jpeg", "image/jpeg"
|
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):
|
def _convert_to_new_format(image_bytestream, image_format: str):
|
||||||
new_image = BytesIO()
|
new_image = BytesIO()
|
||||||
try:
|
try:
|
||||||
Image.open(image_bytestream).save(new_image, format=image_format.upper())
|
Image.open(image_bytestream).save(new_image, format=image_format.upper())
|
||||||
new_image.name = f'cover.{image_format.lower()}'
|
|
||||||
new_image.seek(0)
|
new_image.seek(0)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.info(f"Encountered an error converting image to {image_format}\nError: {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
|
return new_image
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -202,6 +199,12 @@ def _safe_font(preferred, *args, **kwargs):
|
||||||
return ImageFont.load_default()
|
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):
|
def draw_text_outlined(draw, xy, text, fill=None, font=None, anchor=None):
|
||||||
x, y = xy
|
x, y = xy
|
||||||
|
|
||||||
|
|
@ -216,7 +219,9 @@ def draw_text_outlined(draw, xy, text, fill=None, font=None, anchor=None):
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
f = make_image(
|
f = make_fallback_image(
|
||||||
'Test of a Title which is quite long and will require multiple lines')
|
'Test of a Title which is quite long and will require multiple lines',
|
||||||
|
'output.png'
|
||||||
|
)
|
||||||
with open('output.png', 'wb') as out:
|
with open('output.png', 'wb') as out:
|
||||||
out.write(f.read())
|
out.write(f.read())
|
||||||
|
|
|
||||||
2
leech.py
2
leech.py
|
|
@ -100,7 +100,7 @@ def create_options(site, site_options, unused_flags):
|
||||||
list(overridden_site_options.items()) +
|
list(overridden_site_options.items()) +
|
||||||
list(flag_specified_site_options.items()) +
|
list(flag_specified_site_options.items()) +
|
||||||
list(cover_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
|
return options, login
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue