Spaces:
Running
Running
Restore to commit bdf29090e1ff324b34841a86e871cc6942e06551
Browse files- ai_runtime_patch_fast.py +55 -95
ai_runtime_patch_fast.py
CHANGED
|
@@ -176,116 +176,76 @@ def _split_into_segments(text, max_segments=10, min_len=30):
|
|
| 176 |
# ======================================================================
|
| 177 |
# FRAME CREATION — with Vietnamese font support
|
| 178 |
# ======================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
"""Create a single frame with Vietnamese text support - matching original layout."""
|
| 183 |
from PIL import Image, ImageDraw
|
| 184 |
_find_vn_fonts()
|
| 185 |
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
bg = Image.new('RGB', (W, H), (12, 12, 12))
|
| 189 |
draw = ImageDraw.Draw(bg)
|
| 190 |
|
| 191 |
-
# Background image
|
| 192 |
if downloaded:
|
| 193 |
try:
|
| 194 |
im = Image.open(img_path).convert('RGB')
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
else:
|
| 200 |
-
nw = W; nh = int(nw / ratio)
|
| 201 |
-
im = im.resize((nw, nh))
|
| 202 |
-
left = (nw - W) // 2
|
| 203 |
-
top = (nh - hero_h) // 2
|
| 204 |
-
bg.paste(im.crop((left, top, left + W, top + hero_h)), (0, 0))
|
| 205 |
except Exception as e:
|
| 206 |
_log.warning(f"paste img: {e}")
|
| 207 |
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
# Source badge on top image corner
|
| 214 |
-
badge = 'Nguồn: ' + _source_badge_url_first(post)
|
| 215 |
-
try:
|
| 216 |
-
b = draw.textbbox((0, 0), badge, font=fb); bw = b[2] - b[0]; bh = b[3] - b[1]
|
| 217 |
-
except:
|
| 218 |
-
bw = len(badge) * 16; bh = 34
|
| 219 |
-
bx = W - bw - 42; by = 24
|
| 220 |
-
draw.rounded_rectangle((bx - 16, by - 8, W - 24, by + bh + 14), radius=18, fill=(0, 0, 0, 170))
|
| 221 |
-
draw.text((bx, by), badge, fill=(255, 255, 255), font=fb)
|
| 222 |
-
|
| 223 |
-
# Bottom text area
|
| 224 |
-
draw.rectangle((0, hero_h - 20, W, H), fill=(12, 12, 12))
|
| 225 |
-
|
| 226 |
-
# Progress bars centered
|
| 227 |
-
total = max(1, total)
|
| 228 |
-
total_w = total * 38 - 14
|
| 229 |
-
start = (W - total_w) // 2
|
| 230 |
-
for i in range(total):
|
| 231 |
-
fill = (92, 184, 122) if i == idx else (70, 70, 70)
|
| 232 |
-
draw.rounded_rectangle((start + i * 38, 820, start + i * 38 + 24, 832), radius=6, fill=fill)
|
| 233 |
-
|
| 234 |
-
brand = 'VNEWS AI SHORT'
|
| 235 |
-
try:
|
| 236 |
-
bb = draw.textbbox((0, 0), brand, font=fb); tx = (W - (bb[2] - bb[0])) // 2
|
| 237 |
-
except:
|
| 238 |
-
tx = 360
|
| 239 |
-
draw.text((tx, 870), brand, fill=(110, 231, 143), font=fb)
|
| 240 |
-
|
| 241 |
-
seg_text = _strip_bullet_prefix(seg)
|
| 242 |
-
lines = wrap_text_vn(draw, seg_text, fb, W - 120, 8) if fb else [seg_text[:80]]
|
| 243 |
-
block_h = len(lines) * 74
|
| 244 |
-
y = max(980, 1250 - block_h // 2)
|
| 245 |
-
_draw_center(draw, lines, fb, y, (255, 255, 255), W, 74)
|
| 246 |
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
_draw_center(draw, title_lines, fb, y2, (220, 220, 220), W, 42)
|
| 251 |
|
| 252 |
-
bg.save(frame_path, quality=
|
| 253 |
return True
|
| 254 |
|
| 255 |
-
|
| 256 |
-
def _source_badge_url_first(post):
|
| 257 |
-
d = _domain(post.get('url', ''))
|
| 258 |
-
if d:
|
| 259 |
-
return d
|
| 260 |
-
for s in post.get('sources') or []:
|
| 261 |
-
d = _domain(s.get('url', ''))
|
| 262 |
-
if d:
|
| 263 |
-
return d
|
| 264 |
-
return 'VNEWS'
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
def wrap_text_vn(draw, text, font, maxw, max_lines):
|
| 268 |
-
words = _clean(text).split()
|
| 269 |
-
lines = []
|
| 270 |
-
cur = ''
|
| 271 |
-
for w in words:
|
| 272 |
-
test = (cur + ' ' + w).strip()
|
| 273 |
-
try:
|
| 274 |
-
width = draw.textbbox((0, 0), test, font=font)[2]
|
| 275 |
-
except:
|
| 276 |
-
width = len(test) * 22
|
| 277 |
-
if width <= maxw:
|
| 278 |
-
cur = test
|
| 279 |
-
else:
|
| 280 |
-
if cur:
|
| 281 |
-
lines.append(cur)
|
| 282 |
-
cur = w
|
| 283 |
-
if len(lines) >= max_lines:
|
| 284 |
-
break
|
| 285 |
-
if cur and len(lines) < max_lines:
|
| 286 |
-
lines.append(cur)
|
| 287 |
-
return lines
|
| 288 |
-
|
| 289 |
# ======================================================================
|
| 290 |
# SHORT VIDEO GENERATOR — FULLY ROBUST
|
| 291 |
# ======================================================================
|
|
@@ -369,7 +329,7 @@ def _gen_short_sync(post) -> str:
|
|
| 369 |
frame_ok = False
|
| 370 |
if has_pil:
|
| 371 |
try:
|
| 372 |
-
_make_frame(post, seg, idx,
|
| 373 |
if os.path.exists(frame_path) and os.path.getsize(frame_path) > 1000:
|
| 374 |
frame_ok = True
|
| 375 |
else:
|
|
|
|
| 176 |
# ======================================================================
|
| 177 |
# FRAME CREATION — with Vietnamese font support
|
| 178 |
# ======================================================================
|
| 179 |
+
def _draw_vn_text(draw, text, font, x, y, max_width, max_lines=6, fill=(255,255,255)):
|
| 180 |
+
"""Draw text with word wrapping, handling Vietnamese characters."""
|
| 181 |
+
if font is None:
|
| 182 |
+
return y
|
| 183 |
+
|
| 184 |
+
words = text.split()
|
| 185 |
+
lines = []
|
| 186 |
+
current_line = []
|
| 187 |
+
|
| 188 |
+
for w in words:
|
| 189 |
+
test_line = ' '.join(current_line + [w])
|
| 190 |
+
try:
|
| 191 |
+
w_px = font.getlength(test_line)
|
| 192 |
+
except:
|
| 193 |
+
try:
|
| 194 |
+
w_px = draw.textbbox((0,0), test_line, font=font)[2]
|
| 195 |
+
except:
|
| 196 |
+
w_px = len(test_line) * 22
|
| 197 |
+
|
| 198 |
+
if w_px <= max_width:
|
| 199 |
+
current_line.append(w)
|
| 200 |
+
else:
|
| 201 |
+
if current_line:
|
| 202 |
+
lines.append(' '.join(current_line))
|
| 203 |
+
current_line = [w]
|
| 204 |
+
if len(lines) >= max_lines:
|
| 205 |
+
break
|
| 206 |
+
|
| 207 |
+
if current_line and len(lines) < max_lines:
|
| 208 |
+
lines.append(' '.join(current_line))
|
| 209 |
+
|
| 210 |
+
for ln in lines[:max_lines]:
|
| 211 |
+
draw.text((x, y), ln, fill=fill, font=font)
|
| 212 |
+
y += 55
|
| 213 |
+
|
| 214 |
+
return y
|
| 215 |
|
| 216 |
+
def _make_frame(post, seg, idx, img_path, downloaded, frame_path):
|
| 217 |
+
"""Create a single frame with Vietnamese text support."""
|
|
|
|
| 218 |
from PIL import Image, ImageDraw
|
| 219 |
_find_vn_fonts()
|
| 220 |
|
| 221 |
+
sz = (1080, 1920)
|
| 222 |
+
bg = Image.new('RGB', sz, (15, 23, 38))
|
|
|
|
| 223 |
draw = ImageDraw.Draw(bg)
|
| 224 |
|
| 225 |
+
# Background image
|
| 226 |
if downloaded:
|
| 227 |
try:
|
| 228 |
im = Image.open(img_path).convert('RGB')
|
| 229 |
+
im = im.resize((1080, 760))
|
| 230 |
+
bg.paste(im, (0, 0))
|
| 231 |
+
overlay = Image.new('RGBA', (1080, 400), (0, 0, 0, 180))
|
| 232 |
+
bg.paste(overlay, (0, 720), overlay)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
except Exception as e:
|
| 234 |
_log.warning(f"paste img: {e}")
|
| 235 |
|
| 236 |
+
# Title bar
|
| 237 |
+
draw.rectangle([0, 0, sz[0], 100], fill=(25, 118, 210))
|
| 238 |
+
ttl = post.get('title', '')[:50]
|
| 239 |
+
if _FONT_BOLD:
|
| 240 |
+
draw.text((sz[0]//2, 50), ttl, fill='white', font=_FONT_BOLD, anchor='mm')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
+
# Segment text
|
| 243 |
+
seg_text = seg[:200]
|
| 244 |
+
_draw_vn_text(draw, seg_text, _FONT_REGULAR, 80, 820, 920, max_lines=8)
|
|
|
|
| 245 |
|
| 246 |
+
bg.save(frame_path, quality=88)
|
| 247 |
return True
|
| 248 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
# ======================================================================
|
| 250 |
# SHORT VIDEO GENERATOR — FULLY ROBUST
|
| 251 |
# ======================================================================
|
|
|
|
| 329 |
frame_ok = False
|
| 330 |
if has_pil:
|
| 331 |
try:
|
| 332 |
+
_make_frame(post, seg, idx, img_path, downloaded, frame_path)
|
| 333 |
if os.path.exists(frame_path) and os.path.getsize(frame_path) > 1000:
|
| 334 |
frame_ok = True
|
| 335 |
else:
|