)
OpenMontage Backlot 详解基于磁盘事件驱动的实时制片看板Living Storyboard【免费下载链接】OpenMontageWorlds first open-source, agentic video production system. 12 production pipelines, 100 tools, 700 agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontageBacklot 是 OpenMontage 内置的一个只读、本地、由磁盘状态推导的活的故事板living storyboard模块它把正在发生的生产流程实时呈现在浏览器里——流水线各阶段逐一点亮、剧本以分场脚本页呈现、场景计划以胶片条filmstrip形式随素材生成而逐步填充同时展示决策、花费与活动。本文以 backlot/README.md 为核心骨架结合 服务端实现、状态推导层、事件流 与 检查点协议 的源码细节完整讲解 Backlot 的启动方式、实时机制、状态来源、看板构成、回放能力与优雅降级策略读完即可在本地跑起一个实时制片看板并理解其底层原理。Backlot 看板运行实况docs/images/backlot/board-live.png一、Backlot 是什么观察而非汇报Backlot 的全部设计都建立在一个核心契约上看板上的所有状态都不是 Agent 主动上报的而是从流水线本来就会写入projects/id/目录的文件中推导出来的。官方 README 将其概括为A read-only local board that shows a production happening: pipeline stages lighting up, the script as a screenplay page, the scene plan as a filmstrip that fills in as assets generate, decisions, spend, and activity — all derived from what the pipeline already writes toprojects/id/.模块包内 backlot/init.py 进一步把设计契约凝练为三条Observation, not reporting所有状态都来自流水线本就写入的文件Agent 永远不需要去更新 UINever block, never break残缺或损坏的状态只会让看板优雅降级绝不能让它崩溃或阻塞生产Agent 的唯一职责在流水线初始化时执行一次python -m backlot open project。也就是说Backlot 是纯旁观者它读文件、渲染、推送变更但永远不会写项目目录——这一点在 server.py 的模块注释里被明确声明The server never writes to project directories.二、快速开始三条命令README 给出了三种典型用法python -m backlot open project-id # 如无服务则启动并打开浏览器到该项目的看板 python -m backlot open # 打开库视图所有项目一览 python -m backlot serve --port 4750 # 前台运行服务从 backlot/main.py 可以看到命令背后的实际行为open子命令是幂等且非致命的它先探测http://127.0.0.1:port/api/health判断服务是否已在运行若未运行则以分离的后台进程方式拉起serveWindows 使用CREATE_NEW_PROCESS_GROUPPOSIX 使用start_new_session然后轮询健康检查最多 15 秒最后用webbrowser.open打开http://127.0.0.1:port/p/project-id无参数则打开库视图/。端口默认为4750定义于 backlot/init.py 的DEFAULT_PORT可通过环境变量BACKLOT_PORT覆盖。设计上即使服务启动失败open也只是打印一条提示并返回非零退出码但不会抛异常——因为 Agent 在流水线初始化时调用它生产流程必须继续推进。这正是never block的体现。三、实时性如何保持watchfiles SSE 变更推送README 指出Backlot 的live能力完全不需要 Agent 参与一个基于watchfiles的 watcher 监听projects/目录把变更通知通过 SSEServer-Sent Events推送给浏览器浏览器收到后重新拉取看板状态。这一机制在 server.py 中有完整的源码级实现监听循环_watch_projects()在 FastAPI 的 lifespan 中作为后台任务启动_lifespan负责创建与取消该任务使用awatch(PROJECTS_DIR, recursiveTrue, step400)递归监听step400意味着变更事件会先聚合 400ms 再触发一次回调天然具备防抖效果。变更到项目的映射热路径_project_of_change()只做纯字符串比较避免逐路径做文件系统调用把变更路径归一化后截取projects/根下的第一级目录名作为 project id同时过滤掉node_modules、.git、__pycache__、.cache这些对看板无意义的噪声路径。ChangeHub 扇出ChangeHub维护订阅者队列与订阅时绑定的 project idNone表示订阅全部项目。publish(project_id)只把通知投递给匹配的订阅者每个队列maxsize64队列满时直接丢弃——因为队列里只装这个订阅者真正关心的事件满了也意味着必然有一次待消费的唤醒安全可丢。SSE 端点/api/project/{project_id}/events与/api/library/events分别面向单个项目看板与库视图。两者都先发送{type:hello}握手消息之后每 15 秒发送一次heartbeatSSE_HEARTBEAT_SECONDS 15以维持连接收到变更后先排空队列里积压的所有通知coalesce bursts再发送一条{type:change}让浏览器只做一次重拉取——渲染高峰期一次变更批次可能包含成千上万个文件路径这种合并至关重要。库视图缓存_cached_summaries()对每个项目的摘要做缓存全量解析成本高由 watcher 在对应项目变更时通过_invalidate_summary()失效。另外值得一提的是响应头SSE 流设置了Cache-Control: no-cache与X-Accel-Buffering: no确保 Nginx 等反代不会缓冲事件流而 UI 页面与/ui/*静态资源则通过一个 HTTP 中间件统一加no-cache让 UI 修复在普通刷新下即可生效媒体与缩略图保持常规缓存。四、状态从哪来磁盘文件即状态源README 中给出了一张看板元素 ↔ 磁盘来源的对应表这是理解 Backlot 的关键完整列出如下看板元素磁盘来源身份 / 轨道顺序project.jsonpipeline_defs/type.yaml阶段状态、门禁gate、版本checkpoint_stage.jsonhistory/剧本卡片 / 弹窗artifacts/script.json胶片条卡片scene_plan × script × asset_manifest三表联结生成中微光、活动events.jsonl由BaseTool插桩写入花费表检查点的cost_snapshot渲染结果renders/*.mp4外加根目录级 mp4 启发式下面结合源码逐一深入。4.1 项目身份与轨道顺序project.json 流水线清单init_project()见 lib/checkpoint.py在创建项目时会写入标准的目录骨架artifacts/、assets/images|video|audio|music/、renders/和project.json标记文件其中记录project_id、title、pipeline_type、style_playbook与created_at且幂等重复执行保留原created_at并合并字段。load_board_state()见 backlot/state.py读取该标记再通过_load_pipeline_meta()调用 lib/pipeline_loader.py 加载pipeline_defs/type.yaml清单得到每个阶段的名称、门禁标记human_approval_default与产出物列表。若清单加载失败则回退到内置的FALLBACK_STAGESresearch → proposal → idea → script → scene_plan → assets → edit → compose → publish。4.2 阶段状态、门禁与版本checkpoint_stage.jsonhistory/每个阶段的状态以checkpoint_stage.json形式存在于项目根目录由write_checkpoint()原子写入先写临时文件再os.replace防止写一半留下损坏状态。看板端_collect_checkpoints()扫描所有checkpoint_*.json得到当前状态_collect_history()则扫描history/checkpoint_*.json得到归档的历史版本。由此_build_stage_rail()为每个阶段生成轨道条目包含statuspending/in_progress/awaiting_human/completed/failedgated与human_approved是否要求人工审批、是否已批准versionshistory归档数 当前检查点数前端据此显示v2之类的版本徽标history_entries按时间排列的状态轨迹历史 当前这是回放功能的原料gate_skipped门禁审计——若某个 gated 阶段状态为completed但历史与当前中从未出现过awaiting_human且没有human_approved则判定该门禁被跳过前端会亮出 ⚑ GATE SKIPPED 徽标。门禁本身在写入端即被 lib/checkpoint.py 强制执行GATE VIOLATION会直接拒绝写completed看板端则负责把历史遗留或手工写入的违规暴露出来。对于清单未声明的外来检查点如旧运行或流水线类型不匹配轨道依然会为其分配位置——按FALLBACK_STAGES的规范顺序插入idea 应该排在前列而不是吊在 publish 之后并打上undeclared标记供前端区分。4.3 剧本卡片artifacts/script.json看板的剧本卡片script card直接渲染artifacts/script.json标题、总时长、sections 列表含起止时间、对白文本text、speaker_directions舞台指示与enhancement_cues增强提示。前端 board.js 会根据 script 阶段的检查点状态显示 APPROVED / PENDING APPROVAL / DRAFTING 徽标点击卡片可展开完整剧本弹窗。4.4 胶片条scene_plan × script × asset_manifest三表联结这是 Backlot 最核心的故事板逻辑实现在_build_storyboard()中以scene_plan.json的scenes列表为主干为每个场景生成一张卡片场景 ↔ 剧本段落联结_find_script_section优先按script_section_id精确匹配无该字段时回退到时间窗口重叠最大的启发式匹配——把场景的[start_seconds, end_seconds]与每个剧本 section 的时间窗做交集取重叠最大者从而把旁白文本与场景卡片关联起来场景 ↔ 素材联结_asset_entry()将asset_manifest.json中每个 asset 归入其scene_id名下并按扩展名推断类型image / video / audio 等_resolve_asset_path()兼容三种真实世界路径写法项目相对路径assets/images/x.png、仓库相对路径projects/id/assets/images/x.png和绝对路径可渲染性判定renderable exists and ext in (image/video 扩展名)。像 atelier/动画这种指向.tsx合成文件的资产虽然存在于磁盘但无法缩略图化会从takes中剔除看板退而显示该场景的逐场景快照snapshots/scene_id.png见_find_scene_snapshot()而文件缺失的栅格/视频资产则保留为 file missing 指示生成中状态根据events.jsonl中每个场景最近一次顶层事件推断——start标记为 generatingfinish/error清除该状态嵌套depth0的 provider 事件被跳过因为外层调用的 finish 才是真正完成。前端据此在卡片上显示生成中微光与产生该素材的工具名generating_tool。每张场景卡片还携带duration_seconds、hero_moment高光时刻、shot_language、shot_intent、framing、movement等镜头语言信息以及takes可渲染素材列表、audio旁白/音乐/音效列表。4.5 活动流events.jsonlevents.jsonl是只追加的工具事件日志由 lib/events.py 提供读写能力emit_event()将一条事件ts 工具名、事件类型、场景 id、耗时、花费等追加到项目目录的events.jsonl永远不抛异常——可观测性绝不能中断生产infer_project_dir()从工具调用的入参推断归属项目显式的project_dir/project_path优先其次在一组路径提示键output_path、video_path、image_path等中查找只有解析到projects/根之下的路径才归属归属失败就静默不写never guess loudly, never failread_events()读取时跳过损坏行如跨进程追加撕裂的半行看板端只取最近 250 条。在 lib/checkpoint.py 的注释与 scripts/backlot_simulate_run.py 的用法中可以看到工具事件由BaseTool插桩层在工具执行时写入它同时驱动生成中微光与整块活动时间线。4.6 花费表检查点cost_snapshot看板取最新检查点携带的cost_snapshot按 mtime 排序取最后一个有快照的若没有则回退到asset_manifest.json的total_cost_usd。前端把total_spent_usd与budget_remaining_usd相加得到总预算渲染出花费数字与进度条超过 75% 转黄、超过 90% 转红。4.7 渲染结果renders/*.mp4与根目录启发式_scan_media()扫描renders/目录下的视频文件外加两个启发式项目根目录的*.mp4atelier 交付物惯例与*.mp3以及assets/music/与snapshots/、verify/目录下的图片。所有渲染结果按 mtime 倒序排列。此外_find_poster()为库视图挑选最佳封面优先故事板卡片的图片视觉其次快照再按assets/images → assets/frames → exports → assets → .的顺序找图最后兜底用最新渲染由/thumb端点抽帧。五、媒体服务缩略图与安全边界server.py 提供两条媒体端点/media/{project_id}/{file_path:path}直接以FileResponse返回项目内文件支持 Range 请求便于视频拖动播放/thumb/{project_id}/{file_path:path}缩略图端点宽度从(320, 640, 960)三档中就近取整。图片用 PIL 缩放后存 JPEGquality82视频则调用ffmpeg在1.5 秒处抽取一帧作为海报-ss 1.5 -frames:v 1再缩到目标宽度。缩略图以sha1(路径|mtime|size|宽度)为键缓存到.backlot/thumbs/并发未命中时用临时文件隔离写入。对无法抽帧的视频宁可返回 404 也绝不把原始视频字节喂给img消费者源码注释中的 F-03。两条端点都用_safe_project_dir()校验 project id拒绝含/ \ :的 id 与.、..并对解析后的目标路径做relative_to检查防止路径逃逸出项目目录越界即 403。六、看板呈现阶段轨道、剧本、胶片条与回放前端是纯原生 JS 的单页应用board.js board.css核心渲染逻辑包括头部状态条slate流水线类型、场景数/总时长、风格书style playbook三个 chip以及一个活动指示器——有awaiting_human阶段时显示 ◈ AWAITING YOU有停滞阶段显示红色 ⚠ STALLED?运行中显示 LIVE否则显示 IDLE · 距上次活动时间旁边是花费条。阶段轨道rail每个阶段一个节点按状态着色done / active / await / failed图标与副标题随状态变化如in_progress且有partial_progress.completed_scene_ids时显示N scenes done。点击节点打开抽屉drawer展示该阶段的评审指标critical / suggestions / nitpicks、评审摘要与规范产物如 compose 阶段对应render_report、final_review未运行的阶段显示 This stage hasnt run yet.。剧本卡片与胶片条如前所述四段折叠展示剧本、按场景渲染胶片条卡片每张卡片显示时间码、镜头语言、旁白与已生成的 takes。回放ReplayREADME 指出已完成的生产可以从头到尾擦洗scrub观看看板上的 ▶ REPLAY RUN状态由检查点历史与事件时间戳重建。前端replay {t0, t1, t, playing}即回放模式的运行状态回放数据源正是_build_stage_rail()产出的history_entries按时间排列的阶段状态轨迹与events.jsonl的时间戳——把两者按时间轴推进即可重演整条生产线的点亮过程。库视图index.html library.js则是一个项目卡片网格每张卡片显示标题、流水线类型、封面poster、live 状态、活动时间、当前活动阶段、是否在等待人工、完成阶段数、渲染数与场景数。七、没有真实生产也能体验模拟运行README 提供了一条不用跑真实流水线就能体验看板的路python scripts/backlot_simulate_run.py # 实时演示运行约 1 分钟 python -m backlot open backlot-demo-runscripts/backlot_simulate_run.py 会驱动一个虚构的 The Last Lighthouse 电影生产走完整套真实契约init_project初始化、write_checkpoint写各阶段检查点包括awaiting_human门禁、human_approved批准、partial_progress与cost_snapshot、emit_event写入按场景的工具事件、逐步追加asset_manifest并生成占位图片让看板真正活起来。脚本还支持--fast压缩等待到约 0.3s供自动化验证与--cleanup结束时清理项目目录两个参数。八、优雅降级与健壮性设计README 明确了两层降级无检查点的项目退化为watcher 发现了什么就显示什么的视图——媒体、快照、渲染结果仍然可见。看板端 state.py 的模块注释总结了设计原则never block, never break——畸形的 JSON、缺失的产物、写了一半的检查点都只能让看板降级绝不能让它崩溃。load_board_state()与list_projects()都永不抛异常解析失败统一返回带error字段的占位摘要。watcher 不可用_watch_projects()在ImportError未安装 watchfiles时直接返回看板退化为手动刷新README 与 server.py 均注明这一点。健壮性细节还包括_read_json()以errorsreplace读取并捕获一切异常事件读取跳过损坏行检查点in_progress心跳不会进入history/它不是版本只是进度心跳见_archive_superseded_checkpoint()停滞检测state.py 的 F-05 注释——一个in_progress阶段若超过STALL_WINDOW_SECONDS10 分钟没有文件系统活动会被标记stalled并在前端显式警告让卡死的 Agent可见而非沉默LIVE_WINDOW_SECONDS5 分钟则决定看板读作 live 还是 idle。九、测试保障tests/backlot/目录为该模块提供了成体系的测试覆盖test_server.py服务端 API 与 SSE、test_state.py状态推导、test_gate_scenarios.py门禁场景、test_ui_bug_bash.pyUI 冒烟、test_visual_eval.py视觉评估与 test_watch_captures.pywatcher 捕获。从 scripts/backlot_simulate_run.py 可以看到模拟运行脚本还复用了tests/contracts/test_phase0_contracts.py中的 schema 合法测试夹具来构造产物说明看板测试与契约测试共享同一套数据契约。十、总结Backlot 的价值在于它把可观测性的成本降到了极致不需要 Agent 额外上报、不需要数据库、不需要独立的前后端构建——流水线本来就要写的检查点、产物、事件日志就是全部数据源一个 watcher 加一组只读 API 就把它变成了实时演进的制片看板。它既是 Agent 与人之间的交接窗口awaiting_human门禁的呈现处也是事后审计与回放的基础。对想要在自己的流水线系统里构建类似磁盘即状态、文件变更即事件式可观测层的开发者backlot/server.py 的 SSE 扇出设计、backlot/state.py 的多源联结与降级策略都是可以直接借鉴的范本。【免费下载链接】OpenMontageWorlds first open-source, agentic video production system. 12 production pipelines, 100 tools, 700 agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考