habulaj commited on
Commit
61d2da5
·
verified ·
1 Parent(s): 59d001e

Update routers/news.py

Browse files
Files changed (1) hide show
  1. routers/news.py +195 -7
routers/news.py CHANGED
@@ -3,7 +3,7 @@ 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
 
@@ -42,6 +42,184 @@ def resize_and_crop_to_fill(img: Image.Image, target_width: int, 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
@@ -175,7 +353,7 @@ def get_device_dimensions(device: str) -> tuple[int, int]:
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"
@@ -196,11 +374,21 @@ def create_canvas(image_url: Optional[str], headline: Optional[str], device: str
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:
@@ -278,7 +466,7 @@ def create_canvas(image_url: Optional[str], headline: Optional[str], device: str
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"),
 
3
  from PIL import Image, ImageDraw, ImageFont
4
  from io import BytesIO
5
  import requests
6
+ from typing import Optional, List, Union
7
 
8
  router = APIRouter()
9
 
 
42
 
43
  return img_resized.crop((left, top, right, bottom))
44
 
45
+ def create_collage_background(image_urls: List[str], canvas_width: int, canvas_height: int, device: str) -> Image.Image:
46
+ """Cria uma colagem como fundo baseada na lista de URLs"""
47
+ num_images = len(image_urls)
48
+ border_size = 12 if num_images > 1 else 0
49
+ is_web = device.lower() == "web"
50
+
51
+ images = [download_image_from_url(url) for url in image_urls]
52
+ canvas = Image.new("RGBA", (canvas_width, canvas_height), (255, 255, 255, 255))
53
+
54
+ if num_images == 1:
55
+ img = resize_and_crop_to_fill(images[0], canvas_width, canvas_height)
56
+ canvas.paste(img, (0, 0))
57
+
58
+ elif num_images == 2:
59
+ # Ambos dispositivos: lado a lado
60
+ slot_width = (canvas_width - border_size) // 2
61
+ img1 = resize_and_crop_to_fill(images[0], slot_width, canvas_height)
62
+ img2 = resize_and_crop_to_fill(images[1], slot_width, canvas_height)
63
+ canvas.paste(img1, (0, 0))
64
+ canvas.paste(img2, (slot_width + border_size, 0))
65
+
66
+ elif num_images == 3:
67
+ if is_web:
68
+ # Web: 1 grande à esquerda, 2 pequenas empilhadas à direita
69
+ left_width = (canvas_width - border_size) * 2 // 3
70
+ right_width = canvas_width - left_width - border_size
71
+ half_height = (canvas_height - border_size) // 2
72
+
73
+ img1 = resize_and_crop_to_fill(images[0], left_width, canvas_height)
74
+ img2 = resize_and_crop_to_fill(images[1], right_width, half_height)
75
+ img3 = resize_and_crop_to_fill(images[2], right_width, half_height)
76
+
77
+ canvas.paste(img1, (0, 0))
78
+ canvas.paste(img2, (left_width + border_size, 0))
79
+ canvas.paste(img3, (left_width + border_size, half_height + border_size))
80
+ else:
81
+ # IG: layout original
82
+ half_height = (canvas_height - border_size) // 2
83
+ half_width = (canvas_width - border_size) // 2
84
+ img1 = resize_and_crop_to_fill(images[0], half_width, half_height)
85
+ img2 = resize_and_crop_to_fill(images[1], half_width, half_height)
86
+ img3 = resize_and_crop_to_fill(images[2], canvas_width, half_height)
87
+ canvas.paste(img1, (0, 0))
88
+ canvas.paste(img2, (half_width + border_size, 0))
89
+ canvas.paste(img3, (0, half_height + border_size))
90
+
91
+ elif num_images == 4:
92
+ if is_web:
93
+ # Web: 4 imagens lado a lado horizontalmente
94
+ slot_width = (canvas_width - 3 * border_size) // 4
95
+ for i in range(4):
96
+ img = resize_and_crop_to_fill(images[i], slot_width, canvas_height)
97
+ x_pos = i * (slot_width + border_size)
98
+ canvas.paste(img, (x_pos, 0))
99
+ else:
100
+ # IG: Layout 2x2
101
+ half_height = (canvas_height - border_size) // 2
102
+ half_width = (canvas_width - border_size) // 2
103
+ img1 = resize_and_crop_to_fill(images[0], half_width, half_height)
104
+ img2 = resize_and_crop_to_fill(images[1], half_width, half_height)
105
+ img3 = resize_and_crop_to_fill(images[2], half_width, half_height)
106
+ img4 = resize_and_crop_to_fill(images[3], half_width, half_height)
107
+ canvas.paste(img1, (0, 0))
108
+ canvas.paste(img2, (half_width + border_size, 0))
109
+ canvas.paste(img3, (0, half_height + border_size))
110
+ canvas.paste(img4, (half_width + border_size, half_height + border_size))
111
+
112
+ elif num_images == 5:
113
+ if is_web:
114
+ # Web: 3 em cima, 2 embaixo
115
+ top_height = (canvas_height - border_size) * 3 // 5
116
+ bottom_height = canvas_height - top_height - border_size
117
+
118
+ available_width = canvas_width - 2 * border_size
119
+ third_width = available_width // 3
120
+ third_width_last = canvas_width - (third_width * 2 + border_size * 2)
121
+ half_width = (canvas_width - border_size) // 2
122
+
123
+ # 3 imagens em cima
124
+ img1 = resize_and_crop_to_fill(images[0], third_width, top_height)
125
+ img2 = resize_and_crop_to_fill(images[1], third_width, top_height)
126
+ img3 = resize_and_crop_to_fill(images[2], third_width_last, top_height)
127
+ canvas.paste(img1, (0, 0))
128
+ canvas.paste(img2, (third_width + border_size, 0))
129
+ canvas.paste(img3, (third_width * 2 + border_size * 2, 0))
130
+
131
+ # 2 imagens embaixo
132
+ y_offset = top_height + border_size
133
+ img4 = resize_and_crop_to_fill(images[3], half_width, bottom_height)
134
+ img5 = resize_and_crop_to_fill(images[4], half_width, bottom_height)
135
+ canvas.paste(img4, (0, y_offset))
136
+ canvas.paste(img5, (half_width + border_size, y_offset))
137
+ else:
138
+ # IG: layout original
139
+ top_height = (canvas_height - border_size) * 2 // 5
140
+ bottom_height = canvas_height - top_height - border_size
141
+ half_width = (canvas_width - border_size) // 2
142
+
143
+ img1 = resize_and_crop_to_fill(images[0], half_width, top_height)
144
+ img2 = resize_and_crop_to_fill(images[1], half_width, top_height)
145
+ canvas.paste(img1, (0, 0))
146
+ canvas.paste(img2, (half_width + border_size, 0))
147
+
148
+ y_offset = top_height + border_size
149
+ third_width = (canvas_width - 2 * border_size) // 3
150
+ third_width_last = canvas_width - (third_width * 2 + border_size * 2)
151
+
152
+ img3 = resize_and_crop_to_fill(images[2], third_width, bottom_height)
153
+ img4 = resize_and_crop_to_fill(images[3], third_width, bottom_height)
154
+ img5 = resize_and_crop_to_fill(images[4], third_width_last, bottom_height)
155
+ canvas.paste(img3, (0, y_offset))
156
+ canvas.paste(img4, (third_width + border_size, y_offset))
157
+ canvas.paste(img5, (third_width * 2 + border_size * 2, y_offset))
158
+
159
+ elif num_images == 6:
160
+ if is_web:
161
+ # Web: 3x2 (3 colunas, 2 linhas)
162
+ half_height = (canvas_height - border_size) // 2
163
+ available_width = canvas_width - 2 * border_size
164
+ third_width = available_width // 3
165
+ third_width_last = canvas_width - (third_width * 2 + border_size * 2)
166
+
167
+ # Primeira linha
168
+ img1 = resize_and_crop_to_fill(images[0], third_width, half_height)
169
+ img2 = resize_and_crop_to_fill(images[1], third_width, half_height)
170
+ img3 = resize_and_crop_to_fill(images[2], third_width_last, half_height)
171
+ canvas.paste(img1, (0, 0))
172
+ canvas.paste(img2, (third_width + border_size, 0))
173
+ canvas.paste(img3, (third_width * 2 + border_size * 2, 0))
174
+
175
+ # Segunda linha
176
+ y_offset = half_height + border_size
177
+ img4 = resize_and_crop_to_fill(images[3], third_width, half_height)
178
+ img5 = resize_and_crop_to_fill(images[4], third_width, half_height)
179
+ img6 = resize_and_crop_to_fill(images[5], third_width_last, half_height)
180
+ canvas.paste(img4, (0, y_offset))
181
+ canvas.paste(img5, (third_width + border_size, y_offset))
182
+ canvas.paste(img6, (third_width * 2 + border_size * 2, y_offset))
183
+ else:
184
+ # IG: layout original (3x2)
185
+ half_height = (canvas_height - border_size) // 2
186
+ third_width = (canvas_width - 2 * border_size) // 3
187
+ third_width_last = canvas_width - (third_width * 2 + border_size * 2)
188
+
189
+ # Primeira linha
190
+ img1 = resize_and_crop_to_fill(images[0], third_width, half_height)
191
+ img2 = resize_and_crop_to_fill(images[1], third_width, half_height)
192
+ img3 = resize_and_crop_to_fill(images[2], third_width, half_height)
193
+ canvas.paste(img1, (0, 0))
194
+ canvas.paste(img2, (third_width + border_size, 0))
195
+ canvas.paste(img3, (third_width * 2 + border_size * 2, 0))
196
+
197
+ # Segunda linha
198
+ y_offset = half_height + border_size
199
+ img4 = resize_and_crop_to_fill(images[3], third_width, half_height)
200
+ img5 = resize_and_crop_to_fill(images[4], third_width, half_height)
201
+ img6 = resize_and_crop_to_fill(images[5], third_width_last, half_height)
202
+ canvas.paste(img4, (0, y_offset))
203
+ canvas.paste(img5, (third_width + border_size, y_offset))
204
+ canvas.paste(img6, (third_width * 2 + border_size * 2, y_offset))
205
+
206
+ else:
207
+ raise HTTPException(status_code=400, detail="Apenas até 6 imagens são suportadas.")
208
+
209
+ return canvas
210
+
211
+ def parse_image_urls(image_url_param: Union[str, List[str]]) -> List[str]:
212
+ """Converte o parâmetro de URL(s) em lista de URLs"""
213
+ if isinstance(image_url_param, list):
214
+ return image_url_param
215
+ elif isinstance(image_url_param, str):
216
+ # Se contém vírgulas, divide em múltiplas URLs
217
+ if ',' in image_url_param:
218
+ return [url.strip() for url in image_url_param.split(',') if url.strip()]
219
+ else:
220
+ return [image_url_param]
221
+ return []
222
+
223
  def create_gradient_overlay(width: int, height: int, text_position: str = "bottom") -> Image.Image:
224
  """
225
  Cria gradiente overlay baseado na posição do texto
 
353
  else: # Instagram por padrão
354
  return (1080, 1350)
355
 
356
+ def create_canvas(image_url: Optional[Union[str, List[str]]], headline: Optional[str], device: str = "ig",
357
  text_position: str = "bottom", text_color: str = "white") -> BytesIO:
358
  width, height = get_device_dimensions(device)
359
  is_web = device.lower() == "web"
 
374
 
375
  canvas = Image.new("RGBA", (width, height), color=(255, 255, 255, 255))
376
 
377
+ # Adicionar imagem(s) de fundo se fornecida(s)
378
  if image_url:
379
+ parsed_urls = parse_image_urls(image_url)
380
+ if parsed_urls:
381
+ if len(parsed_urls) > 6:
382
+ raise HTTPException(status_code=400, detail="Máximo de 6 imagens permitidas")
383
+
384
+ if len(parsed_urls) == 1:
385
+ # Uma única imagem - comportamento original
386
+ img = download_image_from_url(parsed_urls[0])
387
+ filled_img = resize_and_crop_to_fill(img, width, height)
388
+ canvas.paste(filled_img, (0, 0))
389
+ else:
390
+ # Múltiplas imagens - criar colagem
391
+ canvas = create_collage_background(parsed_urls, width, height, device)
392
 
393
  # Para Instagram: adicionar gradiente e texto
394
  if not is_web:
 
466
 
467
  @router.get("/cover/news")
468
  def get_news_image(
469
+ 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)"),
470
  headline: Optional[str] = Query(None, description="Texto do título (opcional para IG, ignorado para web)"),
471
  device: str = Query("ig", description="Dispositivo: 'ig' para Instagram (1080x1350) ou 'web' para Web (1280x720)"),
472
  text_position: str = Query("bottom", description="Posição do texto: 'top' para topo ou 'bottom' para parte inferior"),