habulaj commited on
Commit
bb64f0e
·
verified ·
1 Parent(s): d5d9100

Update routers/news.py

Browse files
Files changed (1) hide show
  1. routers/news.py +168 -225
routers/news.py CHANGED
@@ -72,11 +72,10 @@ def resize_and_crop_to_fill(img: Image.Image, target_width: int, target_height:
72
 
73
  return img_resized.crop((left, top, right, bottom))
74
 
75
- def create_collage_background(image_urls: List[str], canvas_width: int, canvas_height: int, device: str, crop_position: str = "center") -> Image.Image:
76
  """Cria uma colagem como fundo baseada na lista de URLs"""
77
  num_images = len(image_urls)
78
  border_size = 4 if num_images > 1 else 0 # Linha mais fina e elegante
79
- is_web = device.lower() == "web"
80
 
81
  images = [download_image_from_url(url) for url in image_urls]
82
  canvas = Image.new("RGBA", (canvas_width, canvas_height), (255, 255, 255, 255))
@@ -85,151 +84,81 @@ def create_collage_background(image_urls: List[str], canvas_width: int, canvas_h
85
  img = resize_and_crop_to_fill(images[0], canvas_width, canvas_height, crop_position)
86
  canvas.paste(img, (0, 0))
87
  elif num_images == 2:
88
- # Ambos dispositivos: lado a lado
89
  slot_width = (canvas_width - border_size) // 2
90
  img1 = resize_and_crop_to_fill(images[0], slot_width, canvas_height, crop_position)
91
  img2 = resize_and_crop_to_fill(images[1], slot_width, canvas_height, crop_position)
92
  canvas.paste(img1, (0, 0))
93
  canvas.paste(img2, (slot_width + border_size, 0))
94
  elif num_images == 3:
95
- if is_web:
96
- # Web: 1 grande à esquerda, 2 pequenas empilhadas à direita
97
- left_width = (canvas_width - border_size) * 2 // 3
98
- right_width = canvas_width - left_width - border_size
99
- half_height = (canvas_height - border_size) // 2
100
-
101
- img1 = resize_and_crop_to_fill(images[0], left_width, canvas_height, crop_position)
102
- img2 = resize_and_crop_to_fill(images[1], right_width, half_height, crop_position)
103
- img3 = resize_and_crop_to_fill(images[2], right_width, half_height, crop_position)
104
-
105
- canvas.paste(img1, (0, 0))
106
- canvas.paste(img2, (left_width + border_size, 0))
107
- canvas.paste(img3, (left_width + border_size, half_height + border_size))
108
- else:
109
- # IG: layout original
110
- half_height = (canvas_height - border_size) // 2
111
- half_width = (canvas_width - border_size) // 2
112
-
113
- img1 = resize_and_crop_to_fill(images[0], half_width, half_height, crop_position)
114
- img2 = resize_and_crop_to_fill(images[1], half_width, half_height, crop_position)
115
- img3 = resize_and_crop_to_fill(images[2], canvas_width, half_height, crop_position)
116
-
117
- canvas.paste(img1, (0, 0))
118
- canvas.paste(img2, (half_width + border_size, 0))
119
- canvas.paste(img3, (0, half_height + border_size))
120
  elif num_images == 4:
121
- if is_web:
122
- # Web: 4 imagens lado a lado horizontalmente
123
- slot_width = (canvas_width - 3 * border_size) // 4
124
- for i in range(4):
125
- img = resize_and_crop_to_fill(images[i], slot_width, canvas_height, crop_position)
126
- x_pos = i * (slot_width + border_size)
127
- canvas.paste(img, (x_pos, 0))
128
- else:
129
- # IG: Layout 2x2
130
- half_height = (canvas_height - border_size) // 2
131
- half_width = (canvas_width - border_size) // 2
132
-
133
- img1 = resize_and_crop_to_fill(images[0], half_width, half_height, crop_position)
134
- img2 = resize_and_crop_to_fill(images[1], half_width, half_height, crop_position)
135
- img3 = resize_and_crop_to_fill(images[2], half_width, half_height, crop_position)
136
- img4 = resize_and_crop_to_fill(images[3], half_width, half_height, crop_position)
137
-
138
- canvas.paste(img1, (0, 0))
139
- canvas.paste(img2, (half_width + border_size, 0))
140
- canvas.paste(img3, (0, half_height + border_size))
141
- canvas.paste(img4, (half_width + border_size, half_height + border_size))
142
  elif num_images == 5:
143
- if is_web:
144
- # Web: 3 em cima, 2 embaixo
145
- top_height = (canvas_height - border_size) * 3 // 5
146
- bottom_height = canvas_height - top_height - border_size
147
- available_width = canvas_width - 2 * border_size
148
- third_width = available_width // 3
149
- third_width_last = canvas_width - (third_width * 2 + border_size * 2)
150
- half_width = (canvas_width - border_size) // 2
151
-
152
- # 3 imagens em cima
153
- img1 = resize_and_crop_to_fill(images[0], third_width, top_height, crop_position)
154
- img2 = resize_and_crop_to_fill(images[1], third_width, top_height, crop_position)
155
- img3 = resize_and_crop_to_fill(images[2], third_width_last, top_height, crop_position)
156
- canvas.paste(img1, (0, 0))
157
- canvas.paste(img2, (third_width + border_size, 0))
158
- canvas.paste(img3, (third_width * 2 + border_size * 2, 0))
159
-
160
- # 2 imagens embaixo
161
- y_offset = top_height + border_size
162
- img4 = resize_and_crop_to_fill(images[3], half_width, bottom_height, crop_position)
163
- img5 = resize_and_crop_to_fill(images[4], half_width, bottom_height, crop_position)
164
- canvas.paste(img4, (0, y_offset))
165
- canvas.paste(img5, (half_width + border_size, y_offset))
166
- else:
167
- # IG: layout original
168
- top_height = (canvas_height - border_size) * 2 // 5
169
- bottom_height = canvas_height - top_height - border_size
170
- half_width = (canvas_width - border_size) // 2
171
-
172
- img1 = resize_and_crop_to_fill(images[0], half_width, top_height, crop_position)
173
- img2 = resize_and_crop_to_fill(images[1], half_width, top_height, crop_position)
174
- canvas.paste(img1, (0, 0))
175
- canvas.paste(img2, (half_width + border_size, 0))
176
-
177
- y_offset = top_height + border_size
178
- third_width = (canvas_width - 2 * border_size) // 3
179
- third_width_last = canvas_width - (third_width * 2 + border_size * 2)
180
-
181
- img3 = resize_and_crop_to_fill(images[2], third_width, bottom_height, crop_position)
182
- img4 = resize_and_crop_to_fill(images[3], third_width, bottom_height, crop_position)
183
- img5 = resize_and_crop_to_fill(images[4], third_width_last, bottom_height, crop_position)
184
- canvas.paste(img3, (0, y_offset))
185
- canvas.paste(img4, (third_width + border_size, y_offset))
186
- canvas.paste(img5, (third_width * 2 + border_size * 2, y_offset))
187
  elif num_images == 6:
188
- if is_web:
189
- # Web: 3x2 (3 colunas, 2 linhas)
190
- half_height = (canvas_height - border_size) // 2
191
- available_width = canvas_width - 2 * border_size
192
- third_width = available_width // 3
193
- third_width_last = canvas_width - (third_width * 2 + border_size * 2)
194
-
195
- # Primeira linha
196
- img1 = resize_and_crop_to_fill(images[0], third_width, half_height, crop_position)
197
- img2 = resize_and_crop_to_fill(images[1], third_width, half_height, crop_position)
198
- img3 = resize_and_crop_to_fill(images[2], third_width_last, half_height, crop_position)
199
- canvas.paste(img1, (0, 0))
200
- canvas.paste(img2, (third_width + border_size, 0))
201
- canvas.paste(img3, (third_width * 2 + border_size * 2, 0))
202
-
203
- # Segunda linha
204
- y_offset = half_height + border_size
205
- img4 = resize_and_crop_to_fill(images[3], third_width, half_height, crop_position)
206
- img5 = resize_and_crop_to_fill(images[4], third_width, half_height, crop_position)
207
- img6 = resize_and_crop_to_fill(images[5], third_width_last, half_height, crop_position)
208
- canvas.paste(img4, (0, y_offset))
209
- canvas.paste(img5, (third_width + border_size, y_offset))
210
- canvas.paste(img6, (third_width * 2 + border_size * 2, y_offset))
211
- else:
212
- # IG: layout original (3x2)
213
- half_height = (canvas_height - border_size) // 2
214
- third_width = (canvas_width - 2 * border_size) // 3
215
- third_width_last = canvas_width - (third_width * 2 + border_size * 2)
216
-
217
- # Primeira linha
218
- img1 = resize_and_crop_to_fill(images[0], third_width, half_height, crop_position)
219
- img2 = resize_and_crop_to_fill(images[1], third_width, half_height, crop_position)
220
- img3 = resize_and_crop_to_fill(images[2], third_width, half_height, crop_position)
221
- canvas.paste(img1, (0, 0))
222
- canvas.paste(img2, (third_width + border_size, 0))
223
- canvas.paste(img3, (third_width * 2 + border_size * 2, 0))
224
-
225
- # Segunda linha
226
- y_offset = half_height + border_size
227
- img4 = resize_and_crop_to_fill(images[3], third_width, half_height, crop_position)
228
- img5 = resize_and_crop_to_fill(images[4], third_width, half_height, crop_position)
229
- img6 = resize_and_crop_to_fill(images[5], third_width_last, half_height, crop_position)
230
- canvas.paste(img4, (0, y_offset))
231
- canvas.paste(img5, (third_width + border_size, y_offset))
232
- canvas.paste(img6, (third_width * 2 + border_size * 2, y_offset))
233
  else:
234
  raise HTTPException(status_code=400, detail="Apenas até 6 imagens são suportadas.")
235
 
@@ -334,19 +263,6 @@ def get_responsive_font_and_lines(text: str, font_path: str, max_width: int, max
334
  lines = wrap_text(text, font, max_width, temp_draw)
335
  return font, lines, min_font_size
336
 
337
- def get_font_and_lines(text: str, font_path: str, font_size: int, max_width: int) -> tuple[ImageFont.FreeTypeFont, list[str]]:
338
- """ Retorna a fonte e linhas com tamanho fixo de fonte. """
339
- try:
340
- font = ImageFont.truetype(font_path, font_size)
341
- except Exception:
342
- font = ImageFont.load_default()
343
-
344
- temp_img = Image.new("RGB", (1, 1))
345
- temp_draw = ImageDraw.Draw(temp_img)
346
- lines = wrap_text(text, font, max_width, temp_draw)
347
-
348
- return font, lines
349
-
350
  def get_text_color_rgb(text_color: str) -> tuple[int, int, int]:
351
  """ Converte o parâmetro text_color para RGB. """
352
  if text_color.lower() == "black":
@@ -354,28 +270,25 @@ def get_text_color_rgb(text_color: str) -> tuple[int, int, int]:
354
  else: # white por padrão
355
  return (255, 255, 255)
356
 
357
- def get_device_dimensions(device: str) -> tuple[int, int]:
358
- """Retorna as dimensões baseadas no dispositivo"""
359
- if device.lower() == "web":
360
- return (1280, 720)
361
- else: # Instagram por padrão
362
- return (1080, 1350)
 
 
363
 
364
- def create_canvas(image_url: Optional[Union[str, List[str]]], headline: Optional[str], device: str = "ig", text_position: str = "bottom", text_color: str = "white", crop_position: str = "center") -> BytesIO:
365
- width, height = get_device_dimensions(device)
366
- is_web = device.lower() == "web"
367
  text_rgb = get_text_color_rgb(text_color)
368
 
369
- # Configurações específicas por dispositivo
370
- if is_web:
371
- padding_x = 40
372
- logo_width, logo_height = 120, 22
373
- logo_padding = 40
374
- else:
375
- padding_x = 60
376
- bottom_padding = 80
377
- top_padding = 60
378
- logo_width, logo_height = 121, 23 # Novas dimensões: L:121, A:22.75 (arredondado para 23)
379
 
380
  max_width = width - 2 * padding_x
381
  canvas = Image.new("RGBA", (width, height), color=(255, 255, 255, 255))
@@ -394,66 +307,95 @@ def create_canvas(image_url: Optional[Union[str, List[str]]], headline: Optional
394
  canvas.paste(filled_img, (0, 0))
395
  else:
396
  # Múltiplas imagens - criar colagem
397
- canvas = create_collage_background(parsed_urls, width, height, device, crop_position)
 
 
 
 
 
398
 
399
- # Para Instagram: adicionar gradiente e texto
400
- if not is_web:
401
- # aplicar gradiente se o texto for branco
402
- if text_color.lower() != "black":
403
- gradient_overlay = create_gradient_overlay(width, height, text_position)
404
- canvas = Image.alpha_composite(canvas, gradient_overlay)
405
 
406
- if headline:
407
- draw = ImageDraw.Draw(canvas)
408
- font_path = "fonts/AGaramondPro-Semibold.ttf"
409
- line_height_factor = 1.05 # 105% da altura da linha
410
-
411
- try:
412
- font, lines, font_size = get_responsive_font_and_lines(
413
- headline, font_path, max_width, max_lines=3, max_font_size=80, min_font_size=20
414
- )
415
- line_height = int(font_size * line_height_factor)
416
- except Exception as e:
417
- raise HTTPException(status_code=500, detail=f"Erro ao processar a fonte: {e}")
418
-
419
- total_text_height = len(lines) * line_height
 
 
 
 
 
 
 
 
420
 
421
- # Posicionar texto baseado no parâmetro text_position
422
- if text_position.lower() == "bottom":
423
- # Posicionar texto 50px acima da logo (que está em Y:1274)
424
- text_end_y = 1274 - 50
425
- start_y = text_end_y - total_text_height
426
- else: # text_position == "top"
427
- # Posicionar texto no topo com padding
 
 
 
428
  start_y = top_padding
429
-
430
- # Adicionar logo no canto inferior direito (posição fixa)
 
431
  try:
432
- logo_path = "recurve.png"
433
- logo = Image.open(logo_path).convert("RGBA")
434
- logo_resized = logo.resize((logo_width, logo_height))
 
 
 
435
 
436
- # Aplicar opacidade de 42%
437
- logo_with_opacity = Image.new("RGBA", logo_resized.size)
438
- for x in range(logo_resized.width):
439
- for y in range(logo_resized.height):
440
- r, g, b, a = logo_resized.getpixel((x, y))
441
- new_alpha = int(a * 0.42) # 42% de opacidade
442
- logo_with_opacity.putpixel((x, y), (r, g, b, new_alpha))
443
 
444
- # Posição fixa: X:891, Y:1274
445
- canvas.paste(logo_with_opacity, (891, 1274), logo_with_opacity)
 
446
  except Exception as e:
447
- raise HTTPException(status_code=500, detail=f"Erro ao carregar a logo: {e}")
 
 
 
 
 
 
448
 
449
- # Adiciona texto com a cor especificada
450
- for i, line in enumerate(lines):
451
- y = start_y + i * line_height
452
- draw.text((padding_x, y), line, font=font, fill=text_rgb)
453
-
454
- # Para web: apenas logo no canto inferior direito
455
- else:
456
- pass
 
 
 
 
 
 
 
 
 
457
 
458
  buffer = BytesIO()
459
  canvas.convert("RGB").save(buffer, format="PNG")
@@ -463,14 +405,15 @@ def create_canvas(image_url: Optional[Union[str, List[str]]], headline: Optional
463
  @router.get("/cover/news")
464
  def get_news_image(
465
  image_url: Optional[Union[str, List[str]]] = Query(None, description="URL da imagem ou lista de URLs separadas por vírgula para colagem (máximo 6)"),
466
- headline: Optional[str] = Query(None, description="Texto do título (opcional para IG, ignorado para web)"),
467
- device: str = Query("ig", description="Dispositivo: 'ig' para Instagram (1080x1350) ou 'web' para Web (1280x720)"),
468
  text_position: str = Query("bottom", description="Posição do texto: 'top' para topo ou 'bottom' para parte inferior"),
469
  text_color: str = Query("white", description="Cor do texto: 'white' (padrão) ou 'black'. Se 'black', remove o gradiente de fundo"),
470
- crop_position: str = Query("center", description="Posição do enquadramento da imagem: 'center' (padrão), 'top', 'bottom', 'left', 'right'")
 
 
471
  ):
472
  try:
473
- buffer = create_canvas(image_url, headline, device, text_position, text_color, crop_position)
474
  return StreamingResponse(buffer, media_type="image/png")
475
  except Exception as e:
476
  raise HTTPException(status_code=500, detail=f"Erro ao gerar imagem: {str(e)}")
 
72
 
73
  return img_resized.crop((left, top, right, bottom))
74
 
75
+ def create_collage_background(image_urls: List[str], canvas_width: int, canvas_height: int, crop_position: str = "center") -> Image.Image:
76
  """Cria uma colagem como fundo baseada na lista de URLs"""
77
  num_images = len(image_urls)
78
  border_size = 4 if num_images > 1 else 0 # Linha mais fina e elegante
 
79
 
80
  images = [download_image_from_url(url) for url in image_urls]
81
  canvas = Image.new("RGBA", (canvas_width, canvas_height), (255, 255, 255, 255))
 
84
  img = resize_and_crop_to_fill(images[0], canvas_width, canvas_height, crop_position)
85
  canvas.paste(img, (0, 0))
86
  elif num_images == 2:
87
+ # Lado a lado
88
  slot_width = (canvas_width - border_size) // 2
89
  img1 = resize_and_crop_to_fill(images[0], slot_width, canvas_height, crop_position)
90
  img2 = resize_and_crop_to_fill(images[1], slot_width, canvas_height, crop_position)
91
  canvas.paste(img1, (0, 0))
92
  canvas.paste(img2, (slot_width + border_size, 0))
93
  elif num_images == 3:
94
+ # Layout original IG
95
+ half_height = (canvas_height - border_size) // 2
96
+ half_width = (canvas_width - border_size) // 2
97
+
98
+ img1 = resize_and_crop_to_fill(images[0], half_width, half_height, crop_position)
99
+ img2 = resize_and_crop_to_fill(images[1], half_width, half_height, crop_position)
100
+ img3 = resize_and_crop_to_fill(images[2], canvas_width, half_height, crop_position)
101
+
102
+ canvas.paste(img1, (0, 0))
103
+ canvas.paste(img2, (half_width + border_size, 0))
104
+ canvas.paste(img3, (0, half_height + border_size))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  elif num_images == 4:
106
+ # Layout 2x2
107
+ half_height = (canvas_height - border_size) // 2
108
+ half_width = (canvas_width - border_size) // 2
109
+
110
+ img1 = resize_and_crop_to_fill(images[0], half_width, half_height, crop_position)
111
+ img2 = resize_and_crop_to_fill(images[1], half_width, half_height, crop_position)
112
+ img3 = resize_and_crop_to_fill(images[2], half_width, half_height, crop_position)
113
+ img4 = resize_and_crop_to_fill(images[3], half_width, half_height, crop_position)
114
+
115
+ canvas.paste(img1, (0, 0))
116
+ canvas.paste(img2, (half_width + border_size, 0))
117
+ canvas.paste(img3, (0, half_height + border_size))
118
+ canvas.paste(img4, (half_width + border_size, half_height + border_size))
 
 
 
 
 
 
 
 
119
  elif num_images == 5:
120
+ # Layout original IG
121
+ top_height = (canvas_height - border_size) * 2 // 5
122
+ bottom_height = canvas_height - top_height - border_size
123
+ half_width = (canvas_width - border_size) // 2
124
+
125
+ img1 = resize_and_crop_to_fill(images[0], half_width, top_height, crop_position)
126
+ img2 = resize_and_crop_to_fill(images[1], half_width, top_height, crop_position)
127
+ canvas.paste(img1, (0, 0))
128
+ canvas.paste(img2, (half_width + border_size, 0))
129
+
130
+ y_offset = top_height + border_size
131
+ third_width = (canvas_width - 2 * border_size) // 3
132
+ third_width_last = canvas_width - (third_width * 2 + border_size * 2)
133
+
134
+ img3 = resize_and_crop_to_fill(images[2], third_width, bottom_height, crop_position)
135
+ img4 = resize_and_crop_to_fill(images[3], third_width, bottom_height, crop_position)
136
+ img5 = resize_and_crop_to_fill(images[4], third_width_last, bottom_height, crop_position)
137
+ canvas.paste(img3, (0, y_offset))
138
+ canvas.paste(img4, (third_width + border_size, y_offset))
139
+ canvas.paste(img5, (third_width * 2 + border_size * 2, y_offset))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  elif num_images == 6:
141
+ # Layout original IG (3x2)
142
+ half_height = (canvas_height - border_size) // 2
143
+ third_width = (canvas_width - 2 * border_size) // 3
144
+ third_width_last = canvas_width - (third_width * 2 + border_size * 2)
145
+
146
+ # Primeira linha
147
+ img1 = resize_and_crop_to_fill(images[0], third_width, half_height, crop_position)
148
+ img2 = resize_and_crop_to_fill(images[1], third_width, half_height, crop_position)
149
+ img3 = resize_and_crop_to_fill(images[2], third_width, half_height, crop_position)
150
+ canvas.paste(img1, (0, 0))
151
+ canvas.paste(img2, (third_width + border_size, 0))
152
+ canvas.paste(img3, (third_width * 2 + border_size * 2, 0))
153
+
154
+ # Segunda linha
155
+ y_offset = half_height + border_size
156
+ img4 = resize_and_crop_to_fill(images[3], third_width, half_height, crop_position)
157
+ img5 = resize_and_crop_to_fill(images[4], third_width, half_height, crop_position)
158
+ img6 = resize_and_crop_to_fill(images[5], third_width_last, half_height, crop_position)
159
+ canvas.paste(img4, (0, y_offset))
160
+ canvas.paste(img5, (third_width + border_size, y_offset))
161
+ canvas.paste(img6, (third_width * 2 + border_size * 2, y_offset))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  else:
163
  raise HTTPException(status_code=400, detail="Apenas até 6 imagens são suportadas.")
164
 
 
263
  lines = wrap_text(text, font, max_width, temp_draw)
264
  return font, lines, min_font_size
265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  def get_text_color_rgb(text_color: str) -> tuple[int, int, int]:
267
  """ Converte o parâmetro text_color para RGB. """
268
  if text_color.lower() == "black":
 
270
  else: # white por padrão
271
  return (255, 255, 255)
272
 
273
+ def get_category_image_path(category: str) -> str:
274
+ """Retorna o caminho da imagem baseado na categoria"""
275
+ valid_categories = ["person", "movie", "series", "place", "event", "other"]
276
+
277
+ if category.lower() in valid_categories:
278
+ return f"{category.lower()}.png"
279
+ else:
280
+ return "other.png" # Default para other
281
 
282
+ def create_canvas(image_url: Optional[Union[str, List[str]]], headline: Optional[str], text_position: str = "bottom", text_color: str = "white", crop_position: str = "center", breaking_news: bool = False, category: str = "other") -> BytesIO:
283
+ # Dimensões fixas do Instagram
284
+ width, height = 1080, 1350
285
  text_rgb = get_text_color_rgb(text_color)
286
 
287
+ # Configurações do Instagram
288
+ padding_x = 60
289
+ bottom_padding = 80
290
+ top_padding = 60
291
+ logo_width, logo_height = 121, 23
 
 
 
 
 
292
 
293
  max_width = width - 2 * padding_x
294
  canvas = Image.new("RGBA", (width, height), color=(255, 255, 255, 255))
 
307
  canvas.paste(filled_img, (0, 0))
308
  else:
309
  # Múltiplas imagens - criar colagem
310
+ canvas = create_collage_background(parsed_urls, width, height, crop_position)
311
+
312
+ # Adicionar gradiente se o texto for branco
313
+ if text_color.lower() != "black":
314
+ gradient_overlay = create_gradient_overlay(width, height, text_position)
315
+ canvas = Image.alpha_composite(canvas, gradient_overlay)
316
 
317
+ if headline:
318
+ draw = ImageDraw.Draw(canvas)
319
+ font_path = "fonts/AGaramondPro-Semibold.ttf"
320
+ line_height_factor = 1.05 # 105% da altura da linha
 
 
321
 
322
+ try:
323
+ font, lines, font_size = get_responsive_font_and_lines(
324
+ headline, font_path, max_width, max_lines=3, max_font_size=80, min_font_size=20
325
+ )
326
+ line_height = int(font_size * line_height_factor)
327
+ except Exception as e:
328
+ raise HTTPException(status_code=500, detail=f"Erro ao processar a fonte: {e}")
329
+
330
+ total_text_height = len(lines) * line_height
331
+
332
+ # Calcular espaço necessário para imagem de categoria/breaking
333
+ category_img_height = 0
334
+ category_spacing = 0
335
+ if breaking_news or category:
336
+ category_img_height = 46 # altura da imagem
337
+ category_spacing = 38 # separação entre imagem e título
338
+
339
+ # Posicionar texto baseado no parâmetro text_position
340
+ if text_position.lower() == "bottom":
341
+ # Posicionar texto 50px acima da logo (que está em Y:1274)
342
+ text_end_y = 1274 - 50
343
+ start_y = text_end_y - total_text_height
344
 
345
+ # Se imagem de categoria, ajustar posição para dar espaço
346
+ if breaking_news or category:
347
+ category_y = start_y - category_spacing - category_img_height
348
+ else: # text_position == "top"
349
+ # Posicionar texto no topo com padding
350
+ if breaking_news or category:
351
+ # Começar com imagem de categoria no topo
352
+ category_y = top_padding
353
+ start_y = category_y + category_img_height + category_spacing
354
+ else:
355
  start_y = top_padding
356
+
357
+ # Adicionar imagem de categoria/breaking se necessário
358
+ if breaking_news or category:
359
  try:
360
+ if breaking_news:
361
+ # Breaking news tem prioridade sobre category
362
+ img_path = "breaking.png"
363
+ else:
364
+ # Usar imagem da categoria
365
+ img_path = get_category_image_path(category)
366
 
367
+ category_img = Image.open(img_path).convert("RGBA")
368
+ category_resized = category_img.resize((200, 46))
 
 
 
 
 
369
 
370
+ # Posicionar no canto esquerdo, alinhado com o início do título
371
+ category_x = padding_x
372
+ canvas.paste(category_resized, (category_x, category_y), category_resized)
373
  except Exception as e:
374
+ raise HTTPException(status_code=500, detail=f"Erro ao carregar a imagem de categoria: {e}")
375
+
376
+ # Adicionar logo no canto inferior direito (posição fixa)
377
+ try:
378
+ logo_path = "recurve.png"
379
+ logo = Image.open(logo_path).convert("RGBA")
380
+ logo_resized = logo.resize((logo_width, logo_height))
381
 
382
+ # Aplicar opacidade de 42%
383
+ logo_with_opacity = Image.new("RGBA", logo_resized.size)
384
+ for x in range(logo_resized.width):
385
+ for y in range(logo_resized.height):
386
+ r, g, b, a = logo_resized.getpixel((x, y))
387
+ new_alpha = int(a * 0.42) # 42% de opacidade
388
+ logo_with_opacity.putpixel((x, y), (r, g, b, new_alpha))
389
+
390
+ # Posição fixa: X:891, Y:1274
391
+ canvas.paste(logo_with_opacity, (891, 1274), logo_with_opacity)
392
+ except Exception as e:
393
+ raise HTTPException(status_code=500, detail=f"Erro ao carregar a logo: {e}")
394
+
395
+ # Adiciona texto com a cor especificada
396
+ for i, line in enumerate(lines):
397
+ y = start_y + i * line_height
398
+ draw.text((padding_x, y), line, font=font, fill=text_rgb)
399
 
400
  buffer = BytesIO()
401
  canvas.convert("RGB").save(buffer, format="PNG")
 
405
  @router.get("/cover/news")
406
  def get_news_image(
407
  image_url: Optional[Union[str, List[str]]] = Query(None, description="URL da imagem ou lista de URLs separadas por vírgula para colagem (máximo 6)"),
408
+ headline: Optional[str] = Query(None, description="Texto do título"),
 
409
  text_position: str = Query("bottom", description="Posição do texto: 'top' para topo ou 'bottom' para parte inferior"),
410
  text_color: str = Query("white", description="Cor do texto: 'white' (padrão) ou 'black'. Se 'black', remove o gradiente de fundo"),
411
+ crop_position: str = Query("center", description="Posição do enquadramento da imagem: 'center' (padrão), 'top', 'bottom', 'left', 'right'"),
412
+ breaking_news: bool = Query(False, description="Se true, mostra a imagem breaking.png acima do título (tem prioridade sobre category)"),
413
+ category: str = Query("other", description="Categoria da notícia: 'person', 'movie', 'series', 'place', 'event', 'other' (padrão)")
414
  ):
415
  try:
416
+ buffer = create_canvas(image_url, headline, text_position, text_color, crop_position, breaking_news, category)
417
  return StreamingResponse(buffer, media_type="image/png")
418
  except Exception as e:
419
  raise HTTPException(status_code=500, detail=f"Erro ao gerar imagem: {str(e)}")