@adonis-agora/media 0.8.1 → 0.9.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.
Files changed (117) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/README.md +206 -0
  3. package/dist/providers/media_provider.d.ts +3 -13
  4. package/dist/providers/media_provider.d.ts.map +1 -1
  5. package/dist/providers/media_provider.js +151 -4
  6. package/dist/providers/media_provider.js.map +1 -1
  7. package/dist/src/content_type.d.ts +6 -1
  8. package/dist/src/content_type.d.ts.map +1 -1
  9. package/dist/src/content_type.js +102 -7
  10. package/dist/src/content_type.js.map +1 -1
  11. package/dist/src/define_config.d.ts +88 -5
  12. package/dist/src/define_config.d.ts.map +1 -1
  13. package/dist/src/define_config.js +7 -2
  14. package/dist/src/define_config.js.map +1 -1
  15. package/dist/src/direct_upload.d.ts +177 -0
  16. package/dist/src/direct_upload.d.ts.map +1 -0
  17. package/dist/src/direct_upload.js +299 -0
  18. package/dist/src/direct_upload.js.map +1 -0
  19. package/dist/src/direct_upload_handler.d.ts +83 -0
  20. package/dist/src/direct_upload_handler.d.ts.map +1 -0
  21. package/dist/src/direct_upload_handler.js +180 -0
  22. package/dist/src/direct_upload_handler.js.map +1 -0
  23. package/dist/src/disks/factory.d.ts +11 -3
  24. package/dist/src/disks/factory.d.ts.map +1 -1
  25. package/dist/src/disks/factory.js +5 -3
  26. package/dist/src/disks/factory.js.map +1 -1
  27. package/dist/src/disks/s3.d.ts +10 -0
  28. package/dist/src/disks/s3.d.ts.map +1 -1
  29. package/dist/src/disks/s3.js +96 -22
  30. package/dist/src/disks/s3.js.map +1 -1
  31. package/dist/src/disks/sigv4.d.ts +58 -0
  32. package/dist/src/disks/sigv4.d.ts.map +1 -0
  33. package/dist/src/disks/sigv4.js +87 -0
  34. package/dist/src/disks/sigv4.js.map +1 -0
  35. package/dist/src/disks/xml_fallback.d.ts +3 -3
  36. package/dist/src/disks/xml_fallback.js +3 -3
  37. package/dist/src/errors.d.ts +104 -0
  38. package/dist/src/errors.d.ts.map +1 -1
  39. package/dist/src/errors.js +145 -0
  40. package/dist/src/errors.js.map +1 -1
  41. package/dist/src/hls/delivery.d.ts +112 -0
  42. package/dist/src/hls/delivery.d.ts.map +1 -0
  43. package/dist/src/hls/delivery.js +118 -0
  44. package/dist/src/hls/delivery.js.map +1 -0
  45. package/dist/src/hls/playlist.d.ts +67 -0
  46. package/dist/src/hls/playlist.d.ts.map +1 -0
  47. package/dist/src/hls/playlist.js +126 -0
  48. package/dist/src/hls/playlist.js.map +1 -0
  49. package/dist/src/index.d.ts +18 -3
  50. package/dist/src/index.d.ts.map +1 -1
  51. package/dist/src/index.js +13 -2
  52. package/dist/src/index.js.map +1 -1
  53. package/dist/src/media_collection.d.ts +8 -1
  54. package/dist/src/media_collection.d.ts.map +1 -1
  55. package/dist/src/media_collection.js +23 -1
  56. package/dist/src/media_collection.js.map +1 -1
  57. package/dist/src/media_library.d.ts +32 -2
  58. package/dist/src/media_library.d.ts.map +1 -1
  59. package/dist/src/media_library.js +218 -11
  60. package/dist/src/media_library.js.map +1 -1
  61. package/dist/src/media_manager.d.ts +37 -2
  62. package/dist/src/media_manager.d.ts.map +1 -1
  63. package/dist/src/media_manager.js +54 -1
  64. package/dist/src/media_manager.js.map +1 -1
  65. package/dist/src/media_record.d.ts +22 -2
  66. package/dist/src/media_record.d.ts.map +1 -1
  67. package/dist/src/services/booted_app.d.ts +9 -0
  68. package/dist/src/services/booted_app.d.ts.map +1 -0
  69. package/dist/src/services/booted_app.js +33 -0
  70. package/dist/src/services/booted_app.js.map +1 -0
  71. package/dist/src/services/main.d.ts +12 -0
  72. package/dist/src/services/main.d.ts.map +1 -1
  73. package/dist/src/services/main.js +15 -3
  74. package/dist/src/services/main.js.map +1 -1
  75. package/dist/src/single_file_store.d.ts +4 -0
  76. package/dist/src/single_file_store.d.ts.map +1 -1
  77. package/dist/src/single_file_store.js +8 -4
  78. package/dist/src/single_file_store.js.map +1 -1
  79. package/dist/src/testing/fake_transformer.d.ts +32 -0
  80. package/dist/src/testing/fake_transformer.d.ts.map +1 -0
  81. package/dist/src/testing/fake_transformer.js +32 -0
  82. package/dist/src/testing/fake_transformer.js.map +1 -0
  83. package/dist/src/testing/index.d.ts +2 -0
  84. package/dist/src/testing/index.d.ts.map +1 -1
  85. package/dist/src/testing/index.js +1 -0
  86. package/dist/src/testing/index.js.map +1 -1
  87. package/dist/src/transformer.d.ts +111 -0
  88. package/dist/src/transformer.d.ts.map +1 -0
  89. package/dist/src/transformer.js +2 -0
  90. package/dist/src/transformer.js.map +1 -0
  91. package/dist/src/transformers/factory.d.ts +30 -0
  92. package/dist/src/transformers/factory.d.ts.map +1 -0
  93. package/dist/src/transformers/factory.js +34 -0
  94. package/dist/src/transformers/factory.js.map +1 -0
  95. package/dist/src/transformers/hls.d.ts +110 -0
  96. package/dist/src/transformers/hls.d.ts.map +1 -0
  97. package/dist/src/transformers/hls.js +111 -0
  98. package/dist/src/transformers/hls.js.map +1 -0
  99. package/dist/src/transformers/mediabunny.d.ts +5 -0
  100. package/dist/src/transformers/mediabunny.d.ts.map +1 -0
  101. package/dist/src/transformers/mediabunny.js +150 -0
  102. package/dist/src/transformers/mediabunny.js.map +1 -0
  103. package/dist/src/transformers/mediabunny_loader.d.ts +15 -0
  104. package/dist/src/transformers/mediabunny_loader.d.ts.map +1 -0
  105. package/dist/src/transformers/mediabunny_loader.js +21 -0
  106. package/dist/src/transformers/mediabunny_loader.js.map +1 -0
  107. package/dist/src/transformers/probe.d.ts +56 -0
  108. package/dist/src/transformers/probe.d.ts.map +1 -0
  109. package/dist/src/transformers/probe.js +32 -0
  110. package/dist/src/transformers/probe.js.map +1 -0
  111. package/dist/src/transformers/source_file.d.ts +20 -0
  112. package/dist/src/transformers/source_file.d.ts.map +1 -0
  113. package/dist/src/transformers/source_file.js +30 -0
  114. package/dist/src/transformers/source_file.js.map +1 -0
  115. package/dist/stubs/config/media.stub +12 -0
  116. package/package.json +11 -7
  117. package/LICENSE +0 -21
@@ -0,0 +1,111 @@
1
+ import type { Readable } from 'node:stream';
2
+ import type { MediaRecord } from './media_record.js';
3
+ import type { StorageManager } from './storage_manager.js';
4
+ import type { Disk } from './types.js';
5
+ /**
6
+ * Options for {@link TransformerContext.write}. `contentLength` matters when `contents` is a
7
+ * `Readable` on a disk that streams (S3 needs the length up front); it is ignored for byte payloads.
8
+ */
9
+ export interface TransformerWriteOptions {
10
+ contentType?: string;
11
+ contentLength?: number;
12
+ }
13
+ /**
14
+ * Everything a {@link Transformer} gets to work with: the record being transformed, read access to
15
+ * its original bytes, and a sandboxed writer for the derived artifacts.
16
+ *
17
+ * The context — not the transformer — decides WHERE artifacts live: every {@link write} lands under
18
+ * {@link outputPrefix} (`<owner>/<id>/conversions/<name>/`), and the library tracks each written
19
+ * path so it can persist the artifact list on the record and delete every artifact when the media
20
+ * is deleted. A transformer never touches disk keys outside its prefix.
21
+ */
22
+ export interface TransformerContext {
23
+ /** The media record whose content is being transformed. */
24
+ record: MediaRecord;
25
+ /** The disk holding the original object (and receiving the artifacts). */
26
+ disk: Disk;
27
+ /** Name of {@link disk}, as resolvable through the storage manager. */
28
+ diskName: string;
29
+ /**
30
+ * The storage façade, for reads OUTSIDE the record's own disk — auxiliary assets a transformer
31
+ * was configured with (a watermark logo on another disk, a font, a sidecar file). Artifact
32
+ * writes still go through {@link write}, never directly through a disk.
33
+ */
34
+ storage: StorageManager;
35
+ /** Buffer the original's bytes. Convenient for small originals; prefer {@link getStream} for video. */
36
+ getBytes(): Promise<Uint8Array>;
37
+ /** Stream the original without buffering it. */
38
+ getStream(): Promise<Readable>;
39
+ /**
40
+ * Storage prefix (ending in `/`) reserved for this transformation's artifacts:
41
+ * `<ownerType>/<ownerId>/<collection>/<mediaId>/conversions/<transformerName>/`.
42
+ */
43
+ outputPrefix: string;
44
+ /**
45
+ * Write one artifact at `outputPrefix + relativePath`. The path is validated (no `..`, no
46
+ * absolute paths) and recorded — the persisted conversion lists every written artifact, which is
47
+ * what makes multi-file outputs (an HLS package) deletable and servable file-by-file.
48
+ *
49
+ * A `Readable` with a known `contentLength` streams straight to the disk; anything else is
50
+ * buffered.
51
+ */
52
+ write(relativePath: string, contents: Uint8Array | Readable, options?: TransformerWriteOptions): Promise<void>;
53
+ }
54
+ /**
55
+ * What a {@link Transformer.transform} run reports back. The artifacts themselves were already
56
+ * written through {@link TransformerContext.write}; this names which of them is the entry point and
57
+ * attaches free-form metadata.
58
+ */
59
+ export interface TransformResult {
60
+ /**
61
+ * The entry-point artifact, relative to {@link TransformerContext.outputPrefix} — what
62
+ * `url(id, name)` / `deliver(id, { conversion: name })` resolve for this conversion. Required
63
+ * when any artifact was written (and must be one of them); omit it for a metadata-only
64
+ * transform (a probe, a blurhash) that wrote nothing.
65
+ */
66
+ entry?: string;
67
+ /**
68
+ * Transformer-specific metadata persisted verbatim on the conversion entry
69
+ * (`record.conversions[name].meta`): duration, resolution, codecs, a blurhash string — whatever
70
+ * the transformer learned. Must be JSON-serializable (the Lucid store serializes it).
71
+ */
72
+ meta?: Record<string, unknown>;
73
+ }
74
+ /**
75
+ * A pluggable content transformation: takes a stored media's content and produces derived
76
+ * artifacts on the disk and/or metadata on the record, persisted as a named conversion
77
+ * (`record.conversions[transformer.name]`).
78
+ *
79
+ * This is the seam the library's own {@link HlsTransformer} and {@link MetadataProbeTransformer}
80
+ * implement, and the one you implement for your own derivations — extract an audio track,
81
+ * stamp a watermark, generate a poster frame, request a transcription. The shapes it supports:
82
+ *
83
+ * - **many artifacts** — an HLS package (segments + playlists); `entry` names the master playlist.
84
+ * - **one artifact** — an optimized image, an extracted audio file; `entry` names it.
85
+ * - **no artifact** — a metadata probe or perceptual hash; return only `meta`.
86
+ * - **auxiliary inputs** — configure them on your transformer instance (bytes, a path, a media id)
87
+ * and read foreign disks through {@link TransformerContext.storage}.
88
+ *
89
+ * Registered per collection (`collections: [{ name, transformers: [...] }]`). Set `eager: true`
90
+ * only for cheap transforms — they run synchronously inside `attach`/`attachExisting` (and so
91
+ * inside a TUS finalize), and a failure rolls the new media back. Heavy transforms (video!) stay
92
+ * deferred: the app triggers them from its own job via `media.library.transform(id, name)`,
93
+ * which is idempotent — already generated ⇒ skip.
94
+ *
95
+ * Import heavy engines lazily INSIDE `transform` (the built-ins import `mediabunny` there), so
96
+ * configuring a transformer never loads its optional peer at boot. A transformer that needs a
97
+ * runtime capability the host lacks (e.g. WebCodecs for re-encoding) should throw
98
+ * {@link TransformerRuntimeMissingError} with a clear hint rather than failing obscurely.
99
+ */
100
+ export interface Transformer {
101
+ /** Unique conversion name this transformer produces (the key in `record.conversions`). */
102
+ readonly name: string;
103
+ /**
104
+ * Run synchronously on attach (like an eager image conversion) instead of waiting for an
105
+ * explicit `transform()` call. Default `false` — transformations are assumed heavy.
106
+ */
107
+ readonly eager?: boolean;
108
+ /** Produce the derived artifacts/metadata for one media record. */
109
+ transform(context: TransformerContext): Promise<TransformResult>;
110
+ }
111
+ //# sourceMappingURL=transformer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transformer.d.ts","sourceRoot":"","sources":["../../src/transformer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,MAAM,EAAE,WAAW,CAAC;IACpB,0EAA0E;IAC1E,IAAI,EAAE,IAAI,CAAC;IACX,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,OAAO,EAAE,cAAc,CAAC;IACxB,uGAAuG;IACvG,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAChC,gDAAgD;IAChD,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/B;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,KAAK,CACH,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,UAAU,GAAG,QAAQ,EAC/B,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,WAAW;IAC1B,0FAA0F;IAC1F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,mEAAmE;IACnE,SAAS,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CAClE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=transformer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transformer.js","sourceRoot":"","sources":["../../src/transformer.ts"],"names":[],"mappings":""}
@@ -0,0 +1,30 @@
1
+ import { HlsTransformer, type HlsTransformerOptions } from './hls.js';
2
+ import { MetadataProbeTransformer, type MetadataProbeTransformerOptions } from './probe.js';
3
+ /**
4
+ * The transformer factory namespace used in `config/media.ts` — the {@link Transformer}
5
+ * counterpart of `processors`/`disks`/`stores`:
6
+ *
7
+ * ```ts
8
+ * import { defineConfig, transformers } from '@adonis-agora/media'
9
+ *
10
+ * export default defineConfig({
11
+ * collections: [
12
+ * {
13
+ * name: 'videos',
14
+ * acceptsMimeTypes: ['video/mp4'],
15
+ * transformers: [transformers.hls({ targetDuration: 4 }), transformers.probe()],
16
+ * },
17
+ * ],
18
+ * })
19
+ * ```
20
+ *
21
+ * Constructing a transformer here never loads its engine — the optional `mediabunny` peer is
22
+ * imported inside the first `transform()` call, so config stays side-effect-free.
23
+ */
24
+ export declare const transformers: {
25
+ /** Video → HLS package (MPEG-TS segments + playlists) via mediabunny. Default name `'hls'`. */
26
+ hls<Name extends string = "hls">(options?: HlsTransformerOptions<Name>): HlsTransformer<Name>;
27
+ /** Metadata-only probe (duration/resolution/codecs) via mediabunny. Default name `'probe'`. */
28
+ probe<Name extends string = "probe">(options?: MetadataProbeTransformerOptions<Name>): MetadataProbeTransformer<Name>;
29
+ };
30
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/transformers/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACtE,OAAO,EAAE,wBAAwB,EAAE,KAAK,+BAA+B,EAAE,MAAM,YAAY,CAAC;AAE5F;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,YAAY;IACvB,+FAA+F;QAC3F,IAAI,SAAS,MAAM,oBAAmB,qBAAqB,CAAC,IAAI,CAAC;IAGrE,+FAA+F;UACzF,IAAI,SAAS,MAAM,sBAAqB,+BAA+B,CAAC,IAAI,CAAC;CAGpF,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { HlsTransformer } from './hls.js';
2
+ import { MetadataProbeTransformer } from './probe.js';
3
+ /**
4
+ * The transformer factory namespace used in `config/media.ts` — the {@link Transformer}
5
+ * counterpart of `processors`/`disks`/`stores`:
6
+ *
7
+ * ```ts
8
+ * import { defineConfig, transformers } from '@adonis-agora/media'
9
+ *
10
+ * export default defineConfig({
11
+ * collections: [
12
+ * {
13
+ * name: 'videos',
14
+ * acceptsMimeTypes: ['video/mp4'],
15
+ * transformers: [transformers.hls({ targetDuration: 4 }), transformers.probe()],
16
+ * },
17
+ * ],
18
+ * })
19
+ * ```
20
+ *
21
+ * Constructing a transformer here never loads its engine — the optional `mediabunny` peer is
22
+ * imported inside the first `transform()` call, so config stays side-effect-free.
23
+ */
24
+ export const transformers = {
25
+ /** Video → HLS package (MPEG-TS segments + playlists) via mediabunny. Default name `'hls'`. */
26
+ hls(options = {}) {
27
+ return new HlsTransformer(options);
28
+ },
29
+ /** Metadata-only probe (duration/resolution/codecs) via mediabunny. Default name `'probe'`. */
30
+ probe(options = {}) {
31
+ return new MetadataProbeTransformer(options);
32
+ },
33
+ };
34
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/transformers/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA8B,MAAM,UAAU,CAAC;AACtE,OAAO,EAAE,wBAAwB,EAAwC,MAAM,YAAY,CAAC;AAE5F;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,+FAA+F;IAC/F,GAAG,CAA8B,UAAuC,EAAE;QACxE,OAAO,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IACD,+FAA+F;IAC/F,KAAK,CAAgC,UAAiD,EAAE;QACtF,OAAO,IAAI,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAC/C,CAAC;CACF,CAAC"}
@@ -0,0 +1,110 @@
1
+ import type { TransformResult, Transformer, TransformerContext } from '../transformer.js';
2
+ /**
3
+ * Lazily supplies a WebCodecs implementation (`{ VideoEncoder, VideoDecoder, AudioEncoder, … }`).
4
+ * Node has no built-in WebCodecs, which is exactly why the HLS transformer is remux-only by
5
+ * default — but NAPI-based implementations exist (e.g. `@napi-rs/webcodecs`), and injecting one
6
+ * unlocks the re-encode fallback for sources whose codecs cannot be stream-copied:
7
+ *
8
+ * ```ts
9
+ * transformers.hls({ webcodecs: () => import('@napi-rs/webcodecs') })
10
+ * ```
11
+ *
12
+ * The engine installs the provided classes onto `globalThis` (only the ones missing — a native
13
+ * implementation is never overwritten), because that is where the underlying media engine looks
14
+ * the API up.
15
+ */
16
+ export type WebCodecsProvider = () => Promise<Record<string, unknown>>;
17
+ /** How a runtime can satisfy the WebCodecs requirement. See {@link resolveWebCodecsSupport}. */
18
+ export type WebCodecsSupport = 'native' | 'injected' | 'absent';
19
+ /**
20
+ * Detect how (whether) WebCodecs is available: `'native'` when the runtime already exposes
21
+ * `VideoEncoder`, `'injected'` when a {@link WebCodecsProvider} supplies one, `'absent'` otherwise.
22
+ * `'absent'` means encoding is impossible — transforms stay stream-copy only, and a transformer
23
+ * that cannot proceed without an encoder should throw `TransformerRuntimeMissingError`.
24
+ */
25
+ export declare function resolveWebCodecsSupport(provider?: WebCodecsProvider): Promise<WebCodecsSupport>;
26
+ /** What {@link HlsRemuxEngine.remux} receives. */
27
+ export interface HlsRemuxRequest {
28
+ /** Local path of the source file. */
29
+ sourcePath: string;
30
+ /** Local directory the engine writes the whole package into (entry playlist at its root). */
31
+ outputDir: string;
32
+ /** File name of the entry (master) playlist, e.g. `index.m3u8`. */
33
+ entryName: string;
34
+ /** Target (max) segment duration in seconds. */
35
+ targetDuration: number;
36
+ /** Optional WebCodecs implementation enabling the re-encode fallback. */
37
+ webcodecs?: WebCodecsProvider | undefined;
38
+ }
39
+ /** What the engine learned about the source while remuxing — persisted into the conversion `meta`. */
40
+ export interface HlsRemuxSummary {
41
+ durationSeconds?: number;
42
+ width?: number;
43
+ height?: number;
44
+ videoCodec?: string | null;
45
+ audioCodec?: string | null;
46
+ }
47
+ /**
48
+ * The engine seam of {@link HlsTransformer}: local file in, HLS package (playlists + segments) in
49
+ * a local directory out. The default engine is mediabunny, loaded lazily on first transform; tests
50
+ * inject a fake that writes files, so every bit of orchestration around the engine is testable
51
+ * without the optional peer.
52
+ */
53
+ export interface HlsRemuxEngine {
54
+ remux(request: HlsRemuxRequest): Promise<HlsRemuxSummary>;
55
+ }
56
+ export interface HlsTransformerOptions<Name extends string = 'hls'> {
57
+ /** Conversion name on the record. Default `'hls'`. */
58
+ name?: Name;
59
+ /**
60
+ * Run inside attach. Default `false`, and think twice: a remux takes real time, and an eager
61
+ * failure rolls the attach back. The intended flow is deferred —
62
+ * `media.library.transform(id, 'hls')` from a job.
63
+ */
64
+ eager?: boolean;
65
+ /** Target (max) segment duration in seconds. Default 4. */
66
+ targetDuration?: number;
67
+ /** WebCodecs implementation for the re-encode fallback (see {@link WebCodecsProvider}). */
68
+ webcodecs?: WebCodecsProvider;
69
+ /** Replace the mediabunny engine (mainly for tests). */
70
+ engine?: HlsRemuxEngine;
71
+ }
72
+ /** File name of the entry (master) playlist an {@link HlsTransformer} produces. */
73
+ export declare const HLS_ENTRY_PLAYLIST = "index.m3u8";
74
+ /**
75
+ * The built-in video {@link Transformer}: converts a stored video into an HLS package — MPEG-TS
76
+ * segments plus media playlists behind one master playlist — written under the conversion prefix
77
+ * and persisted as `record.conversions['hls']` with duration/resolution/codec metadata.
78
+ *
79
+ * **Remux-only by default.** The engine (mediabunny, pure JS — no ffmpeg binary) stream-copies
80
+ * the source's h264/aac tracks into segments without re-encoding, because encoding needs
81
+ * WebCodecs, which Node does not provide. Two consequences, both deliberate:
82
+ *
83
+ * - a source whose codecs MPEG-TS cannot carry (vp9, av1…) fails with a clear, definitive
84
+ * `HlsSourceUnsupportedError` — unless a {@link WebCodecsProvider} is configured, which enables
85
+ * the engine's re-encode fallback;
86
+ * - the package has a single variant at the original resolution. Multi-quality renditions are the
87
+ * documented evolution once an encoder is present (mediabunny supports fan-out video options),
88
+ * not something this transformer fakes today.
89
+ *
90
+ * The engine also carries the subtle bit the naive implementation gets wrong: AAC audio with
91
+ * priming (an mp4 edit list) starts at a **negative** timestamp, and a conversion trimmed at t=0
92
+ * would kick that track off the stream-copy fast path — impossible without an encoder. The
93
+ * conversion is therefore trimmed at the source's real first timestamp, shifting all tracks
94
+ * equally (sync is unchanged) and keeping every track copyable.
95
+ *
96
+ * Serving the result is the other half: playlists are stored with storage-relative references and
97
+ * must be rewritten per request — see {@link HlsDeliveryHandler}.
98
+ */
99
+ export declare class HlsTransformer<Name extends string = 'hls'> implements Transformer {
100
+ readonly name: Name;
101
+ readonly eager: boolean;
102
+ private readonly targetDuration;
103
+ private readonly webcodecs;
104
+ private readonly engine;
105
+ constructor(options?: HlsTransformerOptions<Name>);
106
+ transform(context: TransformerContext): Promise<TransformResult>;
107
+ }
108
+ /** Drop `undefined` values so the persisted meta round-trips identically through JSON stores. */
109
+ export declare function compactMeta(meta: Record<string, unknown>): Record<string, unknown>;
110
+ //# sourceMappingURL=hls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hls.d.ts","sourceRoot":"","sources":["../../../src/transformers/hls.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAI1F;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEvE,gGAAgG;AAChG,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEhE;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,CAAC,EAAE,iBAAiB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAI3B;AAED,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC9B,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,6FAA6F;IAC7F,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,cAAc,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,SAAS,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CAC3C;AAED,sGAAsG;AACtG,MAAM,WAAW,eAAe;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CAC3D;AAED,MAAM,WAAW,qBAAqB,CAAC,IAAI,SAAS,MAAM,GAAG,KAAK;IAChE,sDAAsD;IACtD,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2DAA2D;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,wDAAwD;IACxD,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAED,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,cAAc,CAAC,IAAI,SAAS,MAAM,GAAG,KAAK,CAAE,YAAW,WAAW;IAC7E,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAC1D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;gBAExC,OAAO,GAAE,qBAAqB,CAAC,IAAI,CAAM;IAQ/C,SAAS,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC;CA8CvE;AAED,iGAAiG;AACjG,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAElF"}
@@ -0,0 +1,111 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import { readdir, stat } from 'node:fs/promises';
3
+ import { mkdir } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { hlsArtifactContentType } from '../hls/playlist.js';
6
+ import { loadMediabunny } from './mediabunny_loader.js';
7
+ import { withSourceFile } from './source_file.js';
8
+ /**
9
+ * Detect how (whether) WebCodecs is available: `'native'` when the runtime already exposes
10
+ * `VideoEncoder`, `'injected'` when a {@link WebCodecsProvider} supplies one, `'absent'` otherwise.
11
+ * `'absent'` means encoding is impossible — transforms stay stream-copy only, and a transformer
12
+ * that cannot proceed without an encoder should throw `TransformerRuntimeMissingError`.
13
+ */
14
+ export async function resolveWebCodecsSupport(provider) {
15
+ if (globalThis.VideoEncoder !== undefined)
16
+ return 'native';
17
+ if (provider && (await provider()).VideoEncoder !== undefined)
18
+ return 'injected';
19
+ return 'absent';
20
+ }
21
+ /** File name of the entry (master) playlist an {@link HlsTransformer} produces. */
22
+ export const HLS_ENTRY_PLAYLIST = 'index.m3u8';
23
+ /**
24
+ * The built-in video {@link Transformer}: converts a stored video into an HLS package — MPEG-TS
25
+ * segments plus media playlists behind one master playlist — written under the conversion prefix
26
+ * and persisted as `record.conversions['hls']` with duration/resolution/codec metadata.
27
+ *
28
+ * **Remux-only by default.** The engine (mediabunny, pure JS — no ffmpeg binary) stream-copies
29
+ * the source's h264/aac tracks into segments without re-encoding, because encoding needs
30
+ * WebCodecs, which Node does not provide. Two consequences, both deliberate:
31
+ *
32
+ * - a source whose codecs MPEG-TS cannot carry (vp9, av1…) fails with a clear, definitive
33
+ * `HlsSourceUnsupportedError` — unless a {@link WebCodecsProvider} is configured, which enables
34
+ * the engine's re-encode fallback;
35
+ * - the package has a single variant at the original resolution. Multi-quality renditions are the
36
+ * documented evolution once an encoder is present (mediabunny supports fan-out video options),
37
+ * not something this transformer fakes today.
38
+ *
39
+ * The engine also carries the subtle bit the naive implementation gets wrong: AAC audio with
40
+ * priming (an mp4 edit list) starts at a **negative** timestamp, and a conversion trimmed at t=0
41
+ * would kick that track off the stream-copy fast path — impossible without an encoder. The
42
+ * conversion is therefore trimmed at the source's real first timestamp, shifting all tracks
43
+ * equally (sync is unchanged) and keeping every track copyable.
44
+ *
45
+ * Serving the result is the other half: playlists are stored with storage-relative references and
46
+ * must be rewritten per request — see {@link HlsDeliveryHandler}.
47
+ */
48
+ export class HlsTransformer {
49
+ name;
50
+ eager;
51
+ targetDuration;
52
+ webcodecs;
53
+ engine;
54
+ constructor(options = {}) {
55
+ this.name = options.name ?? 'hls';
56
+ this.eager = options.eager ?? false;
57
+ this.targetDuration = options.targetDuration ?? 4;
58
+ this.webcodecs = options.webcodecs;
59
+ this.engine = options.engine;
60
+ }
61
+ async transform(context) {
62
+ const engine = this.engine ?? (await loadMediabunny(this.name)).createHlsEngine();
63
+ return withSourceFile(context, async ({ sourcePath, tempDir }) => {
64
+ const outputDir = join(tempDir, 'hls');
65
+ await mkdir(outputDir, { recursive: true });
66
+ const summary = await engine.remux({
67
+ sourcePath,
68
+ outputDir,
69
+ entryName: HLS_ENTRY_PLAYLIST,
70
+ targetDuration: this.targetDuration,
71
+ webcodecs: this.webcodecs,
72
+ });
73
+ // Upload the whole package the engine wrote — playlists and segments alike — streaming each
74
+ // file (a segment never has to fit in memory). Sorted for deterministic order in
75
+ // `conversions[name].files`.
76
+ const files = (await readdir(outputDir, { recursive: true })).sort();
77
+ let segmentCount = 0;
78
+ let playlistCount = 0;
79
+ for (const file of files) {
80
+ const relative = file.split('\\').join('/');
81
+ const local = join(outputDir, file);
82
+ const info = await stat(local);
83
+ if (!info.isFile())
84
+ continue;
85
+ if (relative.toLowerCase().endsWith('.m3u8'))
86
+ playlistCount += 1;
87
+ else
88
+ segmentCount += 1;
89
+ const contentType = hlsArtifactContentType(relative);
90
+ await context.write(relative, createReadStream(local), {
91
+ contentLength: info.size,
92
+ ...(contentType !== undefined ? { contentType } : {}),
93
+ });
94
+ }
95
+ return {
96
+ entry: HLS_ENTRY_PLAYLIST,
97
+ meta: compactMeta({
98
+ ...summary,
99
+ segmentCount,
100
+ playlistCount,
101
+ targetDuration: this.targetDuration,
102
+ }),
103
+ };
104
+ });
105
+ }
106
+ }
107
+ /** Drop `undefined` values so the persisted meta round-trips identically through JSON stores. */
108
+ export function compactMeta(meta) {
109
+ return Object.fromEntries(Object.entries(meta).filter(([, value]) => value !== undefined));
110
+ }
111
+ //# sourceMappingURL=hls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hls.js","sourceRoot":"","sources":["../../../src/transformers/hls.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { HlsRemuxEngine } from './hls.js';
2
+ import type { MediaProbeEngine } from './probe.js';
3
+ export declare function createHlsEngine(): HlsRemuxEngine;
4
+ export declare function createProbeEngine(): MediaProbeEngine;
5
+ //# sourceMappingURL=mediabunny.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediabunny.d.ts","sourceRoot":"","sources":["../../../src/transformers/mediabunny.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,cAAc,EAAuD,MAAM,UAAU,CAAC;AACpG,OAAO,KAAK,EAAE,gBAAgB,EAAqB,MAAM,YAAY,CAAC;AA+DtE,wBAAgB,eAAe,IAAI,cAAc,CA8DhD;AAED,wBAAgB,iBAAiB,IAAI,gBAAgB,CAiCpD"}
@@ -0,0 +1,150 @@
1
+ import { join } from 'node:path';
2
+ import { ALL_FORMATS, Conversion, FilePathSource, FilePathTarget, HlsOutputFormat, Input, MpegTsOutputFormat, Output, PathedTarget, } from 'mediabunny';
3
+ import { HlsSourceUnsupportedError } from '../errors.js';
4
+ /**
5
+ * The mediabunny-backed engines — the ONLY module in the package that imports the optional
6
+ * `mediabunny` peer, reached lazily through `mediabunny_loader.ts` at first transform.
7
+ *
8
+ * Why mediabunny: pure TypeScript, no ffmpeg binary — a production container without system
9
+ * ffmpeg (the usual case) remuxes fine. The trade-off is that *encoding* rides on WebCodecs,
10
+ * which Node lacks, hence the remux-only default and the {@link WebCodecsProvider} injection
11
+ * seam documented on `HlsTransformer`.
12
+ */
13
+ /** The WebCodecs API surface mediabunny reads from `globalThis`. */
14
+ const WEBCODECS_GLOBALS = [
15
+ 'VideoEncoder',
16
+ 'VideoDecoder',
17
+ 'AudioEncoder',
18
+ 'AudioDecoder',
19
+ 'VideoFrame',
20
+ 'AudioData',
21
+ 'EncodedVideoChunk',
22
+ 'EncodedAudioChunk',
23
+ ];
24
+ /**
25
+ * Install an injected WebCodecs implementation onto `globalThis` — only the classes that are
26
+ * missing; a native implementation is never overwritten. Global mutation is deliberate and
27
+ * additive: `globalThis` is where the WebCodecs spec (and therefore mediabunny) looks the API up,
28
+ * so "injecting an implementation" and "installing it globally" are the same thing. Installing
29
+ * per-conversion and uninstalling after would race concurrent transforms for zero benefit.
30
+ */
31
+ async function installWebCodecs(provider) {
32
+ if (!provider)
33
+ return;
34
+ const implementation = await provider();
35
+ const globals = globalThis;
36
+ for (const name of WEBCODECS_GLOBALS) {
37
+ if (globals[name] === undefined && implementation[name] !== undefined) {
38
+ globals[name] = implementation[name];
39
+ }
40
+ }
41
+ }
42
+ /** Best-effort technical summary of an input — never fails the transform over metadata. */
43
+ async function summarize(input) {
44
+ const summary = {};
45
+ try {
46
+ summary.durationSeconds = await input.computeDuration();
47
+ const video = await input.getPrimaryVideoTrack();
48
+ if (video) {
49
+ summary.width = await video.getDisplayWidth();
50
+ summary.height = await video.getDisplayHeight();
51
+ summary.videoCodec = await video.getCodec();
52
+ }
53
+ const audio = await input.getPrimaryAudioTrack();
54
+ if (audio) {
55
+ summary.audioCodec = await audio.getCodec();
56
+ }
57
+ }
58
+ catch {
59
+ // metadata is a bonus; the artifacts are the product
60
+ }
61
+ return summary;
62
+ }
63
+ export function createHlsEngine() {
64
+ return {
65
+ async remux(request) {
66
+ await installWebCodecs(request.webcodecs);
67
+ const input = new Input({
68
+ source: new FilePathSource(request.sourcePath),
69
+ formats: ALL_FORMATS,
70
+ });
71
+ try {
72
+ const output = new Output({
73
+ format: new HlsOutputFormat({
74
+ segmentFormat: new MpegTsOutputFormat(),
75
+ targetDuration: request.targetDuration,
76
+ }),
77
+ // The HLS output is many files; each one lands in the local output directory.
78
+ target: new PathedTarget(join(request.outputDir, request.entryName), (target) => new FilePathTarget(target.path)),
79
+ });
80
+ // THE gotcha: AAC audio with priming (an mp4 edit list) starts at a NEGATIVE timestamp.
81
+ // Mediabunny's default trims the conversion at t=0, which would throw that audio track off
82
+ // the packet-copy fast path — and re-encoding it needs an encoder we may not have.
83
+ // Trimming at the source's real first timestamp keeps every track a pure stream copy; all
84
+ // tracks shift equally, so A/V sync is untouched.
85
+ const firstTimestamp = await input.getFirstTimestamp();
86
+ const conversion = await Conversion.init({
87
+ input,
88
+ output,
89
+ trim: { start: firstTimestamp },
90
+ });
91
+ // With no video/audio options, mediabunny copies compatible tracks and re-encodes the
92
+ // rest — but re-encoding needs an encoder, so without WebCodecs an incompatible track is
93
+ // DISCARDED instead. Silently shipping a video without its audio (or vice versa) is not a
94
+ // conversion, it is data loss: fail loudly, with the codec and the reason per track.
95
+ if (conversion.discardedTracks.length > 0) {
96
+ const reasons = await Promise.all(conversion.discardedTracks.map(async (discarded) => {
97
+ const codec = await discarded.track.getCodec().catch(() => null);
98
+ return `${discarded.track.type}/${codec ?? 'unknown'}: ${discarded.reason}`;
99
+ }));
100
+ throw new HlsSourceUnsupportedError(reasons.join('; '));
101
+ }
102
+ if (!conversion.isValid) {
103
+ throw new HlsSourceUnsupportedError('the file contains no usable track');
104
+ }
105
+ const summary = await summarize(input);
106
+ await conversion.execute();
107
+ return summary;
108
+ }
109
+ finally {
110
+ input.dispose();
111
+ }
112
+ },
113
+ };
114
+ }
115
+ export function createProbeEngine() {
116
+ return {
117
+ async probe(sourcePath) {
118
+ const input = new Input({ source: new FilePathSource(sourcePath), formats: ALL_FORMATS });
119
+ try {
120
+ const format = await input.getFormat();
121
+ const summary = {
122
+ format: format.name,
123
+ mimeType: await input.getMimeType(),
124
+ durationSeconds: await input.computeDuration(),
125
+ hasVideo: false,
126
+ hasAudio: false,
127
+ };
128
+ const video = await input.getPrimaryVideoTrack();
129
+ if (video) {
130
+ summary.hasVideo = true;
131
+ summary.width = await video.getDisplayWidth();
132
+ summary.height = await video.getDisplayHeight();
133
+ summary.videoCodec = await video.getCodec();
134
+ }
135
+ const audio = await input.getPrimaryAudioTrack();
136
+ if (audio) {
137
+ summary.hasAudio = true;
138
+ summary.audioCodec = await audio.getCodec();
139
+ summary.audioSampleRate = await audio.getSampleRate();
140
+ summary.audioChannels = await audio.getNumberOfChannels();
141
+ }
142
+ return summary;
143
+ }
144
+ finally {
145
+ input.dispose();
146
+ }
147
+ },
148
+ };
149
+ }
150
+ //# sourceMappingURL=mediabunny.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediabunny.js","sourceRoot":"","sources":["../../../src/transformers/mediabunny.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { HlsRemuxEngine } from './hls.js';
2
+ import type { MediaProbeEngine } from './probe.js';
3
+ /** The engine factories `./mediabunny.js` exports (the only module that imports the peer). */
4
+ export interface MediabunnyEngineModule {
5
+ createHlsEngine(): HlsRemuxEngine;
6
+ createProbeEngine(): MediaProbeEngine;
7
+ }
8
+ /**
9
+ * Load the mediabunny-backed engine module, translating a missing optional peer into the typed
10
+ * {@link TransformerRuntimeMissingError} instead of a raw `ERR_MODULE_NOT_FOUND` five imports deep.
11
+ * Import happens here — at first transform — never at boot: configuring a transformer must not
12
+ * load its engine (the same laziness `processors.sharp()` and `disks.s3()` follow).
13
+ */
14
+ export declare function loadMediabunny(transformerName: string): Promise<MediabunnyEngineModule>;
15
+ //# sourceMappingURL=mediabunny_loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediabunny_loader.d.ts","sourceRoot":"","sources":["../../../src/transformers/mediabunny_loader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,8FAA8F;AAC9F,MAAM,WAAW,sBAAsB;IACrC,eAAe,IAAI,cAAc,CAAC;IAClC,iBAAiB,IAAI,gBAAgB,CAAC;CACvC;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAiB7F"}
@@ -0,0 +1,21 @@
1
+ import { TransformerRuntimeMissingError } from '../errors.js';
2
+ /**
3
+ * Load the mediabunny-backed engine module, translating a missing optional peer into the typed
4
+ * {@link TransformerRuntimeMissingError} instead of a raw `ERR_MODULE_NOT_FOUND` five imports deep.
5
+ * Import happens here — at first transform — never at boot: configuring a transformer must not
6
+ * load its engine (the same laziness `processors.sharp()` and `disks.s3()` follow).
7
+ */
8
+ export async function loadMediabunny(transformerName) {
9
+ try {
10
+ return await import('./mediabunny.js');
11
+ }
12
+ catch (error) {
13
+ if (error.code === 'ERR_MODULE_NOT_FOUND' &&
14
+ error instanceof Error &&
15
+ error.message.includes('mediabunny')) {
16
+ throw new TransformerRuntimeMissingError(transformerName, "the optional peer dependency 'mediabunny'", 'Install it (`pnpm add mediabunny`) or inject a custom `engine`.');
17
+ }
18
+ throw error;
19
+ }
20
+ }
21
+ //# sourceMappingURL=mediabunny_loader.js.map