状态: Phase B/C 完成后、Phase D 开工前的代码健康评估 (2026-08-25)。 依据: 逐文件核查 (~2400 行 Rust),编译警告、重复定义、测试盲区。
-
media.rs:sticker字段 /sticker()方法 /StickerType枚举删除 (D 预览需要 Static/Animated 区分时再以真实用途回归) -
transcoder.rs未使用的StickerTypeimport 删除 -
app.rsVIDEO/IMAGE的#[allow(dead_code)]移除(被第 2 项真实消费)
-
SUPPORTED从VIDEO/IMAGE编译期拼装 -
FILE_ACCEPT改为从SUPPORTED派生 (file_accept()) - 单测
supported_extensions_are_consistent:SUPPORTED 每项可识别 + VIDEO/IMAGE 与 SUPPORTED 一致 + 未支持扩展被拒
- 合并为泛型
NumberInput<T>(NumEdittrait:parse/clamp/pretty), 调用点改为类型参数形式,行为不变(编辑态模式保留)
原为零覆盖的纯逻辑(码率公式、时长→系数查表、重试缩放),现已抽出并测试:
-
target_bitrate_bps/default_factor/quantized_bitrate/shrunk_factor(pub, runner 使用) 抽取到 transcoder.rs - 单测覆盖区间端点、量化取整、shrink 公式、进度时间解析(+7 测试)
- 5.
transcoder.rs拆分 →src/transcoder/:mod.rs(类型+编排)、command.rs(命令生成+码率纯函数+单测)、steps.rs(webm 时长补丁、oxipng 图片管道)、error.rs(TranscodeError);check_input以MediaFile::probe归还media.rs - 6.
runner.rs run_all分解:拆出run_single_task() -> TaskOutcome; 取消 watcher 改为每任务一个(跨重试存活,函数出口统一 cancel) - 7. 镜像写入口统一:
with_task(派生字段)与touch_entry(UI-only 字段) 两个入口 + AGENTS.md 契约文档化;runner/probe/接收循环全部改道 - 8. 错误处理枚举化 (E1):
TranscodeError(thiserror)贯穿 transcoder→runner→mirror/toast;取消用matches!(e, Cancelled)判定 - 9. 进度事件节流:接收端 100ms interval 合并同任务进度再写镜像
D 完成后逐点核查锁的使用:
- 预览渲染期锁已消除(输出路径进镜像
output_path,附带修复了 "预览开着转码时 UI 冻结" 的实际 bug) - 剩余锁临界区都很短:
with_task刷镜像、probe、cancel_flag clone - factor 编辑在 running 时本就禁用,不存在 UI 与 worker 并发写
去掉 Arc<Mutex> 要求 runner 独占任务列表,与"运行中允许拖入新文件"
的既有语义冲突(需要额外的消息桥)。收益趋近于零,改动面大。
结论:保留现状。 若将来做 E4 并行转码,每任务独立的 Arc<Mutex<Transcoder>
反而是天然的任务分发单元——届时更没有去除的理由。
BITRATE_BASE_BYTES 已是 command.rs 具名常量。剩余 crf 26 / bufsize ×1.5
提为常量即可(纯命名,无行为变化);"码率基准进设置"仍是行为变更
(码率会随上限变化),与"超限时只重试不改预算"的现有语义冲突,维持暂缓。
settings.toml 只有 ~200 字节,写盘亚毫秒级。500ms 防抖的全部收益
抵不过退出丢写的边界风险。改为 update_settings 内直接
spawn_blocking(config::save),删除 SAVE_GEN 代数计数器。
- crf 26 / bufsize ×1.5 提为具名常量(command.rs:
VP9_CRF/BUFSIZE_RATIO) - update_settings 防抖改为即时落盘(删除 SAVE_GEN 代数计数器与 500ms 窗口)
-
E2 去 Arc— 复核后否决,理由见上 - 预览渲染期锁消除(output_path 镜像,D 阶段遗留 bug)
- 锁纪律:无跨
.await持锁 - cancel watcher 所有 break 路径均有 abort
run_all启动时快照 Settings:运行中改设置下次 Run 生效——有意语义,已文档化
- 新增主题变量
--success-hover/--danger-hover: 亮色取更深同色系(#166b2e/#a40e26),深色取微亮(#4ac269/#ff6a63) -
.btn-primary:hover/.btn-danger:hover改用变量,删除filter: brightness(1.08)写法
- 1. 右键菜单 — 按用户决定保持现状:仅 debug 构建出现(dioxus-desktop
disable_context_menu默认!debug_assertions),release 打包本就没有; 有需求时方案 A:main.rs加.with_disable_context_menu(true)一行关闭 - 2. 日志增强:runner 决策点已补日志——重试(含 output KB、excess 倍率、
factor old→new、retry n/max)、取消、完成(输出大小+耗时);另将
pretty_env_logger默认级别从 Error 提到 Info(原先 info/warn 全被过滤)。 可选延伸"应用内活动日志面板"仍缓(toast 已覆盖错误场景) - 3. 主题跟随系统:
Settings.theme增加"system"档并作为默认值; JSmatchMedia('(prefers-color-scheme: dark)')监听切换实时回写data-theme(非 system 档会移除监听器);工具栏按钮显示当前档位循环切换, 设置面板下拉含 "System (follow OS)" - 6. 任务行显示输出文件名:
TaskEntry.output_file_name镜像字段 (with_task从get_output().file_name()同步),任务行展示文件名 + 大小(tooltip 已含完整路径)
- NSIS 安装包打通:
dx bundle --platform windows --package-types nsis产出StickerProcess_0.1.0_x64-setup.exe(187MB,含 WebView2 离线安装器) - ffmpeg 随包分发:
已废弃(2026-09-06)bundled shared ffmpeg.exe 运行需要同大版本 av*.dll(~235MB),捆绑 exe 不带 DLL,干净机器上无法启动; 有 ffmpeg 的用户 PATH 里已有可用 exe,捆绑冗余。见 docs/RELEASE.md 与 E6_INPROCESS_RESEARCH.md §7 的静态构建路线 - 静默安装验证:
setup.exe /S /D=<dir>安装出 StickerProcess.exe, 分发机器需带 WebView2(离线安装器已捆绑在 NSIS 中)
依据:
dx build --platform web+ Chromium 实机逐项复核(640px 最小窗口、 键盘 Tab、拖放、Run/预览/设置全流程),加一次对转码核心的只读复查。
- 预览弹窗固定
min-width: 720px,在 640px 窗口两侧各裁 59px、Close 点不到 →min(720px, 92vw) - 引擎下拉是设置面板里唯一无 label 且撑满整行的项 → 补
.settings-row - 不支持的文件静默丢弃(只有
log::warn)→ 弹 toast(多于一个报数量+首例) - 预览对比文案超限时仍写 "✓ within limit"(只有颜色变红)→ 按 ratio 分支
- 徽标直接渲染
{:?}(用户看到SizeExcess)→Status::label() - 深色主题原生滚动条/select 弹层仍是浅色(无
color-scheme)→ 随主题设置 - 任务行不可聚焦,键盘用户无法打开预览 →
tabindex/Enter/Space + role/aria - web 产物名尾随
-(ISO 的Z被一起替换)→ 只替换T/:;空串退回毫秒戳 - Reset Defaults 无确认且立即落盘 → 二次确认(4s 自动解除)
- Run 重跑 Done 任务 → 跳过 Done,行内补 Re-run(保留重转能力)
- Cancel 按钮写成
disabled: running || …——运行期间反而禁用 →!running
-
inprocess.rs两处 drain 循环EAGAIN => continue(与四个兄弟循环的break不一致)→ 改break:drain 阶段无帧可喂,自旋会持锁不放(UI 冻结、Cancel 失效) - sidecar 图片路径不
wait()、不查退出码 → 补上与视频路径同一不变量; 解码失败不再伪装成optimize failed,子进程正常回收 - sidecar 视频路径用
child.iter()(借走&mut child整个循环)→ 卡住的 ffmpeg 无法 kill。改自持 stderr + crate 公开的spawn_stderr_thread, 100ms 轮询 cancel(图片路径同样按拍轮询)
-
VideoType::pix_fmt/command::SCALE_FILTER成为三引擎唯一出处 (A/B 实证:加flags=lanczos、去掉-sws_flags后解码帧逐字节一致) -
Engine全程保持类型;wasm 分发表穷尽匹配(原other =>兜底臂只因类型被丢) -
probe统一Result<(), String>(原两套 cfg 签名 + 两组等价 map_err) -
TranscodeError:加Ffmpeg(ffmpeg::Error)(打开阶段,原误标 Decoder)与OutputWrite(写盘失败原被改写成ImagePipe("write failed")) - 默认时长系数表单常量(原 config / Transcoder 两处副本)
-
patch_webm_file改读全文件再写回(文件 ≤512KB),删 8KB 窗口 + 整文件兜底 -
data_url移入唯一消费者components/preview.rs -
SUPPORTED收成唯一扩展名列表,删VIDEO/IMAGE(合并保留测试守卫) -
TaskEntry的镜像字段收进TaskMirror(派生PartialEq):手写 eq 漏字段 的静默 memo 跳过不可能再发生,原回归测试随之删除 -
excess_ratio单公式(镜像侧与 runner 侧共用);runner 里的unreachable!()改 None(不再为一个已排除的状态 panic 整个进程)
inprocess.rs的map_err(|e| TranscodeError::X(…))只替换了"打开容器/上下文" 这类阶段名本来就不准的 8 处;帧循环/封装阶段保留阶段变体,否则错误文案会从 "decoder error: …" 退化成 "ffmpeg error: …",用可诊断性换行数不划算。repo无 CI,clippy 仍是人工门禁;两端的目标平台专属 dead-code 误报清单见 AGENTS.md。
-
4. i18n(原 B 表低优先项):界面文案现散落在各组件 rsx 中; 引入 fluent 或简单字符串表前先盘点文案数量,建议随 E7 文档阶段一起做
-
5. CI 发布矩阵:GitHub Actions 构建多版本安装包并挂 Releases—— a) 精简版(静态 ffmpeg inprocess,无 WebView2 离线包,需在线装 WebView2) b) 完整版(同上 + WebView2 离线安装器,当前默认形态) c) 完整版+sidecar(再嵌 vcpkg 静态
ffmpeg.exe(ffmpeg[ffmpeg]feature, ~20MB),零配置用户也可切 sidecar 引擎) 复用工程build.rs的静态链接逻辑与 vcpkg binary cache;升级语义见 docs/RELEASE.md