taglib-wasm 2.2.3 → 2.3.0
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/README.md +192 -13
- package/dist/index.browser.d.ts +24 -4
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1123 -208
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/simple.browser.js +527 -202
- package/dist/src/errors/base.d.ts +17 -2
- package/dist/src/errors/base.d.ts.map +1 -1
- package/dist/src/errors/base.js +18 -0
- package/dist/src/runtime/wasi-adapter/adapter.d.ts +13 -1
- package/dist/src/runtime/wasi-adapter/adapter.d.ts.map +1 -1
- package/dist/src/runtime/wasi-adapter/adapter.js +17 -0
- package/dist/src/runtime/wasi-adapter/audio-properties.d.ts +4 -1
- package/dist/src/runtime/wasi-adapter/audio-properties.d.ts.map +1 -1
- package/dist/src/runtime/wasi-adapter/audio-properties.js +23 -6
- package/dist/src/runtime/wasi-adapter/path-open.d.ts +68 -0
- package/dist/src/runtime/wasi-adapter/path-open.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/path-open.js +76 -0
- package/dist/src/runtime/wasi-adapter/wasm-io.d.ts.map +1 -1
- package/dist/src/runtime/wasi-adapter/wasm-io.js +36 -28
- package/dist/src/runtime/wasi-host-loader.js +8 -3
- package/dist/src/runtime/wasmer-sdk-loader/types.d.ts +13 -0
- package/dist/src/runtime/wasmer-sdk-loader/types.d.ts.map +1 -1
- package/dist/src/simple/checksum-operations.d.ts +25 -0
- package/dist/src/simple/checksum-operations.d.ts.map +1 -0
- package/dist/src/simple/checksum-operations.js +7 -0
- package/dist/src/simple/index.d.ts +2 -0
- package/dist/src/simple/index.d.ts.map +1 -1
- package/dist/src/simple/index.js +2 -0
- package/dist/src/taglib/audio-file-checksum.d.ts +47 -0
- package/dist/src/taglib/audio-file-checksum.d.ts.map +1 -0
- package/dist/src/taglib/audio-file-checksum.js +77 -0
- package/dist/src/taglib/audio-file-impl.d.ts +4 -0
- package/dist/src/taglib/audio-file-impl.d.ts.map +1 -1
- package/dist/src/taglib/audio-file-impl.js +32 -0
- package/dist/src/taglib/audio-file-interface.d.ts +17 -0
- package/dist/src/taglib/audio-file-interface.d.ts.map +1 -1
- package/dist/src/taglib/media-ranges.d.ts +85 -0
- package/dist/src/taglib/media-ranges.d.ts.map +1 -0
- package/dist/src/taglib/media-ranges.js +192 -0
- package/dist/src/taglib/metadata-extent.d.ts +55 -0
- package/dist/src/taglib/metadata-extent.d.ts.map +1 -1
- package/dist/src/taglib/metadata-extent.js +24 -6
- package/dist/src/taglib/taglib-class.d.ts.map +1 -1
- package/dist/src/taglib/taglib-class.js +13 -24
- package/dist/src/utils/path.d.ts +12 -0
- package/dist/src/utils/path.d.ts.map +1 -1
- package/dist/src/utils/path.js +19 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/wasm.d.ts +9 -0
- package/dist/src/wasm.d.ts.map +1 -1
- package/dist/taglib-wasi.wasm +0 -0
- package/dist/taglib-web.wasm +0 -0
- package/dist/web.browser.d.ts +13 -0
- package/dist/web.browser.d.ts.map +1 -0
- package/dist/web.browser.js +3630 -0
- package/package.json +12 -4
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `mediaChecksum()`: a SHA-256 of a file's *encoded media
|
|
3
|
+
* payload* — the bytes that are the audio, and not the tags around them — plus
|
|
4
|
+
* FLAC's STREAMINFO digest passthrough. Factored out of `AudioFileImpl` so the
|
|
5
|
+
* source rule below (what actually gets hashed) is stated exactly once.
|
|
6
|
+
*/
|
|
7
|
+
import type { PlatformIO } from "../runtime/platform-io.js";
|
|
8
|
+
export type MediaChecksum = {
|
|
9
|
+
source: "audio-payload" | "file";
|
|
10
|
+
algorithm: "sha256";
|
|
11
|
+
hex: string;
|
|
12
|
+
bytesHashed: number;
|
|
13
|
+
} | {
|
|
14
|
+
source: "flac-streaminfo-md5";
|
|
15
|
+
algorithm: "md5";
|
|
16
|
+
hex: string;
|
|
17
|
+
bytesHashed: 16;
|
|
18
|
+
};
|
|
19
|
+
export interface MediaChecksumOptions {
|
|
20
|
+
basis?: "encoded" | "pcm";
|
|
21
|
+
}
|
|
22
|
+
/** The literal a checksum's `source` field carries, so a consumer can hold one
|
|
23
|
+
* without restating the values. */
|
|
24
|
+
export type ChecksumSource = MediaChecksum["source"];
|
|
25
|
+
/** The algorithm any returned checksum can carry — narrowed by `source`. */
|
|
26
|
+
export type ChecksumAlgorithm = MediaChecksum["algorithm"];
|
|
27
|
+
/**
|
|
28
|
+
* What the checksum needs from the open handle — module-internal, and not
|
|
29
|
+
* re-exported from either entry point (`ChecksumSource` above is the public name,
|
|
30
|
+
* and a handle descriptor must not shadow it).
|
|
31
|
+
*/
|
|
32
|
+
interface ChecksumHandleSource {
|
|
33
|
+
/** The bytes the handle holds — a full file, a partial-load window image, or
|
|
34
|
+
* nothing at all when the data lives on disk (WASI path mode). */
|
|
35
|
+
bytes: Uint8Array;
|
|
36
|
+
/** The path, when the handle was opened from one. */
|
|
37
|
+
path?: string;
|
|
38
|
+
/** The File/Blob, when the handle was opened from one. */
|
|
39
|
+
blob?: Blob;
|
|
40
|
+
/** True when `bytes` is a spliced header+footer image rather than the file.
|
|
41
|
+
* Must be captured when the handle is created: `saveToFile()` clears the
|
|
42
|
+
* instance's own flag while the bytes stay spliced. */
|
|
43
|
+
partiallyLoaded: boolean;
|
|
44
|
+
}
|
|
45
|
+
export declare function mediaChecksum(source: ChecksumHandleSource, format: string, io: PlatformIO, options?: MediaChecksumOptions): Promise<MediaChecksum>;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=audio-file-checksum.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-file-checksum.d.ts","sourceRoot":"","sources":["../../../src/taglib/audio-file-checksum.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAG5D,MAAM,MAAM,aAAa,GACrB;IACA,MAAM,EAAE,eAAe,GAAG,MAAM,CAAC;IACjC,SAAS,EAAE,QAAQ,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB,GACC;IACA,MAAM,EAAE,qBAAqB,CAAC;IAC9B,SAAS,EAAE,KAAK,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,EAAE,CAAC;CACjB,CAAC;AAEJ,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC;CAC3B;AAED;mCACmC;AACnC,MAAM,MAAM,cAAc,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;AAErD,4EAA4E;AAC5E,MAAM,MAAM,iBAAiB,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;AAE3D;;;;GAIG;AACH,UAAU,oBAAoB;IAC5B;sEACkE;IAClE,KAAK,EAAE,UAAU,CAAC;IAClB,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ;;2DAEuD;IACvD,eAAe,EAAE,OAAO,CAAC;CAC1B;AA6FD,wBAAsB,aAAa,CACjC,MAAM,EAAE,oBAAoB,EAC5B,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,UAAU,EACd,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,aAAa,CAAC,CAoExB"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { flacStreamInfoMd5, mediaRanges } from "./media-ranges.js";
|
|
2
|
+
import { MetadataError, UnsupportedFormatError } from "../errors.js";
|
|
3
|
+
const STREAMINFO_WINDOW = 65536;
|
|
4
|
+
async function fileBytes(source, io) {
|
|
5
|
+
if (source.path) return await io.readFile(source.path);
|
|
6
|
+
if (source.blob) return new Uint8Array(await source.blob.arrayBuffer());
|
|
7
|
+
if (!source.partiallyLoaded && source.bytes.length > 0) return source.bytes;
|
|
8
|
+
throw new MetadataError(
|
|
9
|
+
"read",
|
|
10
|
+
"the handle holds no file bytes and has no readable source; a partial handle that has been saved drops its source \u2014 checksum the saved path instead"
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
function joinRanges(bytes, ranges) {
|
|
14
|
+
const total = ranges.reduce((n, r) => n + r.length, 0);
|
|
15
|
+
const overrun = ranges.find((r) => r.offset + r.length > bytes.length);
|
|
16
|
+
if (overrun || total > bytes.length) {
|
|
17
|
+
throw new MetadataError(
|
|
18
|
+
"read",
|
|
19
|
+
`range walk overran the buffer (${overrun ? `range ${overrun.offset}+${overrun.length}` : `${total} bytes`} past the ${bytes.length}-byte buffer)`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
if (ranges.length === 1) {
|
|
23
|
+
const only = ranges[0];
|
|
24
|
+
const view = bytes.subarray(only.offset, only.offset + only.length);
|
|
25
|
+
return view.buffer instanceof ArrayBuffer ? view : new Uint8Array(view);
|
|
26
|
+
}
|
|
27
|
+
const out = new Uint8Array(total);
|
|
28
|
+
let at = 0;
|
|
29
|
+
for (const r of ranges) {
|
|
30
|
+
out.set(bytes.subarray(r.offset, r.offset + r.length), at);
|
|
31
|
+
at += r.length;
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
async function mediaChecksum(source, format, io, options = {}) {
|
|
36
|
+
if (options.basis === "pcm") {
|
|
37
|
+
if (format.toUpperCase() !== "FLAC") {
|
|
38
|
+
throw new UnsupportedFormatError(format, ["FLAC"], {
|
|
39
|
+
operation: "media checksum (basis: pcm)"
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const windowed = source.bytes.length > 0 ? source.bytes : source.path && io.readPartial ? await io.readPartial(source.path, STREAMINFO_WINDOW, 0) : void 0;
|
|
43
|
+
let md5 = windowed === void 0 ? void 0 : flacStreamInfoMd5(windowed);
|
|
44
|
+
if (md5 === void 0 && windowed !== source.bytes) {
|
|
45
|
+
md5 = flacStreamInfoMd5(await fileBytes(source, io));
|
|
46
|
+
}
|
|
47
|
+
if (md5 === void 0) {
|
|
48
|
+
throw new MetadataError(
|
|
49
|
+
"read",
|
|
50
|
+
"STREAMINFO digest unavailable"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
source: "flac-streaminfo-md5",
|
|
55
|
+
algorithm: "md5",
|
|
56
|
+
hex: md5,
|
|
57
|
+
bytesHashed: 16
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const bytes = await fileBytes(source, io);
|
|
61
|
+
const walk = mediaRanges(format, bytes);
|
|
62
|
+
const payload = joinRanges(bytes, walk.ranges);
|
|
63
|
+
const digest = new Uint8Array(
|
|
64
|
+
await crypto.subtle.digest("SHA-256", payload)
|
|
65
|
+
);
|
|
66
|
+
let hex = "";
|
|
67
|
+
for (const byte of digest) hex += byte.toString(16).padStart(2, "0");
|
|
68
|
+
return {
|
|
69
|
+
source: walk.kind === "ranges" ? "audio-payload" : "file",
|
|
70
|
+
algorithm: "sha256",
|
|
71
|
+
hex,
|
|
72
|
+
bytesHashed: payload.length
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
mediaChecksum
|
|
77
|
+
};
|
|
@@ -6,6 +6,7 @@ import type { Id3v2Frame, Rating, UnsyncedLyrics } from "../constants/complex-pr
|
|
|
6
6
|
import { type NormalizedRating } from "../utils/rating.js";
|
|
7
7
|
import type { AudioFile } from "./audio-file-interface.js";
|
|
8
8
|
import { BaseAudioFileImpl } from "./audio-file-base.js";
|
|
9
|
+
import { type MediaChecksum, type MediaChecksumOptions } from "./audio-file-checksum.js";
|
|
9
10
|
/** @internal Exported for unit tests (tests/node-fs-acquisition.test.ts). */
|
|
10
11
|
export declare function readFileSync(path: string): Uint8Array;
|
|
11
12
|
/**
|
|
@@ -16,9 +17,12 @@ export declare function readFileSync(path: string): Uint8Array;
|
|
|
16
17
|
*/
|
|
17
18
|
export declare class AudioFileImpl extends BaseAudioFileImpl implements AudioFile {
|
|
18
19
|
private pathModeBuffer;
|
|
20
|
+
/** How the handle was loaded — `isPartiallyLoaded` is mutated by saveToFile(). */
|
|
21
|
+
private readonly partiallyLoadedAtConstruction;
|
|
19
22
|
constructor(module: TagLibModule, fileHandle: WasmFileHandle, sourcePath?: string, originalSource?: string | Uint8Array | ArrayBuffer | File, isPartiallyLoaded?: boolean, partialLoadOptions?: OpenOptions);
|
|
20
23
|
save(): boolean;
|
|
21
24
|
getFileBuffer(): Uint8Array;
|
|
25
|
+
mediaChecksum(options?: MediaChecksumOptions): Promise<MediaChecksum>;
|
|
22
26
|
saveToFile(path?: string): Promise<void>;
|
|
23
27
|
getPictures(): Picture[];
|
|
24
28
|
setPictures(pictures: Picture[]): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio-file-impl.d.ts","sourceRoot":"","sources":["../../../src/taglib/audio-file-impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,YAAY,EACZ,cAAc,EACf,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE/D,OAAO,KAAK,EACV,UAAU,EACV,MAAM,EACN,cAAc,EACf,MAAM,oCAAoC,CAAC;AAc5C,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"audio-file-impl.d.ts","sourceRoot":"","sources":["../../../src/taglib/audio-file-impl.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,10 @@ import { getNodeFsSync } from "../utils/node-fs.js";
|
|
|
19
19
|
import { normalized } from "../utils/rating.js";
|
|
20
20
|
import { isDeno } from "../runtime/detector.js";
|
|
21
21
|
import { BaseAudioFileImpl } from "./audio-file-base.js";
|
|
22
|
+
import {
|
|
23
|
+
mediaChecksum
|
|
24
|
+
} from "./audio-file-checksum.js";
|
|
25
|
+
import { getPlatformIO } from "../runtime/platform-io.js";
|
|
22
26
|
import { saveViaFreshHandle } from "./save-reconstruct.js";
|
|
23
27
|
function sortChapters(list) {
|
|
24
28
|
return [...list].sort((a, b) => a.startTimeMs - b.startTimeMs);
|
|
@@ -50,6 +54,9 @@ class AudioFileImpl extends BaseAudioFileImpl {
|
|
|
50
54
|
partialLoadOptions
|
|
51
55
|
);
|
|
52
56
|
__publicField(this, "pathModeBuffer", null);
|
|
57
|
+
/** How the handle was loaded — `isPartiallyLoaded` is mutated by saveToFile(). */
|
|
58
|
+
__publicField(this, "partiallyLoadedAtConstruction");
|
|
59
|
+
this.partiallyLoadedAtConstruction = isPartiallyLoaded;
|
|
53
60
|
}
|
|
54
61
|
save() {
|
|
55
62
|
if (this.isPartiallyLoaded && this.originalSource) {
|
|
@@ -85,6 +92,31 @@ class AudioFileImpl extends BaseAudioFileImpl {
|
|
|
85
92
|
"No file data available: in-memory buffer is empty and no source path is set"
|
|
86
93
|
);
|
|
87
94
|
}
|
|
95
|
+
mediaChecksum(options) {
|
|
96
|
+
return mediaChecksum(
|
|
97
|
+
{
|
|
98
|
+
// The cheap, no-IO accessor — `getFileBuffer()` would read the whole
|
|
99
|
+
// file from disk in path mode (and cache it), which the source rule is
|
|
100
|
+
// about to read for itself.
|
|
101
|
+
bytes: this.handle.getBuffer(),
|
|
102
|
+
// empty in path mode: the data is on disk
|
|
103
|
+
// Both optional fields are conditional: they are `undefined` far more
|
|
104
|
+
// often than not, and `exactOptionalPropertyTypes` refuses an explicit
|
|
105
|
+
// `undefined` on an optional property.
|
|
106
|
+
...this.sourcePath !== void 0 ? { path: this.sourcePath } : {},
|
|
107
|
+
// originalSource is the File when the caller passed one. A partial File
|
|
108
|
+
// has no path, so this is the only way back to the real bytes.
|
|
109
|
+
...this.originalSource instanceof Blob ? { blob: this.originalSource } : {},
|
|
110
|
+
// Captured at construction, NOT this.isPartiallyLoaded: saveToFile() sets
|
|
111
|
+
// that flag false (and clears originalSource) while the handle still holds
|
|
112
|
+
// the spliced image, which would make the source rule hash the image.
|
|
113
|
+
partiallyLoaded: this.partiallyLoadedAtConstruction
|
|
114
|
+
},
|
|
115
|
+
this.getFormat(),
|
|
116
|
+
getPlatformIO(),
|
|
117
|
+
options
|
|
118
|
+
);
|
|
119
|
+
}
|
|
88
120
|
async saveToFile(path) {
|
|
89
121
|
const targetPath = path ?? this.sourcePath;
|
|
90
122
|
if (!targetPath) {
|
|
@@ -2,6 +2,7 @@ import type { AudioProperties, FileType, Picture, PropertyMap } from "../types.j
|
|
|
2
2
|
import type { TypedAudioProperties } from "../types/audio-formats.js";
|
|
3
3
|
import type { Id3v2Frame, Rating, UnsyncedLyrics } from "../constants/complex-properties.js";
|
|
4
4
|
import type { MutableTag } from "./mutable-tag.js";
|
|
5
|
+
import type { MediaChecksum, MediaChecksumOptions } from "./audio-file-checksum.js";
|
|
5
6
|
import type { FormatPropertyKey } from "../types/format-property-keys.js";
|
|
6
7
|
import type { NormalizedRating } from "../utils/rating.js";
|
|
7
8
|
/**
|
|
@@ -66,6 +67,22 @@ export interface AudioFile {
|
|
|
66
67
|
* and cannot be read back — never returns an empty buffer on failure.
|
|
67
68
|
*/
|
|
68
69
|
getFileBuffer(): Uint8Array;
|
|
70
|
+
/**
|
|
71
|
+
* SHA-256 of the file's encoded media payload — the bytes that are the audio,
|
|
72
|
+
* not the tags around them — so the digest survives a tag edit, with one
|
|
73
|
+
* boundary: an ID3v2 tag appended after a FLAC's audio is inside the payload,
|
|
74
|
+
* so editing it does move the hash. Formats whose payload cannot be delimited
|
|
75
|
+
* answer the whole file instead and say so (`source: "file"`), where a tag
|
|
76
|
+
* edit does move the hash.
|
|
77
|
+
*
|
|
78
|
+
* Pass `{ basis: "pcm" }` for FLAC's STREAMINFO MD5 — the digest of the
|
|
79
|
+
* *uncompressed* stream, read straight from the file's first metadata block.
|
|
80
|
+
* Other formats throw {@link UnsupportedFormatError}.
|
|
81
|
+
*
|
|
82
|
+
* @throws {MetadataError} If the handle holds no file bytes and has no
|
|
83
|
+
* readable source (a partial handle that has already been saved).
|
|
84
|
+
*/
|
|
85
|
+
mediaChecksum(options?: MediaChecksumOptions): Promise<MediaChecksum>;
|
|
69
86
|
/**
|
|
70
87
|
* Save all changes to a file on disk.
|
|
71
88
|
* @param path - Optional file path. If not provided, saves to the original path.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio-file-interface.d.ts","sourceRoot":"","sources":["../../../src/taglib/audio-file-interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,QAAQ,EACR,OAAO,EACP,WAAW,EACZ,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EACV,UAAU,EACV,MAAM,EACN,cAAc,EACf,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,iCAAiC;IACjC,SAAS,IAAI,QAAQ,CAAC;IAEtB,6DAA6D;IAC7D,GAAG,IAAI,UAAU,CAAC;IAElB,mEAAmE;IACnE,eAAe,IAAI,eAAe,GAAG,SAAS,CAAC;IAE/C,sDAAsD;IACtD,UAAU,IAAI,WAAW,CAAC;IAE1B,0DAA0D;IAC1D,aAAa,CAAC,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC;IAE7C;;;;;;OAMG;IACH,WAAW,CAAC,CAAC,SAAS,OAAO,iBAAiB,EAAE,WAAW,EACzD,GAAG,EAAE,CAAC,GACL,OAAO,iBAAiB,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC;IAE5D;;;;;;OAMG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;IAE/C,mDAAmD;IACnD,WAAW,CAAC,CAAC,SAAS,OAAO,iBAAiB,EAAE,WAAW,EACzD,GAAG,EAAE,CAAC,EACN,KAAK,EAAE,OAAO,iBAAiB,EAAE,aAAa,CAAC,CAAC,CAAC,GAChD,IAAI,CAAC;IAER,oDAAoD;IACpD,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAE9C;;;;;;OAMG;IACH,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAElC,gFAAgF;IAChF,QAAQ,CAAC,CAAC,SAAS,QAAQ,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,IAAI,cAAc,CAAC,CAAC,CAAC,CAAC;IAEnE,wCAAwC;IACxC,KAAK,IAAI,OAAO,CAAC;IAEjB,yCAAyC;IACzC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAE5C,yCAAyC;IACzC,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAE7C,4CAA4C;IAC5C,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAEjC,gDAAgD;IAChD,IAAI,IAAI,OAAO,CAAC;IAEhB;;;;OAIG;IACH,aAAa,IAAI,UAAU,CAAC;IAE5B;;;OAGG;IACH,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC,8DAA8D;IAC9D,OAAO,IAAI,OAAO,CAAC;IAEnB,sDAAsD;IACtD,WAAW,IAAI,OAAO,EAAE,CAAC;IAEzB,wEAAwE;IACxE,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAEvC,8CAA8C;IAC9C,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAEnC,+CAA+C;IAC/C,cAAc,IAAI,IAAI,CAAC;IAEvB;;;;OAIG;IACH,WAAW,IAAI,OAAO,sBAAsB,EAAE,OAAO,EAAE,CAAC;IAExD;;;;OAIG;IACH,WAAW,CACT,QAAQ,EAAE,OAAO,sBAAsB,EAAE,OAAO,EAAE,EAClD,OAAO,CAAC,EAAE,OAAO,sBAAsB,EAAE,kBAAkB,GAC1D,IAAI,CAAC;IAER;;;OAGG;IACH,OAAO,IACH,OAAO,iBAAiB,EAAE,uBAAuB,GACjD,SAAS,CAAC;IAEd,uFAAuF;IACvF,OAAO,CAAC,IAAI,EAAE,OAAO,iBAAiB,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAEvE,6FAA6F;IAC7F,WAAW,IAAI,UAAU,GAAG,SAAS,CAAC;IAEtC,uFAAuF;IACvF,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAAC;IAE3C,+EAA+E;IAC/E,OAAO,IAAI,MAAM,GAAG,SAAS,CAAC;IAE9B,wEAAwE;IACxE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAEnC,gEAAgE;IAChE,UAAU,IAAI,MAAM,EAAE,CAAC;IAEvB,6DAA6D;IAC7D,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAEpC,kFAAkF;IAClF,SAAS,IAAI,gBAAgB,GAAG,SAAS,CAAC;IAE1C,mDAAmD;IACnD,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhD;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE,CAAC;IAE1C;;;;OAIG;IACH,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAErD,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAEpC;;;OAGG;IACH,SAAS,IAAI,cAAc,EAAE,CAAC;IAE9B,6EAA6E;IAC7E,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAE1C;;;;OAIG;IACH,UAAU,IAAI;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC;IAE3C;;;OAGG;IACH,YAAY,CAAC,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,EAAE,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAE1D,uDAAuD;IACvD,OAAO,IAAI,IAAI,CAAC;IAEhB,uDAAuD;IACvD,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,QAAQ,CAAE,SAClD,IAAI,CACF,SAAS,EACT,aAAa,GAAG,aAAa,GAAG,gBAAgB,GAAG,iBAAiB,CACrE;IACD,SAAS,IAAI,CAAC,CAAC;IAEf,eAAe,IAAI,oBAAoB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAEvD,WAAW,CAAC,CAAC,SAAS,iBAAiB,CAAC,CAAC,CAAC,EACxC,GAAG,EAAE,CAAC,GACL,OAAO,iBAAiB,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC;IAE5D,WAAW,CAAC,CAAC,SAAS,iBAAiB,CAAC,CAAC,CAAC,EACxC,GAAG,EAAE,CAAC,EACN,KAAK,EAAE,OAAO,iBAAiB,EAAE,aAAa,CAAC,CAAC,CAAC,GAChD,IAAI,CAAC;IAER,cAAc,CAAC,CAAC,SAAS,iBAAiB,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;CAC9D"}
|
|
1
|
+
{"version":3,"file":"audio-file-interface.d.ts","sourceRoot":"","sources":["../../../src/taglib/audio-file-interface.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export interface ByteRange {
|
|
2
|
+
offset: number;
|
|
3
|
+
length: number;
|
|
4
|
+
}
|
|
5
|
+
export interface RangeWalk {
|
|
6
|
+
kind: "ranges" | "fallback";
|
|
7
|
+
ranges: ByteRange[];
|
|
8
|
+
detail: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Start offset of a trailing tag run (ID3v1 and/or APEv2), scanning inward.
|
|
12
|
+
* TagLib ends a FLAC stream at its ID3v1 location for the same reason.
|
|
13
|
+
*/
|
|
14
|
+
export declare function trailingTagStart(bytes: Uint8Array, end?: number): number;
|
|
15
|
+
export declare function walkMpeg(bytes: Uint8Array): RangeWalk;
|
|
16
|
+
/** STREAMINFO is a FLAC file's first metadata block: the `fLaC` marker, a
|
|
17
|
+
* 4-byte block header, then its 34-byte payload, whose last 16 bytes are the
|
|
18
|
+
* MD5 of the *uncompressed* stream — the digest `basis: "pcm"` reports. */
|
|
19
|
+
export declare function flacStreamInfoMd5(bytes: Uint8Array): string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* FLAC's encoded payload: the audio frames between the end of the metadata
|
|
22
|
+
* block chain and an appended ID3v1/APEv2 tag. An appended ID3v2 is *inside*
|
|
23
|
+
* the payload: TagLib ends a FLAC stream at its ID3v1 location
|
|
24
|
+
* (`flacfile.cpp:595-599`), and a tag not at that location is stream by the
|
|
25
|
+
* same rule — TagLib is this feature's ground truth, so the boundary is pinned
|
|
26
|
+
* by `tests/media-ranges.test.ts`, not "fixed" here. Nothing inside a FLAC
|
|
27
|
+
* stream delimits the frames — the block chain's end is the start, and the
|
|
28
|
+
* stream runs to EOF — so the whole rule is the block walk plus the
|
|
29
|
+
* trailing-tag trim.
|
|
30
|
+
*
|
|
31
|
+
* `flacAudioStart` may legitimately answer past `bytes.length`, because a
|
|
32
|
+
* block's declared length can reach beyond the bytes in hand: that is the
|
|
33
|
+
* extent the chain *implies*, and hashing it would read nothing while claiming
|
|
34
|
+
* a payload. The `end <= start` guard is what refuses it.
|
|
35
|
+
*
|
|
36
|
+
* The walk deliberately does **not** carry the STREAMINFO digest: a range result
|
|
37
|
+
* is not a digest carrier, and the PCM basis reads the digest with
|
|
38
|
+
* `flacStreamInfoMd5` directly. A second, unread path to the same value rots.
|
|
39
|
+
*/
|
|
40
|
+
export declare function walkFlac(bytes: Uint8Array): RangeWalk;
|
|
41
|
+
/**
|
|
42
|
+
* MP4's payload: the contents of every non-empty top-level `mdat`, in file
|
|
43
|
+
* order — a fragmented file spreads its audio over several, and a rewritten one
|
|
44
|
+
* may carry an empty `mdat`, which is legal and contributes nothing. The header
|
|
45
|
+
* is excluded, and it is 16 bytes rather than 8 when the size took the 64-bit
|
|
46
|
+
* form.
|
|
47
|
+
*
|
|
48
|
+
* Deliberately not `metadata-extent.ts`'s atom walk: that one looks for `moov`
|
|
49
|
+
* to size the partial-load gate, so it bails on any size below 8 and has no
|
|
50
|
+
* 64-bit path. The checksum's rules differ — `size === 0` runs to EOF, a 64-bit
|
|
51
|
+
* size is legal, and anything the walk cannot fully account for is a fallback —
|
|
52
|
+
* so the two contracts stay apart rather than share an abstraction.
|
|
53
|
+
*
|
|
54
|
+
* Those fallbacks are the point. A range that runs past the buffer would hash
|
|
55
|
+
* zero padding while `bytesHashed` counts it as payload, and bytes no atom
|
|
56
|
+
* accounts for are not something this walk may call audio.
|
|
57
|
+
*/
|
|
58
|
+
export declare function walkMp4(bytes: Uint8Array): RangeWalk;
|
|
59
|
+
/**
|
|
60
|
+
* WAV's payload: every non-empty `data` chunk's contents, payload only.
|
|
61
|
+
*
|
|
62
|
+
* The chunk list is the whole rule: a chunk is a 4-byte id, a 4-byte
|
|
63
|
+
* little-endian size that counts the payload but not the header, and that
|
|
64
|
+
* payload padded to an even length. Tag chunks (`LIST`, `id3 `, `bext`, `iXML`)
|
|
65
|
+
* are therefore skipped by construction rather than by name, and chunks that
|
|
66
|
+
* follow `data` cannot move or clip the range — which is the property the
|
|
67
|
+
* repo's `wav/kiss-snippet.wav` pins, `data` first and `LIST` + `id3 ` behind it.
|
|
68
|
+
*
|
|
69
|
+
* A chunk whose declared size reaches past the buffer is a fallback, not a
|
|
70
|
+
* clamp: hashing the bytes that survive would report a payload the file does not
|
|
71
|
+
* claim. That is also the truncated-file case.
|
|
72
|
+
*/
|
|
73
|
+
export declare function walkWav(bytes: Uint8Array): RangeWalk;
|
|
74
|
+
/**
|
|
75
|
+
* The per-format dispatch: the one entry point a consumer calls, and the one
|
|
76
|
+
* place a format-level fallback becomes a whole-file range.
|
|
77
|
+
*
|
|
78
|
+
* Consumers MUST dispatch on `kind`, never on `ranges.length`. The walks
|
|
79
|
+
* themselves answer a fallback with `ranges: []`, which this function replaces
|
|
80
|
+
* with the whole file — so after normalization there is no empty-ranges answer
|
|
81
|
+
* to detect, and a caller testing `ranges.length === 0` would read a fallback as
|
|
82
|
+
* a genuine payload and hash the whole file while claiming a range.
|
|
83
|
+
*/
|
|
84
|
+
export declare function mediaRanges(format: string, bytes: Uint8Array): RangeWalk;
|
|
85
|
+
//# sourceMappingURL=media-ranges.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-ranges.d.ts","sourceRoot":"","sources":["../../../src/taglib/media-ranges.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC5B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAgCD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,UAAU,EACjB,GAAG,SAAe,GACjB,MAAM,CAoBR;AA8DD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAYrD;AAED;;2EAE2E;AAC3E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAmBvE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAUrD;AAqBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAyBpD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAsBpD;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,SAAS,CAgCxE"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import {
|
|
2
|
+
flacAudioStart,
|
|
3
|
+
flacMarkerOffset,
|
|
4
|
+
id3v2End,
|
|
5
|
+
mpegFrameLength
|
|
6
|
+
} from "./metadata-extent.js";
|
|
7
|
+
const decoder = new TextDecoder();
|
|
8
|
+
const text = (b, o, n) => decoder.decode(b.subarray(o, o + n));
|
|
9
|
+
const fallback = (detail) => ({
|
|
10
|
+
kind: "fallback",
|
|
11
|
+
ranges: [],
|
|
12
|
+
detail
|
|
13
|
+
});
|
|
14
|
+
function adtsFrameLength(bytes, at) {
|
|
15
|
+
if (bytes.length < at + 7) return 0;
|
|
16
|
+
if (bytes[at] !== 255 || (bytes[at + 1] & 246) !== 240) return 0;
|
|
17
|
+
const length = (bytes[at + 3] & 3) << 11 | bytes[at + 4] << 3 | bytes[at + 5] >> 5;
|
|
18
|
+
return length >= 7 ? length : 0;
|
|
19
|
+
}
|
|
20
|
+
const frameLengthAt = (bytes, at) => mpegFrameLength(bytes, at) || adtsFrameLength(bytes, at);
|
|
21
|
+
function trailingTagStart(bytes, end = bytes.length) {
|
|
22
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
23
|
+
let at = Math.max(0, Math.min(end, bytes.length));
|
|
24
|
+
for (; ; ) {
|
|
25
|
+
if (at >= 128 && text(bytes, at - 128, 3) === "TAG") {
|
|
26
|
+
at -= 128;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (at >= 32 && text(bytes, at - 32, 8) === "APETAGEX") {
|
|
30
|
+
const size = view.getUint32(at - 32 + 12, true);
|
|
31
|
+
const flags = view.getUint32(at - 32 + 20, true);
|
|
32
|
+
const start = at - size - ((flags & 2147483648) !== 0 ? 32 : 0);
|
|
33
|
+
if (start < 0 || start >= at) return at;
|
|
34
|
+
at = start;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
return at;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const MPEG_SCAN_LIMIT = 65536;
|
|
41
|
+
function firstFrame(bytes) {
|
|
42
|
+
const from = id3v2End(bytes) ?? 0;
|
|
43
|
+
const limit = Math.min(bytes.length - 6, from + MPEG_SCAN_LIMIT);
|
|
44
|
+
for (let o = from; o < limit; o++) {
|
|
45
|
+
const len = frameLengthAt(bytes, o);
|
|
46
|
+
if (len > 0 && frameLengthAt(bytes, o + len) > 0) return o;
|
|
47
|
+
}
|
|
48
|
+
return -1;
|
|
49
|
+
}
|
|
50
|
+
function forwardLastFrame(bytes, from, end) {
|
|
51
|
+
let o = from;
|
|
52
|
+
let start = -1;
|
|
53
|
+
let length = 0;
|
|
54
|
+
let frames = 0;
|
|
55
|
+
while (o + 6 <= end) {
|
|
56
|
+
const len = frameLengthAt(bytes, o);
|
|
57
|
+
if (len === 0 || o + len > end) break;
|
|
58
|
+
start = o;
|
|
59
|
+
length = len;
|
|
60
|
+
frames++;
|
|
61
|
+
o += len;
|
|
62
|
+
}
|
|
63
|
+
return { start, length, frames };
|
|
64
|
+
}
|
|
65
|
+
function walkMpeg(bytes) {
|
|
66
|
+
const from = firstFrame(bytes);
|
|
67
|
+
if (from < 0) return fallback("no valid first frame");
|
|
68
|
+
const end = trailingTagStart(bytes);
|
|
69
|
+
const last = forwardLastFrame(bytes, from, end);
|
|
70
|
+
if (last.start < 0) return fallback("no valid frames");
|
|
71
|
+
return {
|
|
72
|
+
kind: "ranges",
|
|
73
|
+
ranges: [{ offset: from, length: last.start + last.length - from }],
|
|
74
|
+
detail: `first=${from} last=${last.start}+${last.length} frames=${last.frames}`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function flacStreamInfoMd5(bytes) {
|
|
78
|
+
const marker = flacMarkerOffset(bytes);
|
|
79
|
+
if (marker === void 0) return void 0;
|
|
80
|
+
const header = marker + 4;
|
|
81
|
+
if (header + 4 > bytes.length) return void 0;
|
|
82
|
+
if ((bytes[header] & 127) !== 0) return void 0;
|
|
83
|
+
const blockLength = bytes[header + 1] << 16 | bytes[header + 2] << 8 | bytes[header + 3];
|
|
84
|
+
if (blockLength < 34) return void 0;
|
|
85
|
+
const payload = header + 4;
|
|
86
|
+
if (payload + 34 > bytes.length) return void 0;
|
|
87
|
+
let hex = "";
|
|
88
|
+
for (const byte of bytes.subarray(payload + 18, payload + 34)) {
|
|
89
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
90
|
+
}
|
|
91
|
+
return hex;
|
|
92
|
+
}
|
|
93
|
+
function walkFlac(bytes) {
|
|
94
|
+
const start = flacAudioStart(bytes);
|
|
95
|
+
if (start === void 0) return fallback("no fLaC marker");
|
|
96
|
+
const end = trailingTagStart(bytes);
|
|
97
|
+
if (end <= start) return fallback("no audio bytes");
|
|
98
|
+
return {
|
|
99
|
+
kind: "ranges",
|
|
100
|
+
ranges: [{ offset: start, length: end - start }],
|
|
101
|
+
detail: `audioStart=${start} end=${end}`
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function atomLength(bytes, view, o) {
|
|
105
|
+
const size = view.getUint32(o, false);
|
|
106
|
+
if (size === 0) return { length: bytes.length - o, headerSize: 8 };
|
|
107
|
+
if (size === 1) {
|
|
108
|
+
if (o + 16 > bytes.length) return void 0;
|
|
109
|
+
const big = view.getBigUint64(o + 8, false);
|
|
110
|
+
if (big > BigInt(Number.MAX_SAFE_INTEGER)) return void 0;
|
|
111
|
+
return { length: Number(big), headerSize: 16 };
|
|
112
|
+
}
|
|
113
|
+
return { length: size, headerSize: 8 };
|
|
114
|
+
}
|
|
115
|
+
function walkMp4(bytes) {
|
|
116
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
117
|
+
const ranges = [];
|
|
118
|
+
let o = 0;
|
|
119
|
+
while (o + 8 <= bytes.length) {
|
|
120
|
+
const type = text(bytes, o + 4, 4);
|
|
121
|
+
const atom = atomLength(bytes, view, o);
|
|
122
|
+
if (atom === void 0) return fallback(`malformed ${type} size`);
|
|
123
|
+
if (atom.length < atom.headerSize) {
|
|
124
|
+
return fallback(`malformed ${type} size`);
|
|
125
|
+
}
|
|
126
|
+
if (o + atom.length > bytes.length) return fallback(`oversized ${type}`);
|
|
127
|
+
if (type === "mdat" && atom.length > atom.headerSize) {
|
|
128
|
+
ranges.push({
|
|
129
|
+
offset: o + atom.headerSize,
|
|
130
|
+
length: atom.length - atom.headerSize
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
o += atom.length;
|
|
134
|
+
}
|
|
135
|
+
if (o !== bytes.length) return fallback("trailing bytes after the last atom");
|
|
136
|
+
if (ranges.length === 0) return fallback("no non-empty mdat");
|
|
137
|
+
return { kind: "ranges", ranges, detail: `${ranges.length} mdat(s)` };
|
|
138
|
+
}
|
|
139
|
+
function walkWav(bytes) {
|
|
140
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
141
|
+
if (text(bytes, 0, 4) !== "RIFF") return fallback("not RIFF");
|
|
142
|
+
if (text(bytes, 8, 4) !== "WAVE") return fallback("not WAVE");
|
|
143
|
+
const ranges = [];
|
|
144
|
+
let o = 12;
|
|
145
|
+
while (o + 8 <= bytes.length) {
|
|
146
|
+
const id = text(bytes, o, 4);
|
|
147
|
+
const size = view.getUint32(o + 4, true);
|
|
148
|
+
const dataStart = o + 8;
|
|
149
|
+
if (dataStart + size > bytes.length) return fallback(`truncated ${id}`);
|
|
150
|
+
if (id === "data" && size > 0) {
|
|
151
|
+
ranges.push({ offset: dataStart, length: size });
|
|
152
|
+
}
|
|
153
|
+
o = dataStart + size + size % 2;
|
|
154
|
+
}
|
|
155
|
+
if (ranges.length === 0) return fallback("no data chunk");
|
|
156
|
+
return { kind: "ranges", ranges, detail: `${ranges.length} data chunk(s)` };
|
|
157
|
+
}
|
|
158
|
+
function mediaRanges(format, bytes) {
|
|
159
|
+
const walked = (() => {
|
|
160
|
+
switch (format.toUpperCase()) {
|
|
161
|
+
case "MP3":
|
|
162
|
+
case "AAC":
|
|
163
|
+
return walkMpeg(bytes);
|
|
164
|
+
case "FLAC":
|
|
165
|
+
return walkFlac(bytes);
|
|
166
|
+
case "MP4":
|
|
167
|
+
case "M4A":
|
|
168
|
+
return walkMp4(bytes);
|
|
169
|
+
case "WAV":
|
|
170
|
+
return walkWav(bytes);
|
|
171
|
+
default:
|
|
172
|
+
return { kind: "fallback", ranges: [], detail: "format not covered" };
|
|
173
|
+
}
|
|
174
|
+
})();
|
|
175
|
+
if (walked.kind === "fallback") {
|
|
176
|
+
return {
|
|
177
|
+
kind: "fallback",
|
|
178
|
+
ranges: [{ offset: 0, length: bytes.length }],
|
|
179
|
+
detail: `${walked.detail} \u2192 whole file`
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return walked;
|
|
183
|
+
}
|
|
184
|
+
export {
|
|
185
|
+
flacStreamInfoMd5,
|
|
186
|
+
mediaRanges,
|
|
187
|
+
trailingTagStart,
|
|
188
|
+
walkFlac,
|
|
189
|
+
walkMp4,
|
|
190
|
+
walkMpeg,
|
|
191
|
+
walkWav
|
|
192
|
+
};
|
|
@@ -14,7 +14,62 @@
|
|
|
14
14
|
* asymmetric: return true ONLY when the metadata is provably contained. Anything
|
|
15
15
|
* unrecognised, malformed, or unbounded answers false, which costs a full read
|
|
16
16
|
* and never costs correctness.
|
|
17
|
+
*
|
|
18
|
+
* The container walkers themselves are exported (id3v2End, mpegFrameLength,
|
|
19
|
+
* flacAudioStart, flacMarkerOffset) because the media checksum walks the same
|
|
20
|
+
* containers to find a file's encoded-audio payload. One walker per container:
|
|
21
|
+
* a second copy of these rules could silently disagree with the one that
|
|
22
|
+
* decides whether a splice is safe.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* End of an ID3v2 tag at offset 0, or undefined when there is none.
|
|
26
|
+
*
|
|
27
|
+
* The size field is "syncsafe": seven bits per byte, so the high bit can never
|
|
28
|
+
* produce a false frame sync. A footer, when the flags say there is one, adds
|
|
29
|
+
* ten bytes that the size does not cover.
|
|
30
|
+
*
|
|
31
|
+
* The `ID3` magic check is part of the contract, not a convenience: the size
|
|
32
|
+
* arithmetic reads a number out of any ten bytes, so a tagless buffer would
|
|
33
|
+
* otherwise report 22.8 million as the end of a tag it does not have. Callers
|
|
34
|
+
* that have already established the magic pay one comparison for it; callers
|
|
35
|
+
* that have not (the media checksum walks) need it.
|
|
36
|
+
*/
|
|
37
|
+
export declare function id3v2End(bytes: Uint8Array): number | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Offset of FLAC's `fLaC` stream marker — at 0, or just past a prepended ID3v2
|
|
40
|
+
* tag (TagLib supports exactly that, flacfile.cpp:90) — or undefined when
|
|
41
|
+
* neither is there. A caller that only wants STREAMINFO can seek straight to it
|
|
42
|
+
* instead of walking the metadata-block chain.
|
|
43
|
+
*/
|
|
44
|
+
export declare function flacMarkerOffset(bytes: Uint8Array): number | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Offset of FLAC's first audio byte: the end of the metadata-block chain, past
|
|
47
|
+
* the optional ID3v2 tag.
|
|
48
|
+
*
|
|
49
|
+
* The chain is walked from block headers alone, so the result can exceed
|
|
50
|
+
* `bytes.length` when a block's declared length reaches past the buffer — that
|
|
51
|
+
* is the extent the chain implies, and the caller decides whether it holds
|
|
52
|
+
* enough of the file to believe it. A buffer too short to hold the next block
|
|
53
|
+
* header has no implied extent at all and answers undefined, as does one with
|
|
54
|
+
* no `fLaC` marker.
|
|
55
|
+
*/
|
|
56
|
+
export declare function flacAudioStart(bytes: Uint8Array): number | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Length in bytes of the MPEG (Layer I/II/III) frame whose header sits at `at`,
|
|
59
|
+
* or 0 when `at` is not a plausible frame header.
|
|
60
|
+
*
|
|
61
|
+
* Plausibility is the header alone: eleven sync bits, a non-reserved version
|
|
62
|
+
* and layer, a bitrate index that is neither free (0) nor invalid (15), and a
|
|
63
|
+
* sample-rate index that is not reserved (3). Nothing here looks at what
|
|
64
|
+
* FOLLOWS the frame — a frame header at the very end of a file is as plausible
|
|
65
|
+
* as any other, which is what lets a walk reach the last frame. Callers that
|
|
66
|
+
* need confidence the bytes really are MPEG audio want isMpegFrameSync, which
|
|
67
|
+
* adds the next-header consistency check.
|
|
68
|
+
*
|
|
69
|
+
* The arithmetic mirrors mpegheader.cpp:236-264: bitrate/sample-rate tables
|
|
70
|
+
* plus the padding bit, and C++ integer division truncates.
|
|
17
71
|
*/
|
|
72
|
+
export declare function mpegFrameLength(bytes: Uint8Array, at: number): number;
|
|
18
73
|
/**
|
|
19
74
|
* True only when the metadata of `header` provably ends within `limit` bytes.
|
|
20
75
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata-extent.d.ts","sourceRoot":"","sources":["../../../src/taglib/metadata-extent.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"metadata-extent.d.ts","sourceRoot":"","sources":["../../../src/taglib/metadata-extent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAgBH;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAM9D;AA2BD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAItE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAIpE;AAwGD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CA8BrE;AA2DD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,GACZ,OAAO,CAiCT;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAmBT"}
|