newapi / routers /news.py
habulaj's picture
Update routers/news.py
109f5be verified
from fastapi import APIRouter, Query, HTTPException
from fastapi.responses import StreamingResponse
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
import requests
from typing import Optional
import os
router = APIRouter()
def download_image_from_url(url: str) -> Image.Image:
response = requests.get(url)
if response.status_code != 200:
raise HTTPException(status_code=400, detail="Imagem não pôde ser baixada.")
return Image.open(BytesIO(response.content)).convert("RGBA")
def resize_and_crop_to_fill(img: Image.Image, target_width: int, target_height: int) -> Image.Image:
img_ratio = img.width / img.height
target_ratio = target_width / target_height
if img_ratio > target_ratio:
scale_height = target_height
scale_width = int(scale_height * img_ratio)
else:
scale_width = target_width
scale_height = int(scale_width / img_ratio)
img_resized = img.resize((scale_width, scale_height), Image.LANCZOS)
left = (scale_width - target_width) // 2
top = (scale_height - target_height) // 2
right = left + target_width
bottom = top + target_height
return img_resized.crop((left, top, right, bottom))
def create_black_gradient_overlay(width: int, height: int) -> Image.Image:
gradient = Image.new("RGBA", (width, height))
draw = ImageDraw.Draw(gradient)
for y in range(height):
opacity = int(255 * (y / height))
draw.line([(0, y), (width, y)], fill=(4, 4, 4, opacity))
return gradient
def wrap_text(text: str, font: ImageFont.FreeTypeFont, max_width: int, draw: ImageDraw.Draw) -> list[str]:
words = text.split()
lines = []
current_line = ""
for word in words:
test_line = f"{current_line} {word}".strip()
if draw.textlength(test_line, font=font) <= max_width:
current_line = test_line
else:
if current_line:
lines.append(current_line)
current_line = word
if current_line:
lines.append(current_line)
return lines
def get_responsive_font_and_lines(text: str, font_path: str, max_width: int, max_lines: int = 3,
max_font_size: int = 50, min_font_size: int = 20) -> tuple[ImageFont.FreeTypeFont, list[str], int]:
"""
Retorna a fonte e linhas ajustadas para caber no número máximo de linhas.
Args:
text: Texto a ser renderizado
font_path: Caminho para o arquivo de fonte
max_width: Largura máxima disponível
max_lines: Número máximo de linhas permitidas
max_font_size: Tamanho máximo da fonte
min_font_size: Tamanho mínimo da fonte
Returns:
tuple: (fonte, linhas, tamanho_da_fonte)
"""
# Criar um draw temporário para calcular tamanhos
temp_img = Image.new("RGB", (1, 1))
temp_draw = ImageDraw.Draw(temp_img)
current_font_size = max_font_size
while current_font_size >= min_font_size:
try:
font = ImageFont.truetype(font_path, current_font_size)
except Exception:
# Se não conseguir carregar a fonte, usar fonte padrão
font = ImageFont.load_default()
lines = wrap_text(text, font, max_width, temp_draw)
# Se o texto cabe no número máximo de linhas, usar este tamanho
if len(lines) <= max_lines:
return font, lines, current_font_size
# Diminuir o tamanho da fonte
current_font_size -= 1
# Se chegou ao tamanho mínimo, usar mesmo assim
try:
font = ImageFont.truetype(font_path, min_font_size)
except Exception:
font = ImageFont.load_default()
lines = wrap_text(text, font, max_width, temp_draw)
return font, lines, min_font_size
def create_gradient_bar(width: int, height: int) -> Image.Image:
gradient = Image.new("RGBA", (width, height))
draw = ImageDraw.Draw(gradient)
for x in range(width):
ratio = x / (width - 1)
if ratio < 0.5:
r = int(0xFF * (1 - ratio * 2) + 0xF4 * ratio * 2)
g = int(0x82 * (1 - ratio * 2) + 0x0A * ratio * 2)
b = int(0x26 * (1 - ratio * 2) + 0xFF * ratio * 2)
else:
ratio2 = (ratio - 0.5) * 2
r = int(0xF4 * (1 - ratio2) + 0x03 * ratio2)
g = int(0x0A * (1 - ratio2) + 0xD9 * ratio2)
b = int(0xFF * (1 - ratio2) + 0xE3 * ratio2)
draw.line([(x, 0), (x, height)], fill=(r, g, b, 255))
radius = 44
mask = Image.new("L", (width, height), 0)
mask_draw = ImageDraw.Draw(mask)
mask_draw.rounded_rectangle([(0, 0), (width, height)], radius=radius, fill=255)
gradient.putalpha(mask)
return gradient
def create_canvas(image_url: Optional[str], headline: Optional[str]) -> BytesIO:
width, height = 1080, 1350
padding_x = 60
bottom_padding = 80
max_width = width - 2 * padding_x
canvas = Image.new("RGBA", (width, height), color=(255, 255, 255, 255))
if image_url:
img = download_image_from_url(image_url)
filled_img = resize_and_crop_to_fill(img, width, height)
canvas.paste(filled_img, (0, 0))
gradient_overlay = create_black_gradient_overlay(width, height)
canvas = Image.alpha_composite(canvas, gradient_overlay)
if headline:
draw = ImageDraw.Draw(canvas)
font_path = "fonts/Montserrat-Bold.ttf"
try:
# Obter fonte e linhas responsivas
font, lines, font_size = get_responsive_font_and_lines(
headline, font_path, max_width, max_lines=3,
max_font_size=50, min_font_size=20
)
# Calcular line_height baseado no tamanho da fonte atual
line_height = int(font_size * 1.22) # Proporção similar à original (61/50 = 1.22)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Erro ao processar a fonte: {e}")
total_text_height = len(lines) * line_height
start_y = height - bottom_padding - total_text_height
# Posições dos elementos acima do texto
bar_width = 375
bar_height = 6
space_bar_to_text = 16
space_logo_to_bar = 20
logo_width, logo_height = 162, 30
# Y do degradê
bar_y = start_y - space_bar_to_text - bar_height
# Y da logo
logo_y = bar_y - space_logo_to_bar - logo_height
# Garantir que a logo não ultrapasse o topo
if logo_y > 0:
# Adiciona logo
try:
logo_path = "recurve.png"
logo = Image.open(logo_path).convert("RGBA")
logo_resized = logo.resize((logo_width, logo_height))
canvas.paste(logo_resized, (padding_x, logo_y), logo_resized)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Erro ao carregar a logo: {e}")
# Adiciona barra colorida
bar = create_gradient_bar(bar_width, bar_height)
canvas.paste(bar, (padding_x, bar_y), bar)
# Adiciona texto
for i, line in enumerate(lines):
y = start_y + i * line_height
draw.text((padding_x, y), line, font=font, fill=(255, 255, 255))
buffer = BytesIO()
canvas.convert("RGB").save(buffer, format="PNG")
buffer.seek(0)
return buffer
@router.get("/cover/news")
def get_news_image(
image_url: Optional[str] = Query(None, description="URL da imagem para preencher o fundo"),
headline: Optional[str] = Query(None, description="Texto do título (opcional)")
):
try:
buffer = create_canvas(image_url, headline)
return StreamingResponse(buffer, media_type="image/png")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Erro ao gerar imagem: {str(e)}")