用 Mediabunny 读取视频时长:在 Remotion 中跨浏览器、Node.js 与 Bun 获取视频长度的统一方案
Mediabunny 是 Remotion 官方技能(remotion-multimedia)中推荐的浏览器端多媒体处理库,可安全、跨环境地解析音频与视频元数据。本文以技能文档 get-video-duration.md 为骨架,讲解如何用 Mediabunny 的 Input + computeDuration() 获取视频时长(单位:秒),覆盖远程 URL、Remotion public/ 目录静态资源以及 Node.js / Bun 本地文件三类场景,并结合仓库内真实用法给出源码级佐证。读完即可写出一个可在浏览器、服务端通用复用的 getVideoDuration 工具函数。
Mediabunny 的能力定位与本技能文档在仓库中的位置
Mediabunny 是一个处理浏览器中音视频的多媒体库。Remotion 仓库将其作为官方建议的底层能力接入自身多个包(例如 packages/media、packages/media-parser、packages/studio 等都在 package.json 中声明了 mediabunny 依赖),并以一套"技能(Skill)"文档对 Agent / LLM 给出规范用法。这些技能存放在 packages/skills/skills/remotion-multimedia/ 目录下,入口 SKILL.md 将其描述为 "Interacting with Mediabunny",并按能力拆成三份子文档:
- get-video-duration.md:读取视频时长(本文主题);
- get-audio-duration.md:读取音频时长;
- get-video-dimensions.md:读取视频宽高。
三者共享同一套 API 风格(构造 Input、选择 source、再调用解析方法),掌握了视频时长的读取方式,其他元数据读取几乎可以举一反三。
关于 Mediabunny 的跨环境能力,技能文档明确表述为:It works in browser, Node.js, and Bun environments(在浏览器、Node.js 与 Bun 环境中均可运行)。这也是它被选为 Remotion 统一媒体解析方案的重要原因——media-parser 的 parse-media.ts 中甚至留下了 @deprecated Use Mediabunny instead 的迁移提示,说明该项目正在把元数据解析能力收敛到 Mediabunny 上。
远程 URL 视频:从构造 Input 到 computeDuration()
读取视频时长的核心思路是:把视频源封装进 Mediabunny 的 Input,再用 computeDuration() 异步计算秒数。技能文档给出了可直接落地的实现:
import { Input, ALL_FORMATS, UrlSource } from "mediabunny";
export const getVideoDuration = async (src: string) => {
const input = new Input({
formats: ALL_FORMATS,
source: new UrlSource(src),
});
const durationInSeconds = await input.computeDuration();
return durationInSeconds;
};
这段代码中每个要素都有明确职责:
Input:Mediabunny 的统一解析入口,它持有"解析哪些格式"与"数据从哪来"两项配置;formats: ALL_FORMATS:告诉解析器允许匹配全部封装/容器格式。由于Input需要自行探测媒体容器类型,传入ALL_FORMATS可免去预先判断视频是 MP4、WebM 还是其他容器的工作;UrlSource:以远程 URL 字符串作为数据源。只要字符串指向一个可访问的视频地址即可;await input.computeDuration():返回视频时长,单位为秒,且为浮点数(如10.5表示 10.5 秒)。
computeDuration() 是异步方法,因此在组件、事件回调或服务端函数中调用时记得使用 await。
调用示例
const duration = await getVideoDuration("https://remotion.media/video.mp4");
console.log(duration); // e.g. 10.5 (seconds)
返回结果可直接用于 Remotion 的时间轴、进度条渲染或素材校验等场景——例如判断某段远程视频是否超出合成(composition)时长,再决定是否截断或提示用户。
Remotion 组件中调用:处理好异步生命周期
由于该函数返回 Promise<number>,若在 Remotion 组件内部使用,建议在数据就绪前先返回一个占位 UI(或配合 useEffect 缓存结果),避免"渲染时 await"导致的竞态。技能文档的示例函数是无状态的纯异步工具,天然适合放在 Remotion Studio 的 Props 编辑面板、媒体检查器或渲染前校验逻辑中复用。
读取 public/ 目录内的视频:务必包一层 staticFile()
在 Remotion 项目中,放在 public/ 目录中的视频不能直接把文件系统路径传给 getVideoDuration,必须先用 Remotion 的 staticFile() 把文件名解析成可访问的静态资源 URL。技能文档明确强调这一点:
import { staticFile } from "remotion";
const duration = await getVideoDuration(staticFile("video.mp4"));
这里 staticFile("video.mp4") 会解析为类似 /video.mp4 的路径(在配置了 staticBase 的浏览器环境中还会自动带上该前缀),从而让 UrlSource 能正常加载它。注意 staticFile() 接收的应当是 public/ 目录内文件的名称,而不是裸路径。
从 static-file.ts 的实现可以佐证其设计约束:它会对传入值做严格校验并抛出 TypeError,包括——
- 传入
http://或https://开头的远程 URL(应直接传 URL,不要包staticFile); - 传入
../、./开头的相对路径,或/Users、/home、C:等绝对路径; - 传入带
public/前缀的路径; - 传入
null/undefined。
同时 staticFile() 自 Remotion 4.0 起会自己完成按路径片段的 URL 编码(encodeBySplitting 逐段调用 encodeURIComponent),所以调用方无需(也不应)再手动包一层 encodeURIComponent()。这正是技能文档要求"把 public 目录中的文件路径交给 staticFile() 包裹"的底层原因——它保证 URL 编码、静态基址前缀都得到正确处理,避免本地播放正常、换到浏览器后 404 的隐患。
Node.js 与 Bun 环境:改用 FileSource
当视频不在远程、而是来自本地文件系统时,需把数据源从 UrlSource 换成 FileSource,其余 API 保持一致:
import { Input, ALL_FORMATS, FileSource } from "mediabunny";
const input = new Input({
formats: ALL_FORMATS,
source: new FileSource(file), // File object from input or drag-drop
});
const durationInSeconds = await input.computeDuration();
其中 file 通常来自浏览器中的文件选择(<input type="file">)或拖拽(drag-and-drop)得到的 File 对象;在 Node.js / Bun 这类服务端运行时中,则可基于本地文件构造对应的文件句柄传入 FileSource。这使同一套 Input 抽象得以在浏览器与 SSR、脚本执行场景间无缝切换——浏览器端用 UrlSource/FileSource(File),Node.js 与 Bun 中用 FileSource 指向本地文件。
仓库源码佐证:为什么"元数据优先、computeDuration 兜底"是推荐姿势
computeDuration() 并非唯一的时长来源。在仓库的 packages/media/src/get-duration-or-compute.ts 中,Remotion 自己给出了更高效的组合策略:
import type {Input} from 'mediabunny';
export const getDurationOrCompute = async (input: Input) => {
return (
(await input.getDurationFromMetadata(undefined, {
skipLiveWait: true,
})) ?? input.computeDuration(undefined, {skipLiveWait: true})
);
};
这段源码揭示了两个要点:
- Mediabunny 的
Input还提供getDurationFromMetadata(),它优先从容器/轨道的元数据中直接读取时长(通常无需完整解码、速度更快); - 元数据读取可能拿不到结果(返回空值,例如流式录制、时长信息未写在文件头部的视频),因此用
??回退到computeDuration()做完整解析,保证最终一定能得到数值。
技能文档直接使用 computeDuration(),胜在逻辑最简单、一调即得;在追求性能且格式可控的场景,可参考上述源码按"先元数据、再全量计算"的顺序优化。另需留意的是,文档页 get-video-metadata.mdx 提醒:旧版 @remotion/media-utils 的 getVideoMetadata() 不支持 Linux 上的 H.265 视频并对部分格式解析失败,且其返回的 durationInSeconds 可能为 Infinity(时长未写在文件开头时,例如边录边编码的摄像头视频)。使用 Mediabunny 是官方文档给出的替代方案;同时,若你的视频确实存在"时长写在文件末尾"的录制型格式,仍建议对用户上传素材做边界处理或先用 FFmpeg 重编码将时长信息前置。
安装与版本前提
Mediabunny 是独立发布的 npm 包。在本仓库中,它通过依赖目录表(catalog)在多个包中被引用(如 packages/media/package.json、packages/media-parser/package.json 中的 "mediabunny": "catalog:"),而模板类项目(如 packages/template-three/package.json)直接锁定了 mediabunny 版本号。在读者自己的 Remotion 项目中,若尚未安装,只需执行包管理器安装 mediabunny,即可在浏览器端、Node.js 或 Bun 中导入本文所用的 Input、ALL_FORMATS、UrlSource、FileSource。
与其他媒体技能文档的衔接
Mediabunny 读取媒体元数据的 API 高度统一,本文的模式可以平移到仓库内另两份技能文档:
- 读取音频时长:把
UrlSource指向音频地址并调用同样的computeDuration(),详见 get-audio-duration.md(内部同样覆盖staticFile()包裹与FileSource两种场景); - 读取视频宽高:改用
input.getPrimaryVideoTrack()后读取displayWidth/displayHeight,详见 get-video-dimensions.md。
小结
本文围绕 Remotion 技能文档 get-video-duration.md 给出的方案,总结了用 Mediabunny 获取视频时长的完整实践路径:
| 场景 | 数据源类型 | 关键代码 |
|---|---|---|
| 远程 URL | new UrlSource(src) |
new Input({formats: ALL_FORMATS, source}) + computeDuration() |
Remotion public/ 目录 |
new UrlSource(staticFile("video.mp4")) |
先用 staticFile() 解析,再走 URL 分支 |
| 浏览器本地文件 / Node.js / Bun | new FileSource(file) |
同一 Input,仅更换 source 类型 |
三个可以记住的要点:
computeDuration()返回的是以秒为单位的浮点数,调用是异步的;- Remotion 项目里凡是
public/目录的文件,一律先用staticFile()包裹后再作为 URL 传入; - 跨浏览器 / Node.js / Bun 的本质差异只在于 source 的构造方式(
UrlSource还是FileSource),Input与解析调用完全一致。
掌握了这套 API 之后,把示例扩展成"读取后自动校验时长并写入 Remotion Props""批量生成媒体资源清单"等能力,也就顺理成章了。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00