☰
从OCR到数据面板:构建PS5本地媒体与游戏数据管理工具
2026/10/15 7:33:37 网站建设 项目流程

1. 为什么会有 AnyPS5:三个我一直没被解决的痛点

PS5 玩家玩到最后,其实不是在玩某个游戏,而是在玩“管理”。截图越攒越多,U 盘导出来一看,几千个文件躺在乱糟糟的目录里;奖杯进度想复盘,得一个一个游戏翻进系统里去查;存储空间永远不够用,但每次清理前都要纠结半天到底删哪个。

我做了一个叫 AnyPS5 的小工具,专门把这些分散的信息汇总到一个面板里。它不碰主机的系统层,不做任何灰色操作,纯粹站在“玩家自己的数据”这个角度,帮你把截图录像自动归类、游戏库统一管理、奖杯进度追踪、游玩时长统计、存储空间分析这五件事串成一条流水线。整套方案都是基于官方支持的导出方式和本地数据整理,凡是折腾过 PS5 辅助工具、或者想找个真实项目练手的开发者,都能从这里拿走可复用的思路。

1.1 痛点一:几千个截图文件,根本没法找

我的截图习惯是在游戏里随手按分享键,看到好画面就存一张。半年下来,导出的媒体文件数量轻松超过两千。问题是导出来的文件结构并不规整,部分文件按游戏目录分好了,部分又堆在一起,文件名还带着一串看不出含义的 ID。想找一张“上周打 boss 时的截图”,只能凭记忆往下翻,运气好几分钟,运气不好半小时就没了。

更好的归类方式是让程序替我做这件事:截图画面里通常有游戏标题、菜单文字、任务提示这些信息,只要把图片交给 OCR 识别,再和游戏列表做一次模糊匹配,就能判断“这张图属于哪个游戏”。这个思路不复杂,但涉及图像预处理、字符识别、相似度计算好几层问题,值得做成一个独立模块去调优。

1.2 痛点二:想清理存储,全靠肉眼判断

主机存储页能看每个游戏占了多少空间,但只显示列表,不给建议。我面对的典型场景是:空间剩 80G,装了十来个常玩的和一堆通关后没删的游戏,删哪个最划算?

如果有一个面板按“最近游玩时间 + 占用空间 + 奖杯完成度”三列排序,一眼就能锁定“上次打开是四个月前、占 60G、奖杯才 20%”的冷门游戏,删起来就有依据了。AnyPS5 的存储模块就是干这个的。它不做实时读取——主机没有对外开放这类接口,所以数据以玩家录入为主、以社区公开信息补全为辅,最终给出的是一份“清理决策参考表”,不是实时监控。

1.3 痛点三:玩了什么、玩了多久,全凭感觉

我常和朋友争论“我这个月到底肝了多少小时”,以前只能凭印象。但截图文件自带拍摄时间戳,把这些时间点按游戏聚类,就能还原出大致游玩时段。某天晚上连续拍了二十张截图,说明那段时间在玩;某个周末截图间隔都很短,说明那是一场超过两小时的深度游玩。

用时间戳反推游玩记录,不需要装后台程序,也不依赖任何外部接口,只要把截图归好类就能算。这是 AnyPS5 里我最满意的设计,后面会详细展开。

1.4 为什么不做成脚本,而要做成独立项目

一开始我只是写了个 Python 脚本,把导出的截图按目录名重命名。但脚本越写越长:要处理 OCR、要建游戏库、要看统计报表、还得在手机上也能打开看。脚本撑不住这种复杂度,我才决定重构成一个前后端分离的小项目。独立项目带来的好处是明显的:数据有结构化存储,功能可以逐个迭代,界面操作比命令行友好太多,而且家人朋友也能直接用,不需要学命令。

做之前我给自己定了三条边界:不碰主机系统文件、不抓任何私下接口、所有数据都从官方导出和个人录入来。边界一旦划清楚,后面踩坑的概率大大降低。

2. 整体架构:先画清楚数据流,再写第一行代码

2.1 技术选型:Python 后端 + 轻量前端 + SQLite

后端选 Python 3.11,理由很直接:OCR、图像处理、文件扫描这些活儿,在 Python 生态里都有现成的轮子,PyTesseract、OpenCV、Pillow 装上就能用。服务框架用的 FastAPI——异步支持好,自带接口文档,前端要啥数据直接拖。

前端没有上重框架,用 Vite 搭了一个轻量页,核心页面就五个:概览、游戏库、媒体时间线、奖杯记录、统计报表。数据量级别是几千张图片、几百个游戏条目,SQLite 完全够用,还省掉了数据库部署的麻烦。整个项目一条 Docker 命令就能起,后端、前端、数据库全在一个容器里,对个人项目来说维护成本最低。

2.2 数据通道:官方导出为主,手动录入为辅

我最开始也想做“全自动同步”,调研一圈后发现这条路走不通:主机端没有对普通玩家开放可用的数据接口,系统设置里的信息也读不出来。所以 AnyPS5 的数据来源分两路。

第一路是媒体文件。玩家把主机里的截图和录像通过 U 盘导出,放进一个约定的输入目录,后端定时扫描,提取文件名、拍摄时间、图像内容。这一路几乎全自动。第二路是游戏安装体积和奖杯进度。安装体积通常在系统存储页能看到,奖杯列表在系统奖杯页能看到,这些数据靠玩家在面板里填写或导入。实测下来,两条通道配合,信息完整度能到八成以上,剩下的靠社区公开资料补全。

2.3 数据库设计:五张表撑起全部功能

设计阶段我花最多时间的不是功能页面,而是数据库表结构。核心五张表如下:

CREATE TABLE games ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL UNIQUE, normalized_title TEXT NOT NULL, cover_path TEXT, genre TEXT, release_date TEXT, install_size_mb INTEGER, last_played TEXT ); CREATE TABLE media_assets ( id INTEGER PRIMARY KEY AUTOINCREMENT, game_id INTEGER REFERENCES games(id), file_path TEXT NOT NULL, file_type TEXT CHECK(file_type IN ('image','video')), captured_at TEXT, imported_at TEXT DEFAULT CURRENT_TIMESTAMP, ocr_text TEXT, match_source TEXT ); CREATE TABLE trophy_snapshots ( id INTEGER PRIMARY KEY AUTOINCREMENT, game_id INTEGER REFERENCES games(id), record_date TEXT, bronze INTEGER, silver INTEGER, gold INTEGER, platinum INTEGER, total_percent REAL ); CREATE TABLE play_sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, game_id INTEGER REFERENCES games(id), started_at TEXT, ended_at TEXT, duration_minutes INTEGER );

games 表是主表,media_assets 记录每张截图和录像,trophy_snapshots 存的是奖杯进度的“快照”而不是全量奖杯列表——这样每次记录都留痕,能看到进步曲线。play_sessions 是后端根据时间戳聚类自动生成的,也可以手动补录。第五张表是 system_meta,存版本号、导入批次这些运行信息,建立索引时我用的是 (game_id, captured_at) 联合索引,按游戏查时间线时响应速度很快。

3. 模块一:截图和录像的自动归类

3.1 先从 PS5 导出的文件结构说起

实际导出时,主机会把截图和录像按照游戏聚合,但表现并不统一:一部分媒体会放在以游戏名命名的目录里,另一部分却混在按时间生成的目录中,文件本身是 “一串 ID + 序号 + 时间戳” 的命名规则。第一次扫描时我统计过,命名规范的目录只占约六成,剩下四成的文件完全没有目录线索。

所以自动归类不能只靠文件名,必须上图像识别。截图画面的四角和顶部区域经常带着游戏标题、版本信息或界面文字,把这些文字提取出来,就能当判断依据。

3.2 OCR 识别 + 文件名双通道判断

我的处理管线分三步。第一步预处理:读取图片后转灰度,适当放大两倍,再做一次自适应二值化,让文字更清晰。第二步识别:调用 Tesseract 的中英文混合模型,把整张图的文字提取成字符串。第三步匹配:把识别到的文字和游戏库里的标准化标题做相似度计算,同时把文件夹名也加入计算,两边取置信度高的一方作为归属。

核心代码如下:

import pytesseract from PIL import Image, ImageOps from difflib import SequenceMatcher def recognize_game_from_screenshot(filepath, candidates, folder_hint=""): img = Image.open(filepath).convert("L") img = img.resize((img.width * 2, img.height * 2), Image.LANCZOS) img = ImageOps.autocontrast(img) custom_config = r"--psm 6 -l eng+chi_sim" text = " ".join(pytesseract.image_to_string(img, config=custom_config).split()) best_title, best_score = None, 0 for title in candidates: score = SequenceMatcher(None, text, title).ratio() if score > best_score: best_score, best_title = score, title folder_score = SequenceMatcher(None, folder_hint, best_title).ratio() if best_score >= 0.65 or (folder_hint and folder_score >= 0.7): return best_title return None

实际跑下来,中英文混合场景里英文标题识别率很高,中文标题受字体和游戏内 UI 干扰,识别率稍低,所以必须容忍一定比例的“未识别”,把这些图片放进待确认区。

3.3 识别失败怎么办:未知文件夹与人工确认

任何 OCR 方案都做不到百分之百。游戏内 UI 干扰、暗场景截图、动态模糊画面,识别结果经常为空。我的处理原则是“宁可少归,不要错归”。所有置信度不够的截图统一进入 unknown 目录,面板上提供一个人工确认页面:左边是缩略图,右边是一个下拉框,选完游戏就自动归档。实测约十二分之一的文件需要人工看一眼,量不大,但最后数据库的准确率能到九成八以上。

3.4 按游戏构建媒体时间线

归类完成后,每个游戏就自动生成一条媒体时间线。时间线按拍摄时间排列,缩略图瀑布流呈现,点开能看到大图和拍摄日期。这个功能的开发成本不高,却是我用得最多的页面——回顾一场攻略战的截图、给朋友展示精彩瞬间,都变得特别方便。

4. 模块二:游戏库与奖杯进度追踪

4.1 元数据补全:不碰系统的社区数据方案

新建一个游戏条目时,如果只记一个标题,面板会非常干瘪。我希望能补上封面图、类型、发售日期这些信息。主机系统不提供导出接口,所以这类元数据我用社区公开数据来补全——通过标题精确匹配,抓取游戏简介和封面资源。需要提醒的是,抓取频率要克制,匹配失败就标记为“待补充”,不要在每次扫描时反复请求,否则很容易被对方限流。我在代码里做了一层本地缓存,同样的游戏标题只查一次,结果存在 SQLite 里,后续直接读缓存。

4.2 奖杯进度怎么记录才不麻烦

奖杯进度的困境是:系统里数据清清楚楚,但导不出来。手动逐条录入太累,我的方案是“快照式记录”。你不用记录每个奖杯的名称和条件,只需要在第一次录入时填“白金、金、银、铜”四个数和一个完成百分比,之后每次想更新了,打开游戏看看奖杯页,把新数字填进表单,系统自动生成一条记录。

这样做有几个好处:录入成本低,十秒一条;形成历史记录后能画完成度曲线;也不依赖任何非官方接口,完全合规。曲线对你识别“这个游戏我还玩不玩了”特别有用——连续几次没有新进度,就该考虑放进待清理列表了。

4.3 面板怎么展示才真正有用

游戏库页面我按“最近游玩时间”排序,而不是按标题。每个游戏卡片显示封面、标题、安装体积、上次游玩时间、奖杯完成度五个字段。顶部一排筛选条件:按类型筛选、按有没有归档状态筛选、按奖杯完成度过低筛选。

实际使用中最有帮助的是一个叫“待决清单”的分区:它自动找出“上次游玩超过 90 天”且“奖杯完成度小于 40%”且“安装体积大于 30G”的游戏。不用我自己计算,页面直接告诉我“这几个游戏是主要清理对象”。这比干巴巴的存储列表有用得多。

5. 模块三:存储空间与游玩时长统计

5.1 存储监控做不到实时,但可以做决策支持

必须承认,AnyPS5 的存储监控不是实时的,因为没有官方接口可用。我的做法是把存储信息当作“定期更新的快照”来管理:玩家在系统存储页看一眼各游戏的占用体积,填到面板里;后端把这些数据和上次填报的差异记录下来,估算这些天里游戏体积的增长情况。

面板用一张柱状图展示“体积排行”,旁边给出“删除建议指数”,计算公式是:

建议指数 = 占用体积占比 * 冷落天数占比 * (1 - 奖杯完成度)

三个维度相乘,冷门、占地方、还没怎么玩通的游戏指数自然靠前。指数高的排在表格最前面,配合一个“一键归档”按钮——归档不是删除文件,而是把游戏从主列表移到归档列表,节省的是画面心理负担,真正删不删还是玩家自己决定。这样设计既诚实,又完全规避了越权读取系统数据的问题。

5.2 游玩时段从截图时间戳里“挖”出来

这是 AnyPS5 里我最得意的设计。截图本身带有拍摄时间戳,而玩家在玩游戏时倾向于随手截图,所以同一游戏在一定时间窗口内的截图时间点,就是游玩时段的投影。

算法逻辑不复杂:把某游戏的所有媒体按拍摄时间排序,相邻两条间隔不超过 90 分钟就归为同一段,超过 90 分钟就切一刀。一段聚类中,最早时间减 10 分钟作为开始,最晚时间加 15 分钟作为结束,中间的时长就是一次游玩会话。

def build_sessions(media_items, gap_minutes=90, buffer_start=10, buffer_end=15): items = sorted(media_items, key=lambda x: x.captured_at) sessions = [] current = [items[0]] for item in items[1:]: gap = (item.captured_at - current[-1].captured_at).total_seconds() / 60 if gap <= gap_minutes: current.append(item) else: sessions.append(current) current = [item] sessions.append(current) result = [] for group in sessions: start = group[0].captured_at - timedelta(minutes=buffer_start) end = group[-1].captured_at + timedelta(minutes=buffer_end) result.append((start, end, (end - start).total_seconds() / 60)) return result

这个方案不装后台服务、不采集额外数据,唯一的代价是对“不截图的时间段”不敏感。比如一个人玩了一晚上但一张图都没截,这段时长就不会被计入。所以在面板里我把这类结果标注为“基于截图活动的估算时长”,允许玩家手动补录。我自己的使用习惯是:手动补录只在想精确统计某款游戏时做,日常看趋势完全够用。

5.3 周报/月报:把游戏习惯变成可复盘数据

统计报表模块把会话记录聚合成每日、每周的趋势。我用日历热力图展示“每天游戏时间总量”,颜色越深当天玩得越久;旁边是本周最常玩的三个游戏排名。刚开始做这个页面只是为了好玩,后来发现它对管住自己很有帮助——看到上周某游戏占了 20 小时,就会下意识反思时间分配。

报表的数据口径有三条:同一游戏一天内多段会话合并计时长;跨天会话按结束时间归属日期;只统计置信度高于 0.7 的自动识别结果加手动录入。口径统一之后,报表数字才值得参考。

6. 踩坑记录:四个让我半夜改代码的问题

6.1 Tesseract 缺中文字体,识别率掉到三成

第一次跑 OCR,英文截图识别得很顺,一到中文标题就全乱码,识别率掉到三成以下。排查发现问题是 Tesseract 的语言包虽然装了 chi_sim,但系统里缺少对应的中文字形渲染支持,导致预处理后的图片在识别阶段匹配不到字符。解决方案是在 Docker 镜像里补装中文字体,并设置环境变量指向字体目录。

我的教训是:OCR 的问题百分之六十出在预处理和环境,而不是识别算法本身。遇到识别率异常,先检查字体、图像尺寸、对比度这三个因素,不要急着调模型参数。

6.2 图片时间戳比文件时间早八小时

截图在主机端记录的是本地时间,但导出到电脑后,有些文件的文件系统时间会转换成 UTC,导致“文件修改时间”和“截图拍摄时间”相差八个小时。我在归档环节用过文件修改时间做排序,结果时间线一片混乱。

解决方案是:所有归档判断只用图片 EXIF 里的原始拍摄时间,不用文件系统时间;读取失败时才回退到文件名中的时间戳字段。数据库里时间统一存 ISO 8601 格式,显示时再转时区。

6.3 SQLite 并发写入把成绩表卡死

做媒体导入时,后端是异步任务,前端同时也会写奖杯记录,偶尔还有手动补录操作。几个写操作撞在一起,SQLite 直接报 “database is locked”。我之前没在意,直到一次批量导入把整个服务卡了三十秒。

修正方案有三层:一是所有写操作都走同一个数据库连接队列,不并发写;二是大批量导入用事务包裹,每五千条提交一次;三是把写频率高的媒体表单独建一个连接参数,开启 WAL 模式。

DATABASE_URL = "sqlite:///./data/anyps5.db?timeout=30" PRAGMA journal_mode = WAL;

改了之后,我再没遇到锁库问题。SQLite 对付个人项目的读写量绰绰有余,前提是别把它当 PostgreSQL 用。

6.4 前端图表一次性渲染五千个点

媒体时间线页面加载全部缩略图时,浏览器一次拉几百张图片,直接卡死;统计报表的日历热力图在数据量增长后也明显掉帧。我用了三个办法:列表页改为按拍摄月份分页,每页最多五十条;缩略图异步懒加载,滚动到才加载;热力图按周聚合数据,而不是按天。

这轮优化之后页面流畅度提升明显,也让我意识到,个人项目里性能问题往往不是后端慢,而是前端一次拿太多数据。限制单次请求的数据量,比分库分表实在得多。

7. 部署运行与使用节奏

7.1 用 Docker 把整套环境锁死

个人项目最容易出现“在我机器上能跑”的问题,所以我把运行环境全部写进了 Docker。镜像里装好 Python 依赖、OCR 语言包、中文字体,前端构建产物直接打进镜像。部署只需要一条命令:

services: anyps5: image: anyps5:latest build: . ports: - "8000:8000" volumes: - ./media:/data/media - ./data:/data/db environment: - TZ=Asia/Shanghai - OCR_LANG=eng+chi_sim

这里有个部署细节:媒体目录和数据库目录必须挂载到宿主机,否则容器一重建数据就没了。我吃过一次亏,重跑容器把刚整理完的两千张截图分类结果全丢了,从那以后所有持久化数据一律走 volume,绝不放在容器层里。

7.2 我的“每周两次导入”工作流

工具做出来之后,最重要的是一套能坚持的使用节奏。我现在固定每周两次做媒体导入:把主机里的新截图和录像导出到 U 盘,拷贝进输入目录,打开面板点一次“扫描导入”,系统自动归类并生成时间线。全程大概五分钟。每月底花十分钟更新一次游戏体积和奖杯进度,存储模块的建议指数才会保持准确。

这个节奏坚持了两个月,数据库里已经积累了足够多的会话记录,现在统计报表的参考价值很高——哪个游戏占用了最多时间、哪个游戏已经彻底冷却,一目了然。

7.3 给想自己动手的人三点建议

如果你也想做类似的主机信息管理项目,我的建议是先砍需求再动工。第一版不要做报表、不要做人工确认页,只做“扫描 + 归类 + 展示”三件事,跑通之后再按实际使用反馈加功能。第二个建议是数据库设计要认真做,尤其是一开始就要区分“媒体文件”和“游戏本体”这两类完全不同的实体,不然后面拆表很痛苦。第三个建议是给所有外部资源调用加缓存,无论是元数据还是封面图,没有缓存的个人项目迟早被限流卡住。

我还想提醒一点:这类工具的价值从来不在炫技,而在每天少折腾十分钟。从我自己的体验看,AnyPS5 之后最直观的变化是找截图不用再翻文件夹了,清空间前先看面板而不是凭感觉,一个月下来省下的时间足够再做两个小项目。这个项目目前还躺在我的个人仓库里,后续打算给它加上多设备同步和移动端适配,如果你也有类似想法,欢迎从这套架构里拿点思路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询