diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 7c8038a..72b8aa5 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,9 @@ { "recommendations": [ - "runem.lit-plugin" + "runem.lit-plugin", + "vitest.explorer", + "biomejs.biome", + "hbenl.vscode-test-explorer", + "zerotaskx.rust-extension-pack" ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 07324ab..5cefb2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "extended" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" + [[package]] name = "ffmpeg-sys-next" version = "7.1.0" @@ -139,17 +145,17 @@ dependencies = [ ] [[package]] -name = "konoplayer-codecs" +name = "konoplayer-ffmpeg" version = "0.1.0" dependencies = [ "ffmpeg-sys-next", ] [[package]] -name = "konoplayer-demuxing" +name = "konoplayer-symphonia" version = "0.1.0" dependencies = [ - "symphonia-format-mkv", + "symphonia", ] [[package]] @@ -277,6 +283,67 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "symphonia" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9" +dependencies = [ + "lazy_static", + "symphonia-bundle-flac", + "symphonia-codec-adpcm", + "symphonia-codec-pcm", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-mkv", + "symphonia-format-ogg", + "symphonia-format-riff", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-flac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e34f34298a7308d4397a6c7fbf5b84c5d491231ce3dd379707ba673ab3bd97" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-codec-adpcm" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94e1feac3327cd616e973d5be69ad36b3945f16b06f19c6773fc3ac0b426a0f" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-pcm" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f395a67057c2ebc5e84d7bb1be71cce1a7ba99f64e0f0f0e303a03f79116f89b" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a98765fb46a0a6732b007f7e2870c2129b6f78d87db7987e6533c8f164a9f30" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + [[package]] name = "symphonia-core" version = "0.5.4" @@ -303,6 +370,30 @@ dependencies = [ "symphonia-utils-xiph", ] +[[package]] +name = "symphonia-format-ogg" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada3505789516bcf00fc1157c67729eded428b455c27ca370e41f4d785bfa931" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-riff" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f7be232f962f937f4b7115cbe62c330929345434c834359425e043bfd15f50" +dependencies = [ + "extended", + "log", + "symphonia-core", + "symphonia-metadata", +] + [[package]] name = "symphonia-metadata" version = "0.5.4" diff --git a/Cargo.toml b/Cargo.toml index 93842e9..d56076d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["packages/demuxing", "packages/codecs"] +members = ["packages/symphonia", "packages/ffmpeg"] resolver = "3" diff --git a/apps/mock/.gitignore b/apps/mock/.gitignore deleted file mode 100644 index bc0cfa4..0000000 --- a/apps/mock/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -public/video/huge/* -!public/video/huge/.gitkeep \ No newline at end of file diff --git a/apps/mock/package.json b/apps/mock/package.json index e5393f8..885343f 100644 --- a/apps/mock/package.json +++ b/apps/mock/package.json @@ -1,5 +1,5 @@ { - "name": "mock", + "name": "@konoplayer/mock", "version": "0.1.0", "private": true, "scripts": { diff --git a/apps/mock/public/.gitignore b/apps/mock/public/.gitignore new file mode 100644 index 0000000..e9998b6 --- /dev/null +++ b/apps/mock/public/.gitignore @@ -0,0 +1,2 @@ +video/huge/* +!video/huge/.gitkeep \ No newline at end of file diff --git a/apps/mock/tsconfig.json b/apps/mock/tsconfig.json index 4f82a26..0bf5e3a 100644 --- a/apps/mock/tsconfig.json +++ b/apps/mock/tsconfig.json @@ -4,10 +4,8 @@ "composite": true, "module": "CommonJS", "moduleResolution": "node", - "declaration": true, "emitDeclarationOnly": false, "emitDecoratorMetadata": true, - "experimentalDecorators": true, "allowImportingTsExtensions": false, "outDir": "./dist", "rootDir": ".", diff --git a/apps/playground/package.json b/apps/playground/package.json index 66d038c..1cea90f 100644 --- a/apps/playground/package.json +++ b/apps/playground/package.json @@ -1,6 +1,6 @@ { - "name": "playground", - "version": "1.0.0", + "name": "@konoplayer/playground", + "version": "0.1.0", "private": true, "type": "module", "scripts": { @@ -9,11 +9,11 @@ "preview": "rsbuild preview" }, "dependencies": { - "konoebml": "0.1.2", - "lit": "^3.2.1" + "lit": "^3.2.1", + "@konoplayer/core": "workspace:*", + "@konoplayer/matroska": "workspace:*" }, "devDependencies": { - "@rsbuild/core": "^1.2.14", - "typescript": "^5.8.2" + "@rsbuild/core": "^1.2.14" } } \ No newline at end of file diff --git a/apps/playground/src/fetch/index.ts b/apps/playground/src/fetch/index.ts deleted file mode 100644 index ebe4c51..0000000 --- a/apps/playground/src/fetch/index.ts +++ /dev/null @@ -1,60 +0,0 @@ -export interface RangedStream { - controller: AbortController; - response: Response; - body: ReadableStream; - totalSize?: number; - } - - export async function createRangedStream( - url: string, - byteStart = 0, - byteEnd?: number - ) { - const controller = new AbortController(); - const signal = controller.signal; - const headers = new Headers(); - headers.append( - 'Range', - typeof byteEnd === 'number' - ? `bytes=${byteStart}-${byteEnd}` - : `bytes=${byteStart}-` - ); - - const response = await fetch(url, { signal, headers }); - - if (!response.ok) { - throw new Error('fetch video stream failed'); - } - - const acceptRanges = response.headers.get('Accept-Ranges'); - - if (acceptRanges !== 'bytes') { - throw new Error('video server does not support byte ranges'); - } - - const body = response.body; - - if (!(body instanceof ReadableStream)) { - throw new Error('can not get readable stream from response.body'); - } - - const contentRange = response.headers.get('Content-Range'); - - // - // Content-Range Header Syntax: - // Content-Range: -/ - // Content-Range: -/* - // Content-Range: */ - // - const totalSize = contentRange - ? Number.parseInt(contentRange.split('/')[1], 10) - : undefined; - - return { - controller, - response, - body, - totalSize, - }; - } - \ No newline at end of file diff --git a/apps/playground/src/media/mkv/codecs/hevc.ts b/apps/playground/src/media/mkv/codecs/hevc.ts deleted file mode 100644 index 31a7b7f..0000000 --- a/apps/playground/src/media/mkv/codecs/hevc.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { ParseCodecPrivateError } from '@/media/base/errors'; -import { ArkErrors, type } from 'arktype'; - -export const HEVC_CODEC_TYPE = 'h265(HEVC)'; - -export const HEVCDecoderConfigurationRecordArraySchema = type({ - arrayCompleteness: type.boolean, - reserved: type.number, - NALUnitType: type.number, - numNalus: type.number, - nalUnits: type.instanceOf(Uint8Array).array(), -}); - -export type HEVCDecoderConfigurationRecordArrayType = - typeof HEVCDecoderConfigurationRecordArraySchema.infer; - -// Define the schema for HEVCDecoderConfigurationRecord -export const HEVCDecoderConfigurationRecordSchema = type({ - configurationVersion: type.number, // Must be 1 - generalProfileSpace: type.number, - generalTierFlag: type.boolean, - generalProfileIdc: type.number, - generalProfileCompatibilityFlags: type.number, - generalConstraintIndicatorFlags: type.number.array().exactlyLength(6), // Fixed 6-byte array - generalLevelIdc: type.number, - reserved1: type.number, // 4 bits reserved, must be 1111 - minSpatialSegmentationIdc: type.number, - reserved2: type.number, // 6 bits reserved, must be 111111 - parallelismType: type.number, - chromaFormat: type.number, - bitDepthLumaMinus8: type.number, - bitDepthChromaMinus8: type.number, - avgFrameRate: type.number, - constantFrameRate: type.number, - numTemporalLayers: type.number, - temporalIdNested: type.boolean, - lengthSizeMinusOne: type.number, - numOfArrays: type.number, - arrays: HEVCDecoderConfigurationRecordArraySchema.array(), -}); - -export type HEVCDecoderConfigurationRecordType = - typeof HEVCDecoderConfigurationRecordSchema.infer; - -/** - * Parse HEVCDecoderConfigurationRecord from codec_private Uint8Array - * @param codecPrivate - Uint8Array containing codec_private data - * @returns Parsed HEVCDecoderConfigurationRecord or throws an error if invalid - */ -export function parseHEVCDecoderConfigurationRecord( - codecPrivate: Uint8Array -): HEVCDecoderConfigurationRecordType { - let offset = 0; - - // Read and validate basic fields - const config: HEVCDecoderConfigurationRecordType = { - configurationVersion: codecPrivate[offset++], - generalProfileSpace: codecPrivate[offset] >> 6, - generalTierFlag: Boolean(codecPrivate[offset] & 0x20), - generalProfileIdc: codecPrivate[offset++] & 0x1f, - generalProfileCompatibilityFlags: - (codecPrivate[offset] << 24) | - (codecPrivate[offset + 1] << 16) | - (codecPrivate[offset + 2] << 8) | - codecPrivate[offset + 3], - generalConstraintIndicatorFlags: Array.from( - codecPrivate.subarray(offset + 4, offset + 10) - ), - generalLevelIdc: codecPrivate[offset + 10], - reserved1: (codecPrivate[offset + 11] & 0xf0) >> 4, // 4 bits - minSpatialSegmentationIdc: - ((codecPrivate[offset + 11] & 0x0f) << 8) | codecPrivate[offset + 12], - reserved2: (codecPrivate[offset + 13] & 0xfc) >> 2, // 6 bits - parallelismType: codecPrivate[offset + 13] & 0x03, - chromaFormat: (codecPrivate[offset + 14] & 0xe0) >> 5, - bitDepthLumaMinus8: (codecPrivate[offset + 14] & 0x1c) >> 2, - bitDepthChromaMinus8: codecPrivate[offset + 14] & 0x03, - avgFrameRate: (codecPrivate[offset + 15] << 8) | codecPrivate[offset + 16], - constantFrameRate: (codecPrivate[offset + 17] & 0xc0) >> 6, - numTemporalLayers: (codecPrivate[offset + 17] & 0x38) >> 3, - temporalIdNested: Boolean(codecPrivate[offset + 17] & 0x04), - lengthSizeMinusOne: codecPrivate[offset + 17] & 0x03, - numOfArrays: codecPrivate[offset + 18], - arrays: [], - }; - offset += 19; - - // Parse NAL unit arrays - const arrays = config.arrays; - for (let i = 0; i < config.numOfArrays; i++) { - const array: HEVCDecoderConfigurationRecordArrayType = { - arrayCompleteness: Boolean(codecPrivate[offset] & 0x80), - reserved: (codecPrivate[offset] & 0x40) >> 6, - NALUnitType: codecPrivate[offset] & 0x3f, - numNalus: (codecPrivate[offset + 1] << 8) | codecPrivate[offset + 2], - nalUnits: [] as Uint8Array[], - }; - offset += 3; - - for (let j = 0; j < array.numNalus; j++) { - const nalUnitLength = - (codecPrivate[offset] << 8) | codecPrivate[offset + 1]; - offset += 2; - array.nalUnits.push( - codecPrivate.subarray(offset, offset + nalUnitLength) - ); - offset += nalUnitLength; - } - arrays.push(array); - } - - const result = { ...config, arrays }; - - // Validate using arktype - const validation = HEVCDecoderConfigurationRecordSchema(result); - if (validation instanceof ArkErrors) { - const error = new ParseCodecPrivateError( - HEVC_CODEC_TYPE, - 'Invalid HEVC configuration record' - ); - error.cause = validation; - throw error; - } - - return result; -} - -export function genCodecStringByHEVCDecoderConfigurationRecord( - config: HEVCDecoderConfigurationRecordType -) { - const profileSpace = - config.generalProfileSpace === 0 - ? '' - : String.fromCharCode(65 + config.generalProfileSpace - 1); - const profileIdcHex = config.generalProfileIdc.toString(16); - const tier = config.generalTierFlag ? '7' : '6'; - const levelMajor = Math.floor(config.generalLevelIdc / 30); - const levelMinor = - config.generalLevelIdc % 30 === 0 ? '0' : (config.generalLevelIdc % 30) / 3; - const levelStr = `L${config.generalLevelIdc.toString().padStart(3, '0')}`; - - const constraint = '00'; - return `hev1.${profileSpace}${profileIdcHex}.${tier}.${levelStr}.${constraint}`; -} diff --git a/apps/playground/src/media/mkv/codecs/vp9.ts b/apps/playground/src/media/mkv/codecs/vp9.ts deleted file mode 100644 index 7f33570..0000000 --- a/apps/playground/src/media/mkv/codecs/vp9.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { type } from 'arktype'; -import type {TrackEntryType} from "@/media/mkv/schema.ts"; - -export const VP9DecoderProfileSchema = type('0 | 1 | 2 | 3'); - -export const VP9DecoderConfigurationRecordSchema = type({ - profile: VP9DecoderProfileSchema, - level: type.number, - bitDepth: type.number, -}); - -export type VP9DecoderConfigurationRecordType = - typeof VP9DecoderConfigurationRecordSchema.infer; - -export function parseVP9DecoderConfigurationRecord(track: TrackEntryType) { - const pixelWidth = Number(track.Video?.PixelWidth); - const pixelHeight = Number(track.Video?.PixelHeight); - const pixels = pixelWidth * pixelHeight; - const bitDepth = Number(track.Video?.Colour?.BitsPerChannel) || 10; - -} diff --git a/apps/playground/src/media/mkv/enhance/probe.ts b/apps/playground/src/media/mkv/enhance/probe.ts deleted file mode 100644 index b02b9bf..0000000 --- a/apps/playground/src/media/mkv/enhance/probe.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface ProbeInfo { - -} \ No newline at end of file diff --git a/apps/playground/src/video-pipeline-demo.ts b/apps/playground/src/video-pipeline-demo.ts index 8b34081..2dae978 100644 --- a/apps/playground/src/video-pipeline-demo.ts +++ b/apps/playground/src/video-pipeline-demo.ts @@ -18,9 +18,12 @@ import { fromEvent, filter, } from 'rxjs'; -import { createEbmlController } from './media/mkv/reactive'; -import { TrackTypeRestrictionEnum, type ClusterType } from './media/mkv/schema'; -import type { SegmentComponent } from './media/mkv/model'; +import { createEbmlController } from '@konoplayer/matroska/reactive'; +import { + TrackTypeRestrictionEnum, + type ClusterType, +} from '@konoplayer/matroska/schema'; +import type { SegmentComponent } from '@konoplayer/matroska/model'; import { createRef, ref, type Ref } from 'lit/directives/ref.js'; import { Queue } from 'mnemonist'; @@ -113,7 +116,6 @@ export class VideoPipelineDemo extends LitElement { description: videoTrack.CodecPrivate, // Uint8Array,包含 VPS/SPS/PPS }); - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: const sub = this.cluster$.subscribe((c) => { if (!isFinalized) { for (const b of (c.SimpleBlock || []).filter( @@ -163,7 +165,7 @@ export class VideoPipelineDemo extends LitElement { const numberOfChannels = (audioTrack.Audio?.Channels as number) || 2; const duration = - Math.round(Number(audioTrack.DefaultDuration / 1000)) || + Math.round(Number(audioTrack.DefaultDuration) / 1000) || Math.round((1024 / sampleRate) * 1000000); decoder.configure({ diff --git a/apps/playground/tsconfig.json b/apps/playground/tsconfig.json index c37b5e6..4058d9b 100644 --- a/apps/playground/tsconfig.json +++ b/apps/playground/tsconfig.json @@ -2,19 +2,25 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "composite": true, - "target": "ES2020", "outDir": "./dist", - "experimentalDecorators": true, - "module": "ESNext", - "moduleResolution": "bundler", - "useDefineForClassFields": false, "paths": { - "@/*": [ - "./src/*" + "@konoplayer/core/*": [ + "../../packages/core/src/*" + ], + "@konoplayer/matroska/*": [ + "../../packages/matroska/src/*" ] } }, "include": [ "src" + ], + "references": [ + { + "path": "../../packages/core" + }, + { + "path": "../../packages/matroska" + } ] } \ No newline at end of file diff --git a/apps/proxy/package.json b/apps/proxy/package.json index 70e6da9..4992474 100644 --- a/apps/proxy/package.json +++ b/apps/proxy/package.json @@ -1,5 +1,5 @@ { - "name": "proxy", + "name": "@konoplayer/proxy", "version": "0.1.0", "private": true, "scripts": { diff --git a/apps/test/.vitest/results.json b/apps/test/.vitest/results.json new file mode 100644 index 0000000..1638cf2 --- /dev/null +++ b/apps/test/.vitest/results.json @@ -0,0 +1 @@ +{"version":"3.0.9","results":[[":src/matroska/codecs/av1.spec.ts",{"duration":52.71331099999952,"failed":false}]]} \ No newline at end of file diff --git a/apps/test/package.json b/apps/test/package.json new file mode 100644 index 0000000..cfb9774 --- /dev/null +++ b/apps/test/package.json @@ -0,0 +1,17 @@ +{ + "name": "@konoplayer/test", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": {}, + "dependencies": { + "@konoplayer/core": "workspace:*", + "@konoplayer/matroska": "workspace:*", + "konoebml": "^0.1.2" + }, + "devDependencies": { + "unplugin-swc": "^1.5.1", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.0.9" + } +} \ No newline at end of file diff --git a/apps/test/resources/.gitignore b/apps/test/resources/.gitignore new file mode 100644 index 0000000..e9998b6 --- /dev/null +++ b/apps/test/resources/.gitignore @@ -0,0 +1,2 @@ +video/huge/* +!video/huge/.gitkeep \ No newline at end of file diff --git a/apps/test/resources/video/test-av1.mkv b/apps/test/resources/video/test-av1.mkv new file mode 100644 index 0000000..e6620a9 Binary files /dev/null and b/apps/test/resources/video/test-av1.mkv differ diff --git a/apps/test/resources/video/test-avc.mkv b/apps/test/resources/video/test-avc.mkv new file mode 100644 index 0000000..1019107 Binary files /dev/null and b/apps/test/resources/video/test-avc.mkv differ diff --git a/apps/test/resources/video/test-hevc.mkv b/apps/test/resources/video/test-hevc.mkv new file mode 100644 index 0000000..29abff8 Binary files /dev/null and b/apps/test/resources/video/test-hevc.mkv differ diff --git a/apps/test/resources/video/test-theora.mkv b/apps/test/resources/video/test-theora.mkv new file mode 100644 index 0000000..58f4b4c Binary files /dev/null and b/apps/test/resources/video/test-theora.mkv differ diff --git a/apps/test/resources/video/test-vp8.mkv b/apps/test/resources/video/test-vp8.mkv new file mode 100644 index 0000000..8d8acc7 Binary files /dev/null and b/apps/test/resources/video/test-vp8.mkv differ diff --git a/apps/test/resources/video/test-vp9.mkv b/apps/test/resources/video/test-vp9.mkv new file mode 100644 index 0000000..3d0db7f Binary files /dev/null and b/apps/test/resources/video/test-vp9.mkv differ diff --git a/apps/test/resources/video/test.webm b/apps/test/resources/video/test.webm new file mode 100644 index 0000000..6c2138d Binary files /dev/null and b/apps/test/resources/video/test.webm differ diff --git a/apps/playground/src/media/mkv/codecs/av1.ts b/apps/test/src/init-test.ts similarity index 100% rename from apps/playground/src/media/mkv/codecs/av1.ts rename to apps/test/src/init-test.ts diff --git a/apps/test/src/matroska/codecs/av1.spec.ts b/apps/test/src/matroska/codecs/av1.spec.ts new file mode 100644 index 0000000..f7146c4 --- /dev/null +++ b/apps/test/src/matroska/codecs/av1.spec.ts @@ -0,0 +1,47 @@ +import { SegmentSchema, SegmentType } from '@konoplayer/matroska/schema'; +import { VideoCodecId } from '@konoplayer/matroska/codecs'; +import { + parseAV1DecoderConfigurationRecord, + genCodecStringByAV1DecoderConfigurationRecord, +} from '@konoplayer/matroska/codecs/av1'; +import { loadComponentFromRangedResource } from '../utils/data'; +import { EbmlTagIdEnum, EbmlTagPosition } from 'konoebml'; +import { isTagIdPos } from '@konoplayer/matroska/util'; + +describe('AV1 code test', () => { + it('should parse av1 meta from track entry', async () => { + const [segment] = await loadComponentFromRangedResource({ + resource: 'video/test-av1.mkv', + predicate: isTagIdPos(EbmlTagIdEnum.Segment, EbmlTagPosition.End), + schema: SegmentSchema, + }); + + const av1Track = segment.Tracks?.TrackEntry.find( + (t) => t.CodecID === VideoCodecId.AV1 + )!; + + expect(av1Track).toBeDefined(); + + expect(av1Track.CodecPrivate).toBeDefined(); + + const meta = parseAV1DecoderConfigurationRecord(av1Track)!; + + expect(meta).toBeDefined(); + + const codecStr = genCodecStringByAV1DecoderConfigurationRecord(meta); + + expect(meta.marker).toBe(1); + expect(meta.version).toBe(1); + expect(meta.seqProfile).toBe(0); + expect(meta.seqLevelIdx0).toBe(1); + expect(meta.seqTier0).toBe(0); + expect(meta.highBitdepth).toBe(0); + expect(meta.monochrome).toBe(0); + expect( + `${meta.chromaSubsamplingX}${meta.chromaSubsamplingY}${meta.chromaSamplePosition}` + ).toBe('110'); + expect(meta.initialPresentationDelayMinus1).toBeUndefined(); + + expect(codecStr).toBe('av01.0.01M.08.0.110'); + }); +}); diff --git a/apps/test/src/matroska/codecs/avc.spec.ts b/apps/test/src/matroska/codecs/avc.spec.ts new file mode 100644 index 0000000..f8079a9 --- /dev/null +++ b/apps/test/src/matroska/codecs/avc.spec.ts @@ -0,0 +1,40 @@ +import { SegmentSchema, SegmentType } from '@konoplayer/matroska/schema'; +import { VideoCodecId } from '@konoplayer/matroska/codecs'; +import { + parseAVCDecoderConfigurationRecord, + genCodecStringByAVCDecoderConfigurationRecord, +} from '@konoplayer/matroska/codecs/avc'; +import { loadComponentFromRangedResource } from '../utils/data'; +import { EbmlTagIdEnum, EbmlTagPosition } from 'konoebml'; +import { isTagIdPos } from '@konoplayer/matroska/util'; + +describe('AVC code test', () => { + it('should parse avc meta from track entry', async () => { + const [segment] = await loadComponentFromRangedResource({ + resource: 'video/test-avc.mkv', + predicate: isTagIdPos(EbmlTagIdEnum.Segment, EbmlTagPosition.End), + schema: SegmentSchema, + }); + + const avcTrack = segment.Tracks?.TrackEntry.find( + (t) => t.CodecID === VideoCodecId.H264 + )!; + + expect(avcTrack).toBeDefined(); + + expect(avcTrack.CodecPrivate).toBeDefined(); + + const meta = parseAVCDecoderConfigurationRecord(avcTrack)!; + + expect(meta).toBeDefined(); + + const codecStr = genCodecStringByAVCDecoderConfigurationRecord(meta); + + expect(meta.configurationVersion).toBe(1); + expect(meta.avcProfileIndication).toBe(100); + expect(meta.profileCompatibility).toBe(0); + expect(meta.avcLevelIndication).toBe(30); + + expect(codecStr).toBe('avc1.64001e'); + }); +}); diff --git a/apps/test/src/matroska/codecs/hevc.spec.ts b/apps/test/src/matroska/codecs/hevc.spec.ts new file mode 100644 index 0000000..85f77d0 --- /dev/null +++ b/apps/test/src/matroska/codecs/hevc.spec.ts @@ -0,0 +1,106 @@ +import { SegmentSchema, SegmentType } from '@konoplayer/matroska/schema'; +import { VideoCodecId } from '@konoplayer/matroska/codecs'; +import { + parseHEVCDecoderConfigurationRecord, + genCodecStringByHEVCDecoderConfigurationRecord, + HEVCDecoderConfigurationRecordType, +} from '@konoplayer/matroska/codecs/hevc'; +import { loadComponentFromRangedResource } from '../utils/data'; +import { EbmlTagIdEnum, EbmlTagPosition } from 'konoebml'; +import { isTagIdPos } from '@konoplayer/matroska/util'; +import { assert } from 'vitest'; + +describe('HEVC codec test', () => { + it('should parse hevc meta from track entry', async () => { + const [segment] = await loadComponentFromRangedResource({ + resource: 'video/test-hevc.mkv', + predicate: isTagIdPos(EbmlTagIdEnum.Segment, EbmlTagPosition.End), + schema: SegmentSchema, + }); + + const hevcTrack = segment.Tracks?.TrackEntry.find( + (t) => t.CodecID === VideoCodecId.HEVC + )!; + + expect(hevcTrack).toBeDefined(); + + expect(hevcTrack.CodecPrivate).toBeDefined(); + + const meta = parseHEVCDecoderConfigurationRecord(hevcTrack); + + expect(meta).toBeDefined(); + + const codecStr = genCodecStringByHEVCDecoderConfigurationRecord(meta); + + expect(codecStr).toBe('hev1.1.6.L63.90'); + }); + + it('should match chrome test suite', () => { + function makeHEVCParameterSet( + generalProfileSpace: number, + generalProfileIDC: number, + generalProfileCompatibilityFlags: number, + generalTierFlag: number, + generalConstraintIndicatorFlags: [ + number, + number, + number, + number, + number, + number, + ], + generalLevelIDC: number + ) { + return { + generalProfileSpace: generalProfileSpace, + generalProfileIdc: generalProfileIDC, + generalProfileCompatibilityFlags: generalProfileCompatibilityFlags, + generalTierFlag: generalTierFlag, + generalConstraintIndicatorFlags: Number( + new DataView( + new Uint8Array([0, 0, ...generalConstraintIndicatorFlags]).buffer + ).getBigUint64(0, false) + ), + generalLevelIdc: generalLevelIDC, + } as unknown as HEVCDecoderConfigurationRecordType; + } + + assert( + genCodecStringByHEVCDecoderConfigurationRecord( + makeHEVCParameterSet(0, 1, 0x60000000, 0, [0, 0, 0, 0, 0, 0], 93) + ), + 'hev1.1.6.L93' + ); + assert( + genCodecStringByHEVCDecoderConfigurationRecord( + makeHEVCParameterSet(1, 4, 0x82000000, 1, [0, 0, 0, 0, 0, 0], 120) + ), + 'hev1.A4.41.H120' + ); + assert( + genCodecStringByHEVCDecoderConfigurationRecord( + makeHEVCParameterSet(0, 1, 0x60000000, 0, [176, 0, 0, 0, 0, 0], 93) + ), + 'hev1.1.6.L93.B0' + ); + assert( + genCodecStringByHEVCDecoderConfigurationRecord( + makeHEVCParameterSet(1, 4, 0x82000000, 1, [176, 35, 0, 0, 0, 0], 120) + ), + 'hev1.A4.41.H120.B0.23' + ); + assert( + genCodecStringByHEVCDecoderConfigurationRecord( + makeHEVCParameterSet( + 2, + 1, + 0xf77db57b, + 1, + [18, 52, 86, 120, 154, 188], + 254 + ) + ), + 'hev1.B1.DEADBEEF.H254.12.34.56.78.9A.BC' + ); + }); +}); diff --git a/apps/test/src/matroska/codecs/vp9.spec.ts b/apps/test/src/matroska/codecs/vp9.spec.ts new file mode 100644 index 0000000..1a9a0a0 --- /dev/null +++ b/apps/test/src/matroska/codecs/vp9.spec.ts @@ -0,0 +1,54 @@ +import { SegmentSchema, SegmentType } from '@konoplayer/matroska/schema'; +import { VideoCodecId } from '@konoplayer/matroska/codecs'; +import { + genCodecStringByVP9DecoderConfigurationRecord, + parseVP9DecoderConfigurationRecord, + VP9ColorSpaceEnum, + VP9Subsampling, +} from '@konoplayer/matroska/codecs/vp9'; +import { loadComponentFromRangedResource } from '../utils/data'; +import { EbmlTagIdEnum, EbmlTagPosition } from 'konoebml'; +import { isTagIdPos } from '@konoplayer/matroska/util'; + +describe('VP9 code test', () => { + it('should parse vp9 meta from track entry and keyframe', async () => { + const [segment] = await loadComponentFromRangedResource({ + resource: 'video/test-vp9.mkv', + predicate: isTagIdPos(EbmlTagIdEnum.Segment, EbmlTagPosition.End), + schema: SegmentSchema, + }); + + const vp9Track = segment.Tracks?.TrackEntry.find( + (t) => t.CodecID === VideoCodecId.VP9 + )!; + + expect(vp9Track).toBeDefined(); + + expect(vp9Track.CodecPrivate).toBeFalsy(); + + const keyframe = segment + .Cluster!.flatMap((c) => c.SimpleBlock || []) + .find((b) => b.keyframe && b.track === vp9Track.TrackNumber)!; + + expect(keyframe).toBeDefined(); + expect(keyframe.frames.length).toBe(1); + + const meta = parseVP9DecoderConfigurationRecord( + vp9Track, + keyframe.frames[0] + )!; + + expect(meta).toBeDefined(); + + expect(meta.bitDepth).toBe(8); + expect(meta.subsampling).toBe(VP9Subsampling.YUV420); + expect(meta.width).toBe(640); + expect(meta.height).toBe(360); + expect(meta.colorSpace).toBe(VP9ColorSpaceEnum.BT_601); + expect(meta.profile).toBe(0); + + const codecStr = genCodecStringByVP9DecoderConfigurationRecord(meta); + + expect(codecStr).toBe('vp09.00.21.08'); + }); +}); diff --git a/apps/test/src/matroska/utils/data.ts b/apps/test/src/matroska/utils/data.ts new file mode 100644 index 0000000..9734804 --- /dev/null +++ b/apps/test/src/matroska/utils/data.ts @@ -0,0 +1,56 @@ +import { Type } from 'arktype'; +import { EbmlStreamDecoder, EbmlTagPosition, EbmlTagType } from 'konoebml'; +import { convertEbmlTagToComponent } from '@konoplayer/matroska/util'; +import fs from 'node:fs'; +import { Readable } from 'node:stream'; +import { TransformStream } from 'node:stream/web'; +import path from 'node:path'; + +export interface LoadRangedResourceOptions = any> { + resource: string; + byteStart?: number; + byteEnd?: number; + schema?: S; + predicate?: (tag: EbmlTagType) => boolean; +} + +export async function loadComponentFromRangedResource< + T, + S extends Type = any, +>({ + resource, + byteStart, + byteEnd, + predicate = (tag) => !tag?.parent && tag.position !== EbmlTagPosition.Start, + schema, +}: LoadRangedResourceOptions): Promise { + const input = Readable.toWeb( + fs.createReadStream( + path.join(import.meta.dirname, '..', '..', '..', 'resources', resource), + { + start: byteStart, + end: byteEnd, + } + ) + ); + + const output = input.pipeThrough( + new EbmlStreamDecoder({ + streamStartOffset: byteStart, + collectChild: true, + }) as unknown as TransformStream + ); + + const result: T[] = []; + + for await (const t of output) { + if (predicate(t)) { + let component = convertEbmlTagToComponent(t) as T; + if (schema) { + component = schema.assert(component); + } + result.push(component); + } + } + return result; +} diff --git a/apps/test/tsconfig.json b/apps/test/tsconfig.json new file mode 100644 index 0000000..251248e --- /dev/null +++ b/apps/test/tsconfig.json @@ -0,0 +1,30 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist", + "types": [ + "vitest/globals", + "node" + ], + "paths": { + "@konoplayer/core/*": [ + "../../packages/core/src/*" + ], + "@konoplayer/matroska/*": [ + "../../packages/matroska/src/*" + ] + } + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../../packages/core" + }, + { + "path": "../../packages/matroska" + } + ] +} \ No newline at end of file diff --git a/apps/test/vitest.config.ts b/apps/test/vitest.config.ts new file mode 100644 index 0000000..3addbbb --- /dev/null +++ b/apps/test/vitest.config.ts @@ -0,0 +1,33 @@ +import swc from 'unplugin-swc'; +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + cacheDir: '.vitest', + test: { + setupFiles: ['src/init-test.ts'], + environment: 'happy-dom', + include: ['src/**/*.spec.ts'], + globals: true, + restoreMocks: true, + coverage: { + // you can include other reporters, but 'json-summary' is required, json is recommended + reporter: ['text', 'json-summary', 'json'], + // If you want a coverage reports even if your tests are failing, include the reportOnFailure option + reportOnFailure: true, + include: ['../../packages/core/src/**', '../../packages/matroska/src/**'], + }, + }, + plugins: [ + tsconfigPaths(), + swc.vite({ + include: /\.[mc]?[jt]sx?$/, + // for git+ package only + exclude: [ + /node_modules\/(?!@konoplayer|\.pnpm)/, + /node_modules\/\.pnpm\/(?!@konoplayer)/, + ] as any, + tsconfigFile: './tsconfig.json', + }), + ], +}); diff --git a/biome.jsonc b/biome.jsonc index 2e7d645..beae7bf 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -17,7 +17,13 @@ "noSvgWithoutTitle": "off" }, "complexity": { - "noBannedTypes": "off" + "noBannedTypes": "off", + "noExcessiveCognitiveComplexity": { + "level": "warn", + "options": { + "maxAllowedComplexity": 40 + } + } }, "nursery": { "noEnum": "off", @@ -61,6 +67,33 @@ } } } + }, + { + "include": [ + "apps/test/**" + ], + "javascript": { + "globals": [ + "describe", + "beforeEach", + "it", + "expect", + "afterEach" + ] + }, + "linter": { + "rules": { + "style": { + "useImportType": "off" + }, + "suspicious": { + "noConsole": "off" + }, + "performance": { + "useTopLevelRegex": "off" + } + } + } } ] } \ No newline at end of file diff --git a/justfile b/justfile index 243d287..986d459 100644 --- a/justfile +++ b/justfile @@ -2,10 +2,10 @@ set windows-shell := ["pwsh.exe", "-c"] set dotenv-load := true dev-playground: - pnpm run --filter=playground dev + pnpm run --filter=@konoplayer/playground dev dev-proxy: - pnpm run --filter proxy --filter mock dev + pnpm run --filter=@konoplayer/proxy --filter=@konoplayer/mock dev download-samples: pnpm run download-samples \ No newline at end of file diff --git a/packages/codecs/package.json b/packages/codecs/package.json deleted file mode 100644 index 7aa368c..0000000 --- a/packages/codecs/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "konoplayer-codecs", - "version": "0.1.0", - "private": true, - "type": "module", - "scripts": { - "build": "rsbuild build", - "dev": "rsbuild dev", - "preview": "rsbuild preview" - }, - "dependencies": { - "konoebml": "0.1.2-rc.5", - "lit": "^3.2.1" - }, - "devDependencies": { - "@rsbuild/core": "^1.2.14", - "typescript": "^5.8.2" - } -} \ No newline at end of file diff --git a/packages/codecs/src/index.ts b/packages/codecs/src/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..2f453c6 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,8 @@ +{ + "name": "@konoplayer/core", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": {}, + "dependencies": {} +} \ No newline at end of file diff --git a/apps/playground/src/media/base/audio_codecs.ts b/packages/core/src/codecs/audio-codecs.ts similarity index 100% rename from apps/playground/src/media/base/audio_codecs.ts rename to packages/core/src/codecs/audio-codecs.ts diff --git a/packages/core/src/codecs/index.ts b/packages/core/src/codecs/index.ts new file mode 100644 index 0000000..2c57f1b --- /dev/null +++ b/packages/core/src/codecs/index.ts @@ -0,0 +1,2 @@ +export { AudioCodec } from './audio-codecs'; +export { VideoCodec } from './video-codecs'; diff --git a/apps/playground/src/media/base/video_codecs.ts b/packages/core/src/codecs/video-codecs.ts similarity index 100% rename from apps/playground/src/media/base/video_codecs.ts rename to packages/core/src/codecs/video-codecs.ts diff --git a/packages/core/src/data/bit.ts b/packages/core/src/data/bit.ts new file mode 100644 index 0000000..88c6e19 --- /dev/null +++ b/packages/core/src/data/bit.ts @@ -0,0 +1,39 @@ +export class BitReader { + private data: Uint8Array; + private byteOffset = 0; + private bitOffset = 0; + + constructor(data: Uint8Array) { + this.data = data; + } + + readBits(numBits: number): number { + let value = 0; + for (let i = 0; i < numBits; i++) { + const bit = (this.data[this.byteOffset] >> (7 - this.bitOffset)) & 1; + value = (value << 1) | bit; + this.bitOffset++; + if (this.bitOffset === 8) { + this.bitOffset = 0; + this.byteOffset++; + } + } + return value; + } + + skipBits(numBits: number): void { + this.bitOffset += numBits; + while (this.bitOffset >= 8) { + this.bitOffset -= 8; + this.byteOffset++; + } + } + + hasData(): boolean { + return this.byteOffset < this.data.length; + } + + getRemainingBytes(): Uint8Array { + return this.data.slice(this.byteOffset); + } +} diff --git a/apps/playground/src/fetch.ts b/packages/core/src/data/fetch.ts similarity index 100% rename from apps/playground/src/fetch.ts rename to packages/core/src/data/fetch.ts diff --git a/packages/core/src/data/index.ts b/packages/core/src/data/index.ts new file mode 100644 index 0000000..b42b9ec --- /dev/null +++ b/packages/core/src/data/index.ts @@ -0,0 +1,6 @@ +export { + type RangedStream, + type CreateRangedStreamOptions, + createRangedStream, +} from './fetch'; +export { BitReader } from './bit'; diff --git a/apps/playground/src/media/base/errors.ts b/packages/core/src/errors.ts similarity index 83% rename from apps/playground/src/media/base/errors.ts rename to packages/core/src/errors.ts index e4a9946..1bdec05 100644 --- a/apps/playground/src/media/base/errors.ts +++ b/packages/core/src/errors.ts @@ -4,7 +4,7 @@ export class UnsupportedCodecError extends Error { } } -export class ParseCodecPrivateError extends Error { +export class ParseCodecError extends Error { constructor(codec: string, detail: string) { super(`code ${codec} private parse failed: ${detail}`); } @@ -12,7 +12,7 @@ export class ParseCodecPrivateError extends Error { export class UnreachableOrLogicError extends Error { constructor(detail: string) { - super(`unreachable or logic error: ${detail}`) + super(`unreachable or logic error: ${detail}`); } } @@ -22,4 +22,4 @@ export class ParseCodecErrors extends Error { constructor() { super('failed to parse codecs'); } -} \ No newline at end of file +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..6b3980b --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist" + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/packages/codecs/Cargo.toml b/packages/ffmpeg/Cargo.toml similarity index 82% rename from packages/codecs/Cargo.toml rename to packages/ffmpeg/Cargo.toml index 5b0d574..63571f2 100644 --- a/packages/codecs/Cargo.toml +++ b/packages/ffmpeg/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "konoplayer-codecs" +name = "konoplayer-ffmpeg" version = "0.1.0" edition = "2024" diff --git a/packages/codecs/src/lib.rs b/packages/ffmpeg/src/lib.rs similarity index 100% rename from packages/codecs/src/lib.rs rename to packages/ffmpeg/src/lib.rs diff --git a/packages/matroska/package.json b/packages/matroska/package.json new file mode 100644 index 0000000..8068f32 --- /dev/null +++ b/packages/matroska/package.json @@ -0,0 +1,11 @@ +{ + "name": "@konoplayer/matroska", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": {}, + "dependencies": { + "@konoplayer/core": "workspace:*", + "konoebml": "^0.1.2" + } +} \ No newline at end of file diff --git a/apps/playground/src/media/mkv/codecs/aac.ts b/packages/matroska/src/codecs/aac.ts similarity index 94% rename from apps/playground/src/media/mkv/codecs/aac.ts rename to packages/matroska/src/codecs/aac.ts index 8eed1b0..de0741e 100644 --- a/apps/playground/src/media/mkv/codecs/aac.ts +++ b/packages/matroska/src/codecs/aac.ts @@ -1,4 +1,4 @@ -import { ParseCodecPrivateError } from '@/media/base/errors'; +import { ParseCodecError } from '@konoplayer/core/errors'; import { ArkErrors, type } from 'arktype'; export const AAC_CODEC_TYPE = 'AAC'; @@ -28,10 +28,7 @@ export function parseAudioSpecificConfig( codecPrivate: Uint8Array ): AudioSpecificConfigType { if (codecPrivate.length < 2) { - throw new ParseCodecPrivateError( - AAC_CODEC_TYPE, - 'codec_private data too short' - ); + throw new ParseCodecError(AAC_CODEC_TYPE, 'codec_private data too short'); } // Create a DataView for bit-level manipulation @@ -95,7 +92,7 @@ export function parseAudioSpecificConfig( // Validate with arktype const validation = AudioSpecificConfigSchema(config); if (validation instanceof ArkErrors) { - const error = new ParseCodecPrivateError( + const error = new ParseCodecError( AAC_CODEC_TYPE, 'Invalid AudioSpecificConfig' ); diff --git a/packages/matroska/src/codecs/av1.ts b/packages/matroska/src/codecs/av1.ts new file mode 100644 index 0000000..c12eabf --- /dev/null +++ b/packages/matroska/src/codecs/av1.ts @@ -0,0 +1,167 @@ +import { BitReader } from '@konoplayer/core/data'; +import { type } from 'arktype'; +import type { TrackEntryType } from '../schema'; +import { ParseCodecError } from '@konoplayer/core/errors'; + +export const AV1_CODEC_TYPE = 'AV1'; + +export const AV1DecoderConfigurationRecordSchema = type({ + marker: type.number, // 1 bit, must be 1 + version: type.number, // 7 bits, must be 1 + seqProfile: type.number, // 3 bits, seq profile (0-7) + seqLevelIdx0: type.number, // 5 bits, seq level (0-31) + seqTier0: type.number, // 1 bit, tier (0 or 1) + highBitdepth: type.number, // 1 bit, high or low + twelveBit: type.number, // 1 bit, if 12-bit + monochrome: type.number, // 1 bit, if mono chrome + chromaSubsamplingX: type.number, // 1 bit, sub sampling X + chromaSubsamplingY: type.number, // 1 bit, sub sampling Y + chromaSamplePosition: type.number, // 2 bits + initialPresentationDelayPresent: type.number, // 1 bit + initialPresentationDelayMinus1: type.number.optional(), // 4 bits, optoinal + configOBUs: type.instanceOf(Uint8Array), // remain OBU data +}); + +export type AV1DecoderConfigurationRecordType = + typeof AV1DecoderConfigurationRecordSchema.infer; + +/** + * [webkit impl](https://github.com/movableink/webkit/blob/7e43fe7000b319ce68334c09eed1031642099726/Source/WebCore/platform/graphics/AV1Utilities.cpp#L48) + */ +export function parseAV1DecoderConfigurationRecord( + track: TrackEntryType +): AV1DecoderConfigurationRecordType { + const codecPrivate = track.CodecPrivate; + + if (!codecPrivate) { + throw new ParseCodecError( + AV1_CODEC_TYPE, + 'CodecPrivate of AVC Track is missing' + ); + } + + if (codecPrivate.length < 4) { + throw new ParseCodecError( + AV1_CODEC_TYPE, + 'Input data too short for AV1DecoderConfigurationRecord' + ); + } + + const reader = new BitReader(codecPrivate); + + // Byte 0 + const marker = reader.readBits(1); + const version = reader.readBits(7); + if (marker !== 1 || version !== 1) { + throw new ParseCodecError( + AV1_CODEC_TYPE, + `Invalid marker (${marker}) or version (${version})` + ); + } + + const seqProfile = reader.readBits(3); + const seqLevelIdx0 = reader.readBits(5); + + // Byte 1 + const seqTier0 = reader.readBits(1); + const highBitdepth = reader.readBits(1); + const twelveBit = reader.readBits(1); + const monochrome = reader.readBits(1); + const chromaSubsamplingX = reader.readBits(1); + const chromaSubsamplingY = reader.readBits(1); + const chromaSamplePosition = reader.readBits(2); + + // Byte 2 + const reserved1 = reader.readBits(3); + if (reserved1 !== 0) { + throw new ParseCodecError( + AV1_CODEC_TYPE, + `Reserved bits must be 0, got ${reserved1}` + ); + } + const initialPresentationDelayPresent = reader.readBits(1); + let initialPresentationDelayMinus1: number | undefined; + if (initialPresentationDelayPresent) { + initialPresentationDelayMinus1 = reader.readBits(4); + } else { + const reserved2 = reader.readBits(4); + if (reserved2 !== 0) { + throw new ParseCodecError( + AV1_CODEC_TYPE, + `Reserved bits must be 0, got ${reserved2}` + ); + } + } + + // remain bytes as configOBUs + const configOBUs = reader.getRemainingBytes(); + + return { + marker, + version, + seqProfile, + seqLevelIdx0, + seqTier0, + highBitdepth, + twelveBit, + monochrome, + chromaSubsamplingX, + chromaSubsamplingY, + chromaSamplePosition, + initialPresentationDelayPresent, + initialPresentationDelayMinus1, + configOBUs, + }; +} + +/** + * [webkit impl](https://github.com/movableink/webkit/blob/7e43fe7000b319ce68334c09eed1031642099726/Source/WebCore/platform/graphics/AV1Utilities.cpp#L197) + */ +export function genCodecStringByAV1DecoderConfigurationRecord( + config: AV1DecoderConfigurationRecordType +): string { + const parts: string[] = []; + + // Prefix + parts.push('av01'); + + // Profile + parts.push(config.seqProfile.toString()); + + // Level and Tier + const levelStr = config.seqLevelIdx0.toString().padStart(2, '0'); + const tierStr = config.seqTier0 === 0 ? 'M' : 'H'; + parts.push(`${levelStr}${tierStr}`); + + // Bit Depth + let bitDepthStr: string; + if (config.highBitdepth === 0) { + bitDepthStr = '08'; // 8-bit + } else if (config.twelveBit === 0) { + bitDepthStr = '10'; // 10-bit + } else { + bitDepthStr = '12'; // 12-bit + } + parts.push(bitDepthStr); + + // Monochrome + parts.push(config.monochrome.toString()); + + // Chroma Subsampling + const chromaSubsampling = `${config.chromaSubsamplingX}${config.chromaSubsamplingY}${config.chromaSamplePosition}`; + parts.push(chromaSubsampling); + + // Initial Presentation Delay(optional) + if ( + config.initialPresentationDelayPresent === 1 && + config.initialPresentationDelayMinus1 !== undefined + ) { + const delay = (config.initialPresentationDelayMinus1 + 1) + .toString() + .padStart(2, '0'); + parts.push(delay); + } + + // joined + return parts.join('.'); +} diff --git a/apps/playground/src/media/mkv/codecs/avc.ts b/packages/matroska/src/codecs/avc.ts similarity index 57% rename from apps/playground/src/media/mkv/codecs/avc.ts rename to packages/matroska/src/codecs/avc.ts index bcb6108..7362ab3 100644 --- a/apps/playground/src/media/mkv/codecs/avc.ts +++ b/packages/matroska/src/codecs/avc.ts @@ -1,5 +1,6 @@ -import { ParseCodecPrivateError } from '@/media/base/errors'; +import { ParseCodecError } from '@konoplayer/core/errors'; import { type } from 'arktype'; +import type { TrackEntryType } from '../schema'; export const AVC_CODEC_TYPE = 'h264(AVC)'; @@ -23,46 +24,72 @@ export type AVCDecoderConfigurationRecordType = typeof AVCDecoderConfigurationRecordSchema.infer; /** - * Parse AVCDecoderConfigurationRecord from codec_private Uint8Array - * @param codecPrivate - Uint8Array containing codec_private data - * @returns Parsed AVCDecoderConfigurationRecord or throws an error if invalid + * + * @see [webkit](https://github.com/movableink/webkit/blob/7e43fe7000b319ce68334c09eed1031642099726/Source/WebCore/platform/graphics/HEVCUtilities.cpp#L84) */ export function parseAVCDecoderConfigurationRecord( - codecPrivate: Uint8Array + track: TrackEntryType ): AVCDecoderConfigurationRecordType { - let offset = 0; + // ISO/IEC 14496-10:2014 + // 7.3.2.1.1 Sequence parameter set data syntax + const codecPrivate = track.CodecPrivate; - // Check if data length is sufficient - if (codecPrivate.length < 5) { - throw new ParseCodecPrivateError( + if (!codecPrivate) { + throw new ParseCodecError( + AVC_CODEC_TYPE, + 'CodecPrivate of AVC Track is missing' + ); + } + + // AVCDecoderConfigurationRecord is at a minimum 24 bytes long + if (codecPrivate.length < 24) { + throw new ParseCodecError( AVC_CODEC_TYPE, 'Input data too short for AVCDecoderConfigurationRecord' ); } - const configurationVersion = codecPrivate[offset++]; - const avcProfileIndication = codecPrivate[offset++]; - const profileCompatibility = codecPrivate[offset++]; - const avcLevelIndication = codecPrivate[offset++]; + const view = new DataView(codecPrivate.buffer); + let offset = 0; + + const readUint8 = (move: boolean) => { + const result = view.getUint8(offset); + if (move) { + offset += 1; + } + return result; + }; + + const readUint16 = (move: boolean) => { + const result = view.getUint16(offset, false); + if (move) { + offset += 2; + } + return result; + }; + + const configurationVersion = readUint8(true); + const avcProfileIndication = readUint8(true); + const profileCompatibility = readUint8(true); + const avcLevelIndication = readUint8(true); // Read lengthSizeMinusOne (first 6 bits are reserved, typically 0xFF, last 2 bits are the value) - const lengthSizeMinusOne = codecPrivate[offset++] & 0x03; + const lengthSizeMinusOne = readUint8(true) & 0x03; // Read number of SPS (first 3 bits are reserved, typically 0xE0, last 5 bits are SPS count) - const numOfSPS = codecPrivate[offset++] & 0x1f; + const numOfSPS = readUint8(true) & 0x1f; const sps: Uint8Array[] = []; // Parse SPS for (let i = 0; i < numOfSPS; i++) { if (offset + 2 > codecPrivate.length) { - throw new ParseCodecPrivateError(AVC_CODEC_TYPE, 'Invalid SPS length'); + throw new ParseCodecError(AVC_CODEC_TYPE, 'Invalid SPS length'); } - const spsLength = (codecPrivate[offset] << 8) | codecPrivate[offset + 1]; - offset += 2; + const spsLength = readUint16(true); if (offset + spsLength > codecPrivate.length) { - throw new ParseCodecPrivateError( + throw new ParseCodecError( AVC_CODEC_TYPE, 'SPS data exceeds buffer length' ); @@ -74,22 +101,21 @@ export function parseAVCDecoderConfigurationRecord( // Read number of PPS if (offset >= codecPrivate.length) { - throw new ParseCodecPrivateError(AVC_CODEC_TYPE, 'No space for PPS count'); + throw new ParseCodecError(AVC_CODEC_TYPE, 'No space for PPS count'); } - const numOfPPS = codecPrivate[offset++]; + const numOfPPS = readUint8(true); const pps: Uint8Array[] = []; // Parse PPS for (let i = 0; i < numOfPPS; i++) { if (offset + 2 > codecPrivate.length) { - throw new ParseCodecPrivateError(AVC_CODEC_TYPE, 'Invalid PPS length'); + throw new ParseCodecError(AVC_CODEC_TYPE, 'Invalid PPS length'); } - const ppsLength = (codecPrivate[offset] << 8) | codecPrivate[offset + 1]; - offset += 2; + const ppsLength = readUint16(true); if (offset + ppsLength > codecPrivate.length) { - throw new ParseCodecPrivateError( + throw new ParseCodecError( AVC_CODEC_TYPE, 'PPS data exceeds buffer length' ); @@ -110,16 +136,13 @@ export function parseAVCDecoderConfigurationRecord( }; } -export function genCodecIdByAVCDecoderConfigurationRecord( +export function genCodecStringByAVCDecoderConfigurationRecord( config: AVCDecoderConfigurationRecordType ): string { const profileHex = config.avcProfileIndication.toString(16).padStart(2, '0'); const profileCompatHex = config.profileCompatibility .toString(16) .padStart(2, '0'); - const levelHex = (config.avcLevelIndication / 10) - .toString(16) - .replace(/./g, '') - .padStart(2, '0'); + const levelHex = config.avcLevelIndication.toString(16).padStart(2, '0'); return `avc1.${profileHex}${profileCompatHex}${levelHex}`; } diff --git a/packages/matroska/src/codecs/hevc.ts b/packages/matroska/src/codecs/hevc.ts new file mode 100644 index 0000000..c54a073 --- /dev/null +++ b/packages/matroska/src/codecs/hevc.ts @@ -0,0 +1,214 @@ +import { ParseCodecError } from '@konoplayer/core/errors'; +import { ArkErrors, type } from 'arktype'; +import type { TrackEntryType } from '../schema'; + +export const HEVC_CODEC_TYPE = 'h265(HEVC)'; + +export const HEVCDecoderConfigurationRecordArraySchema = type({ + arrayCompleteness: type.number, + nalUnitType: type.number, + numNalus: type.number, + nalUnit: type.instanceOf(Uint8Array).array(), +}); + +export type HEVCDecoderConfigurationRecordArrayType = + typeof HEVCDecoderConfigurationRecordArraySchema.infer; + +// Define the schema for HEVCDecoderConfigurationRecord +export const HEVCDecoderConfigurationRecordSchema = type({ + configurationVersion: type.number, // Must be 1 + generalProfileSpace: type.number, + generalTierFlag: type.number, + generalProfileIdc: type.number, + generalProfileCompatibilityFlags: type.number, + generalConstraintIndicatorFlags: type.number, + generalLevelIdc: type.number, + minSpatialSegmentationIdc: type.number, + parallelismType: type.number, + chromaFormat: type.number, + bitDepthLumaMinus8: type.number, + bitDepthChromaMinus8: type.number, + avgFrameRate: type.number, + constantFrameRate: type.number, + numTemporalLayers: type.number, + temporalIdNested: type.number, + lengthSizeMinusOne: type.number, + numOfArrays: type.number, + nalUnits: HEVCDecoderConfigurationRecordArraySchema.array(), +}); + +export type HEVCDecoderConfigurationRecordType = + typeof HEVCDecoderConfigurationRecordSchema.infer; + +export function parseHEVCDecoderConfigurationRecord( + track: TrackEntryType +): HEVCDecoderConfigurationRecordType { + const codecPrivate = track.CodecPrivate; + if (!codecPrivate) { + throw new ParseCodecError( + HEVC_CODEC_TYPE, + 'CodecPrivate of HEVC Track is missing' + ); + } + const view = new DataView(codecPrivate.buffer); + let offset = 0; + + const readUint8 = (move: boolean) => { + const result = view.getUint8(offset); + if (move) { + offset += 1; + } + return result; + }; + + const readUint16 = (move: boolean) => { + const result = view.getUint16(offset, false); + if (move) { + offset += 2; + } + return result; + }; + + const readUint48 = (move: boolean) => { + const result = + view.getUint16(offset, false) * 2 ** 32 + + view.getUint32(offset + 2, false); + if (move) { + offset += 6; + } + return result; + }; + + const readUint32 = (move: boolean) => { + const result = view.getUint32(offset, false); + if (move) { + offset += 4; + } + return result; + }; + + // Read and validate basic fields + const config: HEVCDecoderConfigurationRecordType = { + configurationVersion: readUint8(true), + generalProfileSpace: (readUint8(false) & 0xc0) >> 6, + generalTierFlag: (readUint8(false) & 0x20) >> 5, + generalProfileIdc: readUint8(true) & 0x1f, + generalProfileCompatibilityFlags: readUint32(true), + generalConstraintIndicatorFlags: readUint48(true), + generalLevelIdc: readUint8(true), + minSpatialSegmentationIdc: readUint16(true) & 0x0fff, + parallelismType: readUint8(true) & 0x03, + chromaFormat: readUint8(true) & 0x03, + bitDepthLumaMinus8: readUint8(true) & 0x07, + bitDepthChromaMinus8: readUint8(true) & 0x07, + avgFrameRate: readUint16(true), + constantFrameRate: (readUint8(false) & 0xc0) >> 6, + numTemporalLayers: (readUint8(false) & 0x38) >> 3, + temporalIdNested: (readUint8(false) & 0x04) >> 2, + lengthSizeMinusOne: readUint8(true) & 0x03, + numOfArrays: readUint8(true), + nalUnits: [], + }; + + // Parse NAL unit arrays + const arrays = config.nalUnits; + + for (let i = 0; i < config.numOfArrays; i++) { + const array: HEVCDecoderConfigurationRecordArrayType = { + arrayCompleteness: (readUint8(false) & 0x80) >> 7, + nalUnitType: readUint8(true) & 0x3f, + numNalus: readUint16(true), + nalUnit: [] as Uint8Array[], + }; + + for (let j = 0; j < array.numNalus; j++) { + const nalUnitLength = readUint16(true); + array.nalUnit.push(codecPrivate.subarray(offset, offset + nalUnitLength)); + offset += nalUnitLength; + } + arrays.push(array); + } + + // Validate using arktype + const validation = HEVCDecoderConfigurationRecordSchema(config); + if (validation instanceof ArkErrors) { + const error = new ParseCodecError( + HEVC_CODEC_TYPE, + 'Invalid HEVC configuration record' + ); + error.cause = validation; + throw error; + } + + return validation; +} + +function reverseBits32(value: number): number { + let result = 0; + for (let i = 0; i < 32; i++) { + result = (result << 1) | ((value >> i) & 1); + } + return result; +} + +/** + * @see[webkit implementation](https://github.com/movableink/webkit/blob/7e43fe7000b319ce68334c09eed1031642099726/Source/WebCore/platform/graphics/HEVCUtilities.cpp#L204) + */ +export function genCodecStringByHEVCDecoderConfigurationRecord( + config: HEVCDecoderConfigurationRecordType +) { + const result: string[] = []; + + // prefix + result.push(`hev${config.configurationVersion}`); + + // Profile Space + if (config.generalProfileSpace > 0) { + const profileSpaceChar = String.fromCharCode( + 'A'.charCodeAt(0) + config.generalProfileSpace - 1 + ); + result.push(profileSpaceChar + config.generalProfileIdc.toString()); + } else { + result.push(config.generalProfileIdc.toString()); + } + + // Profile Compatibility Flags + const compatFlags = reverseBits32(config.generalProfileCompatibilityFlags) + .toString(16) + .toUpperCase(); + result.push(compatFlags); + + // Tier Flag and Level IDC + const tierPrefix = config.generalTierFlag ? 'H' : 'L'; + result.push(tierPrefix + config.generalLevelIdc.toString()); + + // Constraint Indicator Flags + let constraintBytes: number[]; + if (Array.isArray(config.generalConstraintIndicatorFlags)) { + constraintBytes = config.generalConstraintIndicatorFlags as number[]; + } else { + // split 48 bit integer into 6 byte + const flags = BigInt(config.generalConstraintIndicatorFlags); + constraintBytes = []; + for (let i = 5; i >= 0; i--) { + constraintBytes.push(Number((flags >> BigInt(8 * i)) & BigInt(0xff))); + } + } + + // find last non-zero byte + const lastNonZeroIndex = constraintBytes.reduce( + (last, byte, i) => (byte ? i : last), + -1 + ); + if (lastNonZeroIndex >= 0) { + for (let i = 0; i <= lastNonZeroIndex; i++) { + const byteHex = constraintBytes[i] + .toString(16) + .padStart(2, '0') + .toUpperCase(); + result.push(byteHex); + } + } + + return result.join('.'); +} diff --git a/apps/playground/src/media/mkv/codecs/index.ts b/packages/matroska/src/codecs/index.ts similarity index 67% rename from apps/playground/src/media/mkv/codecs/index.ts rename to packages/matroska/src/codecs/index.ts index f737531..cd87b33 100644 --- a/apps/playground/src/media/mkv/codecs/index.ts +++ b/packages/matroska/src/codecs/index.ts @@ -1,16 +1,26 @@ -import { AudioCodec } from '../../base/audio_codecs'; -import { UnsupportedCodecError } from '../../base/errors'; -import { VideoCodec } from '../../base/video_codecs'; +import { UnsupportedCodecError } from '@konoplayer/core/errors'; +import { VideoCodec, AudioCodec } from '@konoplayer/core/codecs'; import type { TrackEntryType } from '../schema'; import { genCodecIdByAudioSpecificConfig, parseAudioSpecificConfig, } from './aac'; import { - genCodecIdByAVCDecoderConfigurationRecord, + genCodecStringByAVCDecoderConfigurationRecord, parseAVCDecoderConfigurationRecord, } from './avc'; -import type {ProbeInfo} from "@/media/mkv/enhance/probe.ts"; +import { + genCodecStringByAV1DecoderConfigurationRecord, + parseAV1DecoderConfigurationRecord, +} from './av1.ts'; +import { + genCodecStringByHEVCDecoderConfigurationRecord, + parseHEVCDecoderConfigurationRecord, +} from './hevc.ts'; +import { + genCodecStringByVP9DecoderConfigurationRecord, + parseVP9DecoderConfigurationRecord, +} from './vp9.ts'; export const VideoCodecId = { VCM: 'V_MS/VFW/FOURCC', @@ -108,63 +118,85 @@ export type SubtitleCodecIdType = | `${(typeof SubtitleCodecId)[keyof typeof SubtitleCodecId]}` | string; - export interface VideoDecoderConfigExt extends VideoDecoderConfig { - codecType: VideoCodec, + codecType: VideoCodec; } export function videoCodecIdToWebCodecs( track: TrackEntryType, - _probeInfo?: ProbeInfo + keyframe: Uint8Array ): VideoDecoderConfigExt { const codecId = track.CodecID; const codecPrivate = track.CodecPrivate; const shareOptions = { - description: codecPrivate - } + description: codecPrivate, + }; switch (codecId) { case VideoCodecId.HEVC: - return { ...shareOptions, codecType: VideoCodec.HEVC, codec: 'hevc' }; + return { + ...shareOptions, + codecType: VideoCodec.HEVC, + codec: genCodecStringByHEVCDecoderConfigurationRecord( + parseHEVCDecoderConfigurationRecord(track) + ), + }; case VideoCodecId.VP9: - return { ...shareOptions, codecType: VideoCodec.VP9, codec: 'vp09' }; + return { + ...shareOptions, + codecType: VideoCodec.VP9, + codec: genCodecStringByVP9DecoderConfigurationRecord( + parseVP9DecoderConfigurationRecord(track, keyframe) + ), + }; case VideoCodecId.AV1: - return { ...shareOptions, codecType: VideoCodec.AV1, codec: 'av1' }; + return { + ...shareOptions, + codecType: VideoCodec.AV1, + codec: genCodecStringByAV1DecoderConfigurationRecord( + parseAV1DecoderConfigurationRecord(track) + ), + }; case VideoCodecId.H264: - if (!codecPrivate) { - throw new UnsupportedCodecError( - 'h264(without codec_private profile)', - 'web codecs audio decoder' - ); - } return { ...shareOptions, codecType: VideoCodec.H264, - codec: genCodecIdByAVCDecoderConfigurationRecord( - parseAVCDecoderConfigurationRecord(codecPrivate) - ) + codec: genCodecStringByAVCDecoderConfigurationRecord( + parseAVCDecoderConfigurationRecord(track) + ), }; case VideoCodecId.THEORA: return { ...shareOptions, codecType: VideoCodec.Theora, codec: 'theora' }; case VideoCodecId.VP8: return { ...shareOptions, codecType: VideoCodec.VP8, codec: 'vp8' }; case VideoCodecId.MPEG4_ISO_SP: - return { ...shareOptions, codecType: VideoCodec.MPEG4, codec: 'mp4v.01.3' }; + return { + ...shareOptions, + codecType: VideoCodec.MPEG4, + codec: 'mp4v.01.3', + }; case VideoCodecId.MPEG4_ISO_ASP: - return { ...shareOptions, codecType: VideoCodec.MPEG4, codec: 'mp4v.20.9' }; + return { + ...shareOptions, + codecType: VideoCodec.MPEG4, + codec: 'mp4v.20.9', + }; case VideoCodecId.MPEG4_ISO_AP: - return { ...shareOptions, codecType: VideoCodec.MPEG4, codec: 'mp4v.20.9' }; + return { + ...shareOptions, + codecType: VideoCodec.MPEG4, + codec: 'mp4v.20.9', + }; default: throw new UnsupportedCodecError(codecId, 'web codecs video decoder'); } } export interface AudioDecoderConfigExt extends AudioDecoderConfig { - codecType: AudioCodec, + codecType: AudioCodec; } export function audioCodecIdToWebCodecs( - track: TrackEntryType, - _probeInfo?: ProbeInfo + track: TrackEntryType ): AudioDecoderConfigExt { const codecId = track.CodecID; const codecPrivate = track.CodecPrivate; @@ -175,8 +207,8 @@ export function audioCodecIdToWebCodecs( const shareOptions = { numberOfChannels, sampleRate, - description: codecPrivate - } + description: codecPrivate, + }; switch (track.CodecID) { case AudioCodecId.AAC_MPEG4_MAIN: @@ -184,85 +216,94 @@ export function audioCodecIdToWebCodecs( return { ...shareOptions, codecType: AudioCodec.AAC, - codec: 'mp4a.40.1' + codec: 'mp4a.40.1', }; case AudioCodecId.AAC_MPEG2_LC: case AudioCodecId.AAC_MPEG4_LC: return { ...shareOptions, codecType: AudioCodec.AAC, - codec: 'mp4a.40.2' + codec: 'mp4a.40.2', }; case AudioCodecId.AAC_MPEG2_SSR: case AudioCodecId.AAC_MPEG4_SSR: return { ...shareOptions, codecType: AudioCodec.AAC, - codec: 'mp4a.40.3' + codec: 'mp4a.40.3', }; case AudioCodecId.AAC_MPEG4_LTP: return { ...shareOptions, codecType: AudioCodec.AAC, - codec: 'mp4a.40.4' + codec: 'mp4a.40.4', }; case AudioCodecId.AAC_MPEG2_LC_SBR: case AudioCodecId.AAC_MPEG4_SBR: return { ...shareOptions, codecType: AudioCodec.AAC, - codec: 'mp4a.40.5' + codec: 'mp4a.40.5', }; case AudioCodecId.AAC: return { ...shareOptions, codecType: AudioCodec.AAC, codec: codecPrivate - ? genCodecIdByAudioSpecificConfig( - parseAudioSpecificConfig(codecPrivate) - ) : 'mp4a.40.2', + ? genCodecIdByAudioSpecificConfig( + parseAudioSpecificConfig(codecPrivate) + ) + : 'mp4a.40.2', }; case AudioCodecId.AC3: case AudioCodecId.AC3_BSID9: return { ...shareOptions, codecType: AudioCodec.AC3, - codec: 'ac-3' + codec: 'ac-3', }; case AudioCodecId.EAC3: case AudioCodecId.AC3_BSID10: return { ...shareOptions, codecType: AudioCodec.EAC3, - codec: 'ec-3' + codec: 'ec-3', }; case AudioCodecId.MPEG_L3: return { ...shareOptions, codecType: AudioCodec.MP3, - codec: 'mp3' + codec: 'mp3', }; case AudioCodecId.VORBIS: - return { ...shareOptions, codecType: AudioCodec.Vorbis, codec: 'vorbis' } -; + return { ...shareOptions, codecType: AudioCodec.Vorbis, codec: 'vorbis' }; case AudioCodecId.FLAC: - return { ...shareOptions, codecType: AudioCodec.FLAC, codec: 'flac' } -; + return { ...shareOptions, codecType: AudioCodec.FLAC, codec: 'flac' }; case AudioCodecId.OPUS: - return { ...shareOptions, codecType: AudioCodec.Opus, codec: 'opus' } -; + return { ...shareOptions, codecType: AudioCodec.Opus, codec: 'opus' }; case AudioCodecId.ALAC: - return { ...shareOptions, codecType: AudioCodec.ALAC, codec: 'alac' } -; + return { ...shareOptions, codecType: AudioCodec.ALAC, codec: 'alac' }; case AudioCodecId.PCM_INT_BIG: if (bitDepth === 16) { - return { ...shareOptions, codecType: AudioCodec.PCM_S16BE, codec: 'pcm-s16be' }; + return { + ...shareOptions, + codecType: AudioCodec.PCM_S16BE, + codec: 'pcm-s16be', + }; } if (bitDepth === 24) { - return { ...shareOptions, codecType: AudioCodec.PCM_S24BE, codec: 'pcm-s24be' }; + return { + ...shareOptions, + codecType: AudioCodec.PCM_S24BE, + codec: 'pcm-s24be', + }; } if (bitDepth === 32) { - return { ...shareOptions, codecType: AudioCodec.PCM_S32BE, codec: 'pcm-s32be' }; + return { + ...shareOptions, + codecType: AudioCodec.PCM_S32BE, + codec: 'pcm-s32be', + }; } throw new UnsupportedCodecError( `${codecId}(${bitDepth}b)`, @@ -270,20 +311,36 @@ export function audioCodecIdToWebCodecs( ); case AudioCodecId.PCM_INT_LIT: if (bitDepth === 16) { - return { ...shareOptions, codecType: AudioCodec.PCM_S16LE, codec: 'pcm-s16le' }; + return { + ...shareOptions, + codecType: AudioCodec.PCM_S16LE, + codec: 'pcm-s16le', + }; } if (bitDepth === 24) { - return { ...shareOptions, codecType: AudioCodec.PCM_S24LE, codec: 'pcm-s24le' }; + return { + ...shareOptions, + codecType: AudioCodec.PCM_S24LE, + codec: 'pcm-s24le', + }; } if (bitDepth === 32) { - return { ...shareOptions, codecType: AudioCodec.PCM_S32LE, codec: 'pcm-s32le' }; + return { + ...shareOptions, + codecType: AudioCodec.PCM_S32LE, + codec: 'pcm-s32le', + }; } throw new UnsupportedCodecError( `${codecId}(${bitDepth}b)`, 'web codecs audio decoder' ); case AudioCodecId.PCM_FLOAT_IEEE: - return { ...shareOptions, codecType: AudioCodec.PCM_F32LE, codec: 'pcm-f32le' }; + return { + ...shareOptions, + codecType: AudioCodec.PCM_F32LE, + codec: 'pcm-f32le', + }; default: throw new UnsupportedCodecError(codecId, 'web codecs audio decoder'); } diff --git a/packages/matroska/src/codecs/vp9.ts b/packages/matroska/src/codecs/vp9.ts new file mode 100644 index 0000000..368b61a --- /dev/null +++ b/packages/matroska/src/codecs/vp9.ts @@ -0,0 +1,232 @@ +import { type } from 'arktype'; +import type { TrackEntryType } from '../schema'; +import { BitReader } from '@konoplayer/core/data'; +import { ParseCodecError } from '@konoplayer/core/errors'; + +export const VP9_CODEC_TYPE = 'vp9'; + +export enum VP9ColorSpaceEnum { + UNKNOWN = 0, + BT_601 = 1, // eq bt_470bg + BT_709 = 2, + SMPTE_170 = 3, + SMPTE_240 = 4, + BT_2020 = 5, + RESERVED = 6, + SRGB = 7, +} + +export enum VP9YUVRange { + STUDIO_SWING = 0, + FULL_SWING = 1, +} + +export enum VP9Subsampling { + UNKNOWN = 0, + YUV420 = 1, + YUV422 = 2, + YUV440 = 3, + YUV444 = 4, +} + +export const VP9PerformenceLevel = [ + { level: '10', maxSampleRate: 829440, maxResolution: 36864 }, // Level 1 + { level: '11', maxSampleRate: 2764800, maxResolution: 73728 }, // Level 1 + { level: '20', maxSampleRate: 4608000, maxResolution: 122880 }, // Level 2 + { level: '21', maxSampleRate: 9216000, maxResolution: 245760 }, // Level 2.1 + { level: '30', maxSampleRate: 20736000, maxResolution: 552960 }, // Level 3 + { level: '31', maxSampleRate: 36864000, maxResolution: 983040 }, // Level 3.1 + { level: '40', maxSampleRate: 83558400, maxResolution: 2228224 }, // Level 4 + { level: '41', maxSampleRate: 160432128, maxResolution: 2228224 }, // Level 4.1 + { level: '50', maxSampleRate: 311951360, maxResolution: 8912896 }, // Level 5 + { level: '51', maxSampleRate: 588251136, maxResolution: 8912896 }, // Level 5.1 + { level: '52', maxSampleRate: 1176502272, maxResolution: 8912896 }, // Level 5.2 + { level: '60', maxSampleRate: 1176502272, maxResolution: 35651584 }, // Level 6 + { level: '61', maxSampleRate: 2353004544, maxResolution: 35651584 }, // Level 6.1 + { level: '62', maxSampleRate: 4706009088, maxResolution: 35651584 }, // Level 6.2 +]; + +export const VP9DecoderConfigurationRecordSchema = type({ + profile: type.number, // 0 | 1 | 2 | 3, + bitDepth: type.number, // 8 | 10 | 12 + colorSpace: type.number, + subsampling: type.number, // 420 | 422 | 444 + width: type.number, + height: type.number, + yuvRangeFlag: type.number.optional(), + hasScaling: type.boolean, + renderWidth: type.number, + renderHeight: type.number, + frameRate: type.number, // frame per second + estimateLevel: type.string, +}); + +export type VP9DecoderConfigurationRecordType = + typeof VP9DecoderConfigurationRecordSchema.infer; + +export function parseVP9DecoderConfigurationRecord( + track: TrackEntryType, + keyframe: Uint8Array +): VP9DecoderConfigurationRecordType { + const reader = new BitReader(keyframe); + const frameRate = 1_000_000_000 / Number(track.DefaultDuration) || 30; + + // Frame Marker: 2 bits, must be 0b10 + const frameMarker = reader.readBits(2); + if (frameMarker !== 2) { + throw new ParseCodecError(VP9_CODEC_TYPE, 'invalid frame marker'); + } + + // Profile: 2 bits + const version = reader.readBits(1); + const high = reader.readBits(1); + + const profile = (high << 1) + version; + + let reservedZero = 0; + if (profile === 3) { + reservedZero = reader.readBits(1); + if (reservedZero !== 0) { + throw new ParseCodecError( + VP9_CODEC_TYPE, + 'Invalid reserved zero bit for profile 3' + ); + } + } + + // Show Existing Frame: 1 bit + const showExistingFrame = reader.readBits(1); + if (showExistingFrame === 1) { + throw new ParseCodecError(VP9_CODEC_TYPE, 'not a keyframe to parse'); + } + + // Frame Type: 1 bit (0 = keyframe) + const frameType = reader.readBits(1); + if (frameType !== 0) { + throw new ParseCodecError(VP9_CODEC_TYPE, 'not a keyframe to parse'); + } + + // Show Frame and Error Resilient + const _showFrame = reader.readBits(1); + const _errorResilient = reader.readBits(1); + + // Sync Code: 3 bytes (0x49, 0x83, 0x42) + const syncCode = + (reader.readBits(8) << 16) | (reader.readBits(8) << 8) | reader.readBits(8); + if (syncCode !== 0x498342) { + throw new ParseCodecError(VP9_CODEC_TYPE, 'Invalid sync code'); + } + + // Bit Depth + let bitDepth: number; + if (profile >= 2) { + const tenOrTwelveBit = reader.readBits(1); + bitDepth = tenOrTwelveBit === 0 ? 10 : 12; + } else { + bitDepth = 8; + } + + const colorSpace = reader.readBits(3); + + let subsamplingX: number; + let subsamplingY: number; + let yuvRangeFlag: number | undefined; + if (colorSpace !== VP9ColorSpaceEnum.SRGB) { + yuvRangeFlag = reader.readBits(1); + if (profile === 1 || profile === 3) { + subsamplingX = reader.readBits(1); + subsamplingY = reader.readBits(1); + reservedZero = reader.readBits(1); + } else { + subsamplingX = 1; + subsamplingY = 1; + } + } else { + if (profile !== 1 && profile !== 3) { + throw new ParseCodecError( + VP9_CODEC_TYPE, + 'VP9 profile with sRGB ColorSpace must be 1 or 3' + ); + } + subsamplingX = 0; + subsamplingY = 0; + reservedZero = reader.readBits(1); + } + + let subsampling: VP9Subsampling; + + if (!subsamplingX && subsamplingY) { + subsampling = VP9Subsampling.YUV440; + } else if (subsamplingX && !subsamplingY) { + subsampling = VP9Subsampling.YUV422; + } else if (subsamplingX && subsamplingY) { + subsampling = VP9Subsampling.YUV420; + } else if (!subsamplingX && !subsamplingY) { + subsampling = VP9Subsampling.YUV444; + } else { + subsampling = VP9Subsampling.UNKNOWN; + } + + // Frame Size (resolution) + const widthMinus1 = reader.readBits(16); + const heightMinus1 = reader.readBits(16); + const hasScaling = !!reader.readBits(1); + let renderWidthMinus1 = widthMinus1; + let renderHeightMinus1 = heightMinus1; + if (hasScaling) { + renderWidthMinus1 = reader.readBits(16); + renderHeightMinus1 = reader.readBits(16); + } + + const width = widthMinus1 + 1; + const height = heightMinus1 + 1; + + const sampleRate = width * height * frameRate; + const resolution = width * height; + + let estimateLevel = '62'; + for (const { level, maxSampleRate, maxResolution } of VP9PerformenceLevel) { + if (sampleRate <= maxSampleRate && resolution <= maxResolution) { + // 检查 profile 和 bitDepth 的额外要求 + if (profile >= 2 && bitDepth > 8 && Number.parseFloat(level) < 20) { + continue; + } + estimateLevel = level; + break; + } + } + + return { + profile, + bitDepth, + colorSpace, + subsampling, + yuvRangeFlag, + width, + height, + hasScaling, + renderWidth: renderWidthMinus1 + 1, + renderHeight: renderHeightMinus1 + 1, + frameRate, + estimateLevel, + }; +} + +// The format of the 'vp09' codec string is specified in the webm GitHub repo: +// +// +// The codecs parameter string for the VP codec family is as follows: +// ..... +// ... +// +// All parameter values are expressed as double-digit decimals. +// sample entry 4CC, profile, level, and bitDepth are all mandatory fields. +export function genCodecStringByVP9DecoderConfigurationRecord( + config: VP9DecoderConfigurationRecordType +): string { + const profileStr = config.profile.toString().padStart(2, '0'); + const bitDepthStr = config.bitDepth.toString().padStart(2, '0'); + const levelStr = config.estimateLevel; + + return `vp09.${profileStr}.${levelStr}.${bitDepthStr}`; +} diff --git a/apps/playground/src/media/mkv/index.ts b/packages/matroska/src/index.ts similarity index 100% rename from apps/playground/src/media/mkv/index.ts rename to packages/matroska/src/index.ts diff --git a/apps/playground/src/media/mkv/model.ts b/packages/matroska/src/model.ts similarity index 79% rename from apps/playground/src/media/mkv/model.ts rename to packages/matroska/src/model.ts index 8ea910e..d76ad1a 100644 --- a/apps/playground/src/media/mkv/model.ts +++ b/packages/matroska/src/model.ts @@ -30,14 +30,18 @@ import { TagSchema, type TagType, TrackEntrySchema, - type TrackEntryType, TrackTypeRestrictionEnum, + type TrackEntryType, + TrackTypeRestrictionEnum, } from './schema'; -import {concatBufs} from "konoebml/lib/tools"; -import {ParseCodecErrors, UnreachableOrLogicError, UnsupportedCodecError} from "@/media/base/errors.ts"; -import type {ProbeInfo} from "@/media/mkv/enhance/probe.ts"; -import {audioCodecIdToWebCodecs, videoCodecIdToWebCodecs} from "@/media/mkv/codecs"; -import {Queue} from "mnemonist"; -import {BehaviorSubject} from "rxjs"; +import { concatBufs } from 'konoebml/lib/tools'; +import { + ParseCodecErrors, + UnreachableOrLogicError, + UnsupportedCodecError, +} from '@konoplayer/core/errors'; +import { audioCodecIdToWebCodecs, videoCodecIdToWebCodecs } from './codecs'; +import { Queue } from 'mnemonist'; +import { BehaviorSubject } from 'rxjs'; export const SEEK_ID_KAX_INFO = new Uint8Array([0x15, 0x49, 0xa9, 0x66]); export const SEEK_ID_KAX_TRACKS = new Uint8Array([0x16, 0x54, 0xae, 0x6b]); @@ -47,10 +51,7 @@ export const SEEK_ID_KAX_TAGS = new Uint8Array([0x12, 0x54, 0xc3, 0x67]); export class SegmentSystem { startTag: EbmlSegmentTagType; headTags: EbmlTagType[] = []; - teeStream: ReadableStream - teeBufferTask: Promise; firstCluster: EbmlClusterTagType | undefined; - probInfo?: ProbeInfo; cue: CueSystem; cluster: ClusterSystem; @@ -59,7 +60,7 @@ export class SegmentSystem { track: TrackSystem; tag: TagSystem; - constructor(startNode: EbmlSegmentTagType, teeStream: ReadableStream) { + constructor(startNode: EbmlSegmentTagType) { this.startTag = startNode; this.cue = new CueSystem(this); this.cluster = new ClusterSystem(this); @@ -67,35 +68,13 @@ export class SegmentSystem { this.info = new InfoSystem(this); this.track = new TrackSystem(this); this.tag = new TagSystem(this); - this.teeStream = teeStream; - this.teeBufferTask = this.teeWaitingProbingData(teeStream); - } - - private async teeWaitingProbingData (teeStream: ReadableStream): Promise { - const reader = teeStream.getReader(); - const list: Uint8Array[] = []; - while (true) { - try { - const { done, value } = await reader.read(); - if (done) { - break; - } - list.push(value); - } catch (e: any) { - if (e?.name === 'AbortError') { - break; - } - throw e; - } - } - return concatBufs(...list) } get contentStartOffset() { return this.startTag.startOffset + this.startTag.headerLength; } - private seekLocal () { + private seekLocal() { const infoTag = this.seek.seekTagBySeekId(SEEK_ID_KAX_INFO); const tracksTag = this.seek.seekTagBySeekId(SEEK_ID_KAX_TRACKS); const cuesTag = this.seek.seekTagBySeekId(SEEK_ID_KAX_CUES); @@ -131,7 +110,7 @@ export class SegmentSystem { return this; } - async completeMeta () { + async completeMeta() { this.seekLocal(); await this.parseCodes(); @@ -139,41 +118,19 @@ export class SegmentSystem { return this; } - async fetchProbeInfo (_payload: Uint8Array): Promise { - // call local or remote ff-probe - return {} - } - - async parseCodes () { - const candidates = this.track.tracks.filter(c => c.TrackType === TrackTypeRestrictionEnum.AUDIO || c.TrackType === TrackTypeRestrictionEnum.VIDEO); + async parseCodes() { + const candidates = this.track.tracks.filter( + (c) => + c.TrackType === TrackTypeRestrictionEnum.AUDIO || + c.TrackType === TrackTypeRestrictionEnum.VIDEO + ); const parseErrors = new ParseCodecErrors(); - if (!this.probInfo) { - for (const t of candidates) { - try { - await this.track.initTrack(t, undefined) - } catch (e: unknown) { - parseErrors.cause.push(e as Error) - } - } - if (parseErrors.cause.length > 0) { - try { - const teeBuffer = await this.teeBufferTask; - this.probInfo = await this.fetchProbeInfo(teeBuffer); - } catch (e) { - parseErrors.cause.push(e as Error); - return; - } - } else { - return; - } - } - for (const t of candidates) { try { - await this.track.initTrack(t, this.probInfo) + await this.track.initTrack(t, this.); } catch (e) { - parseErrors.cause.push(e as Error) + parseErrors.cause.push(e as Error); } } if (parseErrors.cause.length > 0) { @@ -274,9 +231,9 @@ export class SeekSystem extends SegmentComponentSystemTrait< return this.seekTagByStartOffset(this.seekOffsetBySeekId(seekId)); } - get firstClusterOffset () { + get firstClusterOffset() { if (!this.segment.firstCluster) { - throw new UnreachableOrLogicError("first cluster not found") + throw new UnreachableOrLogicError('first cluster not found'); } return this.segment.firstCluster.startOffset; } @@ -320,11 +277,10 @@ export interface GetTrackEntryOptions { predicate?: (v: SegmentComponent) => boolean; } - export interface TrackState { - decoder: Decoder, - configuration?: Config, - frameBuffer$: BehaviorSubject> + decoder: Decoder; + configuration?: Config; + frameBuffer$: BehaviorSubject>; } export class TrackSystem extends SegmentComponentSystemTrait< @@ -336,8 +292,14 @@ export class TrackSystem extends SegmentComponentSystemTrait< } tracks: SegmentComponent[] = []; - videoTrackState = new WeakMap>(); - audioTrackState = new WeakMap>(); + videoTrackState = new WeakMap< + TrackEntryType, + TrackState + >(); + audioTrackState = new WeakMap< + TrackEntryType, + TrackState + >(); getTrackEntry({ priority = (track) => @@ -357,11 +319,11 @@ export class TrackSystem extends SegmentComponentSystemTrait< return this; } - async initTrack (track: TrackEntryType, probe?: ProbeInfo) { + async initTrack(track: TrackEntryType) { if (track.TrackType === TrackTypeRestrictionEnum.AUDIO) { - const configuration = audioCodecIdToWebCodecs(track, probe); + const configuration = audioCodecIdToWebCodecs(track); if (await AudioDecoder.isConfigSupported(configuration)) { - throw new UnsupportedCodecError(configuration.codec, 'audio decoder') + throw new UnsupportedCodecError(configuration.codec, 'audio decoder'); } const queue$ = new BehaviorSubject(new Queue()); @@ -378,11 +340,11 @@ export class TrackSystem extends SegmentComponentSystemTrait< }, }), frameBuffer$: queue$, - }) + }); } else if (track.TrackType === TrackTypeRestrictionEnum.VIDEO) { - const configuration = videoCodecIdToWebCodecs(track, probe); + const configuration = videoCodecIdToWebCodecs(track, this.keyframe); if (await VideoDecoder.isConfigSupported(configuration)) { - throw new UnsupportedCodecError(configuration.codec, 'audio decoder') + throw new UnsupportedCodecError(configuration.codec, 'audio decoder'); } const queue$ = new BehaviorSubject(new Queue()); @@ -399,7 +361,7 @@ export class TrackSystem extends SegmentComponentSystemTrait< }, }), frameBuffer$: queue$, - }) + }); } } } diff --git a/apps/playground/src/media/mkv/reactive.ts b/packages/matroska/src/reactive.ts similarity index 87% rename from apps/playground/src/media/mkv/reactive.ts rename to packages/matroska/src/reactive.ts index 0425af2..bd1d4d3 100644 --- a/apps/playground/src/media/mkv/reactive.ts +++ b/packages/matroska/src/reactive.ts @@ -1,4 +1,9 @@ -import {EbmlStreamDecoder, EbmlTagIdEnum, EbmlTagPosition, type EbmlTagType,} from 'konoebml'; +import { + EbmlStreamDecoder, + EbmlTagIdEnum, + EbmlTagPosition, + type EbmlTagType, +} from 'konoebml'; import { defer, EMPTY, @@ -19,10 +24,19 @@ import { takeWhile, withLatestFrom, } from 'rxjs'; -import {createRangedStream, type CreateRangedStreamOptions} from '@/fetch'; -import {type CueSystem, SEEK_ID_KAX_CUES, SEEK_ID_KAX_TAGS, type SegmentComponent, SegmentSystem,} from './model'; -import {isTagIdPos, waitTick} from './util'; -import type {ClusterType} from './schema'; +import { + createRangedStream, + type CreateRangedStreamOptions, +} from '@konoplayer/core/data'; +import { + type CueSystem, + SEEK_ID_KAX_CUES, + SEEK_ID_KAX_TAGS, + type SegmentComponent, + SegmentSystem, +} from './model'; +import { isTagIdPos, waitTick } from './util'; +import type { ClusterType } from './schema'; export interface CreateRangedEbmlStreamOptions extends CreateRangedStreamOptions { @@ -33,7 +47,7 @@ export function createRangedEbmlStream({ url, byteStart = 0, byteEnd, - tee = false + tee = false, }: CreateRangedEbmlStreamOptions): Observable<{ ebml$: Observable; totalSize?: number; @@ -135,12 +149,11 @@ export function createEbmlController({ ...options, url, byteStart: 0, - tee: true + tee: true, }); const controller$ = metaRequest$.pipe( map(({ totalSize, ebml$, response, controller, teeBody }) => { - const head$ = ebml$.pipe( filter(isTagIdPos(EbmlTagIdEnum.EBML, EbmlTagPosition.End)), take(1), @@ -166,18 +179,30 @@ export function createEbmlController({ const seekSystem = segment.seek; const meta$ = ebml$.pipe( - scan((acc, tag) => { - // avoid object recreation - acc.hasKeyframe = acc.hasKeyframe || (tag.id === EbmlTagIdEnum.SimpleBlock && tag.keyframe) || (tag.id === EbmlTagIdEnum.BlockGroup && tag.children.every(c => c.id !== EbmlTagIdEnum.ReferenceBlock)); - acc.tag = tag; - return acc; - }, { hasKeyframe: false, tag: undefined as unknown as EbmlTagType }), - takeWhile( - ({ tag, hasKeyframe }) => { - return !isTagIdPos(EbmlTagIdEnum.Segment, EbmlTagPosition.End)(tag) && !(isTagIdPos(EbmlTagIdEnum.Cluster, EbmlTagPosition.End)(tag) && hasKeyframe); + scan( + (acc, tag) => { + // avoid object recreation + acc.hasKeyframe = + acc.hasKeyframe || + (tag.id === EbmlTagIdEnum.SimpleBlock && tag.keyframe) || + (tag.id === EbmlTagIdEnum.BlockGroup && + tag.children.every( + (c) => c.id !== EbmlTagIdEnum.ReferenceBlock + )); + acc.tag = tag; + return acc; }, - true + { hasKeyframe: false, tag: undefined as unknown as EbmlTagType } ), + takeWhile(({ tag, hasKeyframe }) => { + return ( + !isTagIdPos(EbmlTagIdEnum.Segment, EbmlTagPosition.End)(tag) && + !( + isTagIdPos(EbmlTagIdEnum.Cluster, EbmlTagPosition.End)(tag) && + hasKeyframe + ) + ); + }, true), map(({ tag }) => tag), share({ resetOnComplete: false, @@ -312,7 +337,10 @@ export function createEbmlController({ acc.next = curr; return acc; }, - ({ prev: undefined as (SegmentComponent | undefined), next: undefined as SegmentComponent | undefined }) + { + prev: undefined as SegmentComponent | undefined, + next: undefined as SegmentComponent | undefined, + } ), filter((c) => c.next?.Timestamp! > seekTime), map((c) => c.prev ?? c.next!) diff --git a/apps/playground/src/media/mkv/schema.ts b/packages/matroska/src/schema.ts similarity index 100% rename from apps/playground/src/media/mkv/schema.ts rename to packages/matroska/src/schema.ts diff --git a/apps/playground/src/media/mkv/util.ts b/packages/matroska/src/util.ts similarity index 100% rename from apps/playground/src/media/mkv/util.ts rename to packages/matroska/src/util.ts diff --git a/packages/matroska/tsconfig.json b/packages/matroska/tsconfig.json new file mode 100644 index 0000000..3d198e3 --- /dev/null +++ b/packages/matroska/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist", + "paths": { + "@konoplayer/core/*": [ + "../core/src/*" + ] + } + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../core" + } + ] +} \ No newline at end of file diff --git a/packages/demuxing/Cargo.toml b/packages/symphonia/Cargo.toml similarity index 50% rename from packages/demuxing/Cargo.toml rename to packages/symphonia/Cargo.toml index 7545c2b..edceee9 100644 --- a/packages/demuxing/Cargo.toml +++ b/packages/symphonia/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "konoplayer-demuxing" +name = "konoplayer-symphonia" version = "0.1.0" edition = "2024" [dependencies] -symphonia-format-mkv = "0.5.4" +symphonia = "0.5.4" diff --git a/packages/demuxing/src/lib.rs b/packages/symphonia/src/lib.rs similarity index 100% rename from packages/demuxing/src/lib.rs rename to packages/symphonia/src/lib.rs diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aab1534..87b6275 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,9 +88,12 @@ importers: apps/playground: dependencies: - konoebml: - specifier: 0.1.2 - version: 0.1.2(arktype@2.1.10) + '@konoplayer/core': + specifier: workspace:* + version: link:../../packages/core + '@konoplayer/matroska': + specifier: workspace:* + version: link:../../packages/matroska lit: specifier: ^3.2.1 version: 3.2.1 @@ -98,9 +101,6 @@ importers: '@rsbuild/core': specifier: ^1.2.14 version: 1.2.15 - typescript: - specifier: ^5.8.2 - version: 5.8.2 apps/proxy: devDependencies: @@ -111,21 +111,38 @@ importers: specifier: ^2.9.93 version: 2.9.94 - packages/codecs: + apps/test: dependencies: + '@konoplayer/core': + specifier: workspace:* + version: link:../../packages/core + '@konoplayer/matroska': + specifier: workspace:* + version: link:../../packages/matroska konoebml: - specifier: 0.1.2-rc.5 - version: 0.1.2-rc.5(arktype@2.1.10) - lit: - specifier: ^3.2.1 - version: 3.2.1 + specifier: ^0.1.2 + version: 0.1.2(arktype@2.1.10) devDependencies: - '@rsbuild/core': - specifier: ^1.2.14 - version: 1.2.15 - typescript: - specifier: ^5.8.2 - version: 5.8.2 + unplugin-swc: + specifier: ^1.5.1 + version: 1.5.1(@swc/core@1.11.8(@swc/helpers@0.5.15))(rollup@4.37.0) + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.8.2)(vite@6.2.2(@types/node@22.13.11)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)) + vitest: + specifier: ^3.0.9 + version: 3.0.9(@types/node@22.13.11)(happy-dom@17.4.4)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3) + + packages/core: {} + + packages/matroska: + dependencies: + '@konoplayer/core': + specifier: workspace:* + version: link:../core + konoebml: + specifier: ^0.1.2 + version: 0.1.2(arktype@2.1.10) packages: @@ -752,6 +769,115 @@ packages: engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} hasBin: true + '@rollup/pluginutils@5.1.4': + resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.37.0': + resolution: {integrity: sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.37.0': + resolution: {integrity: sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.37.0': + resolution: {integrity: sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.37.0': + resolution: {integrity: sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.37.0': + resolution: {integrity: sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.37.0': + resolution: {integrity: sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.37.0': + resolution: {integrity: sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.37.0': + resolution: {integrity: sha512-jUb/kmn/Gd8epbHKEqkRAxq5c2EwRt0DqhSGWjPFxLeFvldFdHQs/n8lQ9x85oAeVb6bHcS8irhTJX2FCOd8Ag==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.37.0': + resolution: {integrity: sha512-oNrJxcQT9IcbcmKlkF+Yz2tmOxZgG9D9GRq+1OE6XCQwCVwxixYAa38Z8qqPzQvzt1FCfmrHX03E0pWoXm1DqA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.37.0': + resolution: {integrity: sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.37.0': + resolution: {integrity: sha512-yCE0NnutTC/7IGUq/PUHmoeZbIwq3KRh02e9SfFh7Vmc1Z7atuJRYWhRME5fKgT8aS20mwi1RyChA23qSyRGpA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.37.0': + resolution: {integrity: sha512-NxcICptHk06E2Lh3a4Pu+2PEdZ6ahNHuK7o6Np9zcWkrBMuv21j10SQDJW3C9Yf/A/P7cutWoC/DptNLVsZ0VQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.37.0': + resolution: {integrity: sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.37.0': + resolution: {integrity: sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.37.0': + resolution: {integrity: sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.37.0': + resolution: {integrity: sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.37.0': + resolution: {integrity: sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.37.0': + resolution: {integrity: sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.37.0': + resolution: {integrity: sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.37.0': + resolution: {integrity: sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA==} + cpu: [x64] + os: [win32] + '@rsbuild/core@1.2.15': resolution: {integrity: sha512-f17C4q3MoQ1G9CXzGkiZKZj3MHnV9oSovBjjQQ5bXVBICfGVyRHlHHCa5b9b40F67lbez2K6eLkLP9wU1j1Udw==} engines: {node: '>=16.7.0'} @@ -951,6 +1077,35 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@vitest/expect@3.0.9': + resolution: {integrity: sha512-5eCqRItYgIML7NNVgJj6TVCmdzE7ZVgJhruW0ziSQV4V7PvLkDL1bBkBdcTs/VuIz0IxPb5da1IDSqc1TR9eig==} + + '@vitest/mocker@3.0.9': + resolution: {integrity: sha512-ryERPIBOnvevAkTq+L1lD+DTFBRcjueL9lOUfXsLfwP92h4e+Heb+PjiqS3/OURWPtywfafK0kj++yDFjWUmrA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.0.9': + resolution: {integrity: sha512-OW9F8t2J3AwFEwENg3yMyKWweF7oRJlMyHOMIhO5F3n0+cgQAJZBjNgrF8dLwFTEXl5jUqBLXd9QyyKv8zEcmA==} + + '@vitest/runner@3.0.9': + resolution: {integrity: sha512-NX9oUXgF9HPfJSwl8tUZCMP1oGx2+Sf+ru6d05QjzQz4OwWg0psEzwY6VexP2tTHWdOkhKHUIZH+fS6nA7jfOw==} + + '@vitest/snapshot@3.0.9': + resolution: {integrity: sha512-AiLUiuZ0FuA+/8i19mTYd+re5jqjEc2jZbgJ2up0VY0Ddyyxg/uUtBDpIFAy4uzKaQxOW8gMgBdAJJ2ydhu39A==} + + '@vitest/spy@3.0.9': + resolution: {integrity: sha512-/CcK2UDl0aQ2wtkp3YVWldrpLRNCfVcIOFGlVGKO4R5eajsH393Z1yiXLVQ7vWsj26JOEjeZI0x5sm5P4OGUNQ==} + + '@vitest/utils@3.0.9': + resolution: {integrity: sha512-ilHM5fHhZ89MCp5aAaM9uhfl1c2JdxVxl3McqsdVyVNN6JffnEen8UMCdRTzOhGXNQGo5GNL9QugHrz727Wnng==} + '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -1141,6 +1296,10 @@ packages: array-timsort@1.0.3: resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + async-limiter@2.0.0: resolution: {integrity: sha512-nyHFzvVaR+4mfHc90/VqOUQjlnk9+ioDxQfqDuqKnm3m9sIT7joVKW8dkxeaKpamMJ3MYD73t6M8PMKEWlQESQ==} @@ -1211,6 +1370,10 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + cacheable-lookup@7.0.0: resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} engines: {node: '>=14.16'} @@ -1234,6 +1397,10 @@ packages: caniuse-lite@1.0.30001702: resolution: {integrity: sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==} + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1244,6 +1411,10 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -1406,6 +1577,10 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -1523,6 +1698,12 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -1535,6 +1716,10 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} + expect-type@1.2.0: + resolution: {integrity: sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==} + engines: {node: '>=12.0.0'} + express@4.21.2: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} @@ -1684,6 +1869,9 @@ packages: engines: {node: 20 || >=22} hasBin: true + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1894,15 +2082,6 @@ packages: arktype: optional: true - konoebml@0.1.2-rc.5: - resolution: {integrity: sha512-VsXIlsXby0OzSzLER6ERRZE+9kLkqrYUF7Wr9MKAt8qvmUc3/YStf2SdpC2gMOtCjoyxDi7bXCQPIOHziUu4nw==} - engines: {node: '>= 18.0.0'} - peerDependencies: - arktype: ^2.0.0 - peerDependenciesMeta: - arktype: - optional: true - lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -1915,6 +2094,10 @@ packages: lit@3.2.1: resolution: {integrity: sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==} + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + loader-runner@4.3.0: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} engines: {node: '>=6.11.5'} @@ -1929,6 +2112,9 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + lowercase-keys@3.0.0: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2071,6 +2257,11 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -2187,6 +2378,13 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + peek-readable@5.4.2: resolution: {integrity: sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==} engines: {node: '>=14.16'} @@ -2220,6 +2418,10 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -2315,6 +2517,11 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rollup@4.37.0: + resolution: {integrity: sha512-iAtQy/L4QFU+rTJ1YUjXqJOJzuwEghqWzCEYD2FEghT7Gsy1VdABntrO4CLopA5IkflTyqNiLNwPcOJ3S7UKLg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + router@2.1.0: resolution: {integrity: sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==} engines: {node: '>= 18'} @@ -2422,6 +2629,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -2448,6 +2658,10 @@ packages: resolution: {integrity: sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==} engines: {node: '>=0.10.0'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -2459,6 +2673,9 @@ packages: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + starting@8.0.3: resolution: {integrity: sha512-kk2co1LglBnwEEprHUI96khhi4vWhgQlloeGF5XNF+z+Mo6x4fof3kcf2t0iWgDuw+5Z12B/Y1WqgfTQUsawow==} engines: {node: '>= 0.10.0'} @@ -2467,6 +2684,9 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + std-env@3.8.1: + resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==} + streamsearch@0.1.2: resolution: {integrity: sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==} engines: {node: '>=0.8.0'} @@ -2561,6 +2781,24 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -2581,6 +2819,16 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + tsconfck@3.1.5: + resolution: {integrity: sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + tsconfig-paths-webpack-plugin@4.2.0: resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} engines: {node: '>=10.13.0'} @@ -2655,6 +2903,15 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unplugin-swc@1.5.1: + resolution: {integrity: sha512-/ZLrPNjChhGx3Z95pxJ4tQgfI6rWqukgYHKflrNB4zAV1izOQuDhkTn55JWeivpBxDCoK7M/TStb2aS/14PS/g==} + peerDependencies: + '@swc/core': ^1.2.108 + + unplugin@1.16.1: + resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} + engines: {node: '>=14.0.0'} + update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true @@ -2675,6 +2932,87 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite-node@3.0.9: + resolution: {integrity: sha512-w3Gdx7jDcuT9cNn9jExXgOyKmf5UOTb6WMHz8LGAm54eS1Elf5OuBhCxl6zJxGhEeIkgsE1WbHuoL0mj/UXqXg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite-tsconfig-paths@5.1.4: + resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@6.2.2: + resolution: {integrity: sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.0.9: + resolution: {integrity: sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.0.9 + '@vitest/ui': 3.0.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + watchpack@2.4.2: resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} engines: {node: '>=10.13.0'} @@ -2694,6 +3032,9 @@ packages: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + webpack@5.98.0: resolution: {integrity: sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==} engines: {node: '>=10.13.0'} @@ -2721,6 +3062,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -3315,6 +3661,74 @@ snapshots: dependencies: consola: 3.4.0 + '@rollup/pluginutils@5.1.4(rollup@4.37.0)': + dependencies: + '@types/estree': 1.0.6 + estree-walker: 2.0.2 + picomatch: 4.0.2 + optionalDependencies: + rollup: 4.37.0 + + '@rollup/rollup-android-arm-eabi@4.37.0': + optional: true + + '@rollup/rollup-android-arm64@4.37.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.37.0': + optional: true + + '@rollup/rollup-darwin-x64@4.37.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.37.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.37.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.37.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.37.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.37.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.37.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.37.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.37.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.37.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.37.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.37.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.37.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.37.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.37.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.37.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.37.0': + optional: true + '@rsbuild/core@1.2.15': dependencies: '@rspack/core': 1.2.7(@swc/helpers@0.5.15) @@ -3485,6 +3899,46 @@ snapshots: '@types/trusted-types@2.0.7': {} + '@vitest/expect@3.0.9': + dependencies: + '@vitest/spy': 3.0.9 + '@vitest/utils': 3.0.9 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.0.9(vite@6.2.2(@types/node@22.13.11)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3))': + dependencies: + '@vitest/spy': 3.0.9 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.2.2(@types/node@22.13.11)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3) + + '@vitest/pretty-format@3.0.9': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.0.9': + dependencies: + '@vitest/utils': 3.0.9 + pathe: 2.0.3 + + '@vitest/snapshot@3.0.9': + dependencies: + '@vitest/pretty-format': 3.0.9 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.0.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@3.0.9': + dependencies: + '@vitest/pretty-format': 3.0.9 + loupe: 3.1.3 + tinyrainbow: 2.0.0 + '@webassemblyjs/ast@1.14.1': dependencies: '@webassemblyjs/helper-numbers': 1.13.2 @@ -3716,6 +4170,8 @@ snapshots: array-timsort@1.0.3: {} + assertion-error@2.0.1: {} + async-limiter@2.0.0: {} b4a@1.6.7: {} @@ -3812,6 +4268,8 @@ snapshots: bytes@3.1.2: {} + cac@6.7.14: {} + cacheable-lookup@7.0.0: {} cacheable-request@10.2.14: @@ -3838,6 +4296,14 @@ snapshots: caniuse-lite@1.0.30001702: {} + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -3847,6 +4313,8 @@ snapshots: chardet@0.7.0: {} + check-error@2.1.1: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -3982,6 +4450,8 @@ snapshots: dependencies: mimic-response: 3.1.0 + deep-eql@5.0.2: {} + deepmerge@4.3.1: {} defaults@1.0.4: @@ -4093,6 +4563,12 @@ snapshots: estraverse@5.3.0: {} + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + etag@1.8.1: {} events@3.3.0: {} @@ -4109,6 +4585,8 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + expect-type@1.2.0: {} + express@4.21.2: dependencies: accepts: 1.3.8 @@ -4358,6 +4836,8 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 2.0.0 + globrex@0.1.2: {} + gopd@1.2.0: {} got@13.0.0: @@ -4534,13 +5014,6 @@ snapshots: optionalDependencies: arktype: 2.1.10 - konoebml@0.1.2-rc.5(arktype@2.1.10): - dependencies: - mnemonist: 0.40.3 - type-fest: 4.37.0 - optionalDependencies: - arktype: 2.1.10 - lines-and-columns@1.2.4: {} lit-element@4.1.1: @@ -4559,6 +5032,8 @@ snapshots: lit-element: 4.1.1 lit-html: 3.2.1 + load-tsconfig@0.2.5: {} + loader-runner@4.3.0: {} lodash-es@4.17.21: {} @@ -4570,6 +5045,8 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + loupe@3.1.3: {} + lowercase-keys@3.0.0: {} lru-cache@11.0.2: {} @@ -4690,6 +5167,8 @@ snapshots: mute-stream@2.0.0: {} + nanoid@3.3.11: {} + negotiator@0.6.3: {} negotiator@1.0.0: {} @@ -4784,6 +5263,10 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + + pathval@2.0.0: {} + peek-readable@5.4.2: {} pend@1.2.0: {} @@ -4804,6 +5287,12 @@ snapshots: pluralize@8.0.0: {} + postcss@8.5.3: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + process-nextick-args@2.0.1: {} proxy-addr@2.0.7: @@ -4899,6 +5388,32 @@ snapshots: reusify@1.1.0: {} + rollup@4.37.0: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.37.0 + '@rollup/rollup-android-arm64': 4.37.0 + '@rollup/rollup-darwin-arm64': 4.37.0 + '@rollup/rollup-darwin-x64': 4.37.0 + '@rollup/rollup-freebsd-arm64': 4.37.0 + '@rollup/rollup-freebsd-x64': 4.37.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.37.0 + '@rollup/rollup-linux-arm-musleabihf': 4.37.0 + '@rollup/rollup-linux-arm64-gnu': 4.37.0 + '@rollup/rollup-linux-arm64-musl': 4.37.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.37.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.37.0 + '@rollup/rollup-linux-riscv64-gnu': 4.37.0 + '@rollup/rollup-linux-riscv64-musl': 4.37.0 + '@rollup/rollup-linux-s390x-gnu': 4.37.0 + '@rollup/rollup-linux-x64-gnu': 4.37.0 + '@rollup/rollup-linux-x64-musl': 4.37.0 + '@rollup/rollup-win32-arm64-msvc': 4.37.0 + '@rollup/rollup-win32-ia32-msvc': 4.37.0 + '@rollup/rollup-win32-x64-msvc': 4.37.0 + fsevents: 2.3.3 + router@2.1.0: dependencies: is-promise: 4.0.0 @@ -5055,6 +5570,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -5075,6 +5592,8 @@ snapshots: dependencies: is-plain-obj: 1.1.0 + source-map-js@1.2.1: {} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -5084,6 +5603,8 @@ snapshots: source-map@0.7.4: {} + stackback@0.0.2: {} + starting@8.0.3: dependencies: commander: 2.7.1 @@ -5091,6 +5612,8 @@ snapshots: statuses@2.0.1: {} + std-env@3.8.1: {} + streamsearch@0.1.2: {} streamsearch@1.1.0: {} @@ -5184,6 +5707,16 @@ snapshots: through@2.3.8: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinypool@1.0.2: {} + + tinyrainbow@2.0.0: {} + + tinyspy@3.0.2: {} + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -5201,6 +5734,10 @@ snapshots: tree-kill@1.2.2: {} + tsconfck@3.1.5(typescript@5.8.2): + optionalDependencies: + typescript: 5.8.2 + tsconfig-paths-webpack-plugin@4.2.0: dependencies: chalk: 4.1.2 @@ -5267,6 +5804,20 @@ snapshots: unpipe@1.0.0: {} + unplugin-swc@1.5.1(@swc/core@1.11.8(@swc/helpers@0.5.15))(rollup@4.37.0): + dependencies: + '@rollup/pluginutils': 5.1.4(rollup@4.37.0) + '@swc/core': 1.11.8(@swc/helpers@0.5.15) + load-tsconfig: 0.2.5 + unplugin: 1.16.1 + transitivePeerDependencies: + - rollup + + unplugin@1.16.1: + dependencies: + acorn: 8.14.1 + webpack-virtual-modules: 0.6.2 + update-browserslist-db@1.1.3(browserslist@4.24.4): dependencies: browserslist: 4.24.4 @@ -5283,6 +5834,89 @@ snapshots: vary@1.1.2: {} + vite-node@3.0.9(@types/node@22.13.11)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3): + dependencies: + cac: 6.7.14 + debug: 4.4.0 + es-module-lexer: 1.6.0 + pathe: 2.0.3 + vite: 6.2.2(@types/node@22.13.11)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite-tsconfig-paths@5.1.4(typescript@5.8.2)(vite@6.2.2(@types/node@22.13.11)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)): + dependencies: + debug: 4.4.0 + globrex: 0.1.2 + tsconfck: 3.1.5(typescript@5.8.2) + optionalDependencies: + vite: 6.2.2(@types/node@22.13.11)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3) + transitivePeerDependencies: + - supports-color + - typescript + + vite@6.2.2(@types/node@22.13.11)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3): + dependencies: + esbuild: 0.25.1 + postcss: 8.5.3 + rollup: 4.37.0 + optionalDependencies: + '@types/node': 22.13.11 + fsevents: 2.3.3 + jiti: 2.4.2 + terser: 5.39.0 + tsx: 4.19.3 + + vitest@3.0.9(@types/node@22.13.11)(happy-dom@17.4.4)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3): + dependencies: + '@vitest/expect': 3.0.9 + '@vitest/mocker': 3.0.9(vite@6.2.2(@types/node@22.13.11)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)) + '@vitest/pretty-format': 3.0.9 + '@vitest/runner': 3.0.9 + '@vitest/snapshot': 3.0.9 + '@vitest/spy': 3.0.9 + '@vitest/utils': 3.0.9 + chai: 5.2.0 + debug: 4.4.0 + expect-type: 1.2.0 + magic-string: 0.30.17 + pathe: 2.0.3 + std-env: 3.8.1 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 2.0.0 + vite: 6.2.2(@types/node@22.13.11)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3) + vite-node: 3.0.9(@types/node@22.13.11)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.13.11 + happy-dom: 17.4.4 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + watchpack@2.4.2: dependencies: glob-to-regexp: 0.4.1 @@ -5298,6 +5932,8 @@ snapshots: webpack-sources@3.2.3: {} + webpack-virtual-modules@0.6.2: {} + webpack@5.98.0(@swc/core@1.11.8(@swc/helpers@0.5.15)): dependencies: '@types/eslint-scope': 3.7.7 @@ -5375,6 +6011,11 @@ snapshots: transitivePeerDependencies: - supports-color + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 diff --git a/scripts/codegen-mkv.ts b/scripts/codegen-mkv.ts index 795a39a..c947e80 100644 --- a/scripts/codegen-mkv.ts +++ b/scripts/codegen-mkv.ts @@ -363,7 +363,6 @@ function generateMkvSchemaHierarchy(elements_: EbmlElementType[]) { const selfSchema = [ `export const ${el.name}Schema = type({`, - // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: ...associated.map((v) => { let meta: any; const restriction = generateRestriction(v); diff --git a/tsconfig.base.json b/tsconfig.base.json index ab5a802..82836d8 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -24,9 +24,8 @@ "target": "ES2021", "strictNullChecks": true, "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "useDefineForClassFields": true, + "useDefineForClassFields": false, "exactOptionalPropertyTypes": false, + "experimentalDecorators": true } } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 70736b5..4c2fb93 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,15 @@ }, { "path": "./tsconfig.scripts.json" + }, + { + "path": "./packages/matroska" + }, + { + "path": "./packages/core" + }, + { + "path": "./apps/test" } ] } \ No newline at end of file