jacky3102 commited on
Commit
4006c25
·
verified ·
1 Parent(s): 66a1d32

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +18 -45
app.py CHANGED
@@ -8,7 +8,7 @@ from PIL import Image, ImageDraw, ImageFont, ImageFilter
8
  from pydub import AudioSegment
9
  import subprocess
10
  from pathlib import Path
11
- from typing import Optional, List, Tuple
12
  import random
13
  import math
14
  import traceback
@@ -17,7 +17,7 @@ import sys
17
  app = FastAPI(title="Video Generator with Effects API")
18
 
19
  # ============================================================
20
- # 粒子系统(全局画布坐标)
21
  # ============================================================
22
 
23
  class Particle:
@@ -29,12 +29,13 @@ class Particle:
29
  self.reset()
30
 
31
  def reset(self):
 
32
  self.x = random.uniform(0, self.canvas_w)
33
- self.y = random.uniform(-50, -10)
34
- self.size = random.uniform(1, 4)
35
  self.speed = random.uniform(0.5, 2.5)
36
  self.wind = random.uniform(-0.5, 0.5)
37
- self.opacity = random.uniform(80, 255)
38
  self.rotation = random.uniform(0, 360)
39
  self.rot_speed = random.uniform(-3, 3)
40
  self.wobble = random.uniform(0, math.pi * 2)
@@ -173,13 +174,9 @@ def apply_flame(frame, frame_idx, fps):
173
 
174
 
175
  # ============================================================
176
- # ✅ 图片缩放:保持原比例,返回画布(无黑边,画布=图片大小)
177
  # ============================================================
178
  def fit_image_to_canvas(img, target_w, target_h):
179
- """
180
- 保持原比例缩放图片,放到 target_w x target_h 的画布上。
181
- 如果图片比例和画布不一致,会有黑边。
182
- """
183
  h, w = img.shape[:2]
184
  scale = min(target_w / w, target_h / h)
185
  new_w = int(w * scale)
@@ -232,23 +229,17 @@ def get_chinese_font():
232
 
233
 
234
  # ============================================================
235
- # ✅ 字幕渲染:更小、无背景板、精致阴影字
236
  # ============================================================
237
 
238
  def draw_subtitle(frame, text, font_path, video_w, video_h):
239
- """
240
- 精致字幕:透明阴影字 + 底部细线装饰
241
- ✅ 无背景板
242
- ✅ 字体更小
243
- ✅ 淡阴影增强可读性
244
- """
245
  if not text:
246
  return frame
247
 
248
  img_rgba = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)).convert("RGBA")
249
  draw = ImageDraw.Draw(img_rgba)
250
 
251
- # ✅ 更小的字体:视频宽度的 1/22
252
  font_size = max(18, min(video_w // 22, 40))
253
  try:
254
  font_obj = ImageFont.truetype(font_path, font_size) if font_path else ImageFont.load_default()
@@ -270,8 +261,6 @@ def draw_subtitle(frame, text, font_path, video_w, video_h):
270
  lines.append(current_line)
271
 
272
  line_height = font_size + 10
273
-
274
- # ✅ 字幕放在底部上方 8% 处
275
  bottom_margin = int(video_h * 0.08)
276
  total_text_height = len(lines) * line_height
277
  y_start = video_h - bottom_margin - total_text_height
@@ -282,25 +271,13 @@ def draw_subtitle(frame, text, font_path, video_w, video_h):
282
  text_x = (video_w - text_w) // 2
283
  text_y = y_start + i * line_height
284
 
285
- # ✅ 柔和阴影(无背景板)
286
  for ox, oy in [(-1, -1), (1, -1), (-1, 1), (1, 1)]:
287
  draw.text((text_x + ox, text_y + oy), line, fill=(0, 0, 0, 140), font=font_obj)
288
 
289
- # 主文字:亮白带微暖色调
290
  draw.text((text_x, text_y), line, fill=(255, 252, 245, 255), font=font_obj)
291
 
292
- # ✅ 底部一条微弱的渐变线
293
- line_y = y_start + total_text_height + 8
294
- grad_w = int(video_w * 0.3)
295
- grad_x = (video_w - grad_w) // 2
296
- for j in range(2):
297
- alpha = 100 - j * 40
298
- draw.line(
299
- [(grad_x, line_y + j), (grad_x + grad_w, line_y + j)],
300
- fill=(255, 255, 255, alpha),
301
- width=1,
302
- )
303
-
304
  return cv2.cvtColor(np.array(img_rgba.convert("RGB")), cv2.COLOR_RGB2BGR)
305
 
306
 
@@ -333,8 +310,6 @@ def generate_video(
333
  # ✅ 用第一张图的比例决定视频尺寸
334
  first_h, first_w = images_raw[0].shape[:2]
335
  aspect_ratio = first_w / first_h
336
-
337
- # 限制最大尺寸,避免免费 Space 内存爆炸
338
  max_dim = 1280
339
  if first_w >= first_h:
340
  out_w = min(first_w, max_dim)
@@ -342,24 +317,22 @@ def generate_video(
342
  else:
343
  out_h = min(first_h, max_dim)
344
  out_w = int(out_h * aspect_ratio)
345
-
346
- # 确保偶数尺寸(ffmpeg 要求)
347
  out_w = out_w if out_w % 2 == 0 else out_w + 1
348
  out_h = out_h if out_h % 2 == 0 else out_h + 1
349
-
350
- print(f"📐 视频尺寸: {out_w}x{out_h} (基于第一张图比例)", file=sys.stderr)
351
  print(f"🖼️ 加载了 {len(images_raw)} 张图片", file=sys.stderr)
352
 
353
- # 所有图片适配到相同尺寸
354
  images_fitted = [fit_image_to_canvas(img, out_w, out_h) for img in images_raw]
355
-
356
  frames_per_image = total_frames // len(images_fitted)
357
 
358
- # ✅ 粒子基于全图
359
  particles = []
360
  if effect in ("snow", "leaves", "rain", "sparkle", "dust"):
361
- for _ in range(80):
 
 
362
  particles.append(Particle(out_w, out_h, effect))
 
363
 
364
  font_path = get_chinese_font()
365
  print(f"🔤 字体: {font_path or '默认'}", file=sys.stderr)
@@ -375,7 +348,7 @@ def generate_video(
375
  img_idx = min(frame_idx // frames_per_image, len(images_fitted) - 1)
376
  frame = images_fitted[img_idx].copy()
377
 
378
- # ✅ 全图特效
379
  if effect in ("snow", "leaves", "rain", "sparkle", "dust"):
380
  frame = apply_effect(frame, particles, effect)
381
  elif effect == "flame":
 
8
  from pydub import AudioSegment
9
  import subprocess
10
  from pathlib import Path
11
+ from typing import Optional, List
12
  import random
13
  import math
14
  import traceback
 
17
  app = FastAPI(title="Video Generator with Effects API")
18
 
19
  # ============================================================
20
+ # 粒子系统(全图覆盖)
21
  # ============================================================
22
 
23
  class Particle:
 
29
  self.reset()
30
 
31
  def reset(self):
32
+ # ✅ 初始位置分布在全图各处,而非只在顶部
33
  self.x = random.uniform(0, self.canvas_w)
34
+ self.y = random.uniform(-50, self.canvas_h + 50)
35
+ self.size = random.uniform(2, 6) # 更大更明显
36
  self.speed = random.uniform(0.5, 2.5)
37
  self.wind = random.uniform(-0.5, 0.5)
38
+ self.opacity = random.uniform(100, 255) # 更不透明
39
  self.rotation = random.uniform(0, 360)
40
  self.rot_speed = random.uniform(-3, 3)
41
  self.wobble = random.uniform(0, math.pi * 2)
 
174
 
175
 
176
  # ============================================================
177
+ # 图片缩放:保持原比例 + 黑边填充
178
  # ============================================================
179
  def fit_image_to_canvas(img, target_w, target_h):
 
 
 
 
180
  h, w = img.shape[:2]
181
  scale = min(target_w / w, target_h / h)
182
  new_w = int(w * scale)
 
229
 
230
 
231
  # ============================================================
232
+ # 字幕渲染:无背景板 + 柔和阴影
233
  # ============================================================
234
 
235
  def draw_subtitle(frame, text, font_path, video_w, video_h):
236
+ """精致字幕:柔阴影 + 暖白字,无背景板"""
 
 
 
 
 
237
  if not text:
238
  return frame
239
 
240
  img_rgba = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)).convert("RGBA")
241
  draw = ImageDraw.Draw(img_rgba)
242
 
 
243
  font_size = max(18, min(video_w // 22, 40))
244
  try:
245
  font_obj = ImageFont.truetype(font_path, font_size) if font_path else ImageFont.load_default()
 
261
  lines.append(current_line)
262
 
263
  line_height = font_size + 10
 
 
264
  bottom_margin = int(video_h * 0.08)
265
  total_text_height = len(lines) * line_height
266
  y_start = video_h - bottom_margin - total_text_height
 
271
  text_x = (video_w - text_w) // 2
272
  text_y = y_start + i * line_height
273
 
274
+ # 柔和阴影
275
  for ox, oy in [(-1, -1), (1, -1), (-1, 1), (1, 1)]:
276
  draw.text((text_x + ox, text_y + oy), line, fill=(0, 0, 0, 140), font=font_obj)
277
 
278
+ # 主文字
279
  draw.text((text_x, text_y), line, fill=(255, 252, 245, 255), font=font_obj)
280
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  return cv2.cvtColor(np.array(img_rgba.convert("RGB")), cv2.COLOR_RGB2BGR)
282
 
283
 
 
310
  # ✅ 用第一张图的比例决定视频尺寸
311
  first_h, first_w = images_raw[0].shape[:2]
312
  aspect_ratio = first_w / first_h
 
 
313
  max_dim = 1280
314
  if first_w >= first_h:
315
  out_w = min(first_w, max_dim)
 
317
  else:
318
  out_h = min(first_h, max_dim)
319
  out_w = int(out_h * aspect_ratio)
 
 
320
  out_w = out_w if out_w % 2 == 0 else out_w + 1
321
  out_h = out_h if out_h % 2 == 0 else out_h + 1
322
+ print(f"📐 视频尺寸: {out_w}x{out_h}", file=sys.stderr)
 
323
  print(f"🖼️ 加载了 {len(images_raw)} 张图片", file=sys.stderr)
324
 
 
325
  images_fitted = [fit_image_to_canvas(img, out_w, out_h) for img in images_raw]
 
326
  frames_per_image = total_frames // len(images_fitted)
327
 
328
+ # ✅ 粒子按画面面积动态计算数量
329
  particles = []
330
  if effect in ("snow", "leaves", "rain", "sparkle", "dust"):
331
+ area = out_w * out_h
332
+ particle_count = max(100, int(area / 8000))
333
+ for _ in range(particle_count):
334
  particles.append(Particle(out_w, out_h, effect))
335
+ print(f"✨ 粒子数量: {particle_count} (画面 {out_w}x{out_h})", file=sys.stderr)
336
 
337
  font_path = get_chinese_font()
338
  print(f"🔤 字体: {font_path or '默认'}", file=sys.stderr)
 
348
  img_idx = min(frame_idx // frames_per_image, len(images_fitted) - 1)
349
  frame = images_fitted[img_idx].copy()
350
 
351
+ # ✅ 全图特效(粒子持续运行,不随图片切换重置)
352
  if effect in ("snow", "leaves", "rain", "sparkle", "dust"):
353
  frame = apply_effect(frame, particles, effect)
354
  elif effect == "flame":