UNPKG

tav-media

Version:

Cross platform media editing framework

364 lines (299 loc) 11 kB
# TAVMedia TAVMedia 是一个跨平台的音视频编辑框架,支持在移动端、桌面端、浏览器端和服务器端使用。 # Web 版使用指南 ## 浏览器支持 TAVMedia 目前支持 Chrome 75+, Safari 12+. ## 安装 在 Web 工程中使用 TAVMedia,您可以直接使用 NPM 将 TAVMedia 安装到您的项目中 ```shell npm install tav-media ``` ## 初始化 tav-media ```typescript import { initializeWasm, TAVWasmOptions } from 'tav-media'; const env: TAVWasmOptions = { /** * 可选,指定 wasm 文件在服务端的路径。如果不指定,则使用 `./tav-media-wasm.wasm` */ locateFile: wasmFileName => `/node_modules/tav-media/bin/wasm/${wasmFileName}`, /** * 可选,后续资源的 path 为相对路径时,会自动添加此前缀 */ baseUrl: '/', }; initializeWasm(env).then(async () => { // 调用业务初始化代码 }); ``` # License 指引 ## License 鉴权 在 TAVMedia SDK 中提供了鉴权的对应的 API ,可以参考使用: ```typescript /** * 验证授权信息 */ async function authorize() { const authResult = await TAVLicense.Auth( // License url 或者 license 内容的 ArrayBuffer 'assets/tav_media.license', // AppId 'your_appid_here', // License secret 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' ); if (authResult !== OAuthErrorCode.OK) { // 根据验证结果进行进一步处理 console.warn('授权失败,使用 TAVMedia 水印试用版'); } } initializeWasm(env).then(async () => { // 加载 TAVMedia wasm 后验证 license await authorize(); }); ``` # 基础功能 ## 使用 TAVMedia 渲染视频 TAVMedia Web 版封装了 `TAVMediaView` 类, 封装了 `TAVSurface`, `TAVVideoReader`, `TAVAudioReader` 的创建和调用过程, 提供了适合 Web 播放使用的 API。视频图像渲染到指定的 canvas,**音频会直接通过浏览器输出,暂时不支持读取每帧的音频合成数据**。 ```typescript // 创建一个 MovieClip 对象 const ghost = await MovieAsset.MakeFromPath('assets/ghost.mp4'); const movie = await MovieClip.MakeFromAsset(ghost); movie.duration = ghost.duration; // 创建一个 TAVMediaView,将图像渲染到 id 为 stage 的 canvas const view = TAVMediaView.MakeFromHtmlCanvas('#stage'); // 播放视频 await view.setMedia(movie); view.play(); ``` ## 同时播放多个媒体 通过将多个媒体添加到 Composition 的方式,可以在场景中同时播放多个视频和特效。例如下面的视频转场的例子。 ```typescript // 创建主时间轴 const root = new Composition(); root.width = 1280; root.height = 720; root.duration = 3_000_000; // 添加第一个视频 const ghost = await MovieAsset.MakeFromPath('video-640x360.mp4'); const movie1 = await MovieClip.MakeFromAsset(ghost); movie1.duration = ghost.duration; movie1.startTime = 0; // 添加第二个视频 const dog = await MovieAsset.MakeFromPath('video-640x360-2.mp4'); const movie2 = await MovieClip.MakeFromAsset(dog); movie2.duration = dog.duration; movie2.startTime = 800_000; // 添加转场 const zcAsset = await PAGAsset.MakeFromPath('zc.pag'); const zc = await PAGEffect.MakeFromAsset(zcAsset); zc.startTime = 800_000; zc.duration = zcAsset.duration; // 将两个视频作为转场效果的输入,在转场播放过程中,将根据设计师 // 指定的方式展示两个视频切换的效果。 zc.addInput(movie1); zc.addInput(movie2); // 添加到主时间轴 root.addClip(movie1); root.addClip(movie2); root.addClip(zc); // 播放 await view.setMedia(root); view.play(); ``` # 层级结构 # 显示控制 ## Matrix 和 CropRect 控制位移缩放旋转裁切 ```typescript const asset = await MovieAsset.MakeFromPath('video-640x360.mp4'); const clip = await MovieClip.MakeFromAsset(asset); clip.duration = 3_000_000; const matrix = new Matrix(); matrix.postTranslate(-asset.width / 2, -asset.height / 2); matrix.postScale(2, 2); matrix.postTranslate(asset.width / 2, asset.height / 2); const cropRect = Rect.MakeXYWH(asset.width / 4, asset.height / 4, asset.width / 2, asset.height / 2); clip.matrix = matrix; clip.cropRect = cropRect; ``` ## Opacity opacity是不透明度,通过设置opacity可以控制Movie的透明度。取值范围是0.0-1.00是完全透明,1是完全不透明。 ```typescript const clip = await MovieClip.MakeFromAsset(asset); clip.opacity = 0.5; ``` # 时间控制 ## 通过 Clip 和 Composition 排布时间轴 ```typescript const ghost = await MovieAsset.MakeFromPath('video-640x360.mp4'); const movie1 = await MovieClip.MakeFromAsset(ghost); movie1.startTime = 1_000_000; movie1.duration = 4_000_000; // 创建一个分辨率为 720 * 1280 的Composition // contentStartTime 为 1_000_000,表示从视频的第1秒开始播放 // contentDuration 为 10_000_000,表示播放 10 秒 const composition = await Composition.Make(720, 1280, 2_000_000, 10_000_000); composition.duration = 10_000_000; composition.addClip(movie1); ``` ## 通过 MovieClip 裁剪资源片段 ```typescript const ghost = await MovieAsset.MakeFromPath('video-640x360.mp4'); const movie1 = await MovieClip.MakeFromAsset(ghost, 1_000_000, 4_000_000); movie1.startTime = 0; movie1.duration = 4_000_000; ``` ## 变速 ```typescript // 通过 Movie 直接变速 const totalDuration = 10_000_000; const asset = await MovieAsset.MakeFromPath('video-640x360.mp4'); const movie = await MovieClip.MakeFromAsset(asset, 0, totalDuration); movie.startTime = 0; movie.duration = totalDuration / 2; // 通过 Composition 进行变速 const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4'); const movie1 = await MovieClip.MakeFromAsset(asset1, 0, totalDuration); movie1.startTime = 0; movie1.duration = totalDuration; const composition = await Composition.Make(640, 360, 0, totalDuration); composition.addClip(movie1); composition.startTime = 0; composition.duration = totalDuration / 2; ``` ## 定格 ```typescript // 通过 Movie 定格 const totalDuration = 10_000_000; const asset = await MovieAsset.MakeFromPath('video-640x360.mp4'); const movie = await MovieClip.MakeFromAsset(asset, 1_000_000, 0); movie.startTime = 0; movie.duration = totalDuration; // 通过 Composition 定格 const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4'); const movie1 = await MovieClip.MakeFromAsset(asset1, 0, totalDuration); movie1.startTime = 0; movie1.duration = totalDuration; const composition = await Composition.Make(640, 360, 1_000_000, 0); composition.addClip(movie1); composition.startTime = 0; composition.duration = totalDuration; ``` # 音频播放 ## TAVMedia的音频播放 ```typescript const totalDuration = 10_000_000; const path = 'hoaprox.mp3'; const asset = await AudioAsset.MakeFromPath(path); const audio = await AudioClip.MakeFromAsset(asset, 0, totalDuration); audio.duration = totalDuration; const effect = await AudioVolumeEffect.MakeFIFOEffect(audio, 1.0, 3_000_000, 3_000_000); effect.startTime = 0; effect.duration = totalDuration; ``` # 添加效果 ## TransformEffect ```typescript const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4'); const movie1 = await MovieClip.MakeFromAsset(asset1); movie1.startTime = 0; movie1.duration = asset1.duration; const transformEffect = TransformEffect.MakeTransformEffect(); const keyframes: NoBlankArray<Keyframe> = [ TAVKeyframe.MakeLinear(0, 1_000_000, 0, 90), ]; transformEffect.transform2D = { rotation: TAVProperty.MakeAnimatableProperty(keyframes), } transformEffect.addInput(movie1); ``` ## TAVColorTuningEffect ```typescript const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4'); const movie1 = await MovieClip.MakeFromAsset(asset1); movie1.startTime = 0; movie1.duration = asset1.duration; const colorTuningEffect = ColorTuningEffect.MakeColorTuningEffect(); const keyframes: NoBlankArray<Keyframe> = [ TAVKeyframe.MakeLinear(0, 1_000_000, -50, 50), ]; // 设置饱和度 colorTuningEffect.colorTuning = { saturation: TAVProperty.MakeAnimatableProperty(keyframes), } colorTuningEffect.addInput(movie1); ``` ## LUTEffect ```typescript const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4'); const movie1 = await MovieClip.MakeFromAsset(asset1); movie1.startTime = 0; movie1.duration = asset1.duration; const lutEffect = await LUTEffect.MakeFromPath('lut.png'); lutEffect.startTime = 0; lutEffect.duration = 1_000_000; lutEffect.strength = 0.5; lutEffect.addInput(movie1); ``` ## ChromaMatting ```typescript const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4'); const movie1 = await MovieClip.MakeFromAsset(asset1); movie1.startTime = 0; movie1.duration = asset1.duration; const chromaMattingEffect = ChromaMattingEffect.Make(); chromaMattingEffect.chromaMattingConfig = { intensity: 0.2, shadow: 0.5, selectedColor: { red: 0, green: 255, blue: 0, alpha: 255, }, } chromaMattingEffect.startTime = 0; chromaMattingEffect.duration = 1_000_000; chromaMattingEffect.addInput(movie1); ``` ## PAGEffect ```typescript const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4'); const movie1 = await MovieClip.MakeFromAsset(asset1); movie1.startTime = 0; movie1.duration = asset1.duration; const pagAsset = await PAGAsset.MakeFromPath('fw.pag'); const pagEffect = await PAGEffect.MakeFromAsset(pagAsset); // numImgs 表示该 PAG File 最大支持的替换图层数量 const numImgs = pagEffect.numImages; // 添加 movie1 作为 effect 的输入, // scaleMode 可以设置如何缩放 movie1 以适应 PAG 图层的大小 // editableIndex 表示 PAG File 中的第几个可替换图层 pagEffect.addInput(movie1, ScaleMode.LetterBox, 0); pagEffect.startTime = 0; // 这边可以根据需求设置成任意时长,例子中设置成文件时长 pagEffect.duration = pagAsset.duration; ``` ```typescript const movieDuration = 3_000_000; const asset1 = await MovieAsset.MakeFromPath('1.mp4'); const movie1 = await MovieClip.MakeFromAsset(asset1); movie1.duration = movieDuration; const asset2 = await MovieAsset.MakeFromPath('2.mp4'); const movie2 = await MovieClip.MakeFromAsset(asset2); movie2.duration = movieDuration; const pagAsset = await PAGAsset.MakeFromPath('zc.pag'); const pagEffect = await PAGEffect.MakeFromAsset(pagAsset); const numImgs = pagEffect.numImages; // 分别设置 movie1 和 movie2 替换 PAG file 中的第一个替换图层和第二个替换图层 // 替换哪两个替换图层需要根据 PAG 文件来决定,通常情况下 input0 对应 editableIndex0,input1 对应 editableIndex1 // 也可以通过 `addInput` 的 `editableIndex` 参数来指定 pagEffect.addInput(movie1); pagEffect.addInput(movie2); // 这边可以根据需求设置成任意时长,例子中设置成文件时长 pagEffect.duration = pagAsset.duration; // 设置转场和 movie2 的开始时间为 movie1 的时长减去 PAG File 的时长, // 这样 PAG Effect 能够将效果作用到 movie1 和 movie2 之间 movie2.startTime = movie1.duration - pagEffect.duration; pagEffect.startTime = movie2.startTime; ```