cyberchef 10.22.1 → 10.24.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 (118) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/CONTRIBUTING.md +40 -0
  3. package/Dockerfile +2 -0
  4. package/Gruntfile.js +2 -28
  5. package/README.md +1 -1
  6. package/babel.config.js +0 -6
  7. package/package.json +64 -63
  8. package/src/core/Chef.mjs +8 -1
  9. package/src/core/Ingredient.mjs +5 -2
  10. package/src/core/Operation.mjs +6 -1
  11. package/src/core/Recipe.mjs +10 -5
  12. package/src/core/config/Categories.json +20 -4
  13. package/src/core/config/OperationConfig.json +550 -26
  14. package/src/core/config/modules/Ciphers.mjs +6 -0
  15. package/src/core/config/modules/Crypto.mjs +6 -0
  16. package/src/core/config/modules/Default.mjs +8 -0
  17. package/src/core/config/modules/Shellcode.mjs +2 -0
  18. package/src/core/config/scripts/fixCryptoApiImports.mjs +54 -0
  19. package/src/core/config/scripts/fixSnackBarMarkup.mjs +28 -0
  20. package/src/core/lib/AudioBytes.mjs +103 -0
  21. package/src/core/lib/AudioMetaSchema.mjs +82 -0
  22. package/src/core/lib/AudioParsers.mjs +630 -0
  23. package/src/core/lib/BigIntUtils.mjs +73 -0
  24. package/src/core/lib/Extract.mjs +5 -0
  25. package/src/core/lib/Modhex.mjs +2 -0
  26. package/src/core/lib/ParityBit.mjs +50 -0
  27. package/src/core/lib/QRCode.mjs +30 -10
  28. package/src/core/lib/RC6.mjs +625 -0
  29. package/src/core/operations/A1Z26CipherDecode.mjs +1 -1
  30. package/src/core/operations/AddTextToImage.mjs +116 -64
  31. package/src/core/operations/AnalyseUUID.mjs +109 -6
  32. package/src/core/operations/BlurImage.mjs +10 -12
  33. package/src/core/operations/ContainImage.mjs +50 -40
  34. package/src/core/operations/ConvertImageFormat.mjs +33 -39
  35. package/src/core/operations/CoverImage.mjs +39 -37
  36. package/src/core/operations/CropImage.mjs +35 -21
  37. package/src/core/operations/DisassembleARM.mjs +193 -0
  38. package/src/core/operations/DitherImage.mjs +8 -8
  39. package/src/core/operations/EscapeUnicodeCharacters.mjs +0 -17
  40. package/src/core/operations/ExtractAudioMetadata.mjs +175 -0
  41. package/src/core/operations/ExtractEmailAddresses.mjs +2 -3
  42. package/src/core/operations/ExtractLSB.mjs +17 -11
  43. package/src/core/operations/ExtractRGBA.mjs +12 -10
  44. package/src/core/operations/FlaskSessionDecode.mjs +80 -0
  45. package/src/core/operations/FlaskSessionSign.mjs +89 -0
  46. package/src/core/operations/FlaskSessionVerify.mjs +136 -0
  47. package/src/core/operations/FlipImage.mjs +14 -10
  48. package/src/core/operations/GenerateImage.mjs +39 -32
  49. package/src/core/operations/ImageBrightnessContrast.mjs +10 -10
  50. package/src/core/operations/ImageFilter.mjs +14 -13
  51. package/src/core/operations/ImageHueSaturationLightness.mjs +22 -20
  52. package/src/core/operations/ImageOpacity.mjs +6 -8
  53. package/src/core/operations/InvertImage.mjs +4 -6
  54. package/src/core/operations/Jq.mjs +12 -4
  55. package/src/core/operations/NormaliseImage.mjs +5 -7
  56. package/src/core/operations/OffsetChecker.mjs +1 -1
  57. package/src/core/operations/ParityBit.mjs +128 -0
  58. package/src/core/operations/ParseEthernetFrame.mjs +112 -0
  59. package/src/core/operations/ParseIPv4Header.mjs +23 -6
  60. package/src/core/operations/ParseQRCode.mjs +13 -13
  61. package/src/core/operations/PseudoRandomIntegerGenerator.mjs +164 -0
  62. package/src/core/operations/RC6Decrypt.mjs +119 -0
  63. package/src/core/operations/RC6Encrypt.mjs +119 -0
  64. package/src/core/operations/RandomizeColourPalette.mjs +11 -11
  65. package/src/core/operations/RegularExpression.mjs +2 -1
  66. package/src/core/operations/RenderMarkdown.mjs +35 -4
  67. package/src/core/operations/ResizeImage.mjs +30 -23
  68. package/src/core/operations/RotateImage.mjs +8 -9
  69. package/src/core/operations/SQLBeautify.mjs +21 -3
  70. package/src/core/operations/SharpenImage.mjs +94 -62
  71. package/src/core/operations/SplitColourChannels.mjs +47 -21
  72. package/src/core/operations/TextIntegerConverter.mjs +123 -0
  73. package/src/core/operations/UnescapeUnicodeCharacters.mjs +17 -0
  74. package/src/core/operations/ViewBitPlane.mjs +16 -20
  75. package/src/core/operations/index.mjs +22 -0
  76. package/src/node/index.mjs +55 -0
  77. package/src/web/HTMLIngredient.mjs +24 -43
  78. package/src/web/HTMLOperation.mjs +8 -2
  79. package/src/web/Manager.mjs +1 -0
  80. package/src/web/html/index.html +6 -6
  81. package/src/web/static/fonts/bmfonts/Roboto72White.fnt +491 -485
  82. package/src/web/static/fonts/bmfonts/RobotoBlack72White.fnt +494 -488
  83. package/src/web/static/fonts/bmfonts/RobotoMono72White.fnt +110 -103
  84. package/src/web/static/fonts/bmfonts/RobotoSlab72White.fnt +498 -492
  85. package/src/web/stylesheets/layout/_banner.css +30 -0
  86. package/src/web/stylesheets/layout/_modals.css +5 -0
  87. package/src/web/stylesheets/utils/_overrides.css +7 -0
  88. package/src/web/waiters/ControlsWaiter.mjs +82 -0
  89. package/src/web/waiters/InputWaiter.mjs +12 -6
  90. package/src/web/waiters/OperationsWaiter.mjs +30 -13
  91. package/src/web/waiters/RecipeWaiter.mjs +2 -2
  92. package/tests/browser/02_ops.js +23 -3
  93. package/tests/node/index.mjs +1 -0
  94. package/tests/node/tests/lib/BigIntUtils.mjs +150 -0
  95. package/tests/node/tests/operations.mjs +11 -10
  96. package/tests/operations/index.mjs +13 -0
  97. package/tests/operations/tests/A1Z26CipherDecode.mjs +33 -0
  98. package/tests/operations/tests/AnalyseUUID.mjs +66 -0
  99. package/tests/operations/tests/DisassembleARM.mjs +377 -0
  100. package/tests/operations/tests/ExtractAudioMetadata.mjs +287 -0
  101. package/tests/operations/tests/ExtractEmailAddresses.mjs +38 -12
  102. package/tests/operations/tests/Fernet.mjs +18 -3
  103. package/tests/operations/tests/FlaskSession.mjs +246 -0
  104. package/tests/operations/tests/GenerateQRCode.mjs +67 -0
  105. package/tests/operations/tests/JWTSign.mjs +83 -8
  106. package/tests/operations/tests/Jq.mjs +32 -0
  107. package/tests/operations/tests/Modhex.mjs +20 -0
  108. package/tests/operations/tests/ParityBit.mjs +147 -0
  109. package/tests/operations/tests/ParseEthernetFrame.mjs +45 -0
  110. package/tests/operations/tests/RC6.mjs +487 -0
  111. package/tests/operations/tests/RegularExpression.mjs +75 -0
  112. package/tests/operations/tests/RenderMarkdown.mjs +110 -0
  113. package/tests/operations/tests/SQLBeautify.mjs +54 -0
  114. package/tests/operations/tests/TextIntegerConverter.mjs +199 -0
  115. package/tests/samples/Audio.mjs +73 -0
  116. package/tests/samples/Images.mjs +0 -12
  117. package/webpack.config.js +10 -7
  118. package/src/core/lib/ImageManipulation.mjs +0 -251
@@ -37,11 +37,14 @@ import GOSTSign from "../../operations/GOSTSign.mjs";
37
37
  import GOSTVerify from "../../operations/GOSTVerify.mjs";
38
38
  import GenerateECDSAKeyPair from "../../operations/GenerateECDSAKeyPair.mjs";
39
39
  import GenerateRSAKeyPair from "../../operations/GenerateRSAKeyPair.mjs";
40
+ import PseudoRandomIntegerGenerator from "../../operations/PseudoRandomIntegerGenerator.mjs";
40
41
  import PseudoRandomNumberGenerator from "../../operations/PseudoRandomNumberGenerator.mjs";
41
42
  import RC2Decrypt from "../../operations/RC2Decrypt.mjs";
42
43
  import RC2Encrypt from "../../operations/RC2Encrypt.mjs";
43
44
  import RC4 from "../../operations/RC4.mjs";
44
45
  import RC4Drop from "../../operations/RC4Drop.mjs";
46
+ import RC6Decrypt from "../../operations/RC6Decrypt.mjs";
47
+ import RC6Encrypt from "../../operations/RC6Encrypt.mjs";
45
48
  import RSADecrypt from "../../operations/RSADecrypt.mjs";
46
49
  import RSAEncrypt from "../../operations/RSAEncrypt.mjs";
47
50
  import RSASign from "../../operations/RSASign.mjs";
@@ -95,11 +98,14 @@ OpModules.Ciphers = {
95
98
  "GOST Verify": GOSTVerify,
96
99
  "Generate ECDSA Key Pair": GenerateECDSAKeyPair,
97
100
  "Generate RSA Key Pair": GenerateRSAKeyPair,
101
+ "Pseudo-Random Integer Generator": PseudoRandomIntegerGenerator,
98
102
  "Pseudo-Random Number Generator": PseudoRandomNumberGenerator,
99
103
  "RC2 Decrypt": RC2Decrypt,
100
104
  "RC2 Encrypt": RC2Encrypt,
101
105
  "RC4": RC4,
102
106
  "RC4 Drop": RC4Drop,
107
+ "RC6 Decrypt": RC6Decrypt,
108
+ "RC6 Encrypt": RC6Encrypt,
103
109
  "RSA Decrypt": RSADecrypt,
104
110
  "RSA Encrypt": RSAEncrypt,
105
111
  "RSA Sign": RSASign,
@@ -20,6 +20,9 @@ import CipherSaber2Encrypt from "../../operations/CipherSaber2Encrypt.mjs";
20
20
  import CompareCTPHHashes from "../../operations/CompareCTPHHashes.mjs";
21
21
  import CompareSSDEEPHashes from "../../operations/CompareSSDEEPHashes.mjs";
22
22
  import DeriveHKDFKey from "../../operations/DeriveHKDFKey.mjs";
23
+ import FlaskSessionDecode from "../../operations/FlaskSessionDecode.mjs";
24
+ import FlaskSessionSign from "../../operations/FlaskSessionSign.mjs";
25
+ import FlaskSessionVerify from "../../operations/FlaskSessionVerify.mjs";
23
26
  import Fletcher16Checksum from "../../operations/Fletcher16Checksum.mjs";
24
27
  import Fletcher32Checksum from "../../operations/Fletcher32Checksum.mjs";
25
28
  import Fletcher64Checksum from "../../operations/Fletcher64Checksum.mjs";
@@ -81,6 +84,9 @@ OpModules.Crypto = {
81
84
  "Compare CTPH hashes": CompareCTPHHashes,
82
85
  "Compare SSDEEP hashes": CompareSSDEEPHashes,
83
86
  "Derive HKDF key": DeriveHKDFKey,
87
+ "Flask Session Decode": FlaskSessionDecode,
88
+ "Flask Session Sign": FlaskSessionSign,
89
+ "Flask Session Verify": FlaskSessionVerify,
84
90
  "Fletcher-16 Checksum": Fletcher16Checksum,
85
91
  "Fletcher-32 Checksum": Fletcher32Checksum,
86
92
  "Fletcher-64 Checksum": Fletcher64Checksum,
@@ -44,6 +44,7 @@ import EncodeNetBIOSName from "../../operations/EncodeNetBIOSName.mjs";
44
44
  import EscapeString from "../../operations/EscapeString.mjs";
45
45
  import EscapeUnicodeCharacters from "../../operations/EscapeUnicodeCharacters.mjs";
46
46
  import ExpandAlphabetRange from "../../operations/ExpandAlphabetRange.mjs";
47
+ import ExtractAudioMetadata from "../../operations/ExtractAudioMetadata.mjs";
47
48
  import ExtractFiles from "../../operations/ExtractFiles.mjs";
48
49
  import ExtractID3 from "../../operations/ExtractID3.mjs";
49
50
  import FangURL from "../../operations/FangURL.mjs";
@@ -114,8 +115,10 @@ import PHPDeserialize from "../../operations/PHPDeserialize.mjs";
114
115
  import PHPSerialize from "../../operations/PHPSerialize.mjs";
115
116
  import PLISTViewer from "../../operations/PLISTViewer.mjs";
116
117
  import PadLines from "../../operations/PadLines.mjs";
118
+ import ParityBit from "../../operations/ParityBit.mjs";
117
119
  import ParseColourCode from "../../operations/ParseColourCode.mjs";
118
120
  import ParseDateTime from "../../operations/ParseDateTime.mjs";
121
+ import ParseEthernetFrame from "../../operations/ParseEthernetFrame.mjs";
119
122
  import ParseIPRange from "../../operations/ParseIPRange.mjs";
120
123
  import ParseIPv4Header from "../../operations/ParseIPv4Header.mjs";
121
124
  import ParseIPv6Address from "../../operations/ParseIPv6Address.mjs";
@@ -167,6 +170,7 @@ import SymmetricDifference from "../../operations/SymmetricDifference.mjs";
167
170
  import Tail from "../../operations/Tail.mjs";
168
171
  import TakeBytes from "../../operations/TakeBytes.mjs";
169
172
  import TakeNthBytes from "../../operations/TakeNthBytes.mjs";
173
+ import TextIntegerConverter from "../../operations/TextIntegerConverter.mjs";
170
174
  import ToBCD from "../../operations/ToBCD.mjs";
171
175
  import ToBase from "../../operations/ToBase.mjs";
172
176
  import ToBase32 from "../../operations/ToBase32.mjs";
@@ -250,6 +254,7 @@ OpModules.Default = {
250
254
  "Escape string": EscapeString,
251
255
  "Escape Unicode Characters": EscapeUnicodeCharacters,
252
256
  "Expand alphabet range": ExpandAlphabetRange,
257
+ "Extract Audio Metadata": ExtractAudioMetadata,
253
258
  "Extract Files": ExtractFiles,
254
259
  "Extract ID3": ExtractID3,
255
260
  "Fang URL": FangURL,
@@ -320,8 +325,10 @@ OpModules.Default = {
320
325
  "PHP Serialize": PHPSerialize,
321
326
  "P-list Viewer": PLISTViewer,
322
327
  "Pad lines": PadLines,
328
+ "Parity Bit": ParityBit,
323
329
  "Parse colour code": ParseColourCode,
324
330
  "Parse DateTime": ParseDateTime,
331
+ "Parse Ethernet frame": ParseEthernetFrame,
325
332
  "Parse IP range": ParseIPRange,
326
333
  "Parse IPv4 header": ParseIPv4Header,
327
334
  "Parse IPv6 address": ParseIPv6Address,
@@ -373,6 +380,7 @@ OpModules.Default = {
373
380
  "Tail": Tail,
374
381
  "Take bytes": TakeBytes,
375
382
  "Take nth bytes": TakeNthBytes,
383
+ "Text-Integer Conversion": TextIntegerConverter,
376
384
  "To BCD": ToBCD,
377
385
  "To Base": ToBase,
378
386
  "To Base32": ToBase32,
@@ -5,11 +5,13 @@
5
5
  * @copyright Crown Copyright 2026
6
6
  * @license Apache-2.0
7
7
  */
8
+ import DisassembleARM from "../../operations/DisassembleARM.mjs";
8
9
  import DisassembleX86 from "../../operations/DisassembleX86.mjs";
9
10
 
10
11
  const OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
11
12
 
12
13
  OpModules.Shellcode = {
14
+ "Disassemble ARM": DisassembleARM,
13
15
  "Disassemble x86": DisassembleX86,
14
16
  };
15
17
 
@@ -0,0 +1,54 @@
1
+ /**
2
+ * This script updates crypto-api package
3
+ * It adds .mjs to local imports where its missing
4
+ *
5
+ * before:
6
+ * import foo from "./bar";
7
+ * after
8
+ * import foo from "./bar.mjs";
9
+ *
10
+ */
11
+
12
+ /* eslint no-console: ["off"] */
13
+
14
+ import { readdirSync, readFileSync, writeFileSync } from "fs";
15
+ import { join } from "path";
16
+
17
+ // Base directory of crypto-api source
18
+ const baseDir = join(process.cwd(), "node_modules/crypto-api/src");
19
+
20
+ /**
21
+ * Recursively walk a directory, updating import statements
22
+ * to include ".mjs" if missing
23
+ */
24
+ function walk(dir) {
25
+ const entries = readdirSync(dir, { withFileTypes: true });
26
+
27
+ for (const entry of entries) {
28
+ if (entry.name === ".git") continue;
29
+
30
+ const fullPath = join(dir, entry.name);
31
+
32
+ if (entry.isDirectory()) {
33
+ walk(fullPath);
34
+ } else if (entry.isFile()) {
35
+ const content = readFileSync(fullPath, "utf8");
36
+
37
+ // Add .mjs to imports if not present
38
+ const updated = content.replace(
39
+ /from "(\.[^"]*)";/g,
40
+ (match, p1) => {
41
+ if (p1.endsWith(".mjs")) return match;
42
+ return `from "${p1}.mjs";`;
43
+ }
44
+ );
45
+
46
+ if (updated !== content) {
47
+ writeFileSync(fullPath, updated, "utf8");
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ // Run the walker
54
+ walk(baseDir);
@@ -0,0 +1,28 @@
1
+ /**
2
+ * This script updates snackbarjs package
3
+ * Replaces self-closing div with standard opening div
4
+ *
5
+ * before:
6
+ * <div id=snackbar-container/>
7
+ * after:
8
+ * <div id=snackbar-container>
9
+ *
10
+ */
11
+
12
+ /* eslint no-console: ["off"] */
13
+
14
+ import { readFileSync, writeFileSync } from "fs";
15
+ import { join } from "path";
16
+
17
+ // Base directory of snackbarjs source
18
+ const filePath = join(process.cwd(), "node_modules/snackbarjs/src/snackbar.js");
19
+
20
+ const content = readFileSync(filePath, "utf8");
21
+
22
+ // Replace self-closing div with standard opening div
23
+ const updated = content.replace(
24
+ /<div id=snackbar-container\/>/g,
25
+ "<div id=snackbar-container>"
26
+ );
27
+
28
+ writeFileSync(filePath, updated, "utf8");
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Byte-reading and text-decoding utilities for audio metadata parsing.
3
+ *
4
+ * @author d0s1nt [d0s1nt@cyberchefaudio]
5
+ * @copyright Crown Copyright 2025
6
+ * @license Apache-2.0
7
+ */
8
+
9
+ /** @returns {string} 4-byte ASCII at offset, or "" if out of bounds. */
10
+ export function ascii4(b, off) {
11
+ if (off + 4 > b.length) return "";
12
+ return String.fromCharCode(b[off], b[off + 1], b[off + 2], b[off + 3]);
13
+ }
14
+
15
+ /** @returns {number} Byte offset of ASCII needle `s`, or -1. */
16
+ export function indexOfAscii(b, s, start, end) {
17
+ const limit = Math.max(0, Math.min(end, b.length) - s.length);
18
+ for (let i = start; i <= limit; i++) {
19
+ let ok = true;
20
+ for (let j = 0; j < s.length; j++) {
21
+ if (b[i + j] !== s.charCodeAt(j)) {
22
+ ok = false;
23
+ break;
24
+ }
25
+ }
26
+ if (ok) return i;
27
+ }
28
+ return -1;
29
+ }
30
+
31
+ /** @returns {number} Unsigned 32-bit big-endian read. */
32
+ export function u32be(bytes, off) {
33
+ return ((bytes[off] << 24) >>> 0) | (bytes[off + 1] << 16) | (bytes[off + 2] << 8) | bytes[off + 3];
34
+ }
35
+
36
+ /** @returns {number} Unsigned 32-bit little-endian read. */
37
+ export function u32le(bytes, off) {
38
+ return (bytes[off] | (bytes[off + 1] << 8) | (bytes[off + 2] << 16) | (bytes[off + 3] << 24)) >>> 0;
39
+ }
40
+
41
+ /** @returns {number} Unsigned 16-bit little-endian read. */
42
+ export function u16le(bytes, off) {
43
+ return bytes[off] | (bytes[off + 1] << 8);
44
+ }
45
+
46
+ /** @returns {BigInt} Unsigned 64-bit little-endian read. */
47
+ export function u64le(bytes, off) {
48
+ return BigInt(u32le(bytes, off)) | (BigInt(u32le(bytes, off + 4)) << 32n);
49
+ }
50
+
51
+ /** @returns {number} Decoded ID3v2 synchsafe integer from four 7-bit bytes. */
52
+ export function synchsafeToInt(b0, b1, b2, b3) {
53
+ return ((b0 & 0x7f) << 21) | ((b1 & 0x7f) << 14) | ((b2 & 0x7f) << 7) | (b3 & 0x7f);
54
+ }
55
+
56
+ /** @returns {string} Decoded UTF-16LE byte range, nulls stripped. */
57
+ export function decodeUtf16LE(b, off, len) {
58
+ if (len <= 0 || off + len > b.length) return "";
59
+ try {
60
+ return new TextDecoder("utf-16le").decode(b.slice(off, off + len)).replace(/\u0000/g, "").trim();
61
+ } catch {
62
+ return "";
63
+ }
64
+ }
65
+
66
+ /** @returns {{valueBytes: Uint8Array, next: number}} Bytes until null terminator, UTF-16 aware. */
67
+ export function readNullTerminated(bytes, start, encoding) {
68
+ const isUtf16 = encoding === 1 || encoding === 2;
69
+ if (!isUtf16) {
70
+ let i = start;
71
+ while (i < bytes.length && bytes[i] !== 0x00) i++;
72
+ return { valueBytes: bytes.slice(start, i), next: i + 1 };
73
+ }
74
+ let i = start;
75
+ while (i + 1 < bytes.length && !(bytes[i] === 0x00 && bytes[i + 1] === 0x00)) i += 2;
76
+ return { valueBytes: bytes.slice(start, i), next: i + 2 };
77
+ }
78
+
79
+ const ID3_ENCODINGS = ["iso-8859-1", "utf-16", "utf-16be", "utf-8"];
80
+
81
+ /** @returns {string} Text decoded using ID3v2 encoding byte (0=latin1, 1=utf16, 2=utf16be, 3=utf8). */
82
+ export function decodeText(bytes, encoding) {
83
+ if (!bytes || bytes.length === 0) return "";
84
+ try {
85
+ return new TextDecoder(ID3_ENCODINGS[encoding] || "utf-16").decode(bytes);
86
+ } catch {
87
+ return safeUtf8(bytes);
88
+ }
89
+ }
90
+
91
+ /** @returns {string} UTF-8 decode with replacement (never throws). */
92
+ export function safeUtf8(bytes) {
93
+ try {
94
+ return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
95
+ } catch {
96
+ return "";
97
+ }
98
+ }
99
+
100
+ /** @returns {string} ISO-8859-1 decode, nulls stripped, trimmed. */
101
+ export function decodeLatin1Trim(bytes) {
102
+ return decodeText(bytes, 0).replace(/\u0000/g, "").trim();
103
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Report skeleton and container detection for audio metadata extraction.
3
+ *
4
+ * @author d0s1nt [d0s1nt@cyberchefaudio]
5
+ * @copyright Crown Copyright 2025
6
+ * @license Apache-2.0
7
+ */
8
+
9
+ /* eslint-disable camelcase */
10
+
11
+ import { ascii4, indexOfAscii } from "./AudioBytes.mjs";
12
+
13
+ /** Builds the empty report skeleton ready for a format parser to populate. */
14
+ export function makeEmptyReport(filename, byteLength, container) {
15
+ return {
16
+ schema_version: "audio-meta-1.0",
17
+ artifact: {
18
+ filename,
19
+ byte_length: byteLength,
20
+ container: { type: container.type, brand: container.brand || null, mime: container.mime || null },
21
+ },
22
+ detections: { metadata_systems: [], provenance_systems: [] },
23
+ tags: {
24
+ common: {
25
+ title: null, artist: null, album: null, date: null, track: null,
26
+ genre: null, comment: null, composer: null, copyright: null, language: null,
27
+ },
28
+ raw: {},
29
+ },
30
+ embedded: [],
31
+ provenance: {
32
+ c2pa: {
33
+ present: false,
34
+ embedding: [],
35
+ manifest_store: { active_manifest_urn: null, instance_id: null, claim_generator: null },
36
+ assertions: [],
37
+ signature: {
38
+ algorithm: null, signing_time: null,
39
+ certificate: { subject_cn: null, issuer_cn: null, serial_number: null },
40
+ },
41
+ validation: { validation_state: "Unknown", reasons: [], details_raw: null },
42
+ },
43
+ },
44
+ errors: [],
45
+ };
46
+ }
47
+
48
+ /** Detects the audio container format from magic bytes. */
49
+ export function sniffContainer(b) {
50
+ if (b.length >= 3 && b[0] === 0x49 && b[1] === 0x44 && b[2] === 0x33)
51
+ return { type: "mp3", mime: "audio/mpeg" };
52
+ if (b.length >= 2 && b[0] === 0xff && (b[1] & 0xe0) === 0xe0) {
53
+ if ((b[1] & 0x06) === 0x00) return { type: "aac", mime: "audio/aac" };
54
+ return { type: "mp3", mime: "audio/mpeg" };
55
+ }
56
+ if (b.length >= 8 && b[0] === 0x0b && b[1] === 0x77)
57
+ return { type: "ac3", mime: "audio/ac3" };
58
+ if (b.length >= 16 &&
59
+ b[0] === 0x30 && b[1] === 0x26 && b[2] === 0xb2 && b[3] === 0x75 &&
60
+ b[4] === 0x8e && b[5] === 0x66 && b[6] === 0xcf && b[7] === 0x11)
61
+ return { type: "wma", mime: "audio/x-ms-wma" };
62
+ if (b.length >= 12 && ascii4(b, 0) === "RIFF" && ascii4(b, 8) === "WAVE")
63
+ return { type: "wav", mime: "audio/wav" };
64
+ if (b.length >= 12 && ascii4(b, 0) === "BW64" && ascii4(b, 8) === "WAVE")
65
+ return { type: "bw64", mime: "audio/wav" };
66
+ if (b.length >= 4 && ascii4(b, 0) === "fLaC")
67
+ return { type: "flac", mime: "audio/flac" };
68
+ if (b.length >= 4 && ascii4(b, 0) === "OggS") {
69
+ const idx = indexOfAscii(b, "OpusHead", 0, Math.min(b.length, 65536));
70
+ return idx >= 0 ? { type: "opus", mime: "audio/ogg" } : { type: "ogg", mime: "audio/ogg" };
71
+ }
72
+ if (b.length >= 12 && ascii4(b, 4) === "ftyp") {
73
+ const brand = ascii4(b, 8);
74
+ const isM4A = brand === "M4A " || brand === "M4B " || brand === "M4P ";
75
+ return { type: isM4A ? "m4a" : "mp4", mime: isM4A ? "audio/mp4" : "video/mp4", brand };
76
+ }
77
+ if (b.length >= 12 && ascii4(b, 0) === "FORM") {
78
+ const formType = ascii4(b, 8);
79
+ if (formType === "AIFF" || formType === "AIFC") return { type: "aiff", mime: "audio/aiff", brand: formType };
80
+ }
81
+ return { type: "unknown", mime: null };
82
+ }