Files
note2any/docs/image-pipeline.md
2025-10-09 12:39:24 +08:00

8.1 KiB
Raw Permalink Blame History

Image Pipeline & Cover Strategy

对应代码核心:src/markdown/local-file.ts, src/article-render.ts 以及相关预处理逻辑。 若需总体结构参见 architecture.md,更广设计参见 detaildesign.md

1. 目标

  • 统一处理所有图片来源Wikilink、标准 Markdown、远程 URL、Base64、生成图 (Mermaid/Excalidraw/SVG)。
  • 保证上传后 HTML 使用微信可访问的正式 URL。
  • 自动封面选取:未指定时从正文首图推断。
  • WebP → JPG 转换保障兼容性。
  • 默认封面配置:若无正文/画廊候选,使用 defaultCoverPic (settings)。
  • 手动 frontmatter 回退metadata 缺失时行级解析 title/author/image

2. 图片来源与归一化

来源 输入示例 归一化策略 备注
Wikilink ![[foo.png]] / `![[foo.png 120x80]]` Marked 扩展直接生成 <img src="vault://..."> 并登记
Markdown ![alt](assets/img/foo.png) (可选) 预处理转换成 ![[foo.png]] 依赖设置:enableMarkdownImageToWikilink
远程 URL <img src="https://..."> 解析为远程上传任务 跳过已是微信域名
Base64 <img src="data:image/png;base64,..."> 解析成 Blob 上传 生成虚拟 id 构造文件名
Mermaid 源码块 → SVG/DOM 转 PNG (html-to-image) / 留 SVG 替换占位容器
Excalidraw ![[diagram.excalidraw]] 远程接口取 SVG 或转 PNG 需 AuthKey
SVG 文件 ![[icon.svg]] 内联 SVG 内容 直接嵌入,不上传

3. 关键数据结构

interface ImageInfo {
  resUrl: string;   // 初始占位或本地资源 URL (vault path 映射)
  filePath: string; // Vault 内实际文件路径
  url: string | null;      // 上传后微信返回 URL
  media_id: string | null; // 上传后素材 IDtype=image 时)
}

管理容器:Map<string, ImageInfo>,键值为 resUrl(初始唯一标识)。

4. 收集阶段

  1. Marked 扩展 (LocalFile.markedExtension) 在 token 遍历时识别 LocalImage。
  2. 根据 wikilink / 尺寸参数解析出真实 vault 文件路径:assetsManager.getResourcePath()
  3. 调用 LocalImageManager.setImage(res.resUrl, info) 建档。
  4. Markdown 图片(标准语法)若转换启用,提前转成 wikilink 进入同一逻辑;否则需未来扩展 tokenizer 直接登记。
  5. Gallery
  • 短代码:{{<gallery dir="..."/>}}{{<load-photoswipe>}} → 目录枚举(受 galleryPrePath + galleryNumPic 影响)→ 多行 ![[...]] 注入。
  • 块级:{{<gallery>}} ... {{</gallery>}}figure src="..." link="..." caption="..." > 解析 src basename 加入候选;link 预留后续包装;caption 未来映射题注。

5. 上传阶段

顺序:

graph TD
A[准备 token] --> B[cachedElementsToImages]
B --> C[uploadLocalImage]
C --> D[uploadRemoteImage]
D --> E[replaceImages]
E --> F[输出 HTML / 复制 / 发布]

5.1 本地图片 uploadLocalImage

流程:

  • 遍历登记表,读取 vault 文件二进制。
  • 若扩展名 .webp → wasm (PrepareImageLib) 转 JPG。
  • 调用 wxUploadImage(blob, filename, token, type)
  • 更新 ImageInfo.url / media_id
  • 失败:Notice + console.error,继续后续项。

5.2 远程图片 uploadRemoteImage

匹配 <img src="http...">

  • 跳过已在微信域名(mmbiz.qpic.cn)。
  • 下载 → Blob → (webp 转换) → 上传。
  • 更新 Map键为原始 src。 Base64
  • 解析 data URI → 生成 Blob/扩展名 → 上传。

5.3 替换阶段 replaceImages

  • 遍历 DOM <img> → 查询 Map → 使用上传后 url 覆盖 src
  • 未登记:输出警告(潜在渲染管线遗漏)。

6. 自动封面策略(更新)

触发点:getMetadata();若存在 thumb_media_id 则跳过。决策链:

  1. frontmatter cover/image非空若 metadataCache 缺失,则手动行级解析首个 --- 块。
  2. 正文本地图候选markdown + wikilink
  3. gallery 展开产生的首图(短代码或块级 figure 列表)。
  4. defaultCoverPicsettings 配置,允许相对路径 / URL / wikilink
  5. 否则封面为空。

Edge Cases

情况 处理
frontmatter cover: "" 视为未配置,继续回退
所有正文图片都是 http(s) 跳过正文阶段,转 gallery -> defaultCoverPic
gallery 块无有效 figure 不贡献候选
defaultCoverPic 不存在 仍返回该引用;上传阶段可能失败(待校验增强)
markdown 图片后缀非图片 跳过
重复图片 以最先出现的为准

7. 正则索引

目的 正则 描述
Frontmatter 删除 /^(---)$.+?^(---)$.+?/ims 首段 YAML
Markdown 图片 /!\[[^\]]*\]\(([^)\s]+)\)/g 捕获路径 group1
Wikilink 图片 /!\[\[(.+?)\]\]/g 捕获内部资源
Gallery 块 {{<gallery>}}([\s\S]*?){{<\/gallery>}} 包裹内容
Gallery figure {{<figure\s+src=\"([^\"]+)\"[^>]*>}} 提取 src
figure link link=\"([^\"]+)\" 可选外链属性
WebP 检测 /\.webp$/i 扩展判断
Base64 前缀 ^data:image/ 判断内嵌图

8. 失败与恢复策略

环节 失败示例 策略
wasm 初始化 网络慢 / 未加载 继续使用原 webp 上传(失败概率增加)
本地文件找不到 路径错误 / 移动 在控制台警告,图片跳过
上传 403/401 token 失效 抛异常终止流程(需重新获取 token
单图 errcode !=0 大小/格式不合规 Notice 提示 + 继续其他
替换未命中 DOM 没登记 记录警告,建议加入补偿扫描

9. 性能优化方向

问题 现状 改进
上传串行 顺序 await 并发池 (N=3~5) + 重试退避
WebP 转换阻塞 单线程 预热 wasm + 批量并行
远程下载串行 每图 await 统一收集 Promise.all 控制并发
首图扫描重复 重新正则两次 合并一次统一匹配 pipeline

10. 与发布流程的接口点

函数 上下游 说明
cachedElementsToImages() 延迟图形 → 图片 在上传前确保所有图形成为 可被收集
uploadLocalImage() 发布前 更新本地图片 URL/media_id
uploadRemoteImage() 发布前 远程/内嵌资源统一化
replaceImages() 发布前 DOM HTML 成为最终状态
getArticleContent() 发布 / 复制 / 导出 输出含最终图片 URL 的 HTML

11. 未来扩展

需求 设想
CDN 直传 微信前置 → 自建或 OSS 中转
图片压缩 上传前可选压缩 (canvas/wasm)
失效重传 保存上传映射 + 校验缺失再补传
分块上传 大图分片(若接口支持)
图片校验 MD5 去重避免重复上传
defaultCoverPic 校验 渲染时验证存在性 + Notice
多默认封面池 数组随机选择减少视觉重复
caption alias figure caption -> wikilink alias/figcaption
link 包裹 link 属性生成 <a> 包裹 <img>

12. 快速检查清单 (Debug Checklist)

  • 图片是否在 LocalImageManager.images Map 中?
  • 是否执行了 cachedElementsToImages()Mermaid/Excalidraw
  • 上传后 media_id 是否为空?(格式/大小不合规)
  • DOM 替换后 <img src> 是否为微信域?
  • 自动封面未生效检查frontmatter cover / 正文本地图是否存在 / gallery 是否展开 / defaultCoverPic 是否配置。
  • 重复封面日志确认日志节流是否失效3 秒窗口内只应出现一次)。

13. 示例

输入 Markdown

---
title: 示例
---
![首图](assets/img/a.png)
正文...
![[b-second.jpg]]

处理:

  1. frontmatter 去除后扫描:首个匹配为 Markdown 图片 a.png → 自动封面 ![[a.png]]
  2. a.png 转 wikilink (开启转换时) → 登记b-second.jpg 登记。
  3. 上传顺序a.png → b-second.jpg。
  4. 最终 HTML <img src="https://mmbiz.qpic.cn/...">

如需补充“并发上传样例代码”或“封面过滤策略扩展”,请提出。