Files
mediabunny/dev/convert.html
T
d426d36386 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]>
2025-09-18 21:05:09 +02:00

190 lines
4.3 KiB
HTML

<!DOCTYPE html>
<script src="../dist/bundles/mediabunny.cjs"></script>
<script src="../packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.js"></script>
<script type="module">
MediabunnyMp3Encoder.registerMp3Encoder();
const fileInput = document.createElement('input');
fileInput.type = 'file';
document.body.append(fileInput);
const progressElement = document.createElement('progress');
progressElement.max = 1;
document.body.append(progressElement);
fileInput.addEventListener('change', async () => {
const file = fileInput.files[0];
const source = new Mediabunny.BlobSource(file);
const target = new Mediabunny.BufferTarget() ?? new Mediabunny.StreamTarget(new WritableStream({
write: console.log
}), {
chunked: true,
chunkSize: 2**20
});
const outputFormat = new Mediabunny.FlacOutputFormat({});
const button = document.createElement('button');
button.textContent = 'Cancel';
button.onclick = () => conversion.cancel();
document.body.append(button);
const yo = document.createElement('canvas');
yo.width = 512;
yo.height = 512;
const context = yo.getContext('2d', { alpha: false });
context.fillStyle = 'red';
context.fillRect(100, 100, 300, 300);
const blob = await new Promise(resolve => yo.toBlob(resolve, 'image/jpeg', 0.92));
const blobData = new Uint8Array(await blob.arrayBuffer());
const mh = new Blob([blobData], { type: 'image/jpeg' });
console.log(URL.createObjectURL(mh))
const output = new Mediabunny.Output({
format: outputFormat,
target
});
const conversion = await Mediabunny.Conversion.init({
input: new Mediabunny.Input({
formats: Mediabunny.ALL_FORMATS,
source
}),
output,
audio: (_, n) => ({
discard: n > 1,
//codec: 'pcm-s16',
//sampleRate: 16000,
//numberOfChannels: 1,
//discard: true,
//codec: 'opus',
//bitrate: 128000,
//numberOfChannels: 1,
//sampleRate: 4000
//discard: true
//forceTranscode: true,
}),
/*
video: {
discard: true,
bitrate: Mediabunny.QUALITY_VERY_HIGH,
//bitrate: Mediabunny.QUALITY_VERY_HIGH
codec: 'av1',
//width: 800,
//fit: 'fill',
//rotate: 90,
},
audio: {
bitrate: Mediabunny.QUALITY_HIGH,
codec: 'aac',
},
*/
/*
video: {
codec: 'avc',
},
audio: {
codec: 'mp3',
bitrate: 320000
},
*/
video: () => ({
discard: true,
//discard: true,
//crop: {
// left: 0,
// top: 0,
// width: 500,
// height: 500,
//},
//rotate: 90,
//width: 200,
//height: 500,
//fit: 'contain',
//forceTranscode: true,
//codec: 'avc',
//fit: 'contain',
//frameRate: 27.123,
//width: 320,
//forceTranscode: true,
//codec: 'av1',
//discard: true,
//width: 1280,
//discard: true,
//width: 640
//forceTranscode: true,
//rotate: 90
//width: 720 ?? 2160,
//height: 1280 ?? 3840,
//fit: 'contain',
//rotate: 90,
//width: 512,
//height: 512,
//width: 200,
//height: 100,
}),
tags: {} ?? {
title: 'Bigggy',
artist: 'Buck Bunny',
images: [{
data: blobData,
kind: 'coverFront',
mimeType: 'image/jpeg'
}],
trackNumber: 4,
tracksTotal: 10,
discNumber: 5,
discsTotal: 8,
lyrics: "There's no way\nThat it's not going there",
raw: {
'AIGC': 'Mediabunny epic own encoder'
}
},
trim: {
//start: 10,
//end: 20
},
});
console.log(conversion);
let progress = 0;
conversion.onProgress = newProgress => progress = newProgress;
function updateProgress() {
progressElement.value = progress;
if (progress === 1) {
return;
}
setTimeout(updateProgress, 1000/60);
}
updateProgress();
console.time();
await conversion.execute();
console.timeEnd()
console.log("Done", target.buffer);
const video = document.createElement('video');
video.src = URL.createObjectURL(new Blob([target.buffer], { type: outputFormat.mimeType }));
video.controls = true;
document.body.append(video);
video.play();
//download(new Blob([target.buffer]), 'converted' + outputFormat.fileExtension);
function download(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
});
</script>