6e0c1bd7a4
- 创建选址分析7个SQL视图+5张物化表(38s→33ms) - 后端新增5个选址API端点 - 前端新增SiteSelectionPage页面(散点图+4Tab) - 侧边栏新增门店选址导航入口 - 修复SQL列引用错误(d.→a.,去掉重复列) - 创建v_store_action_priority_deep_april和v_store_area_efficiency_april基础视图
85 lines
3.8 KiB
Python
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:]}")
|