habulaj commited on
Commit
3de1d5c
·
verified ·
1 Parent(s): c67482c

Update routers/video.py

Browse files
Files changed (1) hide show
  1. routers/video.py +253 -3
routers/video.py CHANGED
@@ -1,13 +1,220 @@
1
- from fastapi import APIRouter, HTTPException
2
  from fastapi.responses import StreamingResponse
3
- from PIL import Image
4
  from io import BytesIO
5
  import requests
 
6
 
7
  router = APIRouter()
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  @router.get("/cover/video")
10
- def get_video_cover():
 
 
11
  """
12
  Endpoint que retorna uma imagem de fundo com dimensões 1080x1920.
13
  """
@@ -56,6 +263,49 @@ def get_video_cover():
56
  if background_image.mode != 'RGB':
57
  background_image = background_image.convert('RGB')
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  # Converter para BytesIO
60
  buffer = BytesIO()
61
  background_image.save(buffer, format="JPEG", quality=95)
 
1
+ from fastapi import APIRouter, HTTPException, Query
2
  from fastapi.responses import StreamingResponse
3
+ from PIL import Image, ImageDraw, ImageFont, ImageFilter
4
  from io import BytesIO
5
  import requests
6
+ from typing import Optional
7
 
8
  router = APIRouter()
9
 
10
+ def render_headline_text(draw: ImageDraw.Draw, text: str, x: int, y: int, max_width: int, max_lines: int = 5) -> None:
11
+ """
12
+ Renderiza headline com fonte Montserrat-ExtraBold.
13
+
14
+ Args:
15
+ draw: Objeto ImageDraw para desenhar
16
+ text: Texto do headline
17
+ x: Posição X
18
+ y: Posição Y
19
+ max_width: Largura máxima do texto
20
+ max_lines: Número máximo de linhas (padrão: 5)
21
+ """
22
+ if not text.strip():
23
+ return
24
+
25
+ # Carregar fonte Montserrat-ExtraBold
26
+ try:
27
+ font_path = "fonts/Montserrat-ExtraBold.ttf"
28
+ base_font = ImageFont.truetype(font_path, 70)
29
+ except (OSError, IOError):
30
+ # Fallback para fonte padrão se não encontrar a fonte
31
+ base_font = ImageFont.load_default()
32
+
33
+ # Dividir texto em palavras
34
+ words = text.split()
35
+ if not words:
36
+ return
37
+
38
+ # Função para quebrar texto em linhas
39
+ def wrap_text(text, font_size, max_width):
40
+ try:
41
+ test_font = ImageFont.truetype(font_path, font_size)
42
+ except (OSError, IOError):
43
+ test_font = ImageFont.load_default()
44
+
45
+ lines = []
46
+ current_line = []
47
+
48
+ for word in words:
49
+ test_line = " ".join(current_line + [word])
50
+ bbox = draw.textbbox((0, 0), test_line, font=test_font)
51
+ line_width = bbox[2] - bbox[0]
52
+
53
+ if line_width <= max_width:
54
+ current_line.append(word)
55
+ else:
56
+ if current_line:
57
+ lines.append(" ".join(current_line))
58
+ current_line = [word]
59
+ else:
60
+ # Palavra muito longa, adiciona mesmo assim
61
+ lines.append(word)
62
+
63
+ if current_line:
64
+ lines.append(" ".join(current_line))
65
+
66
+ return lines
67
+
68
+ # Encontrar o tamanho de fonte ideal
69
+ font_size = 70 # Tamanho inicial
70
+ min_font_size = 20 # Tamanho mínimo
71
+
72
+ # Tentar diferentes tamanhos de fonte até encontrar um que caiba em max_lines
73
+ while font_size >= min_font_size:
74
+ lines = wrap_text(text, font_size, max_width)
75
+
76
+ # Se o texto cabe em max_lines ou menos, usar este tamanho
77
+ if len(lines) <= max_lines:
78
+ break
79
+
80
+ # Reduzir o tamanho da fonte
81
+ font_size -= 2
82
+
83
+ # Garantir que não seja menor que o tamanho mínimo
84
+ font_size = max(font_size, min_font_size)
85
+
86
+ # Carregar fonte final
87
+ try:
88
+ final_font = ImageFont.truetype(font_path, font_size)
89
+ except (OSError, IOError):
90
+ final_font = ImageFont.load_default()
91
+
92
+ # Quebrar texto final
93
+ lines = wrap_text(text, font_size, max_width)
94
+
95
+ # Se ainda não couber em max_lines, forçar quebra nas primeiras max_lines
96
+ if len(lines) > max_lines:
97
+ combined_text = " ".join(lines[:max_lines-1] + [" ".join(lines[max_lines-1:])])
98
+ lines = wrap_text(combined_text, font_size, max_width)
99
+ lines = lines[:max_lines]
100
+
101
+ # Calcular altura total do texto
102
+ line_heights = []
103
+ for line in lines:
104
+ bbox = draw.textbbox((0, 0), line, font=final_font)
105
+ line_height = bbox[3] - bbox[1]
106
+ line_heights.append(line_height)
107
+
108
+ # Calcular posição inicial (Y é a posição da primeira linha)
109
+ current_y = y
110
+
111
+ # Desenhar cada linha
112
+ for i, line in enumerate(lines):
113
+ line_height = line_heights[i]
114
+
115
+ # Desenhar texto principal (branco)
116
+ draw.text((x, current_y), line, fill=(0, 0, 0, 89), font=final_font) # Texto com opacidade para sombra
117
+
118
+ # Atualizar posição para próxima linha
119
+ current_y += int(line_height * 1.2) # Line height de 120%
120
+
121
+ def render_headline_text_white(draw: ImageDraw.Draw, text: str, x: int, y: int, max_width: int, max_lines: int = 5) -> None:
122
+ """
123
+ Renderiza headline branco com fonte Montserrat-ExtraBold.
124
+ """
125
+ if not text.strip():
126
+ return
127
+
128
+ # Carregar fonte Montserrat-ExtraBold
129
+ try:
130
+ font_path = "fonts/Montserrat-ExtraBold.ttf"
131
+ except (OSError, IOError):
132
+ font_path = None
133
+
134
+ # Dividir texto em palavras
135
+ words = text.split()
136
+ if not words:
137
+ return
138
+
139
+ # Função para quebrar texto em linhas
140
+ def wrap_text(text, font_size, max_width):
141
+ try:
142
+ test_font = ImageFont.truetype(font_path, font_size)
143
+ except (OSError, IOError):
144
+ test_font = ImageFont.load_default()
145
+
146
+ lines = []
147
+ current_line = []
148
+
149
+ for word in words:
150
+ test_line = " ".join(current_line + [word])
151
+ bbox = draw.textbbox((0, 0), test_line, font=test_font)
152
+ line_width = bbox[2] - bbox[0]
153
+
154
+ if line_width <= max_width:
155
+ current_line.append(word)
156
+ else:
157
+ if current_line:
158
+ lines.append(" ".join(current_line))
159
+ current_line = [word]
160
+ else:
161
+ lines.append(word)
162
+
163
+ if current_line:
164
+ lines.append(" ".join(current_line))
165
+
166
+ return lines
167
+
168
+ # Encontrar o tamanho de fonte ideal
169
+ font_size = 70
170
+ min_font_size = 20
171
+
172
+ while font_size >= min_font_size:
173
+ lines = wrap_text(text, font_size, max_width)
174
+ if len(lines) <= max_lines:
175
+ break
176
+ font_size -= 2
177
+
178
+ font_size = max(font_size, min_font_size)
179
+
180
+ # Carregar fonte final
181
+ try:
182
+ final_font = ImageFont.truetype(font_path, font_size)
183
+ except (OSError, IOError):
184
+ final_font = ImageFont.load_default()
185
+
186
+ # Quebrar texto final
187
+ lines = wrap_text(text, font_size, max_width)
188
+
189
+ if len(lines) > max_lines:
190
+ combined_text = " ".join(lines[:max_lines-1] + [" ".join(lines[max_lines-1:])])
191
+ lines = wrap_text(combined_text, font_size, max_width)
192
+ lines = lines[:max_lines]
193
+
194
+ # Calcular altura total do texto
195
+ line_heights = []
196
+ for line in lines:
197
+ bbox = draw.textbbox((0, 0), line, font=final_font)
198
+ line_height = bbox[3] - bbox[1]
199
+ line_heights.append(line_height)
200
+
201
+ # Calcular posição inicial
202
+ current_y = y
203
+
204
+ # Desenhar cada linha (branco)
205
+ for i, line in enumerate(lines):
206
+ line_height = line_heights[i]
207
+
208
+ # Desenhar texto principal (branco)
209
+ draw.text((x, current_y), line, fill=(255, 255, 255, 255), font=final_font)
210
+
211
+ # Atualizar posição para próxima linha
212
+ current_y += int(line_height * 1.2)
213
+
214
  @router.get("/cover/video")
215
+ def get_video_cover(
216
+ headline: Optional[str] = Query(None, description="Headline a ser exibido")
217
+ ):
218
  """
219
  Endpoint que retorna uma imagem de fundo com dimensões 1080x1920.
220
  """
 
263
  if background_image.mode != 'RGB':
264
  background_image = background_image.convert('RGB')
265
 
266
+ # Adicionar headline se fornecido
267
+ if headline and headline.strip():
268
+ # Converter para RGBA para suportar transparência na sombra
269
+ background_image = background_image.convert('RGBA')
270
+
271
+ # Criar uma nova imagem para o texto com transparência
272
+ text_img = Image.new('RGBA', background_image.size, (0, 0, 0, 0))
273
+ text_draw = ImageDraw.Draw(text_img)
274
+
275
+ # Renderizar o texto na imagem temporária (para sombra)
276
+ render_headline_text(
277
+ text_draw,
278
+ headline,
279
+ x=180,
280
+ y=945,
281
+ max_width=720,
282
+ max_lines=5
283
+ )
284
+
285
+ # Aplicar desfoque na sombra
286
+ shadow_img = text_img.copy()
287
+ shadow_img = shadow_img.filter(ImageFilter.GaussianBlur(radius=18))
288
+
289
+ # Aplicar offset de 3,3 na sombra
290
+ shadow_offset = Image.new('RGBA', background_image.size, (0, 0, 0, 0))
291
+ shadow_offset.paste(shadow_img, (3, 3))
292
+
293
+ # Combinar sombra com imagem de fundo
294
+ background_image = Image.alpha_composite(background_image, shadow_offset)
295
+
296
+ # Criar imagem para texto principal (branco)
297
+ text_white_img = Image.new('RGBA', background_image.size, (0, 0, 0, 0))
298
+ text_white_draw = ImageDraw.Draw(text_white_img)
299
+
300
+ # Renderizar texto branco
301
+ render_headline_text_white(text_white_draw, headline, 180, 945, 720, 5)
302
+
303
+ # Combinar texto principal
304
+ background_image = Image.alpha_composite(background_image, text_white_img)
305
+
306
+ # Converter de volta para RGB
307
+ background_image = background_image.convert('RGB')
308
+
309
  # Converter para BytesIO
310
  buffer = BytesIO()
311
  background_image.save(buffer, format="JPEG", quality=95)