Compare commits
8 Commits
9b310f0c62
...
0b681d4fd1
Author | SHA1 | Date | |
---|---|---|---|
0b681d4fd1 | |||
f921819d2a | |||
d7b8b57e9c | |||
4537190096 | |||
c0d4de4d28 | |||
6da8556f13 | |||
16c807b98e | |||
595e8d29dc |
195
.gitignore
vendored
Normal file
195
.gitignore
vendored
Normal file
@ -0,0 +1,195 @@
|
||||
# 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
|
||||
/lib
|
||||
**/*.tsbuildinfo
|
||||
**/temp
|
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"runem.lit-plugin"
|
||||
]
|
||||
}
|
11
.vscode/settings.json
vendored
Normal file
11
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
// allow autocomplete for ArkType expressions like "string | num"
|
||||
"editor.quickSuggestions": {
|
||||
"strings": "on"
|
||||
},
|
||||
// prioritize ArkType's "type" for autoimports
|
||||
"typescript.preferences.autoImportSpecifierExcludeRegexes": [
|
||||
"^(node:)?os$"
|
||||
],
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
103
Cargo.lock
generated
Normal file
103
Cargo.lock
generated
Normal file
@ -0,0 +1,103 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "demuxing"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"symphonia-format-mkv",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-core"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags",
|
||||
"bytemuck",
|
||||
"lazy_static",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-format-mkv"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bb43471a100f7882dc9937395bd5ebee8329298e766250b15b3875652fe3d6f"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"symphonia-core",
|
||||
"symphonia-metadata",
|
||||
"symphonia-utils-xiph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-metadata"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"symphonia-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symphonia-utils-xiph"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "484472580fa49991afda5f6550ece662237b00c6f562c7d9638d1b086ed010fe"
|
||||
dependencies = [
|
||||
"symphonia-core",
|
||||
"symphonia-metadata",
|
||||
]
|
3
Cargo.toml
Normal file
3
Cargo.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[workspace]
|
||||
members = ["packages/demuxing"]
|
||||
resolver = "2"
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Zhou Yeheng
|
||||
|
||||
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.
|
1
apps/mock/.gitignore
vendored
Normal file
1
apps/mock/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
public/video-sample/huge/*
|
8
apps/mock/nest-cli.json
Normal file
8
apps/mock/nest-cli.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
25
apps/mock/package.json
Normal file
25
apps/mock/package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "mock",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "nest start --watch -b swc",
|
||||
"dev": "pnpm run start"
|
||||
},
|
||||
"keywords": [],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^11.0.11",
|
||||
"@nestjs/config": "^4.0.1",
|
||||
"@nestjs/core": "^11.0.11",
|
||||
"@nestjs/platform-express": "^11.0.11",
|
||||
"@nestjs/serve-static": "^5.0.3",
|
||||
"reflect-metadata": "^0.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^11.0.0",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@swc/cli": "^0.6.0",
|
||||
"@swc/core": "^1.11.8"
|
||||
}
|
||||
}
|
BIN
apps/mock/public/video-sample/audiosample.webm
Normal file
BIN
apps/mock/public/video-sample/audiosample.webm
Normal file
Binary file not shown.
BIN
apps/mock/public/video-sample/test.webm
Normal file
BIN
apps/mock/public/video-sample/test.webm
Normal file
Binary file not shown.
BIN
apps/mock/public/video-sample/video-webm-codecs-avc1-42E01E.webm
Normal file
BIN
apps/mock/public/video-sample/video-webm-codecs-avc1-42E01E.webm
Normal file
Binary file not shown.
BIN
apps/mock/public/video-sample/video-webm-codecs-vp8.webm
Normal file
BIN
apps/mock/public/video-sample/video-webm-codecs-vp8.webm
Normal file
Binary file not shown.
19
apps/mock/src/app.module.ts
Normal file
19
apps/mock/src/app.module.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
import path from 'node:path';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ServeStaticModule.forRoot({
|
||||
rootPath: path.join(__dirname, '..', 'public'),
|
||||
serveRoot: '/api/static',
|
||||
serveStaticOptions: {
|
||||
cacheControl: true,
|
||||
maxAge: '1d',
|
||||
},
|
||||
})
|
||||
],
|
||||
controllers: [],
|
||||
providers: [],
|
||||
})
|
||||
export class AppModule { }
|
8
apps/mock/src/main.ts
Normal file
8
apps/mock/src/main.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
await app.listen(process.env.port ?? 5001);
|
||||
}
|
||||
bootstrap();
|
28
apps/mock/tsconfig.json
Normal file
28
apps/mock/tsconfig.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowImportingTsExtensions": false,
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"baseUrl": ".",
|
||||
"lib": [
|
||||
"ES2024"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"test",
|
||||
"**/*spec.ts"
|
||||
]
|
||||
}
|
13
apps/playground/.gitignore
vendored
Normal file
13
apps/playground/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
# Local
|
||||
.DS_Store
|
||||
*.local
|
||||
*.log*
|
||||
|
||||
# Dist
|
||||
node_modules
|
||||
dist/
|
||||
|
||||
# IDE
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
29
apps/playground/README.md
Normal file
29
apps/playground/README.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Rsbuild project
|
||||
|
||||
## Setup
|
||||
|
||||
Install the dependencies:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## Get started
|
||||
|
||||
Start the dev server:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Build the app for production:
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
Preview the production build locally:
|
||||
|
||||
```bash
|
||||
pnpm preview
|
||||
```
|
19
apps/playground/package.json
Normal file
19
apps/playground/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "playground",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "rsbuild build",
|
||||
"dev": "rsbuild dev",
|
||||
"preview": "rsbuild preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"konoebml": "0.1.1",
|
||||
"lit": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rsbuild/core": "^1.2.14",
|
||||
"typescript": "^5.8.2"
|
||||
}
|
||||
}
|
0
apps/playground/public/.gitkeep
Normal file
0
apps/playground/public/.gitkeep
Normal file
18
apps/playground/rsbuild.config.ts
Normal file
18
apps/playground/rsbuild.config.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { defineConfig } from '@rsbuild/core';
|
||||
|
||||
export default defineConfig({
|
||||
|
||||
html: {
|
||||
title: 'Konoplayer Playground',
|
||||
template: './src/index.html',
|
||||
},
|
||||
source: {
|
||||
decorators: {
|
||||
version: 'legacy',
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5000,
|
||||
},
|
||||
});
|
1
apps/playground/src/env.d.ts
vendored
Normal file
1
apps/playground/src/env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="@rsbuild/core/types" />
|
59
apps/playground/src/fetch.ts
Normal file
59
apps/playground/src/fetch.ts
Normal file
@ -0,0 +1,59 @@
|
||||
export interface RangedStream {
|
||||
controller: AbortController;
|
||||
response: Response;
|
||||
body: ReadableStream;
|
||||
totalSize?: number;
|
||||
}
|
||||
|
||||
export async function createRangedStream(
|
||||
url: string,
|
||||
byteStart = 0,
|
||||
byteEnd?: number
|
||||
) {
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
const headers = new Headers();
|
||||
headers.append(
|
||||
'Range',
|
||||
typeof byteEnd === 'number'
|
||||
? `bytes=${byteStart}-${byteEnd}`
|
||||
: `bytes=${byteStart}-`
|
||||
);
|
||||
|
||||
const response = await fetch(url, { signal, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('fetch video stream failed');
|
||||
}
|
||||
|
||||
const acceptRanges = response.headers.get('Accept-Ranges');
|
||||
|
||||
if (acceptRanges !== 'bytes') {
|
||||
throw new Error('video server does not support byte ranges');
|
||||
}
|
||||
|
||||
const body = response.body;
|
||||
|
||||
if (!(body instanceof ReadableStream)) {
|
||||
throw new Error('can not get readable stream from response.body');
|
||||
}
|
||||
|
||||
const contentRange = response.headers.get('Content-Range');
|
||||
|
||||
//
|
||||
// Content-Range Header Syntax:
|
||||
// Content-Range: <unit> <range-start>-<range-end>/<size>
|
||||
// Content-Range: <unit> <range-start>-<range-end>/*
|
||||
// Content-Range: <unit> */<size>
|
||||
//
|
||||
const totalSize = contentRange
|
||||
? Number.parseInt(contentRange.split('/')[1], 10)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
controller,
|
||||
response,
|
||||
body,
|
||||
totalSize,
|
||||
};
|
||||
}
|
60
apps/playground/src/fetch/index.ts
Normal file
60
apps/playground/src/fetch/index.ts
Normal file
@ -0,0 +1,60 @@
|
||||
export interface RangedStream {
|
||||
controller: AbortController;
|
||||
response: Response;
|
||||
body: ReadableStream<Uint8Array>;
|
||||
totalSize?: number;
|
||||
}
|
||||
|
||||
export async function createRangedStream(
|
||||
url: string,
|
||||
byteStart = 0,
|
||||
byteEnd?: number
|
||||
) {
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
const headers = new Headers();
|
||||
headers.append(
|
||||
'Range',
|
||||
typeof byteEnd === 'number'
|
||||
? `bytes=${byteStart}-${byteEnd}`
|
||||
: `bytes=${byteStart}-`
|
||||
);
|
||||
|
||||
const response = await fetch(url, { signal, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('fetch video stream failed');
|
||||
}
|
||||
|
||||
const acceptRanges = response.headers.get('Accept-Ranges');
|
||||
|
||||
if (acceptRanges !== 'bytes') {
|
||||
throw new Error('video server does not support byte ranges');
|
||||
}
|
||||
|
||||
const body = response.body;
|
||||
|
||||
if (!(body instanceof ReadableStream)) {
|
||||
throw new Error('can not get readable stream from response.body');
|
||||
}
|
||||
|
||||
const contentRange = response.headers.get('Content-Range');
|
||||
|
||||
//
|
||||
// Content-Range Header Syntax:
|
||||
// Content-Range: <unit> <range-start>-<range-end>/<size>
|
||||
// Content-Range: <unit> <range-start>-<range-end>/*
|
||||
// Content-Range: <unit> */<size>
|
||||
//
|
||||
const totalSize = contentRange
|
||||
? Number.parseInt(contentRange.split('/')[1], 10)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
controller,
|
||||
response,
|
||||
body,
|
||||
totalSize,
|
||||
};
|
||||
}
|
||||
|
4
apps/playground/src/index.css
Normal file
4
apps/playground/src/index.css
Normal file
@ -0,0 +1,4 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
}
|
9
apps/playground/src/index.html
Normal file
9
apps/playground/src/index.html
Normal file
@ -0,0 +1,9 @@
|
||||
<!doctype html>
|
||||
|
||||
<head></head>
|
||||
|
||||
<body>
|
||||
<my-element />
|
||||
<video-pipeline-demo src="/api/static/video-sample/test.webm" />
|
||||
<!-- <video-pipeline-demo src="/api/static/video-sample/huge/animation.mkv" /> -->
|
||||
</body>
|
4
apps/playground/src/index.ts
Normal file
4
apps/playground/src/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import './index.css';
|
||||
import { VideoPipelineDemo } from './video-pipeline-demo';
|
||||
|
||||
customElements.define('video-pipeline-demo', VideoPipelineDemo);
|
0
apps/playground/src/media/mkv/index.ts
Normal file
0
apps/playground/src/media/mkv/index.ts
Normal file
282
apps/playground/src/media/mkv/model.ts
Normal file
282
apps/playground/src/media/mkv/model.ts
Normal file
@ -0,0 +1,282 @@
|
||||
import {
|
||||
type EbmlClusterTagType,
|
||||
type EbmlCuePointTagType,
|
||||
type EbmlCuesTagType,
|
||||
type EbmlInfoTagType,
|
||||
type EbmlMasterTagType,
|
||||
type EbmlSeekHeadTagType,
|
||||
type EbmlSegmentTagType,
|
||||
EbmlTagIdEnum,
|
||||
EbmlTagPosition,
|
||||
type EbmlTagType,
|
||||
type EbmlTrackEntryTagType,
|
||||
type EbmlTracksTagType,
|
||||
} from 'konoebml';
|
||||
import {convertEbmlTagToComponent, type InferType,} from './util';
|
||||
import {isEqual, maxBy} from 'lodash-es';
|
||||
import {ArkErrors, type Type} from 'arktype';
|
||||
import {
|
||||
ClusterSchema,
|
||||
type ClusterType,
|
||||
CuePointSchema,
|
||||
type CuePointType,
|
||||
type CueTrackPositionsType,
|
||||
InfoSchema,
|
||||
type InfoType,
|
||||
SeekHeadSchema,
|
||||
type SeekHeadType,
|
||||
TrackEntrySchema,
|
||||
type TrackEntryType
|
||||
} from './schema';
|
||||
|
||||
export const SEEK_ID_KAX_INFO = new Uint8Array([0x15, 0x49, 0xa9, 0x66]);
|
||||
export const SEEK_ID_KAX_TRACKS = new Uint8Array([0x16, 0x54, 0xae, 0x6b]);
|
||||
export const SEEK_ID_KAX_CUES = new Uint8Array([0x1c, 0x53, 0xbb, 0x6b]);
|
||||
|
||||
export class SegmentSystem {
|
||||
startTag: EbmlSegmentTagType;
|
||||
headTags: EbmlTagType[] = [];
|
||||
|
||||
cue: CueSystem;
|
||||
cluster: ClusterSystem;
|
||||
seek: SeekSystem;
|
||||
info: InfoSystem;
|
||||
track: TrackSystem;
|
||||
|
||||
|
||||
constructor(startNode: EbmlSegmentTagType) {
|
||||
this.startTag = startNode;
|
||||
this.cue = new CueSystem(this);
|
||||
this.cluster = new ClusterSystem(this);
|
||||
this.seek = new SeekSystem(this);
|
||||
this.info = new InfoSystem(this);
|
||||
this.track = new TrackSystem(this);
|
||||
}
|
||||
|
||||
get dataStartOffset() {
|
||||
return this.startTag.startOffset + this.startTag.headerLength;
|
||||
}
|
||||
|
||||
get startOffset () {
|
||||
return this.startTag.startOffset;
|
||||
}
|
||||
|
||||
completeHeads () {
|
||||
const infoTag = this.seek.seekTagBySeekId(SEEK_ID_KAX_INFO);
|
||||
const tracksTag = this.seek.seekTagBySeekId(SEEK_ID_KAX_TRACKS);
|
||||
const cuesTag = this.seek.seekTagBySeekId(SEEK_ID_KAX_CUES);
|
||||
|
||||
if (cuesTag?.id === EbmlTagIdEnum.Cues) {
|
||||
this.cue.prepareCuesWithTag(cuesTag)
|
||||
}
|
||||
if (infoTag?.id === EbmlTagIdEnum.Info) {
|
||||
this.info.prepareWithInfoTag(infoTag);
|
||||
}
|
||||
if (tracksTag?.id === EbmlTagIdEnum.Tracks) {
|
||||
this.track.prepareTracksWithTag(tracksTag);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
scanHead (tag: EbmlTagType) {
|
||||
if (
|
||||
tag.id === EbmlTagIdEnum.SeekHead &&
|
||||
tag.position === EbmlTagPosition.End
|
||||
) {
|
||||
this.seek.addSeekHeadTag(tag);
|
||||
}
|
||||
this.headTags.push(tag);
|
||||
this.seek.memoTag(tag);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class SegmentComponentSystemTrait<E extends EbmlMasterTagType, S extends Type<any>> {
|
||||
segment: SegmentSystem;
|
||||
|
||||
get schema(): S {
|
||||
throw new Error("unimplemented!")
|
||||
}
|
||||
|
||||
constructor(segment: SegmentSystem) {
|
||||
this.segment = segment;
|
||||
}
|
||||
|
||||
componentFromTag(tag: E): InferType<S> {
|
||||
const extracted = convertEbmlTagToComponent(tag);
|
||||
const result = this.schema(extracted);
|
||||
if (result instanceof ArkErrors) {
|
||||
const errors = result;
|
||||
console.error('Parse component from tag error:', tag.toDebugRecord(), errors.flatProblemsByPath)
|
||||
throw errors;
|
||||
}
|
||||
return result as InferType<S>
|
||||
}
|
||||
}
|
||||
|
||||
export class SeekSystem extends SegmentComponentSystemTrait<EbmlSeekHeadTagType, typeof SeekHeadSchema> {
|
||||
override get schema() {
|
||||
return SeekHeadSchema;
|
||||
}
|
||||
|
||||
seekHeads: SeekHeadType[] = [];
|
||||
offsetToTagMemo: Map<number, EbmlTagType> = new Map();
|
||||
|
||||
memoTag (tag: EbmlTagType) {
|
||||
this.offsetToTagMemo.set(tag.startOffset, tag);
|
||||
}
|
||||
|
||||
addSeekHeadTag (tag: EbmlSeekHeadTagType) {
|
||||
const seekHead = this.componentFromTag(tag);
|
||||
this.seekHeads.push(seekHead);
|
||||
return seekHead;
|
||||
}
|
||||
|
||||
offsetFromSeekPosition (position: number): number {
|
||||
return position + this.segment.startOffset;
|
||||
}
|
||||
|
||||
offsetFromSeekDataPosition (position: number) : number {
|
||||
return position + this.segment.dataStartOffset;
|
||||
}
|
||||
|
||||
seekTagByStartOffset (
|
||||
startOffset: number | undefined
|
||||
): EbmlTagType | undefined {
|
||||
return startOffset! >= 0
|
||||
? this.offsetToTagMemo.get(startOffset!)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
seekOffsetBySeekId(seekId: Uint8Array): number | undefined {
|
||||
const seekPosition = this.seekHeads[0]?.Seek?.find((c) => isEqual(c.SeekID, seekId))
|
||||
?.SeekPosition;
|
||||
return seekPosition! >= 0 ? this.offsetFromSeekPosition(seekPosition!) : undefined;
|
||||
}
|
||||
|
||||
seekTagBySeekId(seekId: Uint8Array): EbmlTagType | undefined {
|
||||
return this.seekTagByStartOffset(
|
||||
this.seekOffsetBySeekId(seekId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class InfoSystem extends SegmentComponentSystemTrait<EbmlInfoTagType, typeof InfoSchema> {
|
||||
override get schema() {
|
||||
return InfoSchema;
|
||||
}
|
||||
|
||||
info!: InfoType;
|
||||
|
||||
prepareWithInfoTag (tag: EbmlInfoTagType) {
|
||||
this.info = this.componentFromTag(tag);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class ClusterSystem extends SegmentComponentSystemTrait<EbmlClusterTagType, typeof ClusterSchema> {
|
||||
override get schema() {
|
||||
return ClusterSchema
|
||||
}
|
||||
|
||||
clustersBuffer: ClusterType[] = [];
|
||||
|
||||
addClusterWithTag (tag: EbmlClusterTagType): ClusterType {
|
||||
const cluster = this.componentFromTag(tag);
|
||||
this.clustersBuffer.push(cluster);
|
||||
return cluster;
|
||||
}
|
||||
}
|
||||
|
||||
export class TrackSystem extends SegmentComponentSystemTrait<EbmlTrackEntryTagType, typeof TrackEntrySchema> {
|
||||
override get schema() {
|
||||
return TrackEntrySchema;
|
||||
}
|
||||
|
||||
tracks = new Map<number, TrackEntryType>();
|
||||
|
||||
prepareTracksWithTag (tag: EbmlTracksTagType) {
|
||||
this.tracks.clear();
|
||||
for (const c of tag.children) {
|
||||
if (c.id === EbmlTagIdEnum.TrackEntry) {
|
||||
const trackEntry = this.componentFromTag(c);
|
||||
this.tracks.set(trackEntry.TrackNumber, trackEntry);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class CueSystem extends SegmentComponentSystemTrait<
|
||||
EbmlCuePointTagType,
|
||||
typeof CuePointSchema
|
||||
> {
|
||||
override get schema () {
|
||||
return CuePointSchema
|
||||
};
|
||||
|
||||
cues: CuePointType[] = [];
|
||||
|
||||
|
||||
prepareCuesWithTag (tag: EbmlCuesTagType) {
|
||||
this.cues = tag.children
|
||||
.filter(c => c.id === EbmlTagIdEnum.CuePoint)
|
||||
.map(this.componentFromTag.bind(this));
|
||||
return this;
|
||||
}
|
||||
|
||||
findClosestCue(seekTime: number): CuePointType | undefined {
|
||||
const cues = this.cues;
|
||||
if (!cues || cues.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let left = 0;
|
||||
let right = cues.length - 1;
|
||||
|
||||
if (seekTime <= cues[0].CueTime) {
|
||||
return cues[0];
|
||||
}
|
||||
|
||||
if (seekTime >= cues[right].CueTime) {
|
||||
return cues[right];
|
||||
}
|
||||
|
||||
while (left <= right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
|
||||
if (cues[mid].CueTime === seekTime) {
|
||||
return cues[mid];
|
||||
}
|
||||
|
||||
if (cues[mid].CueTime < seekTime) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
const before = cues[right];
|
||||
const after = cues[left];
|
||||
return Math.abs(before.CueTime - seekTime) <
|
||||
Math.abs(after.CueTime - seekTime)
|
||||
? before
|
||||
: after;
|
||||
}
|
||||
|
||||
getCueTrackPositions (cuePoint: CuePointType, track?: number): CueTrackPositionsType {
|
||||
let cueTrackPositions: CueTrackPositionsType | undefined;
|
||||
if (track! >= 0) {
|
||||
cueTrackPositions = cuePoint.CueTrackPositions.find(c => c.CueTrack === track);
|
||||
}
|
||||
if (!cueTrackPositions) {
|
||||
cueTrackPositions = maxBy(cuePoint.CueTrackPositions, c => c.CueClusterPosition)!;
|
||||
}
|
||||
return cueTrackPositions;
|
||||
}
|
||||
|
||||
get prepared (): boolean {
|
||||
return this.cues.length > 0;
|
||||
}
|
||||
}
|
319
apps/playground/src/media/mkv/reactive.ts
Normal file
319
apps/playground/src/media/mkv/reactive.ts
Normal file
@ -0,0 +1,319 @@
|
||||
import {
|
||||
type EbmlTagType,
|
||||
EbmlStreamDecoder,
|
||||
EbmlTagIdEnum,
|
||||
EbmlTagPosition,
|
||||
} from 'konoebml';
|
||||
import {
|
||||
Observable,
|
||||
from,
|
||||
switchMap,
|
||||
share,
|
||||
defer,
|
||||
EMPTY,
|
||||
of,
|
||||
filter,
|
||||
finalize,
|
||||
isEmpty,
|
||||
map,
|
||||
merge,
|
||||
raceWith,
|
||||
reduce,
|
||||
scan,
|
||||
shareReplay,
|
||||
take,
|
||||
takeUntil,
|
||||
withLatestFrom,
|
||||
} from 'rxjs';
|
||||
import { createRangedStream } from '@/fetch';
|
||||
import { SegmentSystem, SEEK_ID_KAX_CUES, type CueSystem } from './model';
|
||||
import { isTagIdPos } from './util';
|
||||
import type { ClusterType } from "./schema";
|
||||
|
||||
export function createRangedEbmlStream(
|
||||
url: string,
|
||||
byteStart = 0,
|
||||
byteEnd?: number
|
||||
): Observable<{
|
||||
ebml$: Observable<EbmlTagType>;
|
||||
totalSize?: number;
|
||||
response: Response;
|
||||
body: ReadableStream<Uint8Array>;
|
||||
controller: AbortController;
|
||||
}> {
|
||||
const stream$ = from(createRangedStream(url, byteStart, byteEnd));
|
||||
|
||||
return stream$.pipe(
|
||||
switchMap(({ controller, body, totalSize, response }) => {
|
||||
let requestCompleted = false;
|
||||
const originRequest$ = new Observable<EbmlTagType>((subscriber) => {
|
||||
body
|
||||
.pipeThrough(
|
||||
new EbmlStreamDecoder({
|
||||
streamStartOffset: byteStart,
|
||||
collectChild: (child) => child.id !== EbmlTagIdEnum.Cluster,
|
||||
})
|
||||
)
|
||||
.pipeTo(
|
||||
new WritableStream({
|
||||
write: (tag) => subscriber.next(tag),
|
||||
close: () => {
|
||||
if (!requestCompleted) {
|
||||
subscriber.complete();
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
.catch((error) => {
|
||||
if (requestCompleted && error?.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
subscriber.error(error);
|
||||
});
|
||||
|
||||
return () => {
|
||||
requestCompleted = true;
|
||||
controller.abort();
|
||||
};
|
||||
}).pipe(
|
||||
share({
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
resetOnRefCountZero: true,
|
||||
})
|
||||
);
|
||||
|
||||
const ebml$ = defer(() =>
|
||||
requestCompleted ? EMPTY : originRequest$
|
||||
).pipe(
|
||||
share({
|
||||
resetOnError: false,
|
||||
resetOnComplete: true,
|
||||
resetOnRefCountZero: true,
|
||||
})
|
||||
);
|
||||
|
||||
return of({
|
||||
ebml$,
|
||||
totalSize,
|
||||
response,
|
||||
body,
|
||||
controller,
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
export function createEbmlController(src: string) {
|
||||
const request$ = createRangedEbmlStream(src, 0);
|
||||
|
||||
const controller$ = request$.pipe(
|
||||
map(({ totalSize, ebml$, response, controller }) => {
|
||||
const head$ = ebml$.pipe(
|
||||
filter(isTagIdPos(EbmlTagIdEnum.EBML, EbmlTagPosition.End)),
|
||||
take(1),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
console.debug(
|
||||
`stream of video "${src}" created, total size is ${totalSize ?? 'unknown'}`
|
||||
);
|
||||
|
||||
const segmentStart$ = ebml$.pipe(
|
||||
filter((s) => s.position === EbmlTagPosition.Start),
|
||||
filter((tag) => tag.id === EbmlTagIdEnum.Segment)
|
||||
);
|
||||
|
||||
const segments$ = segmentStart$.pipe(
|
||||
map((startTag) => {
|
||||
const segment = new SegmentSystem(startTag);
|
||||
const clusterSystem = segment.cluster;
|
||||
const seekSystem = segment.seek;
|
||||
|
||||
const continuousReusedCluster$ = ebml$.pipe(
|
||||
filter(isTagIdPos(EbmlTagIdEnum.Cluster, EbmlTagPosition.End)),
|
||||
filter((s) => s.id === EbmlTagIdEnum.Cluster),
|
||||
map(clusterSystem.addClusterWithTag.bind(clusterSystem))
|
||||
);
|
||||
|
||||
const segmentEnd$ = ebml$.pipe(
|
||||
filter(isTagIdPos(EbmlTagIdEnum.Segment, EbmlTagPosition.End)),
|
||||
filter((tag) => tag.id === EbmlTagIdEnum.Segment),
|
||||
take(1)
|
||||
);
|
||||
|
||||
const clusterStart$ = ebml$.pipe(
|
||||
filter(isTagIdPos(EbmlTagIdEnum.Cluster, EbmlTagPosition.Start)),
|
||||
take(1),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
const meta$ = ebml$.pipe(
|
||||
takeUntil(clusterStart$.pipe(raceWith(segmentEnd$))),
|
||||
share({
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
resetOnRefCountZero: true,
|
||||
})
|
||||
);
|
||||
|
||||
const withMeta$ = meta$.pipe(
|
||||
reduce((segment, meta) => segment.scanHead(meta), segment),
|
||||
map(segment.completeHeads.bind(segment)),
|
||||
take(1),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
const withRemoteCues$ = withMeta$.pipe(
|
||||
switchMap((s) => {
|
||||
const cueSystem = s.cue;
|
||||
const seekSystem = s.seek;
|
||||
if (cueSystem.prepared) {
|
||||
return EMPTY;
|
||||
}
|
||||
const remoteCuesTagStartOffset = seekSystem.seekOffsetBySeekId(SEEK_ID_KAX_CUES);
|
||||
if (remoteCuesTagStartOffset! >= 0) {
|
||||
return createRangedEbmlStream(src, remoteCuesTagStartOffset).pipe(
|
||||
switchMap((req) => req.ebml$),
|
||||
filter(isTagIdPos(EbmlTagIdEnum.Cues, EbmlTagPosition.End)),
|
||||
withLatestFrom(withMeta$),
|
||||
map(([cues, withMeta]) => {
|
||||
withMeta.cue.prepareCuesWithTag(cues);
|
||||
return withMeta;
|
||||
})
|
||||
);
|
||||
}
|
||||
return EMPTY;
|
||||
}),
|
||||
take(1),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
const withLocalCues$ = withMeta$.pipe(
|
||||
switchMap((s) => s.cue.prepared ? of(s) : EMPTY),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
const withCues$ = merge(withLocalCues$, withRemoteCues$).pipe(
|
||||
take(1)
|
||||
);
|
||||
|
||||
const withoutCues$ = withCues$.pipe(
|
||||
isEmpty(),
|
||||
switchMap((empty) => (empty ? withMeta$ : EMPTY))
|
||||
);
|
||||
|
||||
const seekWithoutCues = (seekTime: number): Observable<ClusterType> => {
|
||||
const cluster$ = continuousReusedCluster$.pipe(
|
||||
isEmpty(),
|
||||
switchMap((empty) => {
|
||||
return empty
|
||||
? clusterStart$.pipe(
|
||||
switchMap((startTag) =>
|
||||
createRangedEbmlStream(src, startTag.startOffset)
|
||||
),
|
||||
switchMap((req) => req.ebml$),
|
||||
filter(
|
||||
isTagIdPos(EbmlTagIdEnum.Cluster, EbmlTagPosition.End)
|
||||
),
|
||||
map((tag) => clusterSystem.addClusterWithTag(tag))
|
||||
)
|
||||
: continuousReusedCluster$;
|
||||
})
|
||||
);
|
||||
if (seekTime === 0) {
|
||||
return cluster$;
|
||||
}
|
||||
|
||||
return cluster$.pipe(
|
||||
scan(
|
||||
(prev, curr) =>
|
||||
[prev?.[1], curr] as [
|
||||
ClusterType | undefined,
|
||||
ClusterType | undefined,
|
||||
],
|
||||
[undefined, undefined] as [
|
||||
ClusterType | undefined,
|
||||
ClusterType | undefined,
|
||||
]
|
||||
),
|
||||
filter((c) => c[1]?.Timestamp! > seekTime),
|
||||
map((c) => c[0] ?? c[1]!)
|
||||
);
|
||||
};
|
||||
|
||||
const seekWithCues = (
|
||||
cues: CueSystem,
|
||||
seekTime: number
|
||||
): Observable<ClusterType> => {
|
||||
if (seekTime === 0) {
|
||||
return seekWithoutCues(seekTime);
|
||||
}
|
||||
|
||||
const cuePoint = cues.findClosestCue(seekTime);
|
||||
|
||||
if (!cuePoint) {
|
||||
return seekWithoutCues(seekTime);
|
||||
}
|
||||
|
||||
return createRangedEbmlStream(
|
||||
src,
|
||||
seekSystem.offsetFromSeekDataPosition(cues.getCueTrackPositions(cuePoint).CueClusterPosition)
|
||||
).pipe(
|
||||
switchMap((req) => req.ebml$),
|
||||
filter(isTagIdPos(EbmlTagIdEnum.Cluster, EbmlTagPosition.End)),
|
||||
map(clusterSystem.addClusterWithTag.bind(clusterSystem))
|
||||
);
|
||||
};
|
||||
|
||||
const seek = (seekTime: number): Observable<ClusterType> => {
|
||||
if (seekTime === 0) {
|
||||
const subscription = merge(withCues$, withoutCues$).subscribe();
|
||||
|
||||
// if seekTime equals to 0 at start, reuse the initialize stream
|
||||
return seekWithoutCues(seekTime).pipe(
|
||||
finalize(() => {
|
||||
subscription.unsubscribe();
|
||||
})
|
||||
);
|
||||
}
|
||||
return merge(
|
||||
withCues$.pipe(
|
||||
switchMap((s) =>
|
||||
seekWithCues(s.cue, seekTime)
|
||||
)
|
||||
),
|
||||
withoutCues$.pipe(switchMap((_) => seekWithoutCues(seekTime)))
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
startTag,
|
||||
head$,
|
||||
segment,
|
||||
meta$,
|
||||
withMeta$,
|
||||
withCues$,
|
||||
withoutCues$,
|
||||
seekWithCues,
|
||||
seekWithoutCues,
|
||||
seek,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
segments$,
|
||||
head$,
|
||||
totalSize,
|
||||
ebml$,
|
||||
controller,
|
||||
response,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
controller$,
|
||||
request$,
|
||||
};
|
||||
}
|
1011
apps/playground/src/media/mkv/schema.ts
Normal file
1011
apps/playground/src/media/mkv/schema.ts
Normal file
File diff suppressed because it is too large
Load Diff
53
apps/playground/src/media/mkv/util.ts
Normal file
53
apps/playground/src/media/mkv/util.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import type { Type } from 'arktype';
|
||||
import { EbmlElementType, EbmlTagIdEnum, type EbmlTagType } from 'konoebml';
|
||||
import { IdMultiSet } from './schema';
|
||||
|
||||
export type InferType<T extends Type<any>> = T['infer'];
|
||||
|
||||
export type PredicateIdExtract<T, K> = Extract<T, { id: K }>;
|
||||
|
||||
export type PredicatePositionExtract<
|
||||
T extends { position: string },
|
||||
P,
|
||||
> = P extends T['position'] ? T : never;
|
||||
|
||||
export function isTagIdPos<
|
||||
I extends EbmlTagIdEnum,
|
||||
P extends PredicateIdExtract<EbmlTagType, I>['position'] | '*' = '*',
|
||||
>(id: I, pos?: P) {
|
||||
return (tag: EbmlTagType): tag is PredicateIdExtract<EbmlTagType, I> =>
|
||||
tag.id === id && (pos === '*' || pos === tag.position);
|
||||
}
|
||||
|
||||
export function isTagPos<
|
||||
T extends { position: string },
|
||||
P extends T['position'],
|
||||
>(pos: P | '*' = '*') {
|
||||
return (tag: T): tag is PredicatePositionExtract<T, P> =>
|
||||
pos === '*' || pos === tag.position;
|
||||
}
|
||||
|
||||
export function convertEbmlTagToComponent (tag: EbmlTagType) {
|
||||
if (tag.type === EbmlElementType.Master) {
|
||||
const obj: Record<string, any> = {};
|
||||
const children = tag.children;
|
||||
for (const c of children) {
|
||||
const name = EbmlTagIdEnum[c.id];
|
||||
const converted = convertEbmlTagToComponent(c);
|
||||
if (IdMultiSet.has(c.id)) {
|
||||
if (obj[name]) {
|
||||
obj[name].push(converted);
|
||||
} else {
|
||||
obj[name] = [converted];
|
||||
}
|
||||
} else {
|
||||
obj[name] = converted;
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
if (tag.id === EbmlTagIdEnum.SimpleBlock || tag.id === EbmlTagIdEnum.Block) {
|
||||
return tag;
|
||||
}
|
||||
return tag.data;
|
||||
}
|
49
apps/playground/src/video-pipeline-demo.ts
Normal file
49
apps/playground/src/video-pipeline-demo.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { html, css, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { type Subscription, switchMap, take } from 'rxjs';
|
||||
import { createEbmlController } from './media/mkv/reactive';
|
||||
|
||||
export class VideoPipelineDemo extends LitElement {
|
||||
@property()
|
||||
src!: string;
|
||||
|
||||
subscripton?: Subscription;
|
||||
|
||||
static styles = css``;
|
||||
|
||||
async prepareVideoPipeline() {
|
||||
if (!this.src) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { controller$ } = createEbmlController(this.src);
|
||||
|
||||
this.subscripton = controller$
|
||||
.pipe(
|
||||
switchMap(({ segments$ }) => segments$.pipe(take(1))),
|
||||
switchMap(({ seek }) => seek(0))
|
||||
)
|
||||
.subscribe((cluster) => console.log(cluster));
|
||||
|
||||
const videoDecoder = new VideoDecoder({
|
||||
output: (frame) => {},
|
||||
error: (e) => {
|
||||
e;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.prepareVideoPipeline();
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.subscripton?.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<video />`;
|
||||
}
|
||||
}
|
20
apps/playground/tsconfig.json
Normal file
20
apps/playground/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"target": "ES2020",
|
||||
"outDir": "./dist",
|
||||
"experimentalDecorators": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"useDefineForClassFields": false,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
5
apps/proxy/.whistle/.gitignore
vendored
Normal file
5
apps/proxy/.whistle/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
**/.backup
|
||||
**/.recycle_bin
|
||||
**/.clientid
|
||||
/properties/*
|
||||
!/properties/properties
|
1
apps/proxy/.whistle/properties/properties
Normal file
1
apps/proxy/.whistle/properties/properties
Normal file
@ -0,0 +1 @@
|
||||
{"filesOrder":["latestVersion"],"Custom1":"Custom1","Custom2":"Custom2"}
|
11
apps/proxy/.whistle/rules/files/0.konoplayer
Normal file
11
apps/proxy/.whistle/rules/files/0.konoplayer
Normal file
@ -0,0 +1,11 @@
|
||||
```x-forwarded.json
|
||||
{
|
||||
"X-Forwarded-Host": "konoplayer.com",
|
||||
"X-Forwarded-Proto": "https"
|
||||
}
|
||||
```
|
||||
|
||||
# ^https://konoplayer.com/api/static/*** resSpeed://1024K
|
||||
^https://konoplayer.com/api*** reqHeaders://{x-forwarded.json} http://127.0.0.1:5001/api$1
|
||||
^https://konoplayer.com/*** reqHeaders://{x-forwarded.json} http://127.0.0.1:5000/$1 excludeFilter://^https://konoplayer.com/api
|
||||
^wss://konoplayer.com/*** reqHeaders://{x-forwarded.json} ws://127.0.0.1:5000/$1 excludeFilter://^wss://konoplayer.com/api
|
1
apps/proxy/.whistle/rules/properties
Normal file
1
apps/proxy/.whistle/rules/properties
Normal file
@ -0,0 +1 @@
|
||||
{"filesOrder":["konoplayer"],"selectedList":["konoplayer"],"disabledDefalutRules":true}
|
0
apps/proxy/.whistle/values/properties
Normal file
0
apps/proxy/.whistle/values/properties
Normal file
15
apps/proxy/package.json
Normal file
15
apps/proxy/package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "proxy",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "cross-env WHISTLE_MODE=\"prod|capture|keepXFF|x-forwarded-host|x-forwarded-proto\" whistle run -p 8899 -t 30000 -D .",
|
||||
"dev": "pnpm run start"
|
||||
},
|
||||
"keywords": [],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"whistle": "^2.9.93"
|
||||
}
|
||||
}
|
77
assets/specification/ebml.xml
Normal file
77
assets/specification/ebml.xml
Normal file
@ -0,0 +1,77 @@
|
||||
<EBMLSchema xmlns="urn:ietf:rfc:8794" docType="ebml" version="1">
|
||||
<element name="EBML" path="\EBML" id="0x1A45DFA3" type="master" minOccurs="1" maxOccurs="1">
|
||||
<documentation lang="en" purpose="definition">Set the EBML characteristics of the data to
|
||||
follow. Each EBML document has to start with this.</documentation>
|
||||
</element>
|
||||
<element name="EBMLVersion" path="\EBML\EBMLVersion" id="0x4286" type="uinteger" range="not 0"
|
||||
default="1" minOccurs="1" maxOccurs="1">
|
||||
<documentation lang="en" purpose="definition">The version of EBML parser used to create the
|
||||
file.</documentation>
|
||||
</element>
|
||||
<element name="EBMLReadVersion" path="\EBML\EBMLReadVersion" id="0x42F7" type="uinteger"
|
||||
range="1" default="1" minOccurs="1" maxOccurs="1">
|
||||
<documentation lang="en" purpose="definition">The minimum EBML version a parser has to
|
||||
support to read this file.</documentation>
|
||||
</element>
|
||||
<element name="EBMLMaxIDLength" path="\EBML\EBMLMaxIDLength" id="0x42F2" type="uinteger"
|
||||
range=">=4" default="4" minOccurs="1" maxOccurs="1">
|
||||
<documentation lang="en" purpose="definition">The maximum length of the IDs you'll find in
|
||||
this file (4 or less in Matroska).</documentation>
|
||||
</element>
|
||||
<element name="EBMLMaxSizeLength" path="\EBML\EBMLMaxSizeLength" id="0x42F3" type="uinteger"
|
||||
range="not 0" default="8" minOccurs="1" maxOccurs="1">
|
||||
<documentation lang="en" purpose="definition">The maximum length of the sizes you'll find in
|
||||
this file (8 or less in Matroska). This does not override the element size indicated at
|
||||
the beginning of an element. Elements that have an indicated size which is larger than
|
||||
what is allowed by EBMLMaxSizeLength shall be considered invalid.</documentation>
|
||||
</element>
|
||||
<element name="DocType" path="\EBML\DocType" id="0x4282" type="string" length=">0"
|
||||
minOccurs="1" maxOccurs="1">
|
||||
<documentation lang="en" purpose="definition">A string that describes the type of document
|
||||
that follows this EBML header, for example 'matroska' or 'webm'.</documentation>
|
||||
</element>
|
||||
<element name="DocTypeVersion" path="\EBML\DocTypeVersion" id="0x4287" type="uinteger"
|
||||
range="not 0" default="1" minOccurs="1" maxOccurs="1">
|
||||
<documentation lang="en" purpose="definition">The version of DocType interpreter used to
|
||||
create the file.</documentation>
|
||||
</element>
|
||||
<element name="DocTypeReadVersion" path="\EBML\DocTypeReadVersion" id="0x4285" type="uinteger"
|
||||
range="not 0" default="1" minOccurs="1" maxOccurs="1">
|
||||
<documentation lang="en" purpose="definition">The minimum DocType version an interpreter has
|
||||
to support to read this file.</documentation>
|
||||
</element>
|
||||
<element name="DocTypeExtension" path="\EBML\DocTypeExtension" id="0x4281" type="master">
|
||||
<documentation lang="en" purpose="definition">A DocTypeExtension adds extra Elements to the
|
||||
main DocType+DocTypeVersion tuple it's attached to. An EBML Reader **MAY** know these
|
||||
extra Elements and how to use them. A DocTypeExtension **MAY** be used to iterate
|
||||
between experimental Elements before they are integrated into a regular DocTypeVersion.
|
||||
Reading one DocTypeExtension version of a DocType+DocTypeVersion tuple doesn't imply one
|
||||
should be able to read upper versions of this DocTypeExtension.</documentation>
|
||||
</element>
|
||||
<element name="DocTypeExtensionName" path="\EBML\DocTypeExtension\DocTypeExtensionName"
|
||||
id="0x4283" type="string" length=">0" minOccurs="1" maxOccurs="1">
|
||||
<documentation lang="en" purpose="definition">The name of the DocTypeExtension to
|
||||
differentiate it from other DocTypeExtensions of the same DocType+DocTypeVersion tuple.
|
||||
A DocTypeExtensionName value **MUST** be unique within the EBML Header.</documentation>
|
||||
</element>
|
||||
<element name="DocTypeExtensionVersion" path="\EBML\DocTypeExtension\DocTypeExtensionVersion"
|
||||
id="0x4284" type="uinteger" range="not 0" minOccurs="1" maxOccurs="1">
|
||||
<documentation lang="en" purpose="definition">The version of the DocTypeExtension. Different
|
||||
DocTypeExtensionVersion values of the same DocType + DocTypeVersion +
|
||||
DocTypeExtensionName tuple **MAY** contain completely different sets of extra Elements.
|
||||
An EBML Reader **MAY** support multiple versions of the same tuple, only one version of
|
||||
the tuple, or not support the tuple at all.</documentation>
|
||||
</element>
|
||||
|
||||
<element name="Void" path="\(-\)Void" id="0xEC" type="binary">
|
||||
<documentation lang="en" purpose="definition">Used to void damaged data, to avoid unexpected
|
||||
behaviors when using damaged data. The content is discarded. Also used to reserve space
|
||||
in a sub-element for later use.</documentation>
|
||||
</element>
|
||||
<element name="CRC-32" path="\(1-\)CRC-32" id="0xBF" type="binary" length="4" maxOccurs="1">
|
||||
<documentation lang="en" purpose="definition">The CRC is computed on all the data of the
|
||||
Master element it's in. The CRC element should be the first in it's parent master for
|
||||
easier reading. All level 1 elements should include a CRC-32. The CRC in use is the IEEE
|
||||
CRC32 Little Endian.</documentation>
|
||||
</element>
|
||||
</EBMLSchema>
|
2477
assets/specification/ebml_mkv.xml
Normal file
2477
assets/specification/ebml_mkv.xml
Normal file
File diff suppressed because it is too large
Load Diff
671
assets/specification/ebml_mkv_legacy.xml
Normal file
671
assets/specification/ebml_mkv_legacy.xml
Normal file
@ -0,0 +1,671 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<table>
|
||||
<element name="EBML" level="0" id="0x1A45DFA3" type="master" mandatory="1" multiple="1"
|
||||
minver="1">Set the EBML characteristics of the data to follow. Each EBML document has to
|
||||
start with this.</element>
|
||||
<element name="EBMLVersion" level="1" id="0x4286" type="uinteger" mandatory="1" default="1"
|
||||
minver="1">The version of EBML parser used to create the file.</element>
|
||||
<element name="EBMLReadVersion" level="1" id="0x42F7" type="uinteger" mandatory="1" default="1"
|
||||
minver="1">The minimum EBML version a parser has to support to read this file.</element>
|
||||
<element name="EBMLMaxIDLength" level="1" id="0x42F2" type="uinteger" mandatory="1" default="4"
|
||||
minver="1">The maximum length of the IDs you'll find in this file (4 or less in Matroska).</element>
|
||||
<element name="EBMLMaxSizeLength" level="1" id="0x42F3" type="uinteger" mandatory="1"
|
||||
default="8" minver="1">The maximum length of the sizes you'll find in this file (8 or less
|
||||
in Matroska). This does not override the element size indicated at the beginning of an
|
||||
element. Elements that have an indicated size which is larger than what is allowed by
|
||||
EBMLMaxSizeLength shall be considered invalid.</element>
|
||||
<element name="DocType" level="1" id="0x4282" type="string" mandatory="1" default="matroska"
|
||||
minver="1">A string that describes the type of document that follows this EBML header.
|
||||
'matroska' in our case or 'webm' for webm files.</element>
|
||||
<element name="DocTypeVersion" level="1" id="0x4287" type="uinteger" mandatory="1" default="1"
|
||||
minver="1">The version of DocType interpreter used to create the file.</element>
|
||||
<element name="DocTypeReadVersion" level="1" id="0x4285" type="uinteger" mandatory="1"
|
||||
default="1" minver="1">The minimum DocType version an interpreter has to support to read
|
||||
this file.</element>
|
||||
<element name="Void" level="-1" id="0xEC" type="binary" minver="1">Used to void damaged data, to
|
||||
avoid unexpected behaviors when using damaged data. The content is discarded. Also used to
|
||||
reserve space in a sub-element for later use.</element>
|
||||
<element name="CRC-32" level="-1" id="0xBF" type="binary" minver="1" webm="0">The CRC is
|
||||
computed on all the data of the Master element it's in. The CRC element should be the first
|
||||
in it's parent master for easier reading. All level 1 elements should include a CRC-32. The
|
||||
CRC in use is the IEEE CRC32 Little Endian</element>
|
||||
<element name="SignatureSlot" level="-1" id="0x1B538667" type="master" multiple="1" webm="0">Contain
|
||||
signature of some (coming) elements in the stream.</element>
|
||||
<element name="SignatureAlgo" level="1" id="0x7E8A" type="uinteger" webm="0">Signature algorithm
|
||||
used (1=RSA, 2=elliptic).</element>
|
||||
<element name="SignatureHash" level="1" id="0x7E9A" type="uinteger" webm="0">Hash algorithm used
|
||||
(1=SHA1-160, 2=MD5).</element>
|
||||
<element name="SignaturePublicKey" level="1" id="0x7EA5" type="binary" webm="0">The public key
|
||||
to use with the algorithm (in the case of a PKI-based signature).</element>
|
||||
<element name="Signature" level="1" id="0x7EB5" type="binary" webm="0">The signature of the data
|
||||
(until a new.</element>
|
||||
<element name="SignatureElements" level="1" id="0x7E5B" type="master" webm="0">Contains elements
|
||||
that will be used to compute the signature.</element>
|
||||
<element name="SignatureElementList" level="2" id="0x7E7B" type="master" multiple="1" webm="0">A
|
||||
list consists of a number of consecutive elements that represent one case where data is used
|
||||
in signature. Ex: <i>Cluster|Block|BlockAdditional</i> means that the BlockAdditional of all
|
||||
Blocks in all Clusters is used for encryption.</element>
|
||||
<element name="SignedElement" level="3" id="0x6532" type="binary" multiple="1" webm="0">An
|
||||
element ID whose data will be used to compute the signature.</element>
|
||||
<element name="Segment" level="0" id="0x18538067" type="master" mandatory="1" multiple="1"
|
||||
minver="1">This element contains all other top-level (level 1) elements. Typically a
|
||||
Matroska file is composed of 1 segment.</element>
|
||||
<element name="SeekHead" cppname="SeekHeader" level="1" id="0x114D9B74" type="master"
|
||||
multiple="1" minver="1">Contains the <a
|
||||
href="http://www.matroska.org/technical/specs/notes.html#Position_References">position</a>
|
||||
of other level 1 elements.</element>
|
||||
<element name="Seek" cppname="SeekPoint" level="2" id="0x4DBB" type="master" mandatory="1"
|
||||
multiple="1" minver="1">Contains a single seek entry to an EBML element.</element>
|
||||
<element name="SeekID" level="3" id="0x53AB" type="binary" mandatory="1" minver="1">The binary
|
||||
ID corresponding to the element name.</element>
|
||||
<element name="SeekPosition" level="3" id="0x53AC" type="uinteger" mandatory="1" minver="1">The <a
|
||||
href="http://www.matroska.org/technical/specs/notes.html#Position_References">position</a>
|
||||
of the element in the segment in octets (0 = first level 1 element).</element>
|
||||
<element name="Info" level="1" id="0x1549A966" type="master" mandatory="1" multiple="1"
|
||||
minver="1">Contains miscellaneous general information and statistics on the file.</element>
|
||||
<element name="SegmentUID" level="2" id="0x73A4" type="binary" minver="1" webm="0" range="not 0"
|
||||
bytesize="16">A randomly generated unique ID to identify the current segment between many
|
||||
others (128 bits).</element>
|
||||
<element name="SegmentFilename" level="2" id="0x7384" type="utf-8" minver="1" webm="0">A
|
||||
filename corresponding to this segment.</element>
|
||||
<element name="PrevUID" level="2" id="0x3CB923" type="binary" minver="1" webm="0" bytesize="16">A
|
||||
unique ID to identify the previous chained segment (128 bits).</element>
|
||||
<element name="PrevFilename" level="2" id="0x3C83AB" type="utf-8" minver="1" webm="0">An escaped
|
||||
filename corresponding to the previous segment.</element>
|
||||
<element name="NextUID" level="2" id="0x3EB923" type="binary" minver="1" webm="0" bytesize="16">A
|
||||
unique ID to identify the next chained segment (128 bits).</element>
|
||||
<element name="NextFilename" level="2" id="0x3E83BB" type="utf-8" minver="1" webm="0">An escaped
|
||||
filename corresponding to the next segment.</element>
|
||||
<element name="SegmentFamily" level="2" id="0x4444" type="binary" multiple="1" minver="1"
|
||||
webm="0" bytesize="16">A randomly generated unique ID that all segments related to each
|
||||
other must use (128 bits).</element>
|
||||
<element name="ChapterTranslate" level="2" id="0x6924" type="master" multiple="1" minver="1"
|
||||
webm="0">A tuple of corresponding ID used by chapter codecs to represent this segment.</element>
|
||||
<element name="ChapterTranslateEditionUID" level="3" id="0x69FC" type="uinteger" multiple="1"
|
||||
minver="1" webm="0">Specify an edition UID on which this correspondance applies. When not
|
||||
specified, it means for all editions found in the segment.</element>
|
||||
<element name="ChapterTranslateCodec" level="3" id="0x69BF" type="uinteger" mandatory="1"
|
||||
minver="1" webm="0">The <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#ChapProcessCodecID">chapter
|
||||
codec</a> using this ID (0: Matroska Script, 1: DVD-menu).</element>
|
||||
<element name="ChapterTranslateID" level="3" id="0x69A5" type="binary" mandatory="1" minver="1"
|
||||
webm="0">The binary value used to represent this segment in the chapter codec data. The
|
||||
format depends on the <a
|
||||
href="http://www.matroska.org/technical/specs/chapters/index.html#ChapProcessCodecID">
|
||||
ChapProcessCodecID</a> used.</element>
|
||||
<element name="TimecodeScale" level="2" id="0x2AD7B1" type="uinteger" mandatory="1" minver="1"
|
||||
default="1000000">Timecode scale in nanoseconds (1.000.000 means all timecodes in the
|
||||
segment are expressed in milliseconds).</element>
|
||||
<!-- <element name="TimecodeScaleDenominator" level="2" id="0x2AD7B2" type="uinteger"
|
||||
mandatory="1" minver="4" default="1000000000">Timecode scale numerator, see <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#TimecodeScale">TimecodeScale</a>.</element>
|
||||
TimecodeScale When combined with <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#TimecodeScaleDenominator">TimecodeScaleDenominator</a>
|
||||
the Timecode scale is given by the fraction TimecodeScale/TimecodeScaleDenominator in
|
||||
seconds.-->
|
||||
<element name="Duration" level="2" id="0x4489" type="float" minver="1" range="> 0">Duration
|
||||
of the segment (based on TimecodeScale).</element>
|
||||
<element name="DateUTC" level="2" id="0x4461" type="date" minver="1">Date of the origin of
|
||||
timecode (value 0), i.e. production date.</element>
|
||||
<element name="Title" level="2" id="0x7BA9" type="utf-8" minver="1" webm="0">General name of the
|
||||
segment.</element>
|
||||
<element name="MuxingApp" level="2" id="0x4D80" type="utf-8" mandatory="1" minver="1">Muxing
|
||||
application or library ("libmatroska-0.4.3").</element>
|
||||
<element name="WritingApp" level="2" id="0x5741" type="utf-8" mandatory="1" minver="1">Writing
|
||||
application ("mkvmerge-0.3.3").</element>
|
||||
<element name="Cluster" level="1" id="0x1F43B675" type="master" multiple="1" minver="1">The
|
||||
lower level element containing the (monolithic) Block structure.</element>
|
||||
<element name="Timecode" cppname="ClusterTimecode" level="2" id="0xE7" type="uinteger"
|
||||
mandatory="1" minver="1">Absolute timecode of the cluster (based on TimecodeScale).</element>
|
||||
<element name="SilentTracks" cppname="ClusterSilentTracks" level="2" id="0x5854" type="master"
|
||||
minver="1" webm="0">The list of tracks that are not used in that part of the stream. It is
|
||||
useful when using overlay tracks on seeking. Then you should decide what track to use.</element>
|
||||
<element name="SilentTrackNumber" cppname="ClusterSilentTrackNumber" level="3" id="0x58D7"
|
||||
type="uinteger" multiple="1" minver="1" webm="0">One of the track number that are not used
|
||||
from now on in the stream. It could change later if not specified as silent in a further
|
||||
Cluster.</element>
|
||||
<element name="Position" cppname="ClusterPosition" level="2" id="0xA7" type="uinteger"
|
||||
minver="1" webm="0">The <a
|
||||
href="http://www.matroska.org/technical/specs/notes.html#Position_References">Position</a>
|
||||
of the Cluster in the segment (0 in live broadcast streams). It might help to resynchronise
|
||||
offset on damaged streams.</element>
|
||||
<element name="PrevSize" cppname="ClusterPrevSize" level="2" id="0xAB" type="uinteger"
|
||||
minver="1">Size of the previous Cluster, in octets. Can be useful for backward playing.</element>
|
||||
<element name="SimpleBlock" level="2" id="0xA3" type="binary" multiple="1" minver="2" webm="1"
|
||||
divx="1">Similar to <a href="http://www.matroska.org/technical/specs/index.html#Block">Block</a>
|
||||
but without all the extra information, mostly used to reduced overhead when no extra feature
|
||||
is needed. (see <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#simpleblock_structure">SimpleBlock
|
||||
Structure</a>)</element>
|
||||
<element name="BlockGroup" level="2" id="0xA0" type="master" multiple="1" minver="1">Basic
|
||||
container of information containing a single Block or BlockVirtual, and information specific
|
||||
to that Block/VirtualBlock.</element>
|
||||
<element name="Block" level="3" id="0xA1" type="binary" mandatory="1" minver="1">Block
|
||||
containing the actual data to be rendered and a timecode relative to the Cluster Timecode.
|
||||
(see <a href="http://www.matroska.org/technical/specs/index.html#block_structure">Block
|
||||
Structure</a>)</element>
|
||||
<element name="BlockVirtual" level="3" id="0xA2" type="binary" webm="0">A Block with no data. It
|
||||
must be stored in the stream at the place the real Block should be in display order. (see <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#block_virtual">Block Virtual</a>
|
||||
)</element>
|
||||
<element name="BlockAdditions" level="3" id="0x75A1" type="master" minver="1" webm="0">Contain
|
||||
additional blocks to complete the main one. An EBML parser that has no knowledge of the
|
||||
Block structure could still see and use/skip these data.</element>
|
||||
<element name="BlockMore" level="4" id="0xA6" type="master" mandatory="1" multiple="1"
|
||||
minver="1" webm="0">Contain the BlockAdditional and some parameters.</element>
|
||||
<element name="BlockAddID" level="5" id="0xEE" type="uinteger" mandatory="1" minver="1" webm="0"
|
||||
default="1" range="not 0">An ID to identify the BlockAdditional level.</element>
|
||||
<element name="BlockAdditional" level="5" id="0xA5" type="binary" mandatory="1" minver="1"
|
||||
webm="0">Interpreted by the codec as it wishes (using the BlockAddID).</element>
|
||||
<element name="BlockDuration" level="3" id="0x9B" type="uinteger" minver="1"
|
||||
default="TrackDuration">The duration of the Block (based on TimecodeScale). This element is
|
||||
mandatory when DefaultDuration is set for the track (but can be omitted as other default
|
||||
values). When not written and with no DefaultDuration, the value is assumed to be the
|
||||
difference between the timecode of this Block and the timecode of the next Block in
|
||||
"display" order (not coding order). This element can be useful at the end of a Track (as
|
||||
there is not other Block available), or when there is a break in a track like for subtitle
|
||||
tracks. When set to 0 that means the frame is not a keyframe.</element>
|
||||
<element name="ReferencePriority" cppname="FlagReferenced" level="3" id="0xFA" type="uinteger"
|
||||
mandatory="1" minver="1" webm="0" default="0">This frame is referenced and has the specified
|
||||
cache priority. In cache only a frame of the same or higher priority can replace this frame.
|
||||
A value of 0 means the frame is not referenced.</element>
|
||||
<element name="ReferenceBlock" level="3" id="0xFB" type="integer" multiple="1" minver="1">Timecode
|
||||
of another frame used as a reference (ie: B or P frame). The timecode is relative to the
|
||||
block it's attached to.</element>
|
||||
<element name="ReferenceVirtual" level="3" id="0xFD" type="integer" webm="0">Relative <a
|
||||
href="http://www.matroska.org/technical/specs/notes.html#Position_References">position</a>
|
||||
of the data that should be in position of the virtual block.</element>
|
||||
<element name="CodecState" level="3" id="0xA4" type="binary" minver="2" webm="0">The new codec
|
||||
state to use. Data interpretation is private to the codec. This information should always be
|
||||
referenced by a seek entry.</element>
|
||||
<element name="Slices" level="3" id="0x8E" type="master" minver="1" divx="0">Contains slices
|
||||
description.</element>
|
||||
<element name="TimeSlice" level="4" id="0xE8" type="master" multiple="1" minver="1" divx="0">Contains
|
||||
extra time information about the data contained in the Block. While there are a few files in
|
||||
the wild with this element, it is no longer in use and has been deprecated. Being able to
|
||||
interpret this element is not required for playback.</element>
|
||||
<element name="LaceNumber" cppname="SliceLaceNumber" level="5" id="0xCC" type="uinteger"
|
||||
minver="1" default="0" divx="0">The reverse number of the frame in the lace (0 is the last
|
||||
frame, 1 is the next to last, etc). While there are a few files in the wild with this
|
||||
element, it is no longer in use and has been deprecated. Being able to interpret this
|
||||
element is not required for playback.</element>
|
||||
<element name="FrameNumber" cppname="SliceFrameNumber" level="5" id="0xCD" type="uinteger"
|
||||
default="0">The number of the frame to generate from this lace with this delay (allow you to
|
||||
generate many frames from the same Block/Frame).</element>
|
||||
<element name="BlockAdditionID" cppname="SliceBlockAddID" level="5" id="0xCB" type="uinteger"
|
||||
default="0">The ID of the BlockAdditional element (0 is the main Block).</element>
|
||||
<element name="Delay" cppname="SliceDelay" level="5" id="0xCE" type="uinteger" default="0">The
|
||||
(scaled) delay to apply to the element.</element>
|
||||
<element name="SliceDuration" level="5" id="0xCF" type="uinteger" default="0">The (scaled)
|
||||
duration to apply to the element.</element>
|
||||
<element name="ReferenceFrame" level="3" id="0xC8" type="master" multiple="0" minver="0"
|
||||
webm="0" divx="1">
|
||||
<a href="http://developer.divx.com/docs/divx_plus_hd/format_features/Smooth_FF_RW">DivX
|
||||
trick track extenstions</a>
|
||||
</element>
|
||||
<element name="ReferenceOffset" level="4" id="0xC9" type="uinteger" multiple="0" mandatory="1"
|
||||
minver="0" webm="0" divx="1">
|
||||
<a href="http://developer.divx.com/docs/divx_plus_hd/format_features/Smooth_FF_RW">DivX
|
||||
trick track extenstions</a>
|
||||
</element>
|
||||
<element name="ReferenceTimeCode" level="4" id="0xCA" type="uinteger" multiple="0" mandatory="1"
|
||||
minver="0" webm="0" divx="1">
|
||||
<a href="http://developer.divx.com/docs/divx_plus_hd/format_features/Smooth_FF_RW">DivX
|
||||
trick track extenstions</a>
|
||||
</element>
|
||||
<element name="EncryptedBlock" level="2" id="0xAF" type="binary" multiple="1" webm="0">Similar
|
||||
to <a href="http://www.matroska.org/technical/specs/index.html#SimpleBlock">SimpleBlock</a>
|
||||
but the data inside the Block are Transformed (encrypt and/or signed). (see <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#encryptedblock_structure">EncryptedBlock
|
||||
Structure</a>)</element>
|
||||
<element name="Tracks" level="1" id="0x1654AE6B" type="master" multiple="1" minver="1">A
|
||||
top-level block of information with many tracks described.</element>
|
||||
<element name="TrackEntry" level="2" id="0xAE" type="master" mandatory="1" multiple="1"
|
||||
minver="1">Describes a track with all elements.</element>
|
||||
<element name="TrackNumber" level="3" id="0xD7" type="uinteger" mandatory="1" minver="1"
|
||||
range="not 0">The track number as used in the Block Header (using more than 127 tracks is
|
||||
not encouraged, though the design allows an unlimited number).</element>
|
||||
<element name="TrackUID" level="3" id="0x73C5" type="uinteger" mandatory="1" minver="1"
|
||||
range="not 0">A unique ID to identify the Track. This should be kept the same when making a
|
||||
direct stream copy of the Track to another file.</element>
|
||||
<element name="TrackType" level="3" id="0x83" type="uinteger" mandatory="1" minver="1"
|
||||
range="1-254">A set of track types coded on 8 bits (1: video, 2: audio, 3: complex, 0x10:
|
||||
logo, 0x11: subtitle, 0x12: buttons, 0x20: control).</element>
|
||||
<element name="FlagEnabled" cppname="TrackFlagEnabled" level="3" id="0xB9" type="uinteger"
|
||||
mandatory="1" minver="2" webm="1" default="1" range="0-1">Set if the track is usable. (1
|
||||
bit)</element>
|
||||
<element name="FlagDefault" cppname="TrackFlagDefault" level="3" id="0x88" type="uinteger"
|
||||
mandatory="1" minver="1" default="1" range="0-1">Set if that track (audio, video or subs)
|
||||
SHOULD be active if no language found matches the user preference. (1 bit)</element>
|
||||
<element name="FlagForced" cppname="TrackFlagForced" level="3" id="0x55AA" type="uinteger"
|
||||
mandatory="1" minver="1" default="0" range="0-1">Set if that track MUST be active during
|
||||
playback. There can be many forced track for a kind (audio, video or subs), the player
|
||||
should select the one which language matches the user preference or the default + forced
|
||||
track. Overlay MAY happen between a forced and non-forced track of the same kind. (1 bit)</element>
|
||||
<element name="FlagLacing" cppname="TrackFlagLacing" level="3" id="0x9C" type="uinteger"
|
||||
mandatory="1" minver="1" default="1" range="0-1">Set if the track may contain blocks using
|
||||
lacing. (1 bit)</element>
|
||||
<element name="MinCache" cppname="TrackMinCache" level="3" id="0x6DE7" type="uinteger"
|
||||
mandatory="1" minver="1" webm="0" default="0">The minimum number of frames a player should
|
||||
be able to cache during playback. If set to 0, the reference pseudo-cache system is not
|
||||
used.</element>
|
||||
<element name="MaxCache" cppname="TrackMaxCache" level="3" id="0x6DF8" type="uinteger"
|
||||
minver="1" webm="0">The maximum cache size required to store referenced frames in and the
|
||||
current frame. 0 means no cache is needed.</element>
|
||||
<element name="DefaultDuration" cppname="TrackDefaultDuration" level="3" id="0x23E383"
|
||||
type="uinteger" minver="1" range="not 0">Number of nanoseconds (not scaled via
|
||||
TimecodeScale) per frame ('frame' in the Matroska sense -- one element put into a
|
||||
(Simple)Block).</element>
|
||||
<element name="TrackTimecodeScale" level="3" id="0x23314F" type="float" mandatory="1" minver="1"
|
||||
maxver="3" webm="0" default="1.0" range="> 0">DEPRECATED, DO NOT USE. The scale to apply
|
||||
on this track to work at normal speed in relation with other tracks (mostly used to adjust
|
||||
video speed when the audio length differs).</element>
|
||||
<element name="TrackOffset" level="3" id="0x537F" type="integer" webm="0" default="0">A value to
|
||||
add to the Block's Timecode. This can be used to adjust the playback offset of a track.</element>
|
||||
<element name="MaxBlockAdditionID" level="3" id="0x55EE" type="uinteger" mandatory="1"
|
||||
minver="1" webm="0" default="0">The maximum value of <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#BlockAddID">BlockAddID</a>. A
|
||||
value 0 means there is no <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#BlockAdditions">BlockAdditions</a>
|
||||
for this track.</element>
|
||||
<element name="Name" cppname="TrackName" level="3" id="0x536E" type="utf-8" minver="1">A
|
||||
human-readable track name.</element>
|
||||
<element name="Language" cppname="TrackLanguage" level="3" id="0x22B59C" type="string"
|
||||
minver="1" default="eng">Specifies the language of the track in the <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#languages">Matroska languages
|
||||
form</a>.</element>
|
||||
<element name="CodecID" level="3" id="0x86" type="string" mandatory="1" minver="1">An ID
|
||||
corresponding to the codec, see the <a
|
||||
href="http://www.matroska.org/technical/specs/codecid/index.html">codec page</a> for
|
||||
more info.</element>
|
||||
<element name="CodecPrivate" level="3" id="0x63A2" type="binary" minver="1">Private data only
|
||||
known to the codec.</element>
|
||||
<element name="CodecName" level="3" id="0x258688" type="utf-8" minver="1">A human-readable
|
||||
string specifying the codec.</element>
|
||||
<element name="AttachmentLink" cppname="TrackAttachmentLink" level="3" id="0x7446"
|
||||
type="uinteger" minver="1" webm="0" range="not 0">The UID of an attachment that is used by
|
||||
this codec.</element>
|
||||
<element name="CodecSettings" level="3" id="0x3A9697" type="utf-8" webm="0">A string describing
|
||||
the encoding setting used.</element>
|
||||
<element name="CodecInfoURL" level="3" id="0x3B4040" type="string" multiple="1" webm="0">A URL
|
||||
to find information about the codec used.</element>
|
||||
<element name="CodecDownloadURL" level="3" id="0x26B240" type="string" multiple="1" webm="0">A
|
||||
URL to download about the codec used.</element>
|
||||
<element name="CodecDecodeAll" level="3" id="0xAA" type="uinteger" mandatory="1" minver="2"
|
||||
webm="0" default="1" range="0-1">The codec can decode potentially damaged data (1 bit).</element>
|
||||
<element name="TrackOverlay" level="3" id="0x6FAB" type="uinteger" multiple="1" minver="1"
|
||||
webm="0">Specify that this track is an overlay track for the Track specified (in the
|
||||
u-integer). That means when this track has a gap (see <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#SilentTracks">SilentTracks</a>)
|
||||
the overlay track should be used instead. The order of multiple TrackOverlay matters, the
|
||||
first one is the one that should be used. If not found it should be the second, etc.</element>
|
||||
<element name="TrackTranslate" level="3" id="0x6624" type="master" multiple="1" minver="1"
|
||||
webm="0">The track identification for the given Chapter Codec.</element>
|
||||
<element name="TrackTranslateEditionUID" level="4" id="0x66FC" type="uinteger" multiple="1"
|
||||
minver="1" webm="0">Specify an edition UID on which this translation applies. When not
|
||||
specified, it means for all editions found in the segment.</element>
|
||||
<element name="TrackTranslateCodec" level="4" id="0x66BF" type="uinteger" mandatory="1"
|
||||
minver="1" webm="0">The <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#ChapProcessCodecID">chapter
|
||||
codec</a> using this ID (0: Matroska Script, 1: DVD-menu).</element>
|
||||
<element name="TrackTranslateTrackID" level="4" id="0x66A5" type="binary" mandatory="1"
|
||||
minver="1" webm="0">The binary value used to represent this track in the chapter codec data.
|
||||
The format depends on the <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#ChapProcessCodecID">
|
||||
ChapProcessCodecID</a> used.</element>
|
||||
<element name="Video" cppname="TrackVideo" level="3" id="0xE0" type="master" minver="1">Video
|
||||
settings.</element>
|
||||
<element name="FlagInterlaced" cppname="VideoFlagInterlaced" level="4" id="0x9A" type="uinteger"
|
||||
mandatory="1" minver="2" webm="1" default="0" range="0-1">Set if the video is interlaced. (1
|
||||
bit)</element>
|
||||
<element name="StereoMode" cppname="VideoStereoMode" level="4" id="0x53B8" type="uinteger"
|
||||
minver="3" webm="1" default="0">Stereo-3D video mode (0: mono, 1: side by side (left eye is
|
||||
first), 2: top-bottom (right eye is first), 3: top-bottom (left eye is first), 4: checkboard
|
||||
(right is first), 5: checkboard (left is first), 6: row interleaved (right is first), 7: row
|
||||
interleaved (left is first), 8: column interleaved (right is first), 9: column interleaved
|
||||
(left is first), 10: anaglyph (cyan/red), 11: side by side (right eye is first), 12:
|
||||
anaglyph (green/magenta), 13 both eyes laced in one Block (left eye is first), 14 both eyes
|
||||
laced in one Block (right eye is first)) . There are some more details on <a
|
||||
href="http://www.matroska.org/technical/specs/notes.html#3D">3D support in the
|
||||
Specification Notes</a>.</element>
|
||||
<element name="OldStereoMode" level="4" id="0x53B9" type="uinteger" maxver="0" webm="0" divx="0">DEPRECATED,
|
||||
DO NOT USE. Bogus StereoMode value used in old versions of libmatroska. (0: mono, 1: right
|
||||
eye, 2: left eye, 3: both eyes).</element>
|
||||
<element name="PixelWidth" cppname="VideoPixelWidth" level="4" id="0xB0" type="uinteger"
|
||||
mandatory="1" minver="1" range="not 0">Width of the encoded video frames in pixels.</element>
|
||||
<element name="PixelHeight" cppname="VideoPixelHeight" level="4" id="0xBA" type="uinteger"
|
||||
mandatory="1" minver="1" range="not 0">Height of the encoded video frames in pixels.</element>
|
||||
<element name="PixelCropBottom" cppname="VideoPixelCropBottom" level="4" id="0x54AA"
|
||||
type="uinteger" minver="1" default="0">The number of video pixels to remove at the bottom of
|
||||
the image (for HDTV content).</element>
|
||||
<element name="PixelCropTop" cppname="VideoPixelCropTop" level="4" id="0x54BB" type="uinteger"
|
||||
minver="1" default="0">The number of video pixels to remove at the top of the image.</element>
|
||||
<element name="PixelCropLeft" cppname="VideoPixelCropLeft" level="4" id="0x54CC" type="uinteger"
|
||||
minver="1" default="0">The number of video pixels to remove on the left of the image.</element>
|
||||
<element name="PixelCropRight" cppname="VideoPixelCropRight" level="4" id="0x54DD"
|
||||
type="uinteger" minver="1" default="0">The number of video pixels to remove on the right of
|
||||
the image.</element>
|
||||
<element name="DisplayWidth" cppname="VideoDisplayWidth" level="4" id="0x54B0" type="uinteger"
|
||||
minver="1" default="PixelWidth" range="not 0">Width of the video frames to display. The
|
||||
default value is only valid when <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#DisplayUnit">DisplayUnit</a> is
|
||||
0.</element>
|
||||
<element name="DisplayHeight" cppname="VideoDisplayHeight" level="4" id="0x54BA" type="uinteger"
|
||||
minver="1" default="PixelHeight" range="not 0">Height of the video frames to display. The
|
||||
default value is only valid when <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#DisplayUnit">DisplayUnit</a> is
|
||||
0.</element>
|
||||
<element name="DisplayUnit" cppname="VideoDisplayUnit" level="4" id="0x54B2" type="uinteger"
|
||||
minver="1" default="0">How DisplayWidth & DisplayHeight should be interpreted (0:
|
||||
pixels, 1: centimeters, 2: inches, 3: Display Aspect Ratio).</element>
|
||||
<element name="AspectRatioType" cppname="VideoAspectRatio" level="4" id="0x54B3" type="uinteger"
|
||||
minver="1" default="0">Specify the possible modifications to the aspect ratio (0: free
|
||||
resizing, 1: keep aspect ratio, 2: fixed).</element>
|
||||
<element name="ColourSpace" cppname="VideoColourSpace" level="4" id="0x2EB524" type="binary"
|
||||
minver="1" webm="0" bytesize="4">Same value as in AVI (32 bits).</element>
|
||||
<element name="GammaValue" cppname="VideoGamma" level="4" id="0x2FB523" type="float" webm="0"
|
||||
range="> 0">Gamma Value.</element>
|
||||
<element name="FrameRate" cppname="VideoFrameRate" level="4" id="0x2383E3" type="float"
|
||||
range="> 0">Number of frames per second. <strong>Informational</strong> only.</element>
|
||||
<element name="Audio" cppname="TrackAudio" level="3" id="0xE1" type="master" minver="1">Audio
|
||||
settings.</element>
|
||||
<element name="SamplingFrequency" cppname="AudioSamplingFreq" level="4" id="0xB5" type="float"
|
||||
mandatory="1" minver="1" default="8000.0" range="> 0">Sampling frequency in Hz.</element>
|
||||
<element name="OutputSamplingFrequency" cppname="AudioOutputSamplingFreq" level="4" id="0x78B5"
|
||||
type="float" minver="1" default="Sampling Frequency" range="> 0">Real output sampling
|
||||
frequency in Hz (used for SBR techniques).</element>
|
||||
<element name="Channels" cppname="AudioChannels" level="4" id="0x9F" type="uinteger"
|
||||
mandatory="1" minver="1" default="1" range="not 0">Numbers of channels in the track.</element>
|
||||
<element name="ChannelPositions" cppname="AudioPosition" level="4" id="0x7D7B" type="binary"
|
||||
webm="0">Table of horizontal angles for each successive channel, see <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#channelposition">appendix</a>.</element>
|
||||
<element name="BitDepth" cppname="AudioBitDepth" level="4" id="0x6264" type="uinteger"
|
||||
minver="1" range="not 0">Bits per sample, mostly used for PCM.</element>
|
||||
<element name="TrackOperation" level="3" id="0xE2" type="master" minver="3" webm="0">Operation
|
||||
that needs to be applied on tracks to create this virtual track. For more details <a
|
||||
href="http://www.matroska.org/technical/specs/notes.html#TrackOperation">look at the
|
||||
Specification Notes</a> on the subject.</element>
|
||||
<element name="TrackCombinePlanes" level="4" id="0xE3" type="master" minver="3" webm="0">Contains
|
||||
the list of all video plane tracks that need to be combined to create this 3D track</element>
|
||||
<element name="TrackPlane" level="5" id="0xE4" type="master" mandatory="1" multiple="1"
|
||||
minver="3" webm="0">Contains a video plane track that need to be combined to create this 3D
|
||||
track</element>
|
||||
<element name="TrackPlaneUID" level="6" id="0xE5" type="uinteger" mandatory="1" minver="3"
|
||||
webm="0" range="not 0">The trackUID number of the track representing the plane.</element>
|
||||
<element name="TrackPlaneType" level="6" id="0xE6" type="uinteger" mandatory="1" minver="3"
|
||||
webm="0">The kind of plane this track corresponds to (0: left eye, 1: right eye, 2:
|
||||
background).</element>
|
||||
<element name="TrackJoinBlocks" level="4" id="0xE9" type="master" minver="3" webm="0">Contains
|
||||
the list of all tracks whose Blocks need to be combined to create this virtual track</element>
|
||||
<element name="TrackJoinUID" level="5" id="0xED" type="uinteger" mandatory="1" multiple="1"
|
||||
minver="3" webm="0" range="not 0">The trackUID number of a track whose blocks are used to
|
||||
create this virtual track.</element>
|
||||
<element name="TrickTrackUID" level="3" id="0xC0" type="uinteger" divx="1">
|
||||
<a href="http://developer.divx.com/docs/divx_plus_hd/format_features/Smooth_FF_RW">DivX
|
||||
trick track extenstions</a>
|
||||
</element>
|
||||
<element name="TrickTrackSegmentUID" level="3" id="0xC1" type="binary" divx="1" bytesize="16">
|
||||
<a href="http://developer.divx.com/docs/divx_plus_hd/format_features/Smooth_FF_RW">DivX
|
||||
trick track extenstions</a>
|
||||
</element>
|
||||
<element name="TrickTrackFlag" level="3" id="0xC6" type="uinteger" divx="1" default="0">
|
||||
<a href="http://developer.divx.com/docs/divx_plus_hd/format_features/Smooth_FF_RW">DivX
|
||||
trick track extenstions</a>
|
||||
</element>
|
||||
<element name="TrickMasterTrackUID" level="3" id="0xC7" type="uinteger" divx="1">
|
||||
<a href="http://developer.divx.com/docs/divx_plus_hd/format_features/Smooth_FF_RW">DivX
|
||||
trick track extenstions</a>
|
||||
</element>
|
||||
<element name="TrickMasterTrackSegmentUID" level="3" id="0xC4" type="binary" divx="1"
|
||||
bytesize="16">
|
||||
<a href="http://developer.divx.com/docs/divx_plus_hd/format_features/Smooth_FF_RW">DivX
|
||||
trick track extenstions</a>
|
||||
</element>
|
||||
<element name="ContentEncodings" level="3" id="0x6D80" type="master" minver="1" webm="0">Settings
|
||||
for several content encoding mechanisms like compression or encryption.</element>
|
||||
<element name="ContentEncoding" level="4" id="0x6240" type="master" mandatory="1" multiple="1"
|
||||
minver="1" webm="0">Settings for one content encoding like compression or encryption.</element>
|
||||
<element name="ContentEncodingOrder" level="5" id="0x5031" type="uinteger" mandatory="1"
|
||||
minver="1" webm="0" default="0">Tells when this modification was used during encoding/muxing
|
||||
starting with 0 and counting upwards. The decoder/demuxer has to start with the highest
|
||||
order number it finds and work its way down. This value has to be unique over all
|
||||
ContentEncodingOrder elements in the segment.</element>
|
||||
<element name="ContentEncodingScope" level="5" id="0x5032" type="uinteger" mandatory="1"
|
||||
minver="1" webm="0" default="1" range="not 0">A bit field that describes which elements have
|
||||
been modified in this way. Values (big endian) can be OR'ed. Possible values:<br /> 1 - all
|
||||
frame contents,<br /> 2 - the track's private data,<br /> 4 - the next ContentEncoding (next
|
||||
ContentEncodingOrder. Either the data inside ContentCompression and/or ContentEncryption)</element>
|
||||
<element name="ContentEncodingType" level="5" id="0x5033" type="uinteger" mandatory="1"
|
||||
minver="1" webm="0" default="0">A value describing what kind of transformation has been
|
||||
done. Possible values:<br /> 0 - compression,<br /> 1 - encryption</element>
|
||||
<element name="ContentCompression" level="5" id="0x5034" type="master" minver="1" webm="0">Settings
|
||||
describing the compression used. Must be present if the value of ContentEncodingType is 0
|
||||
and absent otherwise. Each block must be decompressable even if no previous block is
|
||||
available in order not to prevent seeking.</element>
|
||||
<element name="ContentCompAlgo" level="6" id="0x4254" type="uinteger" mandatory="1" minver="1"
|
||||
webm="0" default="0">The compression algorithm used. Algorithms that have been specified so
|
||||
far are:<br /> 0 - zlib,<br /> <del>1 - bzlib,</del><br /> <del>2 - lzo1x</del><br /> 3 -
|
||||
Header Stripping</element>
|
||||
<element name="ContentCompSettings" level="6" id="0x4255" type="binary" minver="1" webm="0">Settings
|
||||
that might be needed by the decompressor. For Header Stripping (ContentCompAlgo=3), the
|
||||
bytes that were removed from the beggining of each frames of the track.</element>
|
||||
<element name="ContentEncryption" level="5" id="0x5035" type="master" minver="1" webm="0">Settings
|
||||
describing the encryption used. Must be present if the value of ContentEncodingType is 1 and
|
||||
absent otherwise.</element>
|
||||
<element name="ContentEncAlgo" level="6" id="0x47E1" type="uinteger" minver="1" webm="0"
|
||||
default="0">The encryption algorithm used. The value '0' means that the contents have not
|
||||
been encrypted but only signed. Predefined values:<br /> 1 - DES, 2 - 3DES, 3 - Twofish, 4 -
|
||||
Blowfish, 5 - AES</element>
|
||||
<element name="ContentEncKeyID" level="6" id="0x47E2" type="binary" minver="1" webm="0">For
|
||||
public key algorithms this is the ID of the public key the the data was encrypted with.</element>
|
||||
<element name="ContentSignature" level="6" id="0x47E3" type="binary" minver="1" webm="0">A
|
||||
cryptographic signature of the contents.</element>
|
||||
<element name="ContentSigKeyID" level="6" id="0x47E4" type="binary" minver="1" webm="0">This is
|
||||
the ID of the private key the data was signed with.</element>
|
||||
<element name="ContentSigAlgo" level="6" id="0x47E5" type="uinteger" minver="1" webm="0"
|
||||
default="0">The algorithm used for the signature. A value of '0' means that the contents
|
||||
have not been signed but only encrypted. Predefined values:<br /> 1 - RSA</element>
|
||||
<element name="ContentSigHashAlgo" level="6" id="0x47E6" type="uinteger" minver="1" webm="0"
|
||||
default="0">The hash algorithm used for the signature. A value of '0' means that the
|
||||
contents have not been signed but only encrypted. Predefined values:<br /> 1 - SHA1-160<br />
|
||||
2 - MD5</element>
|
||||
<element name="Cues" level="1" id="0x1C53BB6B" type="master" minver="1">A top-level element to
|
||||
speed seeking access. All entries are local to the segment. Should be mandatory for non <a
|
||||
href="http://www.matroska.org/technical/streaming/index.hmtl">"live" streams</a>.</element>
|
||||
<element name="CuePoint" level="2" id="0xBB" type="master" mandatory="1" multiple="1" minver="1">Contains
|
||||
all information relative to a seek point in the segment.</element>
|
||||
<element name="CueTime" level="3" id="0xB3" type="uinteger" mandatory="1" minver="1">Absolute
|
||||
timecode according to the segment time base.</element>
|
||||
<element name="CueTrackPositions" level="3" id="0xB7" type="master" mandatory="1" multiple="1"
|
||||
minver="1">Contain positions for different tracks corresponding to the timecode.</element>
|
||||
<element name="CueTrack" level="4" id="0xF7" type="uinteger" mandatory="1" minver="1"
|
||||
range="not 0">The track for which a position is given.</element>
|
||||
<element name="CueClusterPosition" level="4" id="0xF1" type="uinteger" mandatory="1" minver="1">
|
||||
The <a href="http://www.matroska.org/technical/specs/notes.html#Position_References">
|
||||
position</a> of the Cluster containing the required Block.</element>
|
||||
<element name="CueRelativePosition" level="4" id="0xF0" type="uinteger" mandatory="0" minver="4"
|
||||
webm="0">The relative position of the referenced block inside the cluster with 0 being the
|
||||
first possible position for an element inside that cluster.</element>
|
||||
<element name="CueDuration" level="4" id="0xB2" type="uinteger" mandatory="0" minver="4"
|
||||
webm="0">The duration of the block according to the segment time base. If missing the
|
||||
track's DefaultDuration does not apply and no duration information is available in terms of
|
||||
the cues.</element>
|
||||
<element name="CueBlockNumber" level="4" id="0x5378" type="uinteger" minver="1" default="1"
|
||||
range="not 0">Number of the Block in the specified Cluster.</element>
|
||||
<element name="CueCodecState" level="4" id="0xEA" type="uinteger" minver="2" webm="0"
|
||||
default="0">The <a
|
||||
href="http://www.matroska.org/technical/specs/notes.html#Position_References">position</a>
|
||||
of the Codec State corresponding to this Cue element. 0 means that the data is taken from
|
||||
the initial Track Entry.</element>
|
||||
<element name="CueReference" level="4" id="0xDB" type="master" multiple="1" minver="2" webm="0">The
|
||||
Clusters containing the required referenced Blocks.</element>
|
||||
<element name="CueRefTime" level="5" id="0x96" type="uinteger" mandatory="1" minver="2" webm="0">Timecode
|
||||
of the referenced Block.</element>
|
||||
<element name="CueRefCluster" level="5" id="0x97" type="uinteger" mandatory="1" webm="0">The <a
|
||||
href="http://www.matroska.org/technical/specs/notes.html#Position_References">Position</a>
|
||||
of the Cluster containing the referenced Block.</element>
|
||||
<element name="CueRefNumber" level="5" id="0x535F" type="uinteger" webm="0" default="1"
|
||||
range="not 0">Number of the referenced Block of Track X in the specified Cluster.</element>
|
||||
<element name="CueRefCodecState" level="5" id="0xEB" type="uinteger" webm="0" default="0">The <a
|
||||
href="http://www.matroska.org/technical/specs/notes.html#Position_References">position</a>
|
||||
of the Codec State corresponding to this referenced element. 0 means that the data is taken
|
||||
from the initial Track Entry.</element>
|
||||
<element name="Attachments" level="1" id="0x1941A469" type="master" minver="1" webm="0">Contain
|
||||
attached files.</element>
|
||||
<element name="AttachedFile" level="2" id="0x61A7" type="master" mandatory="1" multiple="1"
|
||||
minver="1" webm="0">An attached file.</element>
|
||||
<element name="FileDescription" level="3" id="0x467E" type="utf-8" minver="1" webm="0">A
|
||||
human-friendly name for the attached file.</element>
|
||||
<element name="FileName" level="3" id="0x466E" type="utf-8" mandatory="1" minver="1" webm="0">Filename
|
||||
of the attached file.</element>
|
||||
<element name="FileMimeType" level="3" id="0x4660" type="string" mandatory="1" minver="1"
|
||||
webm="0">MIME type of the file.</element>
|
||||
<element name="FileData" level="3" id="0x465C" type="binary" mandatory="1" minver="1" webm="0">The
|
||||
data of the file.</element>
|
||||
<element name="FileUID" level="3" id="0x46AE" type="uinteger" mandatory="1" minver="1" webm="0"
|
||||
range="not 0">Unique ID representing the file, as random as possible.</element>
|
||||
<element name="FileReferral" level="3" id="0x4675" type="binary" webm="0">A binary value that a
|
||||
track/codec can refer to when the attachment is needed.</element>
|
||||
<element name="FileUsedStartTime" level="3" id="0x4661" type="uinteger" divx="1">
|
||||
<a href="http://developer.divx.com/docs/divx_plus_hd/format_features/World_Fonts">DivX font
|
||||
extension</a>
|
||||
</element>
|
||||
<element name="FileUsedEndTime" level="3" id="0x4662" type="uinteger" divx="1">
|
||||
<a href="http://developer.divx.com/docs/divx_plus_hd/format_features/World_Fonts">DivX font
|
||||
extension</a>
|
||||
</element>
|
||||
<element name="Chapters" level="1" id="0x1043A770" type="master" minver="1" webm="1">A system to
|
||||
define basic menus and partition data. For more detailed information, look at the <a
|
||||
href="http://www.matroska.org/technical/specs/chapters/index.html">Chapters Explanation</a>
|
||||
.</element>
|
||||
<element name="EditionEntry" level="2" id="0x45B9" type="master" mandatory="1" multiple="1"
|
||||
minver="1" webm="1">Contains all information about a segment edition.</element>
|
||||
<element name="EditionUID" level="3" id="0x45BC" type="uinteger" minver="1" webm="0"
|
||||
range="not 0">A unique ID to identify the edition. It's useful for tagging an edition.</element>
|
||||
<element name="EditionFlagHidden" level="3" id="0x45BD" type="uinteger" mandatory="1" minver="1"
|
||||
webm="0" default="0" range="0-1">If an edition is hidden (1), it should not be available to
|
||||
the user interface (but still to Control Tracks). (1 bit)</element>
|
||||
<element name="EditionFlagDefault" level="3" id="0x45DB" type="uinteger" mandatory="1"
|
||||
minver="1" webm="0" default="0" range="0-1">If a flag is set (1) the edition should be used
|
||||
as the default one. (1 bit)</element>
|
||||
<element name="EditionFlagOrdered" level="3" id="0x45DD" type="uinteger" minver="1" webm="0"
|
||||
default="0" range="0-1">Specify if the chapters can be defined multiple times and the order
|
||||
to play them is enforced. (1 bit)</element>
|
||||
<element name="ChapterAtom" level="3" recursive="1" id="0xB6" type="master" mandatory="1"
|
||||
multiple="1" minver="1" webm="1">Contains the atom information to use as the chapter atom
|
||||
(apply to all tracks).</element>
|
||||
<element name="ChapterUID" level="4" id="0x73C4" type="uinteger" mandatory="1" minver="1"
|
||||
webm="1" range="not 0">A unique ID to identify the Chapter.</element>
|
||||
<element name="ChapterStringUID" level="4" id="0x5654" type="utf-8" mandatory="0" minver="3"
|
||||
webm="1">A unique string ID to identify the Chapter. Use for <a
|
||||
href="http://dev.w3.org/html5/webvtt/#webvtt-cue-identifier">WebVTT cue identifier
|
||||
storage</a>.</element>
|
||||
<element name="ChapterTimeStart" level="4" id="0x91" type="uinteger" mandatory="1" minver="1"
|
||||
webm="1">Timecode of the start of Chapter (not scaled).</element>
|
||||
<element name="ChapterTimeEnd" level="4" id="0x92" type="uinteger" minver="1" webm="0">Timecode
|
||||
of the end of Chapter (timecode excluded, not scaled).</element>
|
||||
<element name="ChapterFlagHidden" level="4" id="0x98" type="uinteger" mandatory="1" minver="1"
|
||||
webm="0" default="0" range="0-1">If a chapter is hidden (1), it should not be available to
|
||||
the user interface (but still to Control Tracks). (1 bit)</element>
|
||||
<element name="ChapterFlagEnabled" level="4" id="0x4598" type="uinteger" mandatory="1"
|
||||
minver="1" webm="0" default="1" range="0-1">Specify wether the chapter is enabled. It can be
|
||||
enabled/disabled by a Control Track. When disabled, the movie should skip all the content
|
||||
between the TimeStart and TimeEnd of this chapter. (1 bit)</element>
|
||||
<element name="ChapterSegmentUID" level="4" id="0x6E67" type="binary" minver="1" webm="0"
|
||||
range=">0" bytesize="16">A segment to play in place of this chapter. Edition
|
||||
ChapterSegmentEditionUID should be used for this segment, otherwise no edition is used.</element>
|
||||
<element name="ChapterSegmentEditionUID" level="4" id="0x6EBC" type="uinteger" minver="1"
|
||||
webm="0" range="not 0">The EditionUID to play from the segment linked in ChapterSegmentUID.</element>
|
||||
<element name="ChapterPhysicalEquiv" level="4" id="0x63C3" type="uinteger" minver="1" webm="0">Specify
|
||||
the physical equivalent of this ChapterAtom like "DVD" (60) or "SIDE" (50), see <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#physical">complete list of
|
||||
values</a>.</element>
|
||||
<element name="ChapterTrack" level="4" id="0x8F" type="master" minver="1" webm="0">List of
|
||||
tracks on which the chapter applies. If this element is not present, all tracks apply</element>
|
||||
<element name="ChapterTrackNumber" level="5" id="0x89" type="uinteger" mandatory="1"
|
||||
multiple="1" minver="1" webm="0" range="not 0">UID of the Track to apply this chapter too.
|
||||
In the absense of a control track, choosing this chapter will select the listed Tracks and
|
||||
deselect unlisted tracks. Absense of this element indicates that the Chapter should be
|
||||
applied to any currently used Tracks.</element>
|
||||
<element name="ChapterDisplay" level="4" id="0x80" type="master" multiple="1" minver="1"
|
||||
webm="1">Contains all possible strings to use for the chapter display.</element>
|
||||
<element name="ChapString" cppname="ChapterString" level="5" id="0x85" type="utf-8"
|
||||
mandatory="1" minver="1" webm="1">Contains the string to use as the chapter atom.</element>
|
||||
<element name="ChapLanguage" cppname="ChapterLanguage" level="5" id="0x437C" type="string"
|
||||
mandatory="1" multiple="1" minver="1" webm="1" default="eng">The languages corresponding to
|
||||
the string, in the <a href="http://lcweb.loc.gov/standards/iso639-2/englangn.html#two">bibliographic
|
||||
ISO-639-2 form</a>.</element>
|
||||
<element name="ChapCountry" cppname="ChapterCountry" level="5" id="0x437E" type="string"
|
||||
multiple="1" minver="1" webm="0">The countries corresponding to the string, same 2 octets as
|
||||
in <a href="http://www.iana.org/cctld/cctld-whois.htm">Internet domains</a>.</element>
|
||||
<element name="ChapProcess" cppname="ChapterProcess" level="4" id="0x6944" type="master"
|
||||
multiple="1" minver="1" webm="0">Contains all the commands associated to the Atom.</element>
|
||||
<element name="ChapProcessCodecID" cppname="ChapterProcessCodecID" level="5" id="0x6955"
|
||||
type="uinteger" mandatory="1" minver="1" webm="0" default="0">Contains the type of the codec
|
||||
used for the processing. A value of 0 means native Matroska processing (to be defined), a
|
||||
value of 1 means the <a
|
||||
href="http://www.matroska.org/technical/specs/chapters/index.html#dvd">DVD</a> command
|
||||
set is used. More codec IDs can be added later.</element>
|
||||
<element name="ChapProcessPrivate" cppname="ChapterProcessPrivate" level="5" id="0x450D"
|
||||
type="binary" minver="1" webm="0">Some optional data attached to the ChapProcessCodecID
|
||||
information. <a href="http://www.matroska.org/technical/specs/chapters/index.html#dvd">For
|
||||
ChapProcessCodecID = 1</a>, it is the "DVD level" equivalent.</element>
|
||||
<element name="ChapProcessCommand" cppname="ChapterProcessCommand" level="5" id="0x6911"
|
||||
type="master" multiple="1" minver="1" webm="0">Contains all the commands associated to the
|
||||
Atom.</element>
|
||||
<element name="ChapProcessTime" cppname="ChapterProcessTime" level="6" id="0x6922"
|
||||
type="uinteger" mandatory="1" minver="1" webm="0">Defines when the process command should be
|
||||
handled (0: during the whole chapter, 1: before starting playback, 2: after playback of the
|
||||
chapter).</element>
|
||||
<element name="ChapProcessData" cppname="ChapterProcessData" level="6" id="0x6933" type="binary"
|
||||
mandatory="1" minver="1" webm="0">Contains the command information. The data should be
|
||||
interpreted depending on the ChapProcessCodecID value. <a
|
||||
href="http://www.matroska.org/technical/specs/chapters/index.html#dvd">For
|
||||
ChapProcessCodecID = 1</a>, the data correspond to the binary DVD cell pre/post commands.</element>
|
||||
<element name="Tags" level="1" id="0x1254C367" type="master" multiple="1" minver="1" webm="0">Element
|
||||
containing elements specific to Tracks/Chapters. A list of valid tags can be found <a
|
||||
href="http://www.matroska.org/technical/specs/tagging/index.html">here.</a></element>
|
||||
<element name="Tag" level="2" id="0x7373" type="master" mandatory="1" multiple="1" minver="1"
|
||||
webm="0">Element containing elements specific to Tracks/Chapters.</element>
|
||||
<element name="Targets" cppname="TagTargets" level="3" id="0x63C0" type="master" mandatory="1"
|
||||
minver="1" webm="0">Contain all UIDs where the specified meta data apply. It is empty to
|
||||
describe everything in the segment.</element>
|
||||
<element name="TargetTypeValue" cppname="TagTargetTypeValue" level="4" id="0x68CA"
|
||||
type="uinteger" minver="1" webm="0" default="50">A number to indicate the logical level of
|
||||
the target (see <a
|
||||
href="http://www.matroska.org/technical/specs/tagging/index.html#targettypes">TargetType</a>
|
||||
).</element>
|
||||
<element name="TargetType" cppname="TagTargetType" level="4" id="0x63CA" type="string"
|
||||
minver="1" webm="0">An <strong>informational</strong> string that can be used to display the
|
||||
logical level of the target like "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc (see <a
|
||||
href="http://www.matroska.org/technical/specs/tagging/index.html#targettypes">TargetType</a>
|
||||
).</element>
|
||||
<element name="TagTrackUID" level="4" id="0x63C5" type="uinteger" multiple="1" minver="1"
|
||||
webm="0" default="0">A unique ID to identify the Track(s) the tags belong to. If the value
|
||||
is 0 at this level, the tags apply to all tracks in the Segment.</element>
|
||||
<element name="TagEditionUID" level="4" id="0x63C9" type="uinteger" multiple="1" minver="1"
|
||||
webm="0" default="0">A unique ID to identify the EditionEntry(s) the tags belong to. If the
|
||||
value is 0 at this level, the tags apply to all editions in the Segment.</element>
|
||||
<element name="TagChapterUID" level="4" id="0x63C4" type="uinteger" multiple="1" minver="1"
|
||||
webm="0" default="0">A unique ID to identify the Chapter(s) the tags belong to. If the value
|
||||
is 0 at this level, the tags apply to all chapters in the Segment.</element>
|
||||
<element name="TagAttachmentUID" level="4" id="0x63C6" type="uinteger" multiple="1" minver="1"
|
||||
webm="0" default="0">A unique ID to identify the Attachment(s) the tags belong to. If the
|
||||
value is 0 at this level, the tags apply to all the attachments in the Segment.</element>
|
||||
<element name="SimpleTag" cppname="TagSimple" level="3" recursive="1" id="0x67C8" type="master"
|
||||
mandatory="1" multiple="1" minver="1" webm="0">Contains general information about the
|
||||
target.</element>
|
||||
<element name="TagName" level="4" id="0x45A3" type="utf-8" mandatory="1" minver="1" webm="0">The
|
||||
name of the Tag that is going to be stored.</element>
|
||||
<element name="TagLanguage" level="4" id="0x447A" type="string" mandatory="1" minver="1"
|
||||
webm="0" default="und">Specifies the language of the tag specified, in the <a
|
||||
href="http://www.matroska.org/technical/specs/index.html#languages">Matroska languages
|
||||
form</a>.</element>
|
||||
<element name="TagDefault" level="4" id="0x4484" type="uinteger" mandatory="1" minver="1"
|
||||
webm="0" default="1" range="0-1">Indication to know if this is the default/original language
|
||||
to use for the given tag. (1 bit)</element>
|
||||
<element name="TagString" level="4" id="0x4487" type="utf-8" minver="1" webm="0">The value of
|
||||
the Tag.</element>
|
||||
<element name="TagBinary" level="4" id="0x4485" type="binary" minver="1" webm="0">The values of
|
||||
the Tag if it is binary. Note that this cannot be used in the same SimpleTag as TagString.</element>
|
||||
</table>
|
64
biome.jsonc
Normal file
64
biome.jsonc
Normal file
@ -0,0 +1,64 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"extends": [
|
||||
"ultracite"
|
||||
],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"noParameterProperties": "off",
|
||||
"noNonNullAssertion": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off"
|
||||
},
|
||||
"a11y": {
|
||||
"noSvgWithoutTitle": "off"
|
||||
},
|
||||
"complexity": {
|
||||
"noBannedTypes": "off"
|
||||
},
|
||||
"nursery": {
|
||||
"noEnum": "off",
|
||||
"useConsistentMemberAccessibility": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"ignore": [
|
||||
".vscode/*.json"
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"include": [
|
||||
"apps/playground/**"
|
||||
],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"suspicious": {
|
||||
"noConsole": "off"
|
||||
},
|
||||
"performance": {
|
||||
"useTopLevelRegex": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"include": [
|
||||
"scripts/**"
|
||||
],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"suspicious": {
|
||||
"noConsole": "off"
|
||||
},
|
||||
"performance": {
|
||||
"useTopLevelRegex": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
8
justfile
Normal file
8
justfile
Normal file
@ -0,0 +1,8 @@
|
||||
set windows-shell := ["pwsh.exe", "-c"]
|
||||
set dotenv-load := true
|
||||
|
||||
dev-playground:
|
||||
pnpm run --filter=playground dev
|
||||
|
||||
dev-proxy:
|
||||
pnpm run --filter proxy --filter mock dev
|
33
package.json
Normal file
33
package.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "konoplayer",
|
||||
"version": "0.0.1",
|
||||
"description": "A strange player, like the dumtruck, taking you to Isekai.",
|
||||
"scripts": {
|
||||
"codegen-mkv": "tsx --tsconfig=./tsconfig.scripts.json ./scripts/codegen-mkv.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "lonelyhentxi",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.6.1",
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@types/node": "^22.13.8",
|
||||
"change-case": "^5.4.4",
|
||||
"happy-dom": "^17.4.4",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.8.2",
|
||||
"ultracite": "^4.1.15"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"arktype": "^2.1.10",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mnemonist": "^0.40.3",
|
||||
"rxjs": "^7.8.2",
|
||||
"type-fest": "^4.37.0"
|
||||
}
|
||||
}
|
7
packages/demuxing/Cargo.toml
Normal file
7
packages/demuxing/Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "demuxing"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
symphonia-format-mkv = "0.5.4"
|
14
packages/demuxing/src/lib.rs
Normal file
14
packages/demuxing/src/lib.rs
Normal file
@ -0,0 +1,14 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
5379
pnpm-lock.yaml
generated
Normal file
5379
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
pnpm-workspace.yaml
Normal file
9
pnpm-workspace.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
packages:
|
||||
- packages/*
|
||||
- apps/*
|
||||
onlyBuiltDependencies:
|
||||
- '@biomejs/biome'
|
||||
- '@nestjs/core'
|
||||
- '@swc/core'
|
||||
- core-js
|
||||
- esbuild
|
4
rust-toolchain.toml
Normal file
4
rust-toolchain.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["rustfmt", "clippy"]
|
||||
profile = "default"
|
454
scripts/codegen-mkv.ts
Normal file
454
scripts/codegen-mkv.ts
Normal file
@ -0,0 +1,454 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { Window, type Element } from 'happy-dom';
|
||||
import { type } from 'arktype';
|
||||
import { omitBy, isNil } from 'lodash-es';
|
||||
import { MultiMap } from 'mnemonist';
|
||||
import assert from 'node:assert/strict';
|
||||
import { constantCase } from 'change-case';
|
||||
|
||||
export const AdHocType = {
|
||||
SimpleBlock: {
|
||||
code: 'SimpleBlock',
|
||||
primitive: () => 'SimpleBlockSchema',
|
||||
default: (_d: string): string => {
|
||||
throw new Error('adhoc type can not has default');
|
||||
},
|
||||
primitiveStr: (_d: string): string => {
|
||||
throw new Error('adhoc type does not have primitiveStr');
|
||||
},
|
||||
},
|
||||
Block: {
|
||||
code: 'Block',
|
||||
primitive: () => 'BlockSchema',
|
||||
default: (_d: string): string => {
|
||||
throw new Error('adhoc type can not has default');
|
||||
},
|
||||
primitiveStr: (_d: string): string => {
|
||||
throw new Error('adhoc type does not have primitiveStr');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const EbmlTypeMetas = {
|
||||
master: {
|
||||
code: 'Master',
|
||||
primitive: (d: string): string => `${d}Schema`,
|
||||
default: (_d: string): string => {
|
||||
throw new Error('master type can not has default');
|
||||
},
|
||||
primitiveStr: (_d: string): string => {
|
||||
throw new Error('master type does not have primitiveStr');
|
||||
},
|
||||
},
|
||||
uinteger: {
|
||||
code: 'Uint',
|
||||
primitive: () => 'type.number',
|
||||
default: (d: string): string => d,
|
||||
primitiveStr: () => 'number',
|
||||
},
|
||||
integer: {
|
||||
code: 'Int',
|
||||
primitive: () => 'type.number',
|
||||
default: (d: string) => d,
|
||||
primitiveStr: () => 'number',
|
||||
},
|
||||
float: {
|
||||
code: 'Float',
|
||||
primitive: () => 'type.number',
|
||||
default: (d: string) => `${Number.parseFloat(d)}`,
|
||||
primitiveStr: () => 'number',
|
||||
},
|
||||
string: {
|
||||
code: 'Ascii',
|
||||
primitive: () => 'type.string',
|
||||
default: (d: string) => JSON.stringify(d),
|
||||
primitiveStr: () => 'string',
|
||||
},
|
||||
'utf-8': {
|
||||
code: 'Utf8',
|
||||
primitive: () => 'type.string',
|
||||
default: (d: string) => JSON.stringify(d),
|
||||
primitiveStr: () => 'string',
|
||||
},
|
||||
binary: {
|
||||
code: 'Binary',
|
||||
primitive: () => 'BinarySchema',
|
||||
default: (_d: string): string => {
|
||||
throw new Error('binary type can not has default');
|
||||
},
|
||||
primitiveStr: (_d: string): string => {
|
||||
throw new Error('binary type does not have primitiveStr');
|
||||
},
|
||||
},
|
||||
date: {
|
||||
code: 'Date',
|
||||
primitive: () => 'BinarySchema',
|
||||
default: (_d: string): string => {
|
||||
throw new Error('date type can not has default');
|
||||
},
|
||||
primitiveStr: (_d: string): string => {
|
||||
throw new Error('date type does not have primitiveStr');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const EbmlTypeSchema = type(
|
||||
'"uinteger" | "master" | "binary" | "float" | "utf-8" | "string" | "integer" | "date"'
|
||||
);
|
||||
|
||||
export type EbmlTypeSchemaType = typeof EbmlTypeSchema.infer;
|
||||
|
||||
const RestrictionEntrySchema = type({
|
||||
value: 'string',
|
||||
label: 'string',
|
||||
desc: 'string?',
|
||||
});
|
||||
|
||||
type RestrictionEntryType = typeof RestrictionEntrySchema.infer;
|
||||
|
||||
const EbmlElementSchema = type({
|
||||
name: 'string',
|
||||
type: EbmlTypeSchema,
|
||||
path: type.string.array().atLeastLength(1),
|
||||
prefix: type.string.array().atLeastLength(0),
|
||||
parentPath: type.string.optional(),
|
||||
level: type.number.atLeast(0),
|
||||
id: 'string',
|
||||
default: type.string.optional(),
|
||||
range: type.string.optional(),
|
||||
maxOccurs: type.number.optional(),
|
||||
minOccurs: type.number.optional(),
|
||||
minVer: type.number.optional(),
|
||||
maxVer: type.number.optional(),
|
||||
restriction: RestrictionEntrySchema.array().optional(),
|
||||
});
|
||||
|
||||
type EbmlElementType = typeof EbmlElementSchema.infer;
|
||||
|
||||
function parseDecimalSafe(value: string | undefined): number | undefined {
|
||||
if (value) {
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
if (!Number.isNaN(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function extractElement(element: Element) {
|
||||
const attrs = element.attributes;
|
||||
const name = attrs.getNamedItem('name')?.value?.replace(/-/g, '')!;
|
||||
const type = attrs.getNamedItem('type')?.value!;
|
||||
const path_ = attrs.getNamedItem('path')?.value!;
|
||||
const id = attrs.getNamedItem('id')?.value!;
|
||||
const default_ = attrs.getNamedItem('default')?.value;
|
||||
const range = attrs.getNamedItem('range')?.value;
|
||||
const maxOccurs = parseDecimalSafe(attrs.getNamedItem('maxOccurs')?.value);
|
||||
const minOccurs = parseDecimalSafe(attrs.getNamedItem('minOccurs')?.value);
|
||||
const minVer = parseDecimalSafe(attrs.getNamedItem('minVer')?.value);
|
||||
const maxVer = parseDecimalSafe(attrs.getNamedItem('maxVer')?.value);
|
||||
const restriction = [...element.querySelectorAll('restriction>enum')].map(
|
||||
(e) => {
|
||||
const value = e.getAttribute('value');
|
||||
const label = e.getAttribute('label');
|
||||
return {
|
||||
value,
|
||||
label,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
assert(typeof path_ === 'string', `path of ${name} is not string ${element}`);
|
||||
const path = path_.split('\\').filter(Boolean);
|
||||
const parentPath = path.at(-2);
|
||||
const prefix = path.slice(0, -1);
|
||||
const level = path.length - 1;
|
||||
const el: EbmlElementType = {
|
||||
name,
|
||||
type: type as any,
|
||||
path,
|
||||
prefix,
|
||||
parentPath,
|
||||
level,
|
||||
id,
|
||||
default: default_,
|
||||
range,
|
||||
maxOccurs,
|
||||
minOccurs,
|
||||
minVer,
|
||||
maxVer,
|
||||
restriction: restriction.length >= 0 ? (restriction as any) : undefined,
|
||||
};
|
||||
try {
|
||||
return EbmlElementSchema.assert(omitBy(el, isNil));
|
||||
} catch (e) {
|
||||
console.error('error element is: ', name);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function extractElementAll() {
|
||||
const allElements = new Map<string, EbmlElementType>();
|
||||
|
||||
// the later has the higher priority
|
||||
const specs = [
|
||||
// 'ebml_mkv_legacy.xml', // ignore legacy when building hirerachy
|
||||
'ebml.xml',
|
||||
'ebml_mkv.xml',
|
||||
];
|
||||
|
||||
for (const spec of specs) {
|
||||
const window = new Window();
|
||||
|
||||
const xmlString = fs.readFileSync(
|
||||
path.join(import.meta.dirname, '..', 'assets', 'specification', spec),
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
const domParser = new window.DOMParser();
|
||||
const xmlDoc = domParser.parseFromString(xmlString, 'application/xml');
|
||||
|
||||
const elements = Array.from(xmlDoc.querySelectorAll('element'));
|
||||
|
||||
for (const el of elements) {
|
||||
const extracted = extractElement(el);
|
||||
if (BigInt(extracted.id) >= Number.MAX_SAFE_INTEGER) {
|
||||
throw new Error('unsafe impl use int, should refactor');
|
||||
}
|
||||
// if (
|
||||
// allElements.has(extracted.id) &&
|
||||
// !isEqual(extracted, allElements.get(extracted.id))
|
||||
// ) {
|
||||
// console.warn(
|
||||
// `conflicts id = 0x${extracted.id}, name = ${extracted.name}, overwriting...`
|
||||
// );
|
||||
// }
|
||||
allElements.set(extracted.id, extracted);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(allElements.values());
|
||||
}
|
||||
|
||||
function preprocessLabels(
|
||||
restrictions: RestrictionEntryType[],
|
||||
type: EbmlTypeSchemaType
|
||||
): RestrictionEntryType[] {
|
||||
const labels = restrictions.map((r) => r.label);
|
||||
const values = restrictions.map((r) => r.value);
|
||||
let preprocessed = labels.map((label) =>
|
||||
constantCase(
|
||||
label
|
||||
.replace(/[\s\-_\\/()]+/g, ' ')
|
||||
.trim()
|
||||
.replace(/\s/g, '_')
|
||||
).replace(/^(\d)/g, '_$1')
|
||||
);
|
||||
let noValidChars = preprocessed.every((p) => /^[\w_]+$/.test(p));
|
||||
let noDuplicated = new Set(preprocessed).size === preprocessed.length;
|
||||
|
||||
if (
|
||||
(!noValidChars || !noDuplicated) &&
|
||||
(type === 'string' || type === 'utf-8')
|
||||
) {
|
||||
preprocessed = values.map((value) =>
|
||||
constantCase(
|
||||
value
|
||||
.replace(/[\s\-_\\/()]+/g, ' ')
|
||||
.trim()
|
||||
.replace(/\s/g, '_')
|
||||
).replace(/^(\d)/g, '_$1')
|
||||
);
|
||||
noValidChars = preprocessed.every((p) => /^\w[\w\d_]*$/.test(p));
|
||||
noDuplicated = new Set(preprocessed).size === preprocessed.length;
|
||||
}
|
||||
|
||||
if (noValidChars && noDuplicated) {
|
||||
return preprocessed.map((l, i) => ({
|
||||
label: l,
|
||||
value: restrictions[i].value,
|
||||
desc: restrictions[i].label,
|
||||
}));
|
||||
}
|
||||
return restrictions.map((r) => ({
|
||||
label: `Value${r.value}`,
|
||||
value: r.value,
|
||||
desc: r.label,
|
||||
}));
|
||||
}
|
||||
|
||||
function preprocessedValues(
|
||||
restrictions: RestrictionEntryType[],
|
||||
type: EbmlTypeSchemaType
|
||||
): RestrictionEntryType[] | undefined {
|
||||
if (type === 'integer' || type === 'uinteger') {
|
||||
return restrictions.map((r) => ({
|
||||
...r,
|
||||
value: /^0x/.test(r.value) ? `${Number.parseInt(r.value, 16)}` : r.value,
|
||||
}));
|
||||
}
|
||||
if (type === 'utf-8' || type === 'string') {
|
||||
return restrictions.map((r) => ({ ...r, value: JSON.stringify(r.value) }));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function generateRestriction(element: EbmlElementType): string | undefined {
|
||||
const restriction = element.restriction;
|
||||
if (!restriction?.length) {
|
||||
return;
|
||||
}
|
||||
const preprocessed = preprocessedValues(
|
||||
preprocessLabels(restriction, element.type),
|
||||
element.type
|
||||
);
|
||||
|
||||
if (!preprocessed) {
|
||||
return;
|
||||
}
|
||||
|
||||
return [
|
||||
`export enum ${element.name}RestrictionEnum {`,
|
||||
...preprocessed.map((r) =>
|
||||
[` // ${r.desc}`, ` ${r.label} = ${r.value},`].join('\n')
|
||||
),
|
||||
'};',
|
||||
`export const ${element.name}Restriction = type('${preprocessed.map((r) => r.value).join(' | ')}');`,
|
||||
`export type ${element.name}RestrictionType = typeof ${element.name}Restriction.infer;`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function generateMkvSchemaImports(_elements: EbmlElementType[]) {
|
||||
return `import { type, match } from 'arktype';
|
||||
import { EbmlTagIdEnum, ${Object.keys(AdHocType)
|
||||
.map((typeCode) => `Ebml${typeCode}Tag`)
|
||||
.join(' ,')} } from 'konoebml';`;
|
||||
}
|
||||
|
||||
function generateMkvSchemaHierarchy(elements_: EbmlElementType[]) {
|
||||
const elements = elements_.toSorted((a, b) => a.level - b.level);
|
||||
const seeds = elements.filter((e) => e.level === 0);
|
||||
|
||||
const hirerachy = new MultiMap<string, EbmlElementType>();
|
||||
|
||||
for (const el of elements) {
|
||||
const parentPath = el.parentPath;
|
||||
if (parentPath) {
|
||||
hirerachy.set(parentPath, el);
|
||||
}
|
||||
}
|
||||
|
||||
const idMulti = new Set<string>();
|
||||
const preDefs = [
|
||||
'export const BinarySchema = type.instanceOf(Uint8Array);',
|
||||
...Object.entries(AdHocType).map(
|
||||
([name, meta]) =>
|
||||
`export const ${meta.primitive()} = type.instanceOf(Ebml${name}Tag);`
|
||||
),
|
||||
];
|
||||
|
||||
const generateAssociated = (el: EbmlElementType): string | undefined => {
|
||||
const associated = hirerachy.get(el.name);
|
||||
|
||||
if (!associated?.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const childrenSchema = [
|
||||
...associated.map(generateAssociated).filter(Boolean),
|
||||
];
|
||||
|
||||
const restrictions: string[] = [];
|
||||
|
||||
const selfSchema = [
|
||||
`export const ${el.name}Schema = type({`,
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <explanation>
|
||||
...associated.map((v) => {
|
||||
let meta: any;
|
||||
const restriction = generateRestriction(v);
|
||||
if (restriction) {
|
||||
restrictions.push(restriction);
|
||||
}
|
||||
if (v.type === 'master') {
|
||||
if (hirerachy.has(v.name)) {
|
||||
meta = EbmlTypeMetas.master;
|
||||
}
|
||||
} else {
|
||||
const adHocKey = v.name as keyof typeof AdHocType;
|
||||
if (AdHocType[adHocKey]) {
|
||||
meta = AdHocType[adHocKey];
|
||||
} else {
|
||||
meta = EbmlTypeMetas[v.type as keyof typeof EbmlTypeMetas];
|
||||
}
|
||||
}
|
||||
if (!meta) {
|
||||
return null;
|
||||
}
|
||||
let expr = restriction
|
||||
? `${v.name}Restriction`
|
||||
: meta.primitive(v.name);
|
||||
if (v.maxOccurs !== 1) {
|
||||
expr = `${expr}.array()`;
|
||||
if (v.maxOccurs !== 1 && v.minOccurs === 1 && !v.default) {
|
||||
expr = `${expr}.atLeastLength(1)`
|
||||
}
|
||||
idMulti.add(v.name);
|
||||
}
|
||||
if (v.default) {
|
||||
if (v.maxOccurs === 1) {
|
||||
expr = `${expr}.default(${meta.default(v.default)})`;
|
||||
} else {
|
||||
childrenSchema.push(`export const ${v.name}Schema = match({
|
||||
"${meta.primitiveStr(v.name)}[]": v => v.length > 0 ? v : [${meta.default(v.default)}],
|
||||
"undefined": () => [${meta.default(v.default)}],
|
||||
default: "assert"
|
||||
});`);
|
||||
expr = `${v.name}Schema`;
|
||||
}
|
||||
} else if (!v.minOccurs) {
|
||||
expr = `${expr}.optional()`;
|
||||
}
|
||||
return ` ${v.name}: ${expr},`;
|
||||
}),
|
||||
'});',
|
||||
'',
|
||||
`export type ${el.name}Type = typeof ${el.name}Schema.infer;`,
|
||||
].join('\n');
|
||||
|
||||
return [...childrenSchema, ...restrictions, selfSchema].join('\n\n');
|
||||
};
|
||||
|
||||
const associations = seeds.map(generateAssociated).filter(Boolean);
|
||||
|
||||
const idMultiSchema = `export const IdMultiSet = new Set([\n${Array.from(
|
||||
idMulti.keys()
|
||||
)
|
||||
.map((name) => ` EbmlTagIdEnum.${name}`)
|
||||
.join(',\n')}\n])`;
|
||||
|
||||
return [preDefs.join('\n'), ...associations, idMultiSchema].join('\n\n');
|
||||
}
|
||||
|
||||
function main() {
|
||||
const elementSchemas = extractElementAll();
|
||||
|
||||
const files = {
|
||||
'schema.ts': [
|
||||
generateMkvSchemaImports(elementSchemas),
|
||||
generateMkvSchemaHierarchy(elementSchemas),
|
||||
],
|
||||
};
|
||||
|
||||
const outDir = path.join(import.meta.dirname, '..', 'temp', 'codegen', 'mkv');
|
||||
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
|
||||
for (const [filename, fragments] of Object.entries(files)) {
|
||||
const filepath = path.join(outDir, filename);
|
||||
|
||||
fs.writeFileSync(filepath, fragments.join('\n\n'), 'utf-8');
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
32
tsconfig.base.json
Normal file
32
tsconfig.base.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Default",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"incremental": true,
|
||||
"isolatedModules": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES2024",
|
||||
"DOM.AsyncIterable",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"skipLibCheck": true,
|
||||
"target": "ES2021",
|
||||
"strictNullChecks": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"useDefineForClassFields": true,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
}
|
||||
}
|
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"references": [
|
||||
{
|
||||
"path": "./apps/playground"
|
||||
},
|
||||
{
|
||||
"path": "./apps/mock"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.scripts.json"
|
||||
}
|
||||
]
|
||||
}
|
13
tsconfig.scripts.json
Normal file
13
tsconfig.scripts.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "./scripts",
|
||||
"baseUrl": ".",
|
||||
"emitDeclarationOnly": true
|
||||
},
|
||||
"include": [
|
||||
"scripts"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
Loading…
Reference in New Issue
Block a user