Files
SBrainCO/demo/add-subtitles.py
T
freedakgmail 6e0c1bd7a4 feat: 新增门店选址分析功能
- 创建选址分析7个SQL视图+5张物化表(38s→33ms)
- 后端新增5个选址API端点
- 前端新增SiteSelectionPage页面(散点图+4Tab)
- 侧边栏新增门店选址导航入口
- 修复SQL列引用错误(d.→a.,去掉重复列)
- 创建v_store_action_priority_deep_april和v_store_area_efficiency_april基础视图
2026-07-27 08:33:42 +08:00

85 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""Add subtitle text to each screenshot, then build video with ffmpeg."""
import os, subprocess
from PIL import Image, ImageDraw, ImageFont
DEMO_DIR = os.path.dirname(os.path.abspath(__file__))
FONT_PATH = "/System/Library/Fonts/STHeiti Medium.ttc"
FONT_SIZE = 36
# (filename, subtitle_text)
screenshots = [
("s01-dashboard-top.png", "总部驾驶舱 — 全局经营总览,KPI一屏掌握"),
("s02-dashboard-charts.png", "象限散点图·瀑布图·平台成本率 — 数据深度分析"),
("s03-regional-overview.png", "区域经理工作台 — 12区经营汇总,红黄绿风险分布"),
("s04-store-manager-full.png", "店长工作台 — 经营概览·指标进度·SKU备货·任务闭环"),
("s05-store-manager-top.png", "店长工作台 — 异常标注+指标进度条,几分钟看完就知道今天干什么"),
("s06-store-detail-overview.png", "门店详情·概览 — 菜百店全链路:KPI·日度趋势·平台经济性"),
("s07-store-detail-meal-period.png", "门店详情·餐段分析 — 午市/晚市/下午茶/早市客流与客单价"),
("s08-store-detail-category.png", "门店详情·品类结构 — 销售集中度与搭售机会分析"),
("s09-store-detail-cost.png", "门店详情·成本分析 — 理论成本vs实际成本,精准定位差异"),
("s10-store-detail-member.png", "门店详情·会员分析 — RFM分层,沉睡会员召回机会"),
("s11-store-detail-anomaly.png", "门店详情·异常账单 — 风险内控,自动识别与闭环处理"),
("s12-monthly-review-summary.png", "月度验收·验收汇总 — 从数字化到智能化,只推可执行指令"),
("s13-monthly-review-grade.png", "月度验收·升降级面板 — P0/P1/P2等级变化,复制盈利模型"),
("s14-monthly-review-completion.png","月度验收·任务完成率 — 按优先级汇总,闭环验证改善效果"),
("s15-monthly-review-activity.png", "月度验收·活动清单 — 逐活动贡献毛利,停改留由数据驱动"),
("s16-monthly-review-sku.png", "月度验收·SKU治理 — ABC分类,长尾精简,标准化输出"),
]
sub_dir = os.path.join(DEMO_DIR, "sub")
os.makedirs(sub_dir, exist_ok=True)
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
for fname, text in screenshots:
src = os.path.join(DEMO_DIR, fname)
img = Image.open(src).convert("RGBA")
w, h = img.size
# Create overlay for text background bar
overlay = Image.new("RGBA", (w, h), (0, 0, 0, 0))
draw = ImageDraw.Draw(overlay)
# Measure text
bbox = draw.textbbox((0, 0), text, font=font)
tw = bbox[2] - bbox[0]
th = bbox[3] - bbox[1]
# Draw semi-transparent black bar at bottom
bar_h = th + 30
bar_y = h - bar_h
draw.rectangle([0, bar_y, w, h], fill=(0, 0, 0, 180))
# Draw text centered
tx = (w - tw) // 2
ty = bar_y + 15
draw.text((tx, ty), text, font=font, fill=(255, 255, 255, 255))
# Composite
result = Image.alpha_composite(img, overlay).convert("RGB")
dst = os.path.join(sub_dir, fname)
result.save(dst, "PNG")
print(f" {fname} -> sub/{fname}")
print(f"\nGenerated {len(screenshots)} annotated screenshots in sub/")
# Build video
print("\nBuilding video with ffmpeg...")
cmd = [
"ffmpeg", "-y",
"-framerate", "1/2",
"-pattern_type", "glob",
"-i", os.path.join(sub_dir, "s*.png"),
"-c:v", "libx264",
"-pix_fmt", "yuv420p",
"-vf", "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:0x0F0F0F",
"-r", "30",
os.path.join(DEMO_DIR, "product-demo.mp4")
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
print("Video built: product-demo.mp4")
else:
print(f"Error: {result.stderr[-500:]}")