mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-04 06:13:46 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9180af799d | ||
|
|
b8d5cc5b36 | ||
|
|
d5b07373b3 | ||
|
|
b99b86db0b | ||
|
|
b0e5f655aa | ||
|
|
0c2853d0aa | ||
|
|
cc970e1f53 | ||
|
|
5e68d9655c | ||
|
|
7171cf4338 | ||
|
|
baff6a67a3 | ||
|
|
d0401b42a5 | ||
|
|
72427b8b39 | ||
|
|
6812cf1643 | ||
|
|
c9a828c272 | ||
|
|
e851120bfe | ||
|
|
1a4fec9f6e | ||
|
|
8c8ce8a4a5 | ||
|
|
7ea8f6d751 |
@@ -25,15 +25,6 @@ jobs:
|
||||
with:
|
||||
script: "core.setFailed('Non-prerelease releases must target the main branch (got: ${{ github.event.release.target_commitish }}).')"
|
||||
|
||||
- name: Merge main into release branch
|
||||
if: ${{ !github.event.release.prerelease }}
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git checkout release
|
||||
git merge origin/main --no-ff -m "Merge main into release for tag ${{ github.event.release.tag_name }}"
|
||||
git push origin release
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -107,3 +98,12 @@ jobs:
|
||||
|
||||
- name: Publish workspace packages to npm
|
||||
run: npm publish --access public --workspaces ${{ github.event.release.prerelease && '--tag beta' || '' }}
|
||||
|
||||
- name: Merge main into release branch
|
||||
if: ${{ !github.event.release.prerelease }}
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git checkout release
|
||||
git merge origin/main --no-ff -m "Merge main into release for tag ${{ github.event.release.tag_name }}"
|
||||
git push origin release
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- Prefer functions declared using const, not using the function keyword
|
||||
- Code style is tab indent with semicolons
|
||||
- Mediabunny core code is contained in src/, extensions are in packages/*/, website is in docs/
|
||||
- Tests: Prefer fewer, longer test files over many small ones. Test files should be named after the general catergory of thing that is being tested, not after any individual single test.
|
||||
- Tests: Prefer fewer, longer test files over many small ones. Test files should be named after the general category of thing that is being tested, not after any individual single test.
|
||||
- Avoid ifs without a {} block. So no if (cond) return;, always do if (cond) { return; }
|
||||
- `type` instead of `interface` for object types
|
||||
- `type` instead of `interface` for object types
|
||||
|
||||
@@ -44,14 +44,6 @@ Mediabunny is a JavaScript library for reading, writing, and converting media (l
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### Silver sponsors
|
||||
|
||||
<div align="center">
|
||||
<a href="https://ponder.ai/" target="_blank" rel="sponsored">
|
||||
<img src="./docs/public/sponsors/ponder.png" width="50" height="50" alt="Ponder">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### Bronze sponsors
|
||||
|
||||
<div align="center">
|
||||
@@ -70,6 +62,10 @@ Mediabunny is a JavaScript library for reading, writing, and converting media (l
|
||||
<a href="https://jellypod.ai/" target="_blank" rel="sponsored">
|
||||
<img src="./docs/public/sponsors/jellypod.png" width="40" height="40" alt="Jellypod">
|
||||
</a>
|
||||
|
||||
<a href="https://www.photocircleapp.com/" target="_blank" rel="sponsored">
|
||||
<img src="./docs/public/sponsors/photocircle.png" width="40" height="40" alt="PhotoCircle">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
[Sponsor Mediabunny's development](https://github.com/sponsors/Vanilagy)
|
||||
|
||||
@@ -60,7 +60,7 @@ To get going with Mediabunny, here are some starting points:
|
||||
|
||||
## Server-side usage
|
||||
|
||||
Mediabunny's simple yet flexible API provides a modern alternative to traditional server-side media processing pipelines which may involve calling out to FFmpeg's CLI manually. Mediabunny was primary built for client-side environments, but when combined with the [`@mediabunny/server`](./extensions/server) extension, the full Mediabunny feature set is available in server-side environments such as Node, Bun, and Deno.
|
||||
Mediabunny's simple yet flexible API provides a modern alternative to traditional server-side media processing pipelines which may involve calling out to FFmpeg's CLI manually. Mediabunny was primarily built for client-side environments, but when combined with the [`@mediabunny/server`](./extensions/server) extension, the full Mediabunny feature set is available in server-side environments such as Node, Bun, and Deno.
|
||||
|
||||
The extension enables:
|
||||
- Video decoders and encoders for AVC (H.264), HEVC (H.265), VP8, VP9, and AV1. Supports both length-prefixed and Annex B AVC/HEVC as well as transparent video via VP9.
|
||||
@@ -74,7 +74,7 @@ For more, see [the corresponding guide](./extensions/server).
|
||||
|
||||
## Motivation
|
||||
|
||||
Mediabunny is the evolution of my previous libraries, [mp4-muxer](https://github.com/Vanilagy/mp4-muxer) and [webm-muxer](https://github.com/Vanilagy/webm-muxer), which were both created due to the advent of the WebCodecs API. While they fulfilled their job just fine, I saw a few painpoints:
|
||||
Mediabunny is the evolution of my previous libraries, [mp4-muxer](https://github.com/Vanilagy/mp4-muxer) and [webm-muxer](https://github.com/Vanilagy/webm-muxer), which were both created due to the advent of the WebCodecs API. While they fulfilled their job just fine, I saw a few pain points:
|
||||
- Lots of duplicated code between the two libraries, otherwise very similar API.
|
||||
- No help with the difficulties of navigating the WebCodecs API & related browser APIs.
|
||||
- "mp4-demuxer when??"
|
||||
@@ -99,4 +99,4 @@ At its core, Mediabunny is a collection of multiplexers and demultiplexers, one
|
||||
|
||||
Mediabunny then provides several wrappers around the WebCodecs API to simplify usage: for reading, it creates decoders with the correct codec configuration and efficiently decodes media data in a pipelined way. For writing, it figures out the necessary codec configuration and sets up encoders which are then used to encode raw media data, while respecting the backpressure applied by the encoder. Extracting the right decoder configuration from a media file can be tricky and sometimes involves diving into encoded media packet bitstreams.
|
||||
|
||||
The conversion abstraction is built on top of Mediabunny's reading and writing primitives and combines them both in a heavily-pipelined way, making sure reading and writing happen in lockstep. It also consists of a lot of conditional logic probing output track compatibility, decoding support, and finding encodable codec configurations. It makes use of the Canvas API for video processing operations, and uses a custom implementation for audio resampling and up/downmixing.
|
||||
The conversion abstraction is built on top of Mediabunny's reading and writing primitives and combines them both in a heavily-pipelined way, making sure reading and writing happen in lockstep. It also consists of a lot of conditional logic probing output track compatibility, decoding support, and finding encodable codec configurations. It makes use of the Canvas API for video processing operations, and uses a custom implementation for audio resampling and up/downmixing.
|
||||
|
||||
@@ -342,7 +342,7 @@ Each chunk written to the `WritableStream` represents a contiguous chunk of byte
|
||||
::: warning
|
||||
Note that some byte regions in the output file may be written to multiple times. It is therefore **incorrect** to construct the final file by simply concatenating all `Uint8Array`s together - you **must** write each chunk of data at the specified byte offset position _in the order_ in which the chunks arrived. If you don't do this, your output file will likely be invalid or corrupted.
|
||||
|
||||
Some [output formats](./output-formats) have *append-only* writing modes in which the byte offset of a written chunk will always be equal to the total number of bytes in all previously written chunks. In other words, when writing is append-only, simply concatening all `Uint8Array`s yields the correct result. Some APIs (like `appendBuffer` of Media Source Extensions) require this, so make sure to configure your output format accordingly for those cases.
|
||||
Some [output formats](./output-formats) have *append-only* writing modes in which the byte offset of a written chunk will always be equal to the total number of bytes in all previously written chunks. In other words, when writing is append-only, simply concatenating all `Uint8Array`s yields the correct result. Some APIs (like `appendBuffer` of Media Source Extensions) require this, so make sure to configure your output format accordingly for those cases.
|
||||
:::
|
||||
|
||||
#### Chunked mode
|
||||
@@ -545,4 +545,4 @@ The promise returned by `getMimeType` only resolves once the precise codec strin
|
||||
If you don't care about specific track codecs, you can instead use the simpler [`mimeType`](./output-formats#output-format-properties) property on the `Output`'s format:
|
||||
```ts
|
||||
output.format.mimeType; // => string
|
||||
```
|
||||
```
|
||||
|
||||
+2
-3
@@ -102,14 +102,13 @@ const sponsors = {
|
||||
{ image: '/sponsors/screen-studio.webp', name: 'Screen Studio', url: 'https://screen.studio/' },
|
||||
{ image: '/sponsors/tella.svg', name: 'Tella', url: 'https://www.tella.com/' },
|
||||
],
|
||||
silver: [
|
||||
{ image: '/sponsors/ponder.png', name: 'Ponder', url: 'https://ponder.ai/' },
|
||||
],
|
||||
silver: [],
|
||||
bronze: [
|
||||
{ image: '/sponsors/elevenlabs.png', name: 'ElevenLabs', url: 'https://elevenlabs.io/' },
|
||||
{ image: '/sponsors/rve.png', name: 'React Video Editor', url: 'https://www.reactvideoeditor.com/' },
|
||||
{ image: '/sponsors/mux.jpg', name: 'Mux', url: 'https://www.mux.com/' },
|
||||
{ image: '/sponsors/jellypod.png', name: 'Jellypod', url: 'https://jellypod.ai/' },
|
||||
{ image: '/sponsors/photocircle.png', name: 'PhotoCircle', url: 'https://www.photocircleapp.com/' },
|
||||
],
|
||||
individual: [
|
||||
{ image: '/sponsors/pintura-labs.png', name: 'Pintura Labs', url: 'https://pqina.nl/pintura/' },
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Generated
+13
-13
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mediabunny",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mediabunny",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"license": "MPL-2.0",
|
||||
"workspaces": [
|
||||
".",
|
||||
@@ -10963,9 +10963,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/turbores": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/turbores/-/turbores-1.1.2.tgz",
|
||||
"integrity": "sha512-6k3XH+sG8uOCJEBamKhAXFwIAfLqz7LAQr2mmEx2UBl6zC+4XAGQtp1fGEugnVl2w8Ec5GOUvD+b0tyxUJvmHg==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/turbores/-/turbores-1.2.1.tgz",
|
||||
"integrity": "sha512-HcnLHiUggT2YXmnTpUdT3JHnkGWvdJlW9XBPLKZBBB3X5oOEuBaHF99cW+H+CSxnhM+rth2qDTCAteIKhKwiGQ==",
|
||||
"license": "MPL-2.0",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
@@ -12894,7 +12894,7 @@
|
||||
},
|
||||
"packages/aac-encoder": {
|
||||
"name": "@mediabunny/aac-encoder",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "^1.40.1"
|
||||
@@ -12909,7 +12909,7 @@
|
||||
},
|
||||
"packages/ac3": {
|
||||
"name": "@mediabunny/ac3",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "^1.40.1"
|
||||
@@ -12924,7 +12924,7 @@
|
||||
},
|
||||
"packages/flac-encoder": {
|
||||
"name": "@mediabunny/flac-encoder",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "^1.40.1"
|
||||
@@ -12939,7 +12939,7 @@
|
||||
},
|
||||
"packages/mp3-encoder": {
|
||||
"name": "@mediabunny/mp3-encoder",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "^1.40.1"
|
||||
@@ -12954,10 +12954,10 @@
|
||||
},
|
||||
"packages/prores": {
|
||||
"name": "@mediabunny/prores",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"turbores": "^1.1.2"
|
||||
"turbores": "^1.2.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
@@ -12969,10 +12969,10 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@mediabunny/server",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@mediabunny/prores": "^1.50.0",
|
||||
"@mediabunny/prores": "^1.50.6",
|
||||
"node-av": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "mediabunny",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
@@ -56,10 +56,10 @@
|
||||
"examples:build": "vite build",
|
||||
"fix-build-import-paths": "tsx scripts/add-import-extensions.ts",
|
||||
"append-namespace": "echo 'export as namespace Mediabunny;' >> dist/mediabunny.d.ts",
|
||||
"bump-patch": "npm version patch --no-git-tag-version --workspaces",
|
||||
"bump-minor": "npm version minor --no-git-tag-version --workspaces",
|
||||
"bump-major": "npm version major --no-git-tag-version --workspaces",
|
||||
"set-version": "npm version --no-git-tag-version --workspaces"
|
||||
"bump-patch": "npm version patch --no-git-tag-version --workspaces && tsx scripts/sync-workspace-deps.ts",
|
||||
"bump-minor": "npm version minor --no-git-tag-version --workspaces && tsx scripts/sync-workspace-deps.ts",
|
||||
"bump-major": "npm version major --no-git-tag-version --workspaces && tsx scripts/sync-workspace-deps.ts",
|
||||
"set-version": "npm version --no-git-tag-version --workspaces && tsx scripts/sync-workspace-deps.ts"
|
||||
},
|
||||
"license": "MPL-2.0",
|
||||
"repository": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@mediabunny/aac-encoder",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"description": "AAC encoder extension for Mediabunny, based on FFmpeg.",
|
||||
"main": "./dist/bundles/mediabunny-aac-encoder.mjs",
|
||||
"module": "./dist/bundles/mediabunny-aac-encoder.mjs",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@mediabunny/ac3",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"description": "AC-3 and E-AC-3 (Dolby Digital) decoder and encoder extension for Mediabunny, based on FFmpeg.",
|
||||
"main": "./dist/bundles/mediabunny-ac3.mjs",
|
||||
"module": "./dist/bundles/mediabunny-ac3.mjs",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@mediabunny/flac-encoder",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"description": "FLAC encoder extension for Mediabunny, based on libFLAC.",
|
||||
"main": "./dist/bundles/mediabunny-flac-encoder.mjs",
|
||||
"module": "./dist/bundles/mediabunny-flac-encoder.mjs",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@mediabunny/mp3-encoder",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
||||
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@mediabunny/prores",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"description": "Apple ProRes decoder extension for Mediabunny, based on TurboRes.",
|
||||
"main": "./dist/bundles/mediabunny-prores.mjs",
|
||||
"module": "./dist/bundles/mediabunny-prores.mjs",
|
||||
@@ -34,7 +34,7 @@
|
||||
"url": "https://github.com/sponsors/Vanilagy"
|
||||
},
|
||||
"dependencies": {
|
||||
"turbores": "^1.1.2"
|
||||
"turbores": "^1.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mediabunny": "^1.49.0"
|
||||
|
||||
@@ -83,16 +83,12 @@ class ProresDecoder extends CustomVideoDecoder {
|
||||
async decode(packet: EncodedPacket) {
|
||||
assert(this.decoder);
|
||||
|
||||
if (this.decoder.useSharedMemory) {
|
||||
await this.runDecode(packet);
|
||||
} else {
|
||||
while (this.decoder.decodeQueueSize >= this.decoder.concurrency) {
|
||||
await this.decoder.dequeued;
|
||||
}
|
||||
|
||||
void this.runDecode(packet)
|
||||
.catch(error => this.onError(error));
|
||||
while (this.decoder.desiredSize <= 0) {
|
||||
await this.decoder.dequeued;
|
||||
}
|
||||
|
||||
void this.runDecode(packet)
|
||||
.catch(error => this.onError(error));
|
||||
}
|
||||
|
||||
private async runDecode(packet: EncodedPacket) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@mediabunny/server",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.50.2",
|
||||
"version": "1.50.7",
|
||||
"description": "Adds full video and audio decoder and encoder support to Mediabunny for use in server-side environments (Node, Bun, Deno). Based on NodeAV.",
|
||||
"main": "./dist/bundles/mediabunny-server.cjs",
|
||||
"module": "./dist/bundles/mediabunny-server.mjs",
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"node-av": "^6.0.0",
|
||||
"@mediabunny/prores": "^1.50.0"
|
||||
"@mediabunny/prores": "^1.50.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mediabunny": "^1.45.0"
|
||||
|
||||
+5
-5
@@ -103,7 +103,7 @@ const mp3EncoderVariants = await createVariants(
|
||||
'packages/mp3-encoder/src/index.ts',
|
||||
'MediabunnyMp3Encoder',
|
||||
'packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder',
|
||||
'js', // The bundles are purely for the browser, not for Node (due to the peer dependecy)
|
||||
'js', // The bundles are purely for the browser, not for Node (due to the peer dependency)
|
||||
{
|
||||
plugins: [
|
||||
PluginExternalGlobal.externalGlobalPlugin({
|
||||
@@ -134,7 +134,7 @@ const ac3Variants = await createVariants(
|
||||
'packages/ac3/src/index.ts',
|
||||
'MediabunnyAc3',
|
||||
'packages/ac3/dist/bundles/mediabunny-ac3',
|
||||
'js', // The bundles are purely for the browser, not for Node (due to the peer dependecy)
|
||||
'js', // The bundles are purely for the browser, not for Node (due to the peer dependency)
|
||||
{
|
||||
plugins: [
|
||||
PluginExternalGlobal.externalGlobalPlugin({
|
||||
@@ -165,7 +165,7 @@ const aacEncoderVariants = await createVariants(
|
||||
'packages/aac-encoder/src/index.ts',
|
||||
'MediabunnyAacEncoder',
|
||||
'packages/aac-encoder/dist/bundles/mediabunny-aac-encoder',
|
||||
'js', // The bundles are purely for the browser, not for Node (due to the peer dependecy)
|
||||
'js', // The bundles are purely for the browser, not for Node (due to the peer dependency)
|
||||
{
|
||||
plugins: [
|
||||
PluginExternalGlobal.externalGlobalPlugin({
|
||||
@@ -196,7 +196,7 @@ const flacEncoderVariants = await createVariants(
|
||||
'packages/flac-encoder/src/index.ts',
|
||||
'MediabunnyFlacEncoder',
|
||||
'packages/flac-encoder/dist/bundles/mediabunny-flac-encoder',
|
||||
'js', // The bundles are purely for the browser, not for Node (due to the peer dependecy)
|
||||
'js', // The bundles are purely for the browser, not for Node (due to the peer dependency)
|
||||
{
|
||||
plugins: [
|
||||
PluginExternalGlobal.externalGlobalPlugin({
|
||||
@@ -227,7 +227,7 @@ const proresVariants = await createVariants(
|
||||
'packages/prores/src/index.ts',
|
||||
'MediabunnyProres',
|
||||
'packages/prores/dist/bundles/mediabunny-prores',
|
||||
'js', // The bundles are purely for the browser, not for Node (due to the peer dependecy)
|
||||
'js', // The bundles are purely for the browser, not for Node (due to the peer dependency)
|
||||
{
|
||||
plugins: [
|
||||
PluginExternalGlobal.externalGlobalPlugin({
|
||||
|
||||
@@ -127,25 +127,22 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string, dry = false)
|
||||
const declaration = exportSymbol.valueDeclaration || exportSymbol.declarations?.[0];
|
||||
if (!declaration) return;
|
||||
|
||||
// If it's a reexport, follow it recursively
|
||||
// If it's a reexport, resolve it to the underlying symbol (following chains of aliases,
|
||||
// e.g. a re-export of a re-export) rather than recursing into the whole module it lives in
|
||||
// -- `getExportsOfModule` above already gives us one entry per exported symbol, so a module
|
||||
// recursion here would revisit (and duplicate) every other export of that module too.
|
||||
if (exportSymbol.flags & ts.SymbolFlags.Alias) {
|
||||
const aliasedSymbol = typeChecker.getAliasedSymbol(exportSymbol);
|
||||
let aliasedSymbol = typeChecker.getAliasedSymbol(exportSymbol);
|
||||
while (aliasedSymbol.flags & ts.SymbolFlags.Alias) {
|
||||
aliasedSymbol = typeChecker.getAliasedSymbol(aliasedSymbol);
|
||||
}
|
||||
const aliasedDeclaration = aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0];
|
||||
if (aliasedDeclaration) {
|
||||
const sourceFile = aliasedDeclaration.getSourceFile();
|
||||
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
||||
// If the aliased declaration lives in a module we've already visited (e.g. a
|
||||
// same-file `export type { Foo }` re-export of a local declaration), recursing
|
||||
// won't reach it, so add the alias symbol directly. Otherwise follow the reexport.
|
||||
if (moduleSymbol && !visited.has(moduleSymbol)) {
|
||||
symbols.push(...getAllExportedSymbols(moduleSymbol, visited));
|
||||
} else {
|
||||
// Push the aliased symbol (not the alias) so downstream sees the real
|
||||
// declaration and its JSDoc rather than the empty ExportSpecifier.
|
||||
const hasPublicTag = ts.getJSDocTags(aliasedDeclaration).some(tag => tag.tagName.text === 'public');
|
||||
if (hasPublicTag) {
|
||||
symbols.push(aliasedSymbol);
|
||||
}
|
||||
// Push the aliased symbol (not the alias) so downstream sees the real
|
||||
// declaration and its JSDoc rather than the empty ExportSpecifier.
|
||||
const hasPublicTag = ts.getJSDocTags(aliasedDeclaration).some(tag => tag.tagName.text === 'public');
|
||||
if (hasPublicTag) {
|
||||
symbols.push(aliasedSymbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
// After `npm version --workspaces` bumps each package's version, this rewrites dependency ranges that point at sibling
|
||||
// workspaces so they follow along.
|
||||
|
||||
const root = path.join(import.meta.dirname, '..');
|
||||
|
||||
const workspaceDirs = ['.', ...fs.readdirSync(path.join(root, 'packages')).map(x => `packages/${x}`)]
|
||||
.filter(dir => fs.existsSync(path.join(root, dir, 'package.json')));
|
||||
|
||||
type Manifest = {
|
||||
name: string;
|
||||
version: string;
|
||||
dependencies?: Record<string, string>;
|
||||
};
|
||||
|
||||
const manifests = workspaceDirs.map((dir) => {
|
||||
const filePath = path.join(root, dir, 'package.json');
|
||||
return { filePath, json: JSON.parse(fs.readFileSync(filePath, 'utf8')) as Manifest };
|
||||
});
|
||||
|
||||
const versions = new Map(manifests.map(({ json }) => [json.name, json.version]));
|
||||
|
||||
for (const { filePath, json } of manifests) {
|
||||
let changed = false;
|
||||
|
||||
for (const name of Object.keys(json.dependencies ?? {})) {
|
||||
const version = versions.get(name);
|
||||
if (version && json.dependencies![name] !== `^${version}`) {
|
||||
json.dependencies![name] = `^${version}`;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
fs.writeFileSync(filePath, JSON.stringify(json, null, 2) + '\n');
|
||||
console.log(`Synced workspace dependency ranges in ${path.relative(root, filePath)}`);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1878,7 +1878,7 @@ export type Av1CodecInfo = {
|
||||
chromaSamplePosition: number;
|
||||
};
|
||||
|
||||
/** Iterates over all OBUs in an AV1 packet bistream. */
|
||||
/** Iterates over all OBUs in an AV1 packet bitstream. */
|
||||
export const iterateAv1PacketObus = function* (packet: Uint8Array) {
|
||||
// https://aomediacodec.github.io/av1-spec/av1-spec.pdf
|
||||
|
||||
|
||||
+1
-1
@@ -838,7 +838,7 @@ export class Conversion {
|
||||
if (this._options.trim?.start !== undefined) {
|
||||
this._startTimestamp = this._options.trim.start;
|
||||
} else {
|
||||
// Compute the start timestamp from the set of filtered tracks. Techncially these can still be narrowed
|
||||
// Compute the start timestamp from the set of filtered tracks. Technically these can still be narrowed
|
||||
// down later due to discarded tracks, but we need to fix the start timestamp now due to track processing
|
||||
// depending on it.
|
||||
this._startTimestamp = Math.max(
|
||||
|
||||
@@ -186,7 +186,7 @@ export class FlacDemuxer extends Demuxer {
|
||||
// description is required, and has to be the following:
|
||||
// 1. The bytes 0x66 0x4C 0x61 0x43 ("fLaC" in ASCII)
|
||||
// 2. A metadata block (called the STREAMINFO block) as described in section 7 of [FLAC]
|
||||
// 3. Optionaly (sic) other metadata blocks, that are not used by the specification
|
||||
// 3. Optionally (sic) other metadata blocks, that are not used by the specification
|
||||
|
||||
bitstream.skipBits(16 * 8); // md5 hash
|
||||
|
||||
@@ -296,7 +296,7 @@ export class FlacDemuxer extends Demuxer {
|
||||
// we expect that there are at least `minimumFrameSize` bytes left in the file
|
||||
|
||||
// Ideally we also want to validate the next header is valid
|
||||
// to throw out an accidential sync word
|
||||
// to throw out an accidental sync word
|
||||
|
||||
// The shortest valid FLAC header I can think of, based off the code
|
||||
// of readFlacFrameHeader:
|
||||
|
||||
+2
-1
@@ -544,7 +544,8 @@ export class InputVideoTrack extends InputTrack {
|
||||
}
|
||||
|
||||
async hasOnlyKeyPackets() {
|
||||
return (await this._backing.getHasOnlyKeyPackets?.()) ?? false;
|
||||
return (await this._backing.getHasOnlyKeyPackets?.())
|
||||
?? (await this._backing.getCodec() === 'prores'); // Only ProRes is fully intra-frame
|
||||
}
|
||||
|
||||
/** Returns the width in pixels of the track's coded samples, before any transformations or rotations. */
|
||||
|
||||
+11
-4
@@ -1011,15 +1011,22 @@ class VideoDecoderWrapper extends DecoderWrapper<VideoSample> {
|
||||
if (this.codec === 'avc') {
|
||||
// Workaround for https://issues.chromium.org/issues/470109459
|
||||
const filteredNalUnits: Uint8Array[] = [];
|
||||
let hasFrameData = false;
|
||||
|
||||
for (const loc of iterateAvcNalUnits(packet.data, this.decoderConfig)) {
|
||||
const type = extractNalUnitTypeForAvc(packet.data[loc.offset]!);
|
||||
hasFrameData ||= type >= 1 && type <= 5;
|
||||
|
||||
if (type === AvcNalUnitType.AUD) {
|
||||
// If packets contain an AUD and have NALUs before it, this trips up Chromium's key frame
|
||||
// detector. Clear the NALUs if an AUD is encountered.
|
||||
// https://github.com/Vanilagy/mediabunny/issues/396
|
||||
filteredNalUnits.length = 0;
|
||||
if (hasFrameData) {
|
||||
// Already has actual frame data, so treat an AUD as simply the end of the packet
|
||||
break;
|
||||
} else {
|
||||
// If packets contain an AUD and have NALUs before it, this trips up Chromium's key
|
||||
// frame detector. Clear the NALUs if an AUD is encountered.
|
||||
// https://github.com/Vanilagy/mediabunny/issues/396
|
||||
filteredNalUnits.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// These trip up Chromium's key frame detection, so let's strip them
|
||||
|
||||
@@ -638,11 +638,11 @@ class OggAudioTrackBacking implements InputAudioTrackBacking {
|
||||
|
||||
const lowPages: Page[] = [lowPage];
|
||||
|
||||
// First, let's perform a binary serach (bisection search) on the file to find the approximate page where
|
||||
// First, let's perform a binary search (bisection search) on the file to find the approximate page where
|
||||
// we'll find the packet. We want to find a page whose end packet position is less than or equal to the
|
||||
// packet position we're searching for.
|
||||
|
||||
// Outer loop: Does the binary serach
|
||||
// Outer loop: Does the binary search
|
||||
outer:
|
||||
while (lowPage.headerStartPos + lowPage.totalSize < high) {
|
||||
const low = lowPage.headerStartPos;
|
||||
|
||||
+2
-2
@@ -172,7 +172,7 @@ export class AudioResampler {
|
||||
|
||||
async add(audioSample: AudioSample) {
|
||||
if (this.sourceSampleRate === null) {
|
||||
// This is the first sample, so let's init the missing data. Initting the sample rate from the decoded
|
||||
// This is the first sample, so let's init the missing data. Initing the sample rate from the decoded
|
||||
// sample is more reliable than using the file's metadata, because decoders are free to emit any sample rate
|
||||
// they see fit.
|
||||
this.sourceSampleRate = audioSample.sampleRate;
|
||||
@@ -199,7 +199,7 @@ export class AudioResampler {
|
||||
const inputEndTime = inputStartTime + audioSample.duration;
|
||||
|
||||
// Compute which output frames are affected by this sample
|
||||
const outputStartFrame = Math.floor(inputStartTime * this.targetSampleRate);
|
||||
const outputStartFrame = Math.floor((inputStartTime - 1 / this.sourceSampleRate) * this.targetSampleRate) + 1;
|
||||
const outputEndFrame = Math.ceil(inputEndTime * this.targetSampleRate);
|
||||
|
||||
for (let outputFrame = outputStartFrame; outputFrame < outputEndFrame; outputFrame++) {
|
||||
|
||||
+7
-3
@@ -2129,7 +2129,7 @@ class ReadOrchestrator {
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (worker.running || this.workers.length >= this.options.maxWorkerCount) {
|
||||
if (worker.running) {
|
||||
// Rare, but can happen with multiple concurrent reads. In this case, don't do anything.
|
||||
return;
|
||||
}
|
||||
@@ -2144,15 +2144,19 @@ class ReadOrchestrator {
|
||||
}
|
||||
|
||||
const queuedRead = this.queuedReads[oldestIndex]!;
|
||||
this.queuedReads.splice(oldestIndex, 1);
|
||||
|
||||
const newWorker = this.createWorker(
|
||||
queuedRead.hole.start,
|
||||
queuedRead.hole.end,
|
||||
queuedRead.strictTarget,
|
||||
);
|
||||
assert(newWorker); // We just freed up a worker, so this should never fail
|
||||
if (!newWorker) {
|
||||
// In high-contention cases, it could be that we've already reached max worker count, so in this
|
||||
// case we don't do anything.
|
||||
return;
|
||||
}
|
||||
|
||||
this.queuedReads.splice(oldestIndex, 1);
|
||||
newWorker.pendingSlices = queuedRead.pendingSlices;
|
||||
this.runWorker(newWorker);
|
||||
}
|
||||
|
||||
@@ -177,8 +177,8 @@ test('HLS track assignability is kept #1', async () => {
|
||||
|
||||
await output.finalize();
|
||||
|
||||
const masterPlayist = sanitizeMasterPlaylist(new TextDecoder().decode(files.get('master.m3u8')));
|
||||
expect(masterPlayist.match(/\.m3u8/g)?.length).toBe(1);
|
||||
const masterPlaylist = sanitizeMasterPlaylist(new TextDecoder().decode(files.get('master.m3u8')));
|
||||
expect(masterPlaylist.match(/\.m3u8/g)?.length).toBe(1);
|
||||
|
||||
using input = new Input({
|
||||
formats: ALL_FORMATS,
|
||||
@@ -208,8 +208,8 @@ test('HLS track assignability is kept #1', async () => {
|
||||
const conversion = await Conversion.init({ input, output: newOutput });
|
||||
await conversion.execute();
|
||||
|
||||
const newMasterPlayist = sanitizeMasterPlaylist(new TextDecoder().decode(files.get('new/master.m3u8')));
|
||||
expect(newMasterPlayist).toBe(masterPlayist);
|
||||
const newMasterPlaylist = sanitizeMasterPlaylist(new TextDecoder().decode(files.get('new/master.m3u8')));
|
||||
expect(newMasterPlaylist).toBe(masterPlaylist);
|
||||
});
|
||||
|
||||
test('HLS track assignability is kept #2', async () => {
|
||||
@@ -256,8 +256,8 @@ test('HLS track assignability is kept #2', async () => {
|
||||
|
||||
await output.finalize();
|
||||
|
||||
const masterPlayist = sanitizeMasterPlaylist(new TextDecoder().decode(files.get('master.m3u8')));
|
||||
expect(masterPlayist.match(/\.m3u8/g)?.length).toBe(2);
|
||||
const masterPlaylist = sanitizeMasterPlaylist(new TextDecoder().decode(files.get('master.m3u8')));
|
||||
expect(masterPlaylist.match(/\.m3u8/g)?.length).toBe(2);
|
||||
|
||||
using input = new Input({
|
||||
formats: ALL_FORMATS,
|
||||
@@ -287,8 +287,8 @@ test('HLS track assignability is kept #2', async () => {
|
||||
const conversion = await Conversion.init({ input, output: newOutput });
|
||||
await conversion.execute();
|
||||
|
||||
const newMasterPlayist = sanitizeMasterPlaylist(new TextDecoder().decode(files.get('new/master.m3u8')));
|
||||
expect(newMasterPlayist).toBe(masterPlayist);
|
||||
const newMasterPlaylist = sanitizeMasterPlaylist(new TextDecoder().decode(files.get('new/master.m3u8')));
|
||||
expect(newMasterPlaylist).toBe(masterPlaylist);
|
||||
});
|
||||
|
||||
test('HLS track assignability can be overridden', async () => {
|
||||
@@ -335,8 +335,8 @@ test('HLS track assignability can be overridden', async () => {
|
||||
|
||||
await output.finalize();
|
||||
|
||||
const masterPlayist = sanitizeMasterPlaylist(new TextDecoder().decode(files.get('master.m3u8')));
|
||||
expect(masterPlayist.match(/\.m3u8/g)?.length).toBe(2);
|
||||
const masterPlaylist = sanitizeMasterPlaylist(new TextDecoder().decode(files.get('master.m3u8')));
|
||||
expect(masterPlaylist.match(/\.m3u8/g)?.length).toBe(2);
|
||||
|
||||
using input = new Input({
|
||||
formats: ALL_FORMATS,
|
||||
@@ -371,9 +371,9 @@ test('HLS track assignability can be overridden', async () => {
|
||||
});
|
||||
await conversion.execute();
|
||||
|
||||
const newMasterPlayist = sanitizeMasterPlaylist(new TextDecoder().decode(files.get('new/master.m3u8')));
|
||||
expect(newMasterPlayist).not.toBe(masterPlayist);
|
||||
expect(newMasterPlayist.match(/\.m3u8/g)?.length).toBe(1);
|
||||
const newMasterPlaylist = sanitizeMasterPlaylist(new TextDecoder().decode(files.get('new/master.m3u8')));
|
||||
expect(newMasterPlaylist).not.toBe(masterPlaylist);
|
||||
expect(newMasterPlaylist.match(/\.m3u8/g)?.length).toBe(1);
|
||||
});
|
||||
|
||||
test('Fractional audio sample boundary', async () => {
|
||||
|
||||
@@ -354,7 +354,7 @@ test('MPEG-TS seeking race condition test', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('MPEG-TS video key packets', { timeout: 10_000 }, async () => {
|
||||
test('MPEG-TS video key packets', { timeout: 30_000 }, async () => {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
using input = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '../public/trim-buck-bunny-ffmpeg.ts')),
|
||||
|
||||
Reference in New Issue
Block a user