feat: first step
This commit is contained in:
commit
764addd7f6
193
.gitignore
vendored
Normal file
193
.gitignore
vendored
Normal file
@ -0,0 +1,193 @@
|
||||
|
||||
# Created by https://www.gitignore.io/api/vim,node,jetbrains+all,visualstudiocode
|
||||
|
||||
### JetBrains+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
### JetBrains+all Patch ###
|
||||
# Ignores the whole .idea folder and all .iml files
|
||||
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||
|
||||
.idea/
|
||||
|
||||
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||
|
||||
*.iml
|
||||
modules.xml
|
||||
.idea/misc.xml
|
||||
*.ipr
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
### Vim ###
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
# Session
|
||||
Session.vim
|
||||
|
||||
# Temporary
|
||||
.netrwhist
|
||||
*~
|
||||
|
||||
# Persistent undo
|
||||
[._]*.un~
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
|
||||
# End of https://www.gitignore.io/api/vim,node,jetbrains+all,visualstudiocode
|
||||
|
||||
# babel generated folder now; no need for it to be kept
|
||||
lib/
|
||||
/flow-typed/npm/
|
||||
# https://atom.io/packages/atomic-management
|
||||
.atom/*
|
||||
|
||||
|
||||
# Added by cargo
|
||||
|
||||
**/target
|
||||
/.vitest
|
||||
**/dist
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
Copyright (c) 2025 Yeheng Zhou (konoebml)
|
||||
|
||||
Copyright (c) 2023 Liam Dyer (ebml-web-stream)
|
||||
|
||||
Copyright (c) 2013-2018 Mark Schmale and contributors (ebml-stream)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
|
93
README.md
Normal file
93
README.md
Normal file
@ -0,0 +1,93 @@
|
||||
<h1 align="center">
|
||||
<img src="./assets/konoebml-512x512.webp" alt="logo" height=180 />
|
||||
<br />
|
||||
<b>Konoebml</b>
|
||||
<div align="center"><img src="https://img.shields.io/badge/status-beta-blue" alt="status-badge" /></div>
|
||||
</h1>
|
||||
|
||||
<p align="center"><b>A modern JavaScript implementation of RFC8794 (EBML). </b></p>
|
||||
|
||||
## Note
|
||||
|
||||
[EBML][EBML] stands for Extensible Binary Meta-Language and is somewhat of a binary version of XML. It's used for container formats like [WebM][webm] or [MKV][mkv].
|
||||
|
||||
This package is serving as a fork with extensive rewrites and enhancements to [ebml-web-stream][ebml-web-stream] and [ebml-stream][ebml-stream], providing:
|
||||
|
||||
- better [unknown size vint][unknown size vint] support
|
||||
- bigint support for vint, unsigned and signed int data type
|
||||
- better error types
|
||||
|
||||
# Install
|
||||
|
||||
Install via NPM:
|
||||
|
||||
```bash
|
||||
npm install konoebml
|
||||
```
|
||||
|
||||
# Usage and Examples
|
||||
|
||||
This example reads a media file into memory and decodes it.
|
||||
|
||||
```js
|
||||
import fs from 'node:fs/promises';
|
||||
import {
|
||||
ReadableStream,
|
||||
WritableStream,
|
||||
type TransformStream,
|
||||
} from 'node:stream/web';
|
||||
import { EbmlStreamDecoder } from 'konoebml';
|
||||
|
||||
async function main() {
|
||||
const fileBuffer = await fs.readFile('media/test.webm');
|
||||
await new ReadableStream({
|
||||
pull(controller) {
|
||||
controller.enqueue(fileBuffer);
|
||||
controller.close();
|
||||
},
|
||||
})
|
||||
.pipeThrough(new EbmlStreamDecoder() as unknown as TransformStream)
|
||||
.pipeTo(new WritableStream({ write: console.log }));
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
**Todo: add more docs and tests**
|
||||
|
||||
# State of this project
|
||||
|
||||
Parsing and writing should both work. If something is broken, please create [an issue][new-issue].
|
||||
|
||||
Any additional feature requests can also be submitted as [an issue][new-issue].
|
||||
|
||||
If any well-known tags have special parsing/encoding rules or data structures that aren't implemented, pull requests are welcome!
|
||||
|
||||
# License
|
||||
|
||||
[MIT](./LICENSE)
|
||||
|
||||
# Other Contributors
|
||||
|
||||
(in alphabetical order)
|
||||
|
||||
* [Austin Blake](https://github.com/austinleroy)
|
||||
* [Chris Price](https://github.com/chrisprice)
|
||||
* [Davy Van Deursen](https://github.com/dvdeurse)
|
||||
* [Ed Markowski](https://github.com/siphontv)
|
||||
* [Jonathan Sifuentes](https://github.com/jayands)
|
||||
* [Liam Dyer](https://github.com/Saghen)
|
||||
* [Manuel Wiedenmann](https://github.com/fsmanuel)
|
||||
* [Mark Schmale](https://github.com/themasch)
|
||||
* [Mathias Buus](https://github.com/mafintosh)
|
||||
* [Max Ogden](https://github.com/maxogden)
|
||||
* [Oliver Jones](https://github.com/OllieJones)
|
||||
* [Oliver Walzer](https://github.com/owcd)
|
||||
|
||||
[EBML]: http://ebml.sourceforge.net/
|
||||
[mkv]: http://www.matroska.org/technical/specs/index.html
|
||||
[webm]: https://www.webmproject.org/
|
||||
[new-issue]: https://github.com/saghen/ebml-web-stream/issues/new
|
||||
[unknown size vint]: (https://www.rfc-editor.org/rfc/rfc8794.html#name-unknown-data-size)
|
||||
[ebml-web-stream]: (https://github.com/Saghen/ebml-web-stream)
|
||||
[ebml-stream]: (https://github.com/austinleroy/node-ebml)
|
BIN
assets/konoebml-512x512.png
Normal file
BIN
assets/konoebml-512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 339 KiB |
BIN
assets/konoebml-512x512.webp
Normal file
BIN
assets/konoebml-512x512.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 94 KiB |
62
biome.jsonc
Normal file
62
biome.jsonc
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"extends": [
|
||||
"ultracite"
|
||||
],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"noNonNullAssertion": "off",
|
||||
"noParameterAssign": "off",
|
||||
"useFilenamingConvention": "off",
|
||||
"noParameterProperties": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off"
|
||||
},
|
||||
"complexity": {
|
||||
"noForEach": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"noUnusedImports": {
|
||||
"fix": "none",
|
||||
"level": "warn"
|
||||
}
|
||||
},
|
||||
"nursery": {
|
||||
"noEnum": "off",
|
||||
"useConsistentMemberAccessibility": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"ignore": [
|
||||
".vscode/*.json"
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"include": [
|
||||
"tests/**"
|
||||
],
|
||||
"javascript": {
|
||||
"globals": [
|
||||
"describe",
|
||||
"beforeEach",
|
||||
"it",
|
||||
"expect"
|
||||
]
|
||||
},
|
||||
"linter": {
|
||||
"rules": {
|
||||
"performance": {
|
||||
"useTopLevelRegex": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noMisplacedAssertion": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
21
examples/playground.ts
Normal file
21
examples/playground.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import {
|
||||
ReadableStream,
|
||||
WritableStream,
|
||||
type TransformStream,
|
||||
} from 'node:stream/web';
|
||||
import { EbmlStreamDecoder } from 'konoebml';
|
||||
|
||||
async function main() {
|
||||
const fileBuffer = await fs.readFile('media/test.webm');
|
||||
await new ReadableStream({
|
||||
pull(controller) {
|
||||
controller.enqueue(fileBuffer);
|
||||
controller.close();
|
||||
},
|
||||
})
|
||||
.pipeThrough(new EbmlStreamDecoder() as unknown as TransformStream)
|
||||
.pipeTo(new WritableStream({ write: console.log }));
|
||||
}
|
||||
|
||||
main();
|
BIN
media/audiosample.webm
Normal file
BIN
media/audiosample.webm
Normal file
Binary file not shown.
BIN
media/test.webm
Normal file
BIN
media/test.webm
Normal file
Binary file not shown.
BIN
media/video-webm-codecs-avc1-42E01E.webm
Normal file
BIN
media/video-webm-codecs-avc1-42E01E.webm
Normal file
Binary file not shown.
BIN
media/video-webm-codecs-vp8.webm
Normal file
BIN
media/video-webm-codecs-vp8.webm
Normal file
Binary file not shown.
84
package.json
Normal file
84
package.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"name": "konoebml",
|
||||
"version": "0.1.0-rc.1",
|
||||
"description": "A modern JavaScript implementation of EBML RFC8794",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"type": "module",
|
||||
"maintainers": [
|
||||
"Yeheng Zhou <master@evernightfireworks.com>"
|
||||
],
|
||||
"files": [
|
||||
"dist",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rslib build",
|
||||
"dev": "rslib build --watch",
|
||||
"test": "vitest --coverage",
|
||||
"test-ci": "vitest --watch=false --coverage",
|
||||
"prepublishOnly": "npm run build",
|
||||
"lint": "ultracite lint",
|
||||
"format": "ultracite format",
|
||||
"playground": "tsx --tsconfig=./tsconfig.example.json ./examples/playground.ts"
|
||||
},
|
||||
"repository": "github:dumtruck/konoebml",
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"ebml",
|
||||
"webm",
|
||||
"mkv",
|
||||
"matroska",
|
||||
"format",
|
||||
"rfc8794"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@rslib/core": "^0.5.3",
|
||||
"@swc/core": "^1.11.8",
|
||||
"@types/jasmine": "^5.1.7",
|
||||
"@types/node": "^22.13.10",
|
||||
"@vitest/coverage-v8": "^3.0.8",
|
||||
"cross-env": "^7.0.3",
|
||||
"happy-dom": "^17.4.3",
|
||||
"rimraf": "^6.0.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.2",
|
||||
"ultracite": "^4.1.21",
|
||||
"unplugin-swc": "^1.5.1",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.0.8"
|
||||
},
|
||||
"contributors": [
|
||||
"Yeheng Zhou <master@evernightfireworks.com>",
|
||||
"Liam Dyer <liamcdyer@gmail.com>",
|
||||
"Austin Blake <austin.leroy@hotmail.com>",
|
||||
"Chris Price <price.c@gmail.com>",
|
||||
"Davy Van Deursen <d.vandeursen@evs.com>",
|
||||
"Ed Markowski <siphon@protonmail.com>",
|
||||
"Jonathan Sifuentes <jayands.dev@gmail.com>",
|
||||
"Manuel Wiedenmann <manuel@funkensturm.de>",
|
||||
"Mathias Buus <mathiasbuus@gmail.com>",
|
||||
"Max Ogden <max@maxogden.com>",
|
||||
"Oliver Walzer <walzer@incuray.com>",
|
||||
"greenkeeperio-bot <support@greenkeeper.io>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/dumtruck/konoebml/issues"
|
||||
},
|
||||
"homepage": "https://github.com/dumtruck/konoebml#readme",
|
||||
"dependencies": {
|
||||
"mnemonist": "^0.40.3",
|
||||
"type-fest": "^4.37.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@swc/core"
|
||||
]
|
||||
}
|
||||
}
|
2338
pnpm-lock.yaml
generated
Normal file
2338
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
rslib.config.ts
Normal file
38
rslib.config.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { defineConfig } from '@rslib/core';
|
||||
|
||||
export default defineConfig({
|
||||
source: {
|
||||
tsconfigPath: './tsconfig.lib.json',
|
||||
},
|
||||
lib: [
|
||||
{
|
||||
format: 'esm',
|
||||
syntax: 'es2021',
|
||||
bundle: false,
|
||||
dts: {
|
||||
bundle: false,
|
||||
build: false,
|
||||
distPath: './dist',
|
||||
},
|
||||
source: {
|
||||
entry: {
|
||||
index: ['src/**/*.ts'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
format: 'cjs',
|
||||
syntax: 'es2021',
|
||||
dts: false,
|
||||
bundle: true,
|
||||
source: {
|
||||
entry: {
|
||||
index: './src/index.ts',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
output: {
|
||||
target: 'web',
|
||||
},
|
||||
});
|
12
src/adapters/index.ts
Normal file
12
src/adapters/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { Promisable } from 'type-fest';
|
||||
|
||||
export type FileDataViewController = {
|
||||
getOffset(): number;
|
||||
seek(offset: number): Promisable<number>;
|
||||
read(
|
||||
offset: number,
|
||||
length?: number,
|
||||
exactLength?: boolean
|
||||
): Promise<DataView>;
|
||||
peek(offset: number): Promise<DataView | null>;
|
||||
};
|
123
src/decode-utils.ts
Normal file
123
src/decode-utils.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { type EbmlTagIdType, isEbmlMasterTagId } from './models/enums';
|
||||
import type { EbmlTagTrait } from './models/tag-trait';
|
||||
import type { FileDataViewController } from './adapters';
|
||||
import {
|
||||
checkVintSafeSize,
|
||||
dataViewSlice,
|
||||
readUnsigned,
|
||||
readVint,
|
||||
readVintLength,
|
||||
type SafeSizeVint,
|
||||
} from './tools';
|
||||
import { EbmlTagPosition } from './models/enums';
|
||||
import { createEbmlTag } from './factory';
|
||||
import { UnreachableOrLogicError } from './errors';
|
||||
|
||||
export async function decodeEbmlTagHeader(
|
||||
controller: FileDataViewController
|
||||
): Promise<{
|
||||
sizeVint: SafeSizeVint;
|
||||
tagVint: { length: number };
|
||||
tagId: EbmlTagIdType;
|
||||
}> {
|
||||
const offset = controller.getOffset();
|
||||
|
||||
let view = await controller.read(offset, 1);
|
||||
|
||||
const tagVintLength = readVintLength(view);
|
||||
|
||||
view =
|
||||
tagVintLength > view.byteLength
|
||||
? await controller.read(offset, tagVintLength)
|
||||
: view;
|
||||
|
||||
const tagIdView = dataViewSlice(view, 0, tagVintLength);
|
||||
|
||||
view =
|
||||
tagVintLength + 1 > view.byteLength
|
||||
? await controller.read(offset, tagVintLength + 1)
|
||||
: view;
|
||||
|
||||
const sizeVintLength = readVintLength(
|
||||
dataViewSlice(view, tagVintLength, tagVintLength + 1)
|
||||
);
|
||||
|
||||
view =
|
||||
tagVintLength + sizeVintLength > view.byteLength
|
||||
? await controller.read(offset, tagVintLength + sizeVintLength)
|
||||
: view;
|
||||
|
||||
const sizeVint = readVint(
|
||||
dataViewSlice(view, tagVintLength, tagVintLength + sizeVintLength)
|
||||
)!;
|
||||
|
||||
if (!sizeVint) {
|
||||
throw new UnreachableOrLogicError(
|
||||
'size vint dataView length is invalid, check code logic!'
|
||||
);
|
||||
}
|
||||
|
||||
const tagId = readUnsigned(tagIdView);
|
||||
|
||||
const safeSizeVint = checkVintSafeSize(sizeVint, tagId);
|
||||
|
||||
return {
|
||||
sizeVint: safeSizeVint,
|
||||
tagVint: {
|
||||
length: tagVintLength,
|
||||
},
|
||||
tagId,
|
||||
};
|
||||
}
|
||||
|
||||
export async function* decodeEbmlContent(
|
||||
controller: FileDataViewController
|
||||
): AsyncGenerator<EbmlTagTrait, void, unknown> {
|
||||
while (true) {
|
||||
const offset = controller.getOffset();
|
||||
|
||||
const peeked = await controller.peek(offset);
|
||||
|
||||
if (!peeked) {
|
||||
break;
|
||||
}
|
||||
|
||||
const vints = await decodeEbmlTagHeader(controller);
|
||||
|
||||
const { tagId, tagVint, sizeVint } = vints;
|
||||
|
||||
const headerLength = tagVint.length + sizeVint.length;
|
||||
const contentLength = sizeVint.value;
|
||||
|
||||
const isMaster = isEbmlMasterTagId(tagId);
|
||||
|
||||
if (isMaster) {
|
||||
const tag: EbmlTagTrait = createEbmlTag(tagId, {
|
||||
headerLength,
|
||||
contentLength,
|
||||
startOffset: offset,
|
||||
position: EbmlTagPosition.Start,
|
||||
parent: undefined,
|
||||
});
|
||||
yield tag;
|
||||
}
|
||||
|
||||
await controller.seek(offset + headerLength);
|
||||
|
||||
const tag: EbmlTagTrait = createEbmlTag(tagId, {
|
||||
headerLength,
|
||||
contentLength,
|
||||
startOffset: offset,
|
||||
position: isMaster ? EbmlTagPosition.End : EbmlTagPosition.Content,
|
||||
parent: undefined,
|
||||
});
|
||||
|
||||
for await (const item of tag.decodeContent(controller)) {
|
||||
yield item;
|
||||
}
|
||||
|
||||
tag.endOffset = controller.getOffset();
|
||||
|
||||
yield tag;
|
||||
}
|
||||
}
|
227
src/decoder.ts
Normal file
227
src/decoder.ts
Normal file
@ -0,0 +1,227 @@
|
||||
import { Queue } from 'mnemonist';
|
||||
import type { FileDataViewController } from './adapters';
|
||||
import type { EbmlTagTrait } from './models/tag-trait';
|
||||
import { decodeEbmlContent } from './decode-utils';
|
||||
import { StreamFlushReason, UnreachableOrLogicError } from './errors';
|
||||
import { dataViewSlice } from './tools';
|
||||
|
||||
export class EbmlDecodeStreamTransformer
|
||||
implements Transformer<ArrayBuffer, EbmlTagTrait>, FileDataViewController
|
||||
{
|
||||
private _offset = 0;
|
||||
private _buffer: Uint8Array = new Uint8Array(0);
|
||||
private _requests: Queue<
|
||||
[number, number, (view: DataView) => void, (err: Error) => void]
|
||||
> = new Queue();
|
||||
private _tickIdleCallback: VoidFunction | undefined;
|
||||
private _currentTask: Promise<void> | undefined;
|
||||
private _writeBuffer = new Queue<EbmlTagTrait>();
|
||||
|
||||
public getBuffer(): Uint8Array {
|
||||
return this._buffer;
|
||||
}
|
||||
|
||||
public getOffset(): number {
|
||||
return this._offset;
|
||||
}
|
||||
|
||||
public seek(nextOffset: number): number {
|
||||
const oldOffset = this._offset;
|
||||
|
||||
if (this._requests.size > 0) {
|
||||
throw new UnreachableOrLogicError(
|
||||
'sequential transformer should not seek before all read requests done'
|
||||
);
|
||||
}
|
||||
if (nextOffset < oldOffset) {
|
||||
throw new UnreachableOrLogicError(
|
||||
'sequential transformer should not seek to previous offset'
|
||||
);
|
||||
}
|
||||
|
||||
this._buffer = this._buffer.slice(nextOffset - oldOffset);
|
||||
this._offset = nextOffset;
|
||||
|
||||
return nextOffset;
|
||||
}
|
||||
|
||||
public async read(
|
||||
reqStart: number,
|
||||
reqLength = 0,
|
||||
exactLength = false
|
||||
): Promise<DataView> {
|
||||
const bufferStart = this._offset;
|
||||
const bufferEnd = bufferStart + this._buffer.byteLength;
|
||||
const reqEnd = reqStart + reqLength;
|
||||
|
||||
if (reqStart < bufferStart) {
|
||||
throw new UnreachableOrLogicError(
|
||||
'sequential transformer should not read before current offset'
|
||||
);
|
||||
}
|
||||
let view: DataView;
|
||||
if (bufferEnd >= reqEnd) {
|
||||
view = new DataView(this._buffer.buffer, reqStart - bufferStart);
|
||||
} else {
|
||||
view = await new Promise<DataView>((resolve, reject) => {
|
||||
this._requests.enqueue([reqStart, reqEnd, resolve, reject]);
|
||||
this.notifyIdle();
|
||||
});
|
||||
}
|
||||
|
||||
view = exactLength ? dataViewSlice(view, 0, reqLength) : view;
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public async peek(offset: number): Promise<DataView | null> {
|
||||
try {
|
||||
return await this.read(offset, 1);
|
||||
} catch (e) {
|
||||
if (e instanceof StreamFlushReason) {
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private handleRequests() {
|
||||
const bufferStart = this._offset;
|
||||
const bufferEnd = bufferStart + this._buffer.byteLength;
|
||||
|
||||
let handleCounts = this._requests.size;
|
||||
while (handleCounts > 0) {
|
||||
const req = this._requests.dequeue();
|
||||
if (!req) {
|
||||
break;
|
||||
}
|
||||
const [reqStart, reqEnd, resolve, _] = req;
|
||||
if (reqStart < bufferStart) {
|
||||
throw new UnreachableOrLogicError(
|
||||
'sequential transformer should not read before current offset'
|
||||
);
|
||||
}
|
||||
if (bufferEnd >= reqEnd) {
|
||||
resolve(new DataView(this._buffer.buffer, reqStart - bufferStart));
|
||||
} else {
|
||||
this.notifyIdle();
|
||||
this._requests.enqueue(req);
|
||||
}
|
||||
handleCounts--;
|
||||
}
|
||||
}
|
||||
|
||||
private cancelRequests() {
|
||||
while (true) {
|
||||
const req = this._requests.dequeue();
|
||||
if (!req) {
|
||||
break;
|
||||
}
|
||||
const [_reqStart, _reqEnd, _resolve, reject] = req;
|
||||
reject(new StreamFlushReason());
|
||||
}
|
||||
}
|
||||
|
||||
notifyIdle() {
|
||||
if (this._tickIdleCallback) {
|
||||
this._tickIdleCallback();
|
||||
}
|
||||
}
|
||||
|
||||
tryEnqueueToBuffer(item: EbmlTagTrait) {
|
||||
this._writeBuffer.enqueue(item);
|
||||
}
|
||||
|
||||
waitBufferRelease(
|
||||
ctrl: TransformStreamDefaultController<EbmlTagTrait>,
|
||||
isFlush: boolean
|
||||
) {
|
||||
while (this._writeBuffer.size) {
|
||||
if (ctrl.desiredSize! <= 0 && !isFlush) {
|
||||
break;
|
||||
}
|
||||
ctrl.enqueue(this._writeBuffer.dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
async tick(
|
||||
ctrl: TransformStreamDefaultController<EbmlTagTrait>,
|
||||
isFlush: boolean
|
||||
) {
|
||||
const waitIdle = new Promise<void>((resolve) => {
|
||||
this._tickIdleCallback = () => {
|
||||
resolve();
|
||||
this._tickIdleCallback = undefined;
|
||||
};
|
||||
});
|
||||
|
||||
if (isFlush) {
|
||||
this.cancelRequests();
|
||||
} else {
|
||||
this.handleRequests();
|
||||
}
|
||||
|
||||
if (!this._currentTask && !isFlush) {
|
||||
const decode = async () => {
|
||||
try {
|
||||
for await (const tag of decodeEbmlContent(this)) {
|
||||
this.tryEnqueueToBuffer(tag);
|
||||
}
|
||||
this._currentTask = undefined;
|
||||
} catch (err) {
|
||||
if (!(err instanceof StreamFlushReason)) {
|
||||
ctrl.error(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
this._currentTask = decode();
|
||||
}
|
||||
|
||||
await Promise.race([this._currentTask, waitIdle]);
|
||||
this.waitBufferRelease(ctrl, isFlush);
|
||||
}
|
||||
|
||||
async start(ctrl: TransformStreamDefaultController<EbmlTagTrait>) {
|
||||
this._offset = 0;
|
||||
this._buffer = new Uint8Array(0);
|
||||
this._requests.clear();
|
||||
this._tickIdleCallback = undefined;
|
||||
this._currentTask = undefined;
|
||||
await this.tick(ctrl, false);
|
||||
}
|
||||
|
||||
async transform(
|
||||
chunk: ArrayBuffer,
|
||||
ctrl: TransformStreamDefaultController<EbmlTagTrait>
|
||||
): Promise<void> {
|
||||
if (chunk.byteLength === 0) {
|
||||
return;
|
||||
}
|
||||
const newBuffer = new Uint8Array(
|
||||
this._buffer.byteLength + chunk.byteLength
|
||||
);
|
||||
|
||||
newBuffer.set(this._buffer, 0);
|
||||
newBuffer.set(new Uint8Array(chunk), this._buffer.byteLength);
|
||||
this._buffer = newBuffer;
|
||||
|
||||
await this.tick(ctrl, false);
|
||||
}
|
||||
|
||||
async flush(ctrl: TransformStreamDefaultController<EbmlTagTrait>) {
|
||||
await this.tick(ctrl, true);
|
||||
}
|
||||
}
|
||||
|
||||
export class EbmlStreamDecoder extends TransformStream<
|
||||
ArrayBuffer,
|
||||
EbmlTagTrait
|
||||
> {
|
||||
public readonly transformer: EbmlDecodeStreamTransformer;
|
||||
|
||||
constructor() {
|
||||
const transformer = new EbmlDecodeStreamTransformer();
|
||||
super(transformer);
|
||||
this.transformer = transformer;
|
||||
}
|
||||
}
|
93
src/encoder.ts
Normal file
93
src/encoder.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { Queue, Stack } from 'mnemonist';
|
||||
import { EbmlTagTrait } from './models/tag-trait';
|
||||
import { EbmlTagPosition } from './models/enums';
|
||||
import { EbmlMasterTag } from './models/tag-master';
|
||||
import { EbmlTreeMasterNotMatchError, UnreachableOrLogicError } from './errors';
|
||||
|
||||
export class EbmlEncodeStreamTransformer
|
||||
implements Transformer<EbmlTagTrait, ArrayBuffer>
|
||||
{
|
||||
stack = new Stack<[EbmlMasterTag, ArrayBuffer[]]>();
|
||||
_writeBuffer = new Queue<ArrayBuffer>();
|
||||
_writeBufferTask: Promise<void> | undefined;
|
||||
closed = false;
|
||||
|
||||
tryEnqueueToBuffer(...frag: ArrayBuffer[]) {
|
||||
const top = this.stack.peek();
|
||||
if (top) {
|
||||
top[1].push(...frag);
|
||||
} else {
|
||||
for (const f of frag) {
|
||||
this._writeBuffer.enqueue(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
waitBufferRelease(
|
||||
ctrl: TransformStreamDefaultController<ArrayBuffer>,
|
||||
isFlush: boolean
|
||||
) {
|
||||
while (this._writeBuffer.size) {
|
||||
if (ctrl.desiredSize! <= 0 && !isFlush) {
|
||||
break;
|
||||
}
|
||||
ctrl.enqueue(this._writeBuffer.dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <explanation>
|
||||
async transform(
|
||||
tag: EbmlTagTrait,
|
||||
ctrl: TransformStreamDefaultController<ArrayBuffer>
|
||||
) {
|
||||
if (!(tag instanceof EbmlTagTrait)) {
|
||||
throw new UnreachableOrLogicError('should only accept embl tag but not');
|
||||
}
|
||||
|
||||
if (tag instanceof EbmlMasterTag) {
|
||||
if (tag.contentLength === Number.POSITIVE_INFINITY) {
|
||||
if (tag.position === EbmlTagPosition.Start) {
|
||||
this.tryEnqueueToBuffer(...tag.encodeHeader());
|
||||
}
|
||||
} else {
|
||||
// biome-ignore lint/style/useCollapsedElseIf: <explanation>
|
||||
if (tag.position === EbmlTagPosition.Start) {
|
||||
this.stack.push([tag, []]);
|
||||
} else {
|
||||
const pop = this.stack.pop();
|
||||
if (!pop) {
|
||||
throw new EbmlTreeMasterNotMatchError(tag);
|
||||
}
|
||||
const [startTag, fragments] = pop;
|
||||
const size = fragments.reduce(
|
||||
(acc, curr) => acc + curr.byteLength,
|
||||
0
|
||||
);
|
||||
startTag.contentLength = size;
|
||||
this.tryEnqueueToBuffer(...startTag.encodeHeader());
|
||||
this.tryEnqueueToBuffer(...fragments);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.tryEnqueueToBuffer(...tag.encode());
|
||||
}
|
||||
this.waitBufferRelease(ctrl, false);
|
||||
}
|
||||
|
||||
flush(ctrl: TransformStreamDefaultController<ArrayBuffer>) {
|
||||
this.waitBufferRelease(ctrl, true);
|
||||
}
|
||||
}
|
||||
|
||||
export class EbmlStreamEncoder extends TransformStream<
|
||||
EbmlTagTrait,
|
||||
ArrayBuffer
|
||||
> {
|
||||
public readonly transformer: EbmlEncodeStreamTransformer;
|
||||
|
||||
constructor() {
|
||||
const transformer = new EbmlEncodeStreamTransformer();
|
||||
super(transformer);
|
||||
this.transformer = transformer;
|
||||
}
|
||||
}
|
114
src/errors.ts
Normal file
114
src/errors.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import type { EbmlElementType, EbmlTagIdType } from './models/enums';
|
||||
import type { EbmlTagTrait } from './models/tag-trait';
|
||||
|
||||
export class ElementIdVintDataAllZerosError extends Error {
|
||||
constructor(value: number | bigint) {
|
||||
super(
|
||||
`RFC8794 Element ID VINT_DATA can not be all zeros, but got ${value.toString(16)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ElementIdVintDataAllOnesError extends Error {
|
||||
constructor(value: number | bigint) {
|
||||
super(
|
||||
`RFC8794 Element ID VINT_DATA can not be all ones, but got ${value.toString(16)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ElementIdVintDataNotShortestError extends Error {
|
||||
constructor(value: number | bigint) {
|
||||
super(
|
||||
`RFC8794 Element ID VINT_DATA should be shortest, but ${value.toString(16)} is not`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class VintOutOfRangeError extends Error {
|
||||
constructor(value: number | bigint) {
|
||||
super(
|
||||
`RFC8794 VINT_DATA out of range 0 ~ 2^56-1, but got ${value.toString(16)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class VintLengthOutOfRangeError extends Error {
|
||||
constructor(length: number) {
|
||||
super(
|
||||
`RFC8794 Vint length out of range, valid vint range is 1 ~ 8 octet, but got ${length}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnsupportLengthForElementTypeError extends Error {
|
||||
constructor(type: EbmlElementType, expected: string, found: string | number) {
|
||||
super(
|
||||
`RFC8794 type ${type} length should be ${expected}, but found ${found}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class OutOfRangeForElementTypeError extends Error {
|
||||
constructor(
|
||||
type: EbmlElementType,
|
||||
expected: string,
|
||||
found: string | number | bigint
|
||||
) {
|
||||
super(
|
||||
`RFC8794 type ${type} value range should be ${expected}, but found ${found}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class InconsistentWellKnownEbmlTagTypeError extends Error {
|
||||
constructor(id: EbmlTagIdType, type: EbmlElementType) {
|
||||
super(
|
||||
`Trying to create tag of well-known type "${id.toString(16)}" using content type "${type}" (which is incorrect). Either pass the correct type or ignore the type parameter to EbmlTag.create()`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class InconsistentOffsetOnDecodingContentError extends Error {
|
||||
constructor(tag: EbmlTagTrait, endOffset: number) {
|
||||
super(
|
||||
`Inconsistent offset on decoding content, startOffset(${tag.startOffset + tag.headerLength}) + contentLength(${tag.contentLength}) != endOffset(${endOffset}) of tag(${JSON.stringify(tag.toDebugRecord())})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class SizeUnitOutOfSafeIntegerRangeError extends Error {
|
||||
constructor(size: number | bigint) {
|
||||
super(
|
||||
`Size unit ${size.toString(16)} is a valid vint, but out of ecmascript safe integer range`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnreachableOrLogicError extends Error {
|
||||
constructor(detail: string) {
|
||||
super(`Unreachable or Logic Error: ${detail}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class AbortReason extends Error {
|
||||
isAbortReason = true;
|
||||
|
||||
constructor(reason: string) {
|
||||
super(`Aborted: ${reason}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class StreamFlushReason extends AbortReason {
|
||||
constructor() {
|
||||
super('stream flushed');
|
||||
}
|
||||
}
|
||||
|
||||
export class EbmlTreeMasterNotMatchError extends Error {
|
||||
constructor(tag: EbmlTagTrait) {
|
||||
super(
|
||||
`start and end of master tag does not match in ebml tree at ${JSON.stringify(tag)}`
|
||||
);
|
||||
}
|
||||
}
|
180
src/factory.ts
Normal file
180
src/factory.ts
Normal file
@ -0,0 +1,180 @@
|
||||
import { InconsistentWellKnownEbmlTagTypeError } from './errors';
|
||||
import {
|
||||
type EbmlMasterTagIdType,
|
||||
type EbmlDataTagIdType,
|
||||
type EbmlBlockTagIdType,
|
||||
type EbmlSimpleBlockTagIdType,
|
||||
EbmlElementType,
|
||||
EbmlTagIdEnum,
|
||||
isEbmlBlockTagId,
|
||||
isEbmlSimpleBlockTagId,
|
||||
isEbmlMasterTagId,
|
||||
isEbmlUintDataTagId,
|
||||
isEbmlIntDataTagId,
|
||||
isEbmlFloatDataTagId,
|
||||
isEbmlStringDataTagId,
|
||||
isEbmlUtf8DataTagId,
|
||||
isEbmlDateDataTagId,
|
||||
isEbmlBinaryDataTagId,
|
||||
} from './models/enums';
|
||||
import {
|
||||
type CreateEbmlBlockTagOptions,
|
||||
EbmlBlockTag,
|
||||
} from './models/tag-block';
|
||||
import { type CreateEbmlDataTagOptions, EbmlDataTag } from './models/tag-data';
|
||||
import {
|
||||
type CreateEbmlMasterTagOptions,
|
||||
EbmlMasterTag,
|
||||
} from './models/tag-master';
|
||||
import {
|
||||
type CreateEbmlSimpleBlockTagOptions,
|
||||
EbmlSimpleBlockTag,
|
||||
} from './models/tag-simple-block';
|
||||
import type { CreateEbmlTagOptions, EbmlTagTrait } from './models/tag-trait';
|
||||
|
||||
export function createEbmlTag(
|
||||
id: EbmlMasterTagIdType,
|
||||
options: Omit<CreateEbmlMasterTagOptions, 'id'>
|
||||
): EbmlMasterTag;
|
||||
|
||||
export function createEbmlTag(
|
||||
id: EbmlDataTagIdType,
|
||||
options: Omit<CreateEbmlDataTagOptions, 'id'>
|
||||
): EbmlDataTag;
|
||||
|
||||
export function createEbmlTag(
|
||||
id: EbmlBlockTagIdType,
|
||||
options: Omit<CreateEbmlBlockTagOptions, 'id'>
|
||||
): EbmlBlockTag;
|
||||
|
||||
export function createEbmlTag(
|
||||
id: EbmlSimpleBlockTagIdType,
|
||||
options: Omit<CreateEbmlSimpleBlockTagOptions, 'id'>
|
||||
): EbmlSimpleBlockTag;
|
||||
|
||||
export function createEbmlTag(
|
||||
id: CreateEbmlTagOptions['id'],
|
||||
options: Omit<CreateEbmlTagOptions, 'id'>
|
||||
): EbmlTagTrait;
|
||||
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <explanation>
|
||||
export function createEbmlTag(arg1: unknown, arg2: unknown): EbmlTagTrait {
|
||||
const id = arg1 as EbmlTagIdEnum;
|
||||
const options = arg2 as Omit<CreateEbmlTagOptions, 'id'>;
|
||||
let type: EbmlElementType | undefined = options.type;
|
||||
if (EbmlTagIdEnum[id] !== undefined) {
|
||||
let foundType: EbmlElementType | undefined;
|
||||
if (isEbmlBlockTagId(id)) {
|
||||
return new EbmlBlockTag({ ...options, id });
|
||||
}
|
||||
if (isEbmlSimpleBlockTagId(id)) {
|
||||
return new EbmlSimpleBlockTag({ ...options });
|
||||
}
|
||||
if (isEbmlMasterTagId(id)) {
|
||||
foundType = EbmlElementType.Master;
|
||||
} else if (isEbmlUintDataTagId(id)) {
|
||||
foundType = EbmlElementType.UnsignedInt;
|
||||
} else if (isEbmlIntDataTagId(id)) {
|
||||
foundType = EbmlElementType.Integer;
|
||||
} else if (isEbmlFloatDataTagId(id)) {
|
||||
foundType = EbmlElementType.Float;
|
||||
} else if (isEbmlStringDataTagId(id)) {
|
||||
foundType = EbmlElementType.String;
|
||||
} else if (isEbmlUtf8DataTagId(id)) {
|
||||
foundType = EbmlElementType.UTF8;
|
||||
} else if (isEbmlDateDataTagId(id)) {
|
||||
foundType = EbmlElementType.Date;
|
||||
} else if (isEbmlBinaryDataTagId(id)) {
|
||||
foundType = EbmlElementType.Binary;
|
||||
}
|
||||
|
||||
if (type === undefined) {
|
||||
type = foundType;
|
||||
}
|
||||
if (type !== foundType) {
|
||||
throw new InconsistentWellKnownEbmlTagTypeError(id, type!);
|
||||
}
|
||||
}
|
||||
|
||||
if (type === EbmlElementType.Master) {
|
||||
return new EbmlMasterTag({
|
||||
...options,
|
||||
id: id as EbmlMasterTagIdType,
|
||||
});
|
||||
}
|
||||
return new EbmlDataTag({
|
||||
...options,
|
||||
id,
|
||||
type,
|
||||
});
|
||||
}
|
||||
|
||||
export type EncodeUselessCreateOptionsType =
|
||||
| 'contentLength'
|
||||
| 'headerLength'
|
||||
| 'startOffset';
|
||||
|
||||
export function createEbmlTagForManuallyBuild(
|
||||
id: EbmlMasterTagIdType,
|
||||
options: Omit<
|
||||
CreateEbmlMasterTagOptions,
|
||||
'id' | EncodeUselessCreateOptionsType
|
||||
> &
|
||||
Partial<Pick<CreateEbmlMasterTagOptions, EncodeUselessCreateOptionsType>>
|
||||
): EbmlMasterTag;
|
||||
|
||||
export function createEbmlTagForManuallyBuild(
|
||||
id: EbmlDataTagIdType,
|
||||
options: Omit<
|
||||
CreateEbmlDataTagOptions,
|
||||
'id' | EncodeUselessCreateOptionsType
|
||||
> &
|
||||
Partial<Pick<CreateEbmlMasterTagOptions, EncodeUselessCreateOptionsType>>
|
||||
): EbmlDataTag;
|
||||
|
||||
export function createEbmlTagForManuallyBuild(
|
||||
id: EbmlBlockTagIdType,
|
||||
options: Omit<
|
||||
CreateEbmlBlockTagOptions,
|
||||
'id' | EncodeUselessCreateOptionsType
|
||||
> &
|
||||
Partial<Pick<CreateEbmlMasterTagOptions, EncodeUselessCreateOptionsType>>
|
||||
): EbmlBlockTag;
|
||||
|
||||
export function createEbmlTagForManuallyBuild(
|
||||
id: EbmlSimpleBlockTagIdType,
|
||||
options: Omit<
|
||||
CreateEbmlSimpleBlockTagOptions,
|
||||
'id' | EncodeUselessCreateOptionsType
|
||||
> &
|
||||
Partial<Pick<CreateEbmlMasterTagOptions, EncodeUselessCreateOptionsType>>
|
||||
): EbmlSimpleBlockTag;
|
||||
|
||||
export function createEbmlTagForManuallyBuild(
|
||||
id: CreateEbmlTagOptions['id'],
|
||||
options: Omit<CreateEbmlTagOptions, 'id' | EncodeUselessCreateOptionsType> &
|
||||
Partial<Pick<CreateEbmlMasterTagOptions, EncodeUselessCreateOptionsType>>
|
||||
): EbmlTagTrait;
|
||||
|
||||
export function createEbmlTagForManuallyBuild(
|
||||
arg1: unknown,
|
||||
arg2: unknown
|
||||
): EbmlTagTrait {
|
||||
const id = arg1 as CreateEbmlTagOptions['id'];
|
||||
const options = arg2 as Omit<
|
||||
CreateEbmlTagOptions,
|
||||
'id' | EncodeUselessCreateOptionsType
|
||||
> &
|
||||
Partial<Pick<CreateEbmlMasterTagOptions, EncodeUselessCreateOptionsType>>;
|
||||
return createEbmlTag(
|
||||
id,
|
||||
Object.assign(
|
||||
{
|
||||
contentLength: Number.NaN,
|
||||
headerLength: Number.NaN,
|
||||
startOffset: Number.NaN,
|
||||
},
|
||||
options
|
||||
) as Omit<CreateEbmlTagOptions, 'id'>
|
||||
);
|
||||
}
|
47
src/index.ts
Normal file
47
src/index.ts
Normal file
@ -0,0 +1,47 @@
|
||||
export { EbmlBlockTag } from './models/tag-block';
|
||||
export { EbmlDataTag } from './models/tag-data';
|
||||
export { EbmlMasterTag } from './models/tag-master';
|
||||
export { EbmlSimpleBlockTag } from './models/tag-simple-block';
|
||||
export { EbmlTagTrait } from './models/tag-trait';
|
||||
export {
|
||||
createEbmlTag,
|
||||
createEbmlTagForManuallyBuild,
|
||||
} from './factory';
|
||||
export {
|
||||
decodeEbmlTagHeader,
|
||||
decodeEbmlContent,
|
||||
} from './decode-utils';
|
||||
export { EbmlStreamDecoder, EbmlDecodeStreamTransformer } from './decoder';
|
||||
export {
|
||||
EbmlStreamEncoder,
|
||||
EbmlEncodeStreamTransformer,
|
||||
} from './encoder';
|
||||
export {
|
||||
EbmlBlockLacing,
|
||||
EbmlTagIdEnum,
|
||||
type EbmlElementType,
|
||||
type EbmlBinaryDataTagIdType,
|
||||
type EbmlMasterTagIdType,
|
||||
type EbmlBlockTagIdType,
|
||||
type EbmlDataTagIdType,
|
||||
type EbmlDateDataTagIdType,
|
||||
type EbmlFloatDataTagIdType,
|
||||
type EbmlIntDataTagIdType,
|
||||
type EbmlSimpleBlockTagIdType,
|
||||
type EbmlStringDataTagIdType,
|
||||
type EbmlUintDataTagIdType,
|
||||
type EbmlUtf8DataTagIdType,
|
||||
type EbmlTagIdType,
|
||||
EbmlTagPosition,
|
||||
isEbmlBinaryDataTagId,
|
||||
isEbmlBlockTagId,
|
||||
isEbmlDateDataTagId,
|
||||
isEbmlFloatDataTagId,
|
||||
isEbmlIntDataTagId,
|
||||
isEbmlMasterTagId,
|
||||
isEbmlSimpleBlockTagId,
|
||||
isEbmlStringDataTagId,
|
||||
isEbmlUintDataTagId,
|
||||
isEbmlUtf8DataTagId,
|
||||
isUnknownDataTagId,
|
||||
} from './models/enums';
|
995
src/models/enums.ts
Normal file
995
src/models/enums.ts
Normal file
@ -0,0 +1,995 @@
|
||||
export enum EbmlElementType {
|
||||
Master = 'm',
|
||||
UnsignedInt = 'u',
|
||||
Integer = 'i',
|
||||
String = 's',
|
||||
UTF8 = '8',
|
||||
Binary = 'b',
|
||||
Float = 'f',
|
||||
Date = 'd',
|
||||
}
|
||||
|
||||
export enum EbmlTagPosition {
|
||||
Start = 1,
|
||||
Content = 2,
|
||||
End = 3,
|
||||
}
|
||||
|
||||
export enum EbmlTagIdEnum {
|
||||
ChapterDisplay = 0x80,
|
||||
TrackType = 0x83,
|
||||
ChapString = 0x85,
|
||||
CodecID = 0x86,
|
||||
FlagDefault = 0x88,
|
||||
ChapterTrackNumber = 0x89,
|
||||
ChapterTimeStart = 0x91,
|
||||
ChapterTimeEnd = 0x92,
|
||||
CueRefTime = 0x96,
|
||||
CueRefCluster = 0x97,
|
||||
ChapterFlagHidden = 0x98,
|
||||
ContentCompAlgo = 0x4254,
|
||||
ContentCompSettings = 0x4255,
|
||||
DocType = 0x4282,
|
||||
DocTypeReadVersion = 0x4285,
|
||||
EBMLVersion = 0x4286,
|
||||
DocTypeVersion = 0x4287,
|
||||
SegmentFamily = 0x4444,
|
||||
DateUTC = 0x4461,
|
||||
TagDefault = 0x4484,
|
||||
TagBinary = 0x4485,
|
||||
TagString = 0x4487,
|
||||
Duration = 0x4489,
|
||||
ChapterFlagEnabled = 0x4598,
|
||||
FileMimeType = 0x4660,
|
||||
FileUsedStartTime = 0x4661,
|
||||
FileUsedEndTime = 0x4662,
|
||||
FileReferral = 0x4675,
|
||||
ContentEncodingOrder = 0x5031,
|
||||
ContentEncodingScope = 0x5032,
|
||||
ContentEncodingType = 0x5033,
|
||||
ContentCompression = 0x5034,
|
||||
ContentEncryption = 0x5035,
|
||||
CueBlockNumber = 0x5378,
|
||||
ChapterStringUID = 0x5654,
|
||||
WritingApp = 0x5741,
|
||||
SilentTracks = 0x5854,
|
||||
ContentEncoding = 0x6240,
|
||||
BitDepth = 0x6264,
|
||||
SignedElement = 0x6532,
|
||||
TrackTranslate = 0x6624,
|
||||
ChapProcessCommand = 0x6911,
|
||||
ChapProcessTime = 0x6922,
|
||||
ChapterTranslate = 0x6924,
|
||||
ChapProcessData = 0x6933,
|
||||
ChapProcess = 0x6944,
|
||||
ChapProcessCodecID = 0x6955,
|
||||
Tag = 0x7373,
|
||||
SegmentFilename = 0x7384,
|
||||
AttachmentLink = 0x7446,
|
||||
CodecName = 0x258688,
|
||||
Segment = 0x18538067,
|
||||
TagLanguage = 0x447a,
|
||||
TagName = 0x45a3,
|
||||
SimpleTag = 0x67c8,
|
||||
TagAttachmentUID = 0x63c6,
|
||||
TagChapterUID = 0x63c4,
|
||||
TagEditionUID = 0x63c9,
|
||||
TagTrackUID = 0x63c5,
|
||||
TargetType = 0x63ca,
|
||||
TargetTypeValue = 0x68ca,
|
||||
Targets = 0x63c0,
|
||||
Tags = 0x1254c367,
|
||||
ChapProcessPrivate = 0x450d,
|
||||
ChapCountry = 0x437e,
|
||||
ChapLanguage = 0x437c,
|
||||
ChapterTrack = 0x8f,
|
||||
ChapterPhysicalEquiv = 0x63c3,
|
||||
ChapterSegmentEditionUID = 0x6ebc,
|
||||
ChapterSegmentUID = 0x6e67,
|
||||
ChapterUID = 0x73c4,
|
||||
ChapterAtom = 0xb6,
|
||||
EditionFlagOrdered = 0x45dd,
|
||||
EditionFlagDefault = 0x45db,
|
||||
EditionFlagHidden = 0x45bd,
|
||||
EditionUID = 0x45bc,
|
||||
EditionEntry = 0x45b9,
|
||||
Chapters = 0x1043a770,
|
||||
FileUID = 0x46ae,
|
||||
FileData = 0x465c,
|
||||
FileName = 0x466e,
|
||||
FileDescription = 0x467e,
|
||||
AttachedFile = 0x61a7,
|
||||
Attachments = 0x1941a469,
|
||||
CueRefCodecState = 0xeb,
|
||||
CueRefNumber = 0x535f,
|
||||
CueReference = 0xdb,
|
||||
CueCodecState = 0xea,
|
||||
CueDuration = 0xb2,
|
||||
CueRelativePosition = 0xf0,
|
||||
CueClusterPosition = 0xf1,
|
||||
CueTrack = 0xf7,
|
||||
CueTrackPositions = 0xb7,
|
||||
CueTime = 0xb3,
|
||||
CuePoint = 0xbb,
|
||||
Cues = 0x1c53bb6b,
|
||||
AESSettingsCipherMode = 0x47e8,
|
||||
ContentEncAESSettings = 0x47e7,
|
||||
ContentSigHashAlgo = 0x47e6,
|
||||
ContentSigAlgo = 0x47e5,
|
||||
ContentSigKeyID = 0x47e4,
|
||||
ContentSignature = 0x47e3,
|
||||
ContentEncKeyID = 0x47e2,
|
||||
ContentEncAlgo = 0x47e1,
|
||||
ContentEncodings = 0x6d80,
|
||||
TrickMasterTrackSegmentUID = 0xc4,
|
||||
TrickMasterTrackUID = 0xc7,
|
||||
TrickTrackFlag = 0xc6,
|
||||
TrickTrackSegmentUID = 0xc1,
|
||||
TrickTrackUID = 0xc0,
|
||||
TrackJoinUID = 0xed,
|
||||
TrackJoinBlocks = 0xe9,
|
||||
TrackPlaneType = 0xe6,
|
||||
TrackPlaneUID = 0xe5,
|
||||
TrackPlane = 0xe4,
|
||||
TrackCombinePlanes = 0xe3,
|
||||
TrackOperation = 0xe2,
|
||||
ChannelPositions = 0x7d7b,
|
||||
Channels = 0x9f,
|
||||
OutputSamplingFrequency = 0x78b5,
|
||||
SamplingFrequency = 0xb5,
|
||||
Audio = 0xe1,
|
||||
FrameRate = 0x2383e3,
|
||||
GammaValue = 0x2fb523,
|
||||
ColourSpace = 0x2eb524,
|
||||
AspectRatioType = 0x54b3,
|
||||
DisplayUnit = 0x54b2,
|
||||
DisplayHeight = 0x54ba,
|
||||
DisplayWidth = 0x54b0,
|
||||
PixelCropRight = 0x54dd,
|
||||
PixelCropLeft = 0x54cc,
|
||||
PixelCropTop = 0x54bb,
|
||||
PixelCropBottom = 0x54aa,
|
||||
PixelHeight = 0xba,
|
||||
PixelWidth = 0xb0,
|
||||
OldStereoMode = 0x53b9,
|
||||
AlphaMode = 0x53c0,
|
||||
StereoMode = 0x53b8,
|
||||
FlagInterlaced = 0x9a,
|
||||
Video = 0xe0,
|
||||
TrackTranslateTrackID = 0x66a5,
|
||||
TrackTranslateCodec = 0x66bf,
|
||||
TrackTranslateEditionUID = 0x66fc,
|
||||
SeekPreRoll = 0x56bb,
|
||||
CodecDelay = 0x56aa,
|
||||
TrackOverlay = 0x6fab,
|
||||
CodecDecodeAll = 0xaa,
|
||||
CodecDownloadURL = 0x26b240,
|
||||
CodecInfoURL = 0x3b4040,
|
||||
CodecSettings = 0x3a9697,
|
||||
CodecPrivate = 0x63a2,
|
||||
Language = 0x22b59c,
|
||||
Name = 0x536e,
|
||||
MaxBlockAdditionID = 0x55ee,
|
||||
TrackOffset = 0x537f,
|
||||
TrackTimecodeScale = 0x23314f,
|
||||
DefaultDecodedFieldDuration = 0x234e7a,
|
||||
DefaultDuration = 0x23e383,
|
||||
MaxCache = 0x6df8,
|
||||
MinCache = 0x6de7,
|
||||
FlagLacing = 0x9c,
|
||||
FlagForced = 0x55aa,
|
||||
FlagEnabled = 0xb9,
|
||||
TrackUID = 0x73c5,
|
||||
TrackNumber = 0xd7,
|
||||
TrackEntry = 0xae,
|
||||
Tracks = 0x1654ae6b,
|
||||
EncryptedBlock = 0xaf,
|
||||
ReferenceTimeCode = 0xca,
|
||||
ReferenceOffset = 0xc9,
|
||||
ReferenceFrame = 0xc8,
|
||||
SliceDuration = 0xcf,
|
||||
Delay = 0xce,
|
||||
BlockAdditionID = 0xcb,
|
||||
FrameNumber = 0xcd,
|
||||
LaceNumber = 0xcc,
|
||||
TimeSlice = 0xe8,
|
||||
Slices = 0x8e,
|
||||
DiscardPadding = 0x75a2,
|
||||
CodecState = 0xa4,
|
||||
ReferenceVirtual = 0xfd,
|
||||
ReferenceBlock = 0xfb,
|
||||
ReferencePriority = 0xfa,
|
||||
BlockDuration = 0x9b,
|
||||
BlockAdditional = 0xa5,
|
||||
BlockAddID = 0xee,
|
||||
BlockMore = 0xa6,
|
||||
BlockAdditions = 0x75a1,
|
||||
BlockVirtual = 0xa2,
|
||||
Block = 0xa1,
|
||||
BlockGroup = 0xa0,
|
||||
SimpleBlock = 0xa3,
|
||||
PrevSize = 0xab,
|
||||
Position = 0xa7,
|
||||
SilentTrackNumber = 0x58d7,
|
||||
Timecode = 0xe7,
|
||||
Cluster = 0x1f43b675,
|
||||
MuxingApp = 0x4d80,
|
||||
Title = 0x7ba9,
|
||||
TimecodeScaleDenominator = 0x2ad7b2,
|
||||
TimecodeScale = 0x2ad7b1,
|
||||
ChapterTranslateID = 0x69a5,
|
||||
ChapterTranslateCodec = 0x69bf,
|
||||
ChapterTranslateEditionUID = 0x69fc,
|
||||
NextFilename = 0x3e83bb,
|
||||
NextUID = 0x3eb923,
|
||||
PrevFilename = 0x3c83ab,
|
||||
PrevUID = 0x3cb923,
|
||||
SegmentUID = 0x73a4,
|
||||
Info = 0x1549a966,
|
||||
SeekPosition = 0x53ac,
|
||||
SeekID = 0x53ab,
|
||||
Seek = 0x4dbb,
|
||||
SeekHead = 0x114d9b74,
|
||||
SignatureElementList = 0x7e7b,
|
||||
SignatureElements = 0x7e5b,
|
||||
Signature = 0x7eb5,
|
||||
SignaturePublicKey = 0x7ea5,
|
||||
SignatureHash = 0x7e9a,
|
||||
SignatureAlgo = 0x7e8a,
|
||||
SignatureSlot = 0x1b538667,
|
||||
CRC32 = 0xbf,
|
||||
Void = 0xec,
|
||||
EBMLMaxSizeLength = 0x42f3,
|
||||
EBMLMaxIDLength = 0x42f2,
|
||||
EBMLReadVersion = 0x42f7,
|
||||
EBML = 0x1a45dfa3,
|
||||
}
|
||||
|
||||
export type EbmlTagIdType = EbmlTagIdEnum | number | bigint;
|
||||
|
||||
export type EbmlMasterTagIdType =
|
||||
| EbmlTagIdEnum.ChapterDisplay
|
||||
| EbmlTagIdEnum.ContentCompression
|
||||
| EbmlTagIdEnum.ContentEncryption
|
||||
| EbmlTagIdEnum.SilentTracks
|
||||
| EbmlTagIdEnum.ContentEncoding
|
||||
| EbmlTagIdEnum.TrackTranslate
|
||||
| EbmlTagIdEnum.ChapProcessCommand
|
||||
| EbmlTagIdEnum.ChapterTranslate
|
||||
| EbmlTagIdEnum.ChapProcess
|
||||
| EbmlTagIdEnum.Tag
|
||||
| EbmlTagIdEnum.Segment
|
||||
| EbmlTagIdEnum.SimpleTag
|
||||
| EbmlTagIdEnum.Targets
|
||||
| EbmlTagIdEnum.Tags
|
||||
| EbmlTagIdEnum.ChapterTrack
|
||||
| EbmlTagIdEnum.ChapterAtom
|
||||
| EbmlTagIdEnum.EditionEntry
|
||||
| EbmlTagIdEnum.Chapters
|
||||
| EbmlTagIdEnum.AttachedFile
|
||||
| EbmlTagIdEnum.Attachments
|
||||
| EbmlTagIdEnum.CueReference
|
||||
| EbmlTagIdEnum.CueTrackPositions
|
||||
| EbmlTagIdEnum.CuePoint
|
||||
| EbmlTagIdEnum.Cues
|
||||
| EbmlTagIdEnum.ContentEncAESSettings
|
||||
| EbmlTagIdEnum.ContentEncodings
|
||||
| EbmlTagIdEnum.TrackJoinBlocks
|
||||
| EbmlTagIdEnum.TrackPlane
|
||||
| EbmlTagIdEnum.TrackCombinePlanes
|
||||
| EbmlTagIdEnum.TrackOperation
|
||||
| EbmlTagIdEnum.Audio
|
||||
| EbmlTagIdEnum.Video
|
||||
| EbmlTagIdEnum.TrackEntry
|
||||
| EbmlTagIdEnum.Tracks
|
||||
| EbmlTagIdEnum.ReferenceFrame
|
||||
| EbmlTagIdEnum.TimeSlice
|
||||
| EbmlTagIdEnum.Slices
|
||||
| EbmlTagIdEnum.BlockMore
|
||||
| EbmlTagIdEnum.BlockAdditions
|
||||
| EbmlTagIdEnum.BlockGroup
|
||||
| EbmlTagIdEnum.Cluster
|
||||
| EbmlTagIdEnum.Info
|
||||
| EbmlTagIdEnum.Seek
|
||||
| EbmlTagIdEnum.SeekHead
|
||||
| EbmlTagIdEnum.SignatureElementList
|
||||
| EbmlTagIdEnum.SignatureElements
|
||||
| EbmlTagIdEnum.SignatureSlot
|
||||
| EbmlTagIdEnum.EBML;
|
||||
|
||||
export type EbmlDataTagIdType =
|
||||
| EbmlTagIdEnum.TrackType
|
||||
| EbmlTagIdEnum.FlagDefault
|
||||
| EbmlTagIdEnum.ChapterTrackNumber
|
||||
| EbmlTagIdEnum.ChapterTimeStart
|
||||
| EbmlTagIdEnum.ChapterTimeEnd
|
||||
| EbmlTagIdEnum.CueRefTime
|
||||
| EbmlTagIdEnum.CueRefCluster
|
||||
| EbmlTagIdEnum.ChapterFlagHidden
|
||||
| EbmlTagIdEnum.ContentCompAlgo
|
||||
| EbmlTagIdEnum.DocTypeReadVersion
|
||||
| EbmlTagIdEnum.EBMLVersion
|
||||
| EbmlTagIdEnum.DocTypeVersion
|
||||
| EbmlTagIdEnum.TagDefault
|
||||
| EbmlTagIdEnum.ChapterFlagEnabled
|
||||
| EbmlTagIdEnum.FileUsedStartTime
|
||||
| EbmlTagIdEnum.FileUsedEndTime
|
||||
| EbmlTagIdEnum.ContentEncodingOrder
|
||||
| EbmlTagIdEnum.ContentEncodingScope
|
||||
| EbmlTagIdEnum.ContentEncodingType
|
||||
| EbmlTagIdEnum.CueBlockNumber
|
||||
| EbmlTagIdEnum.BitDepth
|
||||
| EbmlTagIdEnum.ChapProcessTime
|
||||
| EbmlTagIdEnum.ChapProcessCodecID
|
||||
| EbmlTagIdEnum.AttachmentLink
|
||||
| EbmlTagIdEnum.TagAttachmentUID
|
||||
| EbmlTagIdEnum.TagChapterUID
|
||||
| EbmlTagIdEnum.TagEditionUID
|
||||
| EbmlTagIdEnum.TagTrackUID
|
||||
| EbmlTagIdEnum.TargetTypeValue
|
||||
| EbmlTagIdEnum.ChapterPhysicalEquiv
|
||||
| EbmlTagIdEnum.ChapterSegmentEditionUID
|
||||
| EbmlTagIdEnum.ChapterUID
|
||||
| EbmlTagIdEnum.EditionFlagOrdered
|
||||
| EbmlTagIdEnum.EditionFlagDefault
|
||||
| EbmlTagIdEnum.EditionFlagHidden
|
||||
| EbmlTagIdEnum.EditionUID
|
||||
| EbmlTagIdEnum.FileUID
|
||||
| EbmlTagIdEnum.CueRefCodecState
|
||||
| EbmlTagIdEnum.CueRefNumber
|
||||
| EbmlTagIdEnum.CueCodecState
|
||||
| EbmlTagIdEnum.CueDuration
|
||||
| EbmlTagIdEnum.CueRelativePosition
|
||||
| EbmlTagIdEnum.CueClusterPosition
|
||||
| EbmlTagIdEnum.CueTrack
|
||||
| EbmlTagIdEnum.CueTime
|
||||
| EbmlTagIdEnum.AESSettingsCipherMode
|
||||
| EbmlTagIdEnum.ContentSigHashAlgo
|
||||
| EbmlTagIdEnum.ContentSigAlgo
|
||||
| EbmlTagIdEnum.ContentEncAlgo
|
||||
| EbmlTagIdEnum.TrickMasterTrackUID
|
||||
| EbmlTagIdEnum.TrickTrackFlag
|
||||
| EbmlTagIdEnum.TrickTrackUID
|
||||
| EbmlTagIdEnum.TrackJoinUID
|
||||
| EbmlTagIdEnum.TrackPlaneType
|
||||
| EbmlTagIdEnum.TrackPlaneUID
|
||||
| EbmlTagIdEnum.Channels
|
||||
| EbmlTagIdEnum.AspectRatioType
|
||||
| EbmlTagIdEnum.DisplayUnit
|
||||
| EbmlTagIdEnum.DisplayHeight
|
||||
| EbmlTagIdEnum.DisplayWidth
|
||||
| EbmlTagIdEnum.PixelCropRight
|
||||
| EbmlTagIdEnum.PixelCropLeft
|
||||
| EbmlTagIdEnum.PixelCropTop
|
||||
| EbmlTagIdEnum.PixelCropBottom
|
||||
| EbmlTagIdEnum.PixelHeight
|
||||
| EbmlTagIdEnum.PixelWidth
|
||||
| EbmlTagIdEnum.OldStereoMode
|
||||
| EbmlTagIdEnum.AlphaMode
|
||||
| EbmlTagIdEnum.StereoMode
|
||||
| EbmlTagIdEnum.FlagInterlaced
|
||||
| EbmlTagIdEnum.TrackTranslateCodec
|
||||
| EbmlTagIdEnum.TrackTranslateEditionUID
|
||||
| EbmlTagIdEnum.SeekPreRoll
|
||||
| EbmlTagIdEnum.CodecDelay
|
||||
| EbmlTagIdEnum.TrackOverlay
|
||||
| EbmlTagIdEnum.CodecDecodeAll
|
||||
| EbmlTagIdEnum.MaxBlockAdditionID
|
||||
| EbmlTagIdEnum.DefaultDecodedFieldDuration
|
||||
| EbmlTagIdEnum.DefaultDuration
|
||||
| EbmlTagIdEnum.MaxCache
|
||||
| EbmlTagIdEnum.MinCache
|
||||
| EbmlTagIdEnum.FlagLacing
|
||||
| EbmlTagIdEnum.FlagForced
|
||||
| EbmlTagIdEnum.FlagEnabled
|
||||
| EbmlTagIdEnum.TrackUID
|
||||
| EbmlTagIdEnum.TrackNumber
|
||||
| EbmlTagIdEnum.ReferenceTimeCode
|
||||
| EbmlTagIdEnum.ReferenceOffset
|
||||
| EbmlTagIdEnum.SliceDuration
|
||||
| EbmlTagIdEnum.Delay
|
||||
| EbmlTagIdEnum.BlockAdditionID
|
||||
| EbmlTagIdEnum.FrameNumber
|
||||
| EbmlTagIdEnum.LaceNumber
|
||||
| EbmlTagIdEnum.ReferencePriority
|
||||
| EbmlTagIdEnum.BlockDuration
|
||||
| EbmlTagIdEnum.BlockAddID
|
||||
| EbmlTagIdEnum.PrevSize
|
||||
| EbmlTagIdEnum.Position
|
||||
| EbmlTagIdEnum.SilentTrackNumber
|
||||
| EbmlTagIdEnum.Timecode
|
||||
| EbmlTagIdEnum.TimecodeScaleDenominator
|
||||
| EbmlTagIdEnum.TimecodeScale
|
||||
| EbmlTagIdEnum.ChapterTranslateCodec
|
||||
| EbmlTagIdEnum.ChapterTranslateEditionUID
|
||||
| EbmlTagIdEnum.SeekPosition
|
||||
| EbmlTagIdEnum.SignatureHash
|
||||
| EbmlTagIdEnum.SignatureAlgo
|
||||
| EbmlTagIdEnum.EBMLMaxSizeLength
|
||||
| EbmlTagIdEnum.EBMLMaxIDLength
|
||||
| EbmlTagIdEnum.EBMLReadVersion
|
||||
| EbmlTagIdEnum.TrackOffset
|
||||
| EbmlTagIdEnum.DiscardPadding
|
||||
| EbmlTagIdEnum.ReferenceVirtual
|
||||
| EbmlTagIdEnum.ReferenceBlock
|
||||
| EbmlTagIdEnum.CodecID
|
||||
| EbmlTagIdEnum.DocType
|
||||
| EbmlTagIdEnum.FileMimeType
|
||||
| EbmlTagIdEnum.TagLanguage
|
||||
| EbmlTagIdEnum.TargetType
|
||||
| EbmlTagIdEnum.ChapCountry
|
||||
| EbmlTagIdEnum.ChapLanguage
|
||||
| EbmlTagIdEnum.CodecDownloadURL
|
||||
| EbmlTagIdEnum.CodecInfoURL
|
||||
| EbmlTagIdEnum.Language
|
||||
| EbmlTagIdEnum.ChapString
|
||||
| EbmlTagIdEnum.TagString
|
||||
| EbmlTagIdEnum.ChapterStringUID
|
||||
| EbmlTagIdEnum.WritingApp
|
||||
| EbmlTagIdEnum.SegmentFilename
|
||||
| EbmlTagIdEnum.CodecName
|
||||
| EbmlTagIdEnum.TagName
|
||||
| EbmlTagIdEnum.FileName
|
||||
| EbmlTagIdEnum.FileDescription
|
||||
| EbmlTagIdEnum.CodecSettings
|
||||
| EbmlTagIdEnum.Name
|
||||
| EbmlTagIdEnum.MuxingApp
|
||||
| EbmlTagIdEnum.Title
|
||||
| EbmlTagIdEnum.NextFilename
|
||||
| EbmlTagIdEnum.PrevFilename
|
||||
| EbmlTagIdEnum.ContentCompSettings
|
||||
| EbmlTagIdEnum.SegmentFamily
|
||||
| EbmlTagIdEnum.TagBinary
|
||||
| EbmlTagIdEnum.FileReferral
|
||||
| EbmlTagIdEnum.SignedElement
|
||||
| EbmlTagIdEnum.ChapProcessData
|
||||
| EbmlTagIdEnum.ChapProcessPrivate
|
||||
| EbmlTagIdEnum.ChapterSegmentUID
|
||||
| EbmlTagIdEnum.FileData
|
||||
| EbmlTagIdEnum.ContentSigKeyID
|
||||
| EbmlTagIdEnum.ContentSignature
|
||||
| EbmlTagIdEnum.ContentEncKeyID
|
||||
| EbmlTagIdEnum.TrickMasterTrackSegmentUID
|
||||
| EbmlTagIdEnum.TrickTrackSegmentUID
|
||||
| EbmlTagIdEnum.ChannelPositions
|
||||
| EbmlTagIdEnum.ColourSpace
|
||||
| EbmlTagIdEnum.TrackTranslateTrackID
|
||||
| EbmlTagIdEnum.CodecPrivate
|
||||
| EbmlTagIdEnum.EncryptedBlock
|
||||
| EbmlTagIdEnum.CodecState
|
||||
| EbmlTagIdEnum.BlockAdditional
|
||||
| EbmlTagIdEnum.BlockVirtual
|
||||
| EbmlTagIdEnum.ChapterTranslateID
|
||||
| EbmlTagIdEnum.NextUID
|
||||
| EbmlTagIdEnum.PrevUID
|
||||
| EbmlTagIdEnum.SegmentUID
|
||||
| EbmlTagIdEnum.SeekID
|
||||
| EbmlTagIdEnum.Signature
|
||||
| EbmlTagIdEnum.SignaturePublicKey
|
||||
| EbmlTagIdEnum.CRC32
|
||||
| EbmlTagIdEnum.Void
|
||||
| EbmlTagIdEnum.Duration
|
||||
| EbmlTagIdEnum.OutputSamplingFrequency
|
||||
| EbmlTagIdEnum.SamplingFrequency
|
||||
| EbmlTagIdEnum.FrameRate
|
||||
| EbmlTagIdEnum.GammaValue
|
||||
| EbmlTagIdEnum.TrackTimecodeScale
|
||||
| EbmlTagIdEnum.DateUTC;
|
||||
|
||||
export type EbmlBlockTagIdType = EbmlTagIdEnum.Block;
|
||||
|
||||
export type EbmlSimpleBlockTagIdType = EbmlTagIdEnum.SimpleBlock;
|
||||
|
||||
export type EbmlUintDataTagIdType =
|
||||
| EbmlTagIdEnum.TrackType
|
||||
| EbmlTagIdEnum.FlagDefault
|
||||
| EbmlTagIdEnum.ChapterTrackNumber
|
||||
| EbmlTagIdEnum.ChapterTimeStart
|
||||
| EbmlTagIdEnum.ChapterTimeEnd
|
||||
| EbmlTagIdEnum.CueRefTime
|
||||
| EbmlTagIdEnum.CueRefCluster
|
||||
| EbmlTagIdEnum.ChapterFlagHidden
|
||||
| EbmlTagIdEnum.ContentCompAlgo
|
||||
| EbmlTagIdEnum.DocTypeReadVersion
|
||||
| EbmlTagIdEnum.EBMLVersion
|
||||
| EbmlTagIdEnum.DocTypeVersion
|
||||
| EbmlTagIdEnum.TagDefault
|
||||
| EbmlTagIdEnum.ChapterFlagEnabled
|
||||
| EbmlTagIdEnum.FileUsedStartTime
|
||||
| EbmlTagIdEnum.FileUsedEndTime
|
||||
| EbmlTagIdEnum.ContentEncodingOrder
|
||||
| EbmlTagIdEnum.ContentEncodingScope
|
||||
| EbmlTagIdEnum.ContentEncodingType
|
||||
| EbmlTagIdEnum.CueBlockNumber
|
||||
| EbmlTagIdEnum.BitDepth
|
||||
| EbmlTagIdEnum.ChapProcessTime
|
||||
| EbmlTagIdEnum.ChapProcessCodecID
|
||||
| EbmlTagIdEnum.AttachmentLink
|
||||
| EbmlTagIdEnum.TagAttachmentUID
|
||||
| EbmlTagIdEnum.TagChapterUID
|
||||
| EbmlTagIdEnum.TagEditionUID
|
||||
| EbmlTagIdEnum.TagTrackUID
|
||||
| EbmlTagIdEnum.TargetTypeValue
|
||||
| EbmlTagIdEnum.ChapterPhysicalEquiv
|
||||
| EbmlTagIdEnum.ChapterSegmentEditionUID
|
||||
| EbmlTagIdEnum.ChapterUID
|
||||
| EbmlTagIdEnum.EditionFlagOrdered
|
||||
| EbmlTagIdEnum.EditionFlagDefault
|
||||
| EbmlTagIdEnum.EditionFlagHidden
|
||||
| EbmlTagIdEnum.EditionUID
|
||||
| EbmlTagIdEnum.FileUID
|
||||
| EbmlTagIdEnum.CueRefCodecState
|
||||
| EbmlTagIdEnum.CueRefNumber
|
||||
| EbmlTagIdEnum.CueCodecState
|
||||
| EbmlTagIdEnum.CueDuration
|
||||
| EbmlTagIdEnum.CueRelativePosition
|
||||
| EbmlTagIdEnum.CueClusterPosition
|
||||
| EbmlTagIdEnum.CueTrack
|
||||
| EbmlTagIdEnum.CueTime
|
||||
| EbmlTagIdEnum.AESSettingsCipherMode
|
||||
| EbmlTagIdEnum.ContentSigHashAlgo
|
||||
| EbmlTagIdEnum.ContentSigAlgo
|
||||
| EbmlTagIdEnum.ContentEncAlgo
|
||||
| EbmlTagIdEnum.TrickMasterTrackUID
|
||||
| EbmlTagIdEnum.TrickTrackFlag
|
||||
| EbmlTagIdEnum.TrickTrackUID
|
||||
| EbmlTagIdEnum.TrackJoinUID
|
||||
| EbmlTagIdEnum.TrackPlaneType
|
||||
| EbmlTagIdEnum.TrackPlaneUID
|
||||
| EbmlTagIdEnum.Channels
|
||||
| EbmlTagIdEnum.AspectRatioType
|
||||
| EbmlTagIdEnum.DisplayUnit
|
||||
| EbmlTagIdEnum.DisplayHeight
|
||||
| EbmlTagIdEnum.DisplayWidth
|
||||
| EbmlTagIdEnum.PixelCropRight
|
||||
| EbmlTagIdEnum.PixelCropLeft
|
||||
| EbmlTagIdEnum.PixelCropTop
|
||||
| EbmlTagIdEnum.PixelCropBottom
|
||||
| EbmlTagIdEnum.PixelHeight
|
||||
| EbmlTagIdEnum.PixelWidth
|
||||
| EbmlTagIdEnum.OldStereoMode
|
||||
| EbmlTagIdEnum.AlphaMode
|
||||
| EbmlTagIdEnum.StereoMode
|
||||
| EbmlTagIdEnum.FlagInterlaced
|
||||
| EbmlTagIdEnum.TrackTranslateCodec
|
||||
| EbmlTagIdEnum.TrackTranslateEditionUID
|
||||
| EbmlTagIdEnum.SeekPreRoll
|
||||
| EbmlTagIdEnum.CodecDelay
|
||||
| EbmlTagIdEnum.TrackOverlay
|
||||
| EbmlTagIdEnum.CodecDecodeAll
|
||||
| EbmlTagIdEnum.MaxBlockAdditionID
|
||||
| EbmlTagIdEnum.DefaultDecodedFieldDuration
|
||||
| EbmlTagIdEnum.DefaultDuration
|
||||
| EbmlTagIdEnum.MaxCache
|
||||
| EbmlTagIdEnum.MinCache
|
||||
| EbmlTagIdEnum.FlagLacing
|
||||
| EbmlTagIdEnum.FlagForced
|
||||
| EbmlTagIdEnum.FlagEnabled
|
||||
| EbmlTagIdEnum.TrackUID
|
||||
| EbmlTagIdEnum.TrackNumber
|
||||
| EbmlTagIdEnum.ReferenceTimeCode
|
||||
| EbmlTagIdEnum.ReferenceOffset
|
||||
| EbmlTagIdEnum.SliceDuration
|
||||
| EbmlTagIdEnum.Delay
|
||||
| EbmlTagIdEnum.BlockAdditionID
|
||||
| EbmlTagIdEnum.FrameNumber
|
||||
| EbmlTagIdEnum.LaceNumber
|
||||
| EbmlTagIdEnum.ReferencePriority
|
||||
| EbmlTagIdEnum.BlockDuration
|
||||
| EbmlTagIdEnum.BlockAddID
|
||||
| EbmlTagIdEnum.PrevSize
|
||||
| EbmlTagIdEnum.Position
|
||||
| EbmlTagIdEnum.SilentTrackNumber
|
||||
| EbmlTagIdEnum.Timecode
|
||||
| EbmlTagIdEnum.TimecodeScaleDenominator
|
||||
| EbmlTagIdEnum.TimecodeScale
|
||||
| EbmlTagIdEnum.ChapterTranslateCodec
|
||||
| EbmlTagIdEnum.ChapterTranslateEditionUID
|
||||
| EbmlTagIdEnum.SeekPosition
|
||||
| EbmlTagIdEnum.SignatureHash
|
||||
| EbmlTagIdEnum.SignatureAlgo
|
||||
| EbmlTagIdEnum.EBMLMaxSizeLength
|
||||
| EbmlTagIdEnum.EBMLMaxIDLength
|
||||
| EbmlTagIdEnum.EBMLReadVersion;
|
||||
|
||||
export type EbmlIntDataTagIdType =
|
||||
| EbmlTagIdEnum.TrackOffset
|
||||
| EbmlTagIdEnum.DiscardPadding
|
||||
| EbmlTagIdEnum.ReferenceVirtual
|
||||
| EbmlTagIdEnum.ReferenceBlock;
|
||||
|
||||
export type EbmlStringDataTagIdType =
|
||||
| EbmlTagIdEnum.CodecID
|
||||
| EbmlTagIdEnum.DocType
|
||||
| EbmlTagIdEnum.FileMimeType
|
||||
| EbmlTagIdEnum.TagLanguage
|
||||
| EbmlTagIdEnum.TargetType
|
||||
| EbmlTagIdEnum.ChapCountry
|
||||
| EbmlTagIdEnum.ChapLanguage
|
||||
| EbmlTagIdEnum.CodecDownloadURL
|
||||
| EbmlTagIdEnum.CodecInfoURL
|
||||
| EbmlTagIdEnum.Language;
|
||||
|
||||
export type EbmlUtf8DataTagIdType =
|
||||
| EbmlTagIdEnum.ChapString
|
||||
| EbmlTagIdEnum.TagString
|
||||
| EbmlTagIdEnum.ChapterStringUID
|
||||
| EbmlTagIdEnum.WritingApp
|
||||
| EbmlTagIdEnum.SegmentFilename
|
||||
| EbmlTagIdEnum.CodecName
|
||||
| EbmlTagIdEnum.TagName
|
||||
| EbmlTagIdEnum.FileName
|
||||
| EbmlTagIdEnum.FileDescription
|
||||
| EbmlTagIdEnum.CodecSettings
|
||||
| EbmlTagIdEnum.Name
|
||||
| EbmlTagIdEnum.MuxingApp
|
||||
| EbmlTagIdEnum.Title
|
||||
| EbmlTagIdEnum.NextFilename
|
||||
| EbmlTagIdEnum.PrevFilename;
|
||||
|
||||
export type EbmlFloatDataTagIdType =
|
||||
| EbmlTagIdEnum.Duration
|
||||
| EbmlTagIdEnum.OutputSamplingFrequency
|
||||
| EbmlTagIdEnum.SamplingFrequency
|
||||
| EbmlTagIdEnum.FrameRate
|
||||
| EbmlTagIdEnum.GammaValue
|
||||
| EbmlTagIdEnum.TrackTimecodeScale;
|
||||
|
||||
export type EbmlDateDataTagIdType = EbmlTagIdEnum.DateUTC;
|
||||
|
||||
export type EbmlBinaryDataTagIdType =
|
||||
| EbmlTagIdEnum.ContentCompSettings
|
||||
| EbmlTagIdEnum.SegmentFamily
|
||||
| EbmlTagIdEnum.TagBinary
|
||||
| EbmlTagIdEnum.FileReferral
|
||||
| EbmlTagIdEnum.SignedElement
|
||||
| EbmlTagIdEnum.ChapProcessData
|
||||
| EbmlTagIdEnum.ChapProcessPrivate
|
||||
| EbmlTagIdEnum.ChapterSegmentUID
|
||||
| EbmlTagIdEnum.FileData
|
||||
| EbmlTagIdEnum.ContentSigKeyID
|
||||
| EbmlTagIdEnum.ContentSignature
|
||||
| EbmlTagIdEnum.ContentEncKeyID
|
||||
| EbmlTagIdEnum.TrickMasterTrackSegmentUID
|
||||
| EbmlTagIdEnum.TrickTrackSegmentUID
|
||||
| EbmlTagIdEnum.ChannelPositions
|
||||
| EbmlTagIdEnum.ColourSpace
|
||||
| EbmlTagIdEnum.TrackTranslateTrackID
|
||||
| EbmlTagIdEnum.CodecPrivate
|
||||
| EbmlTagIdEnum.EncryptedBlock
|
||||
| EbmlTagIdEnum.CodecState
|
||||
| EbmlTagIdEnum.BlockAdditional
|
||||
| EbmlTagIdEnum.BlockVirtual
|
||||
| EbmlTagIdEnum.ChapterTranslateID
|
||||
| EbmlTagIdEnum.NextUID
|
||||
| EbmlTagIdEnum.PrevUID
|
||||
| EbmlTagIdEnum.SegmentUID
|
||||
| EbmlTagIdEnum.SeekID
|
||||
| EbmlTagIdEnum.Signature
|
||||
| EbmlTagIdEnum.SignaturePublicKey
|
||||
| EbmlTagIdEnum.CRC32
|
||||
| EbmlTagIdEnum.Void;
|
||||
|
||||
export function isEbmlMasterTagId(
|
||||
tagId: EbmlTagIdType
|
||||
): tagId is EbmlMasterTagIdType {
|
||||
switch (tagId) {
|
||||
case EbmlTagIdEnum.ChapterDisplay:
|
||||
case EbmlTagIdEnum.ContentCompression:
|
||||
case EbmlTagIdEnum.ContentEncryption:
|
||||
case EbmlTagIdEnum.SilentTracks:
|
||||
case EbmlTagIdEnum.ContentEncoding:
|
||||
case EbmlTagIdEnum.TrackTranslate:
|
||||
case EbmlTagIdEnum.ChapProcessCommand:
|
||||
case EbmlTagIdEnum.ChapterTranslate:
|
||||
case EbmlTagIdEnum.ChapProcess:
|
||||
case EbmlTagIdEnum.Tag:
|
||||
case EbmlTagIdEnum.Segment:
|
||||
case EbmlTagIdEnum.SimpleTag:
|
||||
case EbmlTagIdEnum.Targets:
|
||||
case EbmlTagIdEnum.Tags:
|
||||
case EbmlTagIdEnum.ChapterTrack:
|
||||
case EbmlTagIdEnum.ChapterAtom:
|
||||
case EbmlTagIdEnum.EditionEntry:
|
||||
case EbmlTagIdEnum.Chapters:
|
||||
case EbmlTagIdEnum.AttachedFile:
|
||||
case EbmlTagIdEnum.Attachments:
|
||||
case EbmlTagIdEnum.CueReference:
|
||||
case EbmlTagIdEnum.CueTrackPositions:
|
||||
case EbmlTagIdEnum.CuePoint:
|
||||
case EbmlTagIdEnum.Cues:
|
||||
case EbmlTagIdEnum.ContentEncAESSettings:
|
||||
case EbmlTagIdEnum.ContentEncodings:
|
||||
case EbmlTagIdEnum.TrackJoinBlocks:
|
||||
case EbmlTagIdEnum.TrackPlane:
|
||||
case EbmlTagIdEnum.TrackCombinePlanes:
|
||||
case EbmlTagIdEnum.TrackOperation:
|
||||
case EbmlTagIdEnum.Audio:
|
||||
case EbmlTagIdEnum.Video:
|
||||
case EbmlTagIdEnum.TrackEntry:
|
||||
case EbmlTagIdEnum.Tracks:
|
||||
case EbmlTagIdEnum.ReferenceFrame:
|
||||
case EbmlTagIdEnum.TimeSlice:
|
||||
case EbmlTagIdEnum.Slices:
|
||||
case EbmlTagIdEnum.BlockMore:
|
||||
case EbmlTagIdEnum.BlockAdditions:
|
||||
case EbmlTagIdEnum.BlockGroup:
|
||||
case EbmlTagIdEnum.Cluster:
|
||||
case EbmlTagIdEnum.Info:
|
||||
case EbmlTagIdEnum.Seek:
|
||||
case EbmlTagIdEnum.SeekHead:
|
||||
case EbmlTagIdEnum.SignatureElementList:
|
||||
case EbmlTagIdEnum.SignatureElements:
|
||||
case EbmlTagIdEnum.SignatureSlot:
|
||||
case EbmlTagIdEnum.EBML:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isEbmlBlockTagId(
|
||||
tagId: EbmlTagIdType
|
||||
): tagId is EbmlBlockTagIdType {
|
||||
return tagId === EbmlTagIdEnum.Block;
|
||||
}
|
||||
|
||||
export function isEbmlSimpleBlockTagId(
|
||||
tagId: EbmlTagIdType
|
||||
): tagId is EbmlSimpleBlockTagIdType {
|
||||
return tagId === EbmlTagIdEnum.SimpleBlock;
|
||||
}
|
||||
|
||||
export function isEbmlUintDataTagId(
|
||||
tagId: EbmlTagIdType
|
||||
): tagId is EbmlUintDataTagIdType {
|
||||
switch (tagId) {
|
||||
case EbmlTagIdEnum.TrackType:
|
||||
case EbmlTagIdEnum.FlagDefault:
|
||||
case EbmlTagIdEnum.ChapterTrackNumber:
|
||||
case EbmlTagIdEnum.ChapterTimeStart:
|
||||
case EbmlTagIdEnum.ChapterTimeEnd:
|
||||
case EbmlTagIdEnum.CueRefTime:
|
||||
case EbmlTagIdEnum.CueRefCluster:
|
||||
case EbmlTagIdEnum.ChapterFlagHidden:
|
||||
case EbmlTagIdEnum.ContentCompAlgo:
|
||||
case EbmlTagIdEnum.DocTypeReadVersion:
|
||||
case EbmlTagIdEnum.EBMLVersion:
|
||||
case EbmlTagIdEnum.DocTypeVersion:
|
||||
case EbmlTagIdEnum.TagDefault:
|
||||
case EbmlTagIdEnum.ChapterFlagEnabled:
|
||||
case EbmlTagIdEnum.FileUsedStartTime:
|
||||
case EbmlTagIdEnum.FileUsedEndTime:
|
||||
case EbmlTagIdEnum.ContentEncodingOrder:
|
||||
case EbmlTagIdEnum.ContentEncodingScope:
|
||||
case EbmlTagIdEnum.ContentEncodingType:
|
||||
case EbmlTagIdEnum.CueBlockNumber:
|
||||
case EbmlTagIdEnum.BitDepth:
|
||||
case EbmlTagIdEnum.ChapProcessTime:
|
||||
case EbmlTagIdEnum.ChapProcessCodecID:
|
||||
case EbmlTagIdEnum.AttachmentLink:
|
||||
case EbmlTagIdEnum.TagAttachmentUID:
|
||||
case EbmlTagIdEnum.TagChapterUID:
|
||||
case EbmlTagIdEnum.TagEditionUID:
|
||||
case EbmlTagIdEnum.TagTrackUID:
|
||||
case EbmlTagIdEnum.TargetTypeValue:
|
||||
case EbmlTagIdEnum.ChapterPhysicalEquiv:
|
||||
case EbmlTagIdEnum.ChapterSegmentEditionUID:
|
||||
case EbmlTagIdEnum.ChapterUID:
|
||||
case EbmlTagIdEnum.EditionFlagOrdered:
|
||||
case EbmlTagIdEnum.EditionFlagDefault:
|
||||
case EbmlTagIdEnum.EditionFlagHidden:
|
||||
case EbmlTagIdEnum.EditionUID:
|
||||
case EbmlTagIdEnum.FileUID:
|
||||
case EbmlTagIdEnum.CueRefCodecState:
|
||||
case EbmlTagIdEnum.CueRefNumber:
|
||||
case EbmlTagIdEnum.CueCodecState:
|
||||
case EbmlTagIdEnum.CueDuration:
|
||||
case EbmlTagIdEnum.CueRelativePosition:
|
||||
case EbmlTagIdEnum.CueClusterPosition:
|
||||
case EbmlTagIdEnum.CueTrack:
|
||||
case EbmlTagIdEnum.CueTime:
|
||||
case EbmlTagIdEnum.AESSettingsCipherMode:
|
||||
case EbmlTagIdEnum.ContentSigHashAlgo:
|
||||
case EbmlTagIdEnum.ContentSigAlgo:
|
||||
case EbmlTagIdEnum.ContentEncAlgo:
|
||||
case EbmlTagIdEnum.TrickMasterTrackUID:
|
||||
case EbmlTagIdEnum.TrickTrackFlag:
|
||||
case EbmlTagIdEnum.TrickTrackUID:
|
||||
case EbmlTagIdEnum.TrackJoinUID:
|
||||
case EbmlTagIdEnum.TrackPlaneType:
|
||||
case EbmlTagIdEnum.TrackPlaneUID:
|
||||
case EbmlTagIdEnum.Channels:
|
||||
case EbmlTagIdEnum.AspectRatioType:
|
||||
case EbmlTagIdEnum.DisplayUnit:
|
||||
case EbmlTagIdEnum.DisplayHeight:
|
||||
case EbmlTagIdEnum.DisplayWidth:
|
||||
case EbmlTagIdEnum.PixelCropRight:
|
||||
case EbmlTagIdEnum.PixelCropLeft:
|
||||
case EbmlTagIdEnum.PixelCropTop:
|
||||
case EbmlTagIdEnum.PixelCropBottom:
|
||||
case EbmlTagIdEnum.PixelHeight:
|
||||
case EbmlTagIdEnum.PixelWidth:
|
||||
case EbmlTagIdEnum.OldStereoMode:
|
||||
case EbmlTagIdEnum.AlphaMode:
|
||||
case EbmlTagIdEnum.StereoMode:
|
||||
case EbmlTagIdEnum.FlagInterlaced:
|
||||
case EbmlTagIdEnum.TrackTranslateCodec:
|
||||
case EbmlTagIdEnum.TrackTranslateEditionUID:
|
||||
case EbmlTagIdEnum.SeekPreRoll:
|
||||
case EbmlTagIdEnum.CodecDelay:
|
||||
case EbmlTagIdEnum.TrackOverlay:
|
||||
case EbmlTagIdEnum.CodecDecodeAll:
|
||||
case EbmlTagIdEnum.MaxBlockAdditionID:
|
||||
case EbmlTagIdEnum.DefaultDecodedFieldDuration:
|
||||
case EbmlTagIdEnum.DefaultDuration:
|
||||
case EbmlTagIdEnum.MaxCache:
|
||||
case EbmlTagIdEnum.MinCache:
|
||||
case EbmlTagIdEnum.FlagLacing:
|
||||
case EbmlTagIdEnum.FlagForced:
|
||||
case EbmlTagIdEnum.FlagEnabled:
|
||||
case EbmlTagIdEnum.TrackUID:
|
||||
case EbmlTagIdEnum.TrackNumber:
|
||||
case EbmlTagIdEnum.ReferenceTimeCode:
|
||||
case EbmlTagIdEnum.ReferenceOffset:
|
||||
case EbmlTagIdEnum.SliceDuration:
|
||||
case EbmlTagIdEnum.Delay:
|
||||
case EbmlTagIdEnum.BlockAdditionID:
|
||||
case EbmlTagIdEnum.FrameNumber:
|
||||
case EbmlTagIdEnum.LaceNumber:
|
||||
case EbmlTagIdEnum.ReferencePriority:
|
||||
case EbmlTagIdEnum.BlockDuration:
|
||||
case EbmlTagIdEnum.BlockAddID:
|
||||
case EbmlTagIdEnum.PrevSize:
|
||||
case EbmlTagIdEnum.Position:
|
||||
case EbmlTagIdEnum.SilentTrackNumber:
|
||||
case EbmlTagIdEnum.Timecode:
|
||||
case EbmlTagIdEnum.TimecodeScaleDenominator:
|
||||
case EbmlTagIdEnum.TimecodeScale:
|
||||
case EbmlTagIdEnum.ChapterTranslateCodec:
|
||||
case EbmlTagIdEnum.ChapterTranslateEditionUID:
|
||||
case EbmlTagIdEnum.SeekPosition:
|
||||
case EbmlTagIdEnum.SignatureHash:
|
||||
case EbmlTagIdEnum.SignatureAlgo:
|
||||
case EbmlTagIdEnum.EBMLMaxSizeLength:
|
||||
case EbmlTagIdEnum.EBMLMaxIDLength:
|
||||
case EbmlTagIdEnum.EBMLReadVersion:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isEbmlIntDataTagId(
|
||||
tagId: EbmlTagIdType
|
||||
): tagId is EbmlIntDataTagIdType {
|
||||
switch (tagId) {
|
||||
case EbmlTagIdEnum.TrackOffset:
|
||||
case EbmlTagIdEnum.DiscardPadding:
|
||||
case EbmlTagIdEnum.ReferenceVirtual:
|
||||
case EbmlTagIdEnum.ReferenceBlock:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isEbmlFloatDataTagId(
|
||||
tagId: EbmlTagIdType
|
||||
): tagId is EbmlFloatDataTagIdType {
|
||||
switch (tagId) {
|
||||
case EbmlTagIdEnum.Duration:
|
||||
case EbmlTagIdEnum.OutputSamplingFrequency:
|
||||
case EbmlTagIdEnum.SamplingFrequency:
|
||||
case EbmlTagIdEnum.FrameRate:
|
||||
case EbmlTagIdEnum.GammaValue:
|
||||
case EbmlTagIdEnum.TrackTimecodeScale:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isEbmlStringDataTagId(
|
||||
tagId: EbmlTagIdType
|
||||
): tagId is EbmlStringDataTagIdType {
|
||||
switch (tagId) {
|
||||
case EbmlTagIdEnum.CodecID:
|
||||
case EbmlTagIdEnum.DocType:
|
||||
case EbmlTagIdEnum.FileMimeType:
|
||||
case EbmlTagIdEnum.TagLanguage:
|
||||
case EbmlTagIdEnum.TargetType:
|
||||
case EbmlTagIdEnum.ChapCountry:
|
||||
case EbmlTagIdEnum.ChapLanguage:
|
||||
case EbmlTagIdEnum.CodecDownloadURL:
|
||||
case EbmlTagIdEnum.CodecInfoURL:
|
||||
case EbmlTagIdEnum.Language:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isEbmlUtf8DataTagId(
|
||||
tagId: EbmlTagIdType
|
||||
): tagId is EbmlUtf8DataTagIdType {
|
||||
switch (tagId) {
|
||||
case EbmlTagIdEnum.ChapString:
|
||||
case EbmlTagIdEnum.TagString:
|
||||
case EbmlTagIdEnum.ChapterStringUID:
|
||||
case EbmlTagIdEnum.WritingApp:
|
||||
case EbmlTagIdEnum.SegmentFilename:
|
||||
case EbmlTagIdEnum.CodecName:
|
||||
case EbmlTagIdEnum.TagName:
|
||||
case EbmlTagIdEnum.FileName:
|
||||
case EbmlTagIdEnum.FileDescription:
|
||||
case EbmlTagIdEnum.CodecSettings:
|
||||
case EbmlTagIdEnum.Name:
|
||||
case EbmlTagIdEnum.MuxingApp:
|
||||
case EbmlTagIdEnum.Title:
|
||||
case EbmlTagIdEnum.NextFilename:
|
||||
case EbmlTagIdEnum.PrevFilename:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isEbmlDateDataTagId(
|
||||
tagId: EbmlTagIdType
|
||||
): tagId is EbmlDateDataTagIdType {
|
||||
return tagId === EbmlTagIdEnum.DateUTC;
|
||||
}
|
||||
|
||||
export function isEbmlBinaryDataTagId(
|
||||
tagId: EbmlTagIdType
|
||||
): tagId is EbmlBinaryDataTagIdType {
|
||||
switch (tagId) {
|
||||
case EbmlTagIdEnum.ContentCompSettings:
|
||||
case EbmlTagIdEnum.SegmentFamily:
|
||||
case EbmlTagIdEnum.TagBinary:
|
||||
case EbmlTagIdEnum.FileReferral:
|
||||
case EbmlTagIdEnum.SignedElement:
|
||||
case EbmlTagIdEnum.ChapProcessData:
|
||||
case EbmlTagIdEnum.ChapProcessPrivate:
|
||||
case EbmlTagIdEnum.ChapterSegmentUID:
|
||||
case EbmlTagIdEnum.FileData:
|
||||
case EbmlTagIdEnum.ContentSigKeyID:
|
||||
case EbmlTagIdEnum.ContentSignature:
|
||||
case EbmlTagIdEnum.ContentEncKeyID:
|
||||
case EbmlTagIdEnum.TrickMasterTrackSegmentUID:
|
||||
case EbmlTagIdEnum.TrickTrackSegmentUID:
|
||||
case EbmlTagIdEnum.ChannelPositions:
|
||||
case EbmlTagIdEnum.ColourSpace:
|
||||
case EbmlTagIdEnum.TrackTranslateTrackID:
|
||||
case EbmlTagIdEnum.CodecPrivate:
|
||||
case EbmlTagIdEnum.EncryptedBlock:
|
||||
case EbmlTagIdEnum.CodecState:
|
||||
case EbmlTagIdEnum.BlockAdditional:
|
||||
case EbmlTagIdEnum.BlockVirtual:
|
||||
case EbmlTagIdEnum.ChapterTranslateID:
|
||||
case EbmlTagIdEnum.NextUID:
|
||||
case EbmlTagIdEnum.PrevUID:
|
||||
case EbmlTagIdEnum.SegmentUID:
|
||||
case EbmlTagIdEnum.SeekID:
|
||||
case EbmlTagIdEnum.Signature:
|
||||
case EbmlTagIdEnum.SignaturePublicKey:
|
||||
case EbmlTagIdEnum.CRC32:
|
||||
case EbmlTagIdEnum.Void:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isUnknownDataTagId(
|
||||
tagId: EbmlDataTagIdType
|
||||
): tagId is Exclude<EbmlDataTagIdType, EbmlTagIdEnum> {
|
||||
return tagId in EbmlTagIdEnum;
|
||||
}
|
||||
export enum EbmlBlockLacing {
|
||||
None = 1,
|
||||
Xiph = 2,
|
||||
EBML = 3,
|
||||
FixedSize = 4,
|
||||
}
|
123
src/models/tag-block.ts
Normal file
123
src/models/tag-block.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { type CreateEbmlDataTagOptions, EbmlDataTag } from './tag-data';
|
||||
import { EbmlBlockLacing } from './enums';
|
||||
import {
|
||||
dataViewSlice,
|
||||
dataViewSliceToBuf,
|
||||
readSigned,
|
||||
readVint,
|
||||
writeSigned,
|
||||
writeVint,
|
||||
} from '../tools';
|
||||
import {
|
||||
type EbmlBlockTagIdType,
|
||||
type EbmlSimpleBlockTagIdType,
|
||||
EbmlTagIdEnum,
|
||||
} from './enums';
|
||||
import { EbmlElementType } from './enums';
|
||||
import type { FileDataViewController } from '../adapters';
|
||||
|
||||
export interface CreateEbmlBlockTagOptions
|
||||
extends Omit<CreateEbmlDataTagOptions, 'id' | 'type'> {
|
||||
id?: EbmlBlockTagIdType | EbmlSimpleBlockTagIdType;
|
||||
}
|
||||
|
||||
export class EbmlBlockTag extends EbmlDataTag {
|
||||
payload = new Uint8Array(0);
|
||||
track: number | bigint = 0;
|
||||
value = 0;
|
||||
|
||||
invisible: boolean | undefined;
|
||||
lacing: EbmlBlockLacing | undefined;
|
||||
|
||||
constructor(options: CreateEbmlBlockTagOptions) {
|
||||
super({
|
||||
...options,
|
||||
id: options.id ?? EbmlTagIdEnum.Block,
|
||||
type: EbmlElementType.Binary,
|
||||
});
|
||||
}
|
||||
|
||||
protected writeTrackBuffer(): Uint8Array {
|
||||
return writeVint(this.track);
|
||||
}
|
||||
|
||||
protected writeValueBuffer(): Uint8Array {
|
||||
return writeSigned(this.value, 2);
|
||||
}
|
||||
|
||||
protected writeFlagsBuffer(): Uint8Array {
|
||||
let flags = 0x00;
|
||||
if (this.invisible) {
|
||||
flags |= 0x10;
|
||||
}
|
||||
|
||||
switch (this.lacing) {
|
||||
case EbmlBlockLacing.None:
|
||||
break;
|
||||
case EbmlBlockLacing.Xiph:
|
||||
flags |= 0x04;
|
||||
break;
|
||||
case EbmlBlockLacing.EBML:
|
||||
flags |= 0x08;
|
||||
break;
|
||||
case EbmlBlockLacing.FixedSize:
|
||||
flags |= 0x0c;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
return new Uint8Array([flags % 256]);
|
||||
}
|
||||
|
||||
*encodeContent(): Generator<Uint8Array, void, unknown> {
|
||||
yield this.writeTrackBuffer();
|
||||
yield this.writeValueBuffer();
|
||||
yield this.writeFlagsBuffer();
|
||||
yield this.payload;
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/useYield: <explanation>
|
||||
async *decodeContentImpl(controller: FileDataViewController) {
|
||||
const offset = controller.getOffset();
|
||||
const view = await controller.read(offset, this.contentLength, true);
|
||||
const track = readVint(view)!;
|
||||
this.track = track.value;
|
||||
this.value = Number(
|
||||
readSigned(dataViewSlice(view, track.length, track.length + 2))
|
||||
);
|
||||
const flags: number = view.getUint8(track.length + 2);
|
||||
this.invisible = Boolean(flags & 0x10);
|
||||
switch (flags & 0x0c) {
|
||||
case 0x00:
|
||||
this.lacing = EbmlBlockLacing.None;
|
||||
break;
|
||||
|
||||
case 0x04:
|
||||
this.lacing = EbmlBlockLacing.Xiph;
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
this.lacing = EbmlBlockLacing.EBML;
|
||||
break;
|
||||
|
||||
case 0x0c:
|
||||
this.lacing = EbmlBlockLacing.FixedSize;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
this.payload = dataViewSliceToBuf(view, track.length + 3, undefined);
|
||||
await controller.seek(offset + view.byteLength);
|
||||
}
|
||||
|
||||
override toDebugRecord() {
|
||||
const s = super.toDebugRecord();
|
||||
return {
|
||||
...s,
|
||||
payload: this.payload,
|
||||
track: this.track,
|
||||
value: this.value,
|
||||
invisible: this.invisible,
|
||||
lacing: EbmlBlockLacing[this.lacing!] || this.lacing,
|
||||
};
|
||||
}
|
||||
}
|
92
src/models/tag-data.ts
Normal file
92
src/models/tag-data.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { type CreateEbmlTagOptions, EbmlTagTrait } from './tag-trait';
|
||||
import { EbmlElementType } from './enums';
|
||||
import {
|
||||
dataViewSlice,
|
||||
dataViewSliceToBuf,
|
||||
readAscii,
|
||||
readFloat,
|
||||
readSigned,
|
||||
readUnsigned,
|
||||
readUtf8,
|
||||
writeAscii,
|
||||
writeFloat,
|
||||
writeSigned,
|
||||
writeUnsigned,
|
||||
writeUtf8,
|
||||
} from '../tools';
|
||||
import { EbmlTagPosition } from './enums';
|
||||
import type { FileDataViewController } from 'src/adapters';
|
||||
|
||||
export type CreateEbmlDataTagOptions = Omit<CreateEbmlTagOptions, 'position'>;
|
||||
|
||||
export class EbmlDataTag extends EbmlTagTrait {
|
||||
data: number | string | bigint | null | Uint8Array | undefined;
|
||||
|
||||
constructor(options: CreateEbmlDataTagOptions) {
|
||||
super({
|
||||
...options,
|
||||
position: EbmlTagPosition.Content,
|
||||
});
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/useYield: <explanation>
|
||||
override async *decodeContentImpl(controller: FileDataViewController) {
|
||||
const offset = controller.getOffset();
|
||||
const view = await controller.read(offset, this.contentLength, true);
|
||||
switch (this.type) {
|
||||
case EbmlElementType.UnsignedInt:
|
||||
this.data = readUnsigned(view);
|
||||
break;
|
||||
case EbmlElementType.Float:
|
||||
this.data = readFloat(view);
|
||||
break;
|
||||
case EbmlElementType.Integer:
|
||||
this.data = readSigned(view);
|
||||
break;
|
||||
case EbmlElementType.String:
|
||||
this.data = readAscii(view);
|
||||
break;
|
||||
case EbmlElementType.UTF8:
|
||||
this.data = readUtf8(view);
|
||||
break;
|
||||
default:
|
||||
this.data = dataViewSliceToBuf(view, undefined, undefined);
|
||||
break;
|
||||
}
|
||||
await controller.seek(offset + view.byteLength);
|
||||
}
|
||||
|
||||
*encodeContent(): Generator<Uint8Array, void, unknown> {
|
||||
switch (this.type) {
|
||||
case EbmlElementType.UnsignedInt:
|
||||
yield writeUnsigned(this.data as any);
|
||||
break;
|
||||
case EbmlElementType.Float:
|
||||
yield writeFloat(this.data as any);
|
||||
break;
|
||||
case EbmlElementType.Integer:
|
||||
yield writeSigned(this.data as any);
|
||||
break;
|
||||
case EbmlElementType.String:
|
||||
yield writeAscii(this.data as any);
|
||||
break;
|
||||
case EbmlElementType.UTF8:
|
||||
yield writeUtf8(this.data as any);
|
||||
break;
|
||||
default:
|
||||
yield this.data as Uint8Array;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
override toDebugRecord() {
|
||||
return {
|
||||
...super.toDebugRecord(),
|
||||
data: this.data,
|
||||
};
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return JSON.stringify(this.toDebugRecord(), null, 2);
|
||||
}
|
||||
}
|
93
src/models/tag-master.ts
Normal file
93
src/models/tag-master.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { type CreateEbmlTagOptions, EbmlTagTrait } from './tag-trait';
|
||||
import { EbmlElementType, EbmlTagPosition, isEbmlMasterTagId } from './enums';
|
||||
import { decodeEbmlTagHeader } from '../decode-utils';
|
||||
import { createEbmlTag } from 'src/factory';
|
||||
import type { EbmlMasterTagIdType } from './enums';
|
||||
import type { FileDataViewController } from '../adapters';
|
||||
|
||||
export interface CreateEbmlMasterTagOptions
|
||||
extends Omit<CreateEbmlTagOptions, 'position' | 'type' | 'id'> {
|
||||
id: EbmlMasterTagIdType;
|
||||
}
|
||||
|
||||
export class EbmlMasterTag extends EbmlTagTrait {
|
||||
private _children: EbmlTagTrait[] = [];
|
||||
|
||||
get children(): EbmlTagTrait[] {
|
||||
return this._children;
|
||||
}
|
||||
|
||||
set children(value: EbmlTagTrait[]) {
|
||||
this._children = value;
|
||||
}
|
||||
|
||||
constructor(options: CreateEbmlMasterTagOptions) {
|
||||
super({
|
||||
...options,
|
||||
id: options.id,
|
||||
type: EbmlElementType.Master,
|
||||
});
|
||||
}
|
||||
|
||||
*encodeContent(): Generator<Uint8Array, void, unknown> {
|
||||
for (const child of this.children) {
|
||||
yield* child.encode();
|
||||
}
|
||||
}
|
||||
|
||||
async *decodeContentImpl(controller: FileDataViewController) {
|
||||
while (true) {
|
||||
const offset = controller.getOffset();
|
||||
|
||||
if (offset >= this.endOffset) {
|
||||
break;
|
||||
}
|
||||
|
||||
const peeked = await controller.peek(offset);
|
||||
|
||||
if (!peeked) {
|
||||
break;
|
||||
}
|
||||
|
||||
const vints = await decodeEbmlTagHeader(controller);
|
||||
|
||||
const { tagId, tagVint, sizeVint } = vints;
|
||||
|
||||
const headerLength = tagVint.length + sizeVint.length;
|
||||
const contentLength = sizeVint.value;
|
||||
|
||||
const isMaster = isEbmlMasterTagId(tagId);
|
||||
|
||||
if (isMaster) {
|
||||
const tag: EbmlTagTrait = createEbmlTag(tagId, {
|
||||
headerLength,
|
||||
contentLength,
|
||||
startOffset: offset,
|
||||
position: EbmlTagPosition.Start,
|
||||
parent: this,
|
||||
});
|
||||
yield tag;
|
||||
}
|
||||
|
||||
await controller.seek(offset + headerLength);
|
||||
|
||||
const tag: EbmlTagTrait = createEbmlTag(tagId, {
|
||||
headerLength,
|
||||
contentLength,
|
||||
startOffset: offset,
|
||||
position: isMaster ? EbmlTagPosition.End : EbmlTagPosition.Content,
|
||||
parent: this,
|
||||
});
|
||||
|
||||
for await (const item of tag.decodeContent(controller)) {
|
||||
yield item;
|
||||
}
|
||||
|
||||
tag.endOffset = controller.getOffset();
|
||||
|
||||
this._children.push(tag);
|
||||
|
||||
yield tag;
|
||||
}
|
||||
}
|
||||
}
|
54
src/models/tag-simple-block.ts
Normal file
54
src/models/tag-simple-block.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { readVint } from '../tools';
|
||||
import { type CreateEbmlBlockTagOptions, EbmlBlockTag } from './tag-block';
|
||||
import type { EbmlSimpleBlockTagIdType } from './enums';
|
||||
import type { FileDataViewController } from '../adapters';
|
||||
|
||||
export interface CreateEbmlSimpleBlockTagOptions
|
||||
extends Omit<CreateEbmlBlockTagOptions, 'id'> {
|
||||
id?: EbmlSimpleBlockTagIdType;
|
||||
}
|
||||
|
||||
export class EbmlSimpleBlockTag extends EbmlBlockTag {
|
||||
discardable: boolean | undefined;
|
||||
keyframe: boolean | undefined;
|
||||
|
||||
// biome-ignore lint/complexity/noUselessConstructor: <explanation>
|
||||
constructor(options: CreateEbmlSimpleBlockTagOptions) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
*encodeContent(): Generator<Uint8Array, void, unknown> {
|
||||
const flags = this.writeFlagsBuffer();
|
||||
|
||||
if (this.keyframe) {
|
||||
flags[0] |= 0x80;
|
||||
}
|
||||
if (this.discardable) {
|
||||
flags[0] |= 0x01;
|
||||
}
|
||||
|
||||
yield this.writeTrackBuffer();
|
||||
yield this.writeValueBuffer();
|
||||
yield flags;
|
||||
yield this.payload;
|
||||
}
|
||||
|
||||
async *decodeContentImpl(controller: FileDataViewController) {
|
||||
const offset = controller.getOffset();
|
||||
|
||||
const view = await controller.read(offset, this.contentLength, true);
|
||||
|
||||
for await (const item of super.decodeContentImpl(controller)) {
|
||||
yield item;
|
||||
}
|
||||
|
||||
const trackVint = readVint(view)!;
|
||||
|
||||
const flags: number = view.getUint8(trackVint.length + 2);
|
||||
this.keyframe = Boolean(flags & 0x80);
|
||||
this.discardable = Boolean(flags & 0x01);
|
||||
|
||||
// seeked by block tag
|
||||
// await controller.seek(offset + this.contentLength);
|
||||
}
|
||||
}
|
202
src/models/tag-trait.ts
Normal file
202
src/models/tag-trait.ts
Normal file
@ -0,0 +1,202 @@
|
||||
import { EbmlTagPosition } from './enums';
|
||||
import { EbmlTagIdEnum, type EbmlTagIdType } from './enums';
|
||||
import type { EbmlElementType } from './enums';
|
||||
import { hexStringToBuf, UNKNOWN_SIZE_VINT_BUF, writeVint } from '../tools';
|
||||
import type { FileDataViewController } from '../adapters';
|
||||
import { InconsistentOffsetOnDecodingContentError } from '../errors';
|
||||
|
||||
export interface CreateEbmlTagOptions {
|
||||
id: EbmlTagIdType;
|
||||
type?: EbmlElementType;
|
||||
position?: EbmlTagPosition;
|
||||
headerLength: number;
|
||||
contentLength: number;
|
||||
startOffset: number;
|
||||
endOffset?: number;
|
||||
parent?: EbmlTagTrait;
|
||||
}
|
||||
|
||||
export abstract class EbmlTagTrait {
|
||||
/**
|
||||
* The id of the EBML tag.
|
||||
* In most documentation this number is in hexadecimal format
|
||||
*/
|
||||
id: EbmlTagIdType;
|
||||
/**
|
||||
* The data type of the EBML tag
|
||||
*/
|
||||
type?: EbmlElementType;
|
||||
/**
|
||||
* The position of this EBML tag.
|
||||
* Currently, one of "Start", "Content", or "End".
|
||||
* "Start" and "End" only for Master type
|
||||
*/
|
||||
position: EbmlTagPosition;
|
||||
/**
|
||||
* Size vint length + tag vint length
|
||||
*/
|
||||
headerLength: number;
|
||||
/**
|
||||
* Start offset relative to context (stream or file) start
|
||||
*/
|
||||
startOffset: number;
|
||||
/**
|
||||
* Parent node
|
||||
*/
|
||||
parent?: EbmlTagTrait;
|
||||
|
||||
/**
|
||||
* Content length in ebml data
|
||||
* Return Number.POSITIVE_INFINITY as "unknown"
|
||||
*/
|
||||
private _contentLength: number;
|
||||
/**
|
||||
* Caculated end offset when
|
||||
*/
|
||||
private _endOffset?: number;
|
||||
|
||||
constructor(options: CreateEbmlTagOptions) {
|
||||
this.id = options.id;
|
||||
this.type = options.type;
|
||||
this.position = options.position ?? EbmlTagPosition.Content;
|
||||
this.parent = options.parent;
|
||||
this.startOffset = options.startOffset;
|
||||
this.headerLength = options.headerLength;
|
||||
this._contentLength = options.contentLength;
|
||||
this._endOffset = options.endOffset;
|
||||
}
|
||||
|
||||
public set contentLength(value: number) {
|
||||
this._contentLength = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* After caculated or known, manually set endOffset
|
||||
*/
|
||||
public set endOffset(offset: number) {
|
||||
this._endOffset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* End offset relative to context (stream or file) start
|
||||
* Calcalate from self _contentLength and parent end offset
|
||||
* Return Number.POSITIVE_INFINITY as "unknown"
|
||||
*/
|
||||
public get endOffset(): number {
|
||||
if (this._endOffset) {
|
||||
return this._endOffset;
|
||||
}
|
||||
if (this._contentLength === Number.POSITIVE_INFINITY) {
|
||||
return this.parent?.endOffset ?? Number.POSITIVE_INFINITY;
|
||||
}
|
||||
return this.startOffset + this.headerLength + this._contentLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Header length + Content Length
|
||||
* Calcalate from self _contentLength and parent end offset
|
||||
* Return Number.POSITIVE_INFINITY as "unknown"
|
||||
*/
|
||||
public get totalLength(): number {
|
||||
return this.endOffset - this.startOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content Length
|
||||
* Calcalate from self _contentLength and parent end offset
|
||||
* Return Number.POSITIVE_INFINITY as "unknown"
|
||||
*/
|
||||
public get contentLength(): number {
|
||||
return this.totalLength - this.headerLength;
|
||||
}
|
||||
|
||||
protected abstract encodeContent(): Generator<Uint8Array, void, unknown>;
|
||||
|
||||
/**
|
||||
* Deep traversal and parse all descendants then yield as AsyncGenerator
|
||||
* @param controller DataView controller, simulate async filesystem file
|
||||
*/
|
||||
protected abstract decodeContentImpl(
|
||||
controller: FileDataViewController
|
||||
): AsyncGenerator<EbmlTagTrait, void, unknown>;
|
||||
|
||||
/**
|
||||
* Wrap of abstract decode content impl function, add before and after lifecircle check
|
||||
* @param controller DataView controller, simulate async filesystem file
|
||||
* @returns Deep traversal async iterators of all descendants
|
||||
*/
|
||||
public async *decodeContent(
|
||||
controller: FileDataViewController
|
||||
): AsyncGenerator<EbmlTagTrait, void, unknown> {
|
||||
if (this.contentLength === 0 || this.position === EbmlTagPosition.Start) {
|
||||
return;
|
||||
}
|
||||
const startOffset = controller.getOffset();
|
||||
for await (const tag of this.decodeContentImpl(controller)) {
|
||||
yield tag;
|
||||
}
|
||||
const endOffset = controller.getOffset();
|
||||
if (
|
||||
startOffset + this.contentLength !== endOffset &&
|
||||
this.contentLength !== Number.POSITIVE_INFINITY
|
||||
) {
|
||||
throw new InconsistentOffsetOnDecodingContentError(this, endOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private getTagDeclaration(): Uint8Array {
|
||||
let tagHex = this.id.toString(16);
|
||||
if (tagHex.length % 2 !== 0) {
|
||||
tagHex = `0${tagHex}`;
|
||||
}
|
||||
return hexStringToBuf(tagHex);
|
||||
}
|
||||
|
||||
public *encodeHeader(): Generator<Uint8Array, void, unknown> {
|
||||
const tagEncoded = this.getTagDeclaration();
|
||||
yield tagEncoded;
|
||||
if (this._contentLength === Number.POSITIVE_INFINITY) {
|
||||
const mayBeSizeLength = this.headerLength - tagEncoded.byteLength;
|
||||
if (mayBeSizeLength > 0 && mayBeSizeLength <= 8) {
|
||||
yield UNKNOWN_SIZE_VINT_BUF[mayBeSizeLength];
|
||||
} else {
|
||||
yield UNKNOWN_SIZE_VINT_BUF[2];
|
||||
}
|
||||
} else {
|
||||
yield writeVint(this._contentLength);
|
||||
}
|
||||
}
|
||||
|
||||
public *encode(): Generator<Uint8Array, void, unknown> {
|
||||
if (this._contentLength === Number.POSITIVE_INFINITY) {
|
||||
yield* this.encodeHeader();
|
||||
for (const part of this.encodeContent()) {
|
||||
yield part;
|
||||
}
|
||||
} else {
|
||||
let size = 0;
|
||||
const parts: Uint8Array[] = [];
|
||||
for (const part of this.encodeContent()) {
|
||||
parts.push(part);
|
||||
size += part.byteLength;
|
||||
}
|
||||
this._contentLength = size;
|
||||
yield* this.encodeHeader();
|
||||
for (const part of parts) {
|
||||
yield part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public toDebugRecord(): Record<string, any> {
|
||||
return {
|
||||
id: EbmlTagIdEnum[this.id as any] || this.id,
|
||||
type: this.type,
|
||||
position: EbmlTagPosition[this.position],
|
||||
contentLength: this.contentLength,
|
||||
headerLength: this.headerLength,
|
||||
startOffset: this.startOffset,
|
||||
endOffset: this.endOffset,
|
||||
};
|
||||
}
|
||||
}
|
671
src/tools.ts
Normal file
671
src/tools.ts
Normal file
@ -0,0 +1,671 @@
|
||||
import {
|
||||
VintOutOfRangeError,
|
||||
VintLengthOutOfRangeError,
|
||||
ElementIdVintDataAllOnesError,
|
||||
ElementIdVintDataAllZerosError,
|
||||
ElementIdVintDataNotShortestError,
|
||||
UnsupportLengthForElementTypeError,
|
||||
OutOfRangeForElementTypeError,
|
||||
SizeUnitOutOfSafeIntegerRangeError,
|
||||
UnreachableOrLogicError,
|
||||
} from './errors';
|
||||
import {
|
||||
EbmlElementType,
|
||||
type EbmlTagIdType,
|
||||
isEbmlMasterTagId,
|
||||
} from './models/enums';
|
||||
|
||||
export const UTF8_DECODER = new TextDecoder('utf-8');
|
||||
export const ASCII_DECODER = new TextDecoder('ascii');
|
||||
export const UTF8_ENCODER = new TextEncoder();
|
||||
export const MAX_SAFE_INTEGER_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
|
||||
export const VINT_WIDTH_MARKER_VALUE_TABLE = [
|
||||
0,
|
||||
128,
|
||||
16384,
|
||||
2097152,
|
||||
268435456,
|
||||
34359738368,
|
||||
4398046511104,
|
||||
562949953421312,
|
||||
72057594037927936n,
|
||||
] as [number, number, number, number, number, number, number, number, bigint];
|
||||
|
||||
export const ELEM_ID_VINT_DATA_UP_LIMIT = [
|
||||
0,
|
||||
127,
|
||||
16383,
|
||||
2097151,
|
||||
268435455,
|
||||
34359738367,
|
||||
4398046511103,
|
||||
562949953421311,
|
||||
72057594037927935n,
|
||||
] as [number, number, number, number, number, number, number, number, bigint];
|
||||
export const MAX_UINT_TABLE = [
|
||||
0,
|
||||
255,
|
||||
65535,
|
||||
16777215,
|
||||
4294967295,
|
||||
1099511627775,
|
||||
281474976710655,
|
||||
72057594037927935n,
|
||||
18446744073709551615n,
|
||||
];
|
||||
export const MIN_INT_TABLE = [
|
||||
0,
|
||||
-128,
|
||||
-32768,
|
||||
-8388608,
|
||||
-2147483648,
|
||||
-549755813888,
|
||||
-140737488355328,
|
||||
-36028797018963968n,
|
||||
-9223372036854775808n,
|
||||
];
|
||||
export const MAX_INT_TABLE = [
|
||||
127,
|
||||
32767,
|
||||
8388607,
|
||||
2147483647,
|
||||
549755813887,
|
||||
140737488355327,
|
||||
36028797018963967n,
|
||||
9223372036854775807n,
|
||||
];
|
||||
export const MAX_UINT32 = 4294967295;
|
||||
export const MAX_UINT64 = 18446744073709551615n;
|
||||
export const MIN_INT32 = -2147483648;
|
||||
export const MAX_INT32 = 2147483647;
|
||||
export const MIN_INT64 = -9223372036854775808n;
|
||||
export const MAX_INT64 = 9223372036854775807n;
|
||||
export const UNKNOWN_SIZE_VINT_BUF = [
|
||||
new Uint8Array(0),
|
||||
new Uint8Array([0xff]),
|
||||
new Uint8Array([0x7f, 0xff]),
|
||||
new Uint8Array([0x3f, 0xff, 0xff]),
|
||||
new Uint8Array([0x1f, 0xff, 0xff, 0xff]),
|
||||
new Uint8Array([0x0f, 0xff, 0xff, 0xff, 0xff]),
|
||||
new Uint8Array([0x07, 0xff, 0xff, 0xff, 0xff, 0xff]),
|
||||
new Uint8Array([0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
|
||||
new Uint8Array([0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
|
||||
];
|
||||
export const UNKNOWN_SIZE_VINT_VALUE = [
|
||||
0,
|
||||
127,
|
||||
16383,
|
||||
2097151,
|
||||
268435455,
|
||||
34359738367,
|
||||
4398046511103,
|
||||
562949953421311,
|
||||
72057594037927935n,
|
||||
] as [number, number, number, number, number, number, number, number, bigint];
|
||||
|
||||
export interface Vint {
|
||||
length: number;
|
||||
value: number | bigint;
|
||||
}
|
||||
|
||||
export interface SafeSizeVint extends Vint {
|
||||
length: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export function checkVintSafeSize(
|
||||
vint: Vint,
|
||||
tagId: EbmlTagIdType
|
||||
): SafeSizeVint {
|
||||
const n = vint.value;
|
||||
if (typeof n === 'bigint') {
|
||||
if (vint.length === 8 && UNKNOWN_SIZE_VINT_VALUE[8] === n) {
|
||||
if (!isEbmlMasterTagId(tagId)) {
|
||||
throw new UnreachableOrLogicError(
|
||||
'only master tag size can be unknown (vint_data be all ones)'
|
||||
);
|
||||
}
|
||||
return {
|
||||
length: vint.length,
|
||||
value: Number.POSITIVE_INFINITY,
|
||||
};
|
||||
}
|
||||
throw new SizeUnitOutOfSafeIntegerRangeError(vint.value);
|
||||
}
|
||||
if (n === 0) {
|
||||
return vint as SafeSizeVint;
|
||||
}
|
||||
if (n === (UNKNOWN_SIZE_VINT_VALUE[vint.length] as number)) {
|
||||
if (!isEbmlMasterTagId(tagId)) {
|
||||
throw new UnreachableOrLogicError(
|
||||
'only master tag size can be unknown (vint_data be all ones)'
|
||||
);
|
||||
}
|
||||
return {
|
||||
length: vint.length,
|
||||
value: Number.POSITIVE_INFINITY,
|
||||
};
|
||||
}
|
||||
return vint as SafeSizeVint;
|
||||
}
|
||||
|
||||
export function readVintLength(view: DataView) {
|
||||
if (view.byteLength < 1) {
|
||||
throw new VintLengthOutOfRangeError(0);
|
||||
}
|
||||
const length = 8 - Math.floor(Math.log2(view.getUint8(0)));
|
||||
|
||||
if (length > 8 || length < 0 || Number.isNaN(length)) {
|
||||
throw new VintLengthOutOfRangeError(length);
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
export function readVint(view: DataView): Vint | null {
|
||||
if (view.byteLength < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const length = readVintLength(view);
|
||||
|
||||
if (length > view.byteLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let value: number | bigint;
|
||||
if (length === 8) {
|
||||
value = view.getBigUint64(0, false) - VINT_WIDTH_MARKER_VALUE_TABLE[8];
|
||||
if (value <= MAX_SAFE_INTEGER_BIGINT) {
|
||||
value = Number(value);
|
||||
}
|
||||
} else if (length === 4) {
|
||||
value = view.getUint32(0, false) - VINT_WIDTH_MARKER_VALUE_TABLE[4];
|
||||
} else if (length === 2) {
|
||||
value = view.getUint16(0, false) - VINT_WIDTH_MARKER_VALUE_TABLE[2];
|
||||
} else if (length === 1) {
|
||||
value = view.getUint8(0) - VINT_WIDTH_MARKER_VALUE_TABLE[1];
|
||||
} else {
|
||||
value = 0;
|
||||
for (let i = 0; i < length; i++) {
|
||||
value = value * 256 + view.getUint8(i);
|
||||
}
|
||||
value -= VINT_WIDTH_MARKER_VALUE_TABLE[length] as number;
|
||||
}
|
||||
|
||||
return {
|
||||
length,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function readElementIdVint(view: DataView): Vint | null {
|
||||
const vint = readVint(view);
|
||||
if (!vint) {
|
||||
return null;
|
||||
}
|
||||
const length = vint.length;
|
||||
const value = vint.value;
|
||||
if (value <= 0) {
|
||||
throw new ElementIdVintDataAllZerosError(value);
|
||||
}
|
||||
if (value === ELEM_ID_VINT_DATA_UP_LIMIT[length]) {
|
||||
throw new ElementIdVintDataAllOnesError(value);
|
||||
}
|
||||
if (value < ELEM_ID_VINT_DATA_UP_LIMIT[length - 1]) {
|
||||
throw new ElementIdVintDataNotShortestError(
|
||||
((value as any) + VINT_WIDTH_MARKER_VALUE_TABLE[length]) as any
|
||||
);
|
||||
}
|
||||
return vint;
|
||||
}
|
||||
|
||||
export function writeVint(
|
||||
value: number | bigint,
|
||||
desiredLength?: number
|
||||
): Uint8Array {
|
||||
if (value < 0 || value >= VINT_WIDTH_MARKER_VALUE_TABLE[8]) {
|
||||
throw new VintOutOfRangeError(value);
|
||||
}
|
||||
if (desiredLength! > 8 || desiredLength! < 1) {
|
||||
throw new VintLengthOutOfRangeError(desiredLength!);
|
||||
}
|
||||
|
||||
let length = desiredLength;
|
||||
|
||||
if (!length) {
|
||||
length = 1;
|
||||
while (value >= ELEM_ID_VINT_DATA_UP_LIMIT[length]) {
|
||||
length++;
|
||||
}
|
||||
}
|
||||
|
||||
if (length === 8) {
|
||||
value = BigInt(value);
|
||||
} else {
|
||||
value = Number(value);
|
||||
}
|
||||
|
||||
if (typeof value === 'bigint') {
|
||||
value = BigInt(value);
|
||||
const buf = new Uint8Array(8);
|
||||
new DataView(buf.buffer).setBigUint64(
|
||||
0,
|
||||
value + VINT_WIDTH_MARKER_VALUE_TABLE[8],
|
||||
false
|
||||
);
|
||||
return buf;
|
||||
}
|
||||
|
||||
value += VINT_WIDTH_MARKER_VALUE_TABLE[length] as number;
|
||||
|
||||
const buffer = new Uint8Array(length);
|
||||
for (let i = length - 1; i >= 0; i -= 1) {
|
||||
buffer[i] = value % 256;
|
||||
value = Math.floor(value / 256);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export function writeElementIdVint(value: number | bigint): Uint8Array {
|
||||
if (value >= ELEM_ID_VINT_DATA_UP_LIMIT[8]) {
|
||||
throw new VintLengthOutOfRangeError(9);
|
||||
}
|
||||
if (value <= 0) {
|
||||
throw new ElementIdVintDataAllZerosError(value);
|
||||
}
|
||||
return writeVint(value);
|
||||
}
|
||||
|
||||
export function readUnsigned(view: DataView): number | bigint {
|
||||
const byteLength = view.byteLength;
|
||||
let value: bigint | number;
|
||||
switch (byteLength) {
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
return view.getUint8(0);
|
||||
case 2:
|
||||
return view.getUint16(0, false);
|
||||
case 3:
|
||||
return view.getUint8(0) * 2 ** 16 + view.getUint16(1, false);
|
||||
case 4:
|
||||
return view.getUint32(0, false);
|
||||
case 5:
|
||||
return view.getUint8(0) * 2 ** 32 + view.getUint32(1, false);
|
||||
case 6:
|
||||
return view.getUint16(0, false) * 2 ** 32 + view.getUint32(2, false);
|
||||
case 7:
|
||||
value =
|
||||
(BigInt(view.getUint8(0)) << 48n) +
|
||||
(BigInt(view.getUint16(1, false)) << 32n) +
|
||||
BigInt(view.getUint32(3, false));
|
||||
break;
|
||||
case 8:
|
||||
value = view.getBigUint64(0, false);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportLengthForElementTypeError(
|
||||
EbmlElementType.UnsignedInt,
|
||||
'0~8',
|
||||
byteLength
|
||||
);
|
||||
}
|
||||
if (value <= MAX_SAFE_INTEGER_BIGINT) {
|
||||
return Number(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function writeUnsigned(
|
||||
num: bigint | number,
|
||||
preferredLength?: number
|
||||
): Uint8Array {
|
||||
if (num < 0 || num > MAX_UINT64) {
|
||||
throw new OutOfRangeForElementTypeError(
|
||||
EbmlElementType.UnsignedInt,
|
||||
`0-${MAX_UINT64}`,
|
||||
num
|
||||
);
|
||||
}
|
||||
let length = preferredLength;
|
||||
if (!length) {
|
||||
length = 0;
|
||||
while (num > MAX_UINT_TABLE[length]) {
|
||||
length++;
|
||||
}
|
||||
}
|
||||
if (length === 0) {
|
||||
return new Uint8Array(0);
|
||||
}
|
||||
switch (length) {
|
||||
case 1: {
|
||||
num = Number(num);
|
||||
const buf = new Uint8Array(1);
|
||||
const view = new DataView(buf.buffer);
|
||||
view.setUint8(0, Number(num));
|
||||
return buf;
|
||||
}
|
||||
case 2: {
|
||||
num = Number(num);
|
||||
const buf = new Uint8Array(2);
|
||||
const view = new DataView(buf.buffer);
|
||||
view.setUint16(0, Number(num), false);
|
||||
return buf;
|
||||
}
|
||||
case 3: {
|
||||
num = Number(num);
|
||||
const buf = new Uint8Array(3);
|
||||
const view = new DataView(buf.buffer);
|
||||
|
||||
const d1 = num % 2 ** 16;
|
||||
num -= d1;
|
||||
view.setUint16(1, d1, false);
|
||||
view.setUint8(0, Math.floor(num / 2 ** 16));
|
||||
return buf;
|
||||
}
|
||||
case 4: {
|
||||
num = Number(num);
|
||||
|
||||
const buf = new Uint8Array(4);
|
||||
const view = new DataView(buf.buffer);
|
||||
view.setUint32(0, Number(num), false);
|
||||
return buf;
|
||||
}
|
||||
case 5: {
|
||||
num = Number(num);
|
||||
|
||||
const buf = new Uint8Array(5);
|
||||
const view = new DataView(buf.buffer);
|
||||
|
||||
const d1 = num % 2 ** 32;
|
||||
num -= d1;
|
||||
view.setUint32(1, d1, false);
|
||||
view.setUint8(0, Math.floor(num / 2 ** 32));
|
||||
return buf;
|
||||
}
|
||||
case 6: {
|
||||
num = BigInt(num);
|
||||
const buf = new Uint8Array(6);
|
||||
const view = new DataView(buf.buffer);
|
||||
|
||||
const d1 = num % (1n << 32n);
|
||||
num -= d1;
|
||||
view.setUint32(2, Number(d1), false);
|
||||
view.setUint16(0, Number(num >> 32n), false);
|
||||
return buf;
|
||||
}
|
||||
case 7: {
|
||||
num = BigInt(num);
|
||||
const buf = new Uint8Array(7);
|
||||
const view = new DataView(buf.buffer);
|
||||
|
||||
const d1 = num % (1n << 32n);
|
||||
num -= d1;
|
||||
view.setUint32(3, Number(d1), false);
|
||||
const d2 = num % (1n << 48n);
|
||||
num -= d2;
|
||||
view.setUint16(1, Number(d2), false);
|
||||
view.setUint8(0, Number(num >> 48n));
|
||||
return buf;
|
||||
}
|
||||
case 8: {
|
||||
const buf = new Uint8Array(8);
|
||||
const view = new DataView(buf.buffer);
|
||||
view.setBigUint64(0, BigInt(num), false);
|
||||
return buf;
|
||||
}
|
||||
default:
|
||||
throw new UnsupportLengthForElementTypeError(
|
||||
EbmlElementType.UnsignedInt,
|
||||
'0~8',
|
||||
length
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <explanation>
|
||||
export function readSigned(view: DataView): number | bigint {
|
||||
const byteLength = view.byteLength;
|
||||
if (byteLength < 0 || byteLength > 8) {
|
||||
throw new UnsupportLengthForElementTypeError(
|
||||
EbmlElementType.Integer,
|
||||
'0~8',
|
||||
byteLength
|
||||
);
|
||||
}
|
||||
if (byteLength === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (byteLength === 1) {
|
||||
return view.getInt8(0);
|
||||
}
|
||||
if (byteLength === 2) {
|
||||
return view.getInt16(0, false);
|
||||
}
|
||||
if (byteLength === 4) {
|
||||
return view.getInt32(0, false);
|
||||
}
|
||||
if (byteLength <= 6) {
|
||||
let n = 0;
|
||||
const signBit = view.getUint8(0) & 0x80;
|
||||
let unignedValue = 0;
|
||||
for (let i = 0; i < byteLength; i++) {
|
||||
unignedValue = unignedValue * 256 + view.getUint8(i);
|
||||
}
|
||||
if (signBit) {
|
||||
const bitLength = byteLength * 8;
|
||||
const maxUnsignedValue = 1 * 2 ** bitLength;
|
||||
n = unignedValue - maxUnsignedValue;
|
||||
} else {
|
||||
n = unignedValue;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
let n = 0n;
|
||||
if (byteLength === 8) {
|
||||
n = view.getBigInt64(0, false);
|
||||
} else {
|
||||
const signBit = view.getUint8(0) & 0x80;
|
||||
let unignedValue = 0n;
|
||||
for (let i = 0; i < byteLength; i++) {
|
||||
unignedValue = (unignedValue << 8n) + BigInt(view.getUint8(i));
|
||||
}
|
||||
if (signBit) {
|
||||
n = unignedValue;
|
||||
} else {
|
||||
const bitLength = BigInt(byteLength * 8);
|
||||
const maxUnsignedValue = 1n << bitLength;
|
||||
n = unignedValue - maxUnsignedValue;
|
||||
}
|
||||
}
|
||||
if (n <= MAX_SAFE_INTEGER_BIGINT) {
|
||||
return Number(n);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <explanation>
|
||||
export function writeSigned(
|
||||
num: number | bigint,
|
||||
preferredLength?: number
|
||||
): Uint8Array {
|
||||
if (num < MIN_INT64 || num > MAX_INT64) {
|
||||
throw new OutOfRangeForElementTypeError(
|
||||
EbmlElementType.Integer,
|
||||
`${MIN_INT64}~${MAX_INT64}`,
|
||||
num
|
||||
);
|
||||
}
|
||||
let length = preferredLength;
|
||||
if (!length) {
|
||||
length = 0;
|
||||
if (num > 0) {
|
||||
while (num > MAX_INT_TABLE[length]) {
|
||||
length++;
|
||||
}
|
||||
} else {
|
||||
while (num < MIN_INT_TABLE[length]) {
|
||||
length++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (length === 0) {
|
||||
return new Uint8Array(0);
|
||||
}
|
||||
|
||||
if (length > 8) {
|
||||
throw new UnsupportLengthForElementTypeError(
|
||||
EbmlElementType.Integer,
|
||||
'0~8',
|
||||
length
|
||||
);
|
||||
}
|
||||
|
||||
if (num < 0) {
|
||||
if (length >= 6) {
|
||||
num = BigInt(num) + BigInt(MAX_UINT_TABLE[length]) + 1n;
|
||||
} else {
|
||||
num = Number(num) + Number(MAX_UINT_TABLE[length]) + 1;
|
||||
}
|
||||
}
|
||||
return writeUnsigned(num, length);
|
||||
}
|
||||
|
||||
export function readFloat(view: DataView): number {
|
||||
const byteLength = view.byteLength;
|
||||
switch (byteLength) {
|
||||
case 0:
|
||||
return 0;
|
||||
case 4:
|
||||
return view.getFloat32(0, false);
|
||||
case 8:
|
||||
return view.getFloat64(0, false);
|
||||
default:
|
||||
throw new UnsupportLengthForElementTypeError(
|
||||
EbmlElementType.Float,
|
||||
'0,4,8',
|
||||
byteLength
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function writeFloat(num: number): Uint8Array {
|
||||
if (num === 0) {
|
||||
return new Uint8Array(0);
|
||||
}
|
||||
const buf = new Uint8Array(4);
|
||||
const view = new DataView(buf.buffer);
|
||||
view.setFloat32(0, num, false);
|
||||
return buf;
|
||||
}
|
||||
|
||||
export function readUtf8(view: ArrayBufferView): string {
|
||||
return UTF8_DECODER.decode(view);
|
||||
}
|
||||
|
||||
export function readAscii(view: ArrayBufferView): string {
|
||||
return ASCII_DECODER.decode(view);
|
||||
}
|
||||
|
||||
export function writeUtf8(str: string): Uint8Array {
|
||||
return UTF8_ENCODER.encode(str);
|
||||
}
|
||||
|
||||
export function writeAscii(str: string): Uint8Array {
|
||||
return writeUtf8(str);
|
||||
}
|
||||
|
||||
export function readHexString(view: DataView): string {
|
||||
const n = readUnsigned(view);
|
||||
return n.toString(16);
|
||||
}
|
||||
|
||||
export function hexStringToBuf(hex: string): Uint8Array {
|
||||
// @ts-ignore
|
||||
if (typeof Uint8Array.fromHex === 'function') {
|
||||
// @ts-ignore
|
||||
return Uint8Array.fromHex(hex);
|
||||
}
|
||||
return new Uint8Array(
|
||||
hex.match(/[\da-fA-F]{2}/gi)!.map((h) => Number.parseInt(h, 16))
|
||||
);
|
||||
}
|
||||
|
||||
export function vintToHexString(num: number, litterEnd = false) {
|
||||
const buf = writeVint(num);
|
||||
const bytes = [...buf];
|
||||
if (litterEnd) {
|
||||
bytes.reverse();
|
||||
}
|
||||
return bytes.map((b) => b.toString(16).padStart(2, '0')).join(' ');
|
||||
}
|
||||
|
||||
export function concatBufs(...bufs: Uint8Array[]): Uint8Array {
|
||||
const tmp = new Uint8Array(
|
||||
bufs.reduce((byteLength, buf) => byteLength + buf.byteLength, 0)
|
||||
);
|
||||
let byteOffset = 0;
|
||||
for (const buf of bufs) {
|
||||
tmp.set(buf, byteOffset);
|
||||
byteOffset += buf.byteLength;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
export function concatArrayBuffers(...bufs: ArrayBuffer[]): Uint8Array {
|
||||
return concatBufs(...bufs.map((b) => new Uint8Array(b)));
|
||||
}
|
||||
|
||||
export function dataViewSliceToBuf(
|
||||
view: DataView,
|
||||
start: number | undefined,
|
||||
end: number | undefined
|
||||
): Uint8Array {
|
||||
const viewBufferEnd = view.byteOffset + view.byteLength;
|
||||
const viewBufferStart = view.byteOffset;
|
||||
|
||||
let newBufferStart = viewBufferStart;
|
||||
if (start! >= 0) {
|
||||
newBufferStart = viewBufferStart + start!;
|
||||
} else if (start! < 0) {
|
||||
newBufferStart = viewBufferEnd + start!;
|
||||
}
|
||||
let newBufferEnd = viewBufferEnd;
|
||||
if (end! >= 0) {
|
||||
newBufferEnd = viewBufferStart + end!;
|
||||
} else if (end! < 0) {
|
||||
newBufferEnd = viewBufferEnd + end!;
|
||||
}
|
||||
|
||||
return new Uint8Array(view.buffer.slice(newBufferStart, newBufferEnd));
|
||||
}
|
||||
|
||||
export function dataViewSlice(
|
||||
view: DataView,
|
||||
start: number | undefined,
|
||||
end: number | undefined
|
||||
) {
|
||||
const viewBufferEnd = view.byteOffset + view.byteLength;
|
||||
const viewBufferStart = view.byteOffset;
|
||||
|
||||
let newBufferStart = viewBufferStart;
|
||||
if (start! >= 0) {
|
||||
newBufferStart = viewBufferStart + start!;
|
||||
} else if (start! < 0) {
|
||||
newBufferStart = viewBufferEnd + start!;
|
||||
}
|
||||
let newBufferEnd = viewBufferEnd;
|
||||
if (end! >= 0) {
|
||||
newBufferEnd = viewBufferStart + end!;
|
||||
} else if (end! < 0) {
|
||||
newBufferEnd = viewBufferEnd + end!;
|
||||
}
|
||||
if (newBufferStart === viewBufferStart && newBufferEnd === viewBufferEnd) {
|
||||
return view;
|
||||
}
|
||||
const newBufferLength = Math.max(newBufferEnd - newBufferStart, 0);
|
||||
return new DataView(view.buffer, newBufferStart, newBufferLength);
|
||||
}
|
135
tests/decoder.spec.ts
Normal file
135
tests/decoder.spec.ts
Normal file
@ -0,0 +1,135 @@
|
||||
import { assert, describe, it } from 'vitest';
|
||||
import {
|
||||
EbmlTagPosition,
|
||||
EbmlElementType,
|
||||
EbmlStreamDecoder as Decoder,
|
||||
EbmlDataTag,
|
||||
type EbmlTagTrait,
|
||||
} from 'konoebml';
|
||||
|
||||
const bufFrom = (data: Uint8Array | readonly number[]): ArrayBuffer =>
|
||||
new Uint8Array(data).buffer;
|
||||
|
||||
const getDecoderWithNullSink = () => {
|
||||
const decoder = new Decoder();
|
||||
decoder.readable.pipeTo(new WritableStream({}));
|
||||
return decoder;
|
||||
};
|
||||
|
||||
async function collectTags(decoder: Decoder): Promise<EbmlTagTrait[]> {
|
||||
const tags: EbmlTagTrait[] = [];
|
||||
await decoder.readable.pipeTo(
|
||||
new WritableStream({
|
||||
write: (tag) => {
|
||||
tags.push(tag);
|
||||
},
|
||||
})
|
||||
);
|
||||
return tags;
|
||||
}
|
||||
|
||||
describe('EbmlStreamDecoder', () => {
|
||||
it('should wait for more data if a tag is longer than the buffer', async () => {
|
||||
const decoder = getDecoderWithNullSink();
|
||||
const writer = decoder.writable.getWriter();
|
||||
await writer.write(bufFrom([0x1a, 0x45]));
|
||||
|
||||
assert.strictEqual(decoder.transformer.getBuffer().byteLength, 2);
|
||||
});
|
||||
|
||||
it('should clear the buffer after a full tag is written in one chunk', async () => {
|
||||
const decoder = getDecoderWithNullSink();
|
||||
const writer = decoder.writable.getWriter();
|
||||
await writer.write(bufFrom([0x42, 0x86, 0x81, 0x01]));
|
||||
|
||||
assert.strictEqual(decoder.transformer.getBuffer().byteLength, 0);
|
||||
});
|
||||
|
||||
it('should clear the buffer after a full tag is written in multiple chunks', async () => {
|
||||
const decoder = getDecoderWithNullSink();
|
||||
const writer = decoder.writable.getWriter();
|
||||
|
||||
await writer.write(bufFrom([0x42, 0x86]));
|
||||
await writer.write(bufFrom([0x81, 0x01]));
|
||||
|
||||
assert.strictEqual(decoder.transformer.getBuffer().byteLength, 0);
|
||||
});
|
||||
|
||||
it('should increment the cursor on each step', async () => {
|
||||
const decoder = getDecoderWithNullSink();
|
||||
const writer = decoder.writable.getWriter();
|
||||
|
||||
await writer.write(bufFrom([0x42])); // 4
|
||||
|
||||
assert.strictEqual(decoder.transformer.getBuffer().byteLength, 1);
|
||||
|
||||
await writer.write(bufFrom([0x86])); // 5
|
||||
|
||||
assert.strictEqual(decoder.transformer.getBuffer().byteLength, 2);
|
||||
|
||||
await writer.write(bufFrom([0x81])); // 6 & 7
|
||||
|
||||
assert.strictEqual(decoder.transformer.getBuffer().byteLength, 0);
|
||||
|
||||
await writer.write(bufFrom([0x01])); // 6 & 7
|
||||
|
||||
assert.strictEqual(decoder.transformer.getBuffer().byteLength, 0);
|
||||
});
|
||||
|
||||
it('should emit correct tag events for simple data', async () => {
|
||||
const decoder = new Decoder();
|
||||
const writer = decoder.writable.getWriter();
|
||||
|
||||
const tags = collectTags(decoder);
|
||||
|
||||
await writer.write(bufFrom([0x42, 0x86, 0x81, 0x01]));
|
||||
await writer.close();
|
||||
|
||||
const [tag] = await tags;
|
||||
|
||||
assert.strictEqual(tag.position, EbmlTagPosition.Content);
|
||||
assert.strictEqual(tag.id.toString(16), '4286');
|
||||
assert.strictEqual(tag.contentLength, 0x01);
|
||||
assert.strictEqual(tag.type, EbmlElementType.UnsignedInt);
|
||||
assert.ok(tag instanceof EbmlDataTag);
|
||||
assert.deepStrictEqual(tag.data, 1);
|
||||
});
|
||||
|
||||
it('should emit correct EBML tag events for master tags', async () => {
|
||||
const decoder = new Decoder();
|
||||
const writer = decoder.writable.getWriter();
|
||||
|
||||
writer.write(bufFrom([0x1a, 0x45, 0xdf, 0xa3, 0x80]));
|
||||
writer.close();
|
||||
|
||||
const [tag] = await collectTags(decoder);
|
||||
|
||||
assert.strictEqual(tag.position, EbmlTagPosition.Start);
|
||||
assert.strictEqual(tag.id.toString(16), '1a45dfa3');
|
||||
assert.strictEqual(tag.contentLength, 0);
|
||||
assert.strictEqual(tag.type, EbmlElementType.Master);
|
||||
assert.ok(!(tag instanceof EbmlDataTag));
|
||||
assert.ok(!('data' in tag));
|
||||
});
|
||||
|
||||
it('should emit correct EBML:end events for master tags', async () => {
|
||||
const decoder = new Decoder();
|
||||
const writer = decoder.writable.getWriter();
|
||||
|
||||
writer.write(bufFrom([0x1a, 0x45, 0xdf, 0xa3]));
|
||||
writer.write(bufFrom([0x84, 0x42, 0x86, 0x81, 0x00]));
|
||||
writer.close();
|
||||
|
||||
const tags = await collectTags(decoder);
|
||||
|
||||
assert.strictEqual(tags.length, 3);
|
||||
|
||||
const firstEndTag = tags.find((t) => t.position === EbmlTagPosition.End)!;
|
||||
|
||||
assert.strictEqual(firstEndTag.id.toString(16), '1a45dfa3');
|
||||
assert.strictEqual(firstEndTag.contentLength, 4);
|
||||
assert.strictEqual(firstEndTag.type, EbmlElementType.Master);
|
||||
assert.ok(!(firstEndTag instanceof EbmlDataTag));
|
||||
assert.ok(!('data' in firstEndTag));
|
||||
});
|
||||
});
|
121
tests/encoder.spec.ts
Normal file
121
tests/encoder.spec.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import { assert, expect, describe, it } from 'vitest';
|
||||
import {
|
||||
EbmlTagPosition,
|
||||
type EbmlTagTrait,
|
||||
EbmlTagIdEnum,
|
||||
createEbmlTagForManuallyBuild,
|
||||
EbmlStreamEncoder,
|
||||
} from 'konoebml';
|
||||
|
||||
const invalidTag: EbmlTagTrait = <EbmlTagTrait>(<any>{
|
||||
id: undefined,
|
||||
type: <any>'404NotFound',
|
||||
position: undefined,
|
||||
size: -1,
|
||||
data: null,
|
||||
});
|
||||
|
||||
const incompleteTag: EbmlTagTrait = undefined!;
|
||||
|
||||
const ebmlStartTag = createEbmlTagForManuallyBuild(EbmlTagIdEnum.EBML, {
|
||||
position: EbmlTagPosition.Start,
|
||||
});
|
||||
|
||||
const ebmlEndTag: EbmlTagTrait = createEbmlTagForManuallyBuild(
|
||||
EbmlTagIdEnum.EBML,
|
||||
{
|
||||
contentLength: 10,
|
||||
position: EbmlTagPosition.End,
|
||||
}
|
||||
);
|
||||
|
||||
const ebmlVersion1Tag = Object.assign(
|
||||
createEbmlTagForManuallyBuild(EbmlTagIdEnum.EBMLVersion, {
|
||||
position: EbmlTagPosition.Content,
|
||||
}),
|
||||
{
|
||||
data: 1,
|
||||
}
|
||||
);
|
||||
|
||||
const ebmlVersion0Tag: EbmlTagTrait = Object.assign(
|
||||
createEbmlTagForManuallyBuild(EbmlTagIdEnum.EBMLVersion, {
|
||||
position: EbmlTagPosition.Content,
|
||||
}),
|
||||
{
|
||||
data: 0,
|
||||
}
|
||||
);
|
||||
|
||||
const makeEncoderTest = async (tags: EbmlTagTrait[]) => {
|
||||
const source = new ReadableStream({
|
||||
pull(controller) {
|
||||
for (const tag of tags) {
|
||||
controller.enqueue(tag);
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
const encoder = new EbmlStreamEncoder();
|
||||
const chunks: ArrayBuffer[] = [];
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
source
|
||||
.pipeThrough(encoder)
|
||||
.pipeTo(
|
||||
new WritableStream({
|
||||
write(chunk) {
|
||||
chunks.push(chunk);
|
||||
},
|
||||
close() {
|
||||
resolve();
|
||||
},
|
||||
})
|
||||
)
|
||||
.catch(reject);
|
||||
});
|
||||
return {
|
||||
encoder,
|
||||
chunks,
|
||||
};
|
||||
};
|
||||
|
||||
describe('EBML Encoder', () => {
|
||||
it('should write a single tag', async () => {
|
||||
const { chunks } = await makeEncoderTest([ebmlVersion1Tag]);
|
||||
|
||||
assert.deepEqual(chunks, [
|
||||
new Uint8Array([0x42, 0x86]),
|
||||
new Uint8Array([0x81]),
|
||||
new Uint8Array([0x01]),
|
||||
]);
|
||||
});
|
||||
it('should write a tag with a single child', async () => {
|
||||
const { chunks } = await makeEncoderTest([
|
||||
ebmlStartTag,
|
||||
ebmlVersion0Tag,
|
||||
ebmlEndTag,
|
||||
]);
|
||||
|
||||
assert.deepEqual(chunks, [
|
||||
new Uint8Array([0x1a, 0x45, 0xdf, 0xa3]),
|
||||
new Uint8Array([0x83]),
|
||||
new Uint8Array([0x42, 0x86]),
|
||||
new Uint8Array([0x80]),
|
||||
new Uint8Array([]),
|
||||
]);
|
||||
});
|
||||
|
||||
describe('#writeTag', () => {
|
||||
it('throws with an incomplete tag data', async () => {
|
||||
await expect(() => makeEncoderTest([incompleteTag])).rejects.toThrow(
|
||||
/should only accept embl tag but not/
|
||||
);
|
||||
});
|
||||
it('throws with an invalid tag id', async () => {
|
||||
await expect(() => makeEncoderTest([invalidTag])).rejects.toThrow(
|
||||
/should only accept embl tag but not/
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
0
tests/init-test.ts
Normal file
0
tests/init-test.ts
Normal file
99
tests/pipeline.spec.ts
Normal file
99
tests/pipeline.spec.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { assert, describe, it, expect } from 'vitest';
|
||||
import {
|
||||
EbmlStreamDecoder,
|
||||
EbmlStreamEncoder,
|
||||
type EbmlTagTrait,
|
||||
EbmlTagIdEnum,
|
||||
type EbmlBlockTag,
|
||||
createEbmlTagForManuallyBuild,
|
||||
} from 'konoebml';
|
||||
import { concatArrayBuffers } from 'konoebml/tools';
|
||||
|
||||
describe('EBML Pipeline', () => {
|
||||
async function assertPipelineOutputEquals(
|
||||
input: number[],
|
||||
expected: number[]
|
||||
) {
|
||||
const buffer = new Uint8Array(input);
|
||||
|
||||
const source = new ReadableStream<ArrayBuffer>({
|
||||
pull(controller) {
|
||||
controller.enqueue(buffer.buffer);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
||||
const chunks: ArrayBuffer[] = [];
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const sink = new WritableStream<ArrayBuffer>({
|
||||
write(chunk) {
|
||||
chunks.push(chunk);
|
||||
},
|
||||
close() {
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
|
||||
source
|
||||
.pipeThrough(new EbmlStreamDecoder())
|
||||
.pipeThrough(new EbmlStreamEncoder())
|
||||
.pipeTo(sink)
|
||||
.catch(reject);
|
||||
});
|
||||
|
||||
expect(concatArrayBuffers(...chunks)).toEqual(new Uint8Array(expected));
|
||||
}
|
||||
|
||||
it('should not immediately output with not unknown sized and not paired master tag', async () => {
|
||||
await assertPipelineOutputEquals(
|
||||
[0x1a, 0x45, 0xdf, 0xa3, 0x83, 0x42, 0x86, 0x81],
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
it('should immediately output with unknown sized master tag', async () => {
|
||||
await assertPipelineOutputEquals(
|
||||
[0x1a, 0x45, 0xdf, 0xa3, 0xff, 0x42, 0x86, 0x81],
|
||||
[0x1a, 0x45, 0xdf, 0xa3, 0xff]
|
||||
);
|
||||
});
|
||||
|
||||
it('should encode and decode Blocks correctly', async () => {
|
||||
const block = createEbmlTagForManuallyBuild(EbmlTagIdEnum.Block, {});
|
||||
block.track = 5;
|
||||
block.invisible = true;
|
||||
const payload = new Uint8Array(50);
|
||||
for (let i = 0; i < payload.byteLength; i++) {
|
||||
payload[i] = Math.floor(Math.random() * 255);
|
||||
}
|
||||
block.payload = payload;
|
||||
const encoder = new EbmlStreamEncoder();
|
||||
const decoder = new EbmlStreamDecoder();
|
||||
const tags: EbmlTagTrait[] = [];
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const source = new ReadableStream<EbmlTagTrait>({
|
||||
pull(controller) {
|
||||
controller.enqueue(block);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
source
|
||||
.pipeThrough(encoder)
|
||||
.pipeThrough(decoder)
|
||||
.pipeTo(
|
||||
new WritableStream<EbmlTagTrait>({
|
||||
write(tag) {
|
||||
tags.push(tag);
|
||||
},
|
||||
close: () => resolve(),
|
||||
})
|
||||
)
|
||||
.catch(reject);
|
||||
});
|
||||
const tag = tags[0] as EbmlBlockTag;
|
||||
assert.strictEqual(tag.id, EbmlTagIdEnum.Block);
|
||||
assert.strictEqual(tag.track, block.track);
|
||||
assert.strictEqual(tag.invisible, block.invisible);
|
||||
assert.deepEqual(tag.payload, block.payload);
|
||||
});
|
||||
});
|
340
tests/tools.spec.ts
Normal file
340
tests/tools.spec.ts
Normal file
@ -0,0 +1,340 @@
|
||||
import { assert, describe, it } from 'vitest';
|
||||
import {
|
||||
readAscii,
|
||||
readElementIdVint,
|
||||
readFloat,
|
||||
readSigned,
|
||||
readUnsigned,
|
||||
readUtf8,
|
||||
readVint,
|
||||
writeVint,
|
||||
} from 'konoebml/tools';
|
||||
|
||||
function bufFrom(data: Uint8Array | readonly number[]): Uint8Array {
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
|
||||
function viewFrom(
|
||||
data: Uint8Array | readonly number[],
|
||||
start?: number,
|
||||
length?: number
|
||||
): DataView {
|
||||
const buf = bufFrom(data);
|
||||
return new DataView(buf.buffer, start, length);
|
||||
}
|
||||
|
||||
describe('EBML Tools', () => {
|
||||
describe('#readVint()', () => {
|
||||
function assertReadVint(
|
||||
data: Uint8Array | readonly number[],
|
||||
expect: number | bigint | [number | bigint, number | undefined],
|
||||
start?: number,
|
||||
length?: number
|
||||
) {
|
||||
const view = viewFrom(data, start, length);
|
||||
const vint = readVint(view)!;
|
||||
|
||||
const expectValue = Array.isArray(expect) ? expect[0] : expect;
|
||||
const expectLength =
|
||||
(Array.isArray(expect) ? expect[1] : undefined) ??
|
||||
view.byteLength - view.byteOffset;
|
||||
assert.strictEqual(vint.value, expectValue);
|
||||
assert.strictEqual(vint.length, expectLength);
|
||||
}
|
||||
|
||||
function assertReadElementIdVint(
|
||||
data: Uint8Array | readonly number[],
|
||||
expect: number | bigint | [number | bigint, number | undefined],
|
||||
start?: number,
|
||||
length?: number
|
||||
) {
|
||||
const view = viewFrom(data, start, length);
|
||||
const vint = readElementIdVint(view)!;
|
||||
|
||||
const expectValue = Array.isArray(expect) ? expect[0] : expect;
|
||||
const expectLength =
|
||||
(Array.isArray(expect) ? expect[1] : undefined) ??
|
||||
view.byteLength - view.byteOffset;
|
||||
assert.strictEqual(vint.value, expectValue);
|
||||
assert.strictEqual(vint.length, expectLength);
|
||||
}
|
||||
|
||||
it('should read the correct value for all 1 byte ints', () => {
|
||||
assertReadVint([0x80], 0);
|
||||
assert.throws(() => {
|
||||
readElementIdVint(viewFrom([0x80]));
|
||||
}, /Element ID VINT_DATA can not be all zeros/);
|
||||
|
||||
for (let i = 1; i < 0x80 - 1; i += 1) {
|
||||
assertReadElementIdVint([i | 0x80], i);
|
||||
assertReadVint([i | 0x80], i);
|
||||
}
|
||||
|
||||
assertReadVint([0xff], 127);
|
||||
assert.throws(() => {
|
||||
readElementIdVint(viewFrom([0xff]));
|
||||
}, /Element ID VINT_DATA can not be all ones/);
|
||||
});
|
||||
|
||||
it('should read the correct value for 1 byte int with non-zero start', () => {
|
||||
assertReadVint([0x00, 0x81], [1, 1], 1);
|
||||
});
|
||||
|
||||
it('should read the correct value for all 2 byte ints', () => {
|
||||
for (let i = 0; i < 0x40; i += 1) {
|
||||
for (let j = 0; j < 0xff; j += 1) {
|
||||
assertReadVint([i | 0x40, j], (i << 8) + j);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should read the correct value for all 3 byte ints', () => {
|
||||
for (let i = 0; i < 0x20; i += 1) {
|
||||
for (let j = 0; j < 0xff; j += 2) {
|
||||
for (let k = 0; k < 0xff; k += 3) {
|
||||
assertReadVint([i | 0x20, j, k], (i << 16) + (j << 8) + k);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// not brute forcing any more bytes, takes sooo long
|
||||
|
||||
it('should read the correct value for 4 byte int min/max values', () => {
|
||||
assertReadVint([0x10, 0x20, 0x00, 0x00], 2 ** 21);
|
||||
assertReadVint([0x1f, 0xff, 0xff, 0xfe], 2 ** 28 - 2);
|
||||
});
|
||||
|
||||
it('should read the correct value for 5 byte int min/max values', () => {
|
||||
assertReadVint([0x08, 0x10, 0x00, 0x00, 0x00], 2 ** 28);
|
||||
assertReadVint([0x0f, 0xff, 0xff, 0xff, 0xfe], 2 ** 35 - 2);
|
||||
});
|
||||
|
||||
it('should read the correct value for 6 byte int min/max values', () => {
|
||||
assertReadVint([0x04, 0x08, 0x00, 0x00, 0x00, 0x00], 2 ** 35);
|
||||
assertReadVint([0x07, 0xff, 0xff, 0xff, 0xff, 0xfe], 2 ** 42 - 2);
|
||||
});
|
||||
|
||||
it('should read the correct value for 7 byte int min/max values', () => {
|
||||
assertReadVint([0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00], 2 ** 42);
|
||||
assertReadVint([0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe], 2 ** 49 - 2);
|
||||
});
|
||||
|
||||
it('should read the correct value for 8 byte int min value', () => {
|
||||
assertReadVint([0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], 2 ** 49);
|
||||
});
|
||||
|
||||
it('should read the correct value for the max representable JS number (2^53 - 1)', () => {
|
||||
assertReadVint(
|
||||
[0x01, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
|
||||
Number.MAX_SAFE_INTEGER
|
||||
);
|
||||
});
|
||||
|
||||
it('should return bigint for more than max representable JS number (2^53)', () => {
|
||||
assertReadVint(
|
||||
[0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||||
BigInt(Number.MAX_SAFE_INTEGER) + 1n
|
||||
);
|
||||
});
|
||||
|
||||
it('should return bigint for more than max representable JS number (8 byte int max value)', () => {
|
||||
assertReadVint(
|
||||
[0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
|
||||
2n ** 56n - 1n
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw for 9+ byte int values', () => {
|
||||
assert.throws(() => {
|
||||
readVint(
|
||||
viewFrom([0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff])
|
||||
);
|
||||
}, /Vint length out of range/);
|
||||
});
|
||||
|
||||
it('should throw for not shortest element id', () => {
|
||||
assert.throws(() => {
|
||||
readElementIdVint(viewFrom([0x40, 0x3f]));
|
||||
}, /Element ID VINT_DATA should be shortest/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#writeVint()', () => {
|
||||
function assertWriteVint(
|
||||
value: number | bigint,
|
||||
expected: Uint8Array | readonly number[]
|
||||
): void {
|
||||
const actual = writeVint(value);
|
||||
assert.strictEqual(
|
||||
Buffer.from(expected).toString('hex'),
|
||||
Buffer.from(actual).toString('hex')
|
||||
);
|
||||
}
|
||||
|
||||
it('should throw when writing -1', () => {
|
||||
assert.throws(() => {
|
||||
writeVint(-1);
|
||||
}, /VINT_DATA out of range/);
|
||||
});
|
||||
|
||||
it('should write all 1 byte ints', () => {
|
||||
for (let i = 0; i < 0x80 - 1; i += 1) {
|
||||
assertWriteVint(i, [i | 0x80]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should write 2 byte int min/max values', () => {
|
||||
assertWriteVint(2 ** 7 - 1, [0x40, 0x7f]);
|
||||
assertWriteVint(2 ** 14 - 2, [0x7f, 0xfe]);
|
||||
});
|
||||
|
||||
it('should write 3 byte int min/max values', () => {
|
||||
assertWriteVint(2 ** 14 - 1, [0x20, 0x3f, 0xff]);
|
||||
assertWriteVint(2 ** 21 - 2, [0x3f, 0xff, 0xfe]);
|
||||
});
|
||||
|
||||
it('should write 4 byte int min/max values', () => {
|
||||
assertWriteVint(2 ** 21 - 1, [0x10, 0x1f, 0xff, 0xff]);
|
||||
assertWriteVint(2 ** 28 - 2, [0x1f, 0xff, 0xff, 0xfe]);
|
||||
});
|
||||
|
||||
it('should write 5 byte int min/max value', () => {
|
||||
assertWriteVint(2 ** 28 - 1, [0x08, 0x0f, 0xff, 0xff, 0xff]);
|
||||
assertWriteVint(2 ** 35 - 2, [0x0f, 0xff, 0xff, 0xff, 0xfe]);
|
||||
});
|
||||
|
||||
it('should write 6 byte int min/max value', () => {
|
||||
assertWriteVint(2 ** 35 - 1, [0x04, 0x07, 0xff, 0xff, 0xff, 0xff]);
|
||||
assertWriteVint(2 ** 42 - 2, [0x07, 0xff, 0xff, 0xff, 0xff, 0xfe]);
|
||||
});
|
||||
|
||||
it('should write 7 byte int min/max value', () => {
|
||||
assertWriteVint(2 ** 42 - 1, [0x02, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff]);
|
||||
assertWriteVint(2 ** 49 - 2, [0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe]);
|
||||
});
|
||||
|
||||
it('should write 8 byte int min/max value', () => {
|
||||
assertWriteVint(
|
||||
2 ** 49 - 1,
|
||||
[0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
|
||||
);
|
||||
assertWriteVint(
|
||||
2n ** 56n - 2n,
|
||||
[0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe]
|
||||
);
|
||||
});
|
||||
|
||||
it('should write the correct value for the max representable JS number (2^53 - 1)', () => {
|
||||
assertWriteVint(
|
||||
Number.MAX_SAFE_INTEGER,
|
||||
[0x01, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw for more than max representable JS number (8 byte int max value)', () => {
|
||||
assert.throws(() => {
|
||||
writeVint(2n ** 56n + 1n);
|
||||
}, /VINT_DATA out of range/);
|
||||
});
|
||||
|
||||
it('should throw for 9+ byte int values', () => {
|
||||
assert.throws(() => {
|
||||
writeVint(2n ** 56n + 1n);
|
||||
}, /VINT_DATA out of range/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#readUnsigned', () => {
|
||||
it('handles 8-bit ints', () => {
|
||||
assert.strictEqual(readUnsigned(viewFrom([0x07])), 7);
|
||||
});
|
||||
it('handles 16-bit ints', () => {
|
||||
assert.strictEqual(readUnsigned(viewFrom([0x07, 0x07])), 1799);
|
||||
});
|
||||
it('handles 32-bit ints', () => {
|
||||
assert.strictEqual(
|
||||
readUnsigned(viewFrom([0x07, 0x07, 0x07, 0x07])),
|
||||
117901063
|
||||
);
|
||||
});
|
||||
it('handles ints smaller than 49 bits as numbers', () => {
|
||||
assert.strictEqual(
|
||||
readUnsigned(viewFrom([0x07, 0x07, 0x07, 0x07, 0x07])),
|
||||
30182672135
|
||||
);
|
||||
assert.strictEqual(
|
||||
readUnsigned(viewFrom([0x07, 0x07, 0x07, 0x07, 0x07, 0x07])),
|
||||
7726764066567
|
||||
);
|
||||
});
|
||||
it('returns ints larger than the max safe number size as bigint', () => {
|
||||
assert.strictEqual(
|
||||
readUnsigned(viewFrom([0x1, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07])),
|
||||
74035645638969095n
|
||||
);
|
||||
assert.strictEqual(
|
||||
typeof readUnsigned(
|
||||
viewFrom([0x1, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07])
|
||||
),
|
||||
'bigint'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#readSigned', () => {
|
||||
it('handles 8-bit ints', () => {
|
||||
assert.strictEqual(readSigned(viewFrom([0x07])), 7);
|
||||
});
|
||||
it('handles 16-bit ints', () => {
|
||||
assert.strictEqual(readSigned(viewFrom([0x07, 0x07])), 1799);
|
||||
});
|
||||
it('handles 32-bit ints', () => {
|
||||
assert.strictEqual(
|
||||
readSigned(viewFrom([0x07, 0x07, 0x07, 0x07])),
|
||||
117901063
|
||||
);
|
||||
});
|
||||
it('handles 32 ~ 64bit ints', () => {
|
||||
assert.strictEqual(readSigned(viewFrom([0x40, 0x20, 0x00])), 4202496);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#readFloat', () => {
|
||||
it('can read 32-bit floats', () => {
|
||||
assert.strictEqual(readFloat(viewFrom([0x40, 0x20, 0x00, 0x00])), 2.5);
|
||||
});
|
||||
it('can read 64-bit floats', () => {
|
||||
assert.strictEqual(
|
||||
readFloat(viewFrom([0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])),
|
||||
2.5
|
||||
);
|
||||
});
|
||||
it('should throw for invalid sized float arrays', () => {
|
||||
assert.throws(() => {
|
||||
readFloat(viewFrom([0x40, 0x20, 0x00]));
|
||||
}, /length should be/);
|
||||
});
|
||||
});
|
||||
describe('#readUtf8', () => {
|
||||
it('can read valid utf-8 strings', () => {
|
||||
assert.strictEqual(readUtf8(viewFrom([97, 98, 99])), 'abc');
|
||||
assert.strictEqual(
|
||||
readUtf8(viewFrom([240, 159, 164, 163, 240, 159, 152, 133])),
|
||||
'🤣😅'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#readAscii', () => {
|
||||
it('can read valid ascii strings', () => {
|
||||
assert.strictEqual(readAscii(viewFrom([97, 98, 99])), 'abc');
|
||||
});
|
||||
|
||||
it('can not read valid ascii strings', () => {
|
||||
assert.notStrictEqual(
|
||||
readAscii(viewFrom([240, 159, 164, 163, 240, 159, 152, 133])),
|
||||
'🤣😅'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
197
tests/value.spec.ts
Normal file
197
tests/value.spec.ts
Normal file
@ -0,0 +1,197 @@
|
||||
import fs from 'node:fs';
|
||||
import { assert, describe, it } from 'vitest';
|
||||
import {
|
||||
EbmlStreamDecoder,
|
||||
EbmlTagIdEnum,
|
||||
EbmlSimpleBlockTag as SimpleBlock,
|
||||
EbmlDataTag,
|
||||
type EbmlMasterTag,
|
||||
} from 'konoebml';
|
||||
import { Readable } from 'node:stream';
|
||||
import { WritableStream } from 'node:stream/web';
|
||||
|
||||
process.setMaxListeners(Number.POSITIVE_INFINITY);
|
||||
|
||||
const createReadStream = (file: string) =>
|
||||
Readable.toWeb(fs.createReadStream(file), {
|
||||
strategy: { highWaterMark: 100, size: (chunk) => chunk.byteLength },
|
||||
}) as ReadableStream<ArrayBuffer>;
|
||||
|
||||
const makeDataStreamTest =
|
||||
(stream: () => ReadableStream<ArrayBuffer>) =>
|
||||
async (cb: (tag: EbmlMasterTag | EbmlDataTag, done: () => void) => void) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
stream()
|
||||
.pipeThrough(new EbmlStreamDecoder())
|
||||
.pipeTo(
|
||||
new WritableStream({
|
||||
write: async (tag) => {
|
||||
cb(tag as EbmlMasterTag | EbmlDataTag, () => resolve(true));
|
||||
},
|
||||
close: () => {
|
||||
reject('hit end of file without calling done');
|
||||
},
|
||||
})
|
||||
)
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
describe('EBML Values in tags', () => {
|
||||
describe('AVC1', () => {
|
||||
const makeAVC1StreamTest = makeDataStreamTest(() =>
|
||||
createReadStream('media/video-webm-codecs-avc1-42E01E.webm')
|
||||
);
|
||||
|
||||
it('should get a correct PixelWidth value from a file (2-byte unsigned int)', async () =>
|
||||
await makeAVC1StreamTest((tag, done) => {
|
||||
if (tag instanceof EbmlDataTag && tag.id === EbmlTagIdEnum.PixelWidth) {
|
||||
assert.strictEqual(tag.data, 352);
|
||||
done();
|
||||
}
|
||||
}));
|
||||
|
||||
it('should get a correct EBMLVersion value from a file (one-byte unsigned int)', async () =>
|
||||
await makeAVC1StreamTest((tag, done) => {
|
||||
if (
|
||||
tag instanceof EbmlDataTag &&
|
||||
tag.id === EbmlTagIdEnum.EBMLVersion
|
||||
) {
|
||||
assert.strictEqual(tag.data, 1);
|
||||
done();
|
||||
}
|
||||
}));
|
||||
|
||||
it('should get a correct TimeCodeScale value from a file (3-byte unsigned int)', () =>
|
||||
makeAVC1StreamTest((tag, done) => {
|
||||
if (
|
||||
tag instanceof EbmlDataTag &&
|
||||
tag.id === EbmlTagIdEnum.TimecodeScale
|
||||
) {
|
||||
assert.strictEqual(tag.data, 1000000);
|
||||
done();
|
||||
}
|
||||
}));
|
||||
|
||||
it('should get a correct TrackUID value from a file (56-bit integer in hex)', () =>
|
||||
makeAVC1StreamTest((tag, done) => {
|
||||
if (tag instanceof EbmlDataTag && tag.id === EbmlTagIdEnum.TrackUID) {
|
||||
assert.strictEqual(tag.data, 7990710658693702);
|
||||
done();
|
||||
}
|
||||
}));
|
||||
|
||||
it('should get a correct DocType value from a file (ASCII text)', () =>
|
||||
makeAVC1StreamTest((tag, done) => {
|
||||
if (tag instanceof EbmlDataTag && tag.id === EbmlTagIdEnum.DocType) {
|
||||
assert.strictEqual(tag.data, 'matroska');
|
||||
done();
|
||||
}
|
||||
}));
|
||||
|
||||
it('should get a correct MuxingApp value from a file (utf8 text)', () =>
|
||||
makeAVC1StreamTest((tag, done) => {
|
||||
if (tag instanceof EbmlDataTag && tag.id === EbmlTagIdEnum.MuxingApp) {
|
||||
assert.strictEqual(tag.data, 'Chrome', JSON.stringify(tag));
|
||||
done();
|
||||
}
|
||||
}));
|
||||
|
||||
it('should get a correct SimpleBlock time payload from a file (binary)', () =>
|
||||
makeAVC1StreamTest((tag, done) => {
|
||||
if (!(tag instanceof SimpleBlock)) {
|
||||
return;
|
||||
}
|
||||
if (tag.value <= 0 || tag.value >= 200) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* look at second simpleBlock */
|
||||
assert.strictEqual(tag.track, 1, 'track');
|
||||
assert.strictEqual(tag.value, 191, 'value (timestamp)');
|
||||
assert.strictEqual(
|
||||
tag.payload.byteLength,
|
||||
169,
|
||||
JSON.stringify(tag.payload)
|
||||
);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('VP8', () => {
|
||||
const makeVP8StreamTest = makeDataStreamTest(() =>
|
||||
createReadStream('media/video-webm-codecs-vp8.webm')
|
||||
);
|
||||
|
||||
it('should get a correct PixelWidth value from a video/webm; codecs="vp8" file (2-byte unsigned int)', () =>
|
||||
makeVP8StreamTest((tag, done) => {
|
||||
if (tag instanceof EbmlDataTag && tag.id === EbmlTagIdEnum.PixelWidth) {
|
||||
assert.strictEqual(tag.data, 352);
|
||||
done();
|
||||
}
|
||||
}));
|
||||
|
||||
it('should get a correct EBMLVersion value from a video/webm; codecs="vp8" file (one-byte unsigned int)', () =>
|
||||
makeVP8StreamTest((tag, done) => {
|
||||
if (
|
||||
tag instanceof EbmlDataTag &&
|
||||
tag.id === EbmlTagIdEnum.EBMLVersion
|
||||
) {
|
||||
assert.strictEqual(tag.data, 1);
|
||||
done();
|
||||
}
|
||||
}));
|
||||
|
||||
it('should get a correct TimeCodeScale value from a video/webm; codecs="vp8" file (3-byte unsigned int)', () =>
|
||||
makeVP8StreamTest((tag, done) => {
|
||||
if (
|
||||
tag instanceof EbmlDataTag &&
|
||||
tag.id === EbmlTagIdEnum.TimecodeScale
|
||||
) {
|
||||
assert.strictEqual(tag.data, 1000000);
|
||||
done();
|
||||
}
|
||||
}));
|
||||
|
||||
it('should get a correct TrackUID value from a video/webm; codecs="vp8" file (56-bit integer in hex)', () =>
|
||||
makeVP8StreamTest((tag, done) => {
|
||||
if (tag instanceof EbmlDataTag && tag.id === EbmlTagIdEnum.TrackUID) {
|
||||
assert.strictEqual(tag.data, 13630657102564614n);
|
||||
done();
|
||||
}
|
||||
}));
|
||||
|
||||
it('should get a correct DocType value from a video/webm; codecs="vp8" file (ASCII text)', () =>
|
||||
makeVP8StreamTest((tag, done) => {
|
||||
if (tag instanceof EbmlDataTag && tag.id === EbmlTagIdEnum.DocType) {
|
||||
assert.strictEqual(tag.data, 'webm');
|
||||
done();
|
||||
}
|
||||
}));
|
||||
|
||||
it('should get a correct MuxingApp value from a video/webm; codecs="vp8" file (utf8 text)', () =>
|
||||
makeVP8StreamTest((tag, done) => {
|
||||
if (tag instanceof EbmlDataTag && tag.id === EbmlTagIdEnum.MuxingApp) {
|
||||
assert.strictEqual(tag.data, 'Chrome');
|
||||
done();
|
||||
}
|
||||
}));
|
||||
|
||||
it('should get a correct SimpleBlock time payload from a file (binary)', () =>
|
||||
makeVP8StreamTest((tag, done) => {
|
||||
if (!(tag instanceof SimpleBlock)) {
|
||||
return;
|
||||
}
|
||||
if (tag.value <= 0 || tag.value >= 100) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert.strictEqual(tag.track, 1, 'track');
|
||||
assert.strictEqual(tag.value, 96, JSON.stringify(tag));
|
||||
/* look at second simpleBlock */
|
||||
assert.strictEqual(tag.payload.byteLength, 43, JSON.stringify(tag));
|
||||
assert.strictEqual(tag.discardable, false, 'discardable');
|
||||
done();
|
||||
}));
|
||||
});
|
||||
});
|
19
tsconfig.base.json
Normal file
19
tsconfig.base.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"useDefineForClassFields": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "bundler",
|
||||
"target": "ES2021",
|
||||
"module": "ESNext",
|
||||
"lib": [
|
||||
"ES2021",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
]
|
||||
}
|
||||
}
|
28
tsconfig.example.json
Normal file
28
tsconfig.example.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": ".",
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"noEmit": true,
|
||||
"paths": {
|
||||
"konoebml": [
|
||||
"./src/index.ts"
|
||||
],
|
||||
"konoebml/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [],
|
||||
"include": [
|
||||
"examples/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"files": [],
|
||||
"include": [],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.example.json"
|
||||
}
|
||||
]
|
||||
}
|
17
tsconfig.lib.json
Normal file
17
tsconfig.lib.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "./src",
|
||||
"baseUrl": ".",
|
||||
"declarationDir": "./dist",
|
||||
"outDir": "./dist",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"emitDeclarationOnly": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
1
tsconfig.lib.tsbuildinfo
Normal file
1
tsconfig.lib.tsbuildinfo
Normal file
File diff suppressed because one or more lines are too long
29
tsconfig.spec.json
Normal file
29
tsconfig.spec.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": ".",
|
||||
"types": [
|
||||
"vitest/globals",
|
||||
"node"
|
||||
],
|
||||
"noEmit": true,
|
||||
"paths": {
|
||||
"konoebml": [
|
||||
"./src/index.ts"
|
||||
],
|
||||
"konoebml/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [],
|
||||
"include": [
|
||||
"tests/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
36
vitest.config.ts
Normal file
36
vitest.config.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import swc from 'unplugin-swc';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
cacheDir: '.vitest',
|
||||
test: {
|
||||
setupFiles: ['tests/init-test.ts'],
|
||||
environment: 'happy-dom',
|
||||
include: ['tests/**/*.spec.ts'],
|
||||
globals: true,
|
||||
restoreMocks: true,
|
||||
coverage: {
|
||||
// you can include other reporters, but 'json-summary' is required, json is recommended
|
||||
reporter: ['text', 'json-summary', 'json'],
|
||||
// If you want a coverage reports even if your tests are failing, include the reportOnFailure option
|
||||
reportOnFailure: true,
|
||||
exclude: [
|
||||
'vitest.config.ts',
|
||||
'rslib.config.ts',
|
||||
'scripts/**',
|
||||
'examples/**',
|
||||
'dist/**',
|
||||
],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
tsconfigPaths(),
|
||||
swc.vite({
|
||||
include: /\.[mc]?[jt]sx?$/,
|
||||
// for git+ package only
|
||||
exclude: [] as any,
|
||||
tsconfigFile: './tsconfig.spec.json',
|
||||
}),
|
||||
],
|
||||
});
|
Loading…
Reference in New Issue
Block a user