@mediabunny/server 1.49.0 → 1.50.0

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mediabunny/server",
3
3
  "author": "Vanilagy",
4
- "version": "1.49.0",
4
+ "version": "1.50.0",
5
5
  "description": "Adds full video and audio decoder and encoder support to Mediabunny for use in server-side environments (Node, Bun, Deno). Based on NodeAV.",
6
6
  "main": "./dist/bundles/mediabunny-server.cjs",
7
7
  "module": "./dist/bundles/mediabunny-server.mjs",
@@ -34,7 +34,8 @@
34
34
  "url": "https://github.com/sponsors/Vanilagy"
35
35
  },
36
36
  "dependencies": {
37
- "node-av": "^6.0.0"
37
+ "node-av": "^6.0.0",
38
+ "@mediabunny/prores": "^1.50.0"
38
39
  },
39
40
  "peerDependencies": {
40
41
  "mediabunny": "^1.45.0"
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ import { NodeAvAudioDecoder } from './audio-decoder';
22
22
  import { NodeAvAudioEncoder } from './audio-encoder';
23
23
  import { copyVideoSampleToAvFrame, AvFrameVideoSampleResource, transformVideoSample } from './video-sample';
24
24
  import { copyAudioSampleToAvFrame, AvFrameAudioSampleResource } from './audio-sample';
25
+ import { registerProresDecoder } from '@mediabunny/prores';
25
26
 
26
27
  const SERVER_LOADED_SYMBOL = Symbol.for('@mediabunny/server loaded');
27
28
  if ((globalThis as Record<symbol, unknown>)[SERVER_LOADED_SYMBOL]) {
@@ -100,6 +101,7 @@ export const registerMediabunnyServer = (options: MediabunnyServerOptions = {})
100
101
 
101
102
  // Video
102
103
  registerDecoder(NodeAvVideoDecoder);
104
+ registerProresDecoder(); // Faster than FFmpeg, so we use it
103
105
  registerEncoder(NodeAvVideoEncoder);
104
106
 
105
107
  // Audio
package/src/misc.ts CHANGED
@@ -16,6 +16,7 @@ export const CODEC_TO_CODEC_ID: Partial<Record<MediaCodec, NodeAv.AVCodecID>> =
16
16
  vp8: NodeAv.AV_CODEC_ID_VP8,
17
17
  vp9: NodeAv.AV_CODEC_ID_VP9,
18
18
  av1: NodeAv.AV_CODEC_ID_AV1,
19
+ prores: NodeAv.AV_CODEC_ID_PRORES,
19
20
 
20
21
  aac: NodeAv.AV_CODEC_ID_AAC,
21
22
  opus: NodeAv.AV_CODEC_ID_OPUS,
@@ -38,9 +38,18 @@ import {
38
38
  serializeAvcDecoderConfigurationRecord,
39
39
  serializeHevcDecoderConfigurationRecord,
40
40
  } from '../../../src/codec-data';
41
- import { extractVideoCodecString } from '../../../src/codec';
41
+ import { extractVideoCodecString, ProresFourCc } from '../../../src/codec';
42
42
  import { assert, binarySearchLessOrEqual, simplifyRational, toUint8Array } from '../../../src/misc';
43
43
 
44
+ const PRORES_FOURCC_TO_PROFILE: Record<ProresFourCc, NodeAv.AVProfile> = {
45
+ apco: NodeAv.AV_PROFILE_PRORES_PROXY,
46
+ apcs: NodeAv.AV_PROFILE_PRORES_LT,
47
+ apcn: NodeAv.AV_PROFILE_PRORES_STANDARD,
48
+ apch: NodeAv.AV_PROFILE_PRORES_HQ,
49
+ ap4h: NodeAv.AV_PROFILE_PRORES_4444,
50
+ ap4x: NodeAv.AV_PROFILE_PRORES_XQ,
51
+ };
52
+
44
53
  export class NodeAvVideoEncoder extends CustomVideoEncoder {
45
54
  frame!: NodeAv.Frame;
46
55
  packet!: NodeAv.Packet;
@@ -62,8 +71,10 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
62
71
  }[] = [];
63
72
 
64
73
  static override supports(codec: VideoCodec, config: VideoEncoderConfig): boolean {
65
- return (codec === 'avc' || codec === 'hevc' || codec === 'vp8' || codec === 'vp9' || codec === 'av1')
66
- && config.bitrateMode !== 'quantizer';
74
+ return (
75
+ codec === 'avc' || codec === 'hevc' || codec === 'vp8' || codec === 'vp9' || codec === 'av1'
76
+ || codec === 'prores'
77
+ ) && config.bitrateMode !== 'quantizer';
67
78
  }
68
79
 
69
80
  async init(): Promise<void> {
@@ -77,13 +88,25 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
77
88
  const codecId = CODEC_TO_CODEC_ID[this.codec];
78
89
  assert(codecId !== undefined);
79
90
 
91
+ const getSoftwareCodec = () => {
92
+ if (this.codec === 'prores') {
93
+ // Prefer prores_ks for ProRes
94
+ const proresKs = NodeAv.Codec.findEncoderByName(NodeAv.FF_ENCODER_PRORES_KS);
95
+ if (proresKs) {
96
+ return proresKs;
97
+ }
98
+ }
99
+
100
+ return NodeAv.Codec.findEncoder(codecId);
101
+ };
102
+
80
103
  let codec: NodeAv.Codec | null = null;
81
104
  if (this.codec === 'vp9' && this.config.alpha === 'keep') {
82
105
  codec = NodeAv.Codec.findEncoderByName(NodeAv.FF_ENCODER_LIBVPX_VP9) ?? NodeAv.Codec.findEncoder(codecId);
83
106
  } else if (this.config.hardwareAcceleration === 'prefer-software') {
84
- codec = NodeAv.Codec.findEncoder(codecId);
107
+ codec = getSoftwareCodec();
85
108
  } else {
86
- codec = (await getHardwareEncoderCodec(codecId)) ?? NodeAv.Codec.findEncoder(codecId);
109
+ codec = (await getHardwareEncoderCodec(codecId)) ?? getSoftwareCodec();
87
110
  }
88
111
 
89
112
  if (!codec) {
@@ -108,8 +131,17 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
108
131
  pixelFormat = this.avCodec.pixelFormats[0]!;
109
132
  }
110
133
 
111
- if (this.config.alpha === 'keep' && this.avCodec.pixelFormats.includes(NodeAv.AV_PIX_FMT_YUVA420P)) {
112
- pixelFormat = NodeAv.AV_PIX_FMT_YUVA420P;
134
+ if (this.config.alpha === 'keep') {
135
+ if (this.avCodec.pixelFormats.includes(NodeAv.AV_PIX_FMT_YUVA420P)) {
136
+ pixelFormat = NodeAv.AV_PIX_FMT_YUVA420P;
137
+ } else {
138
+ // Let FFmpeg pick the best alpha-capable format it supports, minimizing data loss versus a
139
+ // high-quality YUVA source
140
+ pixelFormat = NodeAv.avcodecFindBestPixFmtOfList(
141
+ this.avCodec.pixelFormats,
142
+ NodeAv.AV_PIX_FMT_YUVA444P12LE,
143
+ );
144
+ }
113
145
  }
114
146
  }
115
147
 
@@ -173,6 +205,14 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
173
205
  }
174
206
  }
175
207
 
208
+ if (this.codec === 'prores') {
209
+ // Pick the encoder profile from the requested ProRes four-character code
210
+ const profile = PRORES_FOURCC_TO_PROFILE[this.config.codec as ProresFourCc];
211
+ assert(profile !== undefined);
212
+
213
+ codecContext.setOption('profile', String(profile));
214
+ }
215
+
176
216
  const ret = await codecContext.open2();
177
217
  NodeAv.FFmpegError.throwIfError(ret, 'Open codec context');
178
218
 
@@ -375,6 +415,7 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
375
415
  hevcCodecInfo: null,
376
416
  vp9CodecInfo: null,
377
417
  av1CodecInfo: null,
418
+ proresFormat: null,
378
419
  });
379
420
 
380
421
  if (!expectsAnnexB) {
@@ -450,6 +491,7 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
450
491
  hevcCodecInfo: null,
451
492
  vp9CodecInfo: null,
452
493
  av1CodecInfo: null,
494
+ proresFormat: null,
453
495
  });
454
496
  }
455
497
  } else if (this.codec === 'vp9') {
@@ -467,6 +509,7 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
467
509
  hevcCodecInfo: null,
468
510
  vp9CodecInfo,
469
511
  av1CodecInfo: null,
512
+ proresFormat: null,
470
513
  });
471
514
  }
472
515
  } else if (this.codec === 'av1') {
@@ -484,6 +527,23 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
484
527
  hevcCodecInfo: null,
485
528
  vp9CodecInfo: null,
486
529
  av1CodecInfo,
530
+ proresFormat: null,
531
+ });
532
+ }
533
+ } else if (this.codec === 'prores') {
534
+ if (!this.packetEmitted) {
535
+ decoderConfigCodecString = extractVideoCodecString({
536
+ width: this.config.width,
537
+ height: this.config.height,
538
+ codec: 'prores',
539
+ codecDescription: null,
540
+ colorSpace: null,
541
+ avcType: null,
542
+ avcCodecInfo: null,
543
+ hevcCodecInfo: null,
544
+ vp9CodecInfo: null,
545
+ av1CodecInfo: null,
546
+ proresFormat: this.config.codec as ProresFourCc,
487
547
  });
488
548
  }
489
549
  } else {