@mediabunny/server 1.48.1 → 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/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  By default, Mediabunny requires a browser environment for full access to decoders, encoders, and video processing features. `@mediabunny/server` uses [NodeAV](https://github.com/seydx/node-av) to polyfill this functionality for server-side environments such as Node, Bun, or Deno, enabling the usage of all Mediabunny features on the server. The result is a server-side media processing API that integrates naturally with TypeScript as opposed to the awkwardness and inefficiencies of calling out to the FFmpeg CLI.
13
13
 
14
14
  Features added by this package include:
15
- - Video decoders and encoders for AVC (H.264), HEVC (H.265), VP8, VP9, and AV1. Supports both length-prefixed and Annex B AVC/HEVC as well as transparent video via VP9.
15
+ - Video decoders and encoders for AVC (H.264), HEVC (H.265), VP8, VP9, AV1, and ProRes. Supports both length-prefixed and Annex B AVC/HEVC as well as transparent video via VP9 and ProRes.
16
16
  - Audio decoders and encoders for AAC, MP3, Vorbis, Opus, FLAC, AC-3 and E-AC-3. Supports AAC in both AAC and ADTS formats.
17
17
  - Video frame transformation support (resize, rotate, crop)
18
18
  - Automatic hardware acceleration on all platforms (macOS, Linux, Windows)
@@ -279,6 +279,40 @@ return new VideoSample(pixels, {
279
279
  });
280
280
  ```
281
281
 
282
+ ## Controlling logging
283
+
284
+ Especially in command-line applications you usually don't want Mediabunny interfering with your stdout and stderr output. Mediabunny provides ways to control its console output:
285
+ ```ts
286
+ import { Logging, LogLevel } from 'mediabunny';
287
+
288
+ // The default: Mediabunny can log errors, warnings, and information messages.
289
+ Logging.level = LogLevel.Info;
290
+
291
+ // Only log warnings and errors.
292
+ Logging.level = LogLevel.Warnings;
293
+
294
+ // Only log errors.
295
+ Logging.level = LogLevel.Errors;
296
+
297
+ // Don't log anything at all.
298
+ Logging.level = LogLevel.None;
299
+ ```
300
+
301
+ You can also hook into log events:
302
+ ```ts
303
+ Logging.on('error', (args: unknown[]) => {
304
+ // Handle error message
305
+ });
306
+
307
+ Logging.on('warn', (args: unknown[]) => {
308
+ // Handle warning message
309
+ });
310
+
311
+ Logging.on('info', (args: unknown[]) => {
312
+ // Handle info message
313
+ });
314
+ ```
315
+
282
316
  ## Implementation details
283
317
 
284
318
  `@mediabunny/server` uses [NodeAV](https://github.com/seydx/node-av) under the hood which provides N-API C bindings to FFmpeg's C API. Using NodeAV, this package implements [custom decoders and encoders](https://mediabunny.dev/guide/supported-formats-and-codecs#custom-coders) by directly using the APIs provided by `libavcodec`.
@@ -287,6 +321,8 @@ For encoding, video frames and audio samples are transferred to FFmpeg by conver
287
321
 
288
322
  Whenever possible, `AVFrame`s are never copied over to JavaScript unless explicitly needed. This enables zero-copy decode -> transformation -> encode paths.
289
323
 
324
+ ProRes decoding support is provided by [TurboRes](https://github.com/Vanilagy/turbores), as it is often faster than FFmpeg.
325
+
290
326
  ## License
291
327
 
292
328
  `@mediabunny/server` uses the same MPL-2.0 license as Mediabunny.
@@ -65,6 +65,7 @@ var MediabunnyServer = (() => {
65
65
  vp8: NodeAv.AV_CODEC_ID_VP8,
66
66
  vp9: NodeAv.AV_CODEC_ID_VP9,
67
67
  av1: NodeAv.AV_CODEC_ID_AV1,
68
+ prores: NodeAv.AV_CODEC_ID_PRORES,
68
69
  aac: NodeAv.AV_CODEC_ID_AAC,
69
70
  opus: NodeAv.AV_CODEC_ID_OPUS,
70
71
  mp3: NodeAv.AV_CODEC_ID_MP3,
@@ -380,6 +381,56 @@ var MediabunnyServer = (() => {
380
381
  }
381
382
  };
382
383
 
384
+ // src/logging.ts
385
+ var _Logging = class _Logging {
386
+ constructor() {
387
+ }
388
+ /** The current log level. Defaults to {@link LogLevel.Info}. */
389
+ static get level() {
390
+ return _Logging._level;
391
+ }
392
+ static set level(value) {
393
+ if (value !== 0 /* Silent */ && value !== 1 /* Errors */ && value !== 2 /* Warnings */ && value !== 3 /* Info */) {
394
+ throw new TypeError("Invalid log level. Use one of the values of the LogLevel enum.");
395
+ }
396
+ _Logging._level = value;
397
+ }
398
+ /** @internal */
399
+ static get _emitter() {
400
+ return _Logging._emitterInstance ??= new EventEmitter();
401
+ }
402
+ /** Registers a listener for a log event. Returns a function that, when called, removes the listener again. */
403
+ static on(event, listener, options) {
404
+ return _Logging._emitter.on(event, listener, options);
405
+ }
406
+ /** @internal */
407
+ static _error(...args) {
408
+ _Logging._emitter._emit("error", args);
409
+ if (_Logging._level >= 1 /* Errors */) {
410
+ console.error(...args);
411
+ }
412
+ }
413
+ /** @internal */
414
+ static _warn(...args) {
415
+ _Logging._emitter._emit("warn", args);
416
+ if (_Logging._level >= 2 /* Warnings */) {
417
+ console.warn(...args);
418
+ }
419
+ }
420
+ /** @internal */
421
+ static _info(...args) {
422
+ _Logging._emitter._emit("info", args);
423
+ if (_Logging._level >= 3 /* Info */) {
424
+ console.info(...args);
425
+ }
426
+ }
427
+ };
428
+ /** @internal */
429
+ _Logging._level = 3 /* Info */;
430
+ /** @internal */
431
+ _Logging._emitterInstance = null;
432
+ var Logging = _Logging;
433
+
383
434
  // src/misc.ts
384
435
  function assert(x) {
385
436
  if (!x) {
@@ -487,6 +538,9 @@ var MediabunnyServer = (() => {
487
538
  }
488
539
  return ans;
489
540
  };
541
+ var assertNever = (x) => {
542
+ throw new Error(`Unexpected value: ${x}`);
543
+ };
490
544
  var SECOND_TO_MICROSECOND_FACTOR = 1e6 * (1 + Number.EPSILON);
491
545
  var simplifyRational = (rational) => {
492
546
  assert(Number.isInteger(rational.num));
@@ -505,6 +559,41 @@ var MediabunnyServer = (() => {
505
559
  den: rational.den / gcd
506
560
  };
507
561
  };
562
+ var EventEmitter = class {
563
+ constructor() {
564
+ /** @internal */
565
+ this._listeners = /* @__PURE__ */ new Map();
566
+ }
567
+ /** Registers a listener for the given event. Returns a function that, when called, removes the listener again. */
568
+ on(event, listener, options) {
569
+ if (!this._listeners.has(event)) {
570
+ this._listeners.set(event, /* @__PURE__ */ new Set());
571
+ }
572
+ const entry = { fn: listener, once: options?.once ?? false };
573
+ this._listeners.get(event).add(entry);
574
+ return () => {
575
+ this._listeners.get(event)?.delete(entry);
576
+ };
577
+ }
578
+ /** @internal */
579
+ _emit(...args) {
580
+ const [event, data] = args;
581
+ const listeners = this._listeners.get(event);
582
+ if (!listeners) {
583
+ return;
584
+ }
585
+ for (const entry of listeners) {
586
+ try {
587
+ entry.fn(data);
588
+ } catch (error) {
589
+ console.error(error);
590
+ }
591
+ if (entry.once) {
592
+ listeners.delete(entry);
593
+ }
594
+ }
595
+ }
596
+ };
508
597
 
509
598
  // packages/server/src/video-sample.ts
510
599
  var import_mediabunny = __require("mediabunny");
@@ -1101,8 +1190,31 @@ var MediabunnyServer = (() => {
1101
1190
  ];
1102
1191
  var VP9_DEFAULT_SUFFIX = ".01.01.01.01.00";
1103
1192
  var AV1_DEFAULT_SUFFIX = ".0.110.01.01.01.0";
1193
+ var PRORES_FOURCCS = [
1194
+ "ap4x",
1195
+ // ProRes 4444 XQ
1196
+ "ap4h",
1197
+ // ProRes 4444
1198
+ "apch",
1199
+ // ProRes 422 High Quality
1200
+ "apcn",
1201
+ // ProRes 422 Standard Definition
1202
+ "apcs",
1203
+ // ProRes 422 LT
1204
+ "apco"
1205
+ // ProRes 422 Proxy
1206
+ ];
1104
1207
  var extractVideoCodecString = (trackInfo) => {
1105
- const { codec, codecDescription, colorSpace, avcCodecInfo, hevcCodecInfo, vp9CodecInfo, av1CodecInfo } = trackInfo;
1208
+ const {
1209
+ codec,
1210
+ codecDescription,
1211
+ colorSpace,
1212
+ avcCodecInfo,
1213
+ hevcCodecInfo,
1214
+ vp9CodecInfo,
1215
+ av1CodecInfo,
1216
+ proresFormat
1217
+ } = trackInfo;
1106
1218
  if (codec === "avc") {
1107
1219
  assert(trackInfo.avcType !== null);
1108
1220
  if (avcCodecInfo) {
@@ -1222,9 +1334,14 @@ var MediabunnyServer = (() => {
1222
1334
  string = string.slice(0, -AV1_DEFAULT_SUFFIX.length);
1223
1335
  }
1224
1336
  return string;
1337
+ } else if (codec === "prores") {
1338
+ return proresFormat ?? "apch";
1339
+ } else if (codec !== null) {
1340
+ assertNever(codec);
1225
1341
  }
1226
1342
  throw new TypeError(`Unhandled codec '${codec}'.`);
1227
1343
  };
1344
+ var VALID_VIDEO_CODEC_STRING_PREFIXES = ["avc1", "avc3", "hev1", "hvc1", "vp8", "vp09", "av01", ...PRORES_FOURCCS];
1228
1345
 
1229
1346
  // src/codec-data.ts
1230
1347
  var iterateNalUnitsInAnnexB = function* (packetData) {
@@ -1320,7 +1437,7 @@ var MediabunnyServer = (() => {
1320
1437
  sequenceParameterSetExt: hasExtendedData ? spsExtUnits : null
1321
1438
  };
1322
1439
  } catch (error) {
1323
- console.error("Error building AVC Decoder Configuration Record:", error);
1440
+ Logging._error("Error building AVC Decoder Configuration Record:", error);
1324
1441
  return null;
1325
1442
  }
1326
1443
  };
@@ -1594,7 +1711,7 @@ var MediabunnyServer = (() => {
1594
1711
  maxDecFrameBuffering
1595
1712
  };
1596
1713
  } catch (error) {
1597
- console.error("Error parsing AVC SPS:", error);
1714
+ Logging._error("Error parsing AVC SPS:", error);
1598
1715
  return null;
1599
1716
  }
1600
1717
  };
@@ -1738,7 +1855,7 @@ var MediabunnyServer = (() => {
1738
1855
  minSpatialSegmentationIdc
1739
1856
  };
1740
1857
  } catch (error) {
1741
- console.error("Error parsing HEVC SPS:", error);
1858
+ Logging._error("Error parsing HEVC SPS:", error);
1742
1859
  return null;
1743
1860
  }
1744
1861
  };
@@ -1849,7 +1966,7 @@ var MediabunnyServer = (() => {
1849
1966
  };
1850
1967
  return record;
1851
1968
  } catch (error) {
1852
- console.error("Error building HEVC Decoder Configuration Record:", error);
1969
+ Logging._error("Error building HEVC Decoder Configuration Record:", error);
1853
1970
  return null;
1854
1971
  }
1855
1972
  };
@@ -2523,6 +2640,14 @@ var MediabunnyServer = (() => {
2523
2640
  var EAC3_REGISTRATION_DESCRIPTOR = new Uint8Array([5, 4, 69, 65, 67, 51]);
2524
2641
 
2525
2642
  // packages/server/src/video-encoder.ts
2643
+ var PRORES_FOURCC_TO_PROFILE = {
2644
+ apco: NodeAv4.AV_PROFILE_PRORES_PROXY,
2645
+ apcs: NodeAv4.AV_PROFILE_PRORES_LT,
2646
+ apcn: NodeAv4.AV_PROFILE_PRORES_STANDARD,
2647
+ apch: NodeAv4.AV_PROFILE_PRORES_HQ,
2648
+ ap4h: NodeAv4.AV_PROFILE_PRORES_4444,
2649
+ ap4x: NodeAv4.AV_PROFILE_PRORES_XQ
2650
+ };
2526
2651
  var NodeAvVideoEncoder = class extends import_mediabunny3.CustomVideoEncoder {
2527
2652
  constructor() {
2528
2653
  super(...arguments);
@@ -2536,7 +2661,7 @@ var MediabunnyServer = (() => {
2536
2661
  this.preciseTimings = [];
2537
2662
  }
2538
2663
  static supports(codec, config) {
2539
- return (codec === "avc" || codec === "hevc" || codec === "vp8" || codec === "vp9" || codec === "av1") && config.bitrateMode !== "quantizer";
2664
+ return (codec === "avc" || codec === "hevc" || codec === "vp8" || codec === "vp9" || codec === "av1" || codec === "prores") && config.bitrateMode !== "quantizer";
2540
2665
  }
2541
2666
  async init() {
2542
2667
  this.frame = new NodeAv4.Frame();
@@ -2546,13 +2671,22 @@ var MediabunnyServer = (() => {
2546
2671
  this.packet.alloc();
2547
2672
  const codecId = CODEC_TO_CODEC_ID[this.codec];
2548
2673
  assert(codecId !== void 0);
2674
+ const getSoftwareCodec = () => {
2675
+ if (this.codec === "prores") {
2676
+ const proresKs = NodeAv4.Codec.findEncoderByName(NodeAv4.FF_ENCODER_PRORES_KS);
2677
+ if (proresKs) {
2678
+ return proresKs;
2679
+ }
2680
+ }
2681
+ return NodeAv4.Codec.findEncoder(codecId);
2682
+ };
2549
2683
  let codec = null;
2550
2684
  if (this.codec === "vp9" && this.config.alpha === "keep") {
2551
2685
  codec = NodeAv4.Codec.findEncoderByName(NodeAv4.FF_ENCODER_LIBVPX_VP9) ?? NodeAv4.Codec.findEncoder(codecId);
2552
2686
  } else if (this.config.hardwareAcceleration === "prefer-software") {
2553
- codec = NodeAv4.Codec.findEncoder(codecId);
2687
+ codec = getSoftwareCodec();
2554
2688
  } else {
2555
- codec = await getHardwareEncoderCodec(codecId) ?? NodeAv4.Codec.findEncoder(codecId);
2689
+ codec = await getHardwareEncoderCodec(codecId) ?? getSoftwareCodec();
2556
2690
  }
2557
2691
  if (!codec) {
2558
2692
  throw new Error(`Unable to obtain libav codec for '${this.codec}'.`);
@@ -2569,8 +2703,15 @@ var MediabunnyServer = (() => {
2569
2703
  if (!this.avCodec.pixelFormats.includes(NodeAv4.AV_PIX_FMT_YUV420P)) {
2570
2704
  pixelFormat = this.avCodec.pixelFormats[0];
2571
2705
  }
2572
- if (this.config.alpha === "keep" && this.avCodec.pixelFormats.includes(NodeAv4.AV_PIX_FMT_YUVA420P)) {
2573
- pixelFormat = NodeAv4.AV_PIX_FMT_YUVA420P;
2706
+ if (this.config.alpha === "keep") {
2707
+ if (this.avCodec.pixelFormats.includes(NodeAv4.AV_PIX_FMT_YUVA420P)) {
2708
+ pixelFormat = NodeAv4.AV_PIX_FMT_YUVA420P;
2709
+ } else {
2710
+ pixelFormat = NodeAv4.avcodecFindBestPixFmtOfList(
2711
+ this.avCodec.pixelFormats,
2712
+ NodeAv4.AV_PIX_FMT_YUVA444P12LE
2713
+ );
2714
+ }
2574
2715
  }
2575
2716
  }
2576
2717
  const pixelAspectRatio = simplifyRational({
@@ -2623,6 +2764,11 @@ var MediabunnyServer = (() => {
2623
2764
  codecContext.setOption("preset", "12");
2624
2765
  }
2625
2766
  }
2767
+ if (this.codec === "prores") {
2768
+ const profile = PRORES_FOURCC_TO_PROFILE[this.config.codec];
2769
+ assert(profile !== void 0);
2770
+ codecContext.setOption("profile", String(profile));
2771
+ }
2626
2772
  const ret = await codecContext.open2();
2627
2773
  NodeAv4.FFmpegError.throwIfError(ret, "Open codec context");
2628
2774
  this.codecContext = codecContext;
@@ -2773,7 +2919,8 @@ var MediabunnyServer = (() => {
2773
2919
  avcCodecInfo: null,
2774
2920
  hevcCodecInfo: null,
2775
2921
  vp9CodecInfo: null,
2776
- av1CodecInfo: null
2922
+ av1CodecInfo: null,
2923
+ proresFormat: null
2777
2924
  });
2778
2925
  if (!expectsAnnexB) {
2779
2926
  decoderConfigDescription = serializedRecord;
@@ -2826,7 +2973,8 @@ var MediabunnyServer = (() => {
2826
2973
  avcCodecInfo: null,
2827
2974
  hevcCodecInfo: null,
2828
2975
  vp9CodecInfo: null,
2829
- av1CodecInfo: null
2976
+ av1CodecInfo: null,
2977
+ proresFormat: null
2830
2978
  });
2831
2979
  }
2832
2980
  } else if (this.codec === "vp9") {
@@ -2842,7 +2990,8 @@ var MediabunnyServer = (() => {
2842
2990
  avcCodecInfo: null,
2843
2991
  hevcCodecInfo: null,
2844
2992
  vp9CodecInfo,
2845
- av1CodecInfo: null
2993
+ av1CodecInfo: null,
2994
+ proresFormat: null
2846
2995
  });
2847
2996
  }
2848
2997
  } else if (this.codec === "av1") {
@@ -2858,7 +3007,24 @@ var MediabunnyServer = (() => {
2858
3007
  avcCodecInfo: null,
2859
3008
  hevcCodecInfo: null,
2860
3009
  vp9CodecInfo: null,
2861
- av1CodecInfo
3010
+ av1CodecInfo,
3011
+ proresFormat: null
3012
+ });
3013
+ }
3014
+ } else if (this.codec === "prores") {
3015
+ if (!this.packetEmitted) {
3016
+ decoderConfigCodecString = extractVideoCodecString({
3017
+ width: this.config.width,
3018
+ height: this.config.height,
3019
+ codec: "prores",
3020
+ codecDescription: null,
3021
+ colorSpace: null,
3022
+ avcType: null,
3023
+ avcCodecInfo: null,
3024
+ hevcCodecInfo: null,
3025
+ vp9CodecInfo: null,
3026
+ av1CodecInfo: null,
3027
+ proresFormat: this.config.codec
2862
3028
  });
2863
3029
  }
2864
3030
  } else {
@@ -3348,9 +3514,10 @@ var MediabunnyServer = (() => {
3348
3514
  };
3349
3515
 
3350
3516
  // packages/server/src/index.ts
3517
+ var import_prores = __require("@mediabunny/prores");
3351
3518
  var SERVER_LOADED_SYMBOL = Symbol.for("@mediabunny/server loaded");
3352
3519
  if (globalThis[SERVER_LOADED_SYMBOL]) {
3353
- console.error(
3520
+ import_mediabunny7.Logging._error(
3354
3521
  "[WARNING]\n@mediabunny/server was loaded twice. This will likely cause the package not to work correctly. Check if multiple dependencies are importing different versions of @mediabunny/server, or if something is being bundled incorrectly."
3355
3522
  );
3356
3523
  }
@@ -3373,6 +3540,7 @@ var MediabunnyServer = (() => {
3373
3540
  _serverOptions = options;
3374
3541
  NodeAv8.Log.setLevel(NodeAv8.AV_LOG_ERROR);
3375
3542
  (0, import_mediabunny7.registerDecoder)(NodeAvVideoDecoder);
3543
+ (0, import_prores.registerProresDecoder)();
3376
3544
  (0, import_mediabunny7.registerEncoder)(NodeAvVideoEncoder);
3377
3545
  (0, import_mediabunny7.registerDecoder)(NodeAvAudioDecoder);
3378
3546
  (0, import_mediabunny7.registerEncoder)(NodeAvAudioEncoder);