掌机前端的媒体数据管理: SteamGridDB 封面抓取、图片规格与元数据源选择
掌机前端 iiSU 的界面是那种偏视觉的样式: 进游戏库是一格格方形封面, 选中某个游戏后整个背景换成一张横版大图, 大图正中再浮着游戏的文字 logo。我这个库里有四十多个 Switch 游戏, 一开始封面缺了一大半, 有图的也尺寸、比例、透明通道各不相同; 标题、发行信息这些元数据同样是缺的。手动去网上找图、一张张裁、再一个个填, 换到第十个游戏我就放弃了, 于是把它写成了一条批量流水线。封面和元数据看起来是两件事, 其实都是同一类活: 把外部的媒体数据整理成前端能直接用的格式。
前端要的三张图, 各管一块界面
最开始的误区是把三种图当成同一种封面。实际上它们各管界面的一块区域, 规格也不一样。
icon.png 是方格列表里那张方形封面, 也是游戏卡片的主图, 规格是 512×512、不透明的 RGB。hero_1.jpg 是选中游戏后的全屏背景, 横版 16:9, 我统一成 1280×720, 居中裁剪, 避免拉伸变形。title.png 最容易被搞错——它不是封面, 而是浮在背景之上的文字 logo 浮层, 必须是透明背景的 PNG(RGBA), 只在游戏 logo 处不透明, 同样 1280×720。
分辨方法很直接: 把这张图单独放在白底上看。如果它占满整张、有背景色, 那就是封面; 如果只有中间一行游戏名字、四周透明, 那是 logo 浮层。这个区分做对了, 界面才不会出现"方形 logo 糊在背景中间"或者"背景被拉成方块"的怪样子。有些游戏在 SteamGridDB 上根本没有横版 logo, 硬塞一张方形的进去只会变成中间一块糊图, 这种情况我干脆删掉 title.png, 让它不显示浮层, 反而干净。
flowchart TB
A["ROM 目录名 / TitleID"] --> B["SteamGridDB API<br/>grids / logos / heroes"]
B --> C["下载原图"]
C --> D["统一规格<br/>居中裁剪 / 去黑图 / 透明通道"]
D --> E["adb push 到<br/>consoles/<平台>/<ROM名>/"]
E --> F["删除 rom_asset_index.fb<br/>重启前端重建索引"]SteamGridDB 的三个端点
三种图对应 SteamGridDB 的三个资源端点, 用同一个游戏 ID 分别请求。方形封面走 grids/game/{id} 并带上 types=static&dimensions=512x512, 直接拿到接近目标规格的方图; 透明文字 logo 走 logos/game/{id}, 这里要注意挑横版比例大于 1.8 的那张, 否则会拿到偏方形的 logo, 浮在背景上很难看; 背景横幅走 heroes/game/{id}, 原始图通常是 1920×620, 需要自己裁成 16:9。
端点本身不复杂, 真正费时间的是异常处理。SteamGridDB 在资源缺失时会返回一张约 18KB 的纯黑占位图, 而不是报错, 所以下载完必须做一次"是不是黑图"的判断, 否则背景会莫名变成全黑。免费 Key 还有请求配额, 批量抓的时候偶发 404; 同样的 URL, curl 能拿到而 Python 的 urllib 会 404, 后来我统一改用 curl 拉图绕过这个差异。游戏 ID 可以先按名字在 SteamGridDB 搜一次, 确认匹配的是正确作品再抓——搜错游戏、封面张冠李戴, 比没有封面更难发现。
用 Python 统一成固定规格
下载只是第一步, 关键是把所有来源不同的图裁成同一套规格。方形封面允许任何方形尺寸(比如原生 1024×1024), 缩放到 512×512; 没有原生方形的, 拿竖版图(常见 600×900)居中裁剪成方形再缩放。背景横幅统一从中心裁出 16:9 再缩到 1280×720。透明 logo 保持 RGBA 通道, 只做等比缩放, 不裁背景。
from PIL import Image, ImageStat
def center_crop(im, tw, th):
w, h = im.size
scale = max(tw / w, th / h)
im = im.resize((round(w * scale), round(h * scale)))
w, h = im.size
left, top = (w - tw) // 2, (h - th) // 2
return im.crop((left, top, left + tw, top + th))
def is_black(path):
p = Image.open(path).convert("RGB").resize((32, 32))
mean = ImageStat.Stat(p).mean # 三通道均值接近 0 即占位黑图
return max(mean) < 4方形封面处理后存成不透明的 PNG(RGB), logo 浮层存成带 alpha 的 PNG(RGBA), 背景存成 JPEG。三个文件名固定为 icon.png、title.png、hero_1.jpg, 放进前端约定的游戏目录 assets/media/roms/consoles/<平台>/<ROM名>/。同一套逻辑换到 3DS 平台时, logo 浮层的规格不一样: 3DS 用的是横版透明 logo 横幅(有的甚至接近 4998×2332), 不能套 Switch 的 1280×720。后来我把规格按平台做成参数, Switch 一套、3DS 一套, 而不是写死一个尺寸。
新游戏接入与索引重建
前端不会实时扫描图片, 它是懒加载的: 目录里放好文件后必须删掉 rom_asset_index.fb 再重启前端, 让它重新建立索引, 否则新封面不出现。这个索引文件在 assets/media/roms/consoles/<平台>/ 下。刚开始我不知道这一点, 传完图看不到变化, 反复重启排查了很久。
还有一条和 ROM 命名绑定的规则: 封面目录名要和 ROM 文件名(去掉扩展名)一致。我给游戏统一命名成 <英文官方全名> [<TitleID>].xci 之后, 只要改名, 封面目录的匹配就会失配, 需要重新索引并重抓。好在存档不受影响——模拟器认的是 TitleID, 改文件名不动 TitleID, 存档和 mod 都不变。所以命名规范最好在入库前一次性定好, 别等到封面都配完了再改。
元数据从哪来: 四个源的门槛
封面只是媒体数据的一半, 另一半是标题、发行信息这些元数据。前端抓取元数据时有四个源可以选, 每个的门槛都不一样, 我把它们都试了一遍。
iiSU 默认的 IGDB: 先要一把 Twitch key
iiSU 内置的默认源是 IGDB, 数据质量不错, 但要接进去得先有一把 Twitch 的 key——IGDB 的 API 挂在 Twitch 账号体系下, 得先注册 Twitch、申请开发者应用才能拿到。对只是想让掌机封面好看一点的人来说, 这个前置步骤偏重, 也是我最后没把它当主力的原因。
SteamGridDB: 最省事的那个
SteamGridDB 的门槛最低, 免费 key 在个人设置页申请就能拿到, 拿到后直接请求图片资源, 三类资源的端点前面已经写过。它也有自己的坑, 但重点是在四个源里它最省事: 不需要账号体系审核, 不需要开发者身份, 拿 key 就能用。
ScreenScraper: 要开发者凭据
ScreenScraper 是那种老牌刮削源, 元数据字段比 SteamGridDB 全, 适合想连发行年份、玩家数、简介一起抓的人。但它卡在一道凭据门槛上: API 强制要求 devid 和 devpassword 两个开发者凭据, 而不是普通用户的账号密码。iiSU 内置了一个 devid, 但那个是无效的, 一请求就报 Erreur de login。注册普通账号是不够的——账号只能登录网站; 要用 API, 得再去申请开发者身份, 拿到属于自己的 devid/devpassword。所以这条路我给它的定位是"想要更全的元数据时再去折腾", 而不是日常首选。
TheGamesDB: 免费 API 直接报错, 还有版本 bug
TheGamesDB 本来是最适合做兜底的免费源, 但实际完全用不了。它的免费 API 对请求直接返回错误(HTTP 418), 连正常响应都拿不到; 更麻烦的是 iiSU 0.0.7.4 在这个源上有一个已知的 bug(项目里的 issue #401), 就算 API 通了也搜不到结果。两个问题叠在一起, 这个源在实际操作里可以直接跳过。
怎么选
把四个源放在一起看, 取舍其实很清楚。
flowchart TB
A["iiSU 抓取元数据"] --> B{"选哪个源"}
B -->|默认| C["IGDB<br/>需 Twitch key"]
B -->|要封面和 logo| D["SteamGridDB<br/>免费 key 最省事"]
B -->|要更全的元数据| E["ScreenScraper<br/>需开发者 devid"]
B -->|TheGamesDB| F["HTTP 418 + 版本 bug<br/>基本不可用"]
C --> G["抓不到就手动兜底<br/>Custom Data"]
D --> G
E --> G日常就用 SteamGridDB 的免费 key, 它覆盖了封面、logo、背景三类图, 申请成本最低; 想要更完整的元数据字段, 再去申请 ScreenScraper 的开发者凭据; IGDB 留作备选, 前提是愿意配 Twitch key; TheGamesDB 暂时不用考虑。
抓不到就手动兜底
不管用哪个源, 冷门游戏匹配不上都是常态。前端里留了一条手动通道: 对某个 ROM 用「Get/Update (Custom) Data for ROM」, 手动输入标题去搜索并选中正确条目, 之后封面和元数据就会按这个匹配来抓。我库里好几个冷门游戏的封面就是靠这一步补上的。
整套跑顺之后, 四十多个 Switch 游戏的 icon 全部统一成了 SteamGridDB 的方形图, 其中约三十六个配齐了透明 logo 和背景横幅, 少数没有合适素材的只留封面; 元数据这边以 SteamGridDB 打底, 冷门的靠手动指定。封面和元数据说到底是一件事的两面: 前端要的不是文件本身, 而是整理成它认的规格和条目。值钱的经验不在接口, 而在"三种图的分工、黑图占位、索引重建、平台差异"以及"每个源的账号和权限体系"这些坑里。