video-fps-probe 0.3.0 → 0.4.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/README.md +19 -11
- package/dist/analyze.js +10 -1
- package/dist/api/codes.d.ts +6 -0
- package/dist/api/codes.js +90 -0
- package/dist/aspect/index.d.ts +13 -0
- package/dist/aspect/index.js +180 -0
- package/dist/aspect/rational.js +61 -0
- package/dist/cjs/analyze.d.cts +9 -0
- package/dist/cjs/api/agreement.d.cts +18 -0
- package/dist/cjs/api/codes.d.cts +6 -0
- package/dist/cjs/api/errors.d.cts +12 -0
- package/dist/cjs/api/guards.d.cts +4 -0
- package/dist/cjs/api/summary.d.cts +22 -0
- package/dist/cjs/aspect/index.d.cts +13 -0
- package/dist/cjs/aspect.cjs +1 -0
- package/dist/cjs/check/index.d.cts +60 -0
- package/dist/cjs/check.cjs +1 -0
- package/dist/cjs/chunks/boxes-B0T2nt3e.cjs +1 -0
- package/dist/cjs/chunks/build-DhETwJLO.cjs +1 -0
- package/dist/cjs/chunks/color-xqJHSfhZ.cjs +1 -0
- package/dist/cjs/chunks/compare-B_HKNwqg.cjs +1 -0
- package/dist/cjs/chunks/config-CSbXzEqr.cjs +1 -0
- package/dist/cjs/chunks/errors-0oBg1Ai6.cjs +1 -0
- package/dist/cjs/chunks/fallback-CyPIR3RM.cjs +1 -0
- package/dist/cjs/chunks/guards-DbQNcAZS.cjs +1 -0
- package/dist/cjs/chunks/inline-Bx-TQg8f.cjs +1 -0
- package/dist/cjs/chunks/isobmff-4RrRf9N-.cjs +1 -0
- package/dist/cjs/chunks/matroska-BhgWtuPU.cjs +1 -0
- package/dist/cjs/chunks/metadata-ECdzNLkE.cjs +1 -0
- package/dist/cjs/chunks/options-B_vLHxHd.cjs +1 -0
- package/dist/cjs/chunks/probe-Cn1DtL-C.cjs +1 -0
- package/dist/cjs/chunks/rational-Dq5Vza-Q.cjs +1 -0
- package/dist/cjs/chunks/reader-Dw11ozVU.cjs +1 -0
- package/dist/cjs/chunks/rfps-C-pyKnu8.cjs +1 -0
- package/dist/cjs/chunks/runtime-BgAlQQee.cjs +1 -0
- package/dist/cjs/chunks/segments-B1H6bDz-.cjs +1 -0
- package/dist/cjs/chunks/sniff-CXwqOGwh.cjs +1 -0
- package/dist/cjs/chunks/stats-DW7dVdFa.cjs +1 -0
- package/dist/cjs/client/types.d.cts +96 -0
- package/dist/cjs/color/index.d.cts +7 -0
- package/dist/cjs/color.cjs +1 -0
- package/dist/cjs/core/reader.d.cts +47 -0
- package/dist/cjs/core/types.d.cts +194 -0
- package/dist/cjs/engines/types.d.cts +69 -0
- package/dist/cjs/header/index.d.cts +28 -0
- package/dist/cjs/header/limits.d.cts +26 -0
- package/dist/cjs/header.cjs +1 -0
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.d.cts +13 -0
- package/dist/cjs/plugins/types.d.cts +17 -0
- package/dist/cjs/presets/index.d.cts +19 -0
- package/dist/cjs/presets.cjs +1 -0
- package/dist/cjs/probe.d.cts +3 -0
- package/dist/cjs/url/index.d.cts +47 -0
- package/dist/cjs/url.cjs +1 -0
- package/dist/cjs/worker/protocol.d.cts +47 -0
- package/dist/color/codec-string.js +121 -0
- package/dist/color/config.js +199 -0
- package/dist/color/index.d.ts +7 -0
- package/dist/color/index.js +139 -0
- package/dist/color/names.js +33 -0
- package/dist/color/sps.js +275 -0
- package/dist/core/dispatch.js +18 -29
- package/dist/core/isobmff/boxes.js +6 -5
- package/dist/core/isobmff/build.js +401 -0
- package/dist/core/isobmff/config.js +34 -0
- package/dist/core/isobmff/header.js +677 -0
- package/dist/core/isobmff/index.js +9 -381
- package/dist/core/isobmff/moof.js +2 -2
- package/dist/core/isobmff/moov.js +3 -3
- package/dist/core/matroska/header.js +10 -4
- package/dist/core/matroska/index.js +11 -5
- package/dist/core/options.js +1 -0
- package/dist/core/reader.d.ts +3 -0
- package/dist/core/reader.js +13 -7
- package/dist/core/result.js +1 -0
- package/dist/core/stats.js +3 -0
- package/dist/core/types.d.ts +60 -1
- package/dist/engines/common.js +2 -0
- package/dist/header/index.d.ts +28 -0
- package/dist/header/index.js +182 -0
- package/dist/header/limits.d.ts +26 -0
- package/dist/header/limits.js +144 -0
- package/dist/header/metadata.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/standalone/own-isobmff.worker.js +1 -1
- package/dist/standalone/own-matroska.worker.js +1 -1
- package/dist/standalone/own.worker.js +1 -1
- package/dist/url/index.d.ts +47 -0
- package/dist/url/index.js +335 -0
- package/dist/worker/single.js +2 -1
- package/package.json +117 -2
package/dist/cjs/url.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var RangeSourceError=class extends Error{code;status;constructor(e,t,n={}){super(t,n.cause===void 0?void 0:{cause:n.cause}),this.name=`RangeSourceError`,this.code=e,this.status=typeof n.status==`number`?n.status:null}};const e=/^\s*bytes\s+(\d+)-(\d+)\/(\d+|\*)\s*$/i,noop=()=>{},tag=e=>Object.prototype.toString.call(e);function shown(e){return typeof e==`string`?JSON.stringify(e):typeof e==`object`&&e?Array.isArray(e)?`an array`:`an object`:String(e)}function count(e,t,n=0){if(typeof e!=`number`||!Number.isSafeInteger(e)||e<n)throw TypeError(`${t} must be a non-negative safe integer${n>0?` of at least ${n}`:``}, got ${shown(e)}`);return e}function signalOf(e,t){if(e==null)return null;let n=e;if(typeof n!=`object`||typeof n.aborted!=`boolean`||typeof n.addEventListener!=`function`||typeof n.removeEventListener!=`function`)throw TypeError(`${t} must be an AbortSignal, got ${shown(e)}`);return e}function objectOf(e,t){if(e===void 0)return{};if(typeof e!=`object`||!e||Array.isArray(e))throw TypeError(`${t} must be an object, got ${shown(e)}`);return e}function fetcherOf(e){if(typeof e!=`function`)throw TypeError(`fetcher must be a function, got ${shown(e)}`);return e}const reasonOf=e=>e.reason??new DOMException(`This operation was aborted`,`AbortError`);function throwIfAborted(e){if(e?.aborted)throw reasonOf(e)}function linked(e,t){if(!e||!t||e===t||e.aborted||t.aborted)return[e?.aborted||!t?e:t,noop];let n=new AbortController,fromA=()=>n.abort(reasonOf(e)),fromB=()=>n.abort(reasonOf(t));return e.addEventListener(`abort`,fromA,{once:!0}),t.addEventListener(`abort`,fromB,{once:!0}),[n.signal,()=>(e.removeEventListener(`abort`,fromA),t.removeEventListener(`abort`,fromB))]}function untilAborted(e,t,n=noop){return new Promise((r,i)=>{let stop=()=>i(reasonOf(t));t?.aborted?stop():t?.addEventListener(`abort`,stop,{once:!0}),e.then(e=>{t?.removeEventListener(`abort`,stop),t?.aborted?n(e):r(e)},e=>{t?.removeEventListener(`abort`,stop),i(!t?.aborted&&e?.name===`AbortError`?Object.assign(Error(String(e),{cause:e}),{name:``}):e)})})}function attempt(e){try{return Promise.resolve(e())}catch(e){return Promise.reject(e)}}function discard(e){attempt(()=>e?.discard?.()).catch(noop)}function parseRange(t){let n=e.exec(t);if(!n)return null;let r=Number(n[1]),i=Number(n[2]),a=n[3]===`*`?null:Number(n[3]);return!Number.isSafeInteger(r)||!Number.isSafeInteger(i)||r>i||a!==null&&!Number.isSafeInteger(a)?null:{first:r,last:i,total:a}}const present=e=>e!=null,contentRangeOf=({contentRange:e})=>typeof e==`string`?e.trim()===``?null:e:present(e)?String(e):null;function replyOf(e,t){if(typeof e!=`object`||!e)throw new RangeSourceError(`invalid-reply`,`the reply to ${t} is ${shown(e)}, not an object`);return e}function refusal(e,t,n,r,i){let a=present(e.status)?e.status:null,o=contentRangeOf(e);if(a===null&&o===null)return new RangeSourceError(`unverifiable-reply`,`the reply to ${i} has neither a status nor a Content-Range, so it cannot be checked`);if(a!==null&&a!==206&&(a!==200||t!==0||n!==r))return new RangeSourceError(`unexpected-status`,`status ${shown(a)} to ${i} of an object of ${r} bytes, expected 206${t===0&&n===r?` or 200`:``}`,{status:a});if(o!==null){let e=parseRange(o);if(e&&e.total!==null&&e.total!==r)return new RangeSourceError(`size-changed`,`Content-Range ${shown(o)} gives an object of ${e.total} bytes, the source was opened at ${r} bytes`,{status:a});if(!e||e.first!==t||e.last!==n-1)return new RangeSourceError(`range-mismatch`,`Content-Range ${shown(o)} does not match ${i}`,{status:a})}return present(e.contentLength)&&e.contentLength!==n-t?new RangeSourceError(`length-mismatch`,`Content-Length ${shown(e.contentLength)} does not match ${i} (${n-t} bytes)`,{status:a}):null}function exactly(e,t,n){let r;if(ArrayBuffer.isView(e))r=new Uint8Array(e.buffer,e.byteOffset,e.byteLength);else if(tag(e)===`[object ArrayBuffer]`)r=new Uint8Array(e);else throw new RangeSourceError(`invalid-reply`,`${n} is ${e===null?`null`:typeof e}, not bytes`);if(r.byteLength>t)throw new RangeSourceError(`body-too-long`,`${n} holds ${r.byteLength} bytes, more than the ${t} requested`);if(r.byteLength<t)throw new RangeSourceError(`body-too-short`,`${n} holds ${r.byteLength} of the ${t} bytes requested`);return r.byteOffset===0&&r.byteLength===r.buffer.byteLength&&tag(r.buffer)===`[object ArrayBuffer]`?r:r.slice()}async function bodyOf(e,t,n,r){let i=`the body of ${n}`;try{if(present(e.bytes))return exactly(e.bytes,t,i);let a=e.body;if(typeof a!=`function`)throw new RangeSourceError(`invalid-reply`,`the reply to ${n} has neither bytes nor a body function`);return exactly(await untilAborted(attempt(()=>a.call(e,t)),r),t,i)}catch(t){throw discard(e),t}}function request(e,t,n,r){let i=`bytes=${t}-${n-1}`;return[i,untilAborted(attempt(()=>e({start:t,end:n,range:i,signal:r})),r,discard).then(e=>replyOf(e,i))]}function sourceOf(e,t,n,r){let read=async(i,a,o)=>{if(count(i,`start`),count(a,`end`),i>a||a>t)throw RangeError(`read needs start <= end <= size (${t}), got ${i} and ${a}`);let[s,c]=linked(n,signalOf(o,`signal`));try{if(throwIfAborted(s),i===a)return new Uint8Array;if(r&&a<=r.byteLength)return r.slice(i,a);let[n,o]=request(e,i,a,s),c=await o,l=refusal(c,i,a,t,n);if(l)throw discard(c),l;return await bodyOf(c,a-i,n,s)}finally{c()}},clamp=(e,n)=>{if(e===void 0)return n;let r=Math.round(Number(e))||0;return r<0?Math.max(t+r,0):Math.min(r,t)};return{size:t,read,slice(e,r){let i=clamp(e,0),a=Math.max(i,clamp(r,t));return{arrayBuffer:()=>read(i,a,n).then(e=>e.buffer)}}}}function rangeSource(e,t){let n=fetcherOf(e),r=objectOf(t,`init`);return sourceOf(n,count(r.size,`init.size`),signalOf(r.signal,`init.signal`),null)}async function openRangeSource(e,t={}){let n=fetcherOf(e),r=objectOf(t,`init`),i=signalOf(r.signal,`init.signal`);if(r.size!==void 0)return sourceOf(n,count(r.size,`init.size`),i,null);let a=r.headBytes===void 0?65536:count(r.headBytes,`init.headBytes`,1);throwIfAborted(i);let[o,s]=request(n,0,a,i),c=await s,l=present(c.status)?c.status:null,u=contentRangeOf(c),d=u===null?null:parseRange(u),f=present(c.contentLength)?c.contentLength:null,refuse=(e,t)=>{throw discard(c),new RangeSourceError(e,t,{status:l})};if((l===416||l===null)&&u!==null&&/^\s*bytes\s+\*\/0\s*$/i.test(u))return discard(c),sourceOf(n,0,i,new Uint8Array);let p;if(l===200)(f===null||!Number.isSafeInteger(f)||f<0||f>a)&&refuse(`unexpected-status`,`status 200 to ${o} with ${f===null?`no Content-Length`:`Content-Length ${shown(f)}`}: the server sent the whole object instead of the range, and only an object of at most ${a} bytes is accepted that way`),u!==null&&(!d||d.first!==0||d.last!==f-1||d.total!==f)&&refuse(`range-mismatch`,`Content-Range ${shown(u)} does not match the whole object of ${f} bytes`),p=f;else if(l===206||l===null&&u!==null)u===null&&refuse(`unverifiable-reply`,`the 206 reply to ${o} has no Content-Range, or does not expose it to a cross-origin page, so the object size is unknown: pass init.size`),(!d||d.total===null||d.first!==0||d.last!==Math.min(a,d.total)-1)&&refuse(`range-mismatch`,`Content-Range ${shown(u)} does not match ${o} of an object of known size`),f!==null&&f!==d.last+1&&refuse(`length-mismatch`,`Content-Length ${shown(f)} does not match Content-Range ${shown(u)}`),p=d.total;else if(l===null)return refuse(`unverifiable-reply`,`the reply to ${o} has neither a status nor a Content-Range, so it cannot be checked`);else return refuse(`unexpected-status`,`status ${shown(l)} to ${o}, expected 206`);return sourceOf(n,p,i,await bodyOf(c,Math.min(p,a),o,i))}function named(e,t){let n=e?.cause,r=n?.code??n?.message;return t?.aborted||e?.name!==`TypeError`||typeof r!=`string`?e:TypeError(`${e.message}: ${r}`,{cause:e})}function httpRange(e,t={}){let n=typeof e==`string`?e:typeof e?.href==`string`?e.href:null;if(!n)throw TypeError(`url must be a non-empty string or a URL, got ${shown(e)}`);let r=objectOf(t,`init`),i=objectOf(r.headers,`init.headers`),a=r.fetch??globalThis.fetch;if(typeof a!=`function`)throw TypeError(`httpRange needs a fetch function: pass init.fetch where globalThis.fetch does not exist`);return async({range:e,signal:t})=>{let r=new Headers(i);r.set(`range`,e),r.set(`accept-encoding`,`identity`);let fail=e=>{throw named(e,t)},o=await a(n,{headers:r,signal:t}).catch(fail),s=o.headers.get(`content-length`);return{status:o.status,contentRange:o.headers.get(`content-range`),contentLength:s===null?null:/^\s*\d+\s*$/.test(s)?Number(s):NaN,body:e=>o.body?readCapped(o.body,e,t).catch(fail):Promise.resolve(new Uint8Array),discard:()=>void o.body?.cancel().catch(noop)}}}async function readCapped(e,t,n=null){count(t,`expectedLength`);let r=signalOf(n,`signal`),next,stop,i=e;if(typeof i?.getReader==`function`){let t=e.getReader();next=()=>t.read(),stop=()=>void t.cancel().catch(noop)}else if(typeof i?.[Symbol.asyncIterator]==`function`){let t=e[Symbol.asyncIterator]();next=()=>t.next(),stop=()=>{attempt(()=>e.destroy?.()).catch(noop),attempt(()=>t.return?.()).catch(noop)}}else throw TypeError(`body must be a ReadableStream or an async iterable, got ${shown(e)}`);let a=new Uint8Array(t),o=0;try{for(;;){throwIfAborted(r);let e=await untilAborted(attempt(next),r);if(e.done)break;let n=e.value,i=ArrayBuffer.isView(n)?new Uint8Array(n.buffer,n.byteOffset,n.byteLength):tag(n)===`[object ArrayBuffer]`?new Uint8Array(n):null;if(!i)throw new RangeSourceError(`invalid-reply`,`the body gave a ${n===null?`null`:typeof n} chunk, not bytes`);if(o+i.byteLength>t)throw new RangeSourceError(`body-too-long`,`the body passed the ${t} bytes requested`);a.set(i,o),o+=i.byteLength}}catch(e){throw stop(),e}if(o<t)throw new RangeSourceError(`body-too-short`,`the body ended after ${o} of the ${t} bytes requested`);return a}exports.RangeSourceError=RangeSourceError,exports.httpRange=httpRange,exports.openRangeSource=openRangeSource,exports.rangeSource=rangeSource,exports.readCapped=readCapped;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AnalyzeOptions, AnalyzeProgress, FileResult } from '../core/types.cjs';
|
|
2
|
+
import type { ContainerFamily, WorkerEngineId } from '../plugins/types.cjs';
|
|
3
|
+
export declare const PROTOCOL_VERSION = 1;
|
|
4
|
+
export type WireOptions = Omit<AnalyzeOptions, 'signal' | 'onProgress'>;
|
|
5
|
+
export type SkipReason = 'unsupported-container';
|
|
6
|
+
export type WorkerErrorCode = 'unknown-engine' | 'protocol';
|
|
7
|
+
export type AnalyzeRequest = {
|
|
8
|
+
type: 'analyze';
|
|
9
|
+
file: Blob;
|
|
10
|
+
name: string;
|
|
11
|
+
engine: WorkerEngineId;
|
|
12
|
+
options: WireOptions;
|
|
13
|
+
};
|
|
14
|
+
export type WarmRequest = {
|
|
15
|
+
type: 'warm';
|
|
16
|
+
engines: readonly WorkerEngineId[];
|
|
17
|
+
containers: readonly ContainerFamily[];
|
|
18
|
+
};
|
|
19
|
+
export type JobRequest = AnalyzeRequest | WarmRequest;
|
|
20
|
+
export type WorkerRequest = {
|
|
21
|
+
v: typeof PROTOCOL_VERSION;
|
|
22
|
+
id: number;
|
|
23
|
+
} & (JobRequest | {
|
|
24
|
+
type: 'cancel';
|
|
25
|
+
});
|
|
26
|
+
export type WorkerResponse = {
|
|
27
|
+
v: typeof PROTOCOL_VERSION;
|
|
28
|
+
id: number;
|
|
29
|
+
} & ({
|
|
30
|
+
type: 'progress';
|
|
31
|
+
progress: AnalyzeProgress;
|
|
32
|
+
} | {
|
|
33
|
+
type: 'result';
|
|
34
|
+
result: FileResult;
|
|
35
|
+
} | {
|
|
36
|
+
type: 'skipped';
|
|
37
|
+
reason: SkipReason;
|
|
38
|
+
family: ContainerFamily | null;
|
|
39
|
+
} | {
|
|
40
|
+
type: 'warmed';
|
|
41
|
+
} | {
|
|
42
|
+
type: 'error';
|
|
43
|
+
message: string;
|
|
44
|
+
aborted: boolean;
|
|
45
|
+
code?: WorkerErrorCode;
|
|
46
|
+
reason?: 'load';
|
|
47
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { firstBox, u16, u32 } from "./config.js";
|
|
2
|
+
import { avcConfigSps, hevcConfigSps } from "./sps.js";
|
|
3
|
+
const H264_WITHOUT_EXTENSION = new Set([66, 77, 88]);
|
|
4
|
+
const hex2 = (v) => v.toString(16).padStart(2, '0');
|
|
5
|
+
const dec2 = (v) => String(v).padStart(2, '0');
|
|
6
|
+
function reverseBits32(v) {
|
|
7
|
+
let out = 0;
|
|
8
|
+
for (let i = 0; i < 32; i++) {
|
|
9
|
+
out = out * 2 + (v & 1);
|
|
10
|
+
v >>>= 1;
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
function avcFacts(b, s, fourcc) {
|
|
15
|
+
if (s.end - s.start < 6)
|
|
16
|
+
return null;
|
|
17
|
+
const profile = b[s.start + 1];
|
|
18
|
+
const sps = avcConfigSps(b, s);
|
|
19
|
+
let q = s.start + 6;
|
|
20
|
+
for (let i = 0, n = b[s.start + 5] & 31; i < n && q + 2 <= s.end; i++)
|
|
21
|
+
q += 2 + u16(b, q);
|
|
22
|
+
if (q < s.end)
|
|
23
|
+
for (let i = 0, n = b[q++]; i < n && q + 2 <= s.end; i++)
|
|
24
|
+
q += 2 + u16(b, q);
|
|
25
|
+
const extended = !H264_WITHOUT_EXTENSION.has(profile);
|
|
26
|
+
return {
|
|
27
|
+
profile,
|
|
28
|
+
bitDepth: sps?.bitDepth ?? (!extended ? 8 : q + 4 <= s.end ? (b[q + 1] & 7) + 8 : null),
|
|
29
|
+
codecString: `${fourcc}.${hex2(profile)}${hex2(b[s.start + 2])}${hex2(b[s.start + 3])}`,
|
|
30
|
+
sps,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function hevcFacts(b, s, fourcc) {
|
|
34
|
+
if (s.end - s.start < 23)
|
|
35
|
+
return null;
|
|
36
|
+
const p = s.start;
|
|
37
|
+
const space = b[p + 1] >> 6;
|
|
38
|
+
const coded = b[p + 1] & 31;
|
|
39
|
+
const compatibility = u32(b, p + 2);
|
|
40
|
+
let profile = coded;
|
|
41
|
+
for (let i = 1; !profile && i < 32; i++)
|
|
42
|
+
if ((compatibility >>> (31 - i)) & 1)
|
|
43
|
+
profile = i;
|
|
44
|
+
let constraints = '';
|
|
45
|
+
for (let i = 5, any = false; i >= 0; i--) {
|
|
46
|
+
const c = b[p + 6 + i];
|
|
47
|
+
if (c || any) {
|
|
48
|
+
constraints = `.${c.toString(16)}${constraints}`;
|
|
49
|
+
any = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const codecString = `${fourcc}.${['', 'A', 'B', 'C'][space]}${coded}.${reverseBits32(compatibility).toString(16)}.${b[p + 1] & 0x20 ? 'H' : 'L'}${b[p + 12]}${constraints}`;
|
|
53
|
+
const sps = hevcConfigSps(b, s);
|
|
54
|
+
return { profile, bitDepth: sps?.bitDepth ?? (b[p + 17] & 7) + 8, codecString, sps };
|
|
55
|
+
}
|
|
56
|
+
function av1Facts(b, s) {
|
|
57
|
+
if (s.end - s.start < 4)
|
|
58
|
+
return null;
|
|
59
|
+
const profile = b[s.start + 1] >> 5;
|
|
60
|
+
const flags = b[s.start + 2];
|
|
61
|
+
const high = (flags >> 6) & 1;
|
|
62
|
+
const bitDepth = profile > 2 ? null : high ? (profile === 2 && flags & 0x20 ? 12 : 10) : 8;
|
|
63
|
+
const codecString = bitDepth === null ? null : `av01.${profile}.${dec2(b[s.start + 1] & 31)}${flags & 0x80 ? 'H' : 'M'}.${dec2(bitDepth)}`;
|
|
64
|
+
return { profile, bitDepth, codecString, sps: null };
|
|
65
|
+
}
|
|
66
|
+
function vp9String(profile, level, bitDepth) {
|
|
67
|
+
return profile === null || level === null || bitDepth === null ? null : `vp09.${dec2(profile)}.${dec2(level)}.${dec2(bitDepth)}`;
|
|
68
|
+
}
|
|
69
|
+
function vpcFacts(b, s) {
|
|
70
|
+
if (s.end - s.start < 7)
|
|
71
|
+
return null;
|
|
72
|
+
const profile = b[s.start + 4];
|
|
73
|
+
const bitDepth = b[s.start + 6] >> 4;
|
|
74
|
+
return { profile, bitDepth, codecString: vp9String(profile, b[s.start + 5], bitDepth), sps: null };
|
|
75
|
+
}
|
|
76
|
+
function vp9PrivateFacts(b, s) {
|
|
77
|
+
const features = new Map();
|
|
78
|
+
for (let q = s.start; q + 2 <= s.end;) {
|
|
79
|
+
const id = b[q];
|
|
80
|
+
const len = b[q + 1];
|
|
81
|
+
if (q + 2 + len > s.end)
|
|
82
|
+
break;
|
|
83
|
+
if (len === 1 && !features.has(id))
|
|
84
|
+
features.set(id, b[q + 2]);
|
|
85
|
+
q += 2 + len;
|
|
86
|
+
}
|
|
87
|
+
if (!features.size)
|
|
88
|
+
return null;
|
|
89
|
+
const profile = features.get(1) ?? null;
|
|
90
|
+
const bitDepth = features.get(3) ?? null;
|
|
91
|
+
return { profile, bitDepth, codecString: vp9String(profile, features.get(2) ?? null, bitDepth), sps: null };
|
|
92
|
+
}
|
|
93
|
+
export function isoConfigFacts(codecName, b, entry) {
|
|
94
|
+
const avcC = firstBox(entry, 'avcC');
|
|
95
|
+
if (codecName === 'h264' && avcC)
|
|
96
|
+
return avcFacts(b, avcC, entry.type);
|
|
97
|
+
const hvcC = firstBox(entry, 'hvcC');
|
|
98
|
+
if (codecName === 'hevc' && hvcC)
|
|
99
|
+
return hevcFacts(b, hvcC, entry.type);
|
|
100
|
+
const av1C = firstBox(entry, 'av1C');
|
|
101
|
+
if (codecName === 'av1' && av1C)
|
|
102
|
+
return av1Facts(b, av1C);
|
|
103
|
+
const vpcC = firstBox(entry, 'vpcC');
|
|
104
|
+
if (codecName === 'vp9' && vpcC)
|
|
105
|
+
return vpcFacts(b, vpcC);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
export function matroskaConfigFacts(codecId, b, codecPrivate) {
|
|
109
|
+
if (!codecPrivate)
|
|
110
|
+
return null;
|
|
111
|
+
if (codecId === 'V_MPEG4/ISO/AVC')
|
|
112
|
+
return avcFacts(b, codecPrivate, 'avc1');
|
|
113
|
+
if (codecId === 'V_MPEGH/ISO/HEVC')
|
|
114
|
+
return hevcFacts(b, codecPrivate, 'hvc1');
|
|
115
|
+
if (codecId === 'V_AV1')
|
|
116
|
+
return av1Facts(b, codecPrivate);
|
|
117
|
+
return codecId === 'V_VP9' ? vp9PrivateFacts(b, codecPrivate) : null;
|
|
118
|
+
}
|
|
119
|
+
export function dolbyVisionString(fourcc, profile, level) {
|
|
120
|
+
return `${fourcc}.${dec2(profile)}.${dec2(level)}`;
|
|
121
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
export const ID_VIDEO = 0xe0;
|
|
2
|
+
export const ID_PIXEL_WIDTH = 0xb0;
|
|
3
|
+
export const ID_PIXEL_HEIGHT = 0xba;
|
|
4
|
+
export const ID_DISPLAY_WIDTH = 0x54b0;
|
|
5
|
+
export const ID_DISPLAY_HEIGHT = 0x54ba;
|
|
6
|
+
export const ID_DISPLAY_UNIT = 0x54b2;
|
|
7
|
+
export const ID_STEREO_MODE = 0x53b8;
|
|
8
|
+
export const ID_COLOUR = 0x55b0;
|
|
9
|
+
export const ID_MATRIX_COEFFICIENTS = 0x55b1;
|
|
10
|
+
export const ID_BITS_PER_CHANNEL = 0x55b2;
|
|
11
|
+
export const ID_RANGE = 0x55b9;
|
|
12
|
+
export const ID_TRANSFER_CHARACTERISTICS = 0x55ba;
|
|
13
|
+
export const ID_PRIMARIES = 0x55bb;
|
|
14
|
+
const ID_CODEC_ID = 0x86;
|
|
15
|
+
const ID_CODEC_PRIVATE = 0x63a2;
|
|
16
|
+
const ID_BLOCK_ADDITION_MAPPING = 0x41e4;
|
|
17
|
+
export const ID_BLOCK_ADD_ID_TYPE = 0x41e7;
|
|
18
|
+
export const ID_BLOCK_ADD_ID_EXTRA_DATA = 0x41ed;
|
|
19
|
+
const VISUAL_ENTRY_HEADER = 86;
|
|
20
|
+
const MAX_CODEC_ID = 64;
|
|
21
|
+
const MIXED_ENTRY_TYPES = ['apcn', 'apch', 'dvpp', 'dvcp', 'jpeg'];
|
|
22
|
+
export function u16(b, p) {
|
|
23
|
+
return (b[p] << 8) | b[p + 1];
|
|
24
|
+
}
|
|
25
|
+
export function u32(b, p) {
|
|
26
|
+
return ((b[p] << 24) | (b[p + 1] << 16) | (b[p + 2] << 8) | b[p + 3]) >>> 0;
|
|
27
|
+
}
|
|
28
|
+
export function i32(b, p) {
|
|
29
|
+
return (b[p] << 24) | (b[p + 1] << 16) | (b[p + 2] << 8) | b[p + 3];
|
|
30
|
+
}
|
|
31
|
+
export function fourccAt(b, p) {
|
|
32
|
+
return String.fromCharCode(b[p], b[p + 1], b[p + 2], b[p + 3]);
|
|
33
|
+
}
|
|
34
|
+
function* boxes(b, start, end, likeParser = false) {
|
|
35
|
+
let p = start;
|
|
36
|
+
while (p + 8 <= end) {
|
|
37
|
+
let size = u32(b, p);
|
|
38
|
+
const type = fourccAt(b, p + 4);
|
|
39
|
+
let header = 8;
|
|
40
|
+
if (size === 1) {
|
|
41
|
+
if (p + 16 > end || (!likeParser && u32(b, p + 8) !== 0))
|
|
42
|
+
return;
|
|
43
|
+
size = u32(b, p + 8) * 2 ** 32 + u32(b, p + 12);
|
|
44
|
+
header = 16;
|
|
45
|
+
}
|
|
46
|
+
else if (size === 0)
|
|
47
|
+
size = end - p;
|
|
48
|
+
if (likeParser && type === 'uuid')
|
|
49
|
+
header += 16;
|
|
50
|
+
if (size < header || size > Number.MAX_SAFE_INTEGER)
|
|
51
|
+
return;
|
|
52
|
+
const boxEnd = Math.min(p + size, end);
|
|
53
|
+
yield { type, start: p, end: boxEnd, payload: p + header };
|
|
54
|
+
p += size;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function sampleEntries(stsd) {
|
|
58
|
+
const out = [];
|
|
59
|
+
const top = boxes(stsd, 0, stsd.length).next();
|
|
60
|
+
if (top.done || top.value.type !== 'stsd')
|
|
61
|
+
return out;
|
|
62
|
+
const s = top.value;
|
|
63
|
+
if (s.payload + 8 > s.end)
|
|
64
|
+
return out;
|
|
65
|
+
const count = u32(stsd, s.payload + 4);
|
|
66
|
+
let seen = 0;
|
|
67
|
+
let first = '';
|
|
68
|
+
for (const e of boxes(stsd, s.payload + 8, s.end)) {
|
|
69
|
+
if (seen++ >= count)
|
|
70
|
+
break;
|
|
71
|
+
first ||= e.type;
|
|
72
|
+
if (e.type !== first && !MIXED_ENTRY_TYPES.includes(first) && (first !== 'AV1x' || e.type !== 'AVup'))
|
|
73
|
+
continue;
|
|
74
|
+
const entry = { type: e.type, children: [] };
|
|
75
|
+
out.push(entry);
|
|
76
|
+
if (e.start + VISUAL_ENTRY_HEADER > e.end)
|
|
77
|
+
continue;
|
|
78
|
+
for (const c of boxes(stsd, e.start + VISUAL_ENTRY_HEADER, e.end)) {
|
|
79
|
+
if (c.type === 'sinf') {
|
|
80
|
+
for (const f of boxes(stsd, c.payload, c.end))
|
|
81
|
+
if (f.type === 'frma' && f.payload + 4 <= f.end)
|
|
82
|
+
entry.type = fourccAt(stsd, f.payload);
|
|
83
|
+
}
|
|
84
|
+
entry.children.push({ type: c.type, start: c.payload, end: c.end });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
const live = (b) => ArrayBuffer.isView(b) && b.buffer.byteLength > 0;
|
|
90
|
+
const absentOrLive = (b) => b == null || live(b);
|
|
91
|
+
function lastChild(b, parent, type) {
|
|
92
|
+
let last;
|
|
93
|
+
if (parent)
|
|
94
|
+
for (const c of boxes(b, parent.payload, parent.end, true))
|
|
95
|
+
if (c.type === type)
|
|
96
|
+
last = c;
|
|
97
|
+
return last;
|
|
98
|
+
}
|
|
99
|
+
function moovBoxes(moov, trak) {
|
|
100
|
+
const b = new Uint8Array(moov.buffer, moov.byteOffset, moov.byteLength);
|
|
101
|
+
const cut = (box) => (box ? b.subarray(box.start, box.end) : null);
|
|
102
|
+
const top = boxes(b, 0, b.length, true).next().value;
|
|
103
|
+
let mvhd;
|
|
104
|
+
let n = 0;
|
|
105
|
+
if (top?.type === 'moov') {
|
|
106
|
+
for (const c of boxes(b, top.payload, top.end, true)) {
|
|
107
|
+
if (c.type === 'mvhd')
|
|
108
|
+
mvhd = c;
|
|
109
|
+
else if (c.type === 'trak' && n++ === trak)
|
|
110
|
+
return { stsd: cut(lastChild(b, lastChild(b, lastChild(b, lastChild(b, c, 'mdia'), 'minf'), 'stbl'), 'stsd')), tkhd: cut(lastChild(b, c, 'tkhd')), mvhd: cut(mvhd) };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
export function isoConfig(config) {
|
|
116
|
+
const found = config.family === 'isobmff' ? config : config.family === 'isobmff-moov' && live(config.moov) ? moovBoxes(config.moov, config.trak) : null;
|
|
117
|
+
return found && live(found.stsd) && absentOrLive(found.tkhd) && absentOrLive(found.mvhd) ? { bytes: found.stsd, entries: sampleEntries(found.stsd), tkhd: found.tkhd, mvhd: found.mvhd } : null;
|
|
118
|
+
}
|
|
119
|
+
export function firstBox(entry, type) {
|
|
120
|
+
return entry?.children.find((c) => c.type === type) ?? null;
|
|
121
|
+
}
|
|
122
|
+
export function boxPayload(box) {
|
|
123
|
+
if (!box)
|
|
124
|
+
return null;
|
|
125
|
+
const b = boxes(box, 0, box.length).next();
|
|
126
|
+
return b.done ? null : { start: b.value.payload, end: b.value.end };
|
|
127
|
+
}
|
|
128
|
+
function vint(b, p, end, marker) {
|
|
129
|
+
if (p >= end)
|
|
130
|
+
return null;
|
|
131
|
+
const first = b[p];
|
|
132
|
+
let len = 1;
|
|
133
|
+
while (len <= 8 && !(first & (0x80 >> (len - 1))))
|
|
134
|
+
len++;
|
|
135
|
+
if (len > 8 || p + len > end)
|
|
136
|
+
return null;
|
|
137
|
+
let v = marker ? first : first & (0xff >> len);
|
|
138
|
+
let ones = v === 0xff >> len;
|
|
139
|
+
for (let i = 1; i < len; i++) {
|
|
140
|
+
v = v * 256 + b[p + i];
|
|
141
|
+
ones &&= b[p + i] === 0xff;
|
|
142
|
+
}
|
|
143
|
+
return [!marker && ones ? -1 : v, len];
|
|
144
|
+
}
|
|
145
|
+
export function* elements(b, start, end) {
|
|
146
|
+
let p = start;
|
|
147
|
+
while (p < end) {
|
|
148
|
+
const id = vint(b, p, end, true);
|
|
149
|
+
if (!id)
|
|
150
|
+
return;
|
|
151
|
+
const size = vint(b, p + id[1], end, false);
|
|
152
|
+
if (!size || size[0] < 0)
|
|
153
|
+
return;
|
|
154
|
+
const data = p + id[1] + size[1];
|
|
155
|
+
const dataEnd = Math.min(data + size[0], end);
|
|
156
|
+
yield { id: id[0], start: data, end: dataEnd };
|
|
157
|
+
p = data + size[0];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export function elementUint(b, span) {
|
|
161
|
+
if (!span || span.end === span.start || span.end - span.start > 8)
|
|
162
|
+
return null;
|
|
163
|
+
let v = 0;
|
|
164
|
+
for (let p = span.start; p < span.end; p++)
|
|
165
|
+
v = v * 256 + b[p];
|
|
166
|
+
return v;
|
|
167
|
+
}
|
|
168
|
+
function children(b, span, out = new Map()) {
|
|
169
|
+
for (const e of elements(b, span.start, span.end))
|
|
170
|
+
if (e.id !== ID_COLOUR || !out.has(e.id))
|
|
171
|
+
out.set(e.id, e);
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
function codecIdAt(b, span) {
|
|
175
|
+
let id = '';
|
|
176
|
+
for (let p = span.start; p < span.end && p < span.start + MAX_CODEC_ID && b[p]; p++)
|
|
177
|
+
id += String.fromCharCode(b[p]);
|
|
178
|
+
return id;
|
|
179
|
+
}
|
|
180
|
+
export function matroskaConfig(config) {
|
|
181
|
+
const b = config.family === 'matroska' ? config.trackEntry : null;
|
|
182
|
+
if (!live(b))
|
|
183
|
+
return null;
|
|
184
|
+
const out = { bytes: b, codecId: '', codecPrivate: null, video: new Map(), colour: new Map(), mappings: [] };
|
|
185
|
+
for (const e of elements(b, 0, b.length)) {
|
|
186
|
+
if (e.id === ID_CODEC_ID)
|
|
187
|
+
out.codecId = codecIdAt(b, e);
|
|
188
|
+
else if (e.id === ID_CODEC_PRIVATE)
|
|
189
|
+
out.codecPrivate = e;
|
|
190
|
+
else if (e.id === ID_VIDEO)
|
|
191
|
+
children(b, e, out.video);
|
|
192
|
+
else if (e.id === ID_BLOCK_ADDITION_MAPPING)
|
|
193
|
+
out.mappings.push(children(b, e));
|
|
194
|
+
}
|
|
195
|
+
const colour = out.video.get(ID_COLOUR);
|
|
196
|
+
if (colour)
|
|
197
|
+
out.colour = children(b, colour);
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ColorInfo, FileResult, VideoTrackResult } from '../core/types.js';
|
|
2
|
+
export type { CodecConfig, ColorInfo, ColorSource, DolbyVisionConfig } from '../core/types.js';
|
|
3
|
+
export type ColorTrack = Pick<VideoTrackResult, 'codecName' | 'codecConfig'>;
|
|
4
|
+
export declare function colorOf(track: ColorTrack): ColorInfo | null | undefined;
|
|
5
|
+
export declare function withColor<R extends Pick<FileResult, 'videoTracks'>>(result: R, options?: {
|
|
6
|
+
keepCodecConfig?: boolean;
|
|
7
|
+
}): R;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { dolbyVisionString, isoConfigFacts, matroskaConfigFacts } from "./codec-string.js";
|
|
2
|
+
import { elementUint, fourccAt, ID_BITS_PER_CHANNEL, ID_BLOCK_ADD_ID_EXTRA_DATA, ID_BLOCK_ADD_ID_TYPE, ID_MATRIX_COEFFICIENTS, ID_PRIMARIES, ID_RANGE, ID_TRANSFER_CHARACTERISTICS, isoConfig, matroskaConfig, u16, } from "./config.js";
|
|
3
|
+
import { colorNames, namedCodes, UNSPECIFIED } from "./names.js";
|
|
4
|
+
const DOLBY_VISION_FOURCCS = new Set(['dvh1', 'dvhe', 'dvav', 'dva1', 'dav1']);
|
|
5
|
+
const DOLBY_VISION_BOXES = ['dvcC', 'dvvC', 'dvwC'];
|
|
6
|
+
const DOLBY_VISION_MAPPINGS = new Set([0x64766343, 0x64767643]);
|
|
7
|
+
const SPS_CODECS = new Set(['h264', 'hevc']);
|
|
8
|
+
function dolbyVision(b, s) {
|
|
9
|
+
if (s.end - s.start < 4)
|
|
10
|
+
return null;
|
|
11
|
+
const p = s.start;
|
|
12
|
+
const bits = u16(b, p + 2);
|
|
13
|
+
const more = s.end - s.start >= 5;
|
|
14
|
+
return {
|
|
15
|
+
versionMajor: b[p],
|
|
16
|
+
versionMinor: b[p + 1],
|
|
17
|
+
profile: bits >> 9,
|
|
18
|
+
level: (bits >> 3) & 63,
|
|
19
|
+
rpu: (bits & 4) !== 0,
|
|
20
|
+
el: (bits & 2) !== 0,
|
|
21
|
+
bl: (bits & 1) !== 0,
|
|
22
|
+
compatibilityId: more ? b[p + 4] >> 4 : 0,
|
|
23
|
+
mdCompression: more ? (b[p + 4] >> 2) & 3 : 0,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function colrOf(b, s) {
|
|
27
|
+
if (s.end - s.start < 10)
|
|
28
|
+
return null;
|
|
29
|
+
const kind = fourccAt(b, s.start);
|
|
30
|
+
if (kind !== 'nclx' && kind !== 'nclc')
|
|
31
|
+
return null;
|
|
32
|
+
const codes = namedCodes([u16(b, s.start + 4), u16(b, s.start + 6), u16(b, s.start + 8)]);
|
|
33
|
+
return { source: 'colr', codes, fullRange: kind === 'nclx' ? (s.end - s.start >= 11 ? b[s.start + 10] >> 7 === 1 : false) : null };
|
|
34
|
+
}
|
|
35
|
+
function vpcOf(b, s) {
|
|
36
|
+
if (s.end - s.start < 12 || b[s.start] !== 1)
|
|
37
|
+
return null;
|
|
38
|
+
const p = s.start + 4;
|
|
39
|
+
return { source: 'vpcC', codes: namedCodes([b[p + 3], b[p + 4], b[p + 5]]), fullRange: (b[p + 2] & 1) === 1 };
|
|
40
|
+
}
|
|
41
|
+
function matroskaColour(b, colour) {
|
|
42
|
+
if (!colour.size)
|
|
43
|
+
return null;
|
|
44
|
+
const code = (id, reserved) => {
|
|
45
|
+
const v = elementUint(b, colour.get(id)) ?? UNSPECIFIED;
|
|
46
|
+
return reserved.includes(v) ? UNSPECIFIED : v;
|
|
47
|
+
};
|
|
48
|
+
const range = elementUint(b, colour.get(ID_RANGE));
|
|
49
|
+
return { source: 'Colour', codes: [code(ID_PRIMARIES, [0, 3]), code(ID_TRANSFER_CHARACTERISTICS, [0, 3]), code(ID_MATRIX_COEFFICIENTS, [3])], fullRange: range === 1 ? false : range === 2 ? true : null };
|
|
50
|
+
}
|
|
51
|
+
function hdrOf(transfer) {
|
|
52
|
+
return transfer === 'smpte2084' ? 'pq' : transfer === 'arib-std-b67' ? 'hlg' : null;
|
|
53
|
+
}
|
|
54
|
+
function resolve(codecName, container, facts, dv, bitDepth) {
|
|
55
|
+
const vui = SPS_CODECS.has(codecName) ? (facts?.sps?.vui ?? null) : null;
|
|
56
|
+
const specified = container !== null && container.codes.some((c) => c !== UNSPECIFIED);
|
|
57
|
+
const codes = specified ? container.codes : vui?.colour ? namedCodes(vui.colour) : null;
|
|
58
|
+
const source = specified ? container.source : vui?.colour ? 'sps' : null;
|
|
59
|
+
let fullRange = container?.fullRange ?? vui?.fullRange ?? null;
|
|
60
|
+
if (fullRange === null && codecName === 'hevc' && facts?.sps)
|
|
61
|
+
fullRange = false;
|
|
62
|
+
const [primaries, transfer, space] = colorNames(codes);
|
|
63
|
+
return {
|
|
64
|
+
range: fullRange === null ? null : fullRange ? 'pc' : 'tv',
|
|
65
|
+
primaries,
|
|
66
|
+
transfer,
|
|
67
|
+
space,
|
|
68
|
+
source: codes?.some((c) => c !== UNSPECIFIED) ? source : null,
|
|
69
|
+
bitDepth: facts?.bitDepth ?? bitDepth,
|
|
70
|
+
profile: facts?.profile ?? null,
|
|
71
|
+
codecString: facts?.codecString ?? null,
|
|
72
|
+
hdr: hdrOf(transfer),
|
|
73
|
+
dolbyVision: dv,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function isoColor(codecName, config) {
|
|
77
|
+
const iso = isoConfig(config);
|
|
78
|
+
if (!iso)
|
|
79
|
+
return undefined;
|
|
80
|
+
const first = iso.entries[0];
|
|
81
|
+
if (!first)
|
|
82
|
+
return null;
|
|
83
|
+
const b = iso.bytes;
|
|
84
|
+
let container = null;
|
|
85
|
+
let fullRange = null;
|
|
86
|
+
let dv = null;
|
|
87
|
+
for (const entry of iso.entries) {
|
|
88
|
+
for (const c of entry.children) {
|
|
89
|
+
const found = c.type === 'colr' ? colrOf(b, c) : c.type === 'vpcC' ? vpcOf(b, c) : null;
|
|
90
|
+
if (found) {
|
|
91
|
+
container = found;
|
|
92
|
+
fullRange = found.fullRange ?? fullRange;
|
|
93
|
+
}
|
|
94
|
+
if (DOLBY_VISION_BOXES.includes(c.type))
|
|
95
|
+
dv = dolbyVision(b, c) ?? dv;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const facts = isoConfigFacts(codecName, b, first);
|
|
99
|
+
if (dv && facts && DOLBY_VISION_FOURCCS.has(first.type))
|
|
100
|
+
facts.codecString = dolbyVisionString(first.type, dv.profile, dv.level);
|
|
101
|
+
return container || facts || dv ? resolve(codecName, container && { ...container, fullRange }, facts, dv, null) : null;
|
|
102
|
+
}
|
|
103
|
+
function matroskaColor(codecName, config) {
|
|
104
|
+
const mkv = matroskaConfig(config);
|
|
105
|
+
if (!mkv)
|
|
106
|
+
return undefined;
|
|
107
|
+
const b = mkv.bytes;
|
|
108
|
+
const container = matroskaColour(b, mkv.colour);
|
|
109
|
+
let dv = null;
|
|
110
|
+
for (const m of mkv.mappings) {
|
|
111
|
+
const extra = m.get(ID_BLOCK_ADD_ID_EXTRA_DATA);
|
|
112
|
+
if (DOLBY_VISION_MAPPINGS.has(elementUint(b, m.get(ID_BLOCK_ADD_ID_TYPE)) ?? -1) && extra)
|
|
113
|
+
dv = dolbyVision(b, extra) ?? dv;
|
|
114
|
+
}
|
|
115
|
+
const facts = matroskaConfigFacts(mkv.codecId, b, mkv.codecPrivate);
|
|
116
|
+
const bits = elementUint(b, mkv.colour.get(ID_BITS_PER_CHANNEL));
|
|
117
|
+
if (!container && !facts && !dv && !bits)
|
|
118
|
+
return null;
|
|
119
|
+
return resolve(codecName, container, facts, dv, bits ? bits : null);
|
|
120
|
+
}
|
|
121
|
+
export function colorOf(track) {
|
|
122
|
+
const config = track.codecConfig;
|
|
123
|
+
if (!config)
|
|
124
|
+
return undefined;
|
|
125
|
+
return config.family === 'matroska' ? matroskaColor(track.codecName, config) : isoColor(track.codecName, config);
|
|
126
|
+
}
|
|
127
|
+
export function withColor(result, options = {}) {
|
|
128
|
+
return {
|
|
129
|
+
...result,
|
|
130
|
+
videoTracks: result.videoTracks.map((t) => {
|
|
131
|
+
if (!t.codecConfig)
|
|
132
|
+
return t;
|
|
133
|
+
const out = { ...t, color: colorOf(t) };
|
|
134
|
+
if (!options.keepCodecConfig)
|
|
135
|
+
delete out.codecConfig;
|
|
136
|
+
return out;
|
|
137
|
+
}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const PRIMARIES = 'reserved|bt709|unknown|reserved|bt470m|bt470bg|smpte170m|smpte240m|film|bt2020|smpte428|smpte431|smpte432||||||||||ebu3213'.split('|');
|
|
2
|
+
const TRANSFERS = 'reserved|bt709|unknown|reserved|bt470m|bt470bg|smpte170m|smpte240m|linear|log100|log316|iec61966-2-4|bt1361e|iec61966-2-1|bt2020-10|bt2020-12|smpte2084|smpte428|arib-std-b67'.split('|');
|
|
3
|
+
const MATRICES = 'gbr|bt709|unknown|reserved|fcc|bt470bg|smpte170m|smpte240m|ycgco|bt2020nc|bt2020c|smpte2085|chroma-derived-nc|chroma-derived-c|ictcp|ipt-c2|ycgco-re|ycgco-ro'.split('|');
|
|
4
|
+
const EXTENDED_BASE = 256;
|
|
5
|
+
const TABLES = {
|
|
6
|
+
primaries: { base: PRIMARIES, extended: ['vgamut'] },
|
|
7
|
+
transfer: { base: TRANSFERS, extended: ['vlog'] },
|
|
8
|
+
space: { base: MATRICES, extended: [] },
|
|
9
|
+
};
|
|
10
|
+
export const UNSPECIFIED = 2;
|
|
11
|
+
export const VUI_RULE = {
|
|
12
|
+
h264: { colour: 'container-first', range: 'container-first' },
|
|
13
|
+
hevc: { colour: 'container-first', range: 'container-first' },
|
|
14
|
+
};
|
|
15
|
+
function nameOf(member, code) {
|
|
16
|
+
const table = TABLES[member];
|
|
17
|
+
return (code >= EXTENDED_BASE ? table.extended[code - EXTENDED_BASE] : table.base[code]) || undefined;
|
|
18
|
+
}
|
|
19
|
+
export function colorName(member, code) {
|
|
20
|
+
if (code === null || code === UNSPECIFIED)
|
|
21
|
+
return null;
|
|
22
|
+
return nameOf(member, code) ?? null;
|
|
23
|
+
}
|
|
24
|
+
export function namedOrUnspecified(member, code) {
|
|
25
|
+
return nameOf(member, code) === undefined ? UNSPECIFIED : code;
|
|
26
|
+
}
|
|
27
|
+
const MEMBERS = ['primaries', 'transfer', 'space'];
|
|
28
|
+
export function namedCodes(codes) {
|
|
29
|
+
return MEMBERS.map((m, i) => namedOrUnspecified(m, codes[i]));
|
|
30
|
+
}
|
|
31
|
+
export function colorNames(codes) {
|
|
32
|
+
return codes ? MEMBERS.map((m, i) => colorName(m, codes[i])) : [null, null, null];
|
|
33
|
+
}
|