FLAC container support (#95)

* add a test

* recognize as input format

* scaffold flac demuxer

* implement getting metadata

* Implement mime type

* read all metadata + deduplicate stubs

* Read first packet

* copyright headers

* read the first packet

* Get entire first packet, work on advancing

* iterate over all samples

* testable with bun

* stub out metadata support

* parse descriptive metadata

* All in 1 file

seems more appropriate to the philosophy

* some parameters are not needed anymore all within 1 class

* skip over bytes we are sure are not the syncword

* run prettier

* timestamp is determined based on passed blocks, not maximumBlockSize

* no binary search needed!

* simplifications

* Finish demuxer reading sequentially

* more tests + add a file with a seektable

* don't throw if (this.audioInfo.minimumBlockSize !== this.audioInfo.maximumBlockSize

* Add docs

* Update format compatibility table

* Simplification

* confirm conversion is working

* Finish

* Resolve TODO comment

* Support images (read-only)

* Returning description as Uint8Array

* Cleanup of demuxer

* Misc renames

* Object on same line

* Resolve first batch of comments

* Throw errors on corrupt blocks, correctly use requestSlice()

* Fix description field

* Explain why last frame is a bit shorter

* Add FLAC to README

* Put track backings below demuxer

* convert to methods

* Reorder container checking

* getBlockSize() -> readBlockSize()

* bitStream -> bitstream

* better naming for bytes

* use .skip()

* Don't return blockSize twice in readFlacFrameHeader

* Use enum + switch to distinguish Flac block types

* Use else-if

* Use else-if

* Compressed switch statement

* readCodedNumber

* Update flac-misc.ts

* We don't need the bits variable at all

* reorder functions in flac-demuxer

* Handle gracefully not being able to load another sample

* Update flac-demuxer.ts

* blockingbit null

* use async instead of promise.resolve

* use binary search

* Replace recursion with while loop

* Add mutex to getPacket()

* Load more data not in getPacketAtIndex, but outside

* Apply suggestion from @Vanilagy

Co-authored-by: David P. <[email protected]>

* computeDuration() reads last packet

* Update flac-demuxer.ts

* share vorbis comment reading logic

* reuse vorbis comment writing logic, set vendor always to "Mediabunny"

* flush after writign

* Use FileSlice.tempFromBytes

* assert !== null

* fixing nitpicks

* apply suggestions

* fix ogg

* We are now muxing images

* apply suggestion

* Update src/flac/flac-muxer.ts

Co-authored-by: David P. <[email protected]>

* apply suggestion

* readSampleRate()

* seek outside writeHeader()

* mention vorbis metadata +  add to metadatatags comment

* should be able to -> can

* Add test in metadata tags

* Add test for packets being byte identical after remuxing

* compare to null

* no casting to uint8array

* make test pass

* Update flac-muxer.ts

* Call validateAudioChunkMetadata() and validateAndNormalizeTimestamp()

* `onFrame` option

* emit frames using onFrame

* Run prettier over files

* Fix FLAC PICTURE block logic, small other changes

* Update docs

* Remove .only modifier

* fix remuxing and add test

* Don't throw error if parsing fails in header, since hitting a syncword might just be coincidential

* Fix remaining type errors

---------

Co-authored-by: David P. <[email protected]>
This commit is contained in:
Jonny Burger
2025-09-18 21:05:09 +02:00
committed by GitHub
co-authored by David P.
parent 7664d48f70
commit d426d36386
21 changed files with 2098 additions and 379 deletions
+4
View File
@@ -30,6 +30,8 @@ import {
MP3, // MP3 input format singleton
WAVE, // WAVE input format singleton
OGG, // Ogg input format singleton
ADTS, // ADTS input format singleton
FLAC, // FLAC input format singleton
} from 'mediabunny';
```
@@ -76,6 +78,8 @@ In addition to singletons, input format classes are structured hierarchically:
- `Mp3InputFormat`
- `WaveInputFormat`
- `OggInputFormat`
- `AdtsInputFormat`
- `FlacInputFormat`
This means you can also perform input format checks using `instanceof` instead of `===` comparisons. For example:
```ts
+1 -1
View File
@@ -12,7 +12,7 @@ Here's a long list of stuff this library does:
- Converting media files
- Hardware-accelerated decoding & encoding (via the WebCodecs API)
- Support for multiple video, audio and subtitle tracks
- Read & write support for many container formats (.mp4, .mov, .webm, .mkv, .mp3, .wav, .ogg, .aac), including variations such as MP4 with Fast Start, fragmented MP4, or streamable Matroska
- Read & write support for many container formats (.mp4, .mov, .webm, .mkv, .mp3, .wav, .ogg, .aac, .flac), including variations such as MP4 with Fast Start, fragmented MP4, or streamable Matroska
- Support for 25 different codecs
- Lazy, optimized, on-demand file reading
- Input and output streaming, arbitrary file size support
+22 -1
View File
@@ -267,4 +267,25 @@ type AdtsOutputFormatOptions = {
};
```
- `onFrame`\
Will be called for each ADTS frame that is written.
Will be called for each ADTS frame that is written.
## FLAC
This output format creates FLAC (.flac) files.
```ts
import { Output, FlacOutputFormat } from 'mediabunny';
const output = new Output({
format: new FlacOutputFormat(options),
// ...
});
```
The following options are available:
```ts
type FlacOutputFormatOptions = {
onFrame?: (data: Uint8Array, position: number) => unknown;
};
```
- `onFrame`\
Will be called for each FLAC frame that is written.
+28 -27
View File
@@ -12,6 +12,7 @@ Mediabunny supports many commonly used media container formats, all of which are
- MP3 (.mp3)
- WAVE (.wav)
- ADTS (.aac)
- FLAC (.flac)
## Codecs
@@ -61,33 +62,33 @@ Mediabunny ships with built-in decoders and encoders for all audio PCM codecs, m
Not all codecs can be used with all containers. The following table specifies the supported codec-container combinations:
| | .mp4 | .mov | .mkv | .webm[^1] | .ogg | .mp3 | .wav | .aac |
|:--------------:|:--------:|:-----:|:-----:|:---------:|:-----:|:-----:|:-----:|:-----:|
| `'avc'` | ✓ | ✓ | ✓ | | | | | |
| `'hevc'` | ✓ | ✓ | ✓ | | | | | |
| `'vp8'` | ✓ | ✓ | ✓ | ✓ | | | | |
| `'vp9'` | ✓ | ✓ | ✓ | ✓ | | | | |
| `'av1'` | ✓ | ✓ | ✓ | ✓ | | | | |
| `'aac'` | ✓ | ✓ | ✓ | | | | | ✓ |
| `'opus'` | ✓ | ✓ | ✓ | ✓ | ✓ | | | |
| `'mp3'` | ✓ | ✓ | ✓ | | | ✓ | | |
| `'vorbis'` | ✓ | ✓ | ✓ | ✓ | ✓ | | | |
| `'flac'` | ✓ | ✓ | ✓ | | | | | |
| `'pcm-u8'` | | ✓ | ✓ | | | | ✓ | |
| `'pcm-s8'` | | ✓ | | | | | | |
| `'pcm-s16'` | ✓ | ✓ | ✓ | | | | ✓ | |
| `'pcm-s16be'` | ✓ | ✓ | ✓ | | | | | |
| `'pcm-s24'` | ✓ | ✓ | ✓ | | | | ✓ | |
| `'pcm-s24be'` | ✓ | ✓ | ✓ | | | | | |
| `'pcm-s32'` | ✓ | ✓ | ✓ | | | | ✓ | |
| `'pcm-s32be'` | ✓ | ✓ | ✓ | | | | | |
| `'pcm-f32'` | ✓ | ✓ | ✓ | | | | ✓ | |
| `'pcm-f32be'` | ✓ | ✓ | | | | | | |
| `'pcm-f64'` | ✓ | ✓ | ✓ | | | | | |
| `'pcm-f64be'` | ✓ | ✓ | | | | | | |
| `'ulaw'` | | ✓ | | | | | ✓ | |
| `'alaw'` | | ✓ | | | | | ✓ | |
| `'webvtt'`[^2] | (✓) | | (✓) | (✓) | | | | |
| | .mp4 | .mov | .mkv | .webm[^1] | .ogg | .mp3 | .wav | .aac | .flac |
|:--------------:|:--------:|:-----:|:-----:|:---------:|:-----:|:-----:|:-----:|:-----:|:-----:|
| `'avc'` | ✓ | ✓ | ✓ | | | | | | |
| `'hevc'` | ✓ | ✓ | ✓ | | | | | | |
| `'vp8'` | ✓ | ✓ | ✓ | ✓ | | | | | |
| `'vp9'` | ✓ | ✓ | ✓ | ✓ | | | | | |
| `'av1'` | ✓ | ✓ | ✓ | ✓ | | | | | |
| `'aac'` | ✓ | ✓ | ✓ | | | | | ✓ | |
| `'opus'` | ✓ | ✓ | ✓ | ✓ | ✓ | | | | |
| `'mp3'` | ✓ | ✓ | ✓ | | | ✓ | | | |
| `'vorbis'` | ✓ | ✓ | ✓ | ✓ | ✓ | | | | |
| `'flac'` | ✓ | ✓ | ✓ | | | | | | ✓ |
| `'pcm-u8'` | | ✓ | ✓ | | | | ✓ | | |
| `'pcm-s8'` | | ✓ | | | | | | | |
| `'pcm-s16'` | ✓ | ✓ | ✓ | | | | ✓ | | |
| `'pcm-s16be'` | ✓ | ✓ | ✓ | | | | | | |
| `'pcm-s24'` | ✓ | ✓ | ✓ | | | | ✓ | | |
| `'pcm-s24be'` | ✓ | ✓ | ✓ | | | | | | |
| `'pcm-s32'` | ✓ | ✓ | ✓ | | | | ✓ | | |
| `'pcm-s32be'` | ✓ | ✓ | ✓ | | | | | | |
| `'pcm-f32'` | ✓ | ✓ | ✓ | | | | ✓ | | |
| `'pcm-f32be'` | ✓ | ✓ | | | | | | | |
| `'pcm-f64'` | ✓ | ✓ | ✓ | | | | | | |
| `'pcm-f64be'` | ✓ | ✓ | | | | | | | |
| `'ulaw'` | | ✓ | | | | | ✓ | | |
| `'alaw'` | | ✓ | | | | | ✓ | | |
| `'webvtt'`[^2] | (✓) | | (✓) | (✓) | | | | | |
[^1]: WebM only supports a small subset of the codecs supported by Matroska. However, this library can technically read all codecs from a WebM that are supported by Matroska.