Compare commits

..
20 Commits
Author SHA1 Message Date
Vanilagy f59a79dcf7 Bump permissions for Release workflow 2025-07-09 12:05:33 +02:00
Vanilagy 19b7036d82 Bump version to 1.0.4 2025-07-09 11:59:32 +02:00
Vanilagy 47bae059db Add build artifact upload step to Release workflow 2025-07-09 11:58:15 +02:00
Vanilagy 84c9d37cb7 Revise build step and dist structure for better Webpack, CJS and pnpm support 2025-07-09 11:53:19 +02:00
David P.andGitHub 3e96184a6f Merge pull request #11 from studnitz/patch-1
Fix typo
2025-07-09 10:29:23 +02:00
Alexander von StudnitzandGitHub ee59837857 Fix typo 2025-07-09 09:47:26 +02:00
Vanilagy 08cf34ee0d Adjust README image sizes 2025-07-08 15:42:32 +02:00
Vanilagy 1b382d3e57 Improve unsupported encoder configuration error messages 2025-07-07 11:21:42 +02:00
Vanilagy f78fc697cd Allow DataView as AllowSharedBufferSource 2025-07-07 10:40:28 +02:00
Vanilagy 39df195ada Adjust some meta tags 2025-07-03 20:54:42 +02:00
Vanilagy 326e03cfc9 Link migration guides in docs 2025-07-03 12:42:27 +02:00
Vanilagy 23aef27154 Oops 2025-07-03 12:22:27 +02:00
Vanilagy 9daab77bb5 Clarify live streaming 2025-07-03 12:19:08 +02:00
Vanilagy 5a6e3eebda Adjust README 2025-07-03 12:16:42 +02:00
Vanilagy c4fb325329 Clarify "media" = video/audio 2025-07-03 11:39:46 +02:00
Vanilagy 1071bf9d49 Merge branch 'main' of https://github.com/Vanilagy/metamuxer 2025-07-03 11:36:07 +02:00
Vanilagy a8fa2e4cec Modify examples to only accept video and audio files 2025-07-03 11:36:04 +02:00
Vanilagy 12f14b1f66 Remove .gitattributes 2025-07-03 00:35:24 +02:00
Vanilagy d4d93a7007 PINK PROGRESS BAR 2025-07-02 23:07:47 +02:00
Vanilagy f44cb26d76 Fix styling, add new sponsor 2025-07-02 23:01:21 +02:00
32 changed files with 108 additions and 100 deletions
+1 -1
View File
@@ -10,6 +10,6 @@ indent_size = 2
indent_style = space indent_style = space
indent_size = 4 indent_size = 4
[*.yaml] [*.yml]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
-1
View File
@@ -1 +0,0 @@
build/* linguist-generated
@@ -2,6 +2,7 @@ name: Lint
on: on:
push: push:
pull_request:
jobs: jobs:
lint: lint:
+6 -3
View File
@@ -11,9 +11,7 @@ jobs:
release: release:
name: Release name: Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions: write-all
contents: read
id-token: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -41,6 +39,11 @@ jobs:
- name: Run build - name: Run build
run: npm run build run: npm run build
- name: Upload build artifacts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload ${{ github.event.release.tag_name }} dist/bundles/mediabunny.cjs dist/bundles/mediabunny.min.cjs dist/bundles/mediabunny.mjs dist/bundles/mediabunny.min.mjs dist/mediabunny.d.ts
- name: Create Publish to npm - name: Create Publish to npm
run: npm publish --provenance run: npm publish --provenance
env: env:
-1
View File
@@ -1,5 +1,4 @@
/node_modules /node_modules
/build
/dist /dist
/dist-docs /dist-docs
.DS_Store .DS_Store
+5 -5
View File
@@ -5,10 +5,10 @@
[![](https://img.shields.io/npm/dm/mediabunny)](https://www.npmjs.com/package/mediabunny) [![](https://img.shields.io/npm/dm/mediabunny)](https://www.npmjs.com/package/mediabunny)
<div align="center"> <div align="center">
<img src="./docs/public/mediabunny-logo.svg" height="180"> <img src="./docs/public/mediabunny-logo.svg" width="180" height="180">
</div> </div>
Mediabunny is a JavaScript library for reading, writing, and converting media files (like MP4 or WebM), directly in the browser. It aims to be a complete toolkit for high-performance media operations on the web. It's written from scratch in pure TypeScript, has zero dependencies, is very performant, and is extremely tree-shakable, meaning you only include what you use. You can think of it a bit like [FFmpeg](https://ffmpeg.org/), but built from the ground up for the web. Mediabunny is a JavaScript library for reading, writing, and converting media files (like MP4, WebM, MP3), directly in the browser. It aims to be a complete toolkit for high-performance media operations on the web. It's written from scratch in pure TypeScript, has zero dependencies, is very performant, and is extremely tree-shakable, meaning you only include what you use. You can think of it a bit like [FFmpeg](https://ffmpeg.org/), but built from the ground up for the web.
[Documentation](https://mediabunny.dev) | [Examples](https://mediabunny.dev/examples) | [Sponsoring](#sponsoring) | [License](#license) | [Discord](https://discord.gg/hmpkyYuS4U) [Documentation](https://mediabunny.dev) | [Examples](https://mediabunny.dev/examples) | [Sponsoring](#sponsoring) | [License](#license) | [Discord](https://discord.gg/hmpkyYuS4U)
@@ -16,11 +16,11 @@ Mediabunny is a JavaScript library for reading, writing, and converting media fi
<div align="center"> <div align="center">
<a href="https://www.gling.ai/" target="_blank"> <a href="https://www.gling.ai/" target="_blank">
<img src="./docs/public/sponsors/gling.svg" height="60" alt="Gling AI"> <img src="./docs/public/sponsors/gling.svg" width="60" height="60" alt="Gling AI">
</a> </a>
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://diffusion.studio/" target="_blank"> <a href="https://diffusion.studio/" target="_blank">
<img src="./docs/public/sponsors/diffusionstudio.png" height="60" alt="Diffusion Studio"> <img src="./docs/public/sponsors/diffusionstudio.png" width="60" height="60" alt="Diffusion Studio">
</a> </a>
</div> </div>
@@ -152,7 +152,7 @@ For development, clone this repository and install it using a modern version of
```bash ```bash
npm install # Install dependencies npm install # Install dependencies
npm run watch # Development build with watch mode npm run watch # Build bundles on watch mode
npm run build # Production build with type definitions npm run build # Production build with type definitions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"mainEntryPointFilePath": "build/index.d.ts", "mainEntryPointFilePath": "dist/modules/index.d.ts",
"bundledPackages": [], "bundledPackages": [],
"compiler": {}, "compiler": {},
"apiReport": { "apiReport": {
+4 -4
View File
@@ -37,20 +37,20 @@ const esmConfig = {
const ctxUmd = await esbuild.context({ const ctxUmd = await esbuild.context({
...umdConfig, ...umdConfig,
outfile: 'dist/mediabunny.js', outfile: 'dist/bundles/mediabunny.cjs',
}); });
const ctxEsm = await esbuild.context({ const ctxEsm = await esbuild.context({
...esmConfig, ...esmConfig,
outfile: 'dist/mediabunny.mjs', outfile: 'dist/bundles/mediabunny.mjs',
}); });
const ctxUmdMinified = await esbuild.context({ const ctxUmdMinified = await esbuild.context({
...umdConfig, ...umdConfig,
outfile: 'dist/mediabunny.min.js', outfile: 'dist/bundles/mediabunny.min.cjs',
minify: true, minify: true,
}); });
const ctxEsmMinified = await esbuild.context({ const ctxEsmMinified = await esbuild.context({
...esmConfig, ...esmConfig,
outfile: 'dist/mediabunny.min.mjs', outfile: 'dist/bundles/mediabunny.min.mjs',
minify: true, minify: true,
}); });
+4 -3
View File
@@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<script src="../dist/mediabunny.js"></script> <script src="../dist/bundles/mediabunny.cjs"></script>
<script type="module"> <script type="module">
const fileInput = document.createElement('input'); const fileInput = document.createElement('input');
@@ -21,7 +21,7 @@
chunked: true, chunked: true,
chunkSize: 2**20 chunkSize: 2**20
}); });
const outputFormat = new Mediabunny.MovOutputFormat(); const outputFormat = new Mediabunny.Mp4OutputFormat();
const button = document.createElement('button'); const button = document.createElement('button');
button.textContent = 'Cancel'; button.textContent = 'Cancel';
@@ -38,7 +38,8 @@
target target
}), }),
audio: { audio: {
codec: 'pcm-f64' codec: 'opus',
bitrate: 128000,
//numberOfChannels: 1, //numberOfChannels: 1,
//sampleRate: 4000 //sampleRate: 4000
//discard: true //discard: true
+1 -1
View File
@@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<script src="../dist/mediabunny.js"></script> <script src="../dist/bundles/mediabunny.cjs"></script>
<script type="module"> <script type="module">
const fileInput = document.createElement('input'); const fileInput = document.createElement('input');
+1 -1
View File
@@ -1,6 +1,6 @@
<button>Go</button> <button>Go</button>
<script src="../dist/mediabunny.js"></script> <script src="../dist/bundles/mediabunny.cjs"></script>
<script type="module"> <script type="module">
function download(blob, filename) { function download(blob, filename) {
+1 -1
View File
@@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<script src="../dist/mediabunny.js"></script> <script src="../dist/bundles/mediabunny.cjs"></script>
<script type="module"> <script type="module">
function download(blob, filename) { function download(blob, filename) {
+1 -1
View File
@@ -15,7 +15,7 @@
</div> </div>
</div> </div>
<script src="../dist/mediabunny.js"></script> <script src="../dist/bundles/mediabunny.cjs"></script>
<script type="module"> <script type="module">
const fileInput = document.querySelector('input[type="file"]'); const fileInput = document.querySelector('input[type="file"]');
+4 -1
View File
@@ -11,11 +11,14 @@ export default withMermaid({
+ ' faster than anybunny else.', + ' faster than anybunny else.',
cleanUrls: true, cleanUrls: true,
head: [ head: [
['link', { rel: 'icon', type: 'image/svg+xml', href: '/mediabunny-logo.svg' }],
['link', { rel: 'icon', type: 'image/png', href: '/mediabunny-logo.png' }], ['link', { rel: 'icon', type: 'image/png', href: '/mediabunny-logo.png' }],
['link', { rel: 'icon', type: 'image/svg+xml', href: '/mediabunny-logo.svg' }],
['meta', { property: 'og:type', content: 'website' }], ['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:site_name', content: 'Mediabunny' }], ['meta', { property: 'og:site_name', content: 'Mediabunny' }],
['meta', { property: 'og:url', content: 'https://mediabunny.dev/' }],
['meta', { property: 'og:image', content: '/mediabunny-og-image.png' }], ['meta', { property: 'og:image', content: '/mediabunny-og-image.png' }],
['meta', { property: 'og:locale', content: 'en-US' }],
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
], ],
themeConfig: { themeConfig: {
logo: '/mediabunny-logo.svg', logo: '/mediabunny-logo.svg',
+1 -1
View File
@@ -37,7 +37,7 @@ features:
target: _self target: _self
icon: icon:
src: /mingcute--magic-3-line.svg src: /mingcute--magic-3-line.svg
- title: Live recording - title: Live recording & streaming
details: Record a video from live sources and stream it to a video element. details: Record a video from live sources and stream it to a video element.
link: /examples/live-recording link: /examples/live-recording
target: _self target: _self
+7
View File
@@ -66,6 +66,13 @@ This library is the result of unifying these libraries into one, solving all the
Due to tree shaking, if you only need an MP4 or WebM muxer, this library's bundle size will still be very small. Due to tree shaking, if you only need an MP4 or WebM muxer, this library's bundle size will still be very small.
### Migration
If you're coming from mp4-muxer or webm-muxer, you should migrate to Mediabunny. For that, refer to these guides:
- [Guide: Migrating from mp4-muxer to Mediabunny](https://github.com/Vanilagy/mp4-muxer/blob/main/MIGRATION-GUIDE.md)
- [Guide: Migrating from webm-muxer to Mediabunny](https://github.com/Vanilagy/webm-muxer/blob/main/MIGRATION-GUIDE.md)
## Technical overview ## Technical overview
At its core, Mediabunny is a collection of multiplexers and demultiplexers, one of each for every container format. Demultiplexers stream data from *sources*, while multiplexers stream data to *targets*. Every demultiplexer is capable of extracting file metadata as well as compressed media data, while multiplexers write metadata and encoded media data into a new file. At its core, Mediabunny is a collection of multiplexers and demultiplexers, one of each for every container format. Demultiplexers stream data from *sources*, while multiplexers stream data to *targets*. Every demultiplexer is capable of extracting file metadata as well as compressed media data, while multiplexers write metadata and encoded media data into a new file.
+5 -4
View File
@@ -6,7 +6,7 @@ title: Mediabunny
hero: hero:
name: Mediabunny name: Mediabunny
text: Complete media toolkit text: Complete media toolkit
tagline: A JavaScript library for reading, writing, and converting media files. Directly in the browser, and faster than anybunny else. tagline: A JavaScript library for reading, writing, and converting video and audio files. Directly in the browser, and faster than anybunny else.
image: image:
src: /mediabunny-logo.svg src: /mediabunny-logo.svg
alt: Mediabunny logo alt: Mediabunny logo
@@ -92,6 +92,7 @@ const sponsors = {
{ image: '/sponsors/diffusionstudio.png', name: 'Diffusion Studio', url: 'https://diffusion.studio/' }, { image: '/sponsors/diffusionstudio.png', name: 'Diffusion Studio', url: 'https://diffusion.studio/' },
], ],
individual: [ individual: [
{ image: 'https://avatars.githubusercontent.com/u/84167135', name: 'Memenome', url: 'https://github.com/memenome' },
{ image: 'https://avatars.githubusercontent.com/u/30229596', name: 'Pablo Bonilla', url: 'https://github.com/devPablo' }, { image: 'https://avatars.githubusercontent.com/u/30229596', name: 'Pablo Bonilla', url: 'https://github.com/devPablo' },
{ image: 'https://avatars.githubusercontent.com/u/58149663', name: 'H7GhosT', url: 'https://github.com/H7GhosT' }, { image: 'https://avatars.githubusercontent.com/u/58149663', name: 'H7GhosT', url: 'https://github.com/H7GhosT' },
], ],
@@ -123,7 +124,7 @@ npm install mediabunny
<div class="flex flex-col lg:flex-row lg:gap-20 lg:items-center"> <div class="flex flex-col lg:flex-row lg:gap-20 lg:items-center">
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<h1 class="inline-block" style="background: -webkit-linear-gradient(-30deg, #ff45ac, #ff78c2); -webkit-background-clip: text; color: transparent;">Read any media file, efficiently</h1> <h1 class="inline-block" style="background: -webkit-linear-gradient(-30deg, #ff45ac, #ff78c2); -webkit-background-clip: text; color: transparent;">Read any media file, efficiently</h1>
<p class="text-lg">Mediabunny allows you efficiently read data from any media file, no matter the size: duration, resolution, rotation, tracks, codecs and other metadata, as well as raw or decoded media data from anywhere in the file. Load only what you need.</p> <p class="text-lg">Mediabunny allows you efficiently read data from any video or audio file, no matter the size: duration, resolution, rotation, tracks, codecs and other metadata, as well as raw or decoded media data from anywhere in the file. Load only what you need.</p>
<a class="!no-underline inline-flex items-center gap-1.5" :no-icon="true" href="/guide/reading-media-files"> <a class="!no-underline inline-flex items-center gap-1.5" :no-icon="true" href="/guide/reading-media-files">
Docs Docs
<span class="vpi-arrow-right" /> <span class="vpi-arrow-right" />
@@ -323,7 +324,7 @@ await conversion.execute();
<template v-if="sponsors.gold.length > 0"> <template v-if="sponsors.gold.length > 0">
<h3 class="!text-2xl">Gold sponsors</h3> <h3 class="!text-2xl">Gold sponsors</h3>
<div class="flex flex-wrap mt-1 justify-center gap-1"> <div class="flex flex-wrap mt-1 justify-center gap-1">
<a v-for="sponsor in sponsors.gold" :href="sponsor.url" target="_blank" class="flex items-center p-2 rounded-full hover:bg-(--vp-c-gray-3) !text-[initial] !no-underline"> <a v-for="sponsor in sponsors.gold" :href="sponsor.url" target="_blank" class="flex items-center p-2 rounded-full hover:bg-(--vp-c-gray-3) !text-(--vp-c-text-1) !no-underline">
<img :src="sponsor.image" class="size-16 rounded-full"> <img :src="sponsor.image" class="size-16 rounded-full">
<p class="!my-0 !font-medium px-3">{{ sponsor.name }}</p> <p class="!my-0 !font-medium px-3">{{ sponsor.name }}</p>
</a> </a>
@@ -332,7 +333,7 @@ await conversion.execute();
<template v-if="sponsors.individual.length > 0"> <template v-if="sponsors.individual.length > 0">
<h4>Individual sponsors</h4> <h4>Individual sponsors</h4>
<div class="flex flex-wrap mt-1 justify-center"> <div class="flex flex-wrap mt-1 justify-center">
<a v-for="sponsor in sponsors.individual" :href="sponsor.url" target="_blank" class="flex gap-1 w-24 flex-col items-center p-2 rounded-xl hover:bg-(--vp-c-gray-3) !text-[initial] !no-underline"> <a v-for="sponsor in sponsors.individual" :href="sponsor.url" target="_blank" class="flex gap-1 w-24 flex-col items-center p-2 rounded-xl hover:bg-(--vp-c-gray-3) !text-(--vp-c-text-1) !no-underline">
<img :src="sponsor.image" class="size-8 rounded-full"> <img :src="sponsor.image" class="size-8 rounded-full">
<p class="!my-0 !font-medium text-xs !leading-4">{{ sponsor.name }}</p> <p class="!my-0 !font-medium text-xs !leading-4">{{ sponsor.name }}</p>
</a> </a>
@@ -113,6 +113,7 @@ const compressFile = async (file: File) => {
selectMediaButton.addEventListener('click', () => { selectMediaButton.addEventListener('click', () => {
const fileInput = document.createElement('input'); const fileInput = document.createElement('input');
fileInput.type = 'file'; fileInput.type = 'file';
fileInput.accept = 'video/*,video/x-matroska,audio/*';
fileInput.addEventListener('change', () => { fileInput.addEventListener('change', () => {
const file = fileInput.files?.[0]; const file = fileInput.files?.[0];
if (!file) { if (!file) {
+2 -2
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Live recording example | Mediabunny</title> <title>Live recording & streaming example | Mediabunny</title>
<script type="module" src="../base.ts"></script> <script type="module" src="../base.ts"></script>
<script type="module" src="./live-recording.ts"></script> <script type="module" src="./live-recording.ts"></script>
<link rel="stylesheet" href="../base.css"> <link rel="stylesheet" href="../base.css">
@@ -12,7 +12,7 @@
</head> </head>
<body class="flex flex-col items-center py-10 bg-zinc-50 text-zinc-800 dark:bg-zinc-900 dark:text-zinc-200 px-2"> <body class="flex flex-col items-center py-10 bg-zinc-50 text-zinc-800 dark:bg-zinc-900 dark:text-zinc-200 px-2">
<h1 class="text-3xl font-bold text-orange-500 text-center">Live recording example</h1> <h1 class="text-3xl font-bold text-orange-500 text-center">Live recording & streaming example</h1>
<p class="max-w-lg text-center">The live canvas state and your microphone input will be written into a fragmented MP4 file and live-streamed to a <code>&lt;video&gt;</code> element.</p> <p class="max-w-lg text-center">The live canvas state and your microphone input will be written into a fragmented MP4 file and live-streamed to a <code>&lt;video&gt;</code> element.</p>
<button id="toggle-button" class="rounded-lg bg-zinc-200 dark:bg-zinc-750 hover:bg-zinc-300 dark:hover:bg-zinc-700 px-5 py-2 mt-4"> <button id="toggle-button" class="rounded-lg bg-zinc-200 dark:bg-zinc-750 hover:bg-zinc-300 dark:hover:bg-zinc-700 px-5 py-2 mt-4">
+2 -1
View File
@@ -253,7 +253,7 @@ const runAudioIterator = async () => {
} }
// To play back audio, we loop over all audio chunks (typically very short) of the file and play them at the correct // To play back audio, we loop over all audio chunks (typically very short) of the file and play them at the correct
// timestamp. The result is a continuous, uninteruppted audio signal. // timestamp. The result is a continuous, uninterrupted audio signal.
for await (const { buffer, timestamp } of audioBufferIterator!) { for await (const { buffer, timestamp } of audioBufferIterator!) {
const node = audioContext!.createBufferSource(); const node = audioContext!.createBufferSource();
node.buffer = buffer; node.buffer = buffer;
@@ -544,6 +544,7 @@ const formatSeconds = (seconds: number) => {
selectMediaButton.addEventListener('click', () => { selectMediaButton.addEventListener('click', () => {
const fileInput = document.createElement('input'); const fileInput = document.createElement('input');
fileInput.type = 'file'; fileInput.type = 'file';
fileInput.accept = 'video/*,video/x-matroska,audio/*';
fileInput.addEventListener('change', () => { fileInput.addEventListener('change', () => {
const file = fileInput.files?.[0]; const file = fileInput.files?.[0];
if (!file) { if (!file) {
@@ -155,6 +155,7 @@ const shortDelay = () => {
selectMediaButton.addEventListener('click', () => { selectMediaButton.addEventListener('click', () => {
const fileInput = document.createElement('input'); const fileInput = document.createElement('input');
fileInput.type = 'file'; fileInput.type = 'file';
fileInput.accept = 'video/*,video/x-matroska,audio/*';
fileInput.addEventListener('change', () => { fileInput.addEventListener('change', () => {
const file = fileInput.files?.[0]; const file = fileInput.files?.[0];
if (!file) { if (!file) {
+1 -1
View File
@@ -38,7 +38,7 @@
<p id="error-element" class="text-red-500"></p> <p id="error-element" class="text-red-500"></p>
<div class="w-full max-w-80 h-2 rounded-full bg-zinc-200 dark:bg-zinc-750 overflow-hidden" id="progress-bar-container" style="display: none;"> <div class="w-full max-w-80 h-2 rounded-full bg-zinc-200 dark:bg-zinc-750 overflow-hidden" id="progress-bar-container" style="display: none;">
<div class="h-full bg-teal-500 w-0" id="progress-bar"></div> <div class="h-full bg-pink-500 w-0" id="progress-bar"></div>
</div> </div>
<p class="text-xs font-medium mt-1.5 tabular-nums" id="progress-text" style="display: none;"></p> <p class="text-xs font-medium mt-1.5 tabular-nums" id="progress-text" style="display: none;"></p>
@@ -99,6 +99,7 @@ const generateThumbnails = async (file: File) => {
selectMediaButton.addEventListener('click', () => { selectMediaButton.addEventListener('click', () => {
const fileInput = document.createElement('input'); const fileInput = document.createElement('input');
fileInput.type = 'file'; fileInput.type = 'file';
fileInput.accept = 'video/*,video/x-matroska,audio/*';
fileInput.addEventListener('change', () => { fileInput.addEventListener('change', () => {
const file = fileInput.files?.[0]; const file = fileInput.files?.[0];
if (!file) { if (!file) {
+24 -38
View File
@@ -1,12 +1,12 @@
{ {
"name": "mediabunny", "name": "mediabunny",
"version": "0.1.0", "version": "1.0.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mediabunny", "name": "mediabunny",
"version": "0.1.0", "version": "1.0.4",
"license": "MPL-2.0", "license": "MPL-2.0",
"dependencies": { "dependencies": {
"@types/dom-mediacapture-transform": "^0.1.11", "@types/dom-mediacapture-transform": "^0.1.11",
@@ -319,9 +319,9 @@
} }
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
"version": "7.25.9", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -329,9 +329,9 @@
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.25.9", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -339,13 +339,13 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.27.0", "version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
"integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.27.0" "@babel/types": "^7.28.0"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@@ -355,14 +355,14 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.27.0", "version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz",
"integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.25.9", "@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.25.9" "@babel/helper-validator-identifier": "^7.27.1"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -1238,18 +1238,14 @@
} }
}, },
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8", "version": "0.3.12",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.24" "@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
} }
}, },
"node_modules/@jridgewell/resolve-uri": { "node_modules/@jridgewell/resolve-uri": {
@@ -1262,16 +1258,6 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
@@ -1280,9 +1266,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25", "version": "0.3.29",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
+14 -10
View File
@@ -1,25 +1,27 @@
{ {
"name": "mediabunny", "name": "mediabunny",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.0.0", "version": "1.0.4",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.", "description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module", "type": "module",
"main": "./dist/mediabunny.js", "main": "./dist/bundles/mediabunny.cjs",
"module": "./dist/mediabunny.mjs", "module": "./dist/modules/index.js",
"types": "./dist/mediabunny.d.ts", "types": "./dist/modules/index.d.ts",
"exports": { "exports": {
"types": "./dist/mediabunny.d.ts", "types": "./dist/modules/index.d.ts",
"import": "./dist/mediabunny.mjs", "import": "./dist/modules/index.js",
"require": "./dist/mediabunny.js" "require": "./dist/bundles/mediabunny.cjs"
}, },
"files": [ "files": [
"README.md", "README.md",
"package.json", "package.json",
"LICENSE", "LICENSE",
"dist" "dist",
"src"
], ],
"sideEffects": false,
"scripts": { "scripts": {
"build": "tsx scripts/ensure-license-headers.ts && node build.mjs && tsc -p src && api-extractor run && npm run check-docblocks && tsx scripts/append-namespace.ts", "build": "rm -rf dist && tsx scripts/ensure-license-headers.ts && tsc -p src && npm run fix-build-import-paths && node build.mjs && api-extractor run && npm run check-docblocks && npm run append-namespace",
"watch": "node build.mjs --watch", "watch": "node build.mjs --watch",
"lint": "eslint .", "lint": "eslint .",
"check": "tsc -p src --noEmit && tsc -p scripts --noEmit && tsc -p tsconfig.vite.json --noEmit && rm tsconfig.vite.tsbuildinfo", "check": "tsc -p src --noEmit && tsc -p scripts --noEmit && tsc -p tsconfig.vite.json --noEmit && rm tsconfig.vite.tsbuildinfo",
@@ -28,7 +30,9 @@
"docs:build": "vitepress build docs && npm run examples:build", "docs:build": "vitepress build docs && npm run examples:build",
"docs:preview": "vitepress preview docs", "docs:preview": "vitepress preview docs",
"dev": "vite", "dev": "vite",
"examples:build": "vite build" "examples:build": "vite build",
"fix-build-import-paths": "find dist -name \"*.js\" -type f -exec sed -i -r \"s/ from '([^']+)';/ from '\\1.js';/g\" {} \\;",
"append-namespace": "echo 'export as namespace Mediabunny;' >> dist/mediabunny.d.ts"
}, },
"license": "MPL-2.0", "license": "MPL-2.0",
"repository": { "repository": {
-3
View File
@@ -1,3 +0,0 @@
import { appendFileSync } from 'fs';
appendFileSync('dist/mediabunny.d.ts', '\nexport as namespace Mediabunny;');
+3
View File
@@ -6,6 +6,9 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/ */
/// <reference types="dom-mediacapture-transform" preserve="true" />
/// <reference types="dom-webcodecs" preserve="true" />
export { export {
Output, Output,
OutputOptions, OutputOptions,
+6 -4
View File
@@ -416,8 +416,9 @@ class VideoEncoderWrapper {
const support = await VideoEncoder.isConfigSupported(encoderConfig); const support = await VideoEncoder.isConfigSupported(encoderConfig);
if (!support.supported) { if (!support.supported) {
throw new Error( throw new Error(
'This specific encoder configuration is not supported by this browser. Consider using another' `This specific encoder configuration (${encoderConfig.codec}, ${encoderConfig.bitrate} bps,`
+ ' codec or changing your video parameters.', + ` ${encoderConfig.width}x${encoderConfig.height}) is not supported by this browser. Consider`
+ ` using another codec or changing your video parameters.`,
); );
} }
@@ -988,8 +989,9 @@ class AudioEncoderWrapper {
const support = await AudioEncoder.isConfigSupported(encoderConfig); const support = await AudioEncoder.isConfigSupported(encoderConfig);
if (!support.supported) { if (!support.supported) {
throw new Error( throw new Error(
'This specific encoder configuration not supported by this browser. Consider using another' `This specific encoder configuration (${encoderConfig.codec}, ${encoderConfig.bitrate} bps,`
+ ' codec or changing your audio parameters.', + ` ${encoderConfig.numberOfChannels} channels, ${encoderConfig.sampleRate} Hz) is not`
+ ` supported by this browser. Consider using another codec or changing your audio parameters.`,
); );
} }
+7 -4
View File
@@ -134,7 +134,9 @@ export const writeBits = (bytes: Uint8Array, start: number, end: number, value:
}; };
export const toUint8Array = (source: AllowSharedBufferSource): Uint8Array => { export const toUint8Array = (source: AllowSharedBufferSource): Uint8Array => {
if (source instanceof ArrayBuffer) { if (source instanceof Uint8Array) {
return source;
} else if (source instanceof ArrayBuffer) {
return new Uint8Array(source); return new Uint8Array(source);
} else { } else {
return new Uint8Array(source.buffer, source.byteOffset, source.byteLength); return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
@@ -142,7 +144,9 @@ export const toUint8Array = (source: AllowSharedBufferSource): Uint8Array => {
}; };
export const toDataView = (source: AllowSharedBufferSource) => { export const toDataView = (source: AllowSharedBufferSource) => {
if (source instanceof ArrayBuffer) { if (source instanceof DataView) {
return source;
} else if (source instanceof ArrayBuffer) {
return new DataView(source); return new DataView(source);
} else { } else {
return new DataView(source.buffer, source.byteOffset, source.byteLength); return new DataView(source.buffer, source.byteOffset, source.byteLength);
@@ -202,11 +206,10 @@ export const colorSpaceIsComplete = (
}; };
export const isAllowSharedBufferSource = (x: unknown) => { export const isAllowSharedBufferSource = (x: unknown) => {
// Quite a mouthful:
return ( return (
x instanceof ArrayBuffer x instanceof ArrayBuffer
|| (typeof SharedArrayBuffer !== 'undefined' && x instanceof SharedArrayBuffer) || (typeof SharedArrayBuffer !== 'undefined' && x instanceof SharedArrayBuffer)
|| (ArrayBuffer.isView(x) && !(x instanceof DataView)) || ArrayBuffer.isView(x)
); );
}; };
+1 -5
View File
@@ -890,7 +890,6 @@ export class AudioSample {
} }
return new AudioData({ return new AudioData({
format: this.format, format: this.format,
sampleRate: this.sampleRate, sampleRate: this.sampleRate,
numberOfFrames: this.numberOfFrames, numberOfFrames: this.numberOfFrames,
@@ -900,11 +899,9 @@ export class AudioSample {
}); });
} else { } else {
const data = new ArrayBuffer(this.allocationSize({ planeIndex: 0, format: this.format })); const data = new ArrayBuffer(this.allocationSize({ planeIndex: 0, format: this.format }));
this.copyTo(data, { planeIndex: 0, format: this.format });
this.copyTo(new DataView(data), { planeIndex: 0, format: this.format });
return new AudioData({ return new AudioData({
format: this.format, format: this.format,
sampleRate: this.sampleRate, sampleRate: this.sampleRate,
numberOfFrames: this.numberOfFrames, numberOfFrames: this.numberOfFrames,
@@ -916,7 +913,6 @@ export class AudioSample {
} }
} else { } else {
return new AudioData({ return new AudioData({
format: this.format, format: this.format,
sampleRate: this.sampleRate, sampleRate: this.sampleRate,
numberOfFrames: this.numberOfFrames, numberOfFrames: this.numberOfFrames,
+1 -2
View File
@@ -1,9 +1,8 @@
{ {
"extends": "../tsconfig.json", "extends": "../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../build", "outDir": "../dist/modules",
"declaration": true, "declaration": true,
"sourceMap": true,
"declarationMap": true, "declarationMap": true,
"stripInternal": true, "stripInternal": true,
"noEmit": false "noEmit": false
+1 -1
View File
@@ -20,7 +20,7 @@ const rollupInput = Object.fromEntries(
export default defineConfig({ export default defineConfig({
resolve: { resolve: {
alias: { alias: {
mediabunny: path.resolve(__dirname, './dist/mediabunny.mjs'), mediabunny: path.resolve(__dirname, './dist/bundles/mediabunny.mjs'),
}, },
}, },
plugins: [ plugins: [