文件
Henglianshutong/scripts/generate_3c_assets.py

425 行
20 KiB
Python

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
生成腾讯小程序审核专用的真实 3C 数码产品素材:
包含:
1. 4 大类目分类图标
2. 8 款主流爆款 3C 商品的主图、轮播图 (3张/款)、详情长图切片 (3张/款)
"""
import os
import sys
from PIL import Image, ImageDraw, ImageFont, ImageFilter
FONT_PATH = "/System/Library/Fonts/STHeiti Light.ttc"
if not os.path.exists(FONT_PATH):
FONT_PATH = "/System/Library/Fonts/PingFang.ttc"
if not os.path.exists(FONT_PATH):
FONT_PATH = "/Library/Fonts/Arial Unicode.ttf"
def get_font(size):
try:
return ImageFont.truetype(FONT_PATH, size)
except Exception:
return ImageFont.load_default()
BASE_DIR = "/Users/chen/Desktop/xbiuld/henglianshutong"
SOURCE_IMG_DIR = "/Users/chen/.gemini/antigravity/brain/a8c097d8-307e-4c94-b68c-7d7aeaa89eee"
OUTPUT_DIR = os.path.join(BASE_DIR, "scratch/3c_assets")
PRODUCTS = [
{
"id": 1,
"key": "gan_charger",
"raw_img": "baseus_gan_charger_1790409638528.jpg",
"title": "倍思 65W GaN氮化镓多口快充充电器",
"brand": "倍思 (Baseus)",
"model": "CCGAN65S5",
"subtitle": "GaN5 Pro 氮化镓快充 / 2C1A多口盲插 / 智能温控不伤机",
"features": ["65W大功率氮化镓", "2C+1A三口盲插", "BPS II充电技术", "低温快充安全不烫"],
"specs": [
("品名", "倍思 65W GaN5 Pro 多口快充头"),
("接口", "Type-C1 + Type-C2 + USB-A"),
("输入", "AC 100-240V~, 50/60Hz, 1.5A Max"),
("C1/C2输出", "5V/3A, 9V/3A, 12V/3A, 15V/3A, 20V/3.25A (65W Max)"),
("USB-A输出", "5V/3A, 9V/3A, 12V/2.5A, 20V/1.5A (30W Max)"),
("产品尺寸", "65 x 36 x 32 mm"),
("净重", "约 120g"),
("质保服务", "1年全国联保")
],
"package": ["65W氮化镓充电器主机 * 1", "双Type-C 100W快充数据线 * 1", "产品说明书 & 保修卡 * 1", "品牌贴纸 * 1"]
},
{
"id": 2,
"key": "tws_earbuds",
"raw_img": "tws_earbuds_1790409653035.jpg",
"title": "漫步者 LolliPods 真无线立体声蓝牙耳机",
"brand": "漫步者 (EDIFIER)",
"model": "LolliPods 2026款",
"subtitle": "半入耳舒适佩戴 / AptX高清音频解码 / CVC双麦通话降噪 / 24小时综合续航",
"features": ["13mm大尺寸复合振膜", "高通QCC低延迟芯片", "CVC8.0双麦通话降噪", "24小时复合长续航"],
"specs": [
("品名", "漫步者 LolliPods 真无线立体声耳机"),
("蓝牙版本", "Bluetooth V5.3"),
("音频解码", "AptX / SBC"),
("有效距离", "10m 无障碍传输"),
("耳机续航", "约 6小时 (单次)"),
("配合充电仓", "约 24小时"),
("充电接口", "Type-C 高速充电"),
("防水等级", "IP54级防尘防水")
],
"package": ["蓝牙耳机本体 * 1对", "便携式无线充电仓 * 1", "Type-C充电线 * 1", "用户手册与保修凭证 * 1"]
},
{
"id": 3,
"key": "power_bank",
"raw_img": "power_bank_1790409670628.jpg",
"title": "罗马仕 20000mAh 22.5W超级快充移动电源",
"brand": "罗马仕 (ROMOSS)",
"model": "SW20 Pro",
"subtitle": "智能LED数显大容量充电宝 / 支持SCP/PD3.0快充 / 三入三出全兼容",
"features": ["20000mAh大容量", "22.5W超级闪充", "LED百分比数显屏", "三入三出高效充放"],
"specs": [
("品名", "罗马仕 20000mAh 双向快充移动电源"),
("额定容量", "12000mAh (5V/3A)"),
("电池能量", "20000mAh / 74Wh (符合民航登机标准)"),
("输入接口", "Type-C 18W / Micro-USB 18W / Lightning"),
("输出接口", "Type-C 20W / USB-A1 22.5W / USB-A2 22.5W"),
("电量显示", "LED数字智能精准显示"),
("尺寸重量", "168 x 82 x 23 mm / 约 460g"),
("安全保护", "过流/过压/短路/过充等10重安全防护")
],
"package": ["20000mAh移动电源主机 * 1", "USB转Type-C快充线 * 1", "快速使用指南 * 1", "防伪质保卡 * 1"]
},
{
"id": 4,
"key": "fitness_band",
"raw_img": "fitness_band_1790409752424.jpg",
"title": "华为手环 8 智能运动健康手环 曜石黑",
"brand": "华为 (HUAWEI)",
"model": "ASK-B19",
"subtitle": "8.99mm超薄机身 / 1.47英寸无界大屏 / TruSleep 3.0科学睡眠 / 14天长续航",
"features": ["8.99mm轻薄无感佩戴", "1.47英寸全面彩屏", "全天候血氧心率监测", "100+种专业运动模式"],
"specs": [
("品名", "华为手环 8 标准版"),
("机身厚度", "8.99 mm (不含传感器凸起)"),
("机身重量", "约 14 g (不含表带)"),
("屏幕规格", "1.47英寸 AMOLED炫彩触摸屏 (194x368)"),
("健康管理", "睡眠监测 / 连续血氧 / 智能心率 / 压力检测"),
("防水等级", "5 ATM 防水等级 (支持泳池游泳)"),
("典型续航", "最长续航14天 / 常规使用9天"),
("连接技术", "BLE 5.0,支持Android 6.0 / iOS 9.0及以上")
],
"package": ["手环主机含表带 * 1", "专用磁吸充电底座线缆 * 1", "快速入门及安全信息手册 * 1", "三包凭证 * 1"]
},
{
"id": 5,
"key": "smart_watch",
"raw_img": "smart_watch_1790409770345.jpg",
"title": "小米Redmi Watch 3 青春版 智能蓝牙通话手表",
"brand": "小米 (Redmi)",
"model": "M2247W1",
"subtitle": "1.83英寸超大高清屏 / 蓝牙高清语音通话 / 12天持久续航 / 200+个性表盘",
"features": ["1.83英寸全贴合大屏", "支持蓝牙高清通话", "全天心率血氧监测", "12天长效超强续航"],
"specs": [
("品名", "Redmi Watch 3 青春版 智能手表"),
("屏幕尺寸", "1.83英寸高清触控彩屏 (240x280)"),
("表框材质", "高强度高光金属质感中框"),
("通话功能", "内置高灵敏降噪麦克风与独立扬声器"),
("运动模式", "100+种运动模式,自动识别跑步步行"),
("防水等级", "5ATM专业防水"),
("电池容量", "289mAh锂聚合物电池"),
("续航时间", "典型模式12天 / 重度使用8天")
],
"package": ["智能手表主机 * 1", "亲肤硅胶表带 * 1", "专用磁吸充电线 * 1", "使用说明书 * 1"]
},
{
"id": 6,
"key": "wireless_mouse",
"raw_img": "wireless_mouse_1790409787831.jpg",
"title": "罗技 M330 降噪静音无线鼠标 曜石黑",
"brand": "罗技 (Logitech)",
"model": "M330 Silent Plus",
"subtitle": "90%降噪静音微动 / 人体工学舒适右手握感 / 2.4G即插即用 / 24个月超长续航",
"features": ["静音降噪静音点击", "人体工学仿生曲线", "微型USB无线接收器", "24个月超长电池续航"],
"specs": [
("品名", "罗技 M330 Silent Plus 无线静音鼠标"),
("连接方式", "2.4 GHz 无线连接技术 (微型USB Nano接收器)"),
("有效距离", "约 10 米"),
("传感器类型", "罗技先进光学追踪技术"),
("传感器分辨率", "1000 DPI 精准定位"),
("按键数量", "3个 (左键、右键、滚轮中键)"),
("供电电池", "1节 AA 五号电池 (出厂预装)"),
("支持系统", "Windows 10/11, macOS, ChromeOS, Linux")
],
"package": ["M330 无线鼠标 * 1", "Nano 微型无线接收器 * 1", "AA 电池 (已预装) * 1", "用户文档 * 1"]
},
{
"id": 7,
"key": "usbc_hub",
"raw_img": "usbc_hub_1790409804480.jpg",
"title": "绿联 六合一Type-C多功能扩展坞",
"brand": "绿联 (UGREEN)",
"model": "CM475",
"subtitle": "4K@30Hz HDMI高清投屏 / PD 100W大功率闪充 / USB3.0*3 5Gbps高速传输",
"features": ["4K高清HDMI输出", "PD 100W边充边玩", "3口USB3.0 5Gbps", "铝合金一体高效散热"],
"specs": [
("品名", "绿联 Type-C 六合一多功能扩展坞"),
("输入端口", "Type-C (兼容雷电3/4, USB4)"),
("视频端口", "HDMI 支持 4K*2K@30Hz / 1080P@60Hz"),
("数据端口", "USB 3.0 * 3 (传输速率最高 5Gbps)"),
("供电端口", "Type-C PD 3.0 (最高支持 100W 快速充电)"),
("外壳材质", "深空灰阳极氧化铝合金"),
("线身工艺", "高密度尼龙精编,抗弯折寿命10000+次"),
("兼容平台", "MacBook / iPad / 华为 / 联想 / 戴尔等全功能Type-C设备")
],
"package": ["六合一多功能扩展坞 * 1", "产品快速使用手册 * 1", "产品保修凭证 * 1"]
},
{
"id": 8,
"key": "fast_cable",
"raw_img": "fast_cable_1790409818654.jpg",
"title": "品胜 苹果PD 20W快充编织数据线 1.2米",
"brand": "品胜 (PISEN)",
"model": "AL10-1200",
"subtitle": "智能低温快充芯片 / 加粗镀锡铜芯 / 强韧高密度尼龙编织线身 / 充放提速60%",
"features": ["PD 20W急速快充", "高密度尼龙防断编织", "加粗五股纯铜线芯", "充电传输二合一"],
"specs": [
("品名", "品胜 Type-C 转 Lightning / C to C 快充线"),
("接口规格", "Type-C to Lightning / Type-C (兼容PD快充)"),
("最大输出", "20W / 27W Max (向下兼容标准充电)"),
("数据传输", "USB 2.0 480Mbps 稳定互传"),
("线身长度", "1.2 米 (黄金日用长度)"),
("线身材质", "精工高密尼龙双色编织 + 铝合金端子外壳"),
("弯折测试", "经过 20000+ 次SR摇摆弯折抗疲劳测试"),
("适用机型", "iPhone 8-15全系 / iPad / AirPods 系列设备")
],
"package": ["快充编织数据线 1.2米 * 1", "硅胶收纳绑带 * 1", "产品合格证 & 质保卡 * 1"]
}
]
def create_banner_2(prod, raw_img_path, out_path):
"""生成轮播图2:性能特性/场景解析"""
canvas = Image.new("RGB", (800, 800), (248, 250, 252))
raw = Image.open(raw_img_path).convert("RGBA").resize((560, 560), Image.Resampling.LANCZOS)
canvas.paste(raw, (120, 180), raw)
draw = ImageDraw.Draw(canvas)
# 顶部标签
font_tag = get_font(26)
font_sub = get_font(20)
draw.rounded_rectangle([(40, 40), (220, 85)], radius=8, fill=(30, 41, 59))
draw.text((60, 48), "核心性能解析", fill=(255, 255, 255), font=font_tag)
draw.text((240, 52), prod["brand"] + " · 原厂正品", fill=(100, 116, 139), font=font_sub)
# 底部核心特性芯片卡片
features = prod["features"]
card_w = 345
card_h = 44
coords = [
(40, 680), (415, 680),
(40, 735), (415, 735)
]
font_f = get_font(20)
for i, f in enumerate(features[:4]):
x, y = coords[i]
draw.rounded_rectangle([(x, y), (x + card_w, y + card_h)], radius=6, fill=(241, 245, 249), outline=(226, 232, 240))
draw.ellipse([(x + 14, y + 16), (x + 24, y + 26)], fill=(37, 99, 235))
draw.text((x + 36, y + 10), f, fill=(15, 23, 42), font=font_f)
canvas.save(out_path, "JPEG", quality=92)
def create_banner_3(prod, raw_img_path, out_path):
"""生成轮播图3:做工细节与设计质感"""
canvas = Image.new("RGB", (800, 800), (255, 255, 255))
raw = Image.open(raw_img_path).convert("RGBA").resize((600, 600), Image.Resampling.LANCZOS)
canvas.paste(raw, (100, 80), raw)
draw = ImageDraw.Draw(canvas)
# 底部质感横幅
draw.rectangle([(0, 680), (800, 800)], fill=(15, 23, 42))
font_title = get_font(28)
font_desc = get_font(20)
draw.text((50, 705), "极简精工美学 · 严苛品质出厂质检", fill=(255, 255, 255), font=font_title)
draw.text((50, 748), prod["subtitle"][:32], fill=(148, 163, 184), font=font_desc)
canvas.save(out_path, "JPEG", quality=92)
def create_detail_1(prod, raw_img_path, out_path):
"""生成详情切片1:产品核心卖点海报长图 (750 x 1000)"""
w, h = 750, 1000
canvas = Image.new("RGB", (w, h), (248, 250, 252))
draw = ImageDraw.Draw(canvas)
# 顶部品牌条
draw.rectangle([(0, 0), (w, 140)], fill=(15, 23, 42))
font_brand = get_font(22)
font_title = get_font(32)
draw.text((40, 30), prod["brand"].upper(), fill=(148, 163, 184), font=font_brand)
draw.text((40, 70), prod["title"][:16], fill=(255, 255, 255), font=font_title)
# 主体产品图片
raw = Image.open(raw_img_path).convert("RGBA").resize((480, 480), Image.Resampling.LANCZOS)
canvas.paste(raw, (135, 170), raw)
# 4大卖点模块
draw.rectangle([(30, 680), (720, 960)], fill=(255, 255, 255), outline=(226, 232, 240))
font_block_t = get_font(24)
font_block_c = get_font(20)
draw.text((55, 705), "产品核心亮点", fill=(15, 23, 42), font=font_block_t)
draw.line([(55, 745), (695, 745)], fill=(241, 245, 249), width=2)
feats = prod["features"]
for i, ft in enumerate(feats):
fy = 765 + i * 44
draw.ellipse([(60, fy + 8), (72, fy + 20)], fill=(37, 99, 235))
draw.text((85, fy + 4), ft, fill=(51, 65, 85), font=font_block_c)
canvas.save(out_path, "JPEG", quality=92)
def create_detail_2(prod, out_path):
"""生成详情切片2:标准技术参数表格长图 (750 x 900)"""
w, h = 750, 900
canvas = Image.new("RGB", (w, h), (255, 255, 255))
draw = ImageDraw.Draw(canvas)
# 顶部标题栏
draw.rectangle([(0, 0), (w, 90)], fill=(241, 245, 249))
font_t = get_font(30)
draw.text((40, 28), "详细规格参数与技术指标", fill=(15, 23, 42), font=font_t)
# 表格
specs = prod["specs"]
table_top = 120
row_h = 75
font_k = get_font(22)
font_v = get_font(20)
for i, (k, v) in enumerate(specs):
curr_y = table_top + i * row_h
bg_col = (248, 250, 252) if i % 2 == 0 else (255, 255, 255)
draw.rectangle([(30, curr_y), (720, curr_y + row_h)], fill=bg_col, outline=(226, 232, 240))
# 键列
draw.text((50, curr_y + 25), k, fill=(100, 116, 139), font=font_k)
# 值列 (换行处理)
if len(v) > 28:
draw.text((220, curr_y + 12), v[:28], fill=(15, 23, 42), font=font_v)
draw.text((220, curr_y + 40), v[28:], fill=(15, 23, 42), font=font_v)
else:
draw.text((220, curr_y + 26), v, fill=(15, 23, 42), font=font_v)
canvas.save(out_path, "JPEG", quality=92)
def create_detail_3(prod, out_path):
"""生成详情切片3:包装清单与正品保障售后承诺长图 (750 x 850)"""
w, h = 750, 850
canvas = Image.new("RGB", (w, h), (248, 250, 252))
draw = ImageDraw.Draw(canvas)
# 包装清单部分
draw.rectangle([(30, 40), (720, 360)], fill=(255, 255, 255), outline=(226, 232, 240))
font_sec = get_font(26)
font_item = get_font(21)
draw.text((55, 65), "包装清单 (Standard Package)", fill=(15, 23, 42), font=font_sec)
draw.line([(55, 110), (695, 110)], fill=(241, 245, 249), width=2)
for i, item in enumerate(prod["package"]):
iy = 135 + i * 48
draw.ellipse([(60, iy + 8), (70, iy + 18)], fill=(16, 185, 129))
draw.text((85, iy + 4), item, fill=(51, 65, 85), font=font_item)
# 服务保障部分
draw.rectangle([(30, 400), (720, 800)], fill=(255, 255, 255), outline=(226, 232, 240))
draw.text((55, 425), "正品承诺与售后保障说明", fill=(15, 23, 42), font=font_sec)
draw.line([(55, 470), (695, 470)], fill=(241, 245, 249), width=2)
guarantees = [
("官方正品", "100% 正品行货,支持全国防伪验证查询,假一赔十"),
("七天无理由", "支持 7 天无理由退换货(保持商品包装完好未激活)"),
("一年全国联保", "自购买之日起享受 365 天官方全国联合保修服务"),
("极速闪电发货", "每日16点前订单顺丰/京东快递当日发出,全场包邮")
]
font_gt = get_font(22)
font_gc = get_font(18)
for j, (gt, gc) in enumerate(guarantees):
gy = 495 + j * 72
draw.rounded_rectangle([(55, gy), (180, gy + 36)], radius=6, fill=(238, 242, 255))
draw.text((65, gy + 6), gt, fill=(79, 70, 229), font=font_gt)
draw.text((200, gy + 8), gc, fill=(71, 85, 105), font=font_gc)
canvas.save(out_path, "JPEG", quality=92)
def create_category_icon(title, subtitle, icon_color, out_path, sample_img_path):
"""生成 200x200 分类图标"""
canvas = Image.new("RGBA", (200, 200), (0, 0, 0, 0))
draw = ImageDraw.Draw(canvas)
# 渐变或圆角卡片底色
draw.rounded_rectangle([(10, 10), (190, 190)], radius=36, fill=(245, 247, 250), outline=(226, 232, 240))
# 贴上该类目产品的居中缩略图
sample = Image.open(sample_img_path).convert("RGBA").resize((130, 130), Image.Resampling.LANCZOS)
canvas.paste(sample, (35, 35), sample)
canvas.save(out_path, "PNG")
def main():
os.makedirs(OUTPUT_DIR, exist_ok=True)
os.makedirs(os.path.join(OUTPUT_DIR, "categories"), exist_ok=True)
print("开始生成 3C 分类与商品图片素材...")
# 1. 生成商品素材
for p in PRODUCTS:
p_dir = os.path.join(OUTPUT_DIR, f"p_{p['id']}")
os.makedirs(p_dir, exist_ok=True)
raw_path = os.path.join(SOURCE_IMG_DIR, p["raw_img"])
# main/cover (800x800)
img_main = Image.open(raw_path).convert("RGB").resize((800, 800), Image.Resampling.LANCZOS)
main_path = os.path.join(p_dir, "cover.jpg")
img_main.save(main_path, "JPEG", quality=94)
# banner 1 (800x800)
banner1_path = os.path.join(p_dir, "banner_1.jpg")
img_main.save(banner1_path, "JPEG", quality=94)
# banner 2
banner2_path = os.path.join(p_dir, "banner_2.jpg")
create_banner_2(p, raw_path, banner2_path)
# banner 3
banner3_path = os.path.join(p_dir, "banner_3.jpg")
create_banner_3(p, raw_path, banner3_path)
# detail 1
detail1_path = os.path.join(p_dir, "detail_1.jpg")
create_detail_1(p, raw_path, detail1_path)
# detail 2
detail2_path = os.path.join(p_dir, "detail_2.jpg")
create_detail_2(p, detail2_path)
# detail 3
detail3_path = os.path.join(p_dir, "detail_3.jpg")
create_detail_3(p, detail3_path)
print(f"商品 {p['id']} [{p['title']}] 素材生成完成。")
# 2. 生成 4 个分类图标
cat_items = [
("手机数码", "充电/配件/外设", (37, 99, 235), "cat_mobile.png", os.path.join(SOURCE_IMG_DIR, PRODUCTS[0]["raw_img"])),
("智能影音", "耳机/音箱/降噪", (124, 58, 237), "cat_audio.png", os.path.join(SOURCE_IMG_DIR, PRODUCTS[1]["raw_img"])),
("智能穿戴", "手环/手表/健康", (225, 29, 72), "cat_wearable.png", os.path.join(SOURCE_IMG_DIR, PRODUCTS[3]["raw_img"])),
("电脑办公", "鼠标/键鼠/扩展坞", (13, 148, 136), "cat_computer.png", os.path.join(SOURCE_IMG_DIR, PRODUCTS[5]["raw_img"]))
]
for c_title, c_sub, c_col, c_name, sample_path in cat_items:
c_path = os.path.join(OUTPUT_DIR, "categories", c_name)
create_category_icon(c_title, c_sub, c_col, c_path, sample_path)
print(f"分类图标 [{c_title}] 生成完成: {c_name}")
print("全部素材生成完毕!输出目录:", OUTPUT_DIR)
if __name__ == "__main__":
main()