From 2e2909b08b85d5dac60799c6565f338d96987bb4 Mon Sep 17 00:00:00 2001 From: Borewit Date: Sun, 25 Oct 2026 16:37:51 +0100 Subject: [PATCH] Replace upload artwork reader with music-metadata --- package-lock.json | 301 ++++++++++++++++-- packages/web/package.json | 3 +- .../store/utils/fixtures/cover.mp3 | Bin 0 -> 1432 bytes .../store/utils/fixtures/cover.opus | Bin 0 -> 2949 bytes .../store/utils/fixtures/cover.png | Bin 0 -> 101 bytes .../store/utils/fixtures/no-cover.wav | Bin 0 -> 8898 bytes .../store/utils/processFiles.test.ts | 100 ++++++ .../upload-page/store/utils/processFiles.ts | 37 +-- 8 files changed, 390 insertions(+), 51 deletions(-) create mode 100644 packages/web/src/pages/upload-page/store/utils/fixtures/cover.mp3 create mode 100644 packages/web/src/pages/upload-page/store/utils/fixtures/cover.opus create mode 100644 packages/web/src/pages/upload-page/store/utils/fixtures/cover.png create mode 100644 packages/web/src/pages/upload-page/store/utils/fixtures/no-cover.wav create mode 100644 packages/web/src/pages/upload-page/store/utils/processFiles.test.ts diff --git a/package-lock.json b/package-lock.json index b713fb22718..6ba15afcc35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4138,6 +4138,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@bravemobile/react-native-code-push": { "version": "12.3.2", "resolved": "https://registry.npmjs.org/@bravemobile/react-native-code-push/-/react-native-code-push-12.3.2.tgz", @@ -5511,7 +5521,6 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", - "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -35974,6 +35983,101 @@ } } }, + "node_modules/@tokenizer/inflate": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.5.2.tgz", + "integrity": "sha512-3AZIuReWQ7i916s/UWAzy4V4k2wrkAZLW2sTi448W3w7Hz5uoc+Y7h+cnwzr0cwrwf3PsC5xKsH8MOl0NRltlg==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "strtok3": "^10.6.2", + "token-types": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/inflate/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/@tokenizer/inflate/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "node_modules/@tokenizer/inflate/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/@tokenizer/inflate/node_modules/strtok3": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.6.2.tgz", + "integrity": "sha512-BkdixFFUt7r7LsX6aGNPVyYMLjW+M1USLlKPNbZsrZEmLFh5tGqhdfckKo6xgsZIJqdDD0CZGkeCJJwNWQIKQA==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/inflate/node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "license": "MIT" @@ -38432,11 +38536,6 @@ "parse5": "^7.0.0" } }, - "node_modules/@types/jsmediatags": { - "version": "3.9.1", - "dev": true, - "license": "MIT" - }, "node_modules/@types/json-schema": { "version": "7.0.15", "dev": true, @@ -68880,16 +68979,6 @@ "node": ">=6" } }, - "node_modules/jsmediatags": { - "version": "3.8.1", - "license": "BSD-3-Clause", - "dependencies": { - "xhr2": "^0.1.4" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/json-bigint": { "version": "1.0.0", "license": "MIT", @@ -77796,6 +77885,158 @@ "node": ">=8.0.0" } }, + "node_modules/music-metadata": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-12.3.2.tgz", + "integrity": "sha512-U+Wu8c/7xa8wP331oVuRTAJSHcEWQIkLjpuyRJG+CAIszGYhpr5CRDv6PqkkVHDoX3ndjmdJlB6S49AaLq8SbQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.2", + "@tokenizer/token": "^0.3.0", + "content-type": "^3.1.1", + "debug": "^4.4.3", + "file-type": "^22.2.0", + "media-typer": "^2.0.0", + "strtok3": "^10.6.2", + "token-types": "^6.1.2", + "uint8array-extras": "^1.6.0", + "win-guid": "^0.2.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/music-metadata/node_modules/content-type": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-3.1.1.tgz", + "integrity": "sha512-GW4qUsfFo59d0HbUibDlWv5wPz+vAAcaTWbKIuKCf0JkC7wWkSyf8f13IpXn5JkeMlB2P8iTSSIjwXljorg2vA==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/music-metadata/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/node_modules/file-type": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-22.2.0.tgz", + "integrity": "sha512-XmEVJsRpQMVYC+sWFW+FKAVo2P7/lPctDdWH4UQ1r6oxhdrTobnowdM3z+ZJ93a4J8D3yDpxX0bEvRuFev0vUQ==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.5.1", + "strtok3": "^10.5.0", + "token-types": "^6.1.2", + "uint8array-extras": "^1.6.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/music-metadata/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "node_modules/music-metadata/node_modules/media-typer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-2.0.0.tgz", + "integrity": "sha512-kOy3OxT2HH39N70UnKgu4NWDZjLOz8W/mfyvniHjRH/DrL3f2pOfvWQ4p60offbbtDAnXWp0v9LfMIqMec269Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/music-metadata/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/node_modules/strtok3": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.6.2.tgz", + "integrity": "sha512-BkdixFFUt7r7LsX6aGNPVyYMLjW+M1USLlKPNbZsrZEmLFh5tGqhdfckKo6xgsZIJqdDD0CZGkeCJJwNWQIKQA==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/music-metadata/node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/mustache": { "version": "4.2.0", "dev": true, @@ -99385,6 +99626,18 @@ "node": ">=0.8.0" } }, + "node_modules/uint8array-extras": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.6.0.tgz", + "integrity": "sha512-8iAasVS4wUx0gPLjH8Xtz2PeDzTSiy8QiLGu2DT3X5GU+egFEQhpnbtkehbAwjvDcxIERmCYcysiODFmE3Ud0Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/uint8arrays": { "version": "3.1.1", "license": "MIT", @@ -106208,6 +106461,12 @@ "dev": true, "license": "MIT" }, + "node_modules/win-guid": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/win-guid/-/win-guid-0.2.1.tgz", + "integrity": "sha512-gEIQU4mkgl2OPeoNrWflcJFJ3Ae2BPd4eCsHHA/XikslkIVms/nHhvnvzIZV7VLmBvtFlDOzLt9rrZT+n6D67A==", + "license": "MIT" + }, "node_modules/wkx": { "version": "0.4.8", "license": "MIT", @@ -107100,13 +107359,6 @@ "node": ">=0.10.0" } }, - "node_modules/xhr2": { - "version": "0.1.4", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/xhr2-cookies": { "version": "1.1.0", "license": "MIT", @@ -126921,13 +127173,13 @@ "jimp": "0.6.8", "js-sha3": "0.8.0", "js-yaml": "3.13.1", - "jsmediatags": "3.8.1", "linkify-react": "4.1.0", "linkifyjs": "4.1.0", "localforage": "1.10.0", "lodash": "4.17.21", "lottie-react": "2.4.0", "markdown-to-jsx": "7.4.3", + "music-metadata": "12.3.2", "numeral": "2.0.6", "persona": "5.5.0", "prop-types": "15.7.2", @@ -126987,7 +127239,6 @@ "@types/eth-sig-util": "2.1.1", "@types/history": "^4.7.11", "@types/js-yaml": "3.12.4", - "@types/jsmediatags": "3.9.1", "@types/linkifyjs": "2.1.3", "@types/lodash": "4.14.149", "@types/node": "12.12.35", diff --git a/packages/web/package.json b/packages/web/package.json index 0ecebc2eabc..1e2df5a9565 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -122,13 +122,13 @@ "jimp": "0.6.8", "js-sha3": "0.8.0", "js-yaml": "3.13.1", - "jsmediatags": "3.8.1", "linkify-react": "4.1.0", "linkifyjs": "4.1.0", "localforage": "1.10.0", "lodash": "4.17.21", "lottie-react": "2.4.0", "markdown-to-jsx": "7.4.3", + "music-metadata": "12.3.2", "numeral": "2.0.6", "persona": "5.5.0", "prop-types": "15.7.2", @@ -192,7 +192,6 @@ "@types/eth-sig-util": "2.1.1", "@types/history": "^4.7.11", "@types/js-yaml": "3.12.4", - "@types/jsmediatags": "3.9.1", "@types/linkifyjs": "2.1.3", "@types/lodash": "4.14.149", "@types/node": "12.12.35", diff --git a/packages/web/src/pages/upload-page/store/utils/fixtures/cover.mp3 b/packages/web/src/pages/upload-page/store/utils/fixtures/cover.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ee4309bfbd5802bccddf05de268cc524b40d0906 GIT binary patch literal 1432 zcmeZtF=l1}0!Eh*&k!RZLjZ_fbCXh2Qc_bCN-|4wQW-*mgI$55{6OrJSe9mHq-O|3 z2F47I0iMo4!CD~B%uP&B)i20PXJB?K%FioNNX{=yEn?^l@N?(olHvlY;Pv!y2?Ej} z3j{crfaHXU8D>C=v%n*=7)UdMvA^n(Ye0s$r;B4q#hm0nryZmc6a)XRXba?Z_F!hn zU|_WVxr=3|u zu&+AYXWV)@<>SJG95NB|aq|FtRX6WLZg67jiH-i#$om zFx+y=bVn;g=%~;vokio)cz{AAI=<|Ai>ba$nvr2!h@Up1> zwD$knvj5?){=Pi+`~SZWjBKndGhQ^Lf2rK3<|fopnO1GQCauDwa+%Nrt%kyI4k=qF zwgz{`1@Q|~HfeA%vQBg5e%7IW`_reXzgN6sD}Coxx-03+(|dh;-MX{BYrl2fzvKR{ zl7mxp-)(vOS^Vol(*_@w2_ikL3^G3K26{X^4bf+0FDmey?l{MD%uupN+QARxA%Q0D zKjB>ZZ`=+#u_Qj-|Hk9&RE?87zGo|*Zt`uoVi39P^qQ~1`Af8Jb)Hz9G5LE~m)vIA zNBnMIo``;bojmKDm+kh~v0)pE?|irU@YSu@_X3N`3JnQ{Q(nG>9q+&U$Q;{g&Eq9y0DH zx1_SZ^$!1;%ap*QV|J(4;DQI|4B5uc#s+VR0ItUECM~lWr+fBzw9ZIsn%S6PKEt4C zWz!3-8H^3p>+J&!&dyj}dwa|M6Mt`>|9gJ-R^ET>pZ>qjz+fW4akZsLiWiuBnNCYA z*&{K>QsSbHgo(%uIU|aAK0fc7=s@G06-Y5+5j>jgPKi>Ra%0|DMB% zhglVUC3;wP@89*9CFm!}LmCW1JB}Ogyu-k#!#D5nrVk8tIj@wS?6m*?@6W2TyNUvw zTD{Jpnf3qwKkfhhU%y^iaL%UFpMZJyXNtXl-eex5ySlnrnc>USnb=qsDrEn#V=4Z( z|Nl1~u7kH1z7F74bUdr#)@u-9nZ^?$6u2ls<9W^grCa@Ga!r}ixso&FP`Gj4`T2JD vzqQ7{-5L6SUE804Kd$&jRz zG$B%nWV|jDQj$~%c|;uC8K-;hUH6=|?q0wDUi-iI_qTuRzt{h>en>w*2M#WdpGNFc zoo0t-u%1m$ab$29+1dy1&B+PmjBVMMAldRijVtaZ$Q-Cu$A_yiJyynRAJtRjI`)CaPQht3hbf>=A5EP7h%6&b8*0+Ax_prf~#&60tE^7 zvvIOE39)fQLoxO?U9a9ovnfA=E=BR*l~=TRm&A-c3Uy+H;Io9 zit0>C0S5J2-j~i=7)x08mgjFxf24d$@^>*bX@o4u8*bFfTwDrx+uSc$PP%VP_6Ew* zJqapw{SW4S6qKjIP>j?3+n|q}RXE?LCEuK{ESaP*nJEtoNWnWdK=o)2OCLJ+UCc^w z#o_*{ra;rf{WrBHGXy+5<}x)dxL}tMbxQHdw9`z1XTUvDjP!O!(OLT#nK&|sPewQf zH$Qa|BEZbq;@!J9sp@rIAUADv@!{3pUJE#M@xGhZwJgWc)z1LNVNnBvLS*KUzU0vL zG*bpSp>DaX3#p>7y4mnW;p^+G<5=;tXKr6q`s@MyC?b4Ov}FW z?4T|k*t7B^8ZQ{<0-3w|58fn0W8cslYR#8JvoZX&T7ADuwj^w{8@4zjL~&>*XIWDd zc#xmwFlRMZfmjj^zzWU(sN;(IY}WT!d~vg}ylgJ7CC$Be?Uwd%>N<6GdxN{G9$Im` zR0wtr_?$b2BQ%Mof>%yP?KKS6m;{wKy5pq4Y5WrPPXq#qOS0nENyZ@R5m&_>yOA^U zf-&8Ojns?|7cYaj>e{|u!8xiVEp^4E?}59yvPF(AKjgGh5REx0%op?;qkJ;})rg3Z ziwgtbiC^8g<=XQQI{70I#+3L4GMw^hn>KJHP@qz*WP(RI%gIOa#l+&~?ORPZA9PD# zU%k7EYYTvJ`-O;R+`{BcCnY)W3BAKSe`k0{Ed3oi`gMm~wD{9mxj>#<+KNM}e4Z)X zK%C7+`gHAeH7OV;NO7PA?Yg`5runW_P~)zWAX>IAo~}hh%r|H6 zX1B&t?t^fe3%Qz6$hYb17ZsnxOl0xHTi&aaCpsT%0At&zBuN=}23lYIpB|P~^qUyT0Ent>cmoN=0smNvLn1koiT! zv)G$Esv|Fj|07jY8ETx^W3CRa$0RX?36&$Gm&T#BwIZ;OePLivy&zbWn z1S8J`(-|EPm%Q{jWFP$ySyL1`K)iImFuY9UN>{&;qeCCyLKU&$V#6w%GqLR(cZUz{ za)(6iym{p>Jy6}Fy{1S<(cv0XxpM?3{oCU~Kkl}Di%KhcD=c_Q{0?X)FQsEyYuU)W zuT%F66Dy%0V#J_W+tL(A2z;=ZIILd7q*!xvp-=w_DO7dyiI%El@w3F{4Z=MGm$5= z7WP!ydiXW3=J*r-6p@lw?Tvv_D&!`L{B)i^PAHz76|mdoJnajh#V4%rR*riZddIs+Io z+|s)f8=@W(C+Zl=Z zN~LImwU0qf=6Zp}swG{};jrVW<8>Jpu}G-2EV_fXqnl{umWF)4XS3-5*>* zYvU~XzEc-HSOr-$w&E)4@k&b9b>BQjUz{bJ=t|lW(fw{iM^(;!3%0$$x{gQt_Kc+3 z|3Q`|M*#Jt?H_JDJhc-)Ha2xmKrR6{yboMi*kd+u_ZO`%1tl(+@+G{a&n-fkq^|Db z=j#hCBcKu&HySV@DllD(j1nV0p@LD$03=3N;enZa52G#CbItKZ1iJLuLf8?78H>HN z?u!*NT#u9tPR*>tmVez@ztWq@2=l^5*5KW%ySIhuE(waf?V@si;X>4Fq~V%ibk9IzU2 z{k5mpIh}^JSICm==88+bKi@7PS4r3+iRmAs2)yj`6}^yw--+prd-}w p2jZsbu*Tan%TdsNCa%z%1CPu4lCm}8d=l$D6;(&HM?ze+{sn6pV@Uu2 literal 0 HcmV?d00001 diff --git a/packages/web/src/pages/upload-page/store/utils/fixtures/cover.png b/packages/web/src/pages/upload-page/store/utils/fixtures/cover.png new file mode 100644 index 0000000000000000000000000000000000000000..29a28460e664f281f0b272486aa3b0f21087de56 GIT binary patch literal 101 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|oCO|{#Xy=7jQv%QTmv%1JzX3_ qD&{2rIqe{opcwdfMOz@Rvj;On1_Pt@*9Dp&?F^o-elF{r5}E+BSQc~u literal 0 HcmV?d00001 diff --git a/packages/web/src/pages/upload-page/store/utils/fixtures/no-cover.wav b/packages/web/src/pages/upload-page/store/utils/fixtures/no-cover.wav new file mode 100644 index 0000000000000000000000000000000000000000..34d3d5c482bd94983c2eef39d6082d38830ad4c6 GIT binary patch literal 8898 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 ZQbbOQ$Vm}7DIzCD ({ + newTrackMetadata: vi.fn((metadata) => metadata) +})) +vi.mock( + '@audius/common/utils', + () => import('@audius/common/src/utils/uploadConstants') +) +vi.mock('utils/imageProcessingUtil', () => ({ resizeImage: vi.fn() })) + +// Original 440 Hz tones generated with FFmpeg. cover.mp3 contains an ID3v2.3 +// front cover and title; cover.opus contains METADATA_BLOCK_PICTURE artwork. +const fixtureDirectory = 'src/pages/upload-page/store/utils/fixtures' +const fixture = (name: string, type: string) => + new File([readFileSync(`${fixtureDirectory}/${name}`)], name, { + type + }) + +const upload = async (file: File) => { + const handleInvalid = vi.fn() + const [track] = await Promise.all(processFiles([file], handleInvalid)) + expect(handleInvalid).not.toHaveBeenCalled() + return track! +} + +describe('processFiles artwork', () => { + beforeEach(() => { + vi.mocked(resizeImage).mockReset() + vi.mocked(resizeImage).mockResolvedValue( + new File(['resized artwork'], 'resized.png', { type: 'image/png' }) + ) + vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:artwork') + vi.spyOn(console, 'warn').mockImplementation(() => {}) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it.each([ + ['cover.opus', 'audio/ogg'], + ['cover.mp3', 'audio/mpeg'] + ])( + 'extracts embedded artwork from %s through the resize pipeline', + async (name, type) => { + const file = fixture(name, type) + const track = await upload(file) + + expect(resizeImage).toHaveBeenCalledOnce() + const image = vi.mocked(resizeImage).mock.calls[0][0] + expect(image.type).toBe('image/png') + expect(new Uint8Array(await image.arrayBuffer())).toEqual( + new Uint8Array(readFileSync(`${fixtureDirectory}/cover.png`)) + ) + expect(track.file).toBe(file) + expect(track.metadata.title).toBe(name.replace(/\.[^.]+$/, '')) + expect(track.metadata.orig_filename).toBe(name) + expect(track.metadata.artwork.url).toBe('blob:artwork') + expect(await track.metadata.artwork.file!.text()).toBe('resized artwork') + expect(console.warn).not.toHaveBeenCalled() + } + ) + + it('continues without artwork when the audio has no cover', async () => { + const track = await upload(fixture('no-cover.wav', 'audio/wav')) + + expect(track.metadata.artwork).toEqual({ file: null, url: '' }) + expect(resizeImage).not.toHaveBeenCalled() + expect(console.warn).not.toHaveBeenCalled() + }) + + it('continues uploading when metadata parsing fails', async () => { + const file = new File(['not audio'], 'broken.tsv', { + type: 'audio/unknown' + }) + const track = await upload(file) + + expect(track.file).toBe(file) + expect(track.metadata.artwork).toEqual({ file: null, url: '' }) + expect(resizeImage).not.toHaveBeenCalled() + expect(console.warn).toHaveBeenCalledOnce() + }) + + it('continues uploading when resizing the embedded cover fails', async () => { + vi.mocked(resizeImage).mockRejectedValue( + new Error('Unable to resize image') + ) + const track = await upload(fixture('cover.opus', 'audio/ogg')) + + expect(track.metadata.artwork).toEqual({ file: null, url: '' }) + expect(console.warn).toHaveBeenCalledOnce() + }) +}) diff --git a/packages/web/src/pages/upload-page/store/utils/processFiles.ts b/packages/web/src/pages/upload-page/store/utils/processFiles.ts index 75c1b8eb196..c055b9c7bad 100644 --- a/packages/web/src/pages/upload-page/store/utils/processFiles.ts +++ b/packages/web/src/pages/upload-page/store/utils/processFiles.ts @@ -4,26 +4,10 @@ import { ALLOWED_AUDIO_FILE_EXTENSIONS, ALLOWED_AUDIO_FILE_MIME } from '@audius/common/utils' -import jsmediatags from 'jsmediatags' import { resizeImage } from 'utils/imageProcessingUtil' -const readMediaTags = (file: File): Promise => { - return new Promise(function (resolve, reject) { - jsmediatags.read(file, { - onSuccess: function (tag: any) { - let tags - if (tag && tag.tags) { - tags = tag.tags - } - resolve(tags) - }, - onError: function (error) { - reject(error) - } - }) - }) -} +let musicMetadataPromise: Promise | undefined const createArtwork = async (selectedFiles: File[]) => { const file = selectedFiles[0] @@ -68,13 +52,18 @@ export const processFiles = ( const title = file.name.replace(/\.[^/.]+$/, '') // strip file extension let artwork = { file: null as File | null, url: '' } try { - const tags = await readMediaTags(file) - if (tags && tags.picture) { - const dataArray = new Uint8Array(tags.picture.data) - const blob = new window.Blob([dataArray]) - const artworkFile = new File([blob], 'Artwork', { - type: tags.picture.format - }) + musicMetadataPromise ??= import('music-metadata') + const { parseBlob, selectCover } = await musicMetadataPromise + const { common } = await parseBlob(file) + const picture = selectCover(common.picture) + if (picture) { + const artworkFile = new File( + [new Uint8Array(picture.data)], + 'Artwork', + { + type: picture.format + } + ) artwork = await createArtwork([artworkFile]) } } catch (error) {