From f921819d2a82ad0b2ddfac1ef9bc3c483f411cc5 Mon Sep 17 00:00:00 2001 From: lonelyhentxi Date: Fri, 21 Mar 2025 23:45:12 +0800 Subject: [PATCH] temp: temp save --- .gitignore | 196 +- apps/playground/package.json | 2 +- apps/playground/scripts/codegen.ts | 0 apps/playground/src/media/mkv/model.ts | 142 +- apps/playground/src/media/mkv/reactive.ts | 6 +- apps/playground/src/media/mkv/schema.ts | 1041 +++++++++ apps/playground/src/media/mkv/util.ts | 131 +- assets/specification/ebml.xml | 77 + assets/specification/ebml_mkv.xml | 2477 +++++++++++++++++++++ assets/specification/ebml_mkv_legacy.xml | 671 ++++++ biome.jsonc | 16 + package.json | 7 +- pnpm-lock.yaml | 42 +- pnpm-workspace.yaml | 1 + scripts/codegen-mkv.ts | 451 ++++ tsconfig.base.json | 9 +- tsconfig.json | 3 + tsconfig.scripts.json | 13 + 18 files changed, 5055 insertions(+), 230 deletions(-) delete mode 100644 apps/playground/scripts/codegen.ts create mode 100644 assets/specification/ebml.xml create mode 100644 assets/specification/ebml_mkv.xml create mode 100644 assets/specification/ebml_mkv_legacy.xml create mode 100644 scripts/codegen-mkv.ts create mode 100644 tsconfig.scripts.json diff --git a/.gitignore b/.gitignore index 910beec..ff913b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,195 @@ -**/node_modules +# 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 -**/dist \ No newline at end of file +/.vitest +**/dist +/lib +**/*.tsbuildinfo +**/temp \ No newline at end of file diff --git a/apps/playground/package.json b/apps/playground/package.json index 777d0b3..452f9c1 100644 --- a/apps/playground/package.json +++ b/apps/playground/package.json @@ -9,7 +9,7 @@ "preview": "rsbuild preview" }, "dependencies": { - "konoebml": "0.1.0", + "konoebml": "0.1.1", "lit": "^3.2.1" }, "devDependencies": { diff --git a/apps/playground/scripts/codegen.ts b/apps/playground/scripts/codegen.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/playground/src/media/mkv/model.ts b/apps/playground/src/media/mkv/model.ts index 3d6db16..6f02bda 100644 --- a/apps/playground/src/media/mkv/model.ts +++ b/apps/playground/src/media/mkv/model.ts @@ -7,15 +7,32 @@ import { type EbmlCuesTagType, type EbmlSeekHeadTagType, type EbmlSegmentTagType, + type EbmlCuePointTagType, + type EbmlMasterTagType, } from 'konoebml'; -import { isTagIdPos, simpleMasterExtractor } from './util'; +import { + convertEbmlTagToModelShape, + type InferType, + isTagIdPos, + SEEK_ID_KAX_CUES, + SEEK_ID_KAX_INFO, + SEEK_ID_KAX_TRACKS, +} from './util'; import { isEqual } from 'lodash-es'; -import { type } from 'arktype'; -import { TagWithArktype } from './util'; +import type { Type } from 'arktype'; +import { CuePointSchema, type CuePointType } 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 abstract class StandardComponentSystem< + E extends EbmlMasterTagType, + S extends Type, +> { + abstract get schema(): S; + + componentFromTag(tag: E): InferType { + const extracted = convertEbmlTagToModelShape(tag); + return this.schema.assert(extracted) as InferType; + } +} export class EbmlSegment { startNode: EbmlSegmentTagType; @@ -103,109 +120,22 @@ export class EbmlSegment { } } -export class TrackEntry extends TagWithArktype({ - id: EbmlTagIdEnum.TrackEntry, - schema: type({ - trackNumber: 'number', - trackType: 'number', - trackUID: 'number', - }), - extract: simpleMasterExtractor({ - [EbmlTagIdEnum.TrackNumber]: { - key: 'trackNumber', - extract: (t) => t.data as number, - }, - [EbmlTagIdEnum.TrackType]: { - key: 'trackType', - extract: (t) => t.data as number, - }, - [EbmlTagIdEnum.TrackUID]: { - key: 'trackUID', - extract: (t) => t.data as number, - }, - }), -}) {} - -export class Tracks extends TagWithArktype({ - id: EbmlTagIdEnum.Tracks, - schema: type({ - tracks: type.instanceOf(TrackEntry).array(), - }), - extract: simpleMasterExtractor({ - [EbmlTagIdEnum.TrackEntry]: { - key: 'tracks', - multi: true, - extract: TrackEntry.fromTag.bind(TrackEntry), - }, - }), -}) {} - -export interface EbmlSeekEntry { - seekId: Uint8Array; - seekPosition: number; -} - -export class MHead extends TagWithArktype({ - id: EbmlTagIdEnum.EBML, - schema: type({}), - extract: () => ({}), -}) {} - -export class SimpleBlock extends TagWithArktype({ - id: EbmlTagIdEnum.SimpleBlock, - schema: type({ - frame: type.instanceOf(Uint8Array), - }), - extract: (tag) => ({ - frame: tag.payload, - }), -}) {} - -export class Block extends TagWithArktype({ - id: EbmlTagIdEnum.Block, - schema: type({}), - extract: () => ({}), -}) {} - -export class Cluster extends TagWithArktype({ - id: EbmlTagIdEnum.Cluster, - schema: type({ - timestamp: 'number', - position: 'number?', - prevSize: 'number?', - simpleBlock: type.instanceOf(SimpleBlock).array(), - }), - extract: simpleMasterExtractor({ - [EbmlTagIdEnum.Timecode]: { - key: 'timestamp', - extract: (t) => t.data as number, - }, - [EbmlTagIdEnum.PrevSize]: { - key: 'prevSize', - extract: (t) => t.data as number, - }, - [EbmlTagIdEnum.SimpleBlock]: { - key: 'simpleBlock', - multi: true, - extract: SimpleBlock.fromTag.bind(SimpleBlock), - }, - }), -}) {} - -export type CuePointType = typeof CuePoint.infer; - -export class Cues { +export class CuesSystem extends StandardComponentSystem< + EbmlCuePointTagType, + typeof CuePointSchema +> { + schema = CuePointSchema; cues: CuePointType[]; - constructor( - public readonly tag: EbmlCuesTagType, - cues: CuePointType[] - ) {} + constructor(cues: CuePointType[]) { + super(); + this.cues = cues; + } - findClosestCue(seekTime: number): CuePoint | null { + findClosestCue(seekTime: number): CuePointType | undefined { const cues = this.cues; if (!cues || cues.length === 0) { - return null; + return undefined; } let left = 0; @@ -235,8 +165,8 @@ export class Cues { const before = cues[right]; const after = cues[left]; - return Math.abs(before.timestamp - seekTime) < - Math.abs(after.timestamp - seekTime) + return Math.abs(before.CueTime - seekTime) < + Math.abs(after.CueTime - seekTime) ? before : after; } diff --git a/apps/playground/src/media/mkv/reactive.ts b/apps/playground/src/media/mkv/reactive.ts index 53805fd..3579377 100644 --- a/apps/playground/src/media/mkv/reactive.ts +++ b/apps/playground/src/media/mkv/reactive.ts @@ -26,7 +26,7 @@ import { withLatestFrom, } from 'rxjs'; import { createRangedStream } from '@/fetch'; -import { EbmlSegment, Cluster, SEEK_ID_KAX_CUES, Cues } from './model'; +import { EbmlSegment, Cluster, SEEK_ID_KAX_CUES, CuesSystem } from './model'; import { isTagIdPos } from './util'; export function createRangedEbmlStream( @@ -250,7 +250,7 @@ export function createEbmlController(src: string) { }; const seekWithCues = ( - cues: Cues, + cues: CuesSystem, seekTime: number ): Observable => { if (seekTime === 0) { @@ -287,7 +287,7 @@ export function createEbmlController(src: string) { return merge( withCues$.pipe( switchMap((s) => - seekWithCues(Cues.fromTag(s.cuesNode!), seekTime) + seekWithCues(CuesSystem.fromTag(s.cuesNode!), seekTime) ) ), withoutCues$.pipe(switchMap((_) => seekWithoutCues(seekTime))) diff --git a/apps/playground/src/media/mkv/schema.ts b/apps/playground/src/media/mkv/schema.ts index e69de29..b7fab35 100644 --- a/apps/playground/src/media/mkv/schema.ts +++ b/apps/playground/src/media/mkv/schema.ts @@ -0,0 +1,1041 @@ +import { type, match } from 'arktype'; +import { EbmlTagIdEnum, EbmlSimpleBlockTag, EbmlBlockTag } from 'konoebml'; + +export const BinarySchema = type.instanceOf(Uint8Array); +export const SimpleBlockSchema = type.instanceOf(EbmlSimpleBlockTag); +export const BlockSchema = type.instanceOf(EbmlBlockTag); + +export const DocTypeExtensionSchema = type({ + DocTypeExtensionName: type.string, + DocTypeExtensionVersion: type.number, +}); + +export type DocTypeExtensionType = typeof DocTypeExtensionSchema.infer; + +export const EBMLSchema = type({ + EBMLVersion: type.number.default(1), + EBMLReadVersion: type.number.default(1), + EBMLMaxIDLength: type.number.default(4), + EBMLMaxSizeLength: type.number.default(8), + DocType: type.string, + DocTypeVersion: type.number.default(1), + DocTypeReadVersion: type.number.default(1), + DocTypeExtension: DocTypeExtensionSchema.array().optional(), +}); + +export type EBMLType = typeof EBMLSchema.infer; + +export const SeekSchema = type({ + SeekID: BinarySchema, + SeekPosition: type.number, +}); + +export type SeekType = typeof SeekSchema.infer; + +export const SeekHeadSchema = type({ + Seek: SeekSchema.array(), +}); + +export type SeekHeadType = typeof SeekHeadSchema.infer; + +export const ChapterTranslateSchema = type({ + ChapterTranslateID: BinarySchema, + ChapterTranslateCodec: type.number, + ChapterTranslateEditionUID: type.number.array().optional(), +}); + +export type ChapterTranslateType = typeof ChapterTranslateSchema.infer; + +export const InfoSchema = type({ + SegmentUUID: BinarySchema.optional(), + SegmentFilename: type.string.optional(), + PrevUUID: BinarySchema.optional(), + PrevFilename: type.string.optional(), + NextUUID: BinarySchema.optional(), + NextFilename: type.string.optional(), + SegmentFamily: BinarySchema.array().optional(), + ChapterTranslate: ChapterTranslateSchema.array().optional(), + TimestampScale: type.number.default(1000000), + Duration: type.number.optional(), + DateUTC: BinarySchema.optional(), + Title: type.string.optional(), + MuxingApp: type.string, + WritingApp: type.string, +}); + +export type InfoType = typeof InfoSchema.infer; + +export const SilentTracksSchema = type({ + SilentTrackNumber: type.number.array().optional(), +}); + +export type SilentTracksType = typeof SilentTracksSchema.infer; + +export const BlockMoreSchema = type({ + BlockAdditional: BinarySchema, + BlockAddID: type.number.default(1), +}); + +export type BlockMoreType = typeof BlockMoreSchema.infer; + +export const BlockAdditionsSchema = type({ + BlockMore: BlockMoreSchema.array(), +}); + +export type BlockAdditionsType = typeof BlockAdditionsSchema.infer; + +export const TimeSliceSchema = type({ + LaceNumber: type.number.optional(), + FrameNumber: type.number.default(0), + BlockAdditionID: type.number.default(0), + Delay: type.number.default(0), + SliceDuration: type.number.default(0), +}); + +export type TimeSliceType = typeof TimeSliceSchema.infer; + +export const SlicesSchema = type({ + TimeSlice: TimeSliceSchema.array().optional(), +}); + +export type SlicesType = typeof SlicesSchema.infer; + +export const ReferenceFrameSchema = type({ + ReferenceOffset: type.number, + ReferenceTimestamp: type.number, +}); + +export type ReferenceFrameType = typeof ReferenceFrameSchema.infer; + +export const BlockGroupSchema = type({ + Block: BlockSchema, + BlockVirtual: BinarySchema.optional(), + BlockAdditions: BlockAdditionsSchema.optional(), + BlockDuration: type.number.optional(), + ReferencePriority: type.number.default(0), + ReferenceBlock: type.number.array().optional(), + ReferenceVirtual: type.number.optional(), + CodecState: BinarySchema.optional(), + DiscardPadding: type.number.optional(), + Slices: SlicesSchema.optional(), + ReferenceFrame: ReferenceFrameSchema.optional(), +}); + +export type BlockGroupType = typeof BlockGroupSchema.infer; + +export const ClusterSchema = type({ + Timestamp: type.number, + SilentTracks: SilentTracksSchema.optional(), + Position: type.number.optional(), + PrevSize: type.number.optional(), + SimpleBlock: SimpleBlockSchema.array().optional(), + BlockGroup: BlockGroupSchema.array().optional(), + EncryptedBlock: BinarySchema.array().optional(), +}); + +export type ClusterType = typeof ClusterSchema.infer; + +export const BlockAdditionMappingSchema = type({ + BlockAddIDValue: type.number.optional(), + BlockAddIDName: type.string.optional(), + BlockAddIDType: type.number.default(0), + BlockAddIDExtraData: BinarySchema.optional(), +}); + +export type BlockAdditionMappingType = typeof BlockAdditionMappingSchema.infer; + +export const TrackTranslateSchema = type({ + TrackTranslateTrackID: BinarySchema, + TrackTranslateCodec: type.number, + TrackTranslateEditionUID: type.number.array().optional(), +}); + +export type TrackTranslateType = typeof TrackTranslateSchema.infer; + +export const MasteringMetadataSchema = type({ + PrimaryRChromaticityX: type.number.optional(), + PrimaryRChromaticityY: type.number.optional(), + PrimaryGChromaticityX: type.number.optional(), + PrimaryGChromaticityY: type.number.optional(), + PrimaryBChromaticityX: type.number.optional(), + PrimaryBChromaticityY: type.number.optional(), + WhitePointChromaticityX: type.number.optional(), + WhitePointChromaticityY: type.number.optional(), + LuminanceMax: type.number.optional(), + LuminanceMin: type.number.optional(), +}); + +export type MasteringMetadataType = typeof MasteringMetadataSchema.infer; + +export enum MatrixCoefficientsRestrictionEnum { + // Identity + IDENTITY = 0, + // ITU-R BT.709 + ITU_R_BT_709 = 1, + // unspecified + UNSPECIFIED = 2, + // reserved + RESERVED = 3, + // US FCC 73.682 + US_FCC_73_682 = 4, + // ITU-R BT.470BG + ITU_R_BT_470_BG = 5, + // SMPTE 170M + SMPTE_170_M = 6, + // SMPTE 240M + SMPTE_240_M = 7, + // YCoCg + Y_CO_CG = 8, + // BT2020 Non-constant Luminance + BT2020_NON_CONSTANT_LUMINANCE = 9, + // BT2020 Constant Luminance + BT2020_CONSTANT_LUMINANCE = 10, + // SMPTE ST 2085 + SMPTE_ST_2085 = 11, + // Chroma-derived Non-constant Luminance + CHROMA_DERIVED_NON_CONSTANT_LUMINANCE = 12, + // Chroma-derived Constant Luminance + CHROMA_DERIVED_CONSTANT_LUMINANCE = 13, + // ITU-R BT.2100-0 + ITU_R_BT_2100_0 = 14, +} +export const MatrixCoefficientsRestriction = type( + '0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14' +); +export type MatrixCoefficientsRestrictionType = + typeof MatrixCoefficientsRestriction.infer; + +export enum ChromaSitingHorzRestrictionEnum { + // unspecified + UNSPECIFIED = 0, + // left collocated + LEFT_COLLOCATED = 1, + // half + HALF = 2, +} +export const ChromaSitingHorzRestriction = type('0 | 1 | 2'); +export type ChromaSitingHorzRestrictionType = + typeof ChromaSitingHorzRestriction.infer; + +export enum ChromaSitingVertRestrictionEnum { + // unspecified + UNSPECIFIED = 0, + // top collocated + TOP_COLLOCATED = 1, + // half + HALF = 2, +} +export const ChromaSitingVertRestriction = type('0 | 1 | 2'); +export type ChromaSitingVertRestrictionType = + typeof ChromaSitingVertRestriction.infer; + +export enum RangeRestrictionEnum { + // unspecified + UNSPECIFIED = 0, + // broadcast range + BROADCAST_RANGE = 1, + // full range (no clipping) + FULL_RANGE_NO_CLIPPING = 2, + // defined by MatrixCoefficients / TransferCharacteristics + DEFINED_BY_MATRIX_COEFFICIENTS_TRANSFER_CHARACTERISTICS = 3, +} +export const RangeRestriction = type('0 | 1 | 2 | 3'); +export type RangeRestrictionType = typeof RangeRestriction.infer; + +export enum TransferCharacteristicsRestrictionEnum { + // reserved + RESERVED = 0, + // ITU-R BT.709 + ITU_R_BT_709 = 1, + // unspecified + UNSPECIFIED = 2, + // reserved2 + RESERVED2 = 3, + // Gamma 2.2 curve - BT.470M + GAMMA_2_2_CURVE_BT_470_M = 4, + // Gamma 2.8 curve - BT.470BG + GAMMA_2_8_CURVE_BT_470_BG = 5, + // SMPTE 170M + SMPTE_170_M = 6, + // SMPTE 240M + SMPTE_240_M = 7, + // Linear + LINEAR = 8, + // Log + LOG = 9, + // Log Sqrt + LOG_SQRT = 10, + // IEC 61966-2-4 + IEC_61966_2_4 = 11, + // ITU-R BT.1361 Extended Colour Gamut + ITU_R_BT_1361_EXTENDED_COLOUR_GAMUT = 12, + // IEC 61966-2-1 + IEC_61966_2_1 = 13, + // ITU-R BT.2020 10 bit + ITU_R_BT_2020_10_BIT = 14, + // ITU-R BT.2020 12 bit + ITU_R_BT_2020_12_BIT = 15, + // ITU-R BT.2100 Perceptual Quantization + ITU_R_BT_2100_PERCEPTUAL_QUANTIZATION = 16, + // SMPTE ST 428-1 + SMPTE_ST_428_1 = 17, + // ARIB STD-B67 (HLG) + ARIB_STD_B67_HLG = 18, +} +export const TransferCharacteristicsRestriction = type( + '0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18' +); +export type TransferCharacteristicsRestrictionType = + typeof TransferCharacteristicsRestriction.infer; + +export enum PrimariesRestrictionEnum { + // reserved + RESERVED = 0, + // ITU-R BT.709 + ITU_R_BT_709 = 1, + // unspecified + UNSPECIFIED = 2, + // reserved2 + RESERVED2 = 3, + // ITU-R BT.470M + ITU_R_BT_470_M = 4, + // ITU-R BT.470BG - BT.601 625 + ITU_R_BT_470_BG_BT_601_625 = 5, + // ITU-R BT.601 525 - SMPTE 170M + ITU_R_BT_601_525_SMPTE_170_M = 6, + // SMPTE 240M + SMPTE_240_M = 7, + // FILM + FILM = 8, + // ITU-R BT.2020 + ITU_R_BT_2020 = 9, + // SMPTE ST 428-1 + SMPTE_ST_428_1 = 10, + // SMPTE RP 432-2 + SMPTE_RP_432_2 = 11, + // SMPTE EG 432-2 + SMPTE_EG_432_2 = 12, + // EBU Tech. 3213-E - JEDEC P22 phosphors + EBU_TECH_3213_E_JEDEC_P22_PHOSPHORS = 22, +} +export const PrimariesRestriction = type( + '0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 22' +); +export type PrimariesRestrictionType = typeof PrimariesRestriction.infer; + +export const ColourSchema = type({ + MatrixCoefficients: MatrixCoefficientsRestriction.default(2), + BitsPerChannel: type.number.default(0), + ChromaSubsamplingHorz: type.number.optional(), + ChromaSubsamplingVert: type.number.optional(), + CbSubsamplingHorz: type.number.optional(), + CbSubsamplingVert: type.number.optional(), + ChromaSitingHorz: ChromaSitingHorzRestriction.default(0), + ChromaSitingVert: ChromaSitingVertRestriction.default(0), + Range: RangeRestriction.default(0), + TransferCharacteristics: TransferCharacteristicsRestriction.default(2), + Primaries: PrimariesRestriction.default(2), + MaxCLL: type.number.optional(), + MaxFALL: type.number.optional(), + MasteringMetadata: MasteringMetadataSchema.optional(), +}); + +export type ColourType = typeof ColourSchema.infer; + +export enum ProjectionTypeRestrictionEnum { + // rectangular + RECTANGULAR = 0, + // equirectangular + EQUIRECTANGULAR = 1, + // cubemap + CUBEMAP = 2, + // mesh + MESH = 3, +} +export const ProjectionTypeRestriction = type('0 | 1 | 2 | 3'); +export type ProjectionTypeRestrictionType = + typeof ProjectionTypeRestriction.infer; + +export const ProjectionSchema = type({ + ProjectionType: ProjectionTypeRestriction.default(0), + ProjectionPrivate: BinarySchema.optional(), + ProjectionPoseYaw: type.number.default(0), + ProjectionPosePitch: type.number.default(0), + ProjectionPoseRoll: type.number.default(0), +}); + +export type ProjectionType = typeof ProjectionSchema.infer; + +export enum FlagInterlacedRestrictionEnum { + // undetermined + UNDETERMINED = 0, + // interlaced + INTERLACED = 1, + // progressive + PROGRESSIVE = 2, +} +export const FlagInterlacedRestriction = type('0 | 1 | 2'); +export type FlagInterlacedRestrictionType = + typeof FlagInterlacedRestriction.infer; + +export enum FieldOrderRestrictionEnum { + // progressive + PROGRESSIVE = 0, + // tff + TFF = 1, + // undetermined + UNDETERMINED = 2, + // bff + BFF = 6, + // tff (interleaved) + TFF_INTERLEAVED = 9, + // bff (interleaved) + BFF_INTERLEAVED = 14, +} +export const FieldOrderRestriction = type('0 | 1 | 2 | 6 | 9 | 14'); +export type FieldOrderRestrictionType = typeof FieldOrderRestriction.infer; + +export enum StereoModeRestrictionEnum { + // mono + MONO = 0, + // side by side (left eye first) + SIDE_BY_SIDE_LEFT_EYE_FIRST = 1, + // top - bottom (right eye is first) + TOP_BOTTOM_RIGHT_EYE_IS_FIRST = 2, + // top - bottom (left eye is first) + TOP_BOTTOM_LEFT_EYE_IS_FIRST = 3, + // checkboard (right eye is first) + CHECKBOARD_RIGHT_EYE_IS_FIRST = 4, + // checkboard (left eye is first) + CHECKBOARD_LEFT_EYE_IS_FIRST = 5, + // row interleaved (right eye is first) + ROW_INTERLEAVED_RIGHT_EYE_IS_FIRST = 6, + // row interleaved (left eye is first) + ROW_INTERLEAVED_LEFT_EYE_IS_FIRST = 7, + // column interleaved (right eye is first) + COLUMN_INTERLEAVED_RIGHT_EYE_IS_FIRST = 8, + // column interleaved (left eye is first) + COLUMN_INTERLEAVED_LEFT_EYE_IS_FIRST = 9, + // anaglyph (cyan/red) + ANAGLYPH_CYAN_RED = 10, + // side by side (right eye first) + SIDE_BY_SIDE_RIGHT_EYE_FIRST = 11, + // anaglyph (green/magenta) + ANAGLYPH_GREEN_MAGENTA = 12, + // both eyes laced in one Block (left eye is first) + BOTH_EYES_LACED_IN_ONE_BLOCK_LEFT_EYE_IS_FIRST = 13, + // both eyes laced in one Block (right eye is first) + BOTH_EYES_LACED_IN_ONE_BLOCK_RIGHT_EYE_IS_FIRST = 14, +} +export const StereoModeRestriction = type( + '0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14' +); +export type StereoModeRestrictionType = typeof StereoModeRestriction.infer; + +export enum AlphaModeRestrictionEnum { + // none + NONE = 0, + // present + PRESENT = 1, +} +export const AlphaModeRestriction = type('0 | 1'); +export type AlphaModeRestrictionType = typeof AlphaModeRestriction.infer; + +export enum OldStereoModeRestrictionEnum { + // mono + MONO = 0, + // right eye + RIGHT_EYE = 1, + // left eye + LEFT_EYE = 2, + // both eyes + BOTH_EYES = 3, +} +export const OldStereoModeRestriction = type('0 | 1 | 2 | 3'); +export type OldStereoModeRestrictionType = + typeof OldStereoModeRestriction.infer; + +export enum DisplayUnitRestrictionEnum { + // pixels + PIXELS = 0, + // centimeters + CENTIMETERS = 1, + // inches + INCHES = 2, + // display aspect ratio + DISPLAY_ASPECT_RATIO = 3, + // unknown + UNKNOWN = 4, +} +export const DisplayUnitRestriction = type('0 | 1 | 2 | 3 | 4'); +export type DisplayUnitRestrictionType = typeof DisplayUnitRestriction.infer; + +export enum AspectRatioTypeRestrictionEnum { + // free resizing + FREE_RESIZING = 0, + // keep aspect ratio + KEEP_ASPECT_RATIO = 1, + // fixed + FIXED = 2, +} +export const AspectRatioTypeRestriction = type('0 | 1 | 2'); +export type AspectRatioTypeRestrictionType = + typeof AspectRatioTypeRestriction.infer; + +export const VideoSchema = type({ + FlagInterlaced: FlagInterlacedRestriction.default(0), + FieldOrder: FieldOrderRestriction.default(2), + StereoMode: StereoModeRestriction.default(0), + AlphaMode: AlphaModeRestriction.default(0), + OldStereoMode: OldStereoModeRestriction.optional(), + PixelWidth: type.number, + PixelHeight: type.number, + PixelCropBottom: type.number.default(0), + PixelCropTop: type.number.default(0), + PixelCropLeft: type.number.default(0), + PixelCropRight: type.number.default(0), + DisplayWidth: type.number.optional(), + DisplayHeight: type.number.optional(), + DisplayUnit: DisplayUnitRestriction.default(0), + AspectRatioType: AspectRatioTypeRestriction.default(0), + UncompressedFourCC: BinarySchema.optional(), + GammaValue: type.number.optional(), + FrameRate: type.number.optional(), + Colour: ColourSchema.optional(), + Projection: ProjectionSchema.optional(), +}); + +export type VideoType = typeof VideoSchema.infer; + +export enum EmphasisRestrictionEnum { + // No emphasis + NO_EMPHASIS = 0, + // CD audio + CD_AUDIO = 1, + // reserved + RESERVED = 2, + // CCIT J.17 + CCIT_J_17 = 3, + // FM 50 + FM_50 = 4, + // FM 75 + FM_75 = 5, + // Phono RIAA + PHONO_RIAA = 10, + // Phono IEC N78 + PHONO_IEC_N78 = 11, + // Phono TELDEC + PHONO_TELDEC = 12, + // Phono EMI + PHONO_EMI = 13, + // Phono Columbia LP + PHONO_COLUMBIA_LP = 14, + // Phono LONDON + PHONO_LONDON = 15, + // Phono NARTB + PHONO_NARTB = 16, +} +export const EmphasisRestriction = type( + '0 | 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16' +); +export type EmphasisRestrictionType = typeof EmphasisRestriction.infer; + +export const AudioSchema = type({ + SamplingFrequency: type.number.default(0), + OutputSamplingFrequency: type.number.optional(), + Channels: type.number.default(1), + ChannelPositions: BinarySchema.optional(), + BitDepth: type.number.optional(), + Emphasis: EmphasisRestriction.default(0), +}); + +export type AudioType = typeof AudioSchema.infer; + +export enum TrackPlaneTypeRestrictionEnum { + // left eye + LEFT_EYE = 0, + // right eye + RIGHT_EYE = 1, + // background + BACKGROUND = 2, +} +export const TrackPlaneTypeRestriction = type('0 | 1 | 2'); +export type TrackPlaneTypeRestrictionType = + typeof TrackPlaneTypeRestriction.infer; + +export const TrackPlaneSchema = type({ + TrackPlaneUID: type.number, + TrackPlaneType: TrackPlaneTypeRestriction, +}); + +export type TrackPlaneType = typeof TrackPlaneSchema.infer; + +export const TrackCombinePlanesSchema = type({ + TrackPlane: TrackPlaneSchema.array(), +}); + +export type TrackCombinePlanesType = typeof TrackCombinePlanesSchema.infer; + +export const TrackJoinBlocksSchema = type({ + TrackJoinUID: type.number.array(), +}); + +export type TrackJoinBlocksType = typeof TrackJoinBlocksSchema.infer; + +export const TrackOperationSchema = type({ + TrackCombinePlanes: TrackCombinePlanesSchema.optional(), + TrackJoinBlocks: TrackJoinBlocksSchema.optional(), +}); + +export type TrackOperationType = typeof TrackOperationSchema.infer; + +export enum ContentCompAlgoRestrictionEnum { + // zlib + ZLIB = 0, + // bzlib + BZLIB = 1, + // lzo1x + LZO1X = 2, + // Header Stripping + HEADER_STRIPPING = 3, +} +export const ContentCompAlgoRestriction = type('0 | 1 | 2 | 3'); +export type ContentCompAlgoRestrictionType = + typeof ContentCompAlgoRestriction.infer; + +export const ContentCompressionSchema = type({ + ContentCompAlgo: ContentCompAlgoRestriction.default(0), + ContentCompSettings: BinarySchema.optional(), +}); + +export type ContentCompressionType = typeof ContentCompressionSchema.infer; + +export enum AESSettingsCipherModeRestrictionEnum { + // AES-CTR + AES_CTR = 1, + // AES-CBC + AES_CBC = 2, +} +export const AESSettingsCipherModeRestriction = type('1 | 2'); +export type AESSettingsCipherModeRestrictionType = + typeof AESSettingsCipherModeRestriction.infer; + +export const ContentEncAESSettingsSchema = type({ + AESSettingsCipherMode: AESSettingsCipherModeRestriction, +}); + +export type ContentEncAESSettingsType = + typeof ContentEncAESSettingsSchema.infer; + +export enum ContentEncAlgoRestrictionEnum { + // Not encrypted + NOT_ENCRYPTED = 0, + // DES + DES = 1, + // 3DES + _3_DES = 2, + // Twofish + TWOFISH = 3, + // Blowfish + BLOWFISH = 4, + // AES + AES = 5, +} +export const ContentEncAlgoRestriction = type('0 | 1 | 2 | 3 | 4 | 5'); +export type ContentEncAlgoRestrictionType = + typeof ContentEncAlgoRestriction.infer; + +export enum ContentSigAlgoRestrictionEnum { + // Not signed + NOT_SIGNED = 0, + // RSA + RSA = 1, +} +export const ContentSigAlgoRestriction = type('0 | 1'); +export type ContentSigAlgoRestrictionType = + typeof ContentSigAlgoRestriction.infer; + +export enum ContentSigHashAlgoRestrictionEnum { + // Not signed + NOT_SIGNED = 0, + // SHA1-160 + SHA1_160 = 1, + // MD5 + MD5 = 2, +} +export const ContentSigHashAlgoRestriction = type('0 | 1 | 2'); +export type ContentSigHashAlgoRestrictionType = + typeof ContentSigHashAlgoRestriction.infer; + +export const ContentEncryptionSchema = type({ + ContentEncAlgo: ContentEncAlgoRestriction.default(0), + ContentEncKeyID: BinarySchema.optional(), + ContentEncAESSettings: ContentEncAESSettingsSchema.optional(), + ContentSignature: BinarySchema.optional(), + ContentSigKeyID: BinarySchema.optional(), + ContentSigAlgo: ContentSigAlgoRestriction.default(0), + ContentSigHashAlgo: ContentSigHashAlgoRestriction.default(0), +}); + +export type ContentEncryptionType = typeof ContentEncryptionSchema.infer; + +export enum ContentEncodingScopeRestrictionEnum { + // Block + BLOCK = 1, + // Private + PRIVATE = 2, + // Next + NEXT = 4, +} +export const ContentEncodingScopeRestriction = type('1 | 2 | 4'); +export type ContentEncodingScopeRestrictionType = + typeof ContentEncodingScopeRestriction.infer; + +export enum ContentEncodingTypeRestrictionEnum { + // Compression + COMPRESSION = 0, + // Encryption + ENCRYPTION = 1, +} +export const ContentEncodingTypeRestriction = type('0 | 1'); +export type ContentEncodingTypeRestrictionType = + typeof ContentEncodingTypeRestriction.infer; + +export const ContentEncodingSchema = type({ + ContentEncodingOrder: type.number.default(0), + ContentEncodingScope: ContentEncodingScopeRestriction.default(1), + ContentEncodingType: ContentEncodingTypeRestriction.default(0), + ContentCompression: ContentCompressionSchema.optional(), + ContentEncryption: ContentEncryptionSchema.optional(), +}); + +export type ContentEncodingType = typeof ContentEncodingSchema.infer; + +export const ContentEncodingsSchema = type({ + ContentEncoding: ContentEncodingSchema.array(), +}); + +export type ContentEncodingsType = typeof ContentEncodingsSchema.infer; + +export enum TrackTypeRestrictionEnum { + // video + VIDEO = 1, + // audio + AUDIO = 2, + // complex + COMPLEX = 3, + // logo + LOGO = 16, + // subtitle + SUBTITLE = 17, + // buttons + BUTTONS = 18, + // control + CONTROL = 32, + // metadata + METADATA = 33, +} +export const TrackTypeRestriction = type('1 | 2 | 3 | 16 | 17 | 18 | 32 | 33'); +export type TrackTypeRestrictionType = typeof TrackTypeRestriction.infer; + +export const TrackEntrySchema = type({ + TrackNumber: type.number, + TrackUID: type.number, + TrackType: TrackTypeRestriction, + FlagEnabled: type.number.default(1), + FlagDefault: type.number.default(1), + FlagForced: type.number.default(0), + FlagHearingImpaired: type.number.optional(), + FlagVisualImpaired: type.number.optional(), + FlagTextDescriptions: type.number.optional(), + FlagOriginal: type.number.optional(), + FlagCommentary: type.number.optional(), + FlagLacing: type.number.default(1), + MinCache: type.number.default(0), + MaxCache: type.number.optional(), + DefaultDuration: type.number.optional(), + DefaultDecodedFieldDuration: type.number.optional(), + TrackTimestampScale: type.number.default(0), + TrackOffset: type.number.default(0), + MaxBlockAdditionID: type.number.default(0), + BlockAdditionMapping: BlockAdditionMappingSchema.array().optional(), + Name: type.string.optional(), + Language: type.string.default('eng'), + LanguageBCP47: type.string.optional(), + CodecID: type.string, + CodecPrivate: BinarySchema.optional(), + CodecName: type.string.optional(), + AttachmentLink: type.number.optional(), + CodecSettings: type.string.optional(), + CodecInfoURL: type.string.array().optional(), + CodecDownloadURL: type.string.array().optional(), + CodecDecodeAll: type.number.default(1), + TrackOverlay: type.number.array().optional(), + CodecDelay: type.number.default(0), + SeekPreRoll: type.number.default(0), + TrackTranslate: TrackTranslateSchema.array().optional(), + Video: VideoSchema.optional(), + Audio: AudioSchema.optional(), + TrackOperation: TrackOperationSchema.optional(), + TrickTrackUID: type.number.optional(), + TrickTrackSegmentUID: BinarySchema.optional(), + TrickTrackFlag: type.number.default(0), + TrickMasterTrackUID: type.number.optional(), + TrickMasterTrackSegmentUID: BinarySchema.optional(), + ContentEncodings: ContentEncodingsSchema.optional(), +}); + +export type TrackEntryType = typeof TrackEntrySchema.infer; + +export const TracksSchema = type({ + TrackEntry: TrackEntrySchema.array(), +}); + +export type TracksType = typeof TracksSchema.infer; + +export const CueReferenceSchema = type({ + CueRefTime: type.number, + CueRefCluster: type.number, + CueRefNumber: type.number.default(1), + CueRefCodecState: type.number.default(0), +}); + +export type CueReferenceType = typeof CueReferenceSchema.infer; + +export const CueTrackPositionsSchema = type({ + CueTrack: type.number, + CueClusterPosition: type.number, + CueRelativePosition: type.number.optional(), + CueDuration: type.number.optional(), + CueBlockNumber: type.number.optional(), + CueCodecState: type.number.default(0), + CueReference: CueReferenceSchema.array().optional(), +}); + +export type CueTrackPositionsType = typeof CueTrackPositionsSchema.infer; + +export const CuePointSchema = type({ + CueTime: type.number, + CueTrackPositions: CueTrackPositionsSchema.array(), +}); + +export type CuePointType = typeof CuePointSchema.infer; + +export const CuesSchema = type({ + CuePoint: CuePointSchema.array(), +}); + +export type CuesType = typeof CuesSchema.infer; + +export const AttachedFileSchema = type({ + FileDescription: type.string.optional(), + FileName: type.string, + FileMediaType: type.string, + FileData: BinarySchema, + FileUID: type.number, + FileReferral: BinarySchema.optional(), + FileUsedStartTime: type.number.optional(), + FileUsedEndTime: type.number.optional(), +}); + +export type AttachedFileType = typeof AttachedFileSchema.infer; + +export const AttachmentsSchema = type({ + AttachedFile: AttachedFileSchema.array(), +}); + +export type AttachmentsType = typeof AttachmentsSchema.infer; + +export const EditionDisplaySchema = type({ + EditionString: type.string, + EditionLanguageIETF: type.string.array().optional(), +}); + +export type EditionDisplayType = typeof EditionDisplaySchema.infer; + +export const EditionEntrySchema = type({ + EditionUID: type.number.optional(), + EditionFlagHidden: type.number.default(0), + EditionFlagDefault: type.number.default(0), + EditionFlagOrdered: type.number.default(0), + EditionDisplay: EditionDisplaySchema.array().optional(), +}); + +export type EditionEntryType = typeof EditionEntrySchema.infer; + +export const ChaptersSchema = type({ + EditionEntry: EditionEntrySchema.array(), +}); + +export type ChaptersType = typeof ChaptersSchema.infer; + +export const TagTrackUIDSchema = match({ + 'number[]': (v) => (v.length > 0 ? v : [0]), + undefined: () => [0], + default: 'assert', +}); + +export const TagEditionUIDSchema = match({ + 'number[]': (v) => (v.length > 0 ? v : [0]), + undefined: () => [0], + default: 'assert', +}); + +export const TagChapterUIDSchema = match({ + 'number[]': (v) => (v.length > 0 ? v : [0]), + undefined: () => [0], + default: 'assert', +}); + +export const TagAttachmentUIDSchema = match({ + 'number[]': (v) => (v.length > 0 ? v : [0]), + undefined: () => [0], + default: 'assert', +}); + +export enum TargetTypeValueRestrictionEnum { + // SHOT + SHOT = 10, + // SUBTRACK / MOVEMENT / SCENE + SUBTRACK_MOVEMENT_SCENE = 20, + // TRACK / SONG / CHAPTER + TRACK_SONG_CHAPTER = 30, + // PART / SESSION + PART_SESSION = 40, + // ALBUM / OPERA / CONCERT / MOVIE / EPISODE + ALBUM_OPERA_CONCERT_MOVIE_EPISODE = 50, + // EDITION / ISSUE / VOLUME / OPUS / SEASON / SEQUEL + EDITION_ISSUE_VOLUME_OPUS_SEASON_SEQUEL = 60, + // COLLECTION + COLLECTION = 70, +} +export const TargetTypeValueRestriction = type( + '10 | 20 | 30 | 40 | 50 | 60 | 70' +); +export type TargetTypeValueRestrictionType = + typeof TargetTypeValueRestriction.infer; + +export enum TargetTypeRestrictionEnum { + // TargetTypeValue 70 + COLLECTION = 'COLLECTION', + // TargetTypeValue 60 + EDITION = 'EDITION', + // TargetTypeValue 60 + ISSUE = 'ISSUE', + // TargetTypeValue 60 + VOLUME = 'VOLUME', + // TargetTypeValue 60 + OPUS = 'OPUS', + // TargetTypeValue 60 + SEASON = 'SEASON', + // TargetTypeValue 60 + SEQUEL = 'SEQUEL', + // TargetTypeValue 50 + ALBUM = 'ALBUM', + // TargetTypeValue 50 + OPERA = 'OPERA', + // TargetTypeValue 50 + CONCERT = 'CONCERT', + // TargetTypeValue 50 + MOVIE = 'MOVIE', + // TargetTypeValue 50 + EPISODE = 'EPISODE', + // TargetTypeValue 40 + PART = 'PART', + // TargetTypeValue 40 + SESSION = 'SESSION', + // TargetTypeValue 30 + TRACK = 'TRACK', + // TargetTypeValue 30 + SONG = 'SONG', + // TargetTypeValue 30 + CHAPTER = 'CHAPTER', + // TargetTypeValue 20 + SUBTRACK = 'SUBTRACK', + // TargetTypeValue 20 + MOVEMENT = 'MOVEMENT', + // TargetTypeValue 20 + SCENE = 'SCENE', + // TargetTypeValue 10 + SHOT = 'SHOT', +} +export const TargetTypeRestriction = type( + '"COLLECTION" | "EDITION" | "ISSUE" | "VOLUME" | "OPUS" | "SEASON" | "SEQUEL" | "ALBUM" | "OPERA" | "CONCERT" | "MOVIE" | "EPISODE" | "PART" | "SESSION" | "TRACK" | "SONG" | "CHAPTER" | "SUBTRACK" | "MOVEMENT" | "SCENE" | "SHOT"' +); +export type TargetTypeRestrictionType = typeof TargetTypeRestriction.infer; + +export const TargetsSchema = type({ + TargetTypeValue: TargetTypeValueRestriction.default(50), + TargetType: TargetTypeRestriction.optional(), + TagTrackUID: TagTrackUIDSchema, + TagEditionUID: TagEditionUIDSchema, + TagChapterUID: TagChapterUIDSchema, + TagAttachmentUID: TagAttachmentUIDSchema, +}); + +export type TargetsType = typeof TargetsSchema.infer; + +export const TagSchema = type({ + Targets: TargetsSchema, +}); + +export type TagType = typeof TagSchema.infer; + +export const TagsSchema = type({ + Tag: TagSchema.array(), +}); + +export type TagsType = typeof TagsSchema.infer; + +export const SegmentSchema = type({ + SeekHead: SeekHeadSchema.array().optional(), + Info: InfoSchema, + Cluster: ClusterSchema.array().optional(), + Tracks: TracksSchema.optional(), + Cues: CuesSchema.optional(), + Attachments: AttachmentsSchema.optional(), + Chapters: ChaptersSchema.optional(), + Tags: TagsSchema.array().optional(), +}); + +export type SegmentType = typeof SegmentSchema.infer; + +export const IdMultiSet = new Set([ + EbmlTagIdEnum.DocTypeExtension, + EbmlTagIdEnum.Seek, + EbmlTagIdEnum.ChapterTranslateEditionUID, + EbmlTagIdEnum.SegmentFamily, + EbmlTagIdEnum.ChapterTranslate, + EbmlTagIdEnum.SilentTrackNumber, + EbmlTagIdEnum.BlockMore, + EbmlTagIdEnum.TimeSlice, + EbmlTagIdEnum.ReferenceBlock, + EbmlTagIdEnum.SimpleBlock, + EbmlTagIdEnum.BlockGroup, + EbmlTagIdEnum.EncryptedBlock, + EbmlTagIdEnum.TrackTranslateEditionUID, + EbmlTagIdEnum.TrackPlane, + EbmlTagIdEnum.TrackJoinUID, + EbmlTagIdEnum.ContentEncoding, + EbmlTagIdEnum.BlockAdditionMapping, + EbmlTagIdEnum.CodecInfoURL, + EbmlTagIdEnum.CodecDownloadURL, + EbmlTagIdEnum.TrackOverlay, + EbmlTagIdEnum.TrackTranslate, + EbmlTagIdEnum.TrackEntry, + EbmlTagIdEnum.CueReference, + EbmlTagIdEnum.CueTrackPositions, + EbmlTagIdEnum.CuePoint, + EbmlTagIdEnum.AttachedFile, + EbmlTagIdEnum.EditionLanguageIETF, + EbmlTagIdEnum.EditionDisplay, + EbmlTagIdEnum.EditionEntry, + EbmlTagIdEnum.TagTrackUID, + EbmlTagIdEnum.TagEditionUID, + EbmlTagIdEnum.TagChapterUID, + EbmlTagIdEnum.TagAttachmentUID, + EbmlTagIdEnum.Tag, + EbmlTagIdEnum.SeekHead, + EbmlTagIdEnum.Cluster, + EbmlTagIdEnum.Tags, +]); diff --git a/apps/playground/src/media/mkv/util.ts b/apps/playground/src/media/mkv/util.ts index 5757d39..2b5c10e 100644 --- a/apps/playground/src/media/mkv/util.ts +++ b/apps/playground/src/media/mkv/util.ts @@ -1,68 +1,12 @@ import type { Type } from 'arktype'; -import type { EbmlMasterTagType, EbmlTagIdEnum, EbmlTagType } from 'konoebml'; +import { EbmlElementType, EbmlTagIdEnum, type EbmlTagType } from 'konoebml'; +import { IdMultiSet } from './schema'; export type InferType = T extends Type ? U : never; -export interface TagWithArktypeOptions< - T extends EbmlTagType, - S extends Type, -> { - id: T['id']; - schema: S; - extract: (tag: T, schema: S) => InferType; -} - -export type TagWithArktypeClassInstance< - T extends EbmlTagType, - S extends Type, -> = InferType & { - tag: T; -}; - -export interface TagWithArktypeClass< - T extends EbmlTagType, - S extends Type, -> { - new (tag: T, validatedTag: InferType): TagWithArktypeClassInstance; - - fromTag>( - this: new ( - tag: T, - validatedTag: InferType - ) => TagWithArktypeClassInstance, - tag: T - ): R; - - id: T['id']; - schema: S; -} - -export function TagWithArktype>({ - id, - schema, - extract, -}: TagWithArktypeOptions): TagWithArktypeClass { - const tagWithArktypeImpl = class TagWithArktypeImpl { - static id = id; - static schema = schema; - - tag: T; - - constructor(tag: T, validatedTag: InferType) { - Object.assign(this, validatedTag); - this.tag = tag; - } - - static fromTag(tag: T) { - const extractedData = extract(tag, schema); - const validatedExtractedData = schema(extractedData); - // biome-ignore lint/complexity/noThisInStatic: - return new this(tag, validatedExtractedData); - } - }; - - return tagWithArktypeImpl as unknown as TagWithArktypeClass; -} +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 type PredicateIdExtract = Extract; @@ -86,56 +30,27 @@ export function isTagPos< return (tag: T): tag is PredicatePositionExtract => pos === '*' || pos === tag.position; } -export type MasterChildExtractMap = { - [id in EbmlTagIdEnum]?: K extends keyof T - ? - | { - key: K; - multi: true; - extract: ( - tag: Extract - ) => T[K] extends Array ? U : never; - } - | { - key: K; - multi?: false; - extract: (tag: Extract) => T[K]; - } - : never; -}; -export function simpleMasterExtractor< - T extends EbmlMasterTagType, - S extends Type, - EM extends MasterChildExtractMap, keyof InferType>, ->(map: EM) { - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: - return (tag: T, _schema: S): InferType => { - if (!tag?.children?.length) { - return {} as unknown as InferType; - } - const value = {} as Record; - for (const c of tag.children) { - const entry = ( - map as unknown as Record< - string, - { id: number; multi: boolean; extract: (tag: any) => any } - > - )[c.id as number] as any; - if (entry?.key) { - const key = entry.key; - const item = entry.extract ? entry.extract(c) : c.data; - if (entry.multi) { - if (value[key]) { - value[key].push(item); - } else { - value[key] = [item]; - } +export function convertEbmlTagToModelShape(tag: EbmlTagType) { + if (tag.type === EbmlElementType.Master) { + const obj: Record = {}; + const children = tag.children; + for (const c of children) { + const name = EbmlTagIdEnum[c.id]; + const converted = convertEbmlTagToModelShape(c); + if (IdMultiSet.has(c.id)) { + if (obj[name]) { + obj[name].push(converted); } else { - value[key] = item; + obj[name] = [converted]; } + } else { + obj[name] = converted; } } - return value as unknown as InferType; - }; + } + if (tag.id === EbmlTagIdEnum.SimpleBlock || tag.id === EbmlTagIdEnum.Block) { + return tag; + } + return tag.data; } diff --git a/assets/specification/ebml.xml b/assets/specification/ebml.xml new file mode 100644 index 0000000..5518827 --- /dev/null +++ b/assets/specification/ebml.xml @@ -0,0 +1,77 @@ + + + Set the EBML characteristics of the data to + follow. Each EBML document has to start with this. + + + The version of EBML parser used to create the + file. + + + The minimum EBML version a parser has to + support to read this file. + + + The maximum length of the IDs you'll find in + this file (4 or less in Matroska). + + + 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. + + + A string that describes the type of document + that follows this EBML header, for example 'matroska' or 'webm'. + + + The version of DocType interpreter used to + create the file. + + + The minimum DocType version an interpreter has + to support to read this file. + + + 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. + + + 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. + + + 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. + + + + 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. + + + 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. + + \ No newline at end of file diff --git a/assets/specification/ebml_mkv.xml b/assets/specification/ebml_mkv.xml new file mode 100644 index 0000000..80dd5c3 --- /dev/null +++ b/assets/specification/ebml_mkv.xml @@ -0,0 +1,2477 @@ + + + + + + + The `Root Element` that contains all other + `Top-Level Elements`; see (#data-layout). + + + + Contains seeking information of `Top-Level + Elements`; see (#data-layout). + + + + Contains a single seek entry to an EBML + Element. + + + + The binary EBML ID of a `Top-Level Element`. + + + + The `Segment Position` ((#segment-position)) + of a `Top-Level Element`. + + + + Contains general information about the + `Segment`. + + + + A randomly generated UID that identifies the + `Segment` amongst many others (128 bits). It is equivalent to a Universally Unique + Identifier (UUID) v4 [@!RFC9562] with all bits randomly (or pseudorandomly) chosen. An + actual UUID v4 value, where some bits are not random, **MAY** also be used. + If the `Segment` is a part of a `Linked + Segment`, then this element is **REQUIRED**. + The value of the UID **MUST** contain at least one bit set to 1. + + + + A filename corresponding to this `Segment`. + + + An ID that identifies the previous `Segment` + of a `Linked Segment`. + If the `Segment` is a part of a `Linked + Segment` that uses + Hard Linking ((#hard-linking)), then either the + `PrevUUID` or the `NextUUID` element is + **REQUIRED**. If a `Segment` contains a `PrevUUID` + but not a `NextUUID`, then it **MAY** be considered as the + last `Segment` of the `Linked Segment`. The `PrevUUID` + **MUST NOT** be equal to the `SegmentUUID`. + + + + A filename corresponding to the file of the + previous `Linked Segment`. + Provision of the previous filename is for + display convenience, + but `PrevUUID` **SHOULD** be considered authoritative for identifying the previous + `Segment` in a `Linked Segment`. + + + An ID that identifies the next `Segment` of a + `Linked Segment`. + If the `Segment` is a part of a `Linked + Segment` that uses Hard Linking ((#hard-linking)), + then either the `PrevUUID` or the `NextUUID` element is **REQUIRED**. If a `Segment` + contains a `NextUUID` but not a `PrevUUID`, then it **MAY** be considered as the first + `Segment` of the `Linked Segment`. The `NextUUID` **MUST NOT** be equal to the + `SegmentUUID`. + + + + A filename corresponding to the file of the + next `Linked Segment`. + Provision of the next filename is for display + convenience, + but `NextUUID` **SHOULD** be considered authoritative for identifying the Next + `Segment`. + + + A UID that all `Segments` of a `Linked + Segment` **MUST** share (128 bits). It is equivalent to a UUID v4 [@!RFC9562] with all + bits randomly (or pseudorandomly) chosen. An actual UUID v4 value, where some bits are + not random, **MAY** also be used. + If the `Segment` `Info` contains a + `ChapterTranslate` element, this element is **REQUIRED**. + + + The mapping between this `Segment` and a + segment value in the given Chapter Codec. + Chapter Codecs may need to address different + segments, but they may not know of the way to identify such segments when stored in + Matroska. + This element and its child elements add a way to map the internal segments known to the + Chapter Codec to the `SegmentUUID`s in Matroska. + This allows remuxing a file with Chapter Codec without changing the content of the codec + data, just the `Segment` mapping. + + + The binary value used to represent this + `Segment` in the chapter codec data. + The format depends on the `ChapProcessCodecID` used; see (#chapprocesscodecid-element). + + + Applies to the chapter codec of the given + chapter edition(s); see (#chapprocesscodecid-element). + + + + Specifies a chapter edition UID to which this + `ChapterTranslate` applies. + When no `ChapterTranslateEditionUID` is + specified in the `ChapterTranslate`, the `ChapterTranslate` applies to all chapter + editions found in the `Segment` using the given `ChapterTranslateCodec`. + + + Base unit for Segment Ticks and Track Ticks, + in nanoseconds. A `TimestampScale` value of 1000000 means scaled timestamps in the + `Segment` are expressed in milliseconds; see (#timestamps) on how to interpret + timestamps. + + + + + Duration of the `Segment`, expressed in + `Segment` Ticks, which are based on `TimestampScale`; see (#timestamp-ticks). + + + + The date and time that the `Segment` was + created by the muxing application or library. + + + + General name of the `Segment`. + + + + Muxing application or library (example: + "libmatroska-0.4.3"). + Include the full name of the application or + library followed by the version number. + + + + Writing application (example: + "mkvmerge-0.3.3"). + Include the full name of the application + followed by the version number. + + + + The `Top-Level Element` containing the + (monolithic) `Block` structure. + + + + Absolute timestamp of the cluster, expressed + in Segment Ticks, which are based on `TimestampScale`; see (#timestamp-ticks). + This element **SHOULD** be the first child + element of the `Cluster` it belongs to + or the second if that `Cluster` contains a `CRC-32` element ((#crc-32)). + + + + + The list of tracks that are not used in that + part of the stream. + It is useful when using overlay tracks for seeking or deciding what track to use. + + + + One of the track numbers that is not used from + now on in the stream. + It could change later if not specified as silent in a further `Cluster`. + + + + The `Segment Position` of the `Cluster` in the + `Segment` (0 in live streams). + It might help to resynchronize the offset on damaged streams. + + + + Size of the previous `Cluster`, in octets. Can + be useful for backward playing. + + + + + Similar to `Block` (see (#block-structure)) + but without all the extra information. + Mostly used to reduce overhead when no extra feature is needed; see + (#simpleblock-structure) on `SimpleBlock` Structure. + + + + + Basic container of information containing a + single `Block` and information specific to that `Block`. + + + + `Block` containing the actual data to be + rendered and a timestamp relative to the `Cluster` Timestamp; + see (#block-structure) on `Block` Structure. + + + + A `Block` with no data. It must be stored in + the stream at the place the real `Block` would be in display order. + + + + Contains additional binary data to complete + the `Block` element; see [@?I-D.ietf-cellar-codec, section 4.1.5] for more information. + An EBML parser that has no knowledge of the `Block` structure could still see and + use/skip these data. + + + + Contains the `BlockAdditional` and some + parameters. + + + + Interpreted by the codec as it wishes (using + the `BlockAddID`). + + + + An ID that identifies how to interpret the + `BlockAdditional` data; see [@?I-D.ietf-cellar-codec, section 4.1.5] for + more information. A value of 1 indicates that the `BlockAdditional` data is + defined by the codec. Any other value indicates that the `BlockAdditional` data + should be handled according to the `BlockAddIDType` that is located in the + `TrackEntry`. + Each `BlockAddID` value **MUST** be unique + between all `BlockMore` elements found in a `BlockAdditions` element. To keep + `MaxBlockAdditionID` as low as possible, small values **SHOULD** be used. + + + + The duration of the `Block`, expressed in + Track Ticks; see (#timestamp-ticks). + The `BlockDuration` element can be useful + at the end of a `Track` to define the duration of the last frame (as + there is no subsequent `Block` available) or when there is a break in a + track like for subtitle tracks. + `BlockDuration` **MUST** be set + (minOccurs=1) if the associated `TrackEntry` stores a `DefaultDuration` value. + If a value is not present and no + `DefaultDuration` is defined, the value is assumed to be the difference between the + timestamp of this `Block` and the timestamp of the next `Block` in "display" order (not + coding order). + + + + This frame is referenced and has the specified + cache priority. + In the cache, only a frame of the same or higher priority can replace this frame. A + value of 0 means the frame is not referenced. + + + A timestamp value, relative to the timestamp + of the `Block` in this `BlockGroup`, expressed in Track Ticks; see (#timestamp-ticks). + This is used to reference other frames necessary to decode this frame. + The relative value **SHOULD** correspond to a valid `Block` that this `Block` depends + on. + Historically, `Matroska Writers` didn't write the actual `Block(s)` that this `Block` + depends on, but they did write *some* `Block(s)` in the past. + + The value "0" **MAY** also be used to signify that this `Block` cannot be decoded on its + own, but the necessary reference `Block(s)` is unknown. In this case, other + `ReferenceBlock` elements **MUST NOT** be found in the same `BlockGroup`. If the + `BlockGroup` doesn't have a `ReferenceBlock` element, then the `Block` it contains can + be decoded without using any other `Block` data. + + + + The `Segment Position` of the data that would + otherwise be in position of the virtual block. + + + The new codec state to use. Data + interpretation is private to the codec. + This information **SHOULD** always be referenced by a seek entry. + + + Duration of the silent data added to the + `Block`, expressed in + Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks) + (padding at the end of the `Block` for positive values and at the + beginning of the `Block` for negative values). The duration of + `DiscardPadding` is not calculated in the duration of the + `TrackEntry` and **SHOULD** be discarded during + playback. + + + + Contains slices description. + + + Contains extra time information about the data + contained in the `Block`. + Being able to interpret this element is not required for playback. + + + The reverse number of the frame in the lace (0 + is the last frame, 1 is the next to last, etc.). + Being able to interpret this element is not required for playback. + + + + The number of the frame to generate from this + lace with this delay + (allows for the generation of many frames from the same Block/Frame). + + + + The ID of the `BlockAdditional` element (0 is + the main `Block`). + + + + The delay to apply to the element, expressed + in Track Ticks; see (#timestamp-ticks). + + + + The duration to apply to the element, + expressed in Track Ticks; see (#timestamp-ticks). + + + Contains information about the last reference + frame. See [@?DivXTrickTrack]. + + + + The relative offset, in bytes, from the + previous `BlockGroup` element for this Smooth FF/RW video track to the containing + `BlockGroup` + element. See [@?DivXTrickTrack]. + + + + The timestamp of the `BlockGroup` pointed to + by ReferenceOffset, expressed in Track Ticks; see (#timestamp-ticks). See + [@?DivXTrickTrack]. + + + + + Similar to `SimpleBlock` (see + (#simpleblock-structure)), + but the data inside the `Block` are Transformed (encrypted and/or signed). + + + A `Top-Level Element` of information with many + tracks described. + + + + Describes a track with all elements. + + + + The track number as used in the `Block` + Header. + + + + A UID that identifies the `Track`. + + + + + The `TrackType` defines the type of each frame + found in the `Track`. + The value **SHOULD** be stored on 1 octet. + + + + An image. + + + Audio samples. + + + A mix of different other `TrackType`. + The codec needs to define how the `Matroska Player` should interpret such data. + + + An image to be rendered over the video + track(s). + + + Subtitle or closed caption data to be + rendered over the video track(s). + + + Interactive button(s) to be rendered + over the video track(s). + + + Metadata used to control the player of + the `Matroska Player`. + + + Timed metadata that can be passed on + to the `Matroska Player`. + + + + + + + Set to 1 if the track is usable. It is + possible to turn a track that is not usable into a usable track using chapter codecs or + control tracks. + + + + + Set to 1 if the track (audio, video, or + subtitles) is eligible for automatic selection by the player; see + (#default-track-selection) for more details. + + + + + Applies only to subtitles. Set to 1 if the + track is eligible for automatic selection by the player if it matches the user's + language preference, + even if the user's preferences would not normally enable subtitles with the selected + audio track; + this can be used for tracks containing only translations of audio in foreign languages + or on-screen text. + See (#default-track-selection) for more details. + + + + + Set to 1 if and only if the track is suitable + for users with hearing impairments. + + + Set to 1 if and only if the track is suitable + for users with visual impairments. + + + Set to 1 if and only if the track contains + textual descriptions of video content. + + + Set to 1 if and only if the track is in the + content's original language. + + + Set to 1 if and only if the track contains + commentary. + + + Set to 1 if the track **MAY** contain blocks + that use lacing. + When set to 0, all blocks **MUST** have their lacing flags set to "no lacing"; see + (#block-lacing) on 'Block' Lacing. + + + + + 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. + + + + The maximum cache size necessary to store + referenced frames in and the current frame. + 0 means no cache is needed. + + + + Number of nanoseconds per frame, expressed in + Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks) + ("frame" in the Matroska sense -- one element put into a (Simple)Block). + + + + + + The period between two successive fields at + the output of the decoding process, expressed in Matroska Ticks -- i.e., in nanoseconds; + see (#timestamp-ticks). + See (#defaultdecodedfieldduration) for more information. + + + + + 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). + + + + + A value to add to the `Block`'s Timestamp, + expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks). + This can be used to adjust the playback offset of a track. + + + + The maximum value of `BlockAddID` + ((#blockaddid-element)). + A value of 0 means there is no `BlockAdditions` ((#blockadditions-element)) for this + track. + + + Contains elements that extend the track format + by adding content either to each frame, + with `BlockAddID` ((#blockaddid-element)), or to the track as a whole + with `BlockAddIDExtraData`. + + + If the track format extension needs content + beside frames, + the value refers to the `BlockAddID` ((#blockaddid-element)) value being described. + To keep `MaxBlockAdditionID` as low as + possible, small values **SHOULD** be used. + + + A human-friendly name describing the type of + `BlockAdditional` data, + as defined by the associated `Block Additional Mapping`. + + + Stores the registered identifier of the `Block + Additional Mapping` + to define how the `BlockAdditional` data should be handled. + If `BlockAddIDType` is 0, the + `BlockAddIDValue` and corresponding `BlockAddID` values **MUST** be 1. + + + Extra binary data that the `BlockAddIDType` + can use to interpret the `BlockAdditional` data. + The interpretation of the binary data depends on the `BlockAddIDType` value and the + corresponding `Block Additional Mapping`. + + + A human-readable track name. + + + + + The language of the track, + in the Matroska languages form; see (#language-codes) on language codes. + This element **MUST** be ignored if the `LanguageBCP47` element is used in the same + `TrackEntry`. + + + + + The language of the track, + in the form defined in [@!RFC5646]; see (#language-codes) on language codes. + If this element is used, then any `Language` elements used in the same `TrackEntry` + **MUST** be ignored. + + + + An ID corresponding to the codec; + see [@?I-D.ietf-cellar-codec] for more info. + + + + + Private data only known to the codec. + + + + + A human-readable string specifying the codec. + + + + The UID of an attachment that is used by this + codec. + The value **MUST** match the `FileUID` value + of an attachment found in this `Segment`. + + + + A string describing the encoding setting used. + + + A URL to find information about the codec + used. + + + A URL to download information about the codec + used. + + + Set to 1 if the codec can decode potentially + damaged data. + + + Specify that this track is an overlay track + for the `Track` specified (in the u-integer). + This means that when this track has a gap on `SilentTracks`, the overlay track should be + used instead. The order of multiple `TrackOverlay` matters; the first one is the one + that should be used. + If the first one is not found, it should be the second, etc. + + + The built-in delay for the codec, expressed in + Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks). + It represents the number of codec samples that will be discarded by the decoder during + playback. + This timestamp value **MUST** be subtracted from each frame timestamp in order to get + the timestamp that will be actually played. + The value **SHOULD** be small so the muxing of tracks with the same actual timestamp are + in the same `Cluster`. + + + + + After a discontinuity, the duration of the + data + that the decoder **MUST** decode before the decoded data is valid, expressed in Matroska + Ticks -- i.e., in nanoseconds; see (#timestamp-ticks). + + + + + The mapping between this `TrackEntry` and a + track value in the given Chapter Codec. + Chapter Codecs may need to address content in a + specific track, but they may not know of the way to identify tracks in Matroska. + This element and its child elements add a way to map the internal tracks known to the + Chapter Codec to the track IDs in Matroska. + This allows remuxing a file with Chapter Codec without changing the content of the codec + data, just the track mapping. + + + The binary value used to represent this + `TrackEntry` in the chapter codec data. + The format depends on the `ChapProcessCodecID` used; see (#chapprocesscodecid-element). + + + Applies to the chapter codec of the given + chapter edition(s); see (#chapprocesscodecid-element). + + + + Specifies a chapter edition UID to which this + `TrackTranslate` applies. + When no `TrackTranslateEditionUID` is + specified in the `TrackTranslate`, the `TrackTranslate` applies to all chapter editions + found in the `Segment` using the given `TrackTranslateCodec`. + + + Video settings. + + + + + Specifies whether the video frames in this + track are interlaced. + + + Unknown status. + This value **SHOULD** be avoided. + + + Interlaced frames. + + + No interlacing. + + + + + + + + Specifies the field ordering of video frames + in this track. + If `FlagInterlaced` is not set to 1, this + element **MUST** be ignored. + + + Interlaced frames. + This value **SHOULD** be avoided; + setting `FlagInterlaced` to 2 is sufficient. + + + Top field displayed first. Top field + stored first. + + + Unknown field order. + This value **SHOULD** be avoided. + + + Bottom field displayed first. Bottom + field stored first. + + + Top field displayed first. Fields are + interleaved in storage with the top line of the top field stored first. + + + Bottom field displayed first. Fields + are interleaved in storage with the top line of the top field stored first. + + + + + + + Stereo-3D video mode. See + (#multi-planar-and-3d-videos) for more details. + + + + + + + + + + + + + + + + + + + + + + + + Indicates whether the `BlockAdditional` + element with `BlockAddID` of "1" + contains Alpha data as defined by the Codec Mapping for the `CodecID`. + Undefined values (i.e., values other than 0 or 1) **SHOULD NOT** be used, as the + behavior of known implementations is different. + + + + The `BlockAdditional` element with + `BlockAddID` of "1" does not exist or **SHOULD NOT** be considered as containing + such data. + + + The `BlockAdditional` element with + `BlockAddID` of "1" contains alpha channel data. + + + + + + + + + Bogus `StereoMode` value used in old versions + of [@?libmatroska]. + This element **MUST NOT** be used. It was an + incorrect value used in libmatroska up to 0.9.0. + + + + + + + + + Width of the encoded video frames in pixels. + + + + + + Height of the encoded video frames in pixels. + + + + + + The number of video pixels to remove at the + bottom of the image. + + + + + + The number of video pixels to remove at the + top of the image. + + + + + + The number of video pixels to remove on the + left of the image. + + + + + + The number of video pixels to remove on the + right of the image. + + + + + + Width of the video frames to display. Applies + to the video frame after cropping (PixelCrop* Elements). + If the DisplayUnit of the same `TrackEntry` is + 0, then the default value for `DisplayWidth` is equal to `PixelWidth` - `PixelCropLeft` + - `PixelCropRight`; else, there is no default value. + + + + + + Height of the video frames to display. Applies + to the video frame after cropping (PixelCrop* Elements). + If the DisplayUnit of the same `TrackEntry` is + 0, then the default value for `DisplayHeight` is equal to `PixelHeight` - `PixelCropTop` + - `PixelCropBottom`; else, there is no default value. + + + + + + How `DisplayWidth` and `DisplayHeight` are + interpreted. + + + + + + + + + + + + + Specifies the possible modifications to the + aspect ratio. + + + + + + + + + Specifies the uncompressed pixel format used + for the `Track`'s data as a FourCC. + This value is similar in scope to the biCompression value of AVI's `BITMAPINFO` + [@?AVIFormat]. There is neither a definitive list of FourCC values nor an official + registry. Some common values for YUV pixel formats can be found at [@?MSYUV8], + [@?MSYUV16], and [@?FourCC-YUV]. Some common values for uncompressed RGB pixel formats + can be found at [@?MSRGB] and [@?FourCC-RGB]. + UncompressedFourCC **MUST** be set + (minOccurs=1) in `TrackEntry` when the `CodecID` element of the `TrackEntry` is set to + "V_UNCOMPRESSED". + + + + + Gamma value. + + + + Number of frames per second. This value is + informational only. It is intended for constant frame rate streams and should not be + used for a variable frame rate `TrackEntry`. + + + + Settings describing the color format. + + + + + + The Matrix Coefficients of the video used to + derive luma and chroma values from red, green, and blue color primaries. + For clarity, the value and meanings for `MatrixCoefficients` are adopted from Table 4 of + [@!ITU-H.273]. + + + + + + + + + + + + + + + + + + + + + + + Number of decoded bits per channel. A value of + 0 indicates that the `BitsPerChannel` is unspecified. + + + + + + The number of pixels to remove in the Cr and + Cb channels for every pixel not removed horizontally. Example: For video with 4:2:0 + chroma subsampling, the `ChromaSubsamplingHorz` + **SHOULD** be set to 1. + + + + + + The number of pixels to remove in the Cr and + Cb channels for every pixel not removed vertically. + Example: For video with 4:2:0 chroma subsampling, the `ChromaSubsamplingVert` + **SHOULD** be set to 1. + + + + + + The number of pixels to remove in the Cb + channel for every pixel not removed horizontally. + This is additive with `ChromaSubsamplingHorz`. + Example: For video with 4:2:1 chroma + subsampling, the `ChromaSubsamplingHorz` **SHOULD** be set to 1, and `CbSubsamplingHorz` + **SHOULD** be set to 1. + + + + + + The number of pixels to remove in the Cb + channel for every pixel not removed vertically. + This is additive with `ChromaSubsamplingVert`. + + + + + + How chroma is subsampled horizontally. + + + + + + + + + + + + How chroma is subsampled vertically. + + + + + + + + + + + + Clipping of the color ranges. + + + + + + + + + + + + + The transfer characteristics of the video. For + clarity, + the value and meanings for `TransferCharacteristics` are adopted from Table 3 of + [@!ITU-H.273]. + + + + + + + + + + + + + + + + + + + + + + + + + + + The color primaries of the video. For clarity, + the value and meanings for `Primaries` are adopted from Table 2 of [@!ITU-H.273]. + + + + + + + + + + + + + + + + + + + + + + Maximum brightness of a single pixel (Maximum + Content Light Level) + in candelas per square meter (cd/m^2^). + + + + + + Maximum brightness of a single full frame + (Maximum Frame-Average Light Level) + in candelas per square meter (cd/m^2^). + + + + + + SMPTE 2086 mastering data. + + + + + + Red X chromaticity coordinate, as defined by + [@!CIE-1931]. + + + + + + Red Y chromaticity coordinate, as defined by + [@!CIE-1931]. + + + + + + Green X chromaticity coordinate, as defined by + [@!CIE-1931]. + + + + + + Green Y chromaticity coordinate, as defined by + [@!CIE-1931]. + + + + + + Blue X chromaticity coordinate, as defined by + [@!CIE-1931]. + + + + + + Blue Y chromaticity coordinate, as defined by + [@!CIE-1931]. + + + + + + White X chromaticity coordinate, as defined by + [@!CIE-1931]. + + + + + + White Y chromaticity coordinate, as defined by + [@!CIE-1931]. + + + + + + Maximum luminance. Represented in candelas per + square meter (cd/m^2^). + + + + + + Minimum luminance. Represented in candelas per + square meter (cd/m^2^). + + + + + + Describes the video projection details. Used + to render spherical or VR videos or to flip videos horizontally or vertically. + + + + + + Describes the projection used for this video + track. + + + + + + + + + + + + + Private data that only applies to a specific + projection. + * If `ProjectionType` equals 0 (rectangular), + then this element **MUST NOT** be present. + * If `ProjectionType` equals 1 (equirectangular), then this element **MUST** be present + and contain the same binary data that would be stored inside + an ISOBMFF Equirectangular Projection Box ("equi"). + * If `ProjectionType` equals 2 (cubemap), then this element **MUST** be present and + contain the same binary data that would be stored + inside an ISOBMFF Cubemap Projection Box ("cbmp"). + * If `ProjectionType` equals 3 (mesh), then this element **MUST** be present and contain + the same binary data that would be stored inside + an ISOBMFF Mesh Projection Box ("mshp"). + ISOBMFF box size and FourCC fields are not + included in the binary data, + but the FullBox version and flag fields are. This is to avoid + redundant framing information while preserving versioning and semantics between the two + container formats. + + + + + + Specifies a yaw rotation to the projection. + Value represents a clockwise rotation, in degrees, around the up vector. This rotation + must be applied + before any `ProjectionPosePitch` or `ProjectionPoseRoll` rotations. + The value of this element **MUST** be in the -180 to 180 degree range, both inclusive. + Setting `ProjectionPoseYaw` to 180 or -180 degrees with `ProjectionPoseRoll` and + `ProjectionPosePitch` set to 0 degrees flips the image horizontally. + + + + + + Specifies a pitch rotation to the projection. + Value represents a counter-clockwise rotation, in degrees, around the right vector. This + rotation must be applied + after the `ProjectionPoseYaw` rotation and before the `ProjectionPoseRoll` rotation. + The value of this element **MUST** be in the -90 to 90 degree range, both inclusive. + + + + + + Specifies a roll rotation to the projection. + Value represents a + counter-clockwise rotation, in degrees, around the forward vector. This + rotation must be applied after the `ProjectionPoseYaw` and + `ProjectionPosePitch` rotations. The value of this element + **MUST** be in the -180 to 180 degree range, both inclusive. Setting + `ProjectionPoseRoll` to 180 or -180 degrees and + `ProjectionPoseYaw` to 180 or -180 degrees with + `ProjectionPosePitch` set to 0 degrees flips the image vertically. + Setting `ProjectionPoseRoll` to 180 or -180 degrees with + `ProjectionPoseYaw` and `ProjectionPosePitch` set to 0 degrees + flips the image horizontally and vertically. + + + + + + Audio settings. + + + + + Sampling frequency in Hz. + + + + + + Real output sampling frequency in Hz that is + used for Spectral Band Replication (SBR) techniques. + The default value for + `OutputSamplingFrequency` of the same `TrackEntry` is equal to the `SamplingFrequency`. + + + + + Numbers of channels in the track. + + + + + + Table of horizontal angles for each successive + channel. + + + + Bits per sample, mostly used for PCM. + + + + + + Audio emphasis applied on audio samples. The + player **MUST** apply the inverse emphasis to get the proper audio samples. + + + + First order filter with zero point at + 50 microseconds and a pole at 15 microseconds. Also found on DVD Audio and MPEG + audio. + + + + Defined in [@!ITU-J.17]. + + + FM Radio in Europe. RC Filter with a + time constant of 50 microseconds. + + + FM Radio in the USA. RC Filter with a + time constant of 75 microseconds. + + + Phono filter with time constants of + t1=3180, t2=318 and t3=75 microseconds. [@!NAB1964] + + + Phono filter with time constants of + t1=3180, t2=450 and t3=50 microseconds. + + + Phono filter with time constants of + t1=3180, t2=318 and t3=50 microseconds. + + + Phono filter with time constants of + t1=2500, t2=500 and t3=70 microseconds. + + + Phono filter with time constants of + t1=1590, t2=318 and t3=100 microseconds. + + + Phono filter with time constants of + t1=1590, t2=318 and t3=50 microseconds. + + + Phono filter with time constants of + t1=3180, t2=318 and t3=100 microseconds. + + + + + + Operation that needs to be applied on tracks + to create this virtual track. For more details, see (#track-operation). + + + + Contains the list of all video plane tracks + that need to be combined to create this 3D track. + + + + Contains a video plane track that needs to be + combined to create this 3D track. + + + + The `TrackUID` number of the track + representing the plane. + + + + The kind of plane this track corresponds to. + + + + + + + + + + Contains the list of all tracks whose `Blocks` + need to be combined to create this virtual track. + + + + The `TrackUID` number of a track whose blocks + are used to create this virtual track. + + + + The `TrackUID` of the Smooth FF/RW video in + the paired EBML structure corresponding to this video track. See [@?DivXTrickTrack]. + + + + The `SegmentUUID` of the `Segment` containing + the track identified by TrickTrackUID. See [@?DivXTrickTrack]. + + + + Set to 1 if this video track is a Smooth FF/RW + track. If set to 1, `MasterTrackUID` and `MasterTrackSegUID` should be present, and + `BlockGroups` for this track must contain ReferenceFrame structures. + Otherwise, TrickTrackUID and TrickTrackSegUID must be present if this track has a + corresponding Smooth FF/RW track. See [@?DivXTrickTrack]. + + + + The `TrackUID` of the video track in the + paired EBML structure that corresponds to this Smooth FF/RW track. See + [@?DivXTrickTrack]. + + + + The `SegmentUUID` of the `Segment` containing + the track identified by MasterTrackUID. See [@?DivXTrickTrack]. + + + + Settings for several content encoding + mechanisms like compression or encryption. + + + + + Settings for one content encoding like + compression or encryption. + + + + + Defines the order to apply each + `ContentEncoding` of the `ContentEncodings`. + The decoder/demuxer **MUST** start with the `ContentEncoding` with the highest + `ContentEncodingOrder` and work its way down to the `ContentEncoding` with the lowest + `ContentEncodingOrder`. + This value **MUST** be unique for each `ContentEncoding` found in the `ContentEncodings` + of this `TrackEntry`. + + + + + A bit field that describes which elements have + been modified in this way. Values (big-endian) can be OR'ed. + + + + All frame contents, excluding lacing + data. + + + The track's `CodecPrivate` data. + + + The next ContentEncoding (next + `ContentEncodingOrder`; the data inside `ContentCompression` and/or + `ContentEncryption`). + This value **SHOULD NOT** be used, as + it's not supported by players. + + + + + + + A value describing the kind of transformation + that is applied. + + + + + + + + + + Settings describing the compression used. + This element **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 + to not prevent seeking. + + + + The compression algorithm used. + Compression method "1" (bzlib) and "2" + (lzo1x) lack proper documentation on the format, which limits implementation + possibilities. Due to licensing conflicts on commonly available libraries' compression + methods, "2" (lzo1x) does not offer widespread interoperability. A `Matroska Writer` + **SHOULD NOT** use these compression methods by default. A `Matroska Reader` **MAY** + support methods "1" and "2" and **SHOULD** support other methods. + + + + zlib compression [@!RFC1950]. + + + bzip2 compression [@?BZIP2] **SHOULD + NOT** be used; see usage notes. + + + Lempel-Ziv-Oberhumer compression + [@?LZO] **SHOULD NOT** be used; see usage notes. + + + Octets in `ContentCompSettings` + ((#contentcompsettings-element)) have been stripped from each frame. + + + + + + Settings that might be needed by the + decompressor. For Header Stripping (`ContentCompAlgo`=3), + the bytes that were removed from the beginning of each frame of the track. + + + + Settings describing the encryption used. + This element **MUST** be present if the value of `ContentEncodingType` is 1 (encryption) + and **MUST** be ignored otherwise. + A `Matroska Player` **MAY** support encryption. + + + + + The encryption algorithm used. + + + + The data are not encrypted. + + + Data Encryption Standard (DES) + [@?FIPS46-3]. + This value **SHOULD** be avoided. + + + Triple Data Encryption Algorithm + [@?SP800-67]. + This value **SHOULD** be avoided. + + + Twofish Encryption Algorithm + [@?Twofish]. + + + Blowfish Encryption Algorithm + [@?Blowfish]. + This value **SHOULD** be avoided. + + + Advanced Encryption Standard (AES) + [@?FIPS197]. + + + + + + + For public key algorithms, the ID of the + public key that the data was encrypted with. + + + + + Settings describing the encryption algorithm + used. + ContentEncAESSettings **MUST NOT** be set + (maxOccurs=0) if ContentEncAlgo is not AES (5). + + + + + The AES cipher mode used in the encryption. + AESSettingsCipherMode **MUST NOT** be set + (maxOccurs=0) if ContentEncAlgo is not AES (5). + + + + Counter [@?SP800-38A] + + + Cipher Block Chaining [@?SP800-38A] + + + + + + + A cryptographic signature of the contents. + + + This is the ID of the private key that the + data was signed with. + + + The algorithm used for the signature. + + + + + + + The hash algorithm used for the signature. + + + + + + + + A `Top-Level Element` to speed seeking access. + All entries are + local to the `Segment`. + This element **SHOULD** be set when the + `Segment` is not transmitted as a live stream; see (#livestreaming). + + + + Contains all information relative to a seek + point in the `Segment`. + + + + Absolute timestamp of the seek point, + expressed in Segment Ticks, which are based on `TimestampScale`; see (#timestamp-ticks). + + + + Contains positions for different tracks + corresponding to the timestamp. + + + + The track for which a position is given. + + + + The `Segment Position` ((#segment-position)) + of the `Cluster` containing the associated `Block`. + + + + The relative position inside the `Cluster` of + the referenced `SimpleBlock` or `BlockGroup` + with 0 being the first possible position for an element inside that `Cluster`. + + + + The duration of the block, expressed in + Segment Ticks, which are based on `TimestampScale`; see (#timestamp-ticks). + If missing, the track's `DefaultDuration` does not apply and no duration information is + available in terms of the cues. + + + + Number of the `Block` in the specified + `Cluster`. + + + + The `Segment Position` ((#segment-position)) + of the + Codec State corresponding to this `Cues` element. 0 means that the + data is taken from the initial `TrackEntry`. + + + The `Clusters` containing the referenced + `Blocks`. + + + Timestamp of the referenced `Block`, expressed + in Segment Ticks which is based on `TimestampScale`; see (#timestamp-ticks). + + + The `Segment Position` of the `Cluster` + containing the referenced `Block`. + + + Number of the referenced `Block` of Track X in + the specified `Cluster`. + + + The `Segment Position` of the Codec State + corresponding to this referenced element. + 0 means that the data is taken from the initial `TrackEntry`. + + + Contains attached files. + + + An attached file. + + + + A human-friendly name for the attached file. + + + Filename of the attached file. + + + Media type of the file following the format + described in [@!RFC6838]. + + + + + The data of the file. + + + + UID representing the file, as random as + possible. + + + + A binary value that a track/codec can refer to + when the attachment is needed. + + + The timestamp at which this optimized font + attachment comes into context, expressed in Segment Ticks, which are based on + `TimestampScale`. See [@?DivXWorldFonts]. + This element is reserved for future use and + if written **MUST** be the segment start timestamp. + + + + The timestamp at which this optimized font + attachment goes out of context, expressed in Segment Ticks, which are based on + `TimestampScale`. See [@?DivXWorldFonts]. + This element is reserved for future use and + if written **MUST** be the segment end timestamp. + + + + A system to define basic menus and partition + data. + For more detailed information, see (#chapters). + + + + Contains all information about a `Segment` + edition. + + + + A UID that identifies the edition. It's useful + for tagging an edition. + + + + Set to 1 if an edition is hidden. Hidden + editions **SHOULD NOT** be available to the user interface + (but still be available to Control Tracks; see (#chapter-flags) on `Chapter` flags). + + + + Set to 1 if the edition **SHOULD** be used as + the default one. + + + Set to 1 if the chapters can be defined + multiple times and the order to play them is enforced; see (#editionflagordered). + + + Contains a possible string to use for the + edition display for the given languages. + + + Contains the string to use as the edition + name. + + + One language corresponding to the + EditionString, + in the form defined in [@!RFC5646]; see (#language-codes) on language codes. + + + Contains the atom information to use as the + chapter atom (applies to all tracks). + + + + A UID that identifies the `Chapter`. + + + + + A unique string ID that identifies the + `Chapter`. + For example, it is used as the storage for cue identifier values [@?WebVTT]. + + + + Timestamp of the start of `Chapter`, expressed + in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks). + + + + Timestamp of the end of `Chapter` (timestamp + excluded), expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks). + The value **MUST** be greater than or equal to the `ChapterTimeStart` of the same + `ChapterAtom`. + With the `ChapterTimeEnd` timestamp value + being excluded, it **MUST** take into account the duration of + the last frame it includes, especially for the `ChapterAtom` using the last frames of + the `Segment`. + ChapterTimeEnd **MUST** be set (minOccurs=1) + if the `Edition` is an ordered edition; see (#editionflagordered). If it's a `Parent + Chapter`, see (#nested-chapters). + + + + Set to 1 if a chapter is hidden. + Hidden chapters **SHOULD NOT** be available to the user interface + (but still be available to Control Tracks; see (#chapterflaghidden) on `Chapter` flags). + + + Set to 1 if 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; see (#chapter-flags) on `Chapter` flags. + + + + The `SegmentUUID` of another `Segment` to play + during this chapter. + The value **MUST NOT** be the `SegmentUUID` + value of the `Segment` it belongs to. + `ChapterSegmentUUID` **MUST** be set + (minOccurs=1) if `ChapterSegmentEditionUID` is used; see (#medium-linking) on + Medium-Linking `Segments`. + + + + Indicates what type of content the + `ChapterAtom` contains and might be skipped. + It can be used to automatically skip content based on the type. + If a `ChapterAtom` is inside a `ChapterAtom` that has a `ChapterSkipType` set, it + **MUST NOT** have a `ChapterSkipType` or have a `ChapterSkipType` with the same value as + it's parent `ChapterAtom`. + If the `ChapterAtom` doesn't contain a `ChapterTimeEnd`, the value of the + `ChapterSkipType` is only valid until the next `ChapterAtom` with a `ChapterSkipType` + value or the end of the file. + + + + + Content which should not be skipped. + + + Credits usually found at the beginning + of the content. + + + Credits usually found at the end of + the content. + + + Recap of previous episodes of the + content, usually found around the beginning. + + + Preview of the next episode of the + content, usually found around the end. It may contain spoilers the user wants to + avoid. + + + Preview of the current episode of the + content, usually found around the beginning. It may contain spoilers the user + want to avoid. + + + Advertisement within the content. + + + A pause of content between main parts + of the content. + + + + + The `EditionUID` to play from the `Segment` + linked in `ChapterSegmentUUID`. + If `ChapterSegmentEditionUID` is undeclared, then no `Edition` of the `Linked Segment` + is used; see (#medium-linking) on Medium-Linking `Segments`. + + + Specifies the physical equivalent of this + `ChapterAtom`, e.g., "DVD" (60) or "SIDE" (50); + see (#physical-types) for a complete list of values. + + + List of tracks on which the chapter applies. + If this element is not present, all tracks apply. + + + + UID of the `Track` to apply this chapter to. + In the absence of a control track, choosing this chapter will select the listed `Tracks` + and deselect unlisted tracks. + Absence of this element indicates that the `Chapter` **SHOULD** be applied to any + currently used `Tracks`. + + + + + Contains all possible strings to use for the + chapter display. + + + + Contains the string to use as the chapter + atom. + + + + + A language corresponding to the string, + in the Matroska languages form; see (#language-codes) on language codes. + This element **MUST** be ignored if a `ChapLanguageBCP47` element is used within the + same `ChapterDisplay` element. + + + + + A language corresponding to the `ChapString`, + in the form defined in [@!RFC5646]; see (#language-codes) on language codes. + If a `ChapLanguageBCP47` element is used, then any `ChapLanguage` and `ChapCountry` + elements used in the same `ChapterDisplay` **MUST** be ignored. + + + + A country corresponding to the string, + in the Matroska countries form; see (#country-codes) on country codes. + This element **MUST** be ignored if a `ChapLanguageBCP47` element is used within the + same `ChapterDisplay` element. + + + + + Contains all the commands associated with the + Atom. + + + + Contains the type of the codec used for + processing. + + + + Chapter commands using the Matroska + Script codec. + + + Chapter commands using the DVD-like + codec. + + + + + + Optional data attached to the + `ChapProcessCodecID` information. + For `ChapProcessCodecID` = 1, it is the "DVD level" equivalent; see (#menu-features) on + DVD menus. + + + + Contains all the commands associated with the + Atom. + + + + Defines when the process command **SHOULD** be + handled. + + + + + + + + + Contains the command information. + The data **SHOULD** be interpreted depending on the `ChapProcessCodecID` value. For + `ChapProcessCodecID` = 1, + the data correspond to the binary DVD cell pre/post commands; see (#menu-features) on + DVD menus. + + + + Element containing metadata describing + `Tracks`, `Editions`, `Chapters`, `Attachments`, or the `Segment` as a whole. + A list of valid tags can be found in [@?I-D.ietf-cellar-tags]. + + + + A single metadata descriptor. + + + + Specifies which other elements the metadata + represented by the tag value applies to. + If empty or omitted, then the tag value describes everything in the `Segment`. + + + + + A number to indicate the logical level of the + target. + The `TargetTypeValue` values are meant to be + compared. + Higher values **MUST** correspond to a logical level that contains the lower logical + level `TargetTypeValue` values. + + + + The lowest hierarchy found in music or + movies. + + + Corresponds to parts of a track for + audio, such as a movement or scene in a movie. + + + The common parts of an album or movie. + + + When an album or episode has different + logical parts. + + + The most common grouping level of + music and video (e.g., an episode for TV series). + + + A list of lower levels grouped + together. + + + The highest hierarchical level that + tags can describe. + + + + + + + An informational string that can be used to + display the logical level of the target, such as "ALBUM", "TRACK", "MOVIE", "CHAPTER", + etc. + + + + + + + + + + + + + + + + + + + + + + + + + + + + A UID that identifies the `Track(s)` that the + tags belong to. + If the value is 0 at this level, the tags + apply to all tracks in the `Segment`. + If set to any other value, it **MUST** match the `TrackUID` value of a track found in + this `Segment`. + + + + A UID that identifies the `EditionEntry(s)` + that the tags belong to. + If the value is 0 at this level, the tags + apply to all editions in the `Segment`. + If set to any other value, it **MUST** match the `EditionUID` value of an edition found + in this `Segment`. + + + A UID that identifies the `Chapter(s)` that + the tags belong to. + If the value is 0 at this level, the tags + apply to all chapters in the `Segment`. + If set to any other value, it **MUST** match the `ChapterUID` value of a chapter found + in this `Segment`. + + + A UID that identifies the Attachment(s) that + the tags belong to. + If the value is 0 at this level, the tags + apply to all the attachments in + the `Segment`. If set to any other value, it **MUST** match + the `FileUID` value of an attachment found in this `Segment`. + + + Contains general information about the target. + + + + + The name of the tag value that is going to be + stored. + + + + Specifies the language of the specified tag in + the Matroska languages form; see (#language-codes) on language codes. + This element **MUST** be ignored if the `TagLanguageBCP47` element is used within the + same `SimpleTag` element. + + + + + The language used in the `TagString`, + in the form defined in [@!RFC5646]; see (#language-codes) on language codes. + If this element is used, then any `TagLanguage` elements used in the same `SimpleTag` + **MUST** be ignored. + + + + A boolean value to indicate if this is the + default/original language to use for the given tag. + + + + A variant of the `TagDefault` element with a + bogus element ID; see (#tagdefault-element). + + + The tag value. + + + + The tag value if it is binary. Note that this + cannot be used in the same `SimpleTag` as `TagString`. + + + \ No newline at end of file diff --git a/assets/specification/ebml_mkv_legacy.xml b/assets/specification/ebml_mkv_legacy.xml new file mode 100644 index 0000000..794ae8e --- /dev/null +++ b/assets/specification/ebml_mkv_legacy.xml @@ -0,0 +1,671 @@ + + + Set the EBML characteristics of the data to follow. Each EBML document has to + start with this. + The version of EBML parser used to create the file. + The minimum EBML version a parser has to support to read this file. + The maximum length of the IDs you'll find in this file (4 or less in Matroska). + 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. + A string that describes the type of document that follows this EBML header. + 'matroska' in our case or 'webm' for webm files. + The version of DocType interpreter used to create the file. + The minimum DocType version an interpreter has to support to read + this file. + 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. + 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 + Contain + signature of some (coming) elements in the stream. + Signature algorithm + used (1=RSA, 2=elliptic). + Hash algorithm used + (1=SHA1-160, 2=MD5). + The public key + to use with the algorithm (in the case of a PKI-based signature). + The signature of the data + (until a new. + Contains elements + that will be used to compute the signature. + A + list consists of a number of consecutive elements that represent one case where data is used + in signature. Ex: Cluster|Block|BlockAdditional means that the BlockAdditional of all + Blocks in all Clusters is used for encryption. + An + element ID whose data will be used to compute the signature. + This element contains all other top-level (level 1) elements. Typically a + Matroska file is composed of 1 segment. + Contains the position + of other level 1 elements. + Contains a single seek entry to an EBML element. + The binary + ID corresponding to the element name. + The position + of the element in the segment in octets (0 = first level 1 element). + Contains miscellaneous general information and statistics on the file. + A randomly generated unique ID to identify the current segment between many + others (128 bits). + A + filename corresponding to this segment. + A + unique ID to identify the previous chained segment (128 bits). + An escaped + filename corresponding to the previous segment. + A + unique ID to identify the next chained segment (128 bits). + An escaped + filename corresponding to the next segment. + A randomly generated unique ID that all segments related to each + other must use (128 bits). + A tuple of corresponding ID used by chapter codecs to represent this segment. + Specify an edition UID on which this correspondance applies. When not + specified, it means for all editions found in the segment. + The chapter + codec using this ID (0: Matroska Script, 1: DVD-menu). + The binary value used to represent this segment in the chapter codec data. The + format depends on the + ChapProcessCodecID used. + Timecode scale in nanoseconds (1.000.000 means all timecodes in the + segment are expressed in milliseconds). + + Duration + of the segment (based on TimecodeScale). + Date of the origin of + timecode (value 0), i.e. production date. + General name of the + segment. + Muxing + application or library ("libmatroska-0.4.3"). + Writing + application ("mkvmerge-0.3.3"). + The + lower level element containing the (monolithic) Block structure. + Absolute timecode of the cluster (based on TimecodeScale). + 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. + 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. + The Position + of the Cluster in the segment (0 in live broadcast streams). It might help to resynchronise + offset on damaged streams. + Size of the previous Cluster, in octets. Can be useful for backward playing. + Similar to Block + but without all the extra information, mostly used to reduced overhead when no extra feature + is needed. (see SimpleBlock + Structure) + Basic + container of information containing a single Block or BlockVirtual, and information specific + to that Block/VirtualBlock. + Block + containing the actual data to be rendered and a timecode relative to the Cluster Timecode. + (see Block + Structure) + A Block with no data. It + must be stored in the stream at the place the real Block should be in display order. (see Block Virtual + ) + 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. + Contain the BlockAdditional and some parameters. + An ID to identify the BlockAdditional level. + Interpreted by the codec as it wishes (using the BlockAddID). + 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. + 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. + Timecode + of another frame used as a reference (ie: B or P frame). The timecode is relative to the + block it's attached to. + Relative position + of the data that should be in position of the virtual block. + The new codec + state to use. Data interpretation is private to the codec. This information should always be + referenced by a seek entry. + Contains slices + description. + 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. + 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. + The number of the frame to generate from this lace with this delay (allow you to + generate many frames from the same Block/Frame). + The ID of the BlockAdditional element (0 is the main Block). + The + (scaled) delay to apply to the element. + The (scaled) + duration to apply to the element. + + DivX + trick track extenstions + + + DivX + trick track extenstions + + + DivX + trick track extenstions + + Similar + to SimpleBlock + but the data inside the Block are Transformed (encrypt and/or signed). (see EncryptedBlock + Structure) + A + top-level block of information with many tracks described. + Describes a track with all elements. + The track number as used in the Block Header (using more than 127 tracks is + not encouraged, though the design allows an unlimited number). + 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. + A set of track types coded on 8 bits (1: video, 2: audio, 3: complex, 0x10: + logo, 0x11: subtitle, 0x12: buttons, 0x20: control). + Set if the track is usable. (1 + bit) + Set if that track (audio, video or subs) + SHOULD be active if no language found matches the user preference. (1 bit) + 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) + Set if the track may contain blocks using + lacing. (1 bit) + 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. + The maximum cache size required to store referenced frames in and the + current frame. 0 means no cache is needed. + Number of nanoseconds (not scaled via + TimecodeScale) per frame ('frame' in the Matroska sense -- one element put into a + (Simple)Block). + 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). + A value to + add to the Block's Timecode. This can be used to adjust the playback offset of a track. + The maximum value of BlockAddID. A + value 0 means there is no BlockAdditions + for this track. + A + human-readable track name. + Specifies the language of the track in the Matroska languages + form. + An ID + corresponding to the codec, see the codec page for + more info. + Private data only + known to the codec. + A human-readable + string specifying the codec. + The UID of an attachment that is used by + this codec. + A string describing + the encoding setting used. + A URL + to find information about the codec used. + A + URL to download about the codec used. + The codec can decode potentially damaged data (1 bit). + 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 SilentTracks) + 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. + The track identification for the given Chapter Codec. + Specify an edition UID on which this translation applies. When not + specified, it means for all editions found in the segment. + The chapter + codec using this ID (0: Matroska Script, 1: DVD-menu). + The binary value used to represent this track in the chapter codec data. + The format depends on the + ChapProcessCodecID used. + Video + settings. + Set if the video is interlaced. (1 + bit) + 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 3D support in the + Specification Notes. + DEPRECATED, + DO NOT USE. Bogus StereoMode value used in old versions of libmatroska. (0: mono, 1: right + eye, 2: left eye, 3: both eyes). + Width of the encoded video frames in pixels. + Height of the encoded video frames in pixels. + The number of video pixels to remove at the bottom of + the image (for HDTV content). + The number of video pixels to remove at the top of the image. + The number of video pixels to remove on the left of the image. + The number of video pixels to remove on the right of + the image. + Width of the video frames to display. The + default value is only valid when DisplayUnit is + 0. + Height of the video frames to display. The + default value is only valid when DisplayUnit is + 0. + How DisplayWidth & DisplayHeight should be interpreted (0: + pixels, 1: centimeters, 2: inches, 3: Display Aspect Ratio). + Specify the possible modifications to the aspect ratio (0: free + resizing, 1: keep aspect ratio, 2: fixed). + Same value as in AVI (32 bits). + Gamma Value. + Number of frames per second. Informational only. + Audio + settings. + Sampling frequency in Hz. + Real output sampling + frequency in Hz (used for SBR techniques). + Numbers of channels in the track. + Table of horizontal angles for each successive channel, see appendix. + Bits per sample, mostly used for PCM. + Operation + that needs to be applied on tracks to create this virtual track. For more details look at the + Specification Notes on the subject. + Contains + the list of all video plane tracks that need to be combined to create this 3D track + Contains a video plane track that need to be combined to create this 3D + track + The trackUID number of the track representing the plane. + The kind of plane this track corresponds to (0: left eye, 1: right eye, 2: + background). + Contains + the list of all tracks whose Blocks need to be combined to create this virtual track + The trackUID number of a track whose blocks are used to + create this virtual track. + + DivX + trick track extenstions + + + DivX + trick track extenstions + + + DivX + trick track extenstions + + + DivX + trick track extenstions + + + DivX + trick track extenstions + + Settings + for several content encoding mechanisms like compression or encryption. + Settings for one content encoding like compression or encryption. + 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. + A bit field that describes which elements have + been modified in this way. Values (big endian) can be OR'ed. Possible values:
1 - all + frame contents,
2 - the track's private data,
4 - the next ContentEncoding (next + ContentEncodingOrder. Either the data inside ContentCompression and/or ContentEncryption)
+ A value describing what kind of transformation has been + done. Possible values:
0 - compression,
1 - encryption
+ 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. + The compression algorithm used. Algorithms that have been specified so + far are:
0 - zlib,
1 - bzlib,
2 - lzo1x
3 - + Header Stripping
+ 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. + Settings + describing the encryption used. Must be present if the value of ContentEncodingType is 1 and + absent otherwise. + The encryption algorithm used. The value '0' means that the contents have not + been encrypted but only signed. Predefined values:
1 - DES, 2 - 3DES, 3 - Twofish, 4 - + Blowfish, 5 - AES
+ For + public key algorithms this is the ID of the public key the the data was encrypted with. + A + cryptographic signature of the contents. + This is + the ID of the private key the data was signed with. + The algorithm used for the signature. A value of '0' means that the contents + have not been signed but only encrypted. Predefined values:
1 - RSA
+ The hash algorithm used for the signature. A value of '0' means that the + contents have not been signed but only encrypted. Predefined values:
1 - SHA1-160
+ 2 - MD5
+ A top-level element to + speed seeking access. All entries are local to the segment. Should be mandatory for non "live" streams. + Contains + all information relative to a seek point in the segment. + Absolute + timecode according to the segment time base. + Contain positions for different tracks corresponding to the timecode. + The track for which a position is given. + + The + position of the Cluster containing the required Block. + The relative position of the referenced block inside the cluster with 0 being the + first possible position for an element inside that cluster. + 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. + Number of the Block in the specified Cluster. + The position + of the Codec State corresponding to this Cue element. 0 means that the data is taken from + the initial Track Entry. + The + Clusters containing the required referenced Blocks. + Timecode + of the referenced Block. + The Position + of the Cluster containing the referenced Block. + Number of the referenced Block of Track X in the specified Cluster. + The position + of the Codec State corresponding to this referenced element. 0 means that the data is taken + from the initial Track Entry. + Contain + attached files. + An attached file. + A + human-friendly name for the attached file. + Filename + of the attached file. + MIME type of the file. + The + data of the file. + Unique ID representing the file, as random as possible. + A binary value that a + track/codec can refer to when the attachment is needed. + + DivX font + extension + + + DivX font + extension + + A system to + define basic menus and partition data. For more detailed information, look at the Chapters Explanation + . + Contains all information about a segment edition. + A unique ID to identify the edition. It's useful for tagging an edition. + If an edition is hidden (1), it should not be available to + the user interface (but still to Control Tracks). (1 bit) + If a flag is set (1) the edition should be used + as the default one. (1 bit) + Specify if the chapters can be defined multiple times and the order + to play them is enforced. (1 bit) + Contains the atom information to use as the chapter atom + (apply to all tracks). + A unique ID to identify the Chapter. + A unique string ID to identify the Chapter. Use for WebVTT cue identifier + storage. + Timecode of the start of Chapter (not scaled). + Timecode + of the end of Chapter (timecode excluded, not scaled). + If a chapter is hidden (1), it should not be available to + the user interface (but still to Control Tracks). (1 bit) + 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) + A segment to play in place of this chapter. Edition + ChapterSegmentEditionUID should be used for this segment, otherwise no edition is used. + The EditionUID to play from the segment linked in ChapterSegmentUID. + Specify + the physical equivalent of this ChapterAtom like "DVD" (60) or "SIDE" (50), see complete list of + values. + List of + tracks on which the chapter applies. If this element is not present, all tracks apply + 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. + Contains all possible strings to use for the chapter display. + Contains the string to use as the chapter atom. + The languages corresponding to + the string, in the bibliographic + ISO-639-2 form. + The countries corresponding to the string, same 2 octets as + in Internet domains. + Contains all the commands associated to the Atom. + 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 DVD command + set is used. More codec IDs can be added later. + Some optional data attached to the ChapProcessCodecID + information. For + ChapProcessCodecID = 1, it is the "DVD level" equivalent. + Contains all the commands associated to the + Atom. + Defines when the process command should be + handled (0: during the whole chapter, 1: before starting playback, 2: after playback of the + chapter). + Contains the command information. The data should be + interpreted depending on the ChapProcessCodecID value. For + ChapProcessCodecID = 1, the data correspond to the binary DVD cell pre/post commands. + Element + containing elements specific to Tracks/Chapters. A list of valid tags can be found here. + Element containing elements specific to Tracks/Chapters. + Contain all UIDs where the specified meta data apply. It is empty to + describe everything in the segment. + A number to indicate the logical level of + the target (see TargetType + ). + An informational string that can be used to display the + logical level of the target like "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc (see TargetType + ). + 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. + 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. + 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. + 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. + Contains general information about the + target. + The + name of the Tag that is going to be stored. + Specifies the language of the tag specified, in the Matroska languages + form. + Indication to know if this is the default/original language + to use for the given tag. (1 bit) + The value of + the Tag. + The values of + the Tag if it is binary. Note that this cannot be used in the same SimpleTag as TagString. +
\ No newline at end of file diff --git a/biome.jsonc b/biome.jsonc index cd5aecc..ccfbb04 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -19,6 +19,7 @@ "noBannedTypes": "off" }, "nursery": { + "noEnum": "off", "useConsistentMemberAccessibility": "off" } } @@ -43,6 +44,21 @@ } } } + }, + { + "include": [ + "scripts/**" + ], + "linter": { + "rules": { + "suspicious": { + "noConsole": "off" + }, + "performance": { + "useTopLevelRegex": "off" + } + } + } } ] } \ No newline at end of file diff --git a/package.json b/package.json index b491de8..4a4c648 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,13 @@ "name": "konoplayer", "version": "0.0.1", "description": "A strange player, like the dumtruck, taking you to Isekai.", - "scripts": {}, + "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" @@ -13,6 +16,8 @@ "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" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdb998c..0f16120 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,6 +33,12 @@ importers: '@types/node': specifier: ^22.13.8 version: 22.13.9 + change-case: + specifier: ^5.4.4 + version: 5.4.4 + happy-dom: + specifier: ^17.4.4 + version: 17.4.4 tsx: specifier: ^4.19.2 version: 4.19.3 @@ -80,8 +86,8 @@ importers: apps/playground: dependencies: konoebml: - specifier: 0.1.0 - version: 0.1.0(arktype@2.1.10) + specifier: 0.1.1 + version: 0.1.1(arktype@2.1.10) lit: specifier: ^3.2.1 version: 3.2.1 @@ -1213,6 +1219,9 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} @@ -1674,6 +1683,10 @@ packages: resolution: {integrity: sha512-ffkD1lS3Hl5l/6L6dy93BugNUMCS+0pD730uOj+1T5iHKh7wOwDp95P2xf4PLjezssg8JQzHzBydeWjs1BuyOw==} engines: {node: '>= 0.12.0'} + happy-dom@17.4.4: + resolution: {integrity: sha512-/Pb0ctk3HTZ5xEL3BZ0hK1AqDSAUuRQitOmROPHhfUYEWpmTImwfD8vFDGADmMAX0JYgbcgxWoLFKtsWhcpuVA==} + engines: {node: '>=18.0.0'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1853,8 +1866,8 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - konoebml@0.1.0: - resolution: {integrity: sha512-Fp4nJBr9E82sr2Ap0JwHZFoeyNH7Cy0NdVXRcJ54wxCNfs3MHfo9IXnSLph/zvkGT1b5qw7JSs4nwoygZF4F7g==} + konoebml@0.1.1: + resolution: {integrity: sha512-JuQnHgsoDrschZetWaZfHfTmjCXFV3B31f5Y+FzfHsFs1lQKGNH0ILj+IpyItnuq1KFwkGbslc9EBxY0CDWTFw==} engines: {node: '>= 18.0.0'} peerDependencies: arktype: ^2.0.0 @@ -2637,6 +2650,10 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + webpack-node-externals@3.0.0: resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} engines: {node: '>=6'} @@ -2658,6 +2675,10 @@ packages: weinre2@1.3.6: resolution: {integrity: sha512-xKawRFdgaFvxDDsb0jJ7KpnqYaAFFMvd+VlhFHdi/YSCJSGjYCxjMHuDobViddZVosfiGkQQv6/GHKqxN98EaQ==} + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -3790,6 +3811,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + change-case@5.4.4: {} + chardet@0.7.0: {} chokidar@3.6.0: @@ -4327,6 +4350,11 @@ snapshots: dependencies: safe-buffer: 5.2.1 + happy-dom@17.4.4: + dependencies: + webidl-conversions: 7.0.0 + whatwg-mimetype: 3.0.0 + has-flag@4.0.0: {} has-own-prop@2.0.0: {} @@ -4467,7 +4495,7 @@ snapshots: kind-of@6.0.3: {} - konoebml@0.1.0(arktype@2.1.10): + konoebml@0.1.1(arktype@2.1.10): dependencies: mnemonist: 0.40.3 type-fest: 4.37.0 @@ -5223,6 +5251,8 @@ snapshots: dependencies: defaults: 1.0.4 + webidl-conversions@7.0.0: {} + webpack-node-externals@3.0.0: {} webpack-sources@3.2.3: {} @@ -5266,6 +5296,8 @@ snapshots: transitivePeerDependencies: - supports-color + whatwg-mimetype@3.0.0: {} + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6cea55b..af8e133 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,5 +4,6 @@ packages: onlyBuiltDependencies: - '@biomejs/biome' - '@nestjs/core' + - '@swc/core' - core-js - esbuild diff --git a/scripts/codegen-mkv.ts b/scripts/codegen-mkv.ts new file mode 100644 index 0000000..956a484 --- /dev/null +++ b/scripts/codegen-mkv.ts @@ -0,0 +1,451 @@ +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(); + + // 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(); + + for (const el of elements) { + const parentPath = el.parentPath; + if (parentPath) { + hirerachy.set(parentPath, el); + } + } + + const idMulti = new Set(); + 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: + ...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()`; + 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(); diff --git a/tsconfig.base.json b/tsconfig.base.json index fe74395..ab5a802 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -14,18 +14,19 @@ "DOM.AsyncIterable", "DOM.Iterable" ], - "module": "NodeNext", + "module": "ESNext", "moduleDetection": "force", - "moduleResolution": "nodenext", + "moduleResolution": "bundler", "resolveJsonModule": true, "allowImportingTsExtensions": true, "emitDeclarationOnly": true, "skipLibCheck": true, - "target": "ES2020", + "target": "ES2021", "strictNullChecks": true, "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "useDefineForClassFields": true + "useDefineForClassFields": true, + "exactOptionalPropertyTypes": false, } } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 27b3b51..70736b5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,9 @@ }, { "path": "./apps/mock" + }, + { + "path": "./tsconfig.scripts.json" } ] } \ No newline at end of file diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json new file mode 100644 index 0000000..8ea363b --- /dev/null +++ b/tsconfig.scripts.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "composite": true, + "rootDir": "./scripts", + "baseUrl": ".", + "emitDeclarationOnly": true + }, + "include": [ + "scripts" + ], + "exclude": [] +} \ No newline at end of file