bare-media 2.1.0 → 2.3.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.
package/README.md CHANGED
@@ -115,9 +115,34 @@ Extracts frames from a video in RGBA
115
115
 
116
116
  | Parameter | Type | Description |
117
117
  | ----------------- | ------ | ------------------------------ |
118
- | `fd` | number | File descriptor |
119
118
  | `opts.frameIndex` | number | Number of the frame to extract |
120
119
 
120
+ ### transcode()
121
+
122
+ Transcode a media file to a different format
123
+
124
+ | Parameter | Type | Description |
125
+ | ------------- | ------ | ------------------------------------------------------------------- |
126
+ | `opts.format` | string | Output format name (e.g., `mp4`, `webm`, `matroska`). Default `mp4` |
127
+ | `opts.width` | number | Width of the output video |
128
+ | `opts.height` | number | Height of the output video |
129
+
130
+ **Supported formats**: `mp4` (VP9+Opus), `webm` (VP8+Opus), `matroska`/`mkv` (VP9+Opus)
131
+
132
+ #### Example
133
+
134
+ ```javascript
135
+ import { video } from 'bare-media'
136
+
137
+ for await (const chunk of video('input.mkv').transcode({
138
+ format: 'mp4',
139
+ width: 1280,
140
+ height: 720
141
+ })) {
142
+ console.log('Received chunk:', chunk.buffer.length)
143
+ }
144
+ ```
145
+
121
146
  ## Supported Types
122
147
 
123
148
  Helpers to check supported media types are exposed in `bare-media/types`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-media",
3
- "version": "2.1.0",
3
+ "version": "2.3.0-0",
4
4
  "description": "A set of media APIs for Bare",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -17,7 +17,8 @@
17
17
  "format": "prettier --write .",
18
18
  "format:check": "prettier --check .",
19
19
  "lint": "npm run format:check && lunte",
20
- "test": "brittle-bare test/index.js"
20
+ "test": "brittle-bare test/index.js",
21
+ "example": "bare examples/index.js"
21
22
  },
22
23
  "keywords": [],
23
24
  "author": "Holepunch Inc",
@@ -33,7 +34,7 @@
33
34
  "dependencies": {
34
35
  "bare-bmp": "^1.0.0",
35
36
  "bare-fetch": "^2.4.1",
36
- "bare-ffmpeg": "^1.0.0",
37
+ "bare-ffmpeg": "^1.1.0",
37
38
  "bare-fs": "^4.1.5",
38
39
  "bare-gif": "^1.1.2",
39
40
  "bare-heif": "^1.0.5",
@@ -41,14 +42,17 @@
41
42
  "bare-image-resample": "^1.0.1",
42
43
  "bare-jpeg": "^1.0.1",
43
44
  "bare-png": "^1.0.2",
45
+ "bare-svg": "^1.0.1",
44
46
  "bare-tiff": "^1.0.1",
45
47
  "bare-webp": "^1.0.3",
46
- "get-file-format": "^1.0.1",
48
+ "get-file-format": "^1.1.0",
47
49
  "get-mime-type": "^2.0.1"
48
50
  },
49
51
  "devDependencies": {
50
52
  "b4a": "^1.7.3",
51
53
  "bare-os": "^3.6.2",
54
+ "bare-path": "^3.0.0",
55
+ "bare-process": "^4.2.2",
52
56
  "brittle": "^3.16.3",
53
57
  "corestore": "^7.4.5",
54
58
  "hyperblobs": "^2.8.0",
package/src/codecs.js CHANGED
@@ -9,6 +9,7 @@ export const codecs = {
9
9
  [IMAGE.JPEG]: () => import('bare-jpeg'),
10
10
  [IMAGE.JPG]: () => import('bare-jpeg'),
11
11
  [IMAGE.PNG]: () => import('bare-png'),
12
+ [IMAGE.SVG_XML]: () => import('bare-svg'),
12
13
  [IMAGE.TIF]: () => import('bare-tiff'),
13
14
  [IMAGE.TIFF]: () => import('bare-tiff'),
14
15
  [IMAGE.VND_MS_ICON]: () => import('bare-ico'),
@@ -1,5 +1,6 @@
1
1
  import fs from 'bare-fs'
2
2
  import ffmpeg from 'bare-ffmpeg'
3
+ import { Transcoder } from './transcoder.js'
3
4
 
4
5
  function extractFrames(fd, opts = {}) {
5
6
  const { frameIndex } = opts
@@ -89,6 +90,18 @@ function extractFrames(fd, opts = {}) {
89
90
  return result
90
91
  }
91
92
 
93
+ async function* transcode(fd, opts = {}) {
94
+ const transcoder = new Transcoder(fd, {
95
+ outputParameters: {
96
+ format: opts.format,
97
+ width: opts.width,
98
+ height: opts.height
99
+ },
100
+ bufferSize: opts.bufferSize
101
+ })
102
+ yield* transcoder.transcode()
103
+ }
104
+
92
105
  class VideoPipeline {
93
106
  constructor(input) {
94
107
  this.input = input
@@ -101,6 +114,15 @@ class VideoPipeline {
101
114
  fs.closeSync(fd)
102
115
  return result
103
116
  }
117
+
118
+ async *transcode(opts) {
119
+ const fd = fs.openSync(this.input, 'r')
120
+ try {
121
+ yield* transcode(fd, opts)
122
+ } finally {
123
+ fs.closeSync(fd)
124
+ }
125
+ }
104
126
  }
105
127
 
106
128
  function video(input) {
@@ -108,5 +130,6 @@ function video(input) {
108
130
  }
109
131
 
110
132
  video.extractFrames = extractFrames
133
+ video.transcode = transcode
111
134
 
112
135
  export { video }
@@ -0,0 +1,538 @@
1
+ import fs from 'bare-fs'
2
+ import b4a from 'b4a'
3
+ import ffmpeg from 'bare-ffmpeg'
4
+
5
+ const { VIDEO, AUDIO } = ffmpeg.constants.mediaTypes
6
+
7
+ class FormatRegistry {
8
+ #formats = new Map()
9
+
10
+ register(formatName, config) {
11
+ this.#formats.set(formatName, {
12
+ video: config.video,
13
+ audio: config.audio,
14
+ muxer: config.muxer || {}
15
+ })
16
+ }
17
+
18
+ getVideoConfig(formatName) {
19
+ const format = this.#formats.get(formatName)
20
+ if (!format?.video) {
21
+ throw new Error(`Unsupported video output format: ${formatName}`)
22
+ }
23
+ return format.video
24
+ }
25
+
26
+ getAudioConfig(formatName) {
27
+ const format = this.#formats.get(formatName)
28
+ if (!format?.audio) {
29
+ throw new Error(`Unsupported audio output format: ${formatName}`)
30
+ }
31
+ return format.audio
32
+ }
33
+
34
+ getMuxerOptions(formatName) {
35
+ const format = this.#formats.get(formatName)
36
+ return format?.muxer || {}
37
+ }
38
+
39
+ hasFormat(formatName) {
40
+ return this.#formats.has(formatName)
41
+ }
42
+ }
43
+
44
+ const formatRegistry = new FormatRegistry()
45
+
46
+ formatRegistry.register('webm', {
47
+ video: {
48
+ id: ffmpeg.constants.codecs.VP8,
49
+ format: ffmpeg.constants.pixelFormats.YUV420P,
50
+ encoder: 'libvpx'
51
+ },
52
+ audio: {
53
+ id: ffmpeg.constants.codecs.OPUS,
54
+ format: ffmpeg.constants.sampleFormats.FLTP,
55
+ sampleRate: 48000,
56
+ encoder: 'libopus'
57
+ },
58
+ muxer: { live: '1' }
59
+ })
60
+
61
+ formatRegistry.register('mp4', {
62
+ video: {
63
+ id: ffmpeg.constants.codecs.VP9,
64
+ format: ffmpeg.constants.pixelFormats.YUV420P,
65
+ encoder: 'libvpx-vp9'
66
+ },
67
+ audio: {
68
+ id: ffmpeg.constants.codecs.OPUS,
69
+ format: ffmpeg.constants.sampleFormats.FLTP,
70
+ sampleRate: 48000,
71
+ encoder: 'libopus'
72
+ },
73
+ muxer: { movflags: 'frag_keyframe+empty_moov+default_base_moof' }
74
+ })
75
+
76
+ formatRegistry.register('matroska', {
77
+ video: {
78
+ id: ffmpeg.constants.codecs.VP9,
79
+ format: ffmpeg.constants.pixelFormats.YUV420P,
80
+ encoder: 'libvpx-vp9'
81
+ },
82
+ audio: {
83
+ id: ffmpeg.constants.codecs.OPUS,
84
+ format: ffmpeg.constants.sampleFormats.FLTP,
85
+ sampleRate: 48000,
86
+ encoder: 'libopus'
87
+ },
88
+ muxer: { live: '1' }
89
+ })
90
+
91
+ formatRegistry.register('mkv', {
92
+ video: {
93
+ id: ffmpeg.constants.codecs.VP9,
94
+ format: ffmpeg.constants.pixelFormats.YUV420P,
95
+ encoder: 'libvpx-vp9'
96
+ },
97
+ audio: {
98
+ id: ffmpeg.constants.codecs.OPUS,
99
+ format: ffmpeg.constants.sampleFormats.FLTP,
100
+ sampleRate: 48000,
101
+ encoder: 'libopus'
102
+ },
103
+ muxer: { live: '1' }
104
+ })
105
+
106
+ class TranscodeStreamConfig {
107
+ static create(inputStream, outputFormatContext, containerFormat, outputParameters) {
108
+ const config = new TranscodeStreamConfig(
109
+ inputStream,
110
+ outputFormatContext,
111
+ containerFormat,
112
+ outputParameters
113
+ )
114
+ return config.#initialize() ? config : null
115
+ }
116
+
117
+ constructor(inputStream, outputFormatContext, containerFormat, outputParameters) {
118
+ this.inputStream = inputStream
119
+ this.outputFormatContext = outputFormatContext
120
+ this.containerFormat = containerFormat
121
+ this.outputParameters = outputParameters
122
+ this.codecType = inputStream.codecParameters.type
123
+
124
+ this.outputStream = null
125
+ this.decoder = null
126
+ this.encoder = null
127
+ this.rescaler = null
128
+ this.resampler = null
129
+ this.fifo = null
130
+ this.fifoFrame = null
131
+ this.samplesWritten = 0
132
+ this.nextVideoPts = 0
133
+ this.lastWidth = null
134
+ this.lastHeight = null
135
+ this.lastFormat = null
136
+ }
137
+
138
+ isVideo() {
139
+ return this.codecType === VIDEO
140
+ }
141
+
142
+ isAudio() {
143
+ return this.codecType === AUDIO
144
+ }
145
+
146
+ getConfig() {
147
+ return this.isVideo()
148
+ ? formatRegistry.getVideoConfig(this.containerFormat)
149
+ : formatRegistry.getAudioConfig(this.containerFormat)
150
+ }
151
+
152
+ #initialize() {
153
+ this.decoder = this.#createDecoder()
154
+ if (!this.decoder) return false
155
+
156
+ this.outputStream = this.outputFormatContext.createStream()
157
+ this.#configureOutputStream(this.outputStream, this.decoder)
158
+
159
+ this.encoder = this.#createEncoder(this.outputStream, this.decoder)
160
+ this.outputStream.codecParameters.fromContext(this.encoder)
161
+
162
+ return true
163
+ }
164
+
165
+ #createDecoder() {
166
+ const decoderContext = this.inputStream.decoder()
167
+ try {
168
+ decoderContext.open()
169
+ return decoderContext
170
+ } catch (err) {
171
+ console.warn(`Failed to open decoder for stream ${this.inputStream.index}: ${err.message}`)
172
+ decoderContext.destroy()
173
+ return null
174
+ }
175
+ }
176
+
177
+ #configureOutputStream(outputStream, decoder) {
178
+ const config = this.getConfig()
179
+
180
+ outputStream.codecParameters.type = this.codecType
181
+ outputStream.codecParameters.id = config.id
182
+ outputStream.codecParameters.format = config.format
183
+
184
+ if (this.isVideo()) {
185
+ outputStream.codecParameters.width = this.outputParameters?.width || decoder.width
186
+ outputStream.codecParameters.height = this.outputParameters?.height || decoder.height
187
+ outputStream.timeBase = new ffmpeg.Rational(1, 90000)
188
+ } else {
189
+ outputStream.codecParameters.sampleRate = config.sampleRate
190
+ outputStream.codecParameters.channelLayout = decoder.channelLayout
191
+ outputStream.timeBase = new ffmpeg.Rational(1, config.sampleRate)
192
+ }
193
+ }
194
+
195
+ #createEncoder(outputStream, decoder) {
196
+ const config = this.getConfig()
197
+ const encoder = new ffmpeg.CodecContext(new ffmpeg.Encoder(config.encoder))
198
+ outputStream.codecParameters.toContext(encoder)
199
+
200
+ if (this.isVideo()) {
201
+ this.#configureVideoEncoder(encoder, outputStream, decoder)
202
+ } else {
203
+ this.#configureAudioEncoder(encoder, outputStream)
204
+ }
205
+
206
+ if (this.outputFormatContext.outputFormat.flags & ffmpeg.constants.formatFlags.GLOBALHEADER) {
207
+ encoder.flags |= ffmpeg.constants.codecFlags.GLOBAL_HEADER
208
+ }
209
+
210
+ const encoderOptions = this.isVideo()
211
+ ? ffmpeg.Dictionary.from({ allow_sw: '1' })
212
+ : new ffmpeg.Dictionary()
213
+
214
+ encoder.open(encoderOptions)
215
+ return encoder
216
+ }
217
+
218
+ #configureVideoEncoder(encoder, outputStream, decoder) {
219
+ encoder.timeBase = outputStream.timeBase
220
+ encoder.width = outputStream.codecParameters.width
221
+ encoder.height = outputStream.codecParameters.height
222
+ encoder.pixelFormat = outputStream.codecParameters.format
223
+
224
+ if (decoder.frameRate && decoder.frameRate.valid) {
225
+ encoder.frameRate = decoder.frameRate
226
+ } else {
227
+ encoder.frameRate = new ffmpeg.Rational(30, 1)
228
+ }
229
+ encoder.gopSize = 30
230
+ }
231
+
232
+ #configureAudioEncoder(encoder, outputStream) {
233
+ encoder.timeBase = outputStream.timeBase
234
+ encoder.sampleRate = outputStream.codecParameters.sampleRate
235
+ encoder.channelLayout = outputStream.codecParameters.channelLayout
236
+ encoder.sampleFormat = outputStream.codecParameters.format
237
+ }
238
+ }
239
+
240
+ class VideoFrameProcessor {
241
+ constructor(transcoder) {
242
+ this.transcoder = transcoder
243
+ }
244
+
245
+ process(frame, config, packet) {
246
+ const { encoder, outputStream } = config
247
+
248
+ if (
249
+ !config.rescaler ||
250
+ config.lastWidth !== frame.width ||
251
+ config.lastHeight !== frame.height ||
252
+ config.lastFormat !== frame.format
253
+ ) {
254
+ if (config.rescaler) config.rescaler.destroy()
255
+
256
+ config.rescaler = new ffmpeg.Scaler(
257
+ frame.format,
258
+ frame.width,
259
+ frame.height,
260
+ encoder.pixelFormat,
261
+ encoder.width,
262
+ encoder.height
263
+ )
264
+
265
+ config.lastWidth = frame.width
266
+ config.lastHeight = frame.height
267
+ config.lastFormat = frame.format
268
+ }
269
+
270
+ const outFrame = new ffmpeg.Frame()
271
+ outFrame.format = encoder.pixelFormat
272
+ outFrame.width = encoder.width
273
+ outFrame.height = encoder.height
274
+ outFrame.alloc()
275
+ outFrame.copyProperties(frame)
276
+
277
+ config.rescaler.scale(frame, outFrame)
278
+
279
+ outFrame.pts = config.nextVideoPts
280
+ const frameDuration =
281
+ (encoder.timeBase.denominator * encoder.frameRate.denominator) /
282
+ (encoder.timeBase.numerator * encoder.frameRate.numerator)
283
+ config.nextVideoPts += frameDuration
284
+
285
+ this.transcoder._encodeAndWrite(encoder, outFrame, outputStream, packet)
286
+
287
+ outFrame.destroy()
288
+ }
289
+ }
290
+
291
+ class AudioFrameProcessor {
292
+ constructor(transcoder) {
293
+ this.transcoder = transcoder
294
+ }
295
+
296
+ process(frame, config, packet) {
297
+ const { encoder, outputStream } = config
298
+
299
+ if (!config.resampler) {
300
+ config.resampler = new ffmpeg.Resampler(
301
+ frame.sampleRate,
302
+ frame.channelLayout,
303
+ frame.format,
304
+ encoder.sampleRate,
305
+ encoder.channelLayout,
306
+ encoder.sampleFormat
307
+ )
308
+ }
309
+
310
+ if (!config.fifo) {
311
+ config.fifo = new ffmpeg.AudioFIFO(
312
+ encoder.sampleFormat,
313
+ encoder.channelLayout.nbChannels,
314
+ encoder.frameSize
315
+ )
316
+ config.fifoFrame = new ffmpeg.Frame()
317
+ config.fifoFrame.format = encoder.sampleFormat
318
+ config.fifoFrame.channelLayout = encoder.channelLayout
319
+ config.fifoFrame.sampleRate = encoder.sampleRate
320
+ }
321
+
322
+ const outFrame = new ffmpeg.Frame()
323
+ outFrame.format = encoder.sampleFormat
324
+ outFrame.channelLayout = encoder.channelLayout
325
+ outFrame.sampleRate = encoder.sampleRate
326
+
327
+ const outSamples = Math.ceil((frame.nbSamples * encoder.sampleRate) / frame.sampleRate) + 32
328
+ outFrame.nbSamples = outSamples
329
+ outFrame.alloc()
330
+
331
+ const convertedSamples = config.resampler.convert(frame, outFrame)
332
+ outFrame.nbSamples = convertedSamples
333
+
334
+ config.fifo.write(outFrame)
335
+ outFrame.destroy()
336
+
337
+ const frameSize = encoder.frameSize
338
+ while (config.fifo.size >= frameSize) {
339
+ config.fifoFrame.nbSamples = frameSize
340
+ config.fifoFrame.alloc()
341
+
342
+ config.fifo.read(config.fifoFrame, frameSize)
343
+
344
+ config.fifoFrame.pts = config.samplesWritten
345
+ config.samplesWritten += config.fifoFrame.nbSamples
346
+
347
+ this.transcoder._encodeAndWrite(encoder, config.fifoFrame, outputStream, packet)
348
+ }
349
+ }
350
+
351
+ flush(config, packet) {
352
+ if (config.fifo && config.fifo.size > 0) {
353
+ const remaining = config.fifo.size
354
+ config.fifoFrame.nbSamples = remaining
355
+ config.fifoFrame.alloc()
356
+ config.fifo.read(config.fifoFrame, remaining)
357
+ config.fifoFrame.pts = config.samplesWritten
358
+ config.samplesWritten += config.fifoFrame.nbSamples
359
+ this.transcoder._encodeAndWrite(config.encoder, config.fifoFrame, config.outputStream, packet)
360
+ }
361
+ }
362
+ }
363
+
364
+ class Transcoder {
365
+ constructor(fd, opts = {}) {
366
+ this.fd = fd
367
+ this.outputParameters = opts.outputParameters || {}
368
+ this.bufferSize = opts.bufferSize || 32 * 1024
369
+
370
+ this.chunks = []
371
+ this.inputFormatContext = null
372
+ this.outputFormatContext = null
373
+ this.configs = []
374
+ this.containerFormat = null
375
+
376
+ this.videoProcessor = new VideoFrameProcessor(this)
377
+ this.audioProcessor = new AudioFrameProcessor(this)
378
+ }
379
+
380
+ async *transcode() {
381
+ try {
382
+ this.#setupIOContexts()
383
+ this.#discoverAndConfigureStreams()
384
+ this.#configureOutput()
385
+ this.#processFrames()
386
+ this.#finalize()
387
+ } finally {
388
+ this.#cleanup()
389
+ }
390
+
391
+ for (const chunk of this.chunks) {
392
+ yield { buffer: chunk }
393
+ }
394
+ }
395
+
396
+ #setupIOContexts() {
397
+ const fileSize = fs.fstatSync(this.fd).size
398
+ let offset = 0
399
+
400
+ const inIO = new ffmpeg.IOContext(4096, {
401
+ onread: (buffer, requested) => {
402
+ const read = fs.readSync(this.fd, buffer, 0, requested, offset)
403
+ if (read === 0) return 0
404
+ offset += read
405
+ return read
406
+ },
407
+ onseek: (o, whence) => {
408
+ if (whence === ffmpeg.constants.seek.SIZE) return fileSize
409
+ if (whence === ffmpeg.constants.seek.SET) offset = o
410
+ else if (whence === ffmpeg.constants.seek.CUR) offset += o
411
+ else if (whence === ffmpeg.constants.seek.END) offset = fileSize + o
412
+ else return -1
413
+ return offset
414
+ }
415
+ })
416
+
417
+ this.inputFormatContext = new ffmpeg.InputFormatContext(inIO)
418
+
419
+ const outIO = new ffmpeg.IOContext(this.bufferSize, {
420
+ onwrite: (chunk) => {
421
+ this.chunks.push(b4a.from(chunk))
422
+ return chunk.length
423
+ }
424
+ })
425
+
426
+ this.containerFormat = this.outputParameters?.format || 'mp4'
427
+
428
+ if (!formatRegistry.hasFormat(this.containerFormat)) {
429
+ throw new Error(`Unsupported output format: ${this.containerFormat}`)
430
+ }
431
+
432
+ this.outputFormatContext = new ffmpeg.OutputFormatContext(this.containerFormat, outIO)
433
+ }
434
+
435
+ #discoverAndConfigureStreams() {
436
+ for (const inputStream of this.inputFormatContext.streams) {
437
+ const codecType = inputStream.codecParameters.type
438
+
439
+ if (codecType !== VIDEO && codecType !== AUDIO) {
440
+ continue
441
+ }
442
+
443
+ const config = TranscodeStreamConfig.create(
444
+ inputStream,
445
+ this.outputFormatContext,
446
+ this.containerFormat,
447
+ this.outputParameters
448
+ )
449
+
450
+ if (config) {
451
+ this.configs[inputStream.index] = config
452
+ }
453
+ }
454
+ }
455
+
456
+ #configureOutput() {
457
+ const options = formatRegistry.getMuxerOptions(this.containerFormat)
458
+ const muxerOptions = ffmpeg.Dictionary.from(options)
459
+
460
+ this.outputFormatContext.writeHeader(muxerOptions)
461
+ }
462
+
463
+ #processFrames() {
464
+ const packet = new ffmpeg.Packet()
465
+ const frame = new ffmpeg.Frame()
466
+
467
+ try {
468
+ while (this.inputFormatContext.readFrame(packet)) {
469
+ const config = this.configs[packet.streamIndex]
470
+ if (!config) {
471
+ packet.unref()
472
+ continue
473
+ }
474
+
475
+ const { decoder } = config
476
+
477
+ if (decoder.sendPacket(packet)) {
478
+ while (decoder.receiveFrame(frame)) {
479
+ if (config.isVideo()) {
480
+ this.videoProcessor.process(frame, config, packet)
481
+ } else if (config.isAudio()) {
482
+ this.audioProcessor.process(frame, config, packet)
483
+ }
484
+ }
485
+ }
486
+ packet.unref()
487
+ }
488
+ } finally {
489
+ packet.destroy()
490
+ frame.destroy()
491
+ }
492
+ }
493
+
494
+ #finalize() {
495
+ const packet = new ffmpeg.Packet()
496
+
497
+ try {
498
+ for (const index in this.configs) {
499
+ const config = this.configs[index]
500
+ this.audioProcessor.flush(config, packet)
501
+
502
+ this._encodeAndWrite(config.encoder, null, config.outputStream, packet)
503
+ }
504
+
505
+ this.outputFormatContext.writeTrailer()
506
+ } finally {
507
+ packet.destroy()
508
+ }
509
+ }
510
+
511
+ #cleanup() {
512
+ for (const index in this.configs) {
513
+ const config = this.configs[index]
514
+ config.decoder.destroy()
515
+ config.encoder.destroy()
516
+ if (config.rescaler) config.rescaler.destroy()
517
+ if (config.resampler) config.resampler.destroy()
518
+ if (config.fifo) config.fifo.destroy()
519
+ if (config.fifoFrame) config.fifoFrame.destroy()
520
+ }
521
+
522
+ if (this.inputFormatContext) this.inputFormatContext.destroy()
523
+ if (this.outputFormatContext) this.outputFormatContext.destroy()
524
+ }
525
+
526
+ _encodeAndWrite(encoder, frame, outputStream, packet) {
527
+ if (encoder.sendFrame(frame)) {
528
+ while (encoder.receivePacket(packet)) {
529
+ packet.streamIndex = outputStream.index
530
+ packet.rescaleTimestamps(encoder.timeBase, outputStream.timeBase)
531
+ this.outputFormatContext.writeFrame(packet)
532
+ packet.unref()
533
+ }
534
+ }
535
+ }
536
+ }
537
+
538
+ export { Transcoder }
package/types.js CHANGED
@@ -7,6 +7,7 @@ export const IMAGE = {
7
7
  JPEG: 'image/jpeg',
8
8
  JPG: 'image/jpg',
9
9
  PNG: 'image/png',
10
+ SVG_XML: 'image/svg+xml',
10
11
  TIF: 'image/tif',
11
12
  TIFF: 'image/tiff',
12
13
  VND_MS_ICON: 'image/vnd.microsoft.icon',