habulaj commited on
Commit
209e4ad
·
verified ·
1 Parent(s): 0d25a50

Delete routers/news.py

Browse files
Files changed (1) hide show
  1. routers/news.py +0 -291
routers/news.py DELETED
@@ -1,291 +0,0 @@
1
- from fastapi import APIRouter, Query, HTTPException
2
- from fastapi.responses import StreamingResponse
3
- from PIL import Image, ImageDraw, ImageFont
4
- from io import BytesIO
5
- import requests
6
- from typing import Optional
7
-
8
- router = APIRouter()
9
-
10
- def download_image_from_url(url: str) -> Image.Image:
11
- headers = {
12
- "User-Agent": (
13
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
14
- "AppleWebKit/537.36 (KHTML, like Gecko) "
15
- "Chrome/115.0.0.0 Safari/537.36"
16
- )
17
- }
18
- try:
19
- response = requests.get(url, headers=headers, timeout=10)
20
- response.raise_for_status()
21
- return Image.open(BytesIO(response.content)).convert("RGBA")
22
- except Exception as e:
23
- raise HTTPException(status_code=400, detail=f"Erro ao baixar imagem: {url} ({str(e)})")
24
-
25
- def resize_and_crop_to_fill(img: Image.Image, target_width: int, target_height: int) -> Image.Image:
26
- img_ratio = img.width / img.height
27
- target_ratio = target_width / target_height
28
-
29
- if img_ratio > target_ratio:
30
- scale_height = target_height
31
- scale_width = int(scale_height * img_ratio)
32
- else:
33
- scale_width = target_width
34
- scale_height = int(scale_width / img_ratio)
35
-
36
- img_resized = img.resize((scale_width, scale_height), Image.LANCZOS)
37
-
38
- left = (scale_width - target_width) // 2
39
- top = (scale_height - target_height) // 2
40
- right = left + target_width
41
- bottom = top + target_height
42
-
43
- return img_resized.crop((left, top, right, bottom))
44
-
45
- def create_gradient_overlay(width: int, height: int, text_position: str = "bottom") -> Image.Image:
46
- """
47
- Cria gradiente overlay baseado na posição do texto
48
- """
49
- gradient = Image.new("RGBA", (width, height))
50
- draw = ImageDraw.Draw(gradient)
51
-
52
- if text_position.lower() == "bottom":
53
- # Gradiente para texto embaixo: posição Y:531, altura 835px
54
- gradient_start = 531
55
- gradient_height = 835
56
-
57
- for y in range(gradient_height):
58
- if y + gradient_start < height:
59
- # Gradient: 0% transparent -> 46.63% rgba(0,0,0,0.55) -> 100% rgba(0,0,0,0.7)
60
- ratio = y / gradient_height
61
- if ratio <= 0.4663:
62
- # 0% a 46.63%: de transparente para 0.55
63
- opacity_ratio = ratio / 0.4663
64
- opacity = int(255 * 0.55 * opacity_ratio)
65
- else:
66
- # 46.63% a 100%: de 0.55 para 0.7
67
- opacity_ratio = (ratio - 0.4663) / (1 - 0.4663)
68
- opacity = int(255 * (0.55 + (0.7 - 0.55) * opacity_ratio))
69
-
70
- draw.line([(0, y + gradient_start), (width, y + gradient_start)], fill=(0, 0, 0, opacity))
71
-
72
- else: # text_position == "top"
73
- # Gradiente para texto no topo: posição Y:0, altura 835px
74
- # linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.307045) 16.93%, rgba(0,0,0,0.55) 45.57%, rgba(0,0,0,0.7) 100%)
75
- # 0deg significa: 0% = bottom, 100% = top
76
- gradient_height = 835
77
-
78
- for y in range(gradient_height):
79
- if y < height:
80
- # Inverter a ratio: y=0 (topo) deve ser 100% do gradient, y=835 (bottom) deve ser 0%
81
- ratio = (gradient_height - y) / gradient_height
82
- if ratio <= 0.1693:
83
- # 0% a 16.93%: de 0 (transparente) para 0.307
84
- opacity_ratio = ratio / 0.1693
85
- opacity = int(255 * (0.307 * opacity_ratio))
86
- elif ratio <= 0.4557:
87
- # 16.93% a 45.57%: de 0.307 para 0.55
88
- opacity_ratio = (ratio - 0.1693) / (0.4557 - 0.1693)
89
- opacity = int(255 * (0.307 + (0.55 - 0.307) * opacity_ratio))
90
- else:
91
- # 45.57% a 100%: de 0.55 para 0.7
92
- opacity_ratio = (ratio - 0.4557) / (1 - 0.4557)
93
- opacity = int(255 * (0.55 + (0.7 - 0.55) * opacity_ratio))
94
-
95
- draw.line([(0, y), (width, y)], fill=(0, 0, 0, opacity))
96
-
97
- return gradient
98
-
99
- def wrap_text(text: str, font: ImageFont.FreeTypeFont, max_width: int, draw: ImageDraw.Draw) -> list[str]:
100
- words = text.split()
101
- lines = []
102
- current_line = ""
103
-
104
- for word in words:
105
- test_line = f"{current_line} {word}".strip()
106
- if draw.textlength(test_line, font=font) <= max_width:
107
- current_line = test_line
108
- else:
109
- if current_line:
110
- lines.append(current_line)
111
- current_line = word
112
- if current_line:
113
- lines.append(current_line)
114
- return lines
115
-
116
- def get_responsive_font_and_lines(text: str, font_path: str, max_width: int, max_lines: int = 3,
117
- max_font_size: int = 80, min_font_size: int = 20) -> tuple[ImageFont.FreeTypeFont, list[str], int]:
118
- """
119
- Retorna a fonte e linhas ajustadas para caber no número máximo de linhas.
120
- """
121
- temp_img = Image.new("RGB", (1, 1))
122
- temp_draw = ImageDraw.Draw(temp_img)
123
-
124
- current_font_size = max_font_size
125
-
126
- while current_font_size >= min_font_size:
127
- try:
128
- font = ImageFont.truetype(font_path, current_font_size)
129
- except Exception:
130
- font = ImageFont.load_default()
131
-
132
- lines = wrap_text(text, font, max_width, temp_draw)
133
-
134
- if len(lines) <= max_lines:
135
- return font, lines, current_font_size
136
-
137
- current_font_size -= 1
138
-
139
- try:
140
- font = ImageFont.truetype(font_path, min_font_size)
141
- except Exception:
142
- font = ImageFont.load_default()
143
-
144
- lines = wrap_text(text, font, max_width, temp_draw)
145
- return font, lines, min_font_size
146
-
147
- def get_font_and_lines(text: str, font_path: str, font_size: int, max_width: int) -> tuple[ImageFont.FreeTypeFont, list[str]]:
148
- """
149
- Retorna a fonte e linhas com tamanho fixo de fonte.
150
- """
151
- try:
152
- font = ImageFont.truetype(font_path, font_size)
153
- except Exception:
154
- font = ImageFont.load_default()
155
-
156
- temp_img = Image.new("RGB", (1, 1))
157
- temp_draw = ImageDraw.Draw(temp_img)
158
- lines = wrap_text(text, font, max_width, temp_draw)
159
-
160
- return font, lines
161
-
162
- def get_text_color_rgb(text_color: str) -> tuple[int, int, int]:
163
- """
164
- Converte o parâmetro text_color para RGB.
165
- """
166
- if text_color.lower() == "black":
167
- return (0, 0, 0)
168
- else: # white por padrão
169
- return (255, 255, 255)
170
-
171
- def get_device_dimensions(device: str) -> tuple[int, int]:
172
- """Retorna as dimensões baseadas no dispositivo"""
173
- if device.lower() == "web":
174
- return (1280, 720)
175
- else: # Instagram por padrão
176
- return (1080, 1350)
177
-
178
- def create_canvas(image_url: Optional[str], headline: Optional[str], device: str = "ig",
179
- text_position: str = "bottom", text_color: str = "white") -> BytesIO:
180
- width, height = get_device_dimensions(device)
181
- is_web = device.lower() == "web"
182
- text_rgb = get_text_color_rgb(text_color)
183
-
184
- # Configurações específicas por dispositivo
185
- if is_web:
186
- padding_x = 40
187
- logo_width, logo_height = 120, 22
188
- logo_padding = 40
189
- else:
190
- padding_x = 60
191
- bottom_padding = 80
192
- top_padding = 60
193
- logo_width, logo_height = 121, 23 # Novas dimensões: L:121, A:22.75 (arredondado para 23)
194
-
195
- max_width = width - 2 * padding_x
196
-
197
- canvas = Image.new("RGBA", (width, height), color=(255, 255, 255, 255))
198
-
199
- # Adicionar imagem de fundo se fornecida
200
- if image_url:
201
- img = download_image_from_url(image_url)
202
- filled_img = resize_and_crop_to_fill(img, width, height)
203
- canvas.paste(filled_img, (0, 0))
204
-
205
- # Para Instagram: adicionar gradiente e texto
206
- if not is_web:
207
- # Só aplicar gradiente se o texto for branco
208
- if text_color.lower() != "black":
209
- gradient_overlay = create_gradient_overlay(width, height, text_position)
210
- canvas = Image.alpha_composite(canvas, gradient_overlay)
211
-
212
- if headline:
213
- draw = ImageDraw.Draw(canvas)
214
- font_path = "fonts/AGaramondPro-Semibold.ttf"
215
- line_height_factor = 1.05 # 105% da altura da linha
216
-
217
- try:
218
- font, lines, font_size = get_responsive_font_and_lines(
219
- headline, font_path, max_width, max_lines=3,
220
- max_font_size=80, min_font_size=20
221
- )
222
- line_height = int(font_size * line_height_factor)
223
-
224
- except Exception as e:
225
- raise HTTPException(status_code=500, detail=f"Erro ao processar a fonte: {e}")
226
-
227
- total_text_height = len(lines) * line_height
228
-
229
- # Posicionar texto baseado no parâmetro text_position
230
- if text_position.lower() == "bottom":
231
- # Posicionar texto 50px acima da logo (que está em Y:1274)
232
- text_end_y = 1274 - 50
233
- start_y = text_end_y - total_text_height
234
- else: # text_position == "top"
235
- # Posicionar texto no topo com padding
236
- start_y = top_padding
237
-
238
- # Adicionar logo no canto inferior direito (posição fixa)
239
- try:
240
- logo_path = "recurve.png"
241
- logo = Image.open(logo_path).convert("RGBA")
242
- logo_resized = logo.resize((logo_width, logo_height))
243
-
244
- # Aplicar opacidade de 42%
245
- logo_with_opacity = Image.new("RGBA", logo_resized.size)
246
- for x in range(logo_resized.width):
247
- for y in range(logo_resized.height):
248
- r, g, b, a = logo_resized.getpixel((x, y))
249
- new_alpha = int(a * 0.42) # 42% de opacidade
250
- logo_with_opacity.putpixel((x, y), (r, g, b, new_alpha))
251
-
252
- # Posição fixa: X:891, Y:1274
253
- canvas.paste(logo_with_opacity, (891, 1274), logo_with_opacity)
254
- except Exception as e:
255
- raise HTTPException(status_code=500, detail=f"Erro ao carregar a logo: {e}")
256
-
257
- # Adiciona texto com a cor especificada
258
- for i, line in enumerate(lines):
259
- y = start_y + i * line_height
260
- draw.text((padding_x, y), line, font=font, fill=text_rgb)
261
-
262
- # Para web: apenas logo no canto inferior direito
263
- else:
264
- try:
265
- logo_path = "recurve.png"
266
- logo = Image.open(logo_path).convert("RGBA")
267
- logo_resized = logo.resize((logo_width, logo_height))
268
- logo_x = width - logo_width - logo_padding
269
- logo_y = height - logo_height - logo_padding
270
- canvas.paste(logo_resized, (logo_x, logo_y), logo_resized)
271
- except Exception as e:
272
- raise HTTPException(status_code=500, detail=f"Erro ao carregar a logo: {e}")
273
-
274
- buffer = BytesIO()
275
- canvas.convert("RGB").save(buffer, format="PNG")
276
- buffer.seek(0)
277
- return buffer
278
-
279
- @router.get("/cover/news")
280
- def get_news_image(
281
- image_url: Optional[str] = Query(None, description="URL da imagem de fundo"),
282
- headline: Optional[str] = Query(None, description="Texto do título (opcional para IG, ignorado para web)"),
283
- device: str = Query("ig", description="Dispositivo: 'ig' para Instagram (1080x1350) ou 'web' para Web (1280x720)"),
284
- text_position: str = Query("bottom", description="Posição do texto: 'top' para topo ou 'bottom' para parte inferior"),
285
- text_color: str = Query("white", description="Cor do texto: 'white' (padrão) ou 'black'. Se 'black', remove o gradiente de fundo")
286
- ):
287
- try:
288
- buffer = create_canvas(image_url, headline, device, text_position, text_color)
289
- return StreamingResponse(buffer, media_type="image/png")
290
- except Exception as e:
291
- raise HTTPException(status_code=500, detail=f"Erro ao gerar imagem: {str(e)}")