From e043a1b5b842e5e022a3166dff95b0e1118e3401 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 4 Jul 2025 10:06:13 -0700 Subject: [PATCH] Try upgrading to music-metadata from music-metadata-browser --- packages/webamp/CHANGELOG.md | 2 + packages/webamp/demo/js/webampConfig.ts | 4 +- packages/webamp/js/fileUtils.ts | 30 +++++++-- packages/webamp/js/types.ts | 30 ++++++--- packages/webamp/js/webamp.ts | 4 +- packages/webamp/js/webampLazy.tsx | 4 +- packages/webamp/package.json | 1 + yarn.lock | 81 ++++++++++++++++++++++++- 8 files changed, 134 insertions(+), 22 deletions(-) diff --git a/packages/webamp/CHANGELOG.md b/packages/webamp/CHANGELOG.md index 2e72c0e3..78adb6e9 100644 --- a/packages/webamp/CHANGELOG.md +++ b/packages/webamp/CHANGELOG.md @@ -6,6 +6,7 @@ ### Improvements +- Switched id3/metadata parsing from using a very old version of `music-metadata-browser` to the latest version of `music-metadata`. `music-metadata-browser` is still supported for users of `webamp/lazy` for backwards compatibility, but it is no longer the default. - Added new `Webamp` instance methods: - `webamp.toggleShuffle` - `webamp.toggleRepeat` @@ -15,6 +16,7 @@ ### Bug Fixes +- Fixed broken ID3 tag and bitrate parsing. - Fix bug where scrolling the main window or playlist window would change the volume but also (incorrectly) scroll the page. - Fix bug where resizing the window such that the current layout cannot fit on the page, while also scrolled down the page, would cause the layout to be recentered out of view. - Avoid a console log from Redux Dev Tools. diff --git a/packages/webamp/demo/js/webampConfig.ts b/packages/webamp/demo/js/webampConfig.ts index b2941a0d..d1f76e4d 100644 --- a/packages/webamp/demo/js/webampConfig.ts +++ b/packages/webamp/demo/js/webampConfig.ts @@ -128,9 +128,7 @@ export async function getWebampConfig( // @ts-ignore import(/* webpackChunkName: "jszip" */ "jszip/dist/jszip"), requireMusicMetadata: () => - import( - /* webpackChunkName: "music-metadata-browser" */ "music-metadata-browser/dist/index" - ), + import(/* webpackChunkName: "music-metadata" */ "music-metadata"), __initialState: screenshot ? screenshotInitialState : initialState, __butterchurnOptions, __customMiddlewares: [sentryMiddleware, loggerMiddleware], diff --git a/packages/webamp/js/fileUtils.ts b/packages/webamp/js/fileUtils.ts index cba6afee..f1da00e6 100644 --- a/packages/webamp/js/fileUtils.ts +++ b/packages/webamp/js/fileUtils.ts @@ -1,13 +1,13 @@ import invariant from "invariant"; -import { IMusicMetadataBrowserApi } from "./types"; +import { IMusicMetadataApi, IMusicMetadataBrowserApi } from "./types"; import { IAudioMetadata } from "music-metadata-browser"; // Import music-metadata type definitions import * as Utils from "./utils"; type MediaDataType = string | ArrayBuffer | Blob; -export function genMediaTags( +export async function genMediaTags( file: MediaDataType, - musicMetadata: IMusicMetadataBrowserApi + musicMetadata: IMusicMetadataBrowserApi | IMusicMetadataApi ): Promise { invariant( file != null, @@ -20,7 +20,29 @@ export function genMediaTags( }; if (typeof file === "string") { - return musicMetadata.fetchFromUrl(file, options); + if ( + "parseWebStream" in musicMetadata && + typeof musicMetadata.parseWebStream === "function" + ) { + const response = await fetch(file); + if (!response.ok) { + throw new Error( + `Failed to fetch URL: ${file}, status: ${response.status}` + ); + } + const webStream = response.body; + if (webStream == null) { + throw new Error("Response body is null, cannot parse metadata."); + } + return musicMetadata.parseWebStream(webStream, undefined, options); + } + if ( + "fetchFromUrl" in musicMetadata && + typeof musicMetadata.fetchFromUrl === "function" + ) { + return musicMetadata.fetchFromUrl(file, options); + } + throw new Error("No suitable method available to parse URL"); } // Assume Blob return musicMetadata.parseBlob(file as Blob, options); diff --git a/packages/webamp/js/types.ts b/packages/webamp/js/types.ts index 5daf0c9c..1da6b46c 100644 --- a/packages/webamp/js/types.ts +++ b/packages/webamp/js/types.ts @@ -1,3 +1,4 @@ +import type { AnyWebByteStream, IFileInfo } from "strtok3"; import { PlaylistState } from "./reducers/playlist"; import { SettingsState } from "./reducers/settings"; import { UserInputState } from "./reducers/userInput"; @@ -864,19 +865,32 @@ export interface IMusicMetadataBrowserApi { audioTrackUrl: string, options?: IOptions ): Promise; +} + +/** + * Type definition of the portion of the music-metadata module we use in Webamp. + */ +export interface IMusicMetadataApi { + /** + * Parse audio from Node Stream.Readable + * @param stream - Stream to read the audio track from + * @param fileInfo - File information object or MIME-type, e.g.: 'audio/mpeg' + * @param options - Parsing options + * @returns Metadata + */ + parseWebStream( + webStream: AnyWebByteStream, + fileInfo?: IFileInfo | string, + options?: IOptions + ): Promise; /** - * Parse audio from Node Buffer - * @param {Stream.Readable} stream Audio input stream - * @param {string} mimeType Content specification MIME-type, e.g.: 'audio/mpeg' + * Parse Web API File + * @param {Blob} blob * @param {IOptions} options Parsing options * @returns {Promise} */ - parseBuffer( - buf: Buffer, - mimeType?: string, - options?: IOptions - ): Promise; + parseBlob(blob: Blob, options?: IOptions): Promise; } export interface Extras { diff --git a/packages/webamp/js/webamp.ts b/packages/webamp/js/webamp.ts index 351a6ff6..92d76404 100644 --- a/packages/webamp/js/webamp.ts +++ b/packages/webamp/js/webamp.ts @@ -1,5 +1,5 @@ import JSZip from "jszip"; -import * as musicMetadataBrowser from "music-metadata-browser"; +import * as musicMetadata from "music-metadata"; import { Options } from "./types"; import WebampLazy, { PrivateOptions } from "./webampLazy"; @@ -28,7 +28,7 @@ export default class Webamp extends WebampLazy { super({ ...options, requireJSZip: async () => JSZip, - requireMusicMetadata: async () => musicMetadataBrowser, + requireMusicMetadata: async () => musicMetadata, }); } } diff --git a/packages/webamp/js/webampLazy.tsx b/packages/webamp/js/webampLazy.tsx index 8daf7f5e..1f77cb22 100644 --- a/packages/webamp/js/webampLazy.tsx +++ b/packages/webamp/js/webampLazy.tsx @@ -51,8 +51,8 @@ export interface PrivateOptions { } export interface InjectableDependencies { - requireJSZip: () => Promise; // TODO: Type JSZip - requireMusicMetadata: () => Promise; // TODO: Type music-metadata-browser + requireJSZip: () => Promise; + requireMusicMetadata: () => Promise; } class Webamp { diff --git a/packages/webamp/package.json b/packages/webamp/package.json index 84dd1354..20afeae2 100644 --- a/packages/webamp/package.json +++ b/packages/webamp/package.json @@ -134,6 +134,7 @@ "jszip": "^3.10.1", "lodash": "^4.17.21", "milkdrop-preset-converter-aws": "^0.1.6", + "music-metadata": "^11.6.0", "music-metadata-browser": "^0.6.1", "react": "^19.1.0", "react-dom": "^19.1.0", diff --git a/yarn.lock b/yarn.lock index 94adca1f..cd4ad549 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5779,6 +5779,20 @@ dependencies: defer-to-connect "^2.0.1" +"@tokenizer/inflate@^0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@tokenizer/inflate/-/inflate-0.2.7.tgz#32dd9dfc9abe457c89b3d9b760fc0690c85a103b" + integrity sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg== + dependencies: + debug "^4.4.0" + fflate "^0.8.2" + token-types "^6.0.0" + +"@tokenizer/token@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz" @@ -9344,7 +9358,7 @@ content-disposition@0.5.4, content-disposition@^0.5.2: dependencies: safe-buffer "5.2.1" -content-type@~1.0.4, content-type@~1.0.5: +content-type@^1.0.5, content-type@~1.0.4, content-type@~1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -9999,6 +10013,13 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.4.0, debug@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz" @@ -12239,6 +12260,11 @@ fflate@^0.7.3: resolved "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz" integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw== +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + figures@^1.3.5: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" @@ -12304,6 +12330,16 @@ file-type@^12.0.0: resolved "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz" integrity sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg== +file-type@^21.0.0: + version "21.0.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-21.0.0.tgz#b6c5990064bc4b704f8e5c9b6010c59064d268bc" + integrity sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg== + dependencies: + "@tokenizer/inflate" "^0.2.7" + strtok3 "^10.2.2" + token-types "^6.0.0" + uint8array-extras "^1.4.0" + file-type@^3.8.0: version "3.9.0" resolved "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz" @@ -14045,7 +14081,7 @@ ieee754@1.1.13: resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== -ieee754@^1.1.13, ieee754@^1.1.4: +ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -17606,6 +17642,11 @@ media-typer@0.3.0: resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + memfs@^3.4.3: version "3.6.0" resolved "https://registry.npmjs.org/memfs/-/memfs-3.6.0.tgz" @@ -18648,7 +18689,7 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.0.0, ms@^2.1.1: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -18702,6 +18743,20 @@ music-metadata-browser@^0.6.1: remove "^0.1.5" typedarray-to-buffer "^3.1.5" +music-metadata@^11.6.0: + version "11.6.0" + resolved "https://registry.yarnpkg.com/music-metadata/-/music-metadata-11.6.0.tgz#edc6c136c9ada31ee7ce9ed0c7a7c47546e9a54b" + integrity sha512-l7MbWpuGM5GK8gol22L9tou8d/IoFyS8dnsfLbO6cocjlyMwgyLaCIqdwhp4sN1Nzz/Ql/K9kRLvRJDCVKjO3g== + dependencies: + "@tokenizer/token" "^0.3.0" + content-type "^1.0.5" + debug "^4.4.1" + file-type "^21.0.0" + media-typer "^1.1.0" + strtok3 "^10.3.1" + token-types "^6.0.3" + uint8array-extras "^1.4.0" + music-metadata@^3.4.0: version "3.8.0" resolved "https://registry.npmjs.org/music-metadata/-/music-metadata-3.8.0.tgz" @@ -23846,6 +23901,13 @@ strnum@^1.0.5: resolved "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz" integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== +strtok3@^10.2.2, strtok3@^10.3.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-10.3.1.tgz#80fe431a4ee652de4e33f14e11e15fd5170a627d" + integrity sha512-3JWEZM6mfix/GCJBBUrkA8p2Id2pBkyTkVCJKto55w080QBKZ+8R171fGrbiSp+yMO/u6F8/yUh7K4V9K+YCnw== + dependencies: + "@tokenizer/token" "^0.3.0" + strtok3@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/strtok3/-/strtok3-2.3.0.tgz" @@ -24340,6 +24402,14 @@ token-types@^1.0.1: dependencies: ieee754 "^1.1.13" +token-types@^6.0.0, token-types@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-6.0.3.tgz#684f4f40e0750078ec644c826207a2c160b827a8" + integrity sha512-IKJ6EzuPPWtKtEIEPpIdXv9j5j2LGJEYk0CKY2efgKoYKLBiZdh6iQkLVBow/CB3phyWAWCyk+bZeaimJn6uRQ== + dependencies: + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + toml@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz" @@ -24728,6 +24798,11 @@ ufo@^1.3.2: resolved "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz" integrity sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw== +uint8array-extras@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/uint8array-extras/-/uint8array-extras-1.4.0.tgz#e42a678a6dd335ec2d21661333ed42f44ae7cc74" + integrity sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz"