webcodecs-utils 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -20
- package/dist/index.cjs +1 -1
- package/dist/index.js +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,15 +11,15 @@ npm install webcodecs-utils
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
|
-
import { getBitrate,
|
|
14
|
+
import { getBitrate, GPUFrameRenderer, extractChannels, MP4Demuxer } from 'webcodecs-utils';
|
|
15
15
|
|
|
16
|
-
// Calculate optimal bitrate
|
|
17
|
-
const bitrate = getBitrate(1920, 1080
|
|
16
|
+
// Calculate optimal bitrate (defaults to 30fps)
|
|
17
|
+
const bitrate = getBitrate(1920, 1080);
|
|
18
18
|
|
|
19
19
|
// Zero-copy video rendering with WebGPU
|
|
20
|
-
const renderer = new
|
|
20
|
+
const renderer = new GPUFrameRenderer(canvas);
|
|
21
21
|
await renderer.init();
|
|
22
|
-
renderer.drawImage(videoFrame
|
|
22
|
+
renderer.drawImage(videoFrame);
|
|
23
23
|
|
|
24
24
|
// Extract audio channels
|
|
25
25
|
const decoder = new AudioDecoder({
|
|
@@ -45,14 +45,14 @@ const videoChunks = await demuxer.extractSegment('video', 0, 10);
|
|
|
45
45
|
Calculate optimal bitrate for video encoding based on resolution, framerate, and quality.
|
|
46
46
|
|
|
47
47
|
- 📄 [Source](./src/video/get-bitrate.ts)
|
|
48
|
-
- 🎮 [Demo](./demos/bitrate
|
|
48
|
+
- 🎮 [Demo](./demos/get-bitrate.html)
|
|
49
49
|
|
|
50
50
|
```typescript
|
|
51
51
|
function getBitrate(
|
|
52
52
|
width: number,
|
|
53
53
|
height: number,
|
|
54
|
-
fps
|
|
55
|
-
quality?: 'low' | 'good' | 'high' | 'very-high'
|
|
54
|
+
fps?: number, // default: 30
|
|
55
|
+
quality?: 'low' | 'good' | 'high' | 'very-high' // default: 'good'
|
|
56
56
|
): number
|
|
57
57
|
```
|
|
58
58
|
|
|
@@ -60,7 +60,7 @@ function getBitrate(
|
|
|
60
60
|
Generate proper codec strings (avc1, vp09, etc.) with correct profile/level for VideoEncoder configuration.
|
|
61
61
|
|
|
62
62
|
- 📄 [Source](./src/video/get-codec-string.ts)
|
|
63
|
-
- 🎮 [Demo](./demos/codec-string
|
|
63
|
+
- 🎮 [Demo](./demos/get-codec-string.html)
|
|
64
64
|
|
|
65
65
|
```typescript
|
|
66
66
|
function getCodecString(
|
|
@@ -71,21 +71,23 @@ function getCodecString(
|
|
|
71
71
|
): string
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
#### **
|
|
74
|
+
#### **GPUFrameRenderer**
|
|
75
75
|
Zero-copy video frame rendering using WebGPU importExternalTexture, with fallback to ImageBitmapRenderer.
|
|
76
76
|
|
|
77
|
-
- 📄 [Source](./src/video/gpu-
|
|
78
|
-
- 🎮 [Demo](./demos/gpu-renderer
|
|
77
|
+
- 📄 [Source](./src/video/gpu-renderer.ts)
|
|
78
|
+
- 🎮 [Demo](./demos/gpu-renderer.html)
|
|
79
79
|
|
|
80
80
|
```typescript
|
|
81
|
-
class
|
|
81
|
+
class GPUFrameRenderer {
|
|
82
82
|
constructor(canvas: HTMLCanvasElement | OffscreenCanvas, options?: {
|
|
83
83
|
filterMode?: 'linear' | 'bicubic'
|
|
84
84
|
})
|
|
85
85
|
|
|
86
86
|
async init(): Promise<void>
|
|
87
|
-
drawImage(videoFrame: VideoFrame
|
|
87
|
+
drawImage(videoFrame: VideoFrame): void
|
|
88
88
|
getMode(): 'webgpu' | 'bitmap' | null
|
|
89
|
+
getFilterMode(): 'linear' | 'bicubic'
|
|
90
|
+
setFilterMode(mode: 'linear' | 'bicubic'): void
|
|
89
91
|
destroy(): void
|
|
90
92
|
}
|
|
91
93
|
```
|
|
@@ -98,7 +100,7 @@ Extract and de-interleave audio channels from AudioData into Float32Array[].
|
|
|
98
100
|
Handles both planar (f32-planar) and interleaved (f32) audio formats automatically. Returns an array of Float32Array buffers, one per channel (e.g., [left, right] for stereo).
|
|
99
101
|
|
|
100
102
|
- 📄 [Source](./src/audio/extract-channels.ts)
|
|
101
|
-
- 🎮 [Demo](./demos/
|
|
103
|
+
- 🎮 [Demo](./demos/extract-channels.html)
|
|
102
104
|
|
|
103
105
|
```typescript
|
|
104
106
|
function extractChannels(audioData: AudioData): Float32Array[]
|
|
@@ -119,8 +121,8 @@ for (let i = 0; i < leftChannel.length; i++) {
|
|
|
119
121
|
#### **MP3Encoder**
|
|
120
122
|
Encode AudioData to MP3 format using LameJS.
|
|
121
123
|
|
|
122
|
-
- 📄 [Source](./src/audio/mp3
|
|
123
|
-
- 🎮 [Demo](./demos/mp3-encoder
|
|
124
|
+
- 📄 [Source](./src/audio/mp3.ts)
|
|
125
|
+
- 🎮 [Demo](./demos/mp3-encoder.html)
|
|
124
126
|
|
|
125
127
|
```typescript
|
|
126
128
|
class MP3Encoder {
|
|
@@ -132,16 +134,38 @@ class MP3Encoder {
|
|
|
132
134
|
|
|
133
135
|
processBatch(audioData: AudioData): Uint8Array
|
|
134
136
|
finish(): Blob
|
|
137
|
+
getEncodedSize(): number
|
|
135
138
|
}
|
|
136
139
|
```
|
|
137
140
|
|
|
138
|
-
|
|
141
|
+
#### **MP3Decoder**
|
|
142
|
+
Decode MP3 files to raw PCM samples or AudioData objects.
|
|
143
|
+
|
|
144
|
+
- 📄 [Source](./src/audio/mp3.ts)
|
|
145
|
+
- 🎮 [Demo](./demos/mp3-decoder.html)
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
class MP3Decoder {
|
|
149
|
+
constructor()
|
|
150
|
+
|
|
151
|
+
async initialize(): Promise<void>
|
|
152
|
+
async toSamples(mp3Buffer: ArrayBuffer): Promise<{
|
|
153
|
+
channels: Float32Array[],
|
|
154
|
+
sampleRate: number,
|
|
155
|
+
numberOfChannels: number
|
|
156
|
+
}>
|
|
157
|
+
async toAudioData(mp3Buffer: ArrayBuffer): Promise<AudioData[]>
|
|
158
|
+
async destroy(): Promise<void>
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Demux/Mux
|
|
139
163
|
|
|
140
164
|
#### **MP4Demuxer**
|
|
141
165
|
Parse MP4 files and extract EncodedVideoChunk/EncodedAudioChunk objects using MP4Box.
|
|
142
166
|
|
|
143
167
|
- 📄 [Source](./src/demux/mp4-demuxer.ts)
|
|
144
|
-
- 🎮 [Demo](./demos/mp4-demuxer
|
|
168
|
+
- 🎮 [Demo](./demos/mp4-demuxer.html)
|
|
145
169
|
|
|
146
170
|
```typescript
|
|
147
171
|
class MP4Demuxer {
|
|
@@ -151,11 +175,14 @@ class MP4Demuxer {
|
|
|
151
175
|
getTracks(): TrackData
|
|
152
176
|
getVideoTrack(): VideoTrackData | undefined
|
|
153
177
|
getAudioTrack(): AudioTrackData | undefined
|
|
178
|
+
getVideoDecoderConfig(): VideoDecoderConfig | undefined
|
|
179
|
+
getAudioDecoderConfig(): AudioDecoderConfig | undefined
|
|
154
180
|
async extractSegment(
|
|
155
181
|
trackType: 'audio' | 'video',
|
|
156
182
|
startTime: number,
|
|
157
183
|
endTime: number
|
|
158
184
|
): Promise<EncodedVideoChunk[] | EncodedAudioChunk[]>
|
|
185
|
+
getInfo(): MP4Info
|
|
159
186
|
}
|
|
160
187
|
```
|
|
161
188
|
|
|
@@ -174,7 +201,7 @@ const videoChunks = await demuxer.extractSegment('video', 0, 10);
|
|
|
174
201
|
|
|
175
202
|
These utilities require:
|
|
176
203
|
- **WebCodecs API** - Chrome 94+, Edge 94+, Safari 17.4+ (some features)
|
|
177
|
-
- **WebGPU** (optional) - Chrome 113+, Edge 113+, Safari 18+ (for
|
|
204
|
+
- **WebGPU** (optional) - Chrome 113+, Edge 113+, Safari 18+ (for GPUFrameRenderer)
|
|
178
205
|
|
|
179
206
|
All utilities include compatibility checks and graceful degradation where applicable.
|
|
180
207
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Fn=require("lamejs"),zi=require("mp4box");function Jn(e,t,i="good"){const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Fn=require("lamejs"),zi=require("mp4box");function Jn(e,t,i=30,n="good"){const d=e*t,o={low:.05,good:.08,high:.1,"very-high":.15},u=o[n]||o.good;return d*i*u}function En(e,t,i,n){const d=[{maxMacroblocks:99,maxBitrate:64e3,level:10},{maxMacroblocks:396,maxBitrate:192e3,level:11},{maxMacroblocks:396,maxBitrate:384e3,level:12},{maxMacroblocks:396,maxBitrate:768e3,level:13},{maxMacroblocks:396,maxBitrate:2e6,level:20},{maxMacroblocks:792,maxBitrate:4e6,level:21},{maxMacroblocks:1620,maxBitrate:4e6,level:22},{maxMacroblocks:1620,maxBitrate:1e7,level:30},{maxMacroblocks:3600,maxBitrate:14e6,level:31},{maxMacroblocks:5120,maxBitrate:2e7,level:32},{maxMacroblocks:8192,maxBitrate:2e7,level:40},{maxMacroblocks:8192,maxBitrate:5e7,level:41},{maxMacroblocks:8704,maxBitrate:5e7,level:42},{maxMacroblocks:22080,maxBitrate:135e6,level:50},{maxMacroblocks:36864,maxBitrate:24e7,level:51},{maxMacroblocks:36864,maxBitrate:24e7,level:52},{maxMacroblocks:139264,maxBitrate:24e7,level:60},{maxMacroblocks:139264,maxBitrate:48e7,level:61},{maxMacroblocks:139264,maxBitrate:8e8,level:62}],o=[{maxPictureSize:36864,maxBitrate:128e3,tier:"L",level:30},{maxPictureSize:122880,maxBitrate:15e5,tier:"L",level:60},{maxPictureSize:245760,maxBitrate:3e6,tier:"L",level:63},{maxPictureSize:552960,maxBitrate:6e6,tier:"L",level:90},{maxPictureSize:983040,maxBitrate:1e7,tier:"L",level:93},{maxPictureSize:2228224,maxBitrate:12e6,tier:"L",level:120},{maxPictureSize:2228224,maxBitrate:3e7,tier:"H",level:120},{maxPictureSize:2228224,maxBitrate:2e7,tier:"L",level:123},{maxPictureSize:2228224,maxBitrate:5e7,tier:"H",level:123},{maxPictureSize:8912896,maxBitrate:25e6,tier:"L",level:150},{maxPictureSize:8912896,maxBitrate:1e8,tier:"H",level:150},{maxPictureSize:8912896,maxBitrate:4e7,tier:"L",level:153},{maxPictureSize:8912896,maxBitrate:16e7,tier:"H",level:153},{maxPictureSize:8912896,maxBitrate:6e7,tier:"L",level:156},{maxPictureSize:8912896,maxBitrate:24e7,tier:"H",level:156},{maxPictureSize:35651584,maxBitrate:6e7,tier:"L",level:180},{maxPictureSize:35651584,maxBitrate:24e7,tier:"H",level:180},{maxPictureSize:35651584,maxBitrate:12e7,tier:"L",level:183},{maxPictureSize:35651584,maxBitrate:48e7,tier:"H",level:183},{maxPictureSize:35651584,maxBitrate:24e7,tier:"L",level:186},{maxPictureSize:35651584,maxBitrate:8e8,tier:"H",level:186}],u=[{maxPictureSize:36864,maxBitrate:2e5,level:10},{maxPictureSize:73728,maxBitrate:8e5,level:11},{maxPictureSize:122880,maxBitrate:18e5,level:20},{maxPictureSize:245760,maxBitrate:36e5,level:21},{maxPictureSize:552960,maxBitrate:72e5,level:30},{maxPictureSize:983040,maxBitrate:12e6,level:31},{maxPictureSize:2228224,maxBitrate:18e6,level:40},{maxPictureSize:2228224,maxBitrate:3e7,level:41},{maxPictureSize:8912896,maxBitrate:6e7,level:50},{maxPictureSize:8912896,maxBitrate:12e7,level:51},{maxPictureSize:8912896,maxBitrate:18e7,level:52},{maxPictureSize:35651584,maxBitrate:18e7,level:60},{maxPictureSize:35651584,maxBitrate:24e7,level:61},{maxPictureSize:35651584,maxBitrate:48e7,level:62}],h=[{maxPictureSize:147456,maxBitrate:15e5,tier:"M",level:0},{maxPictureSize:278784,maxBitrate:3e6,tier:"M",level:1},{maxPictureSize:665856,maxBitrate:6e6,tier:"M",level:4},{maxPictureSize:1065024,maxBitrate:1e7,tier:"M",level:5},{maxPictureSize:2359296,maxBitrate:12e6,tier:"M",level:8},{maxPictureSize:2359296,maxBitrate:3e7,tier:"H",level:8},{maxPictureSize:2359296,maxBitrate:2e7,tier:"M",level:9},{maxPictureSize:2359296,maxBitrate:5e7,tier:"H",level:9},{maxPictureSize:8912896,maxBitrate:3e7,tier:"M",level:12},{maxPictureSize:8912896,maxBitrate:1e8,tier:"H",level:12},{maxPictureSize:8912896,maxBitrate:4e7,tier:"M",level:13},{maxPictureSize:8912896,maxBitrate:16e7,tier:"H",level:13},{maxPictureSize:8912896,maxBitrate:6e7,tier:"M",level:14},{maxPictureSize:8912896,maxBitrate:24e7,tier:"H",level:14},{maxPictureSize:35651584,maxBitrate:6e7,tier:"M",level:15},{maxPictureSize:35651584,maxBitrate:24e7,tier:"H",level:15},{maxPictureSize:35651584,maxBitrate:6e7,tier:"M",level:16},{maxPictureSize:35651584,maxBitrate:24e7,tier:"H",level:16},{maxPictureSize:35651584,maxBitrate:1e8,tier:"M",level:17},{maxPictureSize:35651584,maxBitrate:48e7,tier:"H",level:17},{maxPictureSize:35651584,maxBitrate:16e7,tier:"M",level:18},{maxPictureSize:35651584,maxBitrate:8e8,tier:"H",level:18},{maxPictureSize:35651584,maxBitrate:16e7,tier:"M",level:19},{maxPictureSize:35651584,maxBitrate:8e8,tier:"H",level:19}];function S(W){return W?W[W.length-1]:void 0}if(e==="avc"){const I=Math.ceil(t/16)*Math.ceil(i/16),Z=d.find(P=>I<=P.maxMacroblocks&&n<=P.maxBitrate)??S(d),j=Z?Z.level:0,w="64".padStart(2,"0"),A="00",J=j.toString(16).padStart(2,"0");return`avc1.${w}${A}${J}`}else if(e==="hevc"){const W="",Z="6",j=t*i,w=o.find(J=>j<=J.maxPictureSize&&n<=J.maxBitrate)??S(o);return`hev1.${W}1.${Z}.${w.tier}${w.level}.B0`}else{if(e==="vp8")return"vp8";if(e==="vp9"){const W="00",I=t*i,Z=u.find(w=>I<=w.maxPictureSize&&n<=w.maxBitrate)??S(u);return`vp09.${W}.${Z.level.toString().padStart(2,"0")}.08`}else if(e==="av1"){const I=t*i,Z=h.find(A=>I<=A.maxPictureSize&&n<=A.maxBitrate)??S(h);return`av01.0.${Z.level.toString().padStart(2,"0")}${Z.tier}.08`}}throw new TypeError(`Unhandled codec '${e}'.`)}class Nn{constructor(t,i={}){this.canvas=t,this.mode="webgpu",this.filterMode=i.filterMode||"linear",this.device=null,this.context=null,this.linearPipeline=null,this.bicubicPipeline=null,this.sampler=null,this.uniformBuffer=null,this.bitmapCtx=null}async init(){if(navigator.gpu)try{await this.initWebGPU(),this.mode="webgpu",console.log("GPUDrawImage: Using WebGPU (zero-copy)");return}catch(t){console.warn("GPUDrawImage: WebGPU initialization failed, falling back to ImageBitmap",t)}this.initBitmapRenderer(),this.mode="bitmap",console.log("GPUDrawImage: Using ImageBitmapRenderer (fallback)")}async initWebGPU(){const t=await navigator.gpu.requestAdapter();if(!t||(this.device=await t.requestDevice(),!this.device)||(this.context=this.canvas.getContext("webgpu"),!this.context))return!1;const i=navigator.gpu.getPreferredCanvasFormat();this.context.configure({device:this.device,format:i,alphaMode:"opaque"}),this.sampler=this.device.createSampler({magFilter:"linear",minFilter:"linear"}),this.uniformBuffer=this.device.createBuffer({size:8,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST});const n=`
|
|
2
2
|
struct VertexOutput {
|
|
3
3
|
@builtin(position) position: vec4f,
|
|
4
4
|
@location(0) texCoord: vec2f,
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import gn from "lamejs";
|
|
2
2
|
import Fn, { DataStream as bs } from "mp4box";
|
|
3
|
-
function G1(e, t, i = "good") {
|
|
4
|
-
const
|
|
3
|
+
function G1(e, t, i = 30, n = "good") {
|
|
4
|
+
const d = e * t, o = {
|
|
5
5
|
low: 0.05,
|
|
6
6
|
good: 0.08,
|
|
7
7
|
high: 0.1,
|
|
8
8
|
"very-high": 0.15
|
|
9
|
-
},
|
|
10
|
-
return
|
|
9
|
+
}, u = o[n] || o.good;
|
|
10
|
+
return d * i * u;
|
|
11
11
|
}
|
|
12
12
|
function S1(e, t, i, n) {
|
|
13
13
|
const d = [
|