vpbox
v1.2.1
Published
Video encryption & HLS packaging toolkit (PBOX format) with TypeScript support
Readme
vpbox
视频加密与 HLS 打包工具包(PBOX 格式),TypeScript 实现。
把一个视频文件转成 AES 加密的 HLS 分片,打乱分片文件名,生成缩略图,最后把视频信息、密钥、m3u8、缩略图打包进一个用密码加密的 .pbox 容器;同样支持用密码解密还原。
特性
- HLS 转封装:基于 ffmpeg,支持流复制与重编码、自定义分片时长、AES-128 加密
- 分片名打乱:用
md5(uuid)重命名每个分片,并生成新旧名称映射map.json - 缩略图生成:ffmpeg 截图 + sharp 压缩,输出大图与小图
- PBOX 容器:自研二进制格式,
scrypt派生密钥 +AES-256-GCM认证加密 - 视频信息探测:通过 ffprobe 获取分辨率、帧率、码率、编码等
- 视频压缩:按上限阈值条件性重编码(分辨率 / 帧率 / 码率 / 编码)
- 进度回调:HLS 转换与视频压缩均支持进度回调
环境依赖
- Node.js >= 18
- 系统已安装
ffmpeg与ffprobe,并在PATH中可用
安装
npm install vpbox快速开始
import path from 'node:path';
import { setConfig, encrypt, decrypt, getVideoInfo } from 'vpbox';
// 可选:设置 m3u8 中分片的 base url 前缀
setConfig({
M3U8_BASE_URL: 'https://example.com',
});
const input = path.resolve('input.mp4');
(async () => {
// 1. 探测视频信息
const videoInfo = await getVideoInfo(input);
console.log(videoInfo);
// 2. 加密打包
const encrypted = await encrypt({
inputPath: input,
password: 'your-password',
outputPath: path.resolve('temps'),
hlsOptions: {
hlsTime: 10,
onProgress: (info) => console.log(`${info.percent ?? 0}%`),
},
});
console.log(encrypted);
// 3. 解密还原(不传 outputPath 时仅返回文件列表,不写盘)
const decrypted = await decrypt({
inputPath: path.resolve('temps', encrypted.path),
password: 'your-password',
// outputPath: path.resolve('outputs'),
});
console.log(decrypted);
})();API
encrypt(options): Promise<AssemblePboxResult>
完整加密流程,内部依次执行:探测视频信息 → 生成 AES key → 转 HLS(带加密)→ 打乱分片名 → 生成缩略图 → 组装 .pbox。
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| inputPath | string | 输入视频文件路径 |
| password | string | PBOX 容器加密密码 |
| outputPath | string | 输出目录 |
| hlsOptions | Partial<ToHlsOptions> | HLS 转封装选项,见下表 |
返回值包含 hash、name、path、filename、videoInfo、thumbnail 等字段。
decrypt(options): Promise<PboxDecryptedFile[]>
解密 .pbox 文件。
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| inputPath | string | .pbox 文件路径 |
| password | string | 加密时使用的密码 |
| outputPath | string(可选) | 输出目录;不传时仅返回文件列表,其中 m3u8/json 内容会转成字符串 |
getVideoInfo(inputPath): Promise<VideoInfo>
通过 ffprobe 获取视频信息,返回时长、宽高、帧率、码率、编码、像素格式等。
compressVideo(inputPath, options?): Promise<string>
按上限阈值条件性重编码压缩视频:分辨率、帧率、视频码率、视频编码任一不达标则重编码视频流;音频编码不一致或码率超标则重编码音频流;全部达标时直接返回原路径。仅重编码不达标的部分,避免无谓重写。
| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| inputPath | string | — | 输入视频文件路径 |
| options.maxWidth | number | 1080 | 最大宽度(像素),超过则等比缩小 |
| options.maxHeight | number | 1080 | 最大高度(像素),超过则等比缩小 |
| options.maxFrameRate | number | 60 | 最大帧率(fps),超过则降帧 |
| options.maxVideoBitRate | number | 8000000 | 最大视频码率(bps),超过则限流重编码 |
| options.allowedVideoCodecs | string[] | config.ALLOWED_VIDEO_CODECS | 允许的视频编码(小写),命中则视频流可走 copy |
| options.outputVideoCodec | string | 'libx264' | 输出视频编码 |
| options.crf | number | 23 | 重编码质量(CRF),值越小质量越高;仅在视频码率未超限时生效 |
| options.preset | string | 'medium' | x264 编码预设,越慢压缩率越高 |
| options.audioCodec | string | 'aac' | 输出音频编码 |
| options.audioBitRate | string | '160k' | 输出音频码率;源码率更低时按源码率重编码 |
| options.audioChannels | number | 2 | 输出音频声道数 |
| options.outputFormat | string | 'mp4' | 输出容器格式(扩展名,如 mp4/avi/mkv/mov);mp4/m4v/mov 自动加 +faststart |
| options.outputDir | string | 源文件所在目录 | 输出目录 |
| options.onProgress | (p: CompressProgress) => void | — | 进度回调;源文件已满足要求直接返回时会回调一次 100% |
返回值为压缩后的视频文件路径(满足要求时为原路径)。
import { compressVideo } from 'vpbox';
const out = await compressVideo(inputPath, {
maxWidth: 720,
maxVideoBitRate: 2_000_000,
outputFormat: 'mp4',
onProgress: (p) => console.log(`${p.percent}% @ ${p.timemark}`),
});setConfig(config) => void
全局可配置常量,直接修改即可生效:
import { setConfig } from 'vpbox';
setConfig({
M3U8_BASE_URL: 'https://cdn.example.com/',
THUMBNAIL_WIDTH: 320,
THUMBNAIL_HEIGHT: 180,
});| 字段 | 默认值 | 说明 |
| --- | --- | --- |
| ENC_NAME | enc.key | AES 加密 key 文件名 |
| KEY_INFO_NAME | key_info | ffmpeg 用的 key_info 文件名 |
| VIDEO_INFO_NAME | video.json | 视频信息 json 文件名 |
| THUMBNAIL_NAME | thumbnail.jpg | 压缩后缩略图文件名 |
| THUMBNAIL_BIG_NAME | thumbnail_big.jpg | 原始截图文件名 |
| M3U8_NAME | index.m3u8 | m3u8 播放列表文件名 |
| M3U8_BASE_URL | '' | m3u8 中分片的 base url 前缀 |
| THUMBNAIL_TIMESTAMP | 1 | 截图时间点(秒) |
| THUMBNAIL_FRAME | cover | sharp 缩放模式 |
| THUMBNAIL_QUALITY | 60 | 缩略图压缩质量 1-100 |
| THUMBNAIL_WIDTH | 210 | 缩略图宽度 |
| THUMBNAIL_HEIGHT | 110 | 缩略图高度 |
HLS 选项(hlsOptions)
| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| hlsTime | number | 30 | 每个分片时长(秒) |
| playlistType | 'vod' \| 'event' | vod | 播放列表类型 |
| segmentFilename | string | %05d | 分片文件名模板 |
| baseUrl | string | — | m3u8 中分片的 base url |
| hlsListSize | number | 0 | 播放列表条目数,0 为全部 |
| copyStream | boolean | true | 是否使用流复制模式(-c copy) |
| onProgress | (p: HlsProgress) => void | — | 进度回调 |
PBOX 文件格式
二进制布局:
MAGIC(4) | VERSION(1) | SALT(16) | NONCE(12) | CIPHERTEXT | AUTH_TAG(16)- MAGIC:固定字符串
PBOX - VERSION:版本号,当前为
1 - SALT:16 字节随机盐
- NONCE:12 字节随机 nonce
- CIPHERTEXT:AES-256-GCM 加密后的明文(JSON 数组,每项含
name与 base64 编码的data) - AUTH_TAG:16 字节 GCM 认证标签
密钥派生:scrypt(password, salt, 32),参数 N=16384, r=8, p=1。
构建
npm run build # 使用 tsup 构建
npm run dev # 通过 tsx 运行 example.tsLicense
ISC
