OpenReel Video 滤镜预设子系统:基于 LUT 的 60 款滤镜目录设计与配方驱动生成工具链
2026/9/18 18:26:07 网站建设 项目流程

OpenReel Video 滤镜预设子系统:基于 LUT 的 60 款滤镜目录设计与配方驱动生成工具链

【免费下载链接】openreel-videoOpenReel Video - Professional browser-based video editor. Open source CapCut alternative. 100% browser-based, no installation, no cloud uploads, no watermarks.项目地址: https://gitcode.com/GitHub_Trending/op/openreel-video

导读

本文围绕 OpenReel Video(开源浏览器端视频编辑器,README.md 所描述的开源 CapCut 替代方案)中"滤镜预设(Filter Presets)"这一子系统的设计规格展开,完整解读 Filter Presets v1 Design Spec。你将掌握:如何用 YAML 配方 + Python 代码生成 33³ 三维查找表(LUT),如何通过 Cloudflare R2 公共域以"无 Worker"方式交付 60 款滤镜,如何在 iOS / Android 上实现 LUT 渲染、0–100% 强度混合与 CapCut 风格的滤镜选择器,以及从既有 ~20 个参数化滤镜预设到 LUT 目录的无损迁移方案。

背景与目标:从参数化滤镜到 LUT 目录

当前 OpenReel 的视频调色能力由约 20 个参数驱动的滤镜预设组成,仓库中的实现位于 packages/core/src/video/filter-presets.ts,以FilterPreset接口描述(idnamecategorydescriptioneffects[]),效果类型包括brightnesscontrastsaturationhueblursharpenvignettegrain等,并作为能力清单通过 packages/core/src/capabilities/manifest.ts 的filterPresets字段对外声明。这类预设由参数实时计算,跨平台渲染难以保证逐通道一致。

Filter Presets v1 的目标是把它升级为经过策展、代码生成的约 60 款基于 LUT 的滤镜目录

  • 60 款滤镜分布在 6 个分类下,无需发布应用即可上线(纯静态内容下发);
  • 选择器打开后,暖缓存下首块贴图绘制 ≤ 250 ms
  • 强度滑杆在片段(clip)级别混合原始画面与滤镜输出;
  • 跨平台渲染一致性:iOS 与 Android 在相同(source, filter, intensity)三元组下输出差异 ≤ 每通道 1 LSB;
  • 引用旧版filterPreset效果的存量用户项目透明迁移

明确的非目标(Non-Goals)

设计文档划定了严格边界,避免范围蔓延:

  • 晕影(vignette)、颗粒(grain)、模糊(blur)、漏光(light leaks)等无法表达为 3D LUT的程序化效果,归入独立的"程序化特效"规格;
  • AR/美颜、瘦身、化妆、面部贴纸等效果;
  • 用户导入.cube文件(推迟到 v2);
  • "收藏(Favorites)"交互(v1 只有 Recents);
  • 项目级全局观感(v1 仅支持片段级);
  • 付费/Pro 滤镜层级(本设计不含 entitlement 系统)。

架构总览:三块独立可测的组件

设计采用三层架构,每层边界清晰、可独立测试:

┌──────────────────────────┐ ┌────────────────────────────┐ │ Build-time tool │ │ Cloudflare R2 (public) │ │ ───────────────── │ push │ ────────────────── │ │ scripts/filters/ │ ─────▶ │ R2 bucket: │ │ - recipes/*.yaml │ │ openreel-filters/ │ │ - generate.py │ │ manifest.json │ │ - manifest writer │ │ cube/<id>.cube │ │ │ │ Custom domain: │ │ Outputs: │ │ filters.openreel.video │ │ - 60 × .cube │ │ /manifest.json │ │ - manifest.json │ │ /cube/<id>.cube │ │ │ │ (R2 native ETag + cache) │ └──────────────────────────┘ └────────────┬───────────────┘ │ ▼ ┌────────────────────────────────────────────────┐ │ Mobile (iOS Swift & Android Kotlin) │ │ ────────────────────────────────────── │ │ • FilterCatalogService │ │ • FilterLutCache │ │ • FilterRenderer │ │ • FilterPickerViewModel + view │ │ • Clip model: filterId? + intensity │ └────────────────────────────────────────────────┘

三大边界

  • 工具边界:输入为recipes/*.yaml,输出为.cube+ manifest。这是一个纯函数,配套 golden-file 测试;
  • 托管边界:R2 公共桶 + Cloudflare 自定义域,ETag 与不可变缓存直接来自 R2,路径上无 Worker
  • 移动端边界:两个平台各实现四个类,表面接口完全一致,每个类只负责一件事。

与现有代码的插槽关系:iOS 的Core/Effects/CubeLUTParser.swiftCore/Rendering/VideoEffectRenderer.swift已能解析.cube并运行 CIFilter 链;Android 的core/effects/CubeLUTParser.ktcore/effects/ClipEffectPipeline.kt是对等物。滤镜交付属于静态内容负载,直接由 R2 服务,仓库中的apps/cloudWorker 保持不动(它单独处理模板/分享/AI,且按仓库约定被 gitignore)。

Recipe 工具链:YAML 配方驱动的 LUT 工厂

目录结构

设计文档规划的工具目录位于仓库根下的scripts/filters/,当前仓库中已实际落地:

scripts/filters/ ├── generate.py # 主工具(入口) ├── transforms.py # 单个颜色算子(逐像素 RGB→RGB) ├── recipe.py # YAML 配方加载与 step 注册表 ├── lut.py # 33³ 恒等 LUT、向量化变换、.cube 写出 ├── manifest.py # manifest 条目构建 + JSON Schema 校验 ├── manifest_schema.json ├── deploy.sh # 上传 out/ 到 R2(wrangler) ├── requirements.txt ├── recipes/ │ └── cinematic/ │ └── teal_orange.yaml # 首个英雄配方(Teal & Orange) ├── tests/ │ ├── fixtures/sample.yaml + sample.cube (golden) │ ├── test_generate.py / test_lut.py / test_recipe_loader.py / test_transforms.py └── out/ # 生成产物(.gitignored) ├── cube/ # 60 × .cube 文件 └── manifest.json

Recipe 格式(每个滤镜一个文件)

设计文档给出的配方示例如下,仓库中的真实文件 scripts/filters/recipes/cinematic/teal_orange.yaml 与之一致:

id: cinematic.teal_orange name: Teal & Orange category: cinematic accent: "#38BDF8" sort: 10 steps: - temperature: -8 - tint: +3 - contrast: curve: s_curve amount: 1.15 - split_tone: shadows: "#1E3A5F" highlights: "#FFA94D" balance: 0.0 - saturation: 1.10 - hue_shift: reds: -5

每个字段的含义:

字段说明
id全局唯一标识,如cinematic.teal_orange,同时作为.cube文件名与 manifest 索引键
name面向用户的显示名(如 "Teal & Orange")
category所属分类(cinematic/portrait/vlog/retro/mood/bw
accent选择器中选中状态的主题色(十六进制),如#38BDF8
sort分类内排序权重,数值越小越靠前
steps按序执行的调色步骤列表,每一步都必须是逐像素 RGB→RGB 变换,才能折叠进 3D LUT

v1 支持的 steps 全集

temperaturetintexposurecontrastlinear/gamma/s_curve三种曲线)、saturationvibrancehue_shift(按通道或全局)、split_tone(阴影 / 高光 / balance)、lift_gamma_gainchannel_mixer(3×3 RGB 矩阵)、tone_curve(控制点)、clip(黑白电平)、monochrome(带通道权重)。

v1 明确排除vignettegrainblurlight_leak—— 它们不是 LUT 可表达的,推迟到程序化特效子系统。

源码中 scripts/filters/recipe.py 的STEP_REGISTRY把 YAML step 名映射到 scripts/filters/transforms.py 中的具体函数,例如temperatureapply_temperatureamount/100折算为 RGB 偏移)、exposureapply_exposureimage * 2^stops)、contrasts_curvetanh实现软 S 曲线、saturation按 Rec.709 亮度系数[0.2126, 0.7152, 0.0722]加权。未知 step 会在加载时直接报错(对应测试 test_recipe_loader.py 的test_load_recipe_rejects_unknown_step)。

生成器算法

设计文档定义的四步算法,在仓库实现中逐一对应:

  1. 构建 33³ 恒等 LUT—— scripts/filters/lut.py 的identity_lut()np.linspace(0, 1, 33)在 R/G/B 三个轴上做 meshgrid 生成 33×33×33×3 的 float32 数组,LUT_SIZE = 33与设计一致;
  2. 对 LUT 采样点向量化应用每个 step——apply_transforms_to_lut()把 LUT 重塑为(-1, 1, 3)后逐函数作用,设计文档估计每张 LUT 约 5 ms;
  3. 以 Adobe 标准格式写出.cube——write_cube()依次输出TITLEDOMAIN_MIN 0 0 0DOMAIN_MAX 1 1 1LUT_3D_SIZE 33及按 B→G→R 嵌套序排列的r g b三通道浮点值(%.6f),兼容 iOS 与 Android 两个既有解析器;
  4. 把元信息追加进 manifest—— scripts/filters/manifest.py 的build_manifest_entry()计算.cubesha256与字节数,连同id/name/category/accent/sort/cubeUrl构成条目。

分类目录(v1:6 × ~10)

CategoryFilters(代表性示例,最终清单在 Phase 2 锁定)
CinematicTeal & Orange, Blockbuster, Movie, Noir, Hollywood, Drama, Bleach Bypass
PortraitSoft, Warm, Golden, Porcelain, Natural
VlogCrisp, Vibrant, Punchy, Soft Pop
Retro70s, 80s, Polaroid, VHS, Sepia, Faded Film, Old Photo
MoodDreamy, Moody, Golden Hour, Cold Blue, Stormy, Soft Mist
B&WClassic, High Contrast, Matte, Faded, Soft Mono, Gritty

构建与部署

本地生成(scripts/filters/README.md 给出标准流程):

python3 -m venv .venv && source .venv/bin/activate pip install -r requirements.txt python generate.py # 输出到 out/cube/*.cube 与 out/manifest.json pytest tests/ -v # 运行工具层测试

generate.py支持--recipes--out--base-url--version参数,并按 6 分类的有序字典生成categories段(见 generate.py)。部署由 scripts/filters/deploy.sh 完成,对.cube使用public, max-age=31536000, immutable缓存头,对manifest.json使用较短的public, max-age=300, s-maxage=3600以便客户端快速发现新滤镜:

./deploy.sh # 等价于 aws s3 sync out/ s3://openreel-filters/ 的 wrangler 版本

Hosting + Delivery:R2 公共桶与无 Worker 静态交付

R2 桶结构

独立桶openreel-filters(与openreel-templatesopenreel-shares分离,便于生命周期与权限管理):

openreel-filters/ ├── manifest.json └── cube/ ├── cinematic.teal_orange.cube ├── cinematic.blockbuster.cube └── ... (60 files)

Manifest 结构

{ "version": "2026-05-22T1", "minClientVersion": "1.0.0", "filters": [ { "id": "cinematic.teal_orange", "name": "Teal & Orange", "category": "cinematic", "accent": "#38BDF8", "sort": 10, "cubeUrl": "https://filters.openreel.video/cube/cinematic.teal_orange.cube", "sha256": "abc123...", "bytes": 154832, "oldIds": [] } ], "categories": [ { "id": "cinematic", "name": "Cinematic", "sort": 1 }, { "id": "portrait", "name": "Portrait", "sort": 2 }, { "id": "vlog", "name": "Vlog", "sort": 3 }, { "id": "retro", "name": "Retro", "sort": 4 }, { "id": "mood", "name": "Mood", "sort": 5 }, { "id": "bw", "name": "B&W", "sort": 6 } ] }

关键设计点:

  • 每个滤镜的sha256bytes支持下载后完整性校验与跨 manifest 版本复用决策(仓库中manifest.py已实现 sha256 计算,test_recipe_loader.py 的test_build_manifest_entry_includes_sha_and_bytes对此有断言);
  • oldIds数组允许客户端在滤镜改名时透明重映射,避免用户片段悬空;
  • manifest 在写出前会通过 scripts/filters/manifest.py 用 manifest_schema.json 做jsonschema.validate,CI 中亦有 schema 校验。

公共交付与缓存策略

GET https://filters.openreel.video/manifest.json → Cache-Control: public, max-age=300, s-maxage=3600 GET https://filters.openreel.video/cube/<id>.cube → Cache-Control: public, max-age=31536000, immutable

R2 原生提供 ETag 与 304 语义,上传时通过wrangler r2 object put --cache-control设置缓存头;桶上一次性配置 CORS(PUT/GET/HEAD、*源),Web 编辑器可直接跨域拉取。manifest 中的cubeUrl使用绝对 URL,未来即使回迁 Worker 或更换宿主也不需要客户端发版。

版本化与缓存失效

  • version字段采用 ISO 时间戳 + 计数器(如2026-05-22T1);
  • 客户端保存上次见到的 version,在启动与选择器打开时以If-None-Match重取 manifest:304 → 用缓存,200 → 执行 reconcile;
  • LUT URL 按 id 稳定不变,缓存层校验sha256,配方一旦变化自动使下游缓存失效。

客户端预取策略

  • 应用启动:后台低优先级拉取 manifest;
  • 选择器打开:先下载可见区域的 LUT(约 10 个),其余后台续拉;
  • Wi-Fi:预取全部 60 个(约 9 MB);
  • 蜂窝网络:仅按需加载,可在设置中配置;
  • LRU 磁盘缓存,上限 50 MB。

移动端数据模型、缓存与渲染集成

片段数据模型

data class AppliedFilter( val id: String, // matches manifest.filters[].id val intensity: Float, // 0.0 .. 1.0 ) // clip.filter: AppliedFilter? = null

存储在与现有effects: []相邻的位置。存量项目反序列化时filter = null,行为与今天完全一致。

四个类,双平台相同表面

FilterCatalogService singleton, reactive state: StateFlow<FilterCatalog> ← .loading | .ready(filters, categories) | .error refresh() async ← fetch manifest, reconcile against on-disk snapshot Persists last good manifest so the picker is never empty after first successful launch. FilterLutCache fun get(id: String): LutData? ← memory hit suspend fun fetch(id: String): LutData ← disk hit → network fetch → sha verify fun prefetch(ids: List<String>) ← background, queued, cancellable LRU 50MB on disk. Parsed LutData (33³ float array) memoized in memory. FilterRenderer fun apply(image, filterId, intensity): image Looks up LUT via FilterLutCache.get (synchronous, must already be cached). If not cached: returns source unmodified and signals caller to await fetch. Mix: out = lerp(srcPixel, lutSample(srcPixel), intensity). FilterPickerViewModel state: StateFlow<{ categories, filters, selectedId, intensity, thumbnails }> onSelect(id), onIntensityChange(value), onCategoryChange(catId)

边界收得很紧:渲染器不知道 HTTP,目录服务不知道 Metal/GL,缓存不知道 UI。

iOS 集成(插槽于Core/Rendering/VideoEffectRenderer.swift

  • FilterCatalogServiceactor,UI 通过@MainActor快照包装器绑定;
  • FilterLutCache.fetch使用URLSession.downloadTask,文件落到Caches/openreel-filters/{id}.cube
  • LUT 变成名为CIColorCubeCIFilterinputCubeDimension: 33inputCubeData: Data);
  • 强度用CIBlendWithMask:源 CIImage 与 LUT 应用后的 CIImage 之间,用一张 alpha = intensity 的纯色蒙版混合,单个 CIFilter、GPU 融合执行。

Android 集成(插槽于core/effects/ClipEffectPipeline.kt

  • FilterCatalogService暴露StateFlow<FilterCatalog>,缓存使用 OkHttp +withContext(Dispatchers.IO)
  • 渲染器添加一个 Media3GlEffect:把 33³ LUT 上传为GL_TEXTURE_3D,在 fragment shader 中逐像素采样;intensity 作为 uniform,GLSL 中执行mix(src, lut, intensity)

片段渲染链顺序(双平台锁定)

source → LUT (filter @ intensity) → user color adjustments → spatial effects → output

LUT 排在最前,意味着用户后续的颜色调整可预测地叠加在滤镜之上(这是 CapCut 的行为);顺序颠倒会让调整在不同滤镜间表现不一致。

Filter Picker UX:CapCut 风格选择器

入口:选中视频片段时,上下文工具栏上现有的 "Filter" 入口保持不变,仅替换内部面板。

布局(双平台一致):顶部为实时预览区,其下是强度滑杆(带百分比、Reset/Apply按钮),再往下是分类 Tab(Recent / Cinematic / Portrait / Vlog / Retro / Mood / B&W),底部为横向滚动的滤镜贴图行,None始终是最左侧贴图。

交互细节

  • 点选滤镜 → 立即以 100% 强度应用,选中贴图显示对勾 + 按 manifestaccent着色的圆环;
  • 再次点选同一滤镜 → 关闭(回到 None);
  • 拖动强度 → 实时预览,松手前不提交;选中 None 时隐藏滑杆;
  • Reset→ 强度回到 100%,滤镜保持选中;
  • Apply→ 提交并关闭;不点 Apply 直接关闭同样会提交(CapCut 行为,用户有 Undo);
  • Recents Tab:记录跨项目的最近 12 个使用项,持久化在 user defaults / DataStore;
  • 长按贴图 → "应用到全部片段",作为单一可撤销动作;
  • 无障碍:VoiceOver / TalkBack 播报"<滤镜名>, <分类>, <选中 | 未选中>";强度滑杆步进 5%;选中态用边框 + 对勾而非仅靠颜色区分(色盲安全)。

缩略图渲染管线

  1. 选择器打开 → 以低分辨率抓取当前预览帧(竖屏 144×256、横屏 256×144);iOS 从MetalVideoView.lastFrame.pixelBuffer取,Android 从最新的 ExoPlayer surface texture 取;
  2. 快照在内存中是单一 CIImage / Bitmap;
  3. 对每个可见贴图(初始约 8 个),在后台队列运行FilterRenderer.apply(snapshot, filterId, 1.0)
  4. 用户滚动时入队新贴图、取消视口外任务;
  5. (filterID, snapshotHash)为键缓存[filterID → thumbnail],播放头移动超过 1 s 时失效。

单贴图状态机

unknown → pending-lut → ready → rendered \─────────────▶ failed (retries on tap)

pending-lutfailed贴图渲染占位符(accent 色 + 滤镜名),仍可点按,点按会提升下载优先级。

空态 / 失败 / 离线副本

  • 从未加载到 manifest:空态 + 重试按钮;
  • 离线且无任何缓存:同上;
  • 离线但有部分缓存:显示已缓存滤镜 + 横幅提示 "More filters available when you're online"。

错误处理与边界情况

  • 项目引用了 manifest 中已不存在的滤镜:若 LUT 仍在本地缓存,片段正常渲染;选择器在 "Unavailable" 区以灰色显示该滤镜、禁止新应用;若 manifest 与缓存均无,则以intensity = 0渲染并在检查器提示一行警告;
  • Schema 漂移:manifest 解码器忽略未知字段;新增必填字段由minClientVersion门控,低于该版本的客户端隐藏受影响滤镜并提示升级;
  • 磁盘上 LUT 损坏:读取时校验sha256,不匹配则删除 + 重取一次,二次不匹配贴图进入failed
  • 磁盘满ENOSPC时缓存驱逐最旧的 5 条并重试;仍失败则仅内存保留 LUT 供当前会话使用,并弹一次性 "Storage full" 提示;
  • 下载中途应用进入后台:可断点续传(iOS 用 URLSession background 配置,Android 用 OkHttpRange:);恢复后选择器随下载完成刷新贴图;
  • 两个片段并发打开选择器:目录与缓存均为单例,同一filterId返回同一份 memoizedLutData,不会重复解析或下载;
  • filterPreset效果迁移generate.py首次运行会把现有 20 个参数预设烘焙成.cube;项目加载迁移在AppState中把effects: [filterPreset(...)]重写为clip.filter: { id, intensity };旧渲染路径保留一个应用版本作为回退,随后移除;
  • 两个片段使用同一滤镜:共享同一份内存中的LutData
  • 滤镜 id 改名:manifest 的oldIds数组在 reconcile 时透明重映射;
  • manifest 服务端 5xx:使用磁盘上的 last-known-good manifest,首次成功启动后选择器永远不会为空;
  • 检查时点 vs 渲染时点:长导出开始时拍摄LutData快照,导出中途 manifest 变化不影响本次导出;
  • 隐私 / 遥测:滤镜请求不携带 PII、不做按用户标记;未来可选聚合 "filter applied" 事件,同样无 PII。

测试策略与性能预算

工具层(scripts/filters/)

  • 每个变换算子有单测,已知 RGB 三元组上容差 ±1 LSB(仓库中 test_transforms.py 覆盖temperaturetintcontrastsaturation等核心算子);
  • Golden-file 测试:fixture 配方 → 期望.cube,重新生成不一致即报 diff(test_generate.py 的test_sample_generates_expected_cube);
  • manifest 在 CI 中按 JSON Schema 校验(test_recipe_loader.py 的test_write_manifest_validates_against_schema);
  • 每个配方冒烟测试:可加载、可生成、可确定性渲染固定色卡 PNG,提交的期望 PNG 作为视觉回归面。

托管层(R2 公共桶)

部署后 curl 冒烟:curl -sI https://filters.openreel.video/manifest.json应返回 200 + ETag + 正确Cache-Control;携带If-None-Match: <etag>重复请求应得 304;对.cubeURL 同样验证immutable头。

移动端服务层(无 UI)

  • FilterCatalogService用 fixture manifest 做 reconcile 测试:首次拉取、无变化重取、增/删/改/改名、未知字段容忍;
  • FilterLutCache:内存 + 磁盘命中、未命中 → 拉取 → sha 校验、不匹配重试后失败、上限驱逐、并发拉取返回共享结果;
  • FilterRenderer:intensity 0/50/100 的 golden-image 快照测试,容差 ≤ 1 LSB / 通道。

跨平台一致性(Phase 1 起为 CI 门禁)

同一组(sample.png, filterId, intensity)三元组分别通过 iOS 与 Android 渲染器,输出 PNG 逐通道比对,任一处偏差 > 1 LSB 即 CI 失败。

性能预算

  • 选择器打开 → 首块贴图绘制:≤ 250 ms(中端设备、暖缓存),CI 强制
  • LUT 解析 + GPU 上传:≤ 20 ms / 滤镜,profiled 而非门禁
  • 1080p 每帧 LUT 通道:≤ 1 ms,profiled 而非门禁。

明确排除:端到端模拟器测试(不稳定、慢)、网络混沌模糊测试(失败面已被 mock 单测覆盖)。

实施阶段与风险登记

Rollout Phases

  • Phase 0 — 基础(约 1.5 天,无用户可见变化):创建 R2 桶openreel-filters+ 自定义域filters.openreel.video;搭建scripts/filters/(generate.py、transforms、schema、golden fixtures、1 个英雄配方 Teal & Orange);用wrangler r2 object put --cache-control部署并 curl 验证;CI 接入工具单测。退出门禁:manifest 与 Teal & Orange.cubefilters.openreel.video上以正确 ETag + Cache-Control 上线;
  • Phase 1 — 单滤镜端到端(约 4 天):Teal & Orange 贯通双平台移动管线;iOS 四类接入VideoEffectRenderer、选择器替换旧滤镜面板;Android 同样四类接入ClipEffectPipeline;选择器显示 1 个滤镜 + None,强度可用,Apply 提交,跨平台一致性测试通过。退出门禁:TestFlight + Android 内部轨道对真实视频应用滤镜且肉眼一致;
  • Phase 2 — 目录规模化(约 5 天,可并行):编写剩余约 55 个配方;把现有 20 个FilterPresetCatalog参数预设烘焙进 recipes/LUT;60 个全部过generate.py并部署 R2;60 个全部跑跨平台一致性;每个分类对真实素材做内部 QA。退出门禁:60 款滤镜上线、一致性全绿、QA 签字;
  • Phase 3 — 迁移与发布(约 2 天)AppState项目加载迁移;旧VideoEffectType.filterPreset渲染路径保留一个版本后移除;提交 App Store + Play Store。退出门禁:带新选择器的公开版本;
  • Phase 4 — 内容迭代(每次零工程投入):新滤镜 = 写配方 →generate.py→ 同步 R2,客户端下次启动自动生效;可选同一观感的双 manifest 条目 A/B;可选无 PII 的聚合分析。

风险登记

  • 配方质量(最大风险):代码生成的滤镜不经肉眼调校容易"数学味过重"。缓解:Phase 2 对每个分类的真实素材做 QA;快速迭代闭环"改 YAML →generate.py→ 上传 → 客户端即时生效";
  • 跨平台颜色漂移CIColorCube与 GLES 3D 纹理采样之间的差异。缓解:Phase 1 起一致性测试作为 CI 门禁;
  • R2 成本 / 热路径:60 个 LUT × 多设备并非真实规模问题,但Cache-Control: immutable是必须的,确保流量命中边缘缓存而非回源桶。

工作量估算

单人端到端约 2 周;两人(各自负责一个平台,Phase 0 后并行)约 1 周;另加 Phase 2 中 1–2 天的调色品味决策时间。

小结

Filter Presets v1 是一个把"调色能力"产品化的完整范本:以YAML 配方 + NumPy 向量化生成 33³ LUT的方式把滤镜从代码中的参数预设抽离为可版本化、可热更新、可跨平台一致的静态资产,借助R2 公共桶 + 不可变缓存做到"无需发版即可上新滤镜",并通过单例目录服务 + LRU 缓存 + sha256 完整性校验 + 四类组件分离在移动端保证首屏 250 ms 以内的体验与渲染一致性。仓库中 scripts/filters/ 目录已经落地了配方加载、变换算子、LUT 写出、manifest 校验与 R2 部署脚本等核心实现,配合 docs/superpowers/plans/2026-05-22-filter-presets.md 的实施计划,可作为后续开发或复刻此类"云端驱动滤镜目录"架构的直接参考。

【免费下载链接】openreel-videoOpenReel Video - Professional browser-based video editor. Open source CapCut alternative. 100% browser-based, no installation, no cloud uploads, no watermarks.项目地址: https://gitcode.com/GitHub_Trending/op/openreel-video

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询