node-av 3.1.3 → 5.0.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 (192) hide show
  1. package/README.md +88 -52
  2. package/binding.gyp +23 -11
  3. package/dist/api/audio-frame-buffer.d.ts +201 -0
  4. package/dist/api/audio-frame-buffer.js +275 -0
  5. package/dist/api/audio-frame-buffer.js.map +1 -0
  6. package/dist/api/bitstream-filter.d.ts +320 -78
  7. package/dist/api/bitstream-filter.js +684 -151
  8. package/dist/api/bitstream-filter.js.map +1 -1
  9. package/dist/api/constants.d.ts +44 -0
  10. package/dist/api/constants.js +45 -0
  11. package/dist/api/constants.js.map +1 -0
  12. package/dist/api/data/test_av1.ivf +0 -0
  13. package/dist/api/data/test_mjpeg.mjpeg +0 -0
  14. package/dist/api/data/test_vp8.ivf +0 -0
  15. package/dist/api/data/test_vp9.ivf +0 -0
  16. package/dist/api/decoder.d.ts +454 -77
  17. package/dist/api/decoder.js +1081 -271
  18. package/dist/api/decoder.js.map +1 -1
  19. package/dist/api/{media-input.d.ts → demuxer.d.ts} +295 -45
  20. package/dist/api/demuxer.js +1965 -0
  21. package/dist/api/demuxer.js.map +1 -0
  22. package/dist/api/encoder.d.ts +423 -132
  23. package/dist/api/encoder.js +1089 -240
  24. package/dist/api/encoder.js.map +1 -1
  25. package/dist/api/filter-complex.d.ts +769 -0
  26. package/dist/api/filter-complex.js +1596 -0
  27. package/dist/api/filter-complex.js.map +1 -0
  28. package/dist/api/filter-presets.d.ts +80 -5
  29. package/dist/api/filter-presets.js +117 -7
  30. package/dist/api/filter-presets.js.map +1 -1
  31. package/dist/api/filter.d.ts +561 -125
  32. package/dist/api/filter.js +1083 -274
  33. package/dist/api/filter.js.map +1 -1
  34. package/dist/api/{fmp4.d.ts → fmp4-stream.d.ts} +141 -140
  35. package/dist/api/fmp4-stream.js +539 -0
  36. package/dist/api/fmp4-stream.js.map +1 -0
  37. package/dist/api/hardware.d.ts +58 -6
  38. package/dist/api/hardware.js +127 -11
  39. package/dist/api/hardware.js.map +1 -1
  40. package/dist/api/index.d.ts +8 -4
  41. package/dist/api/index.js +17 -8
  42. package/dist/api/index.js.map +1 -1
  43. package/dist/api/io-stream.d.ts +6 -6
  44. package/dist/api/io-stream.js +5 -4
  45. package/dist/api/io-stream.js.map +1 -1
  46. package/dist/api/{media-output.d.ts → muxer.d.ts} +280 -66
  47. package/dist/api/muxer.js +1934 -0
  48. package/dist/api/muxer.js.map +1 -0
  49. package/dist/api/pipeline.d.ts +77 -29
  50. package/dist/api/pipeline.js +449 -439
  51. package/dist/api/pipeline.js.map +1 -1
  52. package/dist/api/rtp-stream.d.ts +312 -0
  53. package/dist/api/rtp-stream.js +630 -0
  54. package/dist/api/rtp-stream.js.map +1 -0
  55. package/dist/api/types.d.ts +533 -56
  56. package/dist/api/utilities/async-queue.d.ts +91 -0
  57. package/dist/api/utilities/async-queue.js +162 -0
  58. package/dist/api/utilities/async-queue.js.map +1 -0
  59. package/dist/api/utilities/audio-sample.d.ts +11 -1
  60. package/dist/api/utilities/audio-sample.js +10 -0
  61. package/dist/api/utilities/audio-sample.js.map +1 -1
  62. package/dist/api/utilities/channel-layout.d.ts +1 -0
  63. package/dist/api/utilities/channel-layout.js +1 -0
  64. package/dist/api/utilities/channel-layout.js.map +1 -1
  65. package/dist/api/utilities/image.d.ts +39 -1
  66. package/dist/api/utilities/image.js +38 -0
  67. package/dist/api/utilities/image.js.map +1 -1
  68. package/dist/api/utilities/index.d.ts +3 -0
  69. package/dist/api/utilities/index.js +6 -0
  70. package/dist/api/utilities/index.js.map +1 -1
  71. package/dist/api/utilities/media-type.d.ts +2 -1
  72. package/dist/api/utilities/media-type.js +1 -0
  73. package/dist/api/utilities/media-type.js.map +1 -1
  74. package/dist/api/utilities/pixel-format.d.ts +4 -1
  75. package/dist/api/utilities/pixel-format.js +3 -0
  76. package/dist/api/utilities/pixel-format.js.map +1 -1
  77. package/dist/api/utilities/sample-format.d.ts +6 -1
  78. package/dist/api/utilities/sample-format.js +5 -0
  79. package/dist/api/utilities/sample-format.js.map +1 -1
  80. package/dist/api/utilities/scheduler.d.ts +138 -0
  81. package/dist/api/utilities/scheduler.js +98 -0
  82. package/dist/api/utilities/scheduler.js.map +1 -0
  83. package/dist/api/utilities/streaming.d.ts +105 -15
  84. package/dist/api/utilities/streaming.js +201 -12
  85. package/dist/api/utilities/streaming.js.map +1 -1
  86. package/dist/api/utilities/timestamp.d.ts +15 -1
  87. package/dist/api/utilities/timestamp.js +14 -0
  88. package/dist/api/utilities/timestamp.js.map +1 -1
  89. package/dist/api/utilities/whisper-model.d.ts +310 -0
  90. package/dist/api/utilities/whisper-model.js +528 -0
  91. package/dist/api/utilities/whisper-model.js.map +1 -0
  92. package/dist/api/webrtc-stream.d.ts +288 -0
  93. package/dist/api/webrtc-stream.js +440 -0
  94. package/dist/api/webrtc-stream.js.map +1 -0
  95. package/dist/api/whisper.d.ts +324 -0
  96. package/dist/api/whisper.js +362 -0
  97. package/dist/api/whisper.js.map +1 -0
  98. package/dist/constants/constants.d.ts +54 -2
  99. package/dist/constants/constants.js +48 -1
  100. package/dist/constants/constants.js.map +1 -1
  101. package/dist/constants/encoders.d.ts +2 -1
  102. package/dist/constants/encoders.js +4 -3
  103. package/dist/constants/encoders.js.map +1 -1
  104. package/dist/constants/hardware.d.ts +26 -0
  105. package/dist/constants/hardware.js +27 -0
  106. package/dist/constants/hardware.js.map +1 -0
  107. package/dist/constants/index.d.ts +1 -0
  108. package/dist/constants/index.js +1 -0
  109. package/dist/constants/index.js.map +1 -1
  110. package/dist/ffmpeg/index.d.ts +3 -3
  111. package/dist/ffmpeg/index.js +3 -3
  112. package/dist/ffmpeg/utils.d.ts +27 -0
  113. package/dist/ffmpeg/utils.js +28 -16
  114. package/dist/ffmpeg/utils.js.map +1 -1
  115. package/dist/lib/binding.d.ts +22 -11
  116. package/dist/lib/binding.js.map +1 -1
  117. package/dist/lib/codec-context.d.ts +87 -0
  118. package/dist/lib/codec-context.js +125 -4
  119. package/dist/lib/codec-context.js.map +1 -1
  120. package/dist/lib/codec-parameters.d.ts +229 -1
  121. package/dist/lib/codec-parameters.js +264 -0
  122. package/dist/lib/codec-parameters.js.map +1 -1
  123. package/dist/lib/codec-parser.d.ts +23 -0
  124. package/dist/lib/codec-parser.js +25 -0
  125. package/dist/lib/codec-parser.js.map +1 -1
  126. package/dist/lib/codec.d.ts +26 -4
  127. package/dist/lib/codec.js +35 -0
  128. package/dist/lib/codec.js.map +1 -1
  129. package/dist/lib/dictionary.js +1 -0
  130. package/dist/lib/dictionary.js.map +1 -1
  131. package/dist/lib/error.js +1 -1
  132. package/dist/lib/error.js.map +1 -1
  133. package/dist/lib/fifo.d.ts +416 -0
  134. package/dist/lib/fifo.js +453 -0
  135. package/dist/lib/fifo.js.map +1 -0
  136. package/dist/lib/filter-context.d.ts +52 -11
  137. package/dist/lib/filter-context.js +56 -12
  138. package/dist/lib/filter-context.js.map +1 -1
  139. package/dist/lib/filter-graph.d.ts +9 -0
  140. package/dist/lib/filter-graph.js +13 -0
  141. package/dist/lib/filter-graph.js.map +1 -1
  142. package/dist/lib/filter.d.ts +21 -0
  143. package/dist/lib/filter.js +28 -0
  144. package/dist/lib/filter.js.map +1 -1
  145. package/dist/lib/format-context.d.ts +48 -14
  146. package/dist/lib/format-context.js +76 -7
  147. package/dist/lib/format-context.js.map +1 -1
  148. package/dist/lib/frame.d.ts +264 -1
  149. package/dist/lib/frame.js +351 -1
  150. package/dist/lib/frame.js.map +1 -1
  151. package/dist/lib/hardware-device-context.d.ts +3 -2
  152. package/dist/lib/hardware-device-context.js.map +1 -1
  153. package/dist/lib/index.d.ts +2 -0
  154. package/dist/lib/index.js +4 -0
  155. package/dist/lib/index.js.map +1 -1
  156. package/dist/lib/input-format.d.ts +21 -0
  157. package/dist/lib/input-format.js +42 -2
  158. package/dist/lib/input-format.js.map +1 -1
  159. package/dist/lib/native-types.d.ts +76 -27
  160. package/dist/lib/option.d.ts +25 -13
  161. package/dist/lib/option.js +28 -0
  162. package/dist/lib/option.js.map +1 -1
  163. package/dist/lib/output-format.d.ts +22 -1
  164. package/dist/lib/output-format.js +28 -0
  165. package/dist/lib/output-format.js.map +1 -1
  166. package/dist/lib/packet.d.ts +35 -0
  167. package/dist/lib/packet.js +52 -2
  168. package/dist/lib/packet.js.map +1 -1
  169. package/dist/lib/rational.d.ts +18 -0
  170. package/dist/lib/rational.js +19 -0
  171. package/dist/lib/rational.js.map +1 -1
  172. package/dist/lib/stream.d.ts +126 -0
  173. package/dist/lib/stream.js +188 -5
  174. package/dist/lib/stream.js.map +1 -1
  175. package/dist/lib/sync-queue.d.ts +179 -0
  176. package/dist/lib/sync-queue.js +197 -0
  177. package/dist/lib/sync-queue.js.map +1 -0
  178. package/dist/lib/types.d.ts +49 -1
  179. package/dist/lib/utilities.d.ts +281 -53
  180. package/dist/lib/utilities.js +298 -55
  181. package/dist/lib/utilities.js.map +1 -1
  182. package/install/check.js +2 -2
  183. package/package.json +37 -26
  184. package/dist/api/fmp4.js +0 -710
  185. package/dist/api/fmp4.js.map +0 -1
  186. package/dist/api/media-input.js +0 -1075
  187. package/dist/api/media-input.js.map +0 -1
  188. package/dist/api/media-output.js +0 -1040
  189. package/dist/api/media-output.js.map +0 -1
  190. package/dist/api/webrtc.d.ts +0 -664
  191. package/dist/api/webrtc.js +0 -1132
  192. package/dist/api/webrtc.js.map +0 -1
@@ -1,1075 +0,0 @@
1
- import { closeSync, openSync, readSync } from 'fs';
2
- import { open } from 'fs/promises';
3
- import { resolve } from 'path';
4
- import { AVFLAG_NONE, AVFMT_FLAG_CUSTOM_IO, AVFMT_FLAG_NONBLOCK, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO } from '../constants/constants.js';
5
- import { avGetPixFmtName, avGetSampleFmtName, Dictionary, FFmpegError, FormatContext, InputFormat, IOContext, Packet, Rational } from '../lib/index.js';
6
- import { IOStream } from './io-stream.js';
7
- /**
8
- * High-level media input for reading and demuxing media files.
9
- *
10
- * Provides simplified access to media streams, packets, and metadata.
11
- * Handles file opening, format detection, and stream information extraction.
12
- * Supports files, URLs, buffers, and raw data input with automatic cleanup.
13
- * Essential component for media processing pipelines and transcoding.
14
- *
15
- * @example
16
- * ```typescript
17
- * import { MediaInput } from 'node-av/api';
18
- *
19
- * // Open media file
20
- * await using input = await MediaInput.open('video.mp4');
21
- * console.log(`Format: ${input.formatName}`);
22
- * console.log(`Duration: ${input.duration}s`);
23
- *
24
- * // Process packets
25
- * for await (const packet of input.packets()) {
26
- * console.log(`Packet from stream ${packet.streamIndex}`);
27
- * packet.free();
28
- * }
29
- * ```
30
- *
31
- * @example
32
- * ```typescript
33
- * // From buffer
34
- * const buffer = await fs.readFile('video.mp4');
35
- * await using input = await MediaInput.open(buffer);
36
- *
37
- * // Access streams
38
- * const videoStream = input.video();
39
- * const audioStream = input.audio();
40
- * ```
41
- *
42
- * @see {@link MediaOutput} For writing media files
43
- * @see {@link Decoder} For decoding packets to frames
44
- * @see {@link FormatContext} For low-level API
45
- */
46
- export class MediaInput {
47
- formatContext;
48
- _streams = [];
49
- ioContext;
50
- isClosed = false;
51
- /**
52
- * @param formatContext - Opened format context
53
- *
54
- * @param ioContext - Optional IO context for custom I/O (e.g., from Buffer)
55
- *
56
- * @internal
57
- */
58
- constructor(formatContext, ioContext) {
59
- this.formatContext = formatContext;
60
- this.ioContext = ioContext;
61
- this._streams = formatContext.streams ?? [];
62
- }
63
- /**
64
- * Probe media format without fully opening the file.
65
- *
66
- * Detects format by analyzing file headers and content.
67
- * Useful for format validation before processing.
68
- *
69
- * Direct mapping to av_probe_input_format().
70
- *
71
- * @param input - File path or buffer to probe
72
- *
73
- * @returns Format information or null if unrecognized
74
- *
75
- * @example
76
- * ```typescript
77
- * const info = await MediaInput.probeFormat('video.mp4');
78
- * if (info) {
79
- * console.log(`Format: ${info.format}`);
80
- * console.log(`Confidence: ${info.confidence}%`);
81
- * }
82
- * ```
83
- *
84
- * @example
85
- * ```typescript
86
- * // Probe from buffer
87
- * const buffer = await fs.readFile('video.webm');
88
- * const info = await MediaInput.probeFormat(buffer);
89
- * console.log(`MIME type: ${info?.mimeType}`);
90
- * ```
91
- *
92
- * @see {@link InputFormat.probe} For low-level probing
93
- */
94
- static async probeFormat(input) {
95
- try {
96
- if (Buffer.isBuffer(input)) {
97
- // Probe from buffer
98
- const format = InputFormat.probe(input);
99
- if (!format) {
100
- return null;
101
- }
102
- return {
103
- format: format.name ?? 'unknown',
104
- longName: format.longName ?? undefined,
105
- extensions: format.extensions ?? undefined,
106
- mimeType: format.mimeType ?? undefined,
107
- confidence: 100, // Direct probe always has high confidence
108
- };
109
- }
110
- else {
111
- // For files, read first part and probe
112
- let fileHandle;
113
- try {
114
- fileHandle = await open(input, 'r');
115
- // Read first 64KB for probing
116
- const buffer = Buffer.alloc(65536);
117
- const { bytesRead } = await fileHandle.read(buffer, 0, 65536, 0);
118
- const probeBuffer = buffer.subarray(0, bytesRead);
119
- const format = InputFormat.probe(probeBuffer, input);
120
- if (!format) {
121
- return null;
122
- }
123
- return {
124
- format: format.name ?? 'unknown',
125
- longName: format.longName ?? undefined,
126
- extensions: format.extensions ?? undefined,
127
- mimeType: format.mimeType ?? undefined,
128
- confidence: 90, // File-based probe with filename hint
129
- };
130
- }
131
- catch {
132
- // If file reading fails, return null
133
- return null;
134
- }
135
- finally {
136
- await fileHandle?.close();
137
- }
138
- }
139
- }
140
- catch {
141
- return null;
142
- }
143
- }
144
- /**
145
- * Probe media format without fully opening the file synchronously.
146
- * Synchronous version of probeFormat.
147
- *
148
- * Detects format by analyzing file headers and content.
149
- * Useful for format validation before processing.
150
- *
151
- * Direct mapping to av_probe_input_format().
152
- *
153
- * @param input - File path or buffer to probe
154
- *
155
- * @returns Format information or null if unrecognized
156
- *
157
- * @example
158
- * ```typescript
159
- * const info = MediaInput.probeFormatSync('video.mp4');
160
- * if (info) {
161
- * console.log(`Format: ${info.format}`);
162
- * console.log(`Confidence: ${info.confidence}%`);
163
- * }
164
- * ```
165
- *
166
- * @example
167
- * ```typescript
168
- * // Probe from buffer
169
- * const buffer = fs.readFileSync('video.webm');
170
- * const info = MediaInput.probeFormatSync(buffer);
171
- * console.log(`MIME type: ${info?.mimeType}`);
172
- * ```
173
- *
174
- * @see {@link probeFormat} For async version
175
- */
176
- static probeFormatSync(input) {
177
- try {
178
- if (Buffer.isBuffer(input)) {
179
- // Probe from buffer
180
- const format = InputFormat.probe(input);
181
- if (!format) {
182
- return null;
183
- }
184
- return {
185
- format: format.name ?? 'unknown',
186
- longName: format.longName ?? undefined,
187
- extensions: format.extensions ?? undefined,
188
- mimeType: format.mimeType ?? undefined,
189
- confidence: 100, // Direct probe always has high confidence
190
- };
191
- }
192
- else {
193
- // For files, read first part and probe
194
- let fd;
195
- try {
196
- fd = openSync(input, 'r');
197
- // Read first 64KB for probing
198
- const buffer = Buffer.alloc(65536);
199
- const bytesRead = readSync(fd, buffer, 0, 65536, 0);
200
- const probeBuffer = buffer.subarray(0, bytesRead);
201
- const format = InputFormat.probe(probeBuffer, input);
202
- if (!format) {
203
- return null;
204
- }
205
- return {
206
- format: format.name ?? 'unknown',
207
- longName: format.longName ?? undefined,
208
- extensions: format.extensions ?? undefined,
209
- mimeType: format.mimeType ?? undefined,
210
- confidence: 90, // File-based probe with filename hint
211
- };
212
- }
213
- catch {
214
- // If file reading fails, return null
215
- return null;
216
- }
217
- finally {
218
- if (fd !== undefined)
219
- closeSync(fd);
220
- }
221
- }
222
- }
223
- catch {
224
- return null;
225
- }
226
- }
227
- static async open(input, options = {}) {
228
- // Check if input is raw data
229
- if (typeof input === 'object' && 'type' in input && ('width' in input || 'sampleRate' in input)) {
230
- // Build options for raw data
231
- const rawOptions = {
232
- bufferSize: options.bufferSize,
233
- format: options.format ?? (input.type === 'video' ? 'rawvideo' : 's16le'),
234
- options: {
235
- ...options.options,
236
- },
237
- };
238
- if (input.type === 'video') {
239
- rawOptions.options = {
240
- ...rawOptions.options,
241
- video_size: `${input.width}x${input.height}`,
242
- pixel_format: avGetPixFmtName(input.pixelFormat) ?? 'yuv420p',
243
- framerate: new Rational(input.frameRate.num, input.frameRate.den).toString(),
244
- };
245
- }
246
- else {
247
- rawOptions.options = {
248
- ...rawOptions.options,
249
- sample_rate: input.sampleRate,
250
- channels: input.channels,
251
- sample_fmt: avGetSampleFmtName(input.sampleFormat) ?? 's16le',
252
- };
253
- }
254
- // Open with the raw data source
255
- return MediaInput.open(input.input, rawOptions);
256
- }
257
- // Original implementation for non-raw data
258
- const formatContext = new FormatContext();
259
- let ioContext;
260
- let optionsDict = null;
261
- let inputFormat = null;
262
- try {
263
- // Create options dictionary if options are provided
264
- if (options.options) {
265
- optionsDict = Dictionary.fromObject(options.options);
266
- }
267
- // Find input format if specified
268
- if (options.format) {
269
- inputFormat = InputFormat.findInputFormat(options.format);
270
- if (!inputFormat) {
271
- throw new Error(`Input format '${options.format}' not found`);
272
- }
273
- }
274
- if (typeof input === 'string') {
275
- // File path or URL - resolve relative paths to absolute
276
- // Check if it's a URL (starts with protocol://) or a file path
277
- const isUrl = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(input);
278
- const resolvedInput = isUrl ? input : resolve(input);
279
- const ret = await formatContext.openInput(resolvedInput, inputFormat, optionsDict);
280
- FFmpegError.throwIfError(ret, 'Failed to open input');
281
- formatContext.setFlags(AVFMT_FLAG_NONBLOCK);
282
- }
283
- else if (Buffer.isBuffer(input)) {
284
- // Validate buffer is not empty
285
- if (input.length === 0) {
286
- throw new Error('Cannot open media from empty buffer');
287
- }
288
- // From buffer - allocate context first for custom I/O
289
- formatContext.allocContext();
290
- ioContext = IOStream.create(input, { bufferSize: options.bufferSize });
291
- formatContext.pb = ioContext;
292
- const ret = await formatContext.openInput('', inputFormat, optionsDict);
293
- FFmpegError.throwIfError(ret, 'Failed to open input from buffer');
294
- }
295
- else if (typeof input === 'object' && 'read' in input) {
296
- // Custom I/O with callbacks - format is required
297
- if (!options.format) {
298
- throw new Error('Format must be specified for custom I/O');
299
- }
300
- // Allocate context first for custom I/O
301
- formatContext.allocContext();
302
- // Setup custom I/O with callbacks
303
- ioContext = new IOContext();
304
- ioContext.allocContextWithCallbacks(options.bufferSize ?? 8192, 0, input.read, null, input.seek);
305
- formatContext.pb = ioContext;
306
- formatContext.flags = AVFMT_FLAG_CUSTOM_IO;
307
- const ret = await formatContext.openInput('', inputFormat, optionsDict);
308
- FFmpegError.throwIfError(ret, 'Failed to open input from custom I/O');
309
- }
310
- else {
311
- throw new TypeError('Invalid input type. Expected file path, URL, Buffer, or IOInputCallbacks');
312
- }
313
- // Find stream information
314
- const ret = await formatContext.findStreamInfo(null);
315
- FFmpegError.throwIfError(ret, 'Failed to find stream info');
316
- const mediaInput = new MediaInput(formatContext, ioContext);
317
- return mediaInput;
318
- }
319
- catch (error) {
320
- // Clean up only on error
321
- if (ioContext) {
322
- // Clear the pb reference first
323
- formatContext.pb = null;
324
- // Free the IOContext (for both custom I/O and buffer-based I/O)
325
- ioContext.freeContext();
326
- }
327
- // Clean up FormatContext
328
- await formatContext.closeInput();
329
- throw error;
330
- }
331
- finally {
332
- // Clean up options dictionary
333
- if (optionsDict) {
334
- optionsDict.free();
335
- }
336
- }
337
- }
338
- static openSync(input, options = {}) {
339
- // Check if input is raw data
340
- if (typeof input === 'object' && 'type' in input && ('width' in input || 'sampleRate' in input)) {
341
- // Build options for raw data
342
- const rawOptions = {
343
- bufferSize: options.bufferSize,
344
- format: options.format ?? (input.type === 'video' ? 'rawvideo' : 's16le'),
345
- options: {
346
- ...options.options,
347
- },
348
- };
349
- if (input.type === 'video') {
350
- rawOptions.options = {
351
- ...rawOptions.options,
352
- video_size: `${input.width}x${input.height}`,
353
- pixel_format: avGetPixFmtName(input.pixelFormat) ?? 'yuv420p',
354
- framerate: new Rational(input.frameRate.num, input.frameRate.den).toString(),
355
- };
356
- }
357
- else {
358
- rawOptions.options = {
359
- ...rawOptions.options,
360
- sample_rate: input.sampleRate,
361
- channels: input.channels,
362
- sample_fmt: avGetSampleFmtName(input.sampleFormat) ?? 's16le',
363
- };
364
- }
365
- // Open with the raw data source
366
- return MediaInput.openSync(input.input, rawOptions);
367
- }
368
- // Original implementation for non-raw data
369
- const formatContext = new FormatContext();
370
- let ioContext;
371
- let optionsDict = null;
372
- let inputFormat = null;
373
- try {
374
- // Create options dictionary if options are provided
375
- if (options.options) {
376
- optionsDict = Dictionary.fromObject(options.options);
377
- }
378
- // Find input format if specified
379
- if (options.format) {
380
- inputFormat = InputFormat.findInputFormat(options.format);
381
- if (!inputFormat) {
382
- throw new Error(`Input format '${options.format}' not found`);
383
- }
384
- }
385
- if (typeof input === 'string') {
386
- // File path or URL - resolve relative paths to absolute
387
- // Check if it's a URL (starts with protocol://) or a file path
388
- const isUrl = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(input);
389
- const resolvedInput = isUrl ? input : resolve(input);
390
- const ret = formatContext.openInputSync(resolvedInput, inputFormat, optionsDict);
391
- FFmpegError.throwIfError(ret, 'Failed to open input');
392
- formatContext.setFlags(AVFMT_FLAG_NONBLOCK);
393
- }
394
- else if (Buffer.isBuffer(input)) {
395
- // Validate buffer is not empty
396
- if (input.length === 0) {
397
- throw new Error('Cannot open media from empty buffer');
398
- }
399
- // From buffer - allocate context first for custom I/O
400
- formatContext.allocContext();
401
- ioContext = IOStream.create(input, { bufferSize: options.bufferSize });
402
- formatContext.pb = ioContext;
403
- const ret = formatContext.openInputSync('', inputFormat, optionsDict);
404
- FFmpegError.throwIfError(ret, 'Failed to open input from buffer');
405
- }
406
- else if (typeof input === 'object' && 'read' in input) {
407
- // Custom I/O with callbacks - format is required
408
- if (!options.format) {
409
- throw new Error('Format must be specified for custom I/O');
410
- }
411
- // Allocate context first for custom I/O
412
- formatContext.allocContext();
413
- // Setup custom I/O with callbacks
414
- ioContext = new IOContext();
415
- ioContext.allocContextWithCallbacks(options.bufferSize ?? 8192, 0, input.read, null, input.seek);
416
- formatContext.pb = ioContext;
417
- formatContext.flags = AVFMT_FLAG_CUSTOM_IO;
418
- const ret = formatContext.openInputSync('', inputFormat, optionsDict);
419
- FFmpegError.throwIfError(ret, 'Failed to open input from custom I/O');
420
- }
421
- else {
422
- throw new TypeError('Invalid input type. Expected file path, URL, Buffer, or IOInputCallbacks');
423
- }
424
- // Find stream information
425
- const ret = formatContext.findStreamInfoSync(null);
426
- FFmpegError.throwIfError(ret, 'Failed to find stream info');
427
- const mediaInput = new MediaInput(formatContext, ioContext);
428
- return mediaInput;
429
- }
430
- catch (error) {
431
- // Clean up only on error
432
- if (ioContext) {
433
- // Clear the pb reference first
434
- formatContext.pb = null;
435
- // Free the IOContext (for both custom I/O and buffer-based I/O)
436
- ioContext.freeContext();
437
- }
438
- // Clean up FormatContext
439
- formatContext.closeInputSync();
440
- throw error;
441
- }
442
- finally {
443
- // Clean up options dictionary
444
- if (optionsDict) {
445
- optionsDict.free();
446
- }
447
- }
448
- }
449
- /**
450
- * Check if input is open.
451
- *
452
- * @example
453
- * ```typescript
454
- * if (!input.isInputOpen) {
455
- * console.log('Input is not open');
456
- * }
457
- * ```
458
- */
459
- get isInputOpen() {
460
- return !this.isClosed;
461
- }
462
- /**
463
- * Get all streams in the media.
464
- *
465
- * @example
466
- * ```typescript
467
- * for (const stream of input.streams) {
468
- * console.log(`Stream ${stream.index}: ${stream.codecpar.codecType}`);
469
- * }
470
- * ```
471
- */
472
- get streams() {
473
- return this._streams;
474
- }
475
- /**
476
- * Get media duration in seconds.
477
- *
478
- * Returns 0 if duration is unknown or not available or input is closed.
479
- *
480
- * @example
481
- * ```typescript
482
- * console.log(`Duration: ${input.duration} seconds`);
483
- * ```
484
- */
485
- get duration() {
486
- if (this.isClosed) {
487
- return 0;
488
- }
489
- const duration = this.formatContext.duration;
490
- if (!duration || duration <= 0) {
491
- return 0;
492
- }
493
- // Convert from AV_TIME_BASE (microseconds) to seconds
494
- return Number(duration) / 1000000;
495
- }
496
- /**
497
- * Get media bitrate in kilobits per second.
498
- *
499
- * Returns 0 if bitrate is unknown or not available or input is closed.
500
- *
501
- * @example
502
- * ```typescript
503
- * console.log(`Bitrate: ${input.bitRate} kbps`);
504
- * ```
505
- */
506
- get bitRate() {
507
- if (this.isClosed) {
508
- return 0;
509
- }
510
- const bitrate = this.formatContext.bitRate;
511
- if (!bitrate || bitrate <= 0) {
512
- return 0;
513
- }
514
- // Convert from bits per second to kilobits per second
515
- return Number(bitrate) / 1000;
516
- }
517
- /**
518
- * Get media metadata.
519
- *
520
- * Returns all metadata tags as key-value pairs.
521
- *
522
- * @example
523
- * ```typescript
524
- * const metadata = input.metadata;
525
- * console.log(`Title: ${metadata.title}`);
526
- * console.log(`Artist: ${metadata.artist}`);
527
- * ```
528
- */
529
- get metadata() {
530
- if (this.isClosed) {
531
- return {};
532
- }
533
- return this.formatContext.metadata?.getAll() ?? {};
534
- }
535
- /**
536
- * Get format name.
537
- *
538
- * Returns 'unknown' if input is closed or format is not available.
539
- *
540
- * @example
541
- * ```typescript
542
- * console.log(`Format: ${input.formatName}`); // "mov,mp4,m4a,3gp,3g2,mj2"
543
- * ```
544
- */
545
- get formatName() {
546
- if (this.isClosed) {
547
- return 'unknown';
548
- }
549
- return this.formatContext.iformat?.name ?? 'unknown';
550
- }
551
- /**
552
- * Get format long name.
553
- *
554
- * Returns 'Unknown Format' if input is closed or format is not available.
555
- *
556
- * @example
557
- * ```typescript
558
- * console.log(`Format: ${input.formatLongName}`); // "QuickTime / MOV"
559
- * ```
560
- */
561
- get formatLongName() {
562
- if (this.isClosed) {
563
- return 'Unknown Format';
564
- }
565
- return this.formatContext.iformat?.longName ?? 'Unknown Format';
566
- }
567
- /**
568
- * Get MIME type of the input format.
569
- *
570
- * Returns null if input is closed or format is not available.
571
- *
572
- * @example
573
- * ```typescript
574
- * console.log(`MIME Type: ${input.mimeType}`); // "video/mp4"
575
- * ```
576
- */
577
- get mimeType() {
578
- if (this.isClosed) {
579
- return null;
580
- }
581
- return this.formatContext.iformat?.mimeType ?? null;
582
- }
583
- /**
584
- * Get input stream by index.
585
- *
586
- * Returns the stream at the specified index.
587
- *
588
- * @param index - Stream index
589
- *
590
- * @returns Stream or undefined if index is invalid
591
- *
592
- * @example
593
- * ```typescript
594
- * const input = await MediaInput.open('input.mp4');
595
- *
596
- * // Get the input stream to inspect codec parameters
597
- * const stream = input.getStream(1); // Get stream at index 1
598
- * if (stream) {
599
- * console.log(`Input codec: ${stream.codecpar.codecId}`);
600
- * }
601
- * ```
602
- *
603
- * @see {@link video} For getting video streams
604
- * @see {@link audio} For getting audio streams
605
- */
606
- getStream(index) {
607
- const streams = this.formatContext.streams;
608
- if (!streams || index < 0 || index >= streams.length) {
609
- return undefined;
610
- }
611
- return streams[index];
612
- }
613
- /**
614
- * Get video stream by index.
615
- *
616
- * Returns the nth video stream (0-based index).
617
- * Returns undefined if stream doesn't exist.
618
- *
619
- * @param index - Video stream index (default: 0)
620
- *
621
- * @returns Video stream or undefined
622
- *
623
- * @example
624
- * ```typescript
625
- * const videoStream = input.video();
626
- * if (videoStream) {
627
- * console.log(`Video: ${videoStream.codecpar.width}x${videoStream.codecpar.height}`);
628
- * }
629
- * ```
630
- *
631
- * @example
632
- * ```typescript
633
- * // Get second video stream
634
- * const secondVideo = input.video(1);
635
- * ```
636
- *
637
- * @see {@link audio} For audio streams
638
- * @see {@link findBestStream} For automatic selection
639
- */
640
- video(index = 0) {
641
- const streams = this._streams.filter((s) => s.codecpar.codecType === AVMEDIA_TYPE_VIDEO);
642
- return streams[index];
643
- }
644
- /**
645
- * Get audio stream by index.
646
- *
647
- * Returns the nth audio stream (0-based index).
648
- * Returns undefined if stream doesn't exist.
649
- *
650
- * @param index - Audio stream index (default: 0)
651
- *
652
- * @returns Audio stream or undefined
653
- *
654
- * @example
655
- * ```typescript
656
- * const audioStream = input.audio();
657
- * if (audioStream) {
658
- * console.log(`Audio: ${audioStream.codecpar.sampleRate}Hz`);
659
- * }
660
- * ```
661
- *
662
- * @example
663
- * ```typescript
664
- * // Get second audio stream
665
- * const secondAudio = input.audio(1);
666
- * ```
667
- *
668
- * @see {@link video} For video streams
669
- * @see {@link findBestStream} For automatic selection
670
- */
671
- audio(index = 0) {
672
- const streams = this._streams.filter((s) => s.codecpar.codecType === AVMEDIA_TYPE_AUDIO);
673
- return streams[index];
674
- }
675
- /**
676
- * Get input format details.
677
- *
678
- * Returns null if input is closed or format is not available.
679
- *
680
- * @returns Input format or null
681
- *
682
- * @example
683
- * ```typescript
684
- * const inputFormat = input.inputFormat;
685
- * if (inputFormat) {
686
- * console.log(`Input Format: ${inputFormat.name}`);
687
- * }
688
- * ```
689
- */
690
- inputFormat() {
691
- return this.formatContext.iformat;
692
- }
693
- /**
694
- * Find the best stream of a given type.
695
- *
696
- * Uses FFmpeg's stream selection algorithm.
697
- * Considers codec support, default flags, and quality.
698
- *
699
- * Direct mapping to av_find_best_stream().
700
- *
701
- * @param type - Media type to find
702
- *
703
- * @returns Best stream or undefined if not found or input is closed
704
- *
705
- * @example
706
- * ```typescript
707
- * import { AVMEDIA_TYPE_VIDEO } from 'node-av/constants';
708
- *
709
- * const bestVideo = input.findBestStream(AVMEDIA_TYPE_VIDEO);
710
- * if (bestVideo) {
711
- * const decoder = await Decoder.create(bestVideo);
712
- * }
713
- * ```
714
- *
715
- * @see {@link video} For direct video stream access
716
- * @see {@link audio} For direct audio stream access
717
- */
718
- findBestStream(type) {
719
- if (this.isClosed) {
720
- return undefined;
721
- }
722
- const bestStreamIndex = this.formatContext.findBestStream(type);
723
- return this._streams.find((s) => s.index === bestStreamIndex);
724
- }
725
- /**
726
- * Read packets from media as async generator.
727
- *
728
- * Yields demuxed packets for processing.
729
- * Automatically handles packet memory management.
730
- * Optionally filters packets by stream index.
731
- *
732
- * Direct mapping to av_read_frame().
733
- *
734
- * @param index - Optional stream index to filter
735
- *
736
- * @yields {Packet} Demuxed packets (must be freed by caller)
737
- *
738
- * @throws {Error} If packet cloning fails
739
- *
740
- * @example
741
- * ```typescript
742
- * // Read all packets
743
- * for await (const packet of input.packets()) {
744
- * console.log(`Packet: stream=${packet.streamIndex}, pts=${packet.pts}`);
745
- * packet.free();
746
- * }
747
- * ```
748
- *
749
- * @example
750
- * ```typescript
751
- * // Read only video packets
752
- * const videoStream = input.video();
753
- * for await (const packet of input.packets(videoStream.index)) {
754
- * // Process video packet
755
- * packet.free();
756
- * }
757
- * ```
758
- *
759
- * @see {@link Decoder.frames} For decoding packets
760
- */
761
- async *packets(index) {
762
- const packet = new Packet();
763
- packet.alloc();
764
- try {
765
- while (!this.isClosed) {
766
- const ret = await this.formatContext.readFrame(packet);
767
- if (ret < 0) {
768
- // End of file or error
769
- break;
770
- }
771
- if (index === undefined || packet.streamIndex === index) {
772
- // Clone the packet for the user
773
- // This creates a new Packet object that shares the same data buffer
774
- // through reference counting. The data won't be freed until both
775
- // the original and the clone are unreferenced.
776
- const cloned = packet.clone();
777
- if (!cloned) {
778
- throw new Error('Failed to clone packet (out of memory)');
779
- }
780
- yield cloned;
781
- }
782
- // Unreference the original packet's data buffer
783
- // This allows us to reuse the packet object for the next readFrame()
784
- // The data itself is still alive because the clone has a reference
785
- packet.unref();
786
- }
787
- }
788
- finally {
789
- packet.free();
790
- }
791
- }
792
- /**
793
- * Read packets from media as generator synchronously.
794
- * Synchronous version of packets.
795
- *
796
- * Yields demuxed packets for processing.
797
- * Automatically handles packet memory management.
798
- * Optionally filters packets by stream index.
799
- *
800
- * Direct mapping to av_read_frame().
801
- *
802
- * @param index - Optional stream index to filter
803
- *
804
- * @yields {Packet} Demuxed packets (must be freed by caller)
805
- *
806
- * @throws {Error} If packet cloning fails
807
- *
808
- * @example
809
- * ```typescript
810
- * // Read all packets
811
- * for (const packet of input.packetsSync()) {
812
- * console.log(`Packet: stream=${packet.streamIndex}, pts=${packet.pts}`);
813
- * packet.free();
814
- * }
815
- * ```
816
- *
817
- * @example
818
- * ```typescript
819
- * // Read only video packets
820
- * const videoStream = input.video();
821
- * for (const packet of input.packetsSync(videoStream.index)) {
822
- * // Process video packet
823
- * packet.free();
824
- * }
825
- * ```
826
- *
827
- * @see {@link packets} For async version
828
- */
829
- *packetsSync(index) {
830
- const packet = new Packet();
831
- packet.alloc();
832
- try {
833
- while (!this.isClosed) {
834
- const ret = this.formatContext.readFrameSync(packet);
835
- if (ret < 0) {
836
- // End of file or error
837
- break;
838
- }
839
- if (index === undefined || packet.streamIndex === index) {
840
- // Clone the packet for the user
841
- // This creates a new Packet object that shares the same data buffer
842
- // through reference counting. The data won't be freed until both
843
- // the original and the clone are unreferenced.
844
- const cloned = packet.clone();
845
- if (!cloned) {
846
- throw new Error('Failed to clone packet (out of memory)');
847
- }
848
- yield cloned;
849
- }
850
- // Unreference the original packet's data buffer
851
- // This allows us to reuse the packet object for the next readFrame()
852
- // The data itself is still alive because the clone has a reference
853
- packet.unref();
854
- }
855
- }
856
- finally {
857
- packet.free();
858
- }
859
- }
860
- /**
861
- * Seek to timestamp in media.
862
- *
863
- * Seeks to the specified position in seconds.
864
- * Can seek in specific stream or globally.
865
- *
866
- * Direct mapping to av_seek_frame().
867
- *
868
- * @param timestamp - Target position in seconds
869
- *
870
- * @param streamIndex - Stream index or -1 for global (default: -1)
871
- *
872
- * @param flags - Seek flags (default: AVFLAG_NONE)
873
- *
874
- * @returns 0 on success, negative on error
875
- *
876
- * @throws {Error} If input is closed
877
- *
878
- * @example
879
- * ```typescript
880
- * // Seek to 30 seconds
881
- * const ret = await input.seek(30);
882
- * FFmpegError.throwIfError(ret, 'seek failed');
883
- * ```
884
- *
885
- * @example
886
- * ```typescript
887
- * import { AVSEEK_FLAG_BACKWARD } from 'node-av/constants';
888
- *
889
- * // Seek to keyframe before 60 seconds
890
- * await input.seek(60, -1, AVSEEK_FLAG_BACKWARD);
891
- * ```
892
- *
893
- * @see {@link AVSeekFlag} For seek flags
894
- */
895
- async seek(timestamp, streamIndex = -1, flags = AVFLAG_NONE) {
896
- if (this.isClosed) {
897
- throw new Error('Cannot seek on closed input');
898
- }
899
- // Convert seconds to AV_TIME_BASE
900
- const ts = BigInt(Math.floor(timestamp * 1000000));
901
- return this.formatContext.seekFrame(streamIndex, ts, flags);
902
- }
903
- /**
904
- * Seek to timestamp in media synchronously.
905
- * Synchronous version of seek.
906
- *
907
- * Seeks to the specified position in seconds.
908
- * Can seek in specific stream or globally.
909
- *
910
- * Direct mapping to av_seek_frame().
911
- *
912
- * @param timestamp - Target position in seconds
913
- *
914
- * @param streamIndex - Stream index or -1 for global (default: -1)
915
- *
916
- * @param flags - Seek flags (default: AVFLAG_NONE)
917
- *
918
- * @returns 0 on success, negative on error
919
- *
920
- * @throws {Error} If input is closed
921
- *
922
- * @example
923
- * ```typescript
924
- * // Seek to 30 seconds
925
- * const ret = input.seekSync(30);
926
- * FFmpegError.throwIfError(ret, 'seek failed');
927
- * ```
928
- *
929
- * @example
930
- * ```typescript
931
- * import { AVSEEK_FLAG_BACKWARD } from 'node-av/constants';
932
- *
933
- * // Seek to keyframe before 60 seconds
934
- * input.seekSync(60, -1, AVSEEK_FLAG_BACKWARD);
935
- * ```
936
- *
937
- * @see {@link seek} For async version
938
- */
939
- seekSync(timestamp, streamIndex = -1, flags = AVFLAG_NONE) {
940
- if (this.isClosed) {
941
- throw new Error('Cannot seek on closed input');
942
- }
943
- // Convert seconds to AV_TIME_BASE
944
- const ts = BigInt(Math.floor(timestamp * 1000000));
945
- return this.formatContext.seekFrameSync(streamIndex, ts, flags);
946
- }
947
- /**
948
- * Close media input and free resources.
949
- *
950
- * Releases format context and I/O context.
951
- * Safe to call multiple times.
952
- * Automatically called by Symbol.asyncDispose.
953
- *
954
- * Direct mapping to avformat_close_input().
955
- *
956
- * @example
957
- * ```typescript
958
- * const input = await MediaInput.open('video.mp4');
959
- * try {
960
- * // Use input
961
- * } finally {
962
- * await input.close();
963
- * }
964
- * ```
965
- *
966
- * @see {@link Symbol.asyncDispose} For automatic cleanup
967
- */
968
- async close() {
969
- if (this.isClosed) {
970
- return;
971
- }
972
- this.isClosed = true;
973
- // IMPORTANT: Clear pb reference FIRST to prevent use-after-free
974
- if (this.ioContext) {
975
- this.formatContext.pb = null;
976
- }
977
- // Close FormatContext
978
- await this.formatContext.closeInput();
979
- // NOW we can safely free the IOContext
980
- if (this.ioContext) {
981
- this.ioContext.freeContext();
982
- this.ioContext = undefined;
983
- }
984
- }
985
- /**
986
- * Close media input and free resources synchronously.
987
- * Synchronous version of close.
988
- *
989
- * Releases format context and I/O context.
990
- * Safe to call multiple times.
991
- * Automatically called by Symbol.dispose.
992
- *
993
- * Direct mapping to avformat_close_input().
994
- *
995
- * @example
996
- * ```typescript
997
- * const input = MediaInput.openSync('video.mp4');
998
- * try {
999
- * // Use input
1000
- * } finally {
1001
- * input.closeSync();
1002
- * }
1003
- * ```
1004
- *
1005
- * @see {@link close} For async version
1006
- */
1007
- closeSync() {
1008
- if (this.isClosed) {
1009
- return;
1010
- }
1011
- this.isClosed = true;
1012
- // IMPORTANT: Clear pb reference FIRST to prevent use-after-free
1013
- if (this.ioContext) {
1014
- this.formatContext.pb = null;
1015
- }
1016
- // Close FormatContext
1017
- this.formatContext.closeInputSync();
1018
- // NOW we can safely free the IOContext
1019
- if (this.ioContext) {
1020
- this.ioContext.freeContext();
1021
- this.ioContext = undefined;
1022
- }
1023
- }
1024
- /**
1025
- * Get underlying format context.
1026
- *
1027
- * Returns the internal format context for advanced operations.
1028
- *
1029
- * @returns Format context
1030
- *
1031
- * @internal
1032
- */
1033
- getFormatContext() {
1034
- return this.formatContext;
1035
- }
1036
- /**
1037
- * Dispose of media input.
1038
- *
1039
- * Implements AsyncDisposable interface for automatic cleanup.
1040
- * Equivalent to calling close().
1041
- *
1042
- * @example
1043
- * ```typescript
1044
- * {
1045
- * await using input = await MediaInput.open('video.mp4');
1046
- * // Process media...
1047
- * } // Automatically closed
1048
- * ```
1049
- *
1050
- * @see {@link close} For manual cleanup
1051
- */
1052
- async [Symbol.asyncDispose]() {
1053
- await this.close();
1054
- }
1055
- /**
1056
- * Dispose of media input synchronously.
1057
- *
1058
- * Implements Disposable interface for automatic cleanup.
1059
- * Equivalent to calling closeSync().
1060
- *
1061
- * @example
1062
- * ```typescript
1063
- * {
1064
- * using input = MediaInput.openSync('video.mp4');
1065
- * // Process media...
1066
- * } // Automatically closed
1067
- * ```
1068
- *
1069
- * @see {@link closeSync} For manual cleanup
1070
- */
1071
- [Symbol.dispose]() {
1072
- this.closeSync();
1073
- }
1074
- }
1075
- //# sourceMappingURL=media-input.js.map