数据模型
API 使用 Prisma + PostgreSQL。核心设计:全局媒体去重 + 用户个人库 + 按指纹共享字幕。
ER 关系
Media(全局媒体库)
按来源分两种类型:
| 字段 | file | youtube |
|---|---|---|
fileHash | SHA-256,唯一 | 下载后填充 |
youtubeVideoId | — | 唯一 |
audioS3Key | 创建时有值 | 下载后填充 |
coverUrl | App 生成 | YouTube 缩略图 |
去重键:
- 本地文件:
fileHash - YouTube:
youtubeVideoId
处理状态(subtitleStatus):pending | processing | success | failed
辅助字段:subtitleLog(Worker 日志)、subtitleError(失败原因)
UserMedia(用户媒体库)
用户与全局媒体的关联表,支持:
- 标题覆盖:
UserMedia.title优先于Media.title - 标签:
tags: string[],首页可按 tag 筛选
删除操作仅移除 UserMedia,不影响全局 Media 与其他用户。
Subtitle(字幕)
- 通过
fileHash与音频关联(即使mediaId尚未绑定也可存在) (fileHash, language)唯一- 创建后挂载到具体
Media.mediaId
指纹复用
attachSubtitlesByFileHash(mediaId, fileHash):
- 查找同
fileHash的所有字幕 - 批量更新
mediaId指向新媒体 - 将
Media.subtitleStatus设为success
这使用户 B 上传用户 A 已处理过的相同音频时,零等待获得字幕。
SubtitleSegment(字幕片段)
| 字段 | 说明 |
|---|---|
segmentIndex | 顺序号,与 Speech SSE 的 index 对应 |
text | 转写文本 |
startSeconds / endSeconds | 时间轴 |
translation | 译文 |
translationCached | 翻译是否来自缓存 |
子表:
- SubtitleToken:Sudachi 分词,含 surface、reading、pos、kanji
- SubtitleBunsetu:Ginza 文节,含 type、meaning、tokenIndices
Infra(运行时配置)
Infra 表存储键值型基础设施配置,API 启动时加载到内存。用于在不重启代码的情况下调整部分运行参数。
索引策略
主要查询路径:
- 用户媒体列表:
UserMedia.userId+createdAt DESC - Worker 认领任务:
Media.subtitleStatus+sourceType+audioS3Key - 字幕分页:
SubtitleSegment.subtitleId+segmentIndex - 指纹查找:
Media.fileHash、Subtitle.fileHash