格式转换器首轮实施记录
状态:首轮功能已交付,原计划的全部验收条件尚未满足(2026-10-09;实际交付见 §12.2)。 编写日期:2026-10-08;校对日期:2026-10-12(Asia/Shanghai)。 首版范围(图片 + 短音频)已超额交付为八个能力区(图片 / 音频 / 视频 / 文档 PDF / 表格 / 文本字幕 / 电子书 / 压缩包),全部在浏览器本地完成。子系统说明见
docs/architecture.md§6.17,玩家向说明见docs/guide/格式转换器使用指南.md。 下文的首版范围、默认参数与技术路线保留为当时的实施建议(§7.1 的限额表已由src/lib/file-converter/formats.ts的LIMITS取代,仍属「起始值、非实测上限」)。 本文作为已交付功能的实施记录留在 docs 根目录;剩余验收与扩展统一登记在docs/plan/format-converter-roadmap.md§16,不把「功能存在」当成 M0–M4 全部验收通过。
长期产品形态、八类文件的转换方向与具体处理路线,另见
docs/plan/format-converter-roadmap.md。§1–§11 保留图片与短音频首版的原始实施建议,
用于对照当时的范围、约束和验收要求;当前实现与偏差以 §12 及代码为准。
1. 目标与首版边界
1.1 用户要完成的事情
在工具箱中进入「格式转换器」,选择本地文件和目标格式,转换后下载到自己的设备。 首版优先解决日常图片与短音频的兼容问题,例如:
- 把 JPG 图片转换为 PNG,或把带透明背景的 PNG 转成指定背景色的 JPG。
- 把 WebP 图片转换成更容易被其他软件接受的 JPG / PNG。
- 把 OGG 录音转换为 MP3,或把 WAV / FLAC / M4A 音频转换为 MP3。
- 一次选择多个同类文件,逐个转换、逐个下载,看到各自的成功或失败结果。
文件格式由实际解码和重新编码改变。只改扩展名不能完成转换。
1.2 首版必须交付
| 项目 | 首版要求 |
|---|---|
| 入口 | 工具箱中默认可见的「格式转换器」卡片,站内路径 /tool/convert |
| 分类 | 「图片」「音频」两个标签页,分别展示支持范围 |
| 文件选择 | 点击选择与桌面拖放,手机保留完整的点击选择流程 |
| 图片转换 | 静态 JPEG / PNG / WebP 输入,PNG / JPEG 输出;WebP 输出按设备能力提供 |
| 音频转换 | MP3 / WAV / FLAC / M4A / OGG 输入,MP3 / WAV 输出;具体编码组合按 M0 白名单验收 |
| 参数 | 图片质量、JPG 背景色;MP3 码率 |
| 反馈 | 识别、加载、排队、转换、成功、失败、取消等状态明确区分 |
| 结果 | 输出名称、格式、体积与图片预览 / 可用的音频试听,以及手动下载按钮 |
| 恢复 | 单项失败不阻断后续任务;允许取消、重试和释放结果 |
| 隐私 | 转换只在浏览器内进行,用户文件不发送到本站或第三方服务 |
| 集成 | 遵守本站样式规范、亮暗主题与文档登记规则 |
纯本地工具建议与现有编码、哈希工具一样匿名可用,不增加角色门槛、鱼干收费或 专注模式限制。这是本计划采用的产品默认值,不代表用户已确认所有产品细节。
1.3 首版范围之外
视频转码、视频提取音频、动画格式互转、HEIC / TIFF / RAW、SVG、PDF / Office 文档、 压缩包转换、裁剪、剪辑、降噪和文件修复均属于后续扩展。
首版也不提供任意 FFmpeg 命令、URL 导入、自动上传图床 / 音频床、云端任务历史、 跨设备继续任务和一键 ZIP 打包。批量任务按同一媒体类别建立,混入其他类别时逐项 说明原因,不按扩展名猜测后强行转换。
2. 本站现状与接入约束
2.1 已核对的现状
| 落点 | 现状 | 对实施的影响 |
|---|---|---|
src/app/tool/ToolMenu.tsx |
工具清单、搜索、分类与卡片集中在同一文件;现有分类为站务、编码、加密 | 新入口和分类在这里增加,保持站内链接 |
src/app/tool/hash/page.tsx |
已有纯前端工具与本地文件处理 | 转换器可以沿用浏览器处理的产品口径 |
package.json |
已有 sharp,没有 FFmpeg.wasm / Mediabunny | 图片原生路线无需新增图片库;音频需要新增依赖 |
src/lib/audio-upload.ts |
音频床刻意只保存原字节,未引入服务端 FFmpeg | 转换器独立完成转换,保持音频床现有上传契约 |
next.config.mjs |
中间件请求体上限配置为 12mb | 浏览器转换不走上传链路,不需要提高该值 |
docs/deploy.md §6 |
nginx 上传请求体上限配为 12m | 若以后增加服务端转换,必须重新设计上传边界 |
src/app/components/FrameBuster.tsx |
本站刻意允许第三方 iframe 嵌入 | 首版不能依赖嵌入方配合提供跨源隔离 |
src/lib/docs-catalog.ts |
每份项目文档都要登记,标题须与 H1 一致 | 本计划及未来使用指南均按同一登记规则加入 |
本站仍以现有 Next 应用作为部署单元。首版不需要新服务、数据库表、迁移、后台循环
或用户文件存储目录。权限与存储背景见 docs/architecture.md §6.6/§8,音频床见
docs/architecture.md §6.15。
2.2 工具箱入口安排
建议增加「文件处理」分类,卡片放在工具箱默认展开区域、位于站务工具之后。 格式转换是日常用途,入口不放进「更多开发者工具」折叠区。
- 卡片标题:格式转换器。
- 描述:图片与音频格式转换,文件在本机处理。
- 搜索词包含:格式、转换、文件、图片、音频、JPG、JPEG、PNG、WebP、OGG、MP3、WAV、FLAC、M4A。
- 标签:图片、音频、本地处理。
- 首版卡片使用站内路径,不链接到第三方转换站。
- 「全部」「文件处理」筛选和搜索均可找到入口;无匹配结果时不留下空分组。
菜单当前对默认展开部分和折叠部分的渲染方式不同,实施时要同时调整分类词表、 分组数据和实际渲染,不能只往数组加一项。保留既有工具入口的可达性测试。
3. 支持格式与转换结果的契约
3.1 图片矩阵
| 实际输入 | PNG 输出 | JPEG 输出 | WebP 输出 | 处理规则 |
|---|---|---|---|---|
静态 JPEG(.jpg / .jpeg) |
必须 | 必须 | 能力探测通过后提供 | 正确应用 EXIF 方向,默认保持像素尺寸 |
| 静态 PNG | 必须 | 必须 | 能力探测通过后提供 | PNG 输出保留透明;JPEG 输出先铺指定背景色 |
| 静态 WebP | 解码成功时必须 | 解码成功时必须 | 能力探测通过后提供 | 识别动画标记,动画文件拒绝进入静态流程 |
| APNG / 动画 WebP | 不提供 | 不提供 | 不提供 | 明确提示首版不支持动画,不能静默只保留一帧 |
| GIF、BMP、HEIC、TIFF、SVG、其他格式 | 不提供 | 不提供 | 不提供 | 显示不支持的格式,扩展时另加解码器与验收样本 |
浏览器原生解码失败时不得伪装成功。WebP 输出能力在运行时以小图试编码,并核对 返回 Blob 的 MIME / 字节;不支持则不给出可执行的 WebP 输出选项。 Canvas 请求不支持的类型时会回退到 PNG,这是必须显式防止的错误。 依据:Canvas toBlob 文档
输入同目标格式时按钮写「重新编码」,不承诺原字节不变。输出统一使用 .jpg、
.png、.webp 规范扩展名,并与实际字节和 MIME 一致。
3.2 音频矩阵
| 文件类别 | 首版纳入的内容 | 必须验证的目标 | 拒绝或提示的情形 |
|---|---|---|---|
| MP3 | 常见 MPEG 音频,含 CBR / VBR 样本 | MP3、WAV | 无音轨、损坏、无法获得可信时长 |
| WAV | 常见 PCM 16 / 24 位与浮点 WAV 样本 | MP3、WAV | 不在实测白名单内的压缩 WAV |
| FLAC | 常见单声道 / 双声道 FLAC | MP3、WAV | 超出声道、采样率或时长限制 |
| M4A | AAC in MP4 | MP3、WAV | ALAC、受保护内容或其他编码留到后续验证 |
| OGG | Vorbis / Opus 两种分别验收 | MP3、WAV | Theora 视频、多路音轨、首版未纳入的 Ogg 编码 |
容器支持不等于里面所有编码都支持。 检查以文件内容、媒体探测结果和实测白名单 为准;文件名与浏览器声明 MIME 只作提示。只含音频和附属封面图的文件可接受, 封面不导出;含正常视频轨、多个音频轨或无法识别的轨道时明确拒绝。
首版有意缩小输出集合为 MP3 / WAV。OGG、FLAC、M4A 输出按后续需求逐个增加, 每增加一项,都要写明容器、编码器、参数与设备播放兼容性,不只增加一个后缀选项。
3.3 默认参数与保真边界
| 参数 | 建议默认值 | 首版允许调整 |
|---|---|---|
| 图片尺寸 | 保持经方向归一后的像素尺寸 | 不提供缩放 |
| JPEG / 有损 WebP 质量 | 90% | 50%–100%,整数滑块;这是编码参数,不是保真百分比 |
| JPEG 背景色 | 白色 | 白色、黑色、自定义不透明颜色 |
| PNG | 浏览器原生无损像素编码 | 不提供无意义的「质量」滑块 |
| MP3 码率 | 192 kbps | 128 / 192 / 256 / 320 kbps |
| MP3 输出采样率 | 44.1 kHz | 首版固定,重采样时明确说明 |
| 音频声道 | 保持单声道 / 双声道 | 多声道首版拒绝,不静默混缩 |
| WAV 输出 | 16 位 PCM,保留已支持的输入采样率 | 首版固定;24 位 / 浮点输入要提示精度变化 |
| 元数据 | 不复制原始 EXIF、音频标签与封面 | 不提供保留开关 |
结果页需要说明:JPG 转 PNG 不能恢复已经丢失的细节;转成 PNG / WAV 后文件可能变大; 有损音频再次转码会损失音质;转成 WAV 不会提升原录音质量。WAV 输出的「无压缩」 不等于任意输入到输出都逐样本无损,例如 24 位输入到 16 位输出会改变精度。
首版面向普通网页图片与短音频,不承诺保留专业摄影色彩管理、ICC、HDR、相机 RAW、 打印 DPI、歌词与封面。EXIF 方向要正确,浏览器解码已应用方向时不能再重复旋转。 Canvas 输出也不承诺保留原文件的 DPI 元数据。
4. 技术路线与 M0 选型判据
4.1 建议基线
图片采用浏览器原生解码与 Canvas 编码;音频采用 FFmpeg.wasm 单线程核心。 转换器页面按需加载音频适配层和核心资源,图片流程不会触发音频库下载。
FFmpeg.wasm 在 Worker 内处理媒体,并提供单线程与多线程核心;官方示例将完整核心 标为约 31 MB。这里的数值只是上游示例,实际选用产物的未压缩体积、压缩传输量、 冷启动耗时与缓存命中要在 M0 分开测量。 依据:架构介绍、核心加载示例
4.2 三条路线的取舍
| 路线 | 文件在哪处理 | 优点 | 本计划的取舍 |
|---|---|---|---|
| 原生图片 API + FFmpeg.wasm | 用户浏览器 | 编码能力较独立,统一处理常见音频;本站无需计算服务 | 首版基线,接受按需加载的大核心 |
| 原生图片 API + Mediabunny / 专用 WASM | 用户浏览器 | 可只引入所需组件,传输量有机会显著降低 | M0 做有界比较,后续可替换音频适配层 |
| sharp + 原生 FFmpeg | 本站服务器 | 原生处理性能更好,用户设备负担小 | 后续独立方案,需要上传、并发、超时与二进制运维设计 |
Mediabunny 提供容器读写和转换能力,但部分编解码依赖浏览器。其 MP3 扩展使用 LAME WASM,上游标注约 130 kB gzip;这个数只属于编码器,不包括容器解析、 解码器与页面代码,不能直接和 FFmpeg 的完整核心体积作同比。 依据:支持范围、MP3 扩展说明
4.3 M0 必须回答的问题
- 锁定一组互相兼容的 FFmpeg wrapper / core 版本,实际产物包含哪些解码器、编码器 与媒体探测能力?对 §3.2 的每个输入组合给出通过或失败样本。
- 单线程核心在桌面 Chromium / Firefox / Safari 与真实 iPhone Safari 上是否可加载、 完成 OGG Vorbis / Opus → MP3、取消后继续工作?iframe 场景是否也能完成?
- Next 开发、生产构建和
next start中,wrapper Worker、核心 JS、WASM 的路径是否 都正确?从其他工具经客户端导航进入时是否仍能加载? - 原生图片流程是否正确处理透明、EXIF 方向、静态 WebP 和动画拒绝?所选设备的 WebP 编码能力探测是否与实际输出一致?
- 在相同样本和设备上,Mediabunny 路线是否更轻且完整覆盖必需音频组合? 如果依赖不具备的浏览器解码能力,则保留 FFmpeg 基线,不引入两套自动回退引擎。
- §7 的初始限制是否合理?完整核心加载后的常驻内存、成功后的残留内存、取消后 的恢复行为,以及重复转换是否有持续增长?
M0 的产物是兼容性与测量记录,不是「所有格式都支持」的声明。替换基线引擎的 条件是:必需组合与取消恢复均通过,实际总传输量更低,内存与处理时间没有影响 目标设备的明显退化;在记录中说明选择原因并更新本计划。
M0 不通过时,图片可以独立进入发布流程,音频保留在开发阶段。此时必须把交付 标为「仅图片阶段」,不能在卡片或完成记录中宣称图片+音频首版已经完成。
4.4 iframe 与部署边界
多线程 FFmpeg 需要 SharedArrayBuffer;其使用受跨源隔离和权限条件约束。 第三方父页面不一定配合这些条件,因此首版固定单线程,不新增全站 COOP / COEP, 也不新增 X-Frame-Options / CSP frame-ancestors。 依据:FFmpeg 多线程要求、浏览器跨源隔离规则
嵌入方的 sandbox 仍可能禁止脚本、Worker 或下载。工具不能绕过父页面设置; 条件不足时显示具体原因和现有可用的完整页面入口。iframe 内的工具不依赖会话 cookie。 HTTPS 与 localhost 是重点验收环境,其他访问环境以实际能力检测提供明确提示。
5. 页面与交互设计
5.1 页面结构
桌面从上到下呈现:标题与本地处理说明、图片 / 音频标签、文件选择区、目标格式和 参数、任务列表、结果预览。较宽视口可以将参数区和任务区分列;手机使用单列, 文件名可以换行,主要操作始终可触达,页面不横向溢出。
默认进入图片标签,用户可主动切换音频。仅切换标签不下载音频核心,用户开始首个 有效音频任务时才触发加载,并提示首次需要下载转换组件。页面不自动扫描、打开 或上传用户的其他文件。
用户文案示例:
- 文件只在你的浏览器中处理,不会上传。
- 首次转换音频需要下载转换组件,之后通常可复用浏览器缓存。
- 页面关闭后,尚未保存的结果会丢失。
5.2 操作与参数冻结
- 选择文件后逐项校验类别、大小与内容,不自动开始转换。图片可以读取尺寸与动画 标记;音频先做轻量类型初筛,完整音轨 / 时长探测在开始执行后进行,避免提前下载核心。
- 用户选择目标与参数,任务列出预计要执行的转换。
- 点击「开始转换」时为本批任务保存参数快照,按顺序执行。
- 转换期间可取消当前任务或整批任务;成功结果保留。
- 修改全局参数只影响下一批或重试生成的新任务,不改变已经成功或正在执行的任务。
- 用户手动试听 / 下载结果。重试必须显式说明沿用旧参数还是使用当前参数。
批量不触发多个自动下载,避免浏览器拦截连续下载。首版每个结果都有独立下载按钮。 处理期间切换类别不丢队列,两个类别共用一个执行调度器。
5.3 结果与可访问性
- 原文件名和输出名使用普通文本渲染,结果名形如
录音-converted.mp3;重复名称 加序号,不能把文件名当 HTML,也不能把原名直接作为引擎内部路径。 - 图片预览展示实际输出;透明图片使用棋盘背景,JPG 显示实际填充后的背景。
- 音频使用受控的本地 Blob URL,手动播放;播放不支持和转换失败分别显示。
- 原始体积、输出体积和增减比例来自实际文件,输出更大时照实显示。
- 选择区有键盘可用的按钮;拖放不是唯一入口。状态通过适度的 aria-live 播报, 不在每次进度事件上打断屏幕阅读器。
- 图片操作要能独立于音频核心失败而继续使用,失败项可被单独移除。
样式依据 docs/frontend-styles.md:复用已有按钮、字段、容器与主题令牌。
不引入另一套组件库;单行字段与多行内容采用各自规定的圆角与聚焦样式。
6. 前端模块与数据流
6.1 计划新增的模块
以下均为拟新增路径,当前尚不存在;实现时可以合并只承担少量职责的文件,避免 为了分层而增加转发模块。
src/app/tool/convert/
page.tsx 页面外壳、metadata、客户端入口
ConverterApp.tsx 标签页、参数、任务列表、结果
src/lib/file-converter/
formats.ts 格式词表、MIME、输出扩展名、参数与限制
types.ts 任务、结果、错误、引擎接口
inspect.ts 内容识别、尺寸 / 动画检查、输入初筛
image.ts 图片解码、方向、背景与 Canvas 编码
audio.ts 动态加载、探测、固定参数转码、资源回收
queue.ts 串行执行、取消、任务代次、结果预算
scripts/copy-converter-assets.mjs 从锁定依赖重建同源核心与许可资料
public/static/converter/ 可重建的 JS / WASM / 许可说明产物
src/styles-scss/pages/_tool-convert.scss
tests/unit/file-converter-*.test.ts
tests/e2e/file-converter.spec.ts
tests/e2e/file-converter-layout.spec.ts
formats.ts 是零依赖词表;客户端模块不能 import 带 Prisma、sharp、Node fs 的上传
服务。可复用现有纯工具函数,但不能为了复用一个常量把服务端模块带进客户端包。
6.2 数据流
用户主动选择本地 File
→ 类型 / 大小初筛
→ 内容识别、尺寸 / 动画检查或音轨探测
→ 建立任务,冻结目标格式与参数
→ 共用队列(同一时间仅一个任务执行)
├─ 图片:本地解码 → 背景 / 方向 → Canvas 编码
└─ 音频:按需加载 Worker → 写入临时文件 → 探测 / 转码
→ 校验实际输出格式、非空结果与资源预算
→ 本地 Blob → 预览 / 试听 → 用户手动下载
→ 释放不再使用的 URL、像素缓冲与引擎临时文件
这个流不调用图床、音频床或其他业务写接口。页面与核心的静态资源请求照常存在, 「不上传文件」不能表述成「页面没有网络请求」或「第一次访问就能离线使用」。
6.3 引擎接口与任务状态
两类引擎输出同一结构:输出 Blob、实际格式 / MIME、建议文件名、元信息与结果提示。 输入校验、引擎加载、媒体探测、转换和结果交付分别报告状态;引擎差异不泄露到菜单。
建议任务状态:
validating → queued → loading-engine / probing → converting
├─ succeeded
├─ failed
└─ cancelled
验证失败直接进入 failed,队列取消直接进入 cancelled。引擎可复用时跳过加载状态, 失败重试创建新的执行代次。每个异步回调校验任务 id 和代次,取消或移除后的旧结果 不能覆盖新结果。输出预算不足时队列进入明确的暂停状态,等待用户释放已有结果。
取消音频任务使用能终止实际计算的机制,不只拒绝一个等待中的 Promise。 FFmpeg 的 terminate 会终止 Worker 和进行中的调用,之后须重新 load。 正常完成在 finally 中回收临时文件;Worker 已终止时接受其文件系统一起消失, 不能让清理失败覆盖任务的原始错误。 依据:FFmpeg API
图片原生操作未必能中断底层编码。首版取消时丢弃晚到结果并回收资源,只在旧操作 真正结束后调度下一项;若 M0 发现大图明显阻塞交互,再采用经目标设备验证的 Worker / OffscreenCanvas 路线,不能宣称一个取消按钮必然立即停止浏览器编码。
6.4 音频探测与输出校验
音频任务使用内部生成的文件名和固定参数数组:显式选定唯一音轨、去掉封面与原 元数据、选择白名单编码器与参数。禁止拼接 shell 命令或接受用户输入的引擎选项。
写入一个输入后先探测时长、音轨、编码、采样率与声道;有探测能力时复用所选 FFmpeg 版本的探测 API。没有可用探测能力的版本不能直接跳过时长与音轨检查, 应在 M0 更换版本或确定等价方案。异常探测结果要拒绝继续编码。
成功判据包含退出码成功、实际输出非空、MIME / 扩展名 / 字节一致,每个音频输出 还要经过媒体探测确认所选编码、声道与采样率。不能把「生成了一段字节」当成可用 文件;失败时不下载半成品。 进度百分比仅在能够可靠获得时长时展示为估计值,完成状态以实际执行和输出校验 为准。加载进度与转码进度分开,不编造精确剩余时间。
7. 文件限制与资源生命周期
7.1 待实测的起始限制
⚠️ 下表是首版(图片+音频)的原始范围。实施后限额已扩展到八个能力区 (视频 / 文档 / 表格 / 文本 / 电子书 / 压缩包),唯一权威是
src/lib/file-converter/formats.ts的LIMITS—— 页面提示与校验读的是它, 本表不再逐项维护(见docs/architecture.md§6.17)。下面的理由与推导仍然有效。
以下数值是 M0 的初始实验条件,不是已测得的安全上限。统一放在词表模块, 页面提示与校验读同一份值。上线前可以依据测量下调,并在本节记录依据。限制作用于 一个页面实例;多个浏览器标签页分别占用设备资源,首版不协调跨标签页的任务。
| 项目 | 起始值 | 理由 / 处理 |
|---|---|---|
| 单张图片输入 | 20 MiB | 控制读取与容器解析成本 |
| 图片总像素 | 1600 万像素 | 压缩文件很小也可能解码成巨量内存;与字节限制并用 |
| 图片单边 | 8192 像素 | 防止极端长图;仍必须满足总像素限制 |
| 单段音频输入 | 20 MiB | 首版面向短录音与短音乐片段 |
| 音频时长 | 5 分钟 | 控制手机计算成本与 WAV 输出膨胀 |
| 音频声道 | 1 或 2 | 多声道不隐式降混 |
| 音频输入采样率 | 白名单内且最高 48 kHz | 更高采样率后续按样本验证 |
| 等待 / 持有的任务数 | 最多 10 项 | 防止无限排队与结果堆积 |
| 持有输入大小总额 | 100 MiB | 统计仍持有 File 的所有任务,跨两个类别计算 |
| 单个输出与保留输出总额 | 各最多 64 MiB | 输出大于预算则不保留,不自动触发下载 |
| 执行并发 | 1 | 图片和音频共用,避免同时争抢内存 |
| 核心加载超时 | 60 秒 | 可重试;图片流程保持可用 |
| 单次音频探测超时 | 15 秒 | 损坏文件不能让队列无限等待 |
| 单次音频转码超时 | 120 秒 | 超时终止 Worker,后续可重新初始化 |
例如 1600 万像素的 RGBA 原始缓冲约 64 MB,尚未计入解码副本、Canvas 与输出。 5 分钟、48 kHz、双声道、16 位 PCM 数据约 57.6 MB,尚未计入输入和引擎内存。 这两项说明字节小不等于内存小,也说明限制仍需真实设备验收。
WAV 输出可在转码前依据时长、采样率和声道估算;结果仍要复核实际体积。结果总额 不足时暂停后续任务,提示先下载并移除已有结果。单项预计超过输出上限时提前拒绝, 实际超过上限时丢弃结果并报告原因,不能长期持有超额 Blob。
输出体积检查不能替代执行中内存约束;Canvas 与 MEMFS 可能在检查之前就已经分配 大缓冲。首版使用输入、像素、音频时长限制降低成本,并测试畸形文件;仍可能出现 浏览器内存不足或标签页关闭,这种情况下不承诺当前任务可恢复。
7.2 回收规则
- 不为每个排队文件预先读取完整 ArrayBuffer,不预先解码所有图片或音频。
- 每次只为执行中的任务建立必要缓冲。音频结果取出后及时释放 MEMFS 中的输入和输出。
- 图片解码对象、Canvas、预览 URL 与音频 URL 在替换、移除、取消和组件卸载时回收。
- 结果不能刚显示就撤销 Blob URL;用户仍需预览、试听和下载。
- 移除任务只释放浏览器内的引用,不改变用户磁盘原文件,也不涉及站内业务记录删除。
- 下载后不假定文件已经落盘成功;结果由用户主动移除或离开页面时释放。
- 队列空闲后按 M0 确定的策略释放音频 Worker,重新使用时可以重新加载核心。
- 首版不保存文件到 localStorage、IndexedDB、OPFS 或服务端,不实现任务断点续作。
性能计时采用浏览器的单调计时 API;本地任务不需要写入数据库时间。
若未来确有业务记录写库,则遵循本站时间口径,见 src/lib/db-time.ts。
8. 核心资源、许可与部署
8.1 依赖与静态资源
实施时只增加必要的音频依赖,锁定 wrapper / core 的明确版本,不跟随 CDN 的 latest。
修改 manifest 与 lockfile 后用 npm ci 验证可重建,不升级 Next 或其他无关依赖。
是否需要 @ffmpeg/util 由适配代码决定,本地 File 读取与同源资源不必为了示例而
额外引入不使用的工具包。
wrapper 由 Next 打包,音频核心 JS / WASM 从锁定依赖复制到同源目录。
复制脚本参考 scripts/copy-emoji-assets.mjs 的可重建资产方式,给核心资源使用带版本
或哈希的子目录,避免旧 JS 与新 WASM 混配;公共生成目录进 .gitignore,二进制
不作为手工维护文件提交。脚本校验来源版本、必需文件和清单,缺文件时明确失败。
官方安装说明建议自行托管 wrapper,因其会创建 Worker;不能把从 CDN 直接 import wrapper 的示例当作适合本站的交付方式。 依据:FFmpeg.wasm 安装说明
复制脚本接入现有资源准备流程,并给跳过安装脚本的构建提供显式准备命令。核心来源 依赖必须在实际生产安装策略下可用,不能只列为被生产安装省略的开发依赖。检查项 覆盖核心 JS、WASM、wrapper Worker 路径、MIME 和文件清单。版本路径可配置长期缓存, 核心资源不修改时不重复下载;实际缓存结果以浏览器和部署响应头测量为准。
8.2 许可资料
引入 npm 代码与可重建 WASM 资产时核对相应版本的许可证、第三方库和构建配置。 FFmpeg wrapper 的 MIT 许可不代表核心也是 MIT,核心包含 FFmpeg 和外部编解码库, 适用许可可能受构建配置影响。复制脚本生成对应的许可说明、上游来源、版本、 构建方式与源码获取信息,不能只复制 wrapper 的 LICENSE 来代表全部产物。 依据:FFmpeg.wasm 许可说明、FFmpeg 官方许可说明
测试媒体使用自己生成或有明确再分发许可的样本。第三方音乐、照片和录音不入库。 若采用 Mediabunny,也分别核对主库 / 扩展的 MPL-2.0 与 LAME 的 LGPL 说明, 并保留所选版本要求的资料;不由其中一个包的许可代替整个组合的许可。
8.3 部署与回退
发布前验证生产构建和实际反代:首次加载、缓存命中、Worker 与 WASM 请求成功, 只使用图片时没有音频资源请求。服务端计算、数据库和上传目录不参与转换。
出现音频加载故障时允许暂时关闭音频标签的可执行功能,同时保留图片转换与明确 状态说明;不能把文件悄悄切换为上传服务器处理。回退代码版本时,HTML、wrapper 与核心资源版本一起回退,并验证旧资源路径仍满足部署策略。
9. 错误处理与隐私验收
9.1 错误类别
| 情形 | 用户应看到的结果 | 恢复方式 |
|---|---|---|
| 空文件、类别不支持 | 具体文件无法进入当前转换流程 | 移除或换文件 |
| 后缀 / MIME 与内容不一致 | 以内容为准识别;不符合白名单则拒绝 | 换正确文件,不靠改名通过 |
| 动画图片 | 首版不支持保留动画,未进行转换 | 使用其他工具或等待扩展 |
| 超过大小 / 像素 / 时长 | 显示对应上限与实际值(可得时) | 缩小源文件或换较短文件 |
| 核心加载失败 | 转换组件未能加载;图片仍可用 | 用户主动重试 |
| 浏览器能力不足 | 缺少具体能力或格式支持 | 换支持的格式 / 浏览器 / 完整页面 |
| 损坏文件或不支持的编码 | 无法解码,并显示格式层面的原因 | 不重试同一失败参数直到无限循环 |
| 转码超时 / 取消 | 超时或已取消,未提供半成品 | 重试,重新加载已终止的引擎 |
| 输出预算不足 | 队列暂停,已有成功结果保留 | 下载并释放已有结果后继续 |
| 输出试听失败 | 转换结果已生成,本浏览器不能试听 | 允许下载;不要改判为转码失败 |
底层日志用于本地诊断,保留有界数量,避免完整控制台内容铺进产品界面。 错误映射失败时提供简明的通用错误和本地诊断信息,不假装已经判断出具体损坏原因。
9.2 隐私与内容边界
验收时拦截页面请求,确认没有用户媒体字节、文件名、元数据、预览和结果被发送到 转换 API、业务上传接口、分析服务或第三方域名。静态资源访问与站点已有请求要与 用户文件数据区分;不宣称浏览器本身或嵌入方不会进行其自己的行为。
只接受用户主动选择的 File。首版禁止远端 URL 输入和任意协议资源引用;不通过 innerHTML、iframe 或 object 标签呈现用户文件。SVG / HTML 不进入可接受媒体集合。 文件内容必须经实际解码,畸形或假冒文件不能只凭后缀获得成功结果。
10. 验证方案与发布验收
10.1 样本与分层验证
样本必须有独立可核对的预期,不能只断言「存在下载按钮」或「输出体积大于零」。 可入库的小样本使用自制图案与合成音频;大文件和压力样本按需本地生成。
| 层级 | 要覆盖的问题 |
|---|---|
| 纯模块单测 | 类型词表、参数边界、内容识别、动画标记、体积预算、文件名、取消后旧回调失效 |
| 图片真实浏览器用例 | JPG → PNG 的尺寸与像素;PNG → JPG 的背景与容许色差;EXIF 方向;WebP 输出探测与字节;动画拒绝 |
| 音频真实引擎用例 | OGG Vorbis / Opus 分别转 MP3;WAV、FLAC、AAC M4A 输入;输出编码、时长、采样率、声道与可解码性 |
| 队列与异常用例 | 成功 / 失败混合、串行、取消与重试、加载失败、超时、预算暂停、切换标签与卸载 |
| 资源与隐私用例 | 图片不加载音频核心;生产资源可达;无媒体上传;结果与临时文件释放 |
| 页面与布局用例 | 工具箱默认入口、搜索 / 分类、直接打开、客户端导航、亮暗主题、窄屏与键盘流程 |
MP3 输出是有损且可能有编码延迟,不按文件哈希或逐字节相等验收。使用合成音调和 已知声道,解码输出后检查时长容差、主频、非静音、声道和采样率;WAV 按声明的 PCM 规格验收,图片按格式和允许的色差验收。音频容差在 M0 记录并固定。
真实引擎用例必须使用将发布的同源核心,不能全部 mock 转码函数。独立解码 / 检查 输出可在开发测试环境使用测试工具,但不能因此把服务端 FFmpeg 加进生产依赖。
10.2 浏览器与设备矩阵
| 环境 | 必测事项 | 记录要求 |
|---|---|---|
| 桌面 Chrome / Edge | 完整图片+音频流程、取消恢复、冷 / 热加载 | 浏览器版本、OS、硬件与样本 |
| 桌面 Firefox | Worker / WASM、原生图片能力、音频完整流程 | 同上;记录能力差异 |
| macOS Safari | 下载、原生图片输出、音频与缓存 | 真机 / 实际 Safari 版本 |
| 真实 iPhone Safari | 短音频、上限附近样本、结果试听 / 下载、取消与重复转换 | iPhone 型号、iOS / Safari 版本 |
| Android Chrome | 文件选择、图片与短音频、下载与内存表现 | Android 版本、设备型号 |
| 同源 iframe | 全流程、下载和 Worker | 嵌入页设置 |
| 跨源 iframe | 普通嵌入下的全流程;受限 sandbox 下的原因提示 | 父页面 origin、权限和 sandbox 属性 |
仓库 Playwright 的 mobile 项目使用设备配置模拟视口,不能替代真实 iOS Safari。
新增布局 spec 要登记到 playwright.config.ts 的 RESPONSIVE_SPECS;桌面功能用例
按既有项目执行。是否加 WebKit 自动化项目按环境支持决定,仍不能代替 iPhone 实测。
10.3 性能测量与发布条件
每个重点设备至少记录一张典型图片、一张像素上限附近图片、两种 OGG 编码的短样本、 一个时长上限附近音频,以及连续十次小文件转换和取消后重试。
分别记录:图片页新增 JS、音频核心原始 / 压缩传输量、冷加载 / 热加载、探测与转码 耗时、输出大小、UI 响应和可获取的内存指标。Safari 无法取得一致的 JS 内存数字时, 记录系统工具观察和浏览器是否重载;不能编造与 Chromium 可直接比较的数值。 上游视频基准只说明 WASM 与原生性能有差距,不能拿来当本站音频处理时间。 依据:上游性能说明
发布的必要条件:
- §3 中的必需输入 / 输出组合在声明支持的设备上通过,文案与实测白名单一致。
- 必测样本没有可复现的崩溃、错误格式输出或取消后任务串扰。
- 正常输入在设定超时内完成;阈值不合适时调低范围并同步文案,不能只调测试等待。
- 非法输入、动画、假后缀、损坏容器和超限文件有明确反馈,不产生可下载半成品。
- 图片功能不依赖音频核心加载成功;生产模式的资源路径与缓存通过。
- 现有工具入口、主题、页面布局和 iframe 使用没有回归。
- 使用指南、格式清单、限制与核心许可资料齐全,实施记录标明已交付和剩余项。
10.4 仓库检查
仅提交本计划时执行文档登记与互指的既有测试,不增加镜像文案的测试,也不需要 为了 Markdown 正文跑整站 E2E:
npm run test -- tests/unit/docs-catalog.test.ts tests/unit/docs-xref.test.ts
git diff --check
后续功能实施时运行新单测、相关样式守卫、链接检查和构建,再跑使用新产物的相关
E2E。npm run e2e 本身不构建,修改源码后不能拿旧 .next 的测试结果验收。
如果类型检查出现已删除路由的幻影错误,按 ../CLAUDE.md 的构建说明处理;
必要时仅清理确认位于本仓的 .next/types 后重建,不顺手清空整个 .next。
11. 里程碑、交付与工作量
11.1 顺序与退出条件
| 里程碑 | 工作与交付 | 退出条件 | 估算开发人日 |
|---|---|---|---|
| M0 技术验证 | 固定依赖候选、真实音频核心与原生图片探针、格式 / 设备 / 资源测量;有界比较轻量引擎 | 引擎选择、输入白名单与限制有测量依据;阻塞项写明 | 1–2 |
| M1 页面与入口 | 文件处理分类、页面结构、词表、参数与任务状态、主题 / 响应式 | 可从真实菜单进入并完成选择 / 校验流程;尚不声称转换已经可用 | 0.5–1 |
| M2 图片转换 | 原生解码 / 编码、透明背景、方向、动画拒绝、预览 / 下载、资源回收 | 图片矩阵与异常、布局、能力探测通过 | 1–2 |
| M3 音频转换 | 同源资产准备、按需加载、探测 / 转码、串行、取消 / 超时、试听与输出验证 | OGG 两种编码及其他首批输入通过真实引擎;失败后可恢复 | 1–2 |
| M4 集成验收 | 真机 / iframe / 生产构建、资源测量、回归、使用指南和实施记录 | §10 发布条件满足,已知限制可查 | 1–2 |
合计约 5–9 个开发人日,仅作安排工作的量级参考;不含等待真机、外部网络条件、 新增格式、服务器转换或修复超出预期的引擎兼容问题。M0 后根据结果重估。
里程碑是交付验收单位,不要求每期只有一个提交。按仓库约定,每个独立功能 / 修复 / 文档完成并验证后立即提交,不同语义分别提交;默认不 push。发布状态由实际验收 记录决定,不能把「有页面」「有输出」「已提交」当成全功能交付。
11.2 需要同步的文件
| 实施阶段 | 现有文件 / 目录 | 要同步的内容 |
|---|---|---|
| 本计划 | docs/README.md、src/lib/docs-catalog.ts |
文档索引与标题 / 摘要登记 |
| M1 | src/app/tool/ToolMenu.tsx、src/styles-scss/main.scss |
入口、分类、搜索与样式导入 |
| M2 / M3 | package.json、package-lock.json、.gitignore |
需要的音频依赖、资源准备 / 检查脚本、可重建资产 |
| M4 | playwright.config.ts、tests/e2e/tools.spec.ts |
响应式用例登记、入口回归 |
| M4 | docs/architecture.md、docs/deploy.md |
已实施的路由 / 模块、静态核心准备与故障排查 |
| M4 | docs/guide/、docs/README.md、src/lib/docs-catalog.ts |
新使用指南、支持格式、限制、隐私与结果保真说明 |
架构和部署文档只在实际实现后更新为现状。现在不把未来路由、依赖或准备命令写成 已存在的能力;使用指南在功能实现时新增,不能提前让玩家进入不存在的转换页面。
12. 决策、实施记录与后续扩展
12.1 决策现状(2026-10-12 校对)
| 事项 | 当前状态 | 依据或剩余工作 |
|---|---|---|
| 统一格式转换器入口 | 已交付 /tool/convert |
页面与工具箱入口已存在 |
| 图片+短音频优先 | 原始范围已扩为八个能力区 | §12.2 记录实际交付;长期版图仍未全部实现 |
| 本地处理、匿名可用 | 已采用 | 现行页面无登录闸门,转换在浏览器内执行 |
| FFmpeg 单线程基线 | 已集成 | package.json 锁定 wrapper / core,scripts/copy-converter-assets.mjs 准备同源资产 |
| Mediabunny 对比 / 替换 | 未验证;优化候选 | 有界比较仍缺记录,不作为已有功能,也不默认要求替换当前引擎 |
| WebP 输出 | 已交付,按设备能力过滤 | 图片类别与 registry.ts 的 webp-encode 判据 |
| 文件 / 时间 / 内存限制 | 已有保护性限额;真机上限测量未完成 | 现行数值以 src/lib/file-converter/formats.ts 的 LIMITS 为准 |
| 依赖版本与核心路径 | 已锁定与登记 | package-lock.json、引擎文件与资产复制脚本;不再是「尚未选择」 |
| 真实设备兼容性与性能矩阵 | 未完成 | §10.2 / §10.3 要求的真机、浏览器、iframe 与资源测量不能由已有 Chrome 用例代替 |
12.2 实施记录
| 日期 | 交付 | 状态与证据 |
|---|---|---|
| 2026-10-08 | 计划文档、项目文档索引与站内目录登记 | 方案交付;M0–M4 均未实施,未新增运行时转换依赖 |
| 2026-10-08 | 长期能力规划与首版计划互指 | 八类文件的转换路线、保真边界与 R1–R5 建设目标已成文;仍属设计,未新增转换功能 |
| 2026-10-09 | 契约层(识别 / 登记表 / 队列 / 执行)+ 八个能力区的引擎与边 + 页面 + 用户指南 | 见下「2026-10-09 交付」 |
2026-10-09 交付(实测记录)
交付范围超出首版(图片 + 音频):八个能力区、共 81 条登记的边(79 条 live +
2 条 planned:office:html-print-pdf / office:docx-to-pdf,需要服务端组件,不进菜单),
全部在浏览器本地完成。
新增运行时依赖:@ffmpeg/ffmpeg + @ffmpeg/core(ST 核心)、pdfjs-dist、pdf-lib、
xlsx、mammoth、docx、pptxgenjs、tesseract.js + 中英语言包、heic2any、utif、
node-unrar-js、7z-wasm、iconv-lite、js-yaml、fast-xml-parser、fflate、marked。
引擎资产(约 66MB,13 个文件)不入库,由 scripts/copy-converter-assets.mjs 在
postinstall 与 build 生成。
验证命令与结果(2026-10-09,Windows / Chrome):
| 验证 | 命令 | 结果 |
|---|---|---|
| 单测 | npm test |
4186 passed / 187 files(其中格式转换器 16 个文件、377 例) |
| 类型 | npx tsc --noEmit |
0 错误 |
| 构建 | npm run build |
成功;/tool/convert 首屏 185kB(重引擎全在动态 chunk 里) |
| 端到端 | npx playwright test tests/e2e/file-converter*.spec.ts |
9 passed(含 PNG→JPG 与 WAV→FLAC→WAV 两条真转换用例,输出按字节魔数断言) |
| 全量 e2e | npm run e2e |
524 passed / 4 failed / 4 skipped |
偏离计划的地方(都已写进代码注释或 docs/architecture.md §6.17):
- 首版只要求图片 + 短音频,实际按长期规划一次做到八类。
- 压缩包只解 RAR / 7Z,不进 压:RAR 编码器是专有技术、7-Zip 的压缩侧没编进 wasm 构建。
pdf:pdf-to-searchable的不可见文字层用 pdf-lib 标准字体(WinAnsi),覆盖不了中文 —— 该边默认语言恰好是chi_sim+eng。目前中文只产出图像层,结果页 notice 如实说明; 要真正支持需引入可再分发的中文字体 +@pdf-lib/fontkit(未做)。- 首版计划里的 Mediabunny 对比、真实 iPhone / Android 设备矩阵未做。
- roadmap §12.1 登记的十个字段里,「执行引擎与版本」「验收证据」没有单独建字段:
引擎与版本写在
engines/*.ts的文件头(与copy-converter-assets.mjs的 版本常量对应),验收证据就是上表的测试与构建结果。真要为每条边存这两个字段, 七十多条边都要动一遍,收益不抵churn —— roadmap 说的是「建议逐步形成」, 记在这里当作有意推迟,别当成漏做。
已知限制(详见 docs/architecture.md §6.17 与各引擎文件头):音频 2 声道 / 5 分钟上限;
视频 1080p / 2 分钟上限;取消或超时需重启整个 FFmpeg 核心(0.12 无软中断)。
当时全量 e2e 的 4 个失败在 tests/e2e/editor-mobile-toolbar.spec.ts,与格式转换器无关,
本次之前就红 —— 已用隔离 worktree 在基准 commit 上复现(数字逐位一致),二分定位到
68f0a35e「fix(blog):窄屏下发文 / 编辑卡片的内边距收到 16px」:窄屏内边距从 40px 收到
16px 后编辑区整体左移,[data-more='open'] 展开时「导出」分组落到靠左位置,而导出浮层是
右锚向左展开(.md-toolbar__menu--right { left:auto; right:0 }),于是在 320px 下越过编辑器
左缘(23.6 < 31.5)、390px 下越过视口左缘(-60.4)。归编辑器的布局缺口,未在本轮修。
2026-10-12 校对:该布局缺口已由 **3c8fbf8d(2026-10-10)**修复,窄屏导出组增加
margin-left: auto 保持右锚菜单在编辑器内;上表失败数保留为 2026-10-09 的历史结果,
本次文档整理没有重跑全量 e2e,不能把它写成当前仍失败或当前全绿。
后续每次交付补充:提交、已完成的范围、设备 / 版本、验证命令与结果、限制、偏离
计划的原因和剩余工作。测量记录使用自制样本,不在公开文档中留下用户文件或
私密录音;本目录文档会被 /docs 对外展示。
12.3 原扩展建议的交付状态(2026-10-12 校对)
| 原建议 | 已交付范围 | 剩余边界 |
|---|---|---|
| 更多音频输出 | FLAC、OGG Vorbis / Opus、AAC M4A、AIFF 与换封装 | 设备样本与播放兼容性仍需验收,不能宣称所有组合无条件可用 |
| 图片缩放与更多格式 | 缩放参数、AVIF / BMP / TIFF / ICO 输出、HEIC / TIFF 输入与动画相关边 | 按浏览器能力过滤;不意味着 HEIC 输出、RAW 与专业色彩能力已交付 |
| ZIP 打包下载 | 成功结果打包及压缩包类别 | 仍受结果预算与压缩包安全上限约束 |
| 视频提取音频 / 短视频转换 | 视频类别、提音频 / 字幕、抽帧与动图 | 当前限 1080p / 2 分钟;真机资源测量未完成 |
| 服务端转换 | 未实施 | 上传、调度、隔离、临时文件与隐私契约仍需单独设计 |
| Office / PDF | DOCX 内容转换、图片型 PDF→DOCX / PPTX、PDF 操作与 OCR | DOCX / HTML 排版导出 PDF 仍为 planned;中文可搜索 PDF 的文字层尚缺字体方案 |
尚未交付的方向、验收缺口与有意推迟项统一见 docs/plan/format-converter-roadmap.md §16。
每次扩展仍先定义输入 / 输出矩阵和验收样本;本节只核对原建议,避免把已有功能重复列为待办。