100 lines
2.8 KiB
Python
100 lines
2.8 KiB
Python
import io
|
|
import os
|
|
import re
|
|
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
WIDTH = 1280
|
|
HEIGHT = 720
|
|
WIDTH2 = WIDTH // 2
|
|
HEIGHT2 = HEIGHT // 2
|
|
HEIGHT6 = HEIGHT // 6
|
|
|
|
|
|
def font_path() -> str:
|
|
dirname = os.path.dirname(os.path.realpath(__file__))
|
|
return os.path.join(dirname, "fonts", "DejaVuSans.ttf")
|
|
|
|
|
|
def draw_text(
|
|
ctx: ImageDraw,
|
|
text: str,
|
|
box: tuple,
|
|
anchor: str,
|
|
fill: str | tuple,
|
|
font_size: int,
|
|
line_width: int = 20,
|
|
):
|
|
"""Draw multiline text inside box with smart anchor."""
|
|
lines = re.findall(r"(.{1,%d})(?:\s|$)" % line_width, text)
|
|
if (font_size > 70 and len(lines) > 3) or (font_size <= 70 and len(lines) > 4):
|
|
return draw_text(ctx, text, box, anchor, fill, font_size - 10, line_width + 3)
|
|
|
|
# https://pillow.readthedocs.io/en/stable/handbook/text-anchors.html#text-anchors
|
|
if anchor[0] == "l":
|
|
x = box[0]
|
|
align = "la" # left-ascender
|
|
elif anchor[0] == "m":
|
|
x = box[0] + (box[2]) // 2
|
|
align = "ma" # middle-ascender
|
|
elif anchor[0] == "r":
|
|
x = box[0] + box[2]
|
|
align = "ra" # right-ascender
|
|
else:
|
|
raise NotImplementedError(anchor)
|
|
|
|
if anchor[1] == "t":
|
|
y = box[1]
|
|
elif anchor[1] == "m":
|
|
y = box[1] + (box[3] - len(lines) * font_size) // 2
|
|
elif anchor[1] == "b":
|
|
y = box[1] + (box[3] - len(lines) * font_size)
|
|
else:
|
|
raise NotImplementedError(anchor)
|
|
|
|
font = ImageFont.truetype(font_path(), font_size, encoding="UTF-8")
|
|
|
|
for line in lines:
|
|
ctx.text((x, y), line, anchor=align, fill=fill, font=font)
|
|
y += font_size
|
|
|
|
|
|
def draw_cover(title: str, artist: str, cover: bytes) -> bytes:
|
|
cover_canvas = Image.open(io.BytesIO(cover))
|
|
assert cover_canvas.size == (400, 400)
|
|
|
|
canvas = Image.new("RGB", (WIDTH, HEIGHT))
|
|
canvas.paste(cover_canvas, (WIDTH2 - 200, HEIGHT6 * 2 - 200))
|
|
|
|
ctx = ImageDraw.Draw(canvas)
|
|
if title:
|
|
draw_text(ctx, title, (0, HEIGHT6 * 4, WIDTH, HEIGHT6), "mb", "white", 60, 35)
|
|
if artist:
|
|
draw_text(ctx, artist, (0, HEIGHT6 * 5, WIDTH, HEIGHT6), "mt", "grey", 50, 40)
|
|
|
|
bytes = io.BytesIO()
|
|
canvas.save(bytes, format="JPEG", quality=75)
|
|
return bytes.getvalue()
|
|
|
|
|
|
def draw_lyrics(first: str | None, second: str | None) -> bytes:
|
|
canvas = Image.new("RGB", (WIDTH, HEIGHT))
|
|
|
|
ctx = ImageDraw.Draw(canvas)
|
|
if first:
|
|
draw_text(ctx, first, (0, 50, WIDTH, HEIGHT2 - 50), "mm", "white", 100)
|
|
if second:
|
|
draw_text(ctx, second, (0, HEIGHT2, WIDTH, HEIGHT2 - 50), "mm", "grey", 100)
|
|
|
|
bytes = io.BytesIO()
|
|
canvas.save(bytes, format="JPEG", quality=75)
|
|
return bytes.getvalue()
|
|
|
|
|
|
def draw_none() -> bytes:
|
|
canvas = Image.new("RGB", (WIDTH, HEIGHT), "grey")
|
|
|
|
bytes = io.BytesIO()
|
|
canvas.save(bytes, format="JPEG", quality=75)
|
|
return bytes.getvalue()
|