From bba79cbad6a91fb93bcc9dad434e23fa591aeba8 Mon Sep 17 00:00:00 2001 From: Triple7 Date: Mon, 4 May 2026 06:13:56 -0700 Subject: [PATCH] Replace deprecated browser metadata parser --- README.md | 3 + docs/manual-qa-checklist.md | 4 +- package-lock.json | 272 ------------------------------------ package.json | 1 - src/utils/metadata.js | 27 +++- 5 files changed, 29 insertions(+), 278 deletions(-) diff --git a/README.md b/README.md index aa25c21..7257982 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,9 @@ The source code is available at [github.com/ChrisAdamsdevelopment/SpectraCleanse For a step-by-step manual validation flow (local, API smoke, auth, billing, upload, cleanse, Docker, and production readiness), see [`docs/manual-qa-checklist.md`](docs/manual-qa-checklist.md). +- Browser metadata analysis uses maintained `music-metadata` with graceful fallback (`parseError`) when parsing fails, times out, or is skipped for very large files. +- Quick Cleanse metadata writing remains local/browser-side (MP3 via `browser-id3-writer`), while Full Server Cleanse still runs through `/api/process`. + --- ## Contact diff --git a/docs/manual-qa-checklist.md b/docs/manual-qa-checklist.md index 6f0c116..a73d6e6 100644 --- a/docs/manual-qa-checklist.md +++ b/docs/manual-qa-checklist.md @@ -160,6 +160,6 @@ Run these checks against your local server: ## 13) Known warnings - `npm audit` currently reports vulnerabilities and should be triaged. -- `music-metadata-browser` is deprecated and currently used only for browser-side metadata analysis; plan a replacement/redesign in a future PR. -- Treat suspicious or corrupt media samples as high-risk during manual QA and verify metadata analysis paths carefully. +- Browser-side metadata analysis now uses maintained `music-metadata` with lazy loading, size bounds, and timeout fallback behavior. +- Treat suspicious, corrupt, or unusually large media samples as high-risk during manual QA and verify metadata analysis fallback behavior (`parseError`) carefully. - Docker build/runtime validation still requires a real Docker-capable environment. diff --git a/package-lock.json b/package-lock.json index 0d20509..74d1257 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,6 @@ "lucide-react": "^0.390.0", "multer": "^2.0.0", "music-metadata": "^11.12.3", - "music-metadata-browser": "2.5.11", "postcss": "^8.4.38", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -1005,18 +1004,6 @@ "vite": "^4.2.0 || ^5.0.0" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1707,24 +1694,6 @@ "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/exiftool-vendored": { "version": "28.8.0", "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.8.0.tgz", @@ -2523,182 +2492,6 @@ "node": ">=18" } }, - "node_modules/music-metadata-browser": { - "version": "2.5.11", - "resolved": "https://registry.npmjs.org/music-metadata-browser/-/music-metadata-browser-2.5.11.tgz", - "integrity": "sha512-Khq5nYapffIet0PUVb5J69pZPgqgn+/yoEr0jkO/OjH5xwfdz6rdwj0zsWPaqo3ylv+OthXoGjT6EegVHbMkJQ==", - "deprecated": "No longer support, superseded by music-metadata", - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "debug": "^4.3.4", - "music-metadata": "^7.13.3", - "readable-stream": "^4.3.0", - "readable-web-to-node-stream": "^3.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/music-metadata-browser/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/music-metadata-browser/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/music-metadata-browser/node_modules/file-type": { - "version": "16.5.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", - "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", - "license": "MIT", - "dependencies": { - "readable-web-to-node-stream": "^3.0.0", - "strtok3": "^6.2.4", - "token-types": "^4.1.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/music-metadata-browser/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/music-metadata-browser/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/music-metadata-browser/node_modules/music-metadata": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-7.14.0.tgz", - "integrity": "sha512-xrm3w7SV0Wk+OythZcSbaI8mcr/KHd0knJieu8bVpaPfMv/Agz5EooCAPz3OR5hbYMiUG6dgAPKZKnMzV+3amA==", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "content-type": "^1.0.5", - "debug": "^4.3.4", - "file-type": "^16.5.4", - "media-typer": "^1.1.0", - "strtok3": "^6.3.0", - "token-types": "^4.2.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/music-metadata-browser/node_modules/peek-readable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", - "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/music-metadata-browser/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/music-metadata-browser/node_modules/strtok3": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", - "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^4.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/music-metadata-browser/node_modules/token-types": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", - "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/music-metadata/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -3075,15 +2868,6 @@ "node": ">=10" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -3226,62 +3010,6 @@ "node": ">= 6" } }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", - "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", - "license": "MIT", - "dependencies": { - "readable-stream": "^4.7.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", diff --git a/package.json b/package.json index f3d1fa1..50cd751 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "lucide-react": "^0.390.0", "multer": "^2.0.0", "music-metadata": "^11.12.3", - "music-metadata-browser": "2.5.11", "postcss": "^8.4.38", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/utils/metadata.js b/src/utils/metadata.js index 36cb772..484720c 100644 --- a/src/utils/metadata.js +++ b/src/utils/metadata.js @@ -2,21 +2,39 @@ import ID3Writer from 'browser-id3-writer'; const AI_MARKERS = ['ai','generated','suno','udio','boomy','aiva','soundraw','mubert','stable audio','provenance','c2pa','content credentials','watermark','synthetic','elevenlabs']; const MARKER_REGEX_CACHE = new Map(); +const MAX_BROWSER_PARSE_BYTES = 100 * 1024 * 1024; +const PARSE_TIMEOUT_MS = 8000; let parseBlobLoader = null; async function getParseBlob() { if (parseBlobLoader) return parseBlobLoader; - parseBlobLoader = import('music-metadata-browser').then((mod) => { + parseBlobLoader = import('music-metadata').then((mod) => { const fn = mod?.parseBlob || mod?.default?.parseBlob; if (typeof fn !== 'function') { - throw new Error('music-metadata-browser parseBlob export not found'); + throw new Error('music-metadata parseBlob export not found'); } return fn; }); return parseBlobLoader; } +function withTimeout(promise, timeoutMs) { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(`Metadata parse timed out after ${timeoutMs}ms`)), timeoutMs); + promise.then( + (value) => { + clearTimeout(timer); + resolve(value); + }, + (error) => { + clearTimeout(timer); + reject(error); + } + ); + }); +} + function escapeRegex(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } @@ -53,8 +71,11 @@ export async function readFileMetadata(file) { let parseError = null; try { + if ((file?.size || 0) > MAX_BROWSER_PARSE_BYTES) { + throw new Error(`File too large for browser metadata analysis (${Math.round(file.size / (1024 * 1024))}MB > ${Math.round(MAX_BROWSER_PARSE_BYTES / (1024 * 1024))}MB)`); + } const parseBlob = await getParseBlob(); - parsed = await parseBlob(file); + parsed = await withTimeout(parseBlob(file), PARSE_TIMEOUT_MS); } catch (error) { parseError = error; }