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.
Files changed (60) hide show
  1. package/README.md +192 -13
  2. package/dist/index.browser.d.ts +24 -4
  3. package/dist/index.browser.d.ts.map +1 -1
  4. package/dist/index.browser.js +1123 -208
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -0
  8. package/dist/simple.browser.js +527 -202
  9. package/dist/src/errors/base.d.ts +17 -2
  10. package/dist/src/errors/base.d.ts.map +1 -1
  11. package/dist/src/errors/base.js +18 -0
  12. package/dist/src/runtime/wasi-adapter/adapter.d.ts +13 -1
  13. package/dist/src/runtime/wasi-adapter/adapter.d.ts.map +1 -1
  14. package/dist/src/runtime/wasi-adapter/adapter.js +17 -0
  15. package/dist/src/runtime/wasi-adapter/audio-properties.d.ts +4 -1
  16. package/dist/src/runtime/wasi-adapter/audio-properties.d.ts.map +1 -1
  17. package/dist/src/runtime/wasi-adapter/audio-properties.js +23 -6
  18. package/dist/src/runtime/wasi-adapter/path-open.d.ts +68 -0
  19. package/dist/src/runtime/wasi-adapter/path-open.d.ts.map +1 -0
  20. package/dist/src/runtime/wasi-adapter/path-open.js +76 -0
  21. package/dist/src/runtime/wasi-adapter/wasm-io.d.ts.map +1 -1
  22. package/dist/src/runtime/wasi-adapter/wasm-io.js +36 -28
  23. package/dist/src/runtime/wasi-host-loader.js +8 -3
  24. package/dist/src/runtime/wasmer-sdk-loader/types.d.ts +13 -0
  25. package/dist/src/runtime/wasmer-sdk-loader/types.d.ts.map +1 -1
  26. package/dist/src/simple/checksum-operations.d.ts +25 -0
  27. package/dist/src/simple/checksum-operations.d.ts.map +1 -0
  28. package/dist/src/simple/checksum-operations.js +7 -0
  29. package/dist/src/simple/index.d.ts +2 -0
  30. package/dist/src/simple/index.d.ts.map +1 -1
  31. package/dist/src/simple/index.js +2 -0
  32. package/dist/src/taglib/audio-file-checksum.d.ts +47 -0
  33. package/dist/src/taglib/audio-file-checksum.d.ts.map +1 -0
  34. package/dist/src/taglib/audio-file-checksum.js +77 -0
  35. package/dist/src/taglib/audio-file-impl.d.ts +4 -0
  36. package/dist/src/taglib/audio-file-impl.d.ts.map +1 -1
  37. package/dist/src/taglib/audio-file-impl.js +32 -0
  38. package/dist/src/taglib/audio-file-interface.d.ts +17 -0
  39. package/dist/src/taglib/audio-file-interface.d.ts.map +1 -1
  40. package/dist/src/taglib/media-ranges.d.ts +85 -0
  41. package/dist/src/taglib/media-ranges.d.ts.map +1 -0
  42. package/dist/src/taglib/media-ranges.js +192 -0
  43. package/dist/src/taglib/metadata-extent.d.ts +55 -0
  44. package/dist/src/taglib/metadata-extent.d.ts.map +1 -1
  45. package/dist/src/taglib/metadata-extent.js +24 -6
  46. package/dist/src/taglib/taglib-class.d.ts.map +1 -1
  47. package/dist/src/taglib/taglib-class.js +13 -24
  48. package/dist/src/utils/path.d.ts +12 -0
  49. package/dist/src/utils/path.d.ts.map +1 -1
  50. package/dist/src/utils/path.js +19 -0
  51. package/dist/src/version.d.ts +1 -1
  52. package/dist/src/version.js +1 -1
  53. package/dist/src/wasm.d.ts +9 -0
  54. package/dist/src/wasm.d.ts.map +1 -1
  55. package/dist/taglib-wasi.wasm +0 -0
  56. package/dist/taglib-web.wasm +0 -0
  57. package/dist/web.browser.d.ts +13 -0
  58. package/dist/web.browser.d.ts.map +1 -0
  59. package/dist/web.browser.js +3630 -0
  60. 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;AAoBzD,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CASrD;AAED;;;;;GAKG;AACH,qBAAa,aAAc,SAAQ,iBAAkB,YAAW,SAAS;IACvE,OAAO,CAAC,cAAc,CAA2B;gBAG/C,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,cAAc,EAC1B,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,IAAI,EACzD,iBAAiB,GAAE,OAAe,EAClC,kBAAkB,CAAC,EAAE,WAAW;IAYlC,IAAI,IAAI,OAAO;IAef,aAAa,IAAI,UAAU;IA2BrB,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4D9C,WAAW,IAAI,OAAO,EAAE;IAYxB,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI;IAStC,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IASlC,cAAc,IAAI,IAAI;IAItB,WAAW,IAAI,OAAO,EAAE;IAqBxB,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,IAAI;IA0BpE,OAAO,IAAI,uBAAuB,GAAG,SAAS;IAI9C,OAAO,CAAC,IAAI,EAAE,uBAAuB,GAAG,IAAI;IAI5C,WAAW,IAAI,UAAU,GAAG,SAAS;IAIrC,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI;IAI1C,OAAO,IAAI,MAAM,GAAG,SAAS;IAI7B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIlC,UAAU,IAAI,MAAM,EAAE;IAWtB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAQnC,SAAS,IAAI,cAAc,EAAE;IAS7B,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI;IAQzC,SAAS,IAAI,gBAAgB,GAAG,SAAS;IAKzC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAQ/C,cAAc,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE;IAMzC,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,IAAI;IAOpD,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAMnC,UAAU,IAAI;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE;IAI1C,YAAY,CAAC,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,EAAE,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;CAM1D"}
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":"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"}
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;;;;;;;;;;;;;;;;GAgBG;AAgPH;;;;;;;;;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"}
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"}