diff --git a/components.d.ts b/components.d.ts index 50895f2..8c59879 100644 --- a/components.d.ts +++ b/components.d.ts @@ -14,9 +14,11 @@ declare module 'vue' { AbLoopDialog: typeof import('./src/components/modals/AbLoopDialog.vue')['default'] AboutSettings: typeof import('./src/components/settings/custom/AboutSettings.vue')['default'] AmllDbServerConfig: typeof import('./src/components/settings/custom/AmllDbServerConfig.vue')['default'] + AMLLLyrics: typeof import('./src/components/player/Lyrics/AMLLLyrics.vue')['default'] AppBackground: typeof import('./src/components/AppBackground.vue')['default'] AutoCloseDialog: typeof import('./src/components/modals/AutoCloseDialog.vue')['default'] BackgroundImagePicker: typeof import('./src/components/settings/custom/BackgroundImagePicker.vue')['default'] + BackgroundRender: typeof import('./src/components/player/FullPlayer/BackgroundRender.vue')['default'] BottomSpectrum: typeof import('./src/components/player/FullPlayer/BottomSpectrum.vue')['default'] ComboboxAnchor: typeof import('reka-ui')['ComboboxAnchor'] ComboboxContent: typeof import('reka-ui')['ComboboxContent'] diff --git a/package.json b/package.json index 4281078..99d8269 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,17 @@ "docs:preview": "vitepress preview docs" }, "dependencies": { + "@applemusic-like-lyrics/core": "^0.5.1", + "@applemusic-like-lyrics/lyric": "^1.0.1", "@electron-toolkit/preload": "^3.0.2", "@electron-toolkit/utils": "^4.0.0", + "@pixi/app": "^7.4.3", + "@pixi/core": "^7.4.3", + "@pixi/display": "^7.4.3", + "@pixi/filter-blur": "^7.4.3", + "@pixi/filter-bulge-pinch": "^5.1.1", + "@pixi/filter-color-matrix": "^7.4.3", + "@pixi/sprite": "^7.4.3", "@hono/node-server": "^2.0.2", "@material/material-color-utilities": "^0.4.0", "@vueuse/core": "^14.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6a0af1..a6283d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@applemusic-like-lyrics/core': + specifier: ^0.5.1 + version: 0.5.1(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))) + '@applemusic-like-lyrics/lyric': + specifier: ^1.0.1 + version: 1.0.1 '@electron-toolkit/preload': specifier: ^3.0.2 version: 3.0.2(electron@41.6.1) @@ -20,6 +26,27 @@ importers: '@material/material-color-utilities': specifier: ^0.4.0 version: 0.4.0 + '@pixi/app': + specifier: ^7.4.3 + version: 7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)) + '@pixi/core': + specifier: ^7.4.3 + version: 7.4.3 + '@pixi/display': + specifier: ^7.4.3 + version: 7.4.3(@pixi/core@7.4.3) + '@pixi/filter-blur': + specifier: ^7.4.3 + version: 7.4.3(@pixi/core@7.4.3) + '@pixi/filter-bulge-pinch': + specifier: ^5.1.1 + version: 5.1.1(@pixi/core@7.4.3) + '@pixi/filter-color-matrix': + specifier: ^7.4.3 + version: 7.4.3(@pixi/core@7.4.3) + '@pixi/sprite': + specifier: ^7.4.3 + version: 7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)) '@vueuse/core': specifier: ^14.2.1 version: 14.2.1(vue@3.5.32(typescript@5.9.3)) @@ -174,6 +201,23 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@applemusic-like-lyrics/core@0.5.1': + resolution: {integrity: sha512-DAEHmAe2USj/9qH4GKRFr/TuIKCOsNlcjgB2J2Lh4Be65RLU/pWpgnRhpIbV9ppinJzs+dWEsueWZiFTgLvMYQ==} + peerDependencies: + '@pixi/app': '*' + '@pixi/core': '*' + '@pixi/display': '*' + '@pixi/filter-blur': '*' + '@pixi/filter-bulge-pinch': '*' + '@pixi/filter-color-matrix': '*' + '@pixi/sprite': '*' + + '@applemusic-like-lyrics/lyric@1.0.1': + resolution: {integrity: sha512-b4/9MUTdp9AuW59JTBpOxb8P+4SF8NjaoZYpLD/nkVHSM/4kfbnYpo9HldSMOjRLwlKVv8bTF6Vr9K3OLHL85Q==} + + '@applemusic-like-lyrics/ttml@1.0.1': + resolution: {integrity: sha512-xhLajMI9Jm+Etv99GbK5Fx8wqa5m9wnS3kCqOTBUy9imjsah2PrV1XyCc6LBssPhEFcQt2SrFNQlwT6GZwpY7A==} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -1564,6 +1608,68 @@ packages: resolution: {integrity: sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==} engines: {node: '>=14.18.0'} + '@pixi/app@7.4.3': + resolution: {integrity: sha512-opyWMuO0Ir8pf1DYUR++wAA6ZfNU+nIX2z95R2OD172HbcdhB4/HD7leLIIAny/LciEdMqlWEBhXK7N93YWbdg==} + peerDependencies: + '@pixi/core': 7.4.3 + '@pixi/display': 7.4.3 + + '@pixi/color@7.4.3': + resolution: {integrity: sha512-a6R+bXKeXMDcRmjYQoBIK+v2EYqxSX49wcjAY579EYM/WrFKS98nSees6lqVUcLKrcQh2DT9srJHX7XMny3voQ==} + + '@pixi/colord@2.9.6': + resolution: {integrity: sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==} + + '@pixi/constants@7.4.3': + resolution: {integrity: sha512-QGmwJUNQy/vVEHzL6VGQvnwawLZ1wceZMI8HwJAT4/I2uAzbBeFDdmCS8WsTpSWLZjF/DszDc1D8BFp4pVJ5UQ==} + + '@pixi/core@7.4.3': + resolution: {integrity: sha512-5YDs11faWgVVTL8VZtLU05/Fl47vaP5Tnsbf+y/WRR0VSW3KhRRGTBU1J3Gdc2xEWbJhUK07KGP7eSZpvtPVgA==} + + '@pixi/display@7.4.3': + resolution: {integrity: sha512-b5m2dAaoNAVdxz1oDaxl3XZ059NEOcNtGkxTOZ4EYCw/jcp9sZXkgSROHRzsGn4k+NugH7+9MP4Id2Z0kkdUhw==} + peerDependencies: + '@pixi/core': 7.4.3 + + '@pixi/extensions@7.4.3': + resolution: {integrity: sha512-FhoiYkHQEDYHUE7wXhqfsTRz6KxLXjuMbSiAwnLb9uG1vAgp6q6qd6HEsf4X30YaZbLFY8a4KY6hFZWjF+4Fdw==} + + '@pixi/filter-blur@7.4.3': + resolution: {integrity: sha512-ZFzS9L/whdRbs5A/EUgF3yQaBcxNarmbuwaMgrfnpQ84mRczkGByqDLGToadiufyals07ufTrXBGRle9lbtEDA==} + peerDependencies: + '@pixi/core': 7.4.3 + + '@pixi/filter-bulge-pinch@5.1.1': + resolution: {integrity: sha512-80I3g813td7Fnzi7IJSiR3z8gZlKblk6WN+5z6WnscQROcNEpck6lgWS/Lf/IdeHB/FtUKJCbx7RzxkUhiRTvA==} + peerDependencies: + '@pixi/core': ^7.0.0-X + + '@pixi/filter-color-matrix@7.4.3': + resolution: {integrity: sha512-TNu0h20SrzjUWIb5v19dAp1vPpqtG0w2XF9kIHN91bMNaf3R1jzhpWG6TtaVO9eo1IolWcEJLw38jIohyC+KNw==} + peerDependencies: + '@pixi/core': 7.4.3 + + '@pixi/math@7.4.3': + resolution: {integrity: sha512-/uJOVhR2DOZ+zgdI6Bs/CwcXT4bNRKsS+TqX3ekRIxPCwaLra+Qdm7aDxT5cTToDzdxbKL5+rwiLu3Y1egILDw==} + + '@pixi/runner@7.4.3': + resolution: {integrity: sha512-TJyfp7y23u5vvRAyYhVSa7ytq0PdKSvPLXu4G3meoFh1oxTLHH6g/RIzLuxUAThPG2z7ftthuW3qWq6dRV+dhw==} + + '@pixi/settings@7.4.3': + resolution: {integrity: sha512-SmGK8smc0PxRB9nr0UJioEtE9hl4gvj9OedCvZx3bxBwA3omA5BmP3CyhQfN8XJ29+o2OUL01r3zAPVol4l4lA==} + + '@pixi/sprite@7.4.3': + resolution: {integrity: sha512-iNBrpOFF9nXDT6m2jcyYy6l/sRzklLDDck1eFHprHZwvNquY2nzRfh+RGBCecxhBcijiLJ3fsZN33fP0LDXkvw==} + peerDependencies: + '@pixi/core': 7.4.3 + '@pixi/display': 7.4.3 + + '@pixi/ticker@7.4.3': + resolution: {integrity: sha512-tHsAD0iOUb6QSGGw+c8cyRBvxsq/NlfzIFBZLEHhWZ+Bx4a0MmXup6I/yJDGmyPCYE+ctCcAfY13wKAzdiVFgQ==} + + '@pixi/utils@7.4.3': + resolution: {integrity: sha512-NO3Y9HAn2UKS1YdxffqsPp+kDpVm8XWvkZcS/E+rBzY9VTLnNOI7cawSRm+dacdET3a8Jad3aDKEDZ0HmAqAFA==} + '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -1763,9 +1869,15 @@ packages: '@types/cacheable-request@6.0.3': resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + '@types/css-font-loading-module@0.0.12': + resolution: {integrity: sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==} + '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/earcut@2.1.4': + resolution: {integrity: sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==} + '@types/esrecurse@4.3.1': resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} @@ -2198,6 +2310,9 @@ packages: resolution: {integrity: sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==} engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} + bezier-easing@3.0.0: + resolution: {integrity: sha512-lE85voPXiK99T8NHOfhaUqCZpJdP1gBbbTEvdBDdPB+phyvPZPNWalBe42eb6lKOYchP0qZrtBiRCARtT4edRQ==} + bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -2277,6 +2392,10 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -2443,6 +2562,9 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deep-freeze@0.0.1: + resolution: {integrity: sha512-Z+z8HiAvsGwmjqlphnHW5oz6yWlOwu6EQfFTjmeTWlDeda3FS2yv3jhq35TX/ewmsnqB+RX2IdsIOyjJCQN5tg==} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2509,6 +2631,9 @@ packages: duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + earcut@2.2.4: + resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==} + ejs@3.1.10: resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} engines: {node: '>=0.10.0'} @@ -2704,6 +2829,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -2850,6 +2978,9 @@ packages: github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + gl-matrix@4.0.0-beta.2: + resolution: {integrity: sha512-OF6IkQpMkF8p2CZF9EtzYZPlPaW3M41KMsgZGlTKmMv/nWaP6GMJi9V5tI+oPn8FG0io85Q5ZtKpCXP4u6YmDA==} + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -3024,6 +3155,9 @@ packages: resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} engines: {node: '>=20'} + ismobilejs@1.1.1: + resolution: {integrity: sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==} + jake@10.9.4: resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} engines: {node: '>=10'} @@ -3308,6 +3442,10 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -3373,6 +3511,9 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -3512,6 +3653,9 @@ packages: pump@3.0.4: resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode@1.4.1: + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -3528,6 +3672,10 @@ packages: engines: {node: '>=10.13.0'} hasBin: true + qs@6.15.2: + resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} + engines: {node: '>=0.6'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -3807,6 +3955,22 @@ packages: shiki@3.23.0: resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4139,6 +4303,10 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url@0.11.4: + resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} + engines: {node: '>= 0.4'} + utf8-byte-length@1.0.5: resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} @@ -4381,6 +4549,27 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.1.1 + '@applemusic-like-lyrics/core@0.5.1(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))': + dependencies: + '@pixi/app': 7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)) + '@pixi/core': 7.4.3 + '@pixi/display': 7.4.3(@pixi/core@7.4.3) + '@pixi/filter-blur': 7.4.3(@pixi/core@7.4.3) + '@pixi/filter-bulge-pinch': 5.1.1(@pixi/core@7.4.3) + '@pixi/filter-color-matrix': 7.4.3(@pixi/core@7.4.3) + '@pixi/sprite': 7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)) + '@ungap/structured-clone': 1.3.1 + bezier-easing: 3.0.0 + deep-freeze: 0.0.1 + gl-matrix: 4.0.0-beta.2 + + '@applemusic-like-lyrics/lyric@1.0.1': + dependencies: + '@applemusic-like-lyrics/ttml': 1.0.1 + pako: 2.1.0 + + '@applemusic-like-lyrics/ttml@1.0.1': {} + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -5550,6 +5739,79 @@ snapshots: tslib: 2.8.1 webcrypto-core: 1.9.2 + '@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))': + dependencies: + '@pixi/core': 7.4.3 + '@pixi/display': 7.4.3(@pixi/core@7.4.3) + + '@pixi/color@7.4.3': + dependencies: + '@pixi/colord': 2.9.6 + + '@pixi/colord@2.9.6': {} + + '@pixi/constants@7.4.3': {} + + '@pixi/core@7.4.3': + dependencies: + '@pixi/color': 7.4.3 + '@pixi/constants': 7.4.3 + '@pixi/extensions': 7.4.3 + '@pixi/math': 7.4.3 + '@pixi/runner': 7.4.3 + '@pixi/settings': 7.4.3 + '@pixi/ticker': 7.4.3 + '@pixi/utils': 7.4.3 + + '@pixi/display@7.4.3(@pixi/core@7.4.3)': + dependencies: + '@pixi/core': 7.4.3 + + '@pixi/extensions@7.4.3': {} + + '@pixi/filter-blur@7.4.3(@pixi/core@7.4.3)': + dependencies: + '@pixi/core': 7.4.3 + + '@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3)': + dependencies: + '@pixi/core': 7.4.3 + + '@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3)': + dependencies: + '@pixi/core': 7.4.3 + + '@pixi/math@7.4.3': {} + + '@pixi/runner@7.4.3': {} + + '@pixi/settings@7.4.3': + dependencies: + '@pixi/constants': 7.4.3 + '@types/css-font-loading-module': 0.0.12 + ismobilejs: 1.1.1 + + '@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))': + dependencies: + '@pixi/core': 7.4.3 + '@pixi/display': 7.4.3(@pixi/core@7.4.3) + + '@pixi/ticker@7.4.3': + dependencies: + '@pixi/extensions': 7.4.3 + '@pixi/settings': 7.4.3 + '@pixi/utils': 7.4.3 + + '@pixi/utils@7.4.3': + dependencies: + '@pixi/color': 7.4.3 + '@pixi/constants': 7.4.3 + '@pixi/settings': 7.4.3 + '@types/earcut': 2.1.4 + earcut: 2.2.4 + eventemitter3: 4.0.7 + url: 0.11.4 + '@polka/url@1.0.0-next.29': {} '@quansync/fs@1.0.0': @@ -5704,10 +5966,14 @@ snapshots: '@types/node': 22.19.17 '@types/responselike': 1.0.3 + '@types/css-font-loading-module@0.0.12': {} + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 + '@types/earcut@2.1.4': {} + '@types/esrecurse@4.3.1': {} '@types/estree@1.0.8': {} @@ -6277,6 +6543,8 @@ snapshots: bindings: 1.5.0 prebuild-install: 7.1.3 + bezier-easing@3.0.0: {} + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -6382,6 +6650,11 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} camelcase@5.3.1: @@ -6512,6 +6785,8 @@ snapshots: deep-extend@0.6.0: {} + deep-freeze@0.0.1: {} + deep-is@0.1.4: {} defer-to-connect@2.0.1: {} @@ -6583,6 +6858,8 @@ snapshots: duplexer@0.1.2: {} + earcut@2.2.4: {} + ejs@3.1.10: dependencies: jake: 10.9.4 @@ -6881,6 +7158,8 @@ snapshots: esutils@2.0.3: {} + eventemitter3@4.0.7: {} + expand-template@2.0.3: {} exponential-backoff@3.1.3: {} @@ -7039,6 +7318,8 @@ snapshots: github-from-package@0.0.0: {} + gl-matrix@4.0.0-beta.2: {} + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -7219,6 +7500,8 @@ snapshots: isexe@4.0.0: {} + ismobilejs@1.1.1: {} + jake@10.9.4: dependencies: async: 3.2.6 @@ -7487,6 +7770,8 @@ snapshots: dependencies: boolbase: 1.0.0 + object-inspect@1.13.4: {} + object-keys@1.1.1: optional: true @@ -7581,6 +7866,8 @@ snapshots: package-manager-detector@1.6.0: {} + pako@2.1.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -7714,6 +8001,8 @@ snapshots: end-of-stream: 1.4.5 once: 1.4.0 + punycode@1.4.1: {} + punycode@2.3.1: {} pvtsutils@1.3.6: @@ -7729,6 +8018,10 @@ snapshots: yargs: 15.4.1 optional: true + qs@6.15.2: + dependencies: + side-channel: 1.1.1 + quansync@0.2.11: {} quansync@1.0.0: {} @@ -8019,6 +8312,34 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -8391,6 +8712,11 @@ snapshots: dependencies: punycode: 2.3.1 + url@0.11.4: + dependencies: + punycode: 1.4.1 + qs: 6.15.2 + utf8-byte-length@1.0.5: {} util-deprecate@1.0.2: {} diff --git a/src/components/player/FullPlayer/BackgroundRender.vue b/src/components/player/FullPlayer/BackgroundRender.vue new file mode 100644 index 0000000..c56d00e --- /dev/null +++ b/src/components/player/FullPlayer/BackgroundRender.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/src/components/player/FullPlayer/PlayerBackground.vue b/src/components/player/FullPlayer/PlayerBackground.vue index 5e1f832..9d15d89 100644 --- a/src/components/player/FullPlayer/PlayerBackground.vue +++ b/src/components/player/FullPlayer/PlayerBackground.vue @@ -4,13 +4,14 @@ import { useThemeStore } from "@/stores/theme"; import { useMediaStore } from "@/stores/media"; import { useStatusStore } from "@/stores/status"; import DEFAULT_COVER from "@/assets/images/song.jpg"; +import BackgroundRender from "./BackgroundRender.vue"; const media = useMediaStore(); const settings = useSettingsStore(); const theme = useThemeStore(); const status = useStatusStore(); -const bgType = computed(() => settings.player.playerBgType); +const bgType = computed(() => settings.player.playerBgType as string); // 封面颜色(纯色模式) const coverColor = computed(() => { @@ -95,6 +96,17 @@ onBeforeUnmount(() => { /> + +
+ +
+
@@ -110,11 +122,13 @@ onBeforeUnmount(() => { diff --git a/src/components/player/Lyrics/renderer.css b/src/components/player/Lyrics/renderer.css index 81538fd..f3219c7 100644 --- a/src/components/player/Lyrics/renderer.css +++ b/src/components/player/Lyrics/renderer.css @@ -120,6 +120,22 @@ .lp-credit { opacity: var(--lp-credit-opacity, 0.3); + pointer-events: auto !important; /* 启用交互并阻止点击穿透到下方的普通歌词行上 */ + z-index: 10 !important; /* 提升层级,防止被普通歌词行(尤其是最后一行)遮挡和阻断点击 */ + background: none !important; + background-color: transparent !important; + box-shadow: none !important; + border: none !important; + outline: none !important; +} + +.lp-credit:hover, +.lp-credit:active { + background: none !important; + background-color: transparent !important; + box-shadow: none !important; + border: none !important; + outline: none !important; } .lp-credit:empty { diff --git a/src/components/settings/SettingsItem.vue b/src/components/settings/SettingsItem.vue index 09b80c2..0dfeed1 100644 --- a/src/components/settings/SettingsItem.vue +++ b/src/components/settings/SettingsItem.vue @@ -19,6 +19,7 @@ const selectOptions = computed(() => ); const isChildrenActive = computed(() => { + if (props.item.type === "title") return true; if (props.item.childrenCondition) return props.item.childrenCondition(); return model.value === true; }); @@ -39,6 +40,14 @@ const descriptionText = computed(() => class="transition-all duration-300" :class="highlighted ? 'animate-highlight-pulse' : ''" /> + +
+ + {{ t(`settings.${item.key}.label`) }} +
diff --git a/src/components/settings/SettingsSearch.vue b/src/components/settings/SettingsSearch.vue index c74ee00..d98a3e9 100644 --- a/src/components/settings/SettingsSearch.vue +++ b/src/components/settings/SettingsSearch.vue @@ -29,6 +29,7 @@ const results = computed(() => { for (const cat of settingsSchema) { for (const sec of cat.sections ?? []) { for (const item of sec.items) { + if (item.visible && !item.visible()) continue; const label = t(`settings.${item.key}.label`); const desc = item.hideDescription ? "" : t(`settings.${item.key}.description`); const kw = item.keywords?.map((k) => t(k)).join(" ") ?? ""; diff --git a/src/components/settings/SettingsSection.vue b/src/components/settings/SettingsSection.vue index 916ff4c..0b10711 100644 --- a/src/components/settings/SettingsSection.vue +++ b/src/components/settings/SettingsSection.vue @@ -13,7 +13,9 @@ const props = withDefaults( const { t } = useI18n(); -const visibleItems = computed(() => props.section.items); +const visibleItems = computed(() => + props.section.items.filter((item) => !item.visible || item.visible()), +); const itemStyle = (i: number) => { const d = props.highlightKey ? "0s" : `${Math.min(props.startIndex + i, 15) * 0.03}s`; diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index fd5ecb2..757d96a 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -619,6 +619,7 @@ "general": "General", "player": "Player", "lyric": "Lyrics", + "fullScreenLyric": "Full-screen Lyrics", "externalLyric": "External Lyrics", "appearance": "Appearance", "hotkeys": "Hotkey Settings", @@ -639,6 +640,7 @@ "language": "Language", "playback": "Player", "playControl": "Playback Control", + "lyricEngine": "Lyric Engine", "lyricContent": "Lyric Content", "lyricTTML": "TTML Lyrics", "lyricExclude": "Metadata Stripping", @@ -646,6 +648,7 @@ "lyricDisplay": "Display Effects", "lyricSpring": "Spring Animation", "lyricLayout": "Layout & Opacity", + "amllLyricSpring": "Physics Spring & Scale", "desktopLyric": "Desktop Lyric", "dynamicIsland": "Dynamic Island Lyric", "taskbarLyric": "Taskbar Lyric", @@ -837,6 +840,56 @@ "openSearch": "Open search" } }, + "engine": { + "label": "Lyric Engine", + "description": "Choose the engine type used for lyric rendering and scrolling" + }, + "lyricEngine": { + "physics": "Default", + "amll": "AMLL" + }, + "useAMSpring": { + "label": "Spring Animation Debug Switch", + "description": "Enable spring bounce and active line scaling effects for applemusic-like-lyrics" + }, + "amllVerticalSpringHeader": { + "label": "Vertical Translation Spring Parameters" + }, + "amllScaleSpringHeader": { + "label": "Scale Spring Parameters" + }, + "amllVerticalSpringMass": { + "label": "Vertical Spring Mass", + "description": "Mass of the vertical scrolling physics spring. Larger mass increases spring inertia." + }, + "amllVerticalSpringDamping": { + "label": "Vertical Spring Damping", + "description": "Damping of the vertical scrolling physics spring. Larger damping reduces oscillation and settles faster." + }, + "amllVerticalSpringStiffness": { + "label": "Vertical Spring Stiffness", + "description": "Stiffness of the vertical scrolling physics spring. Larger stiffness pulls harder and bounces quicker." + }, + "amllVerticalSpringSoft": { + "label": "Vertical Soft Spring", + "description": "Forces the vertical scrolling to use a softer spring profile." + }, + "amllScaleSpringMass": { + "label": "Scale Spring Mass", + "description": "Mass of the active line scale animation. Larger mass increases scale bounce inertia." + }, + "amllScaleSpringDamping": { + "label": "Scale Spring Damping", + "description": "Damping of the active line scale animation. Larger damping reduces scale jitter and settles faster." + }, + "amllScaleSpringStiffness": { + "label": "Scale Spring Stiffness", + "description": "Stiffness of the active line scale animation. Larger stiffness responds and scales quicker." + }, + "amllScaleSpringSoft": { + "label": "Scale Soft Spring", + "description": "Forces the active line scale animation to use a softer spring profile." + }, "autoCenterCover": { "label": "Auto Center Cover", "description": "Center cover and hide lyric area when no lyrics available" @@ -869,6 +922,14 @@ "label": "Show Romanization", "description": "Display romanized lyrics" }, + "amllShowLineRomanization": { + "label": "Show Line Romanization", + "description": "Display romanized lyrics for each line" + }, + "amllShowWordRomanization": { + "label": "Show Word Romanization", + "description": "Display romanized lyrics for each word in word-by-word lyrics" + }, "enableWordHighlight": { "label": "Word Highlight", "description": "Show word-by-word lyric highlight progress" diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 507addd..0780b7b 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -607,6 +607,7 @@ "general": "常规设置", "player": "播放设置", "lyric": "歌词设置", + "fullScreenLyric": "全屏歌词", "externalLyric": "外部歌词", "appearance": "外观设置", "hotkeys": "快捷键配置", @@ -627,6 +628,7 @@ "language": "语言", "playback": "播放器", "playControl": "播放控制", + "lyricEngine": "歌词引擎", "lyricContent": "歌词内容", "lyricTTML": "TTML 歌词", "lyricExclude": "歌词排除", @@ -634,6 +636,7 @@ "lyricDisplay": "显示效果", "lyricSpring": "弹簧动画", "lyricLayout": "布局与透明度", + "amllLyricSpring": "物理回弹与缩放", "desktopLyric": "桌面歌词", "dynamicIsland": "灵动岛歌词", "taskbarLyric": "任务栏歌词", @@ -825,6 +828,56 @@ "openSearch": "打开搜索" } }, + "engine": { + "label": "歌词渲染引擎", + "description": "选择用于歌词渲染与滚动的动效引擎" + }, + "lyricEngine": { + "physics": "默认", + "amll": "AMLL" + }, + "useAMSpring": { + "label": "弹簧动画调试开关", + "description": "为 applemusic-like-lyrics 启用物理弹性回弹与焦点行缩放效果" + }, + "amllVerticalSpringHeader": { + "label": "垂直位移弹簧参数" + }, + "amllScaleSpringHeader": { + "label": "缩放弹簧参数" + }, + "amllVerticalSpringMass": { + "label": "垂直回弹质量", + "description": "垂直弹性滚动的惯性质量。质量越大,回弹惯性越大" + }, + "amllVerticalSpringDamping": { + "label": "垂直回弹阻尼", + "description": "垂直弹性滚动的阻尼阻力。阻力越大,回弹振幅越小且越快静止" + }, + "amllVerticalSpringStiffness": { + "label": "垂直回弹刚度", + "description": "垂直弹性滚动的弹簧刚度。刚度越大,拉力越强,回弹速度越快" + }, + "amllVerticalSpringSoft": { + "label": "垂直强制软弹簧", + "description": "启用后将强制使用较软的垂直滚动弹簧动效" + }, + "amllScaleSpringMass": { + "label": "缩放回弹质量", + "description": "焦点行缩放动画的质量。值越大,缩放回弹的惯性越大" + }, + "amllScaleSpringDamping": { + "label": "缩放回弹阻尼", + "description": "焦点行缩放动画的阻尼阻力。值越大,缩放震荡越少且越快静止" + }, + "amllScaleSpringStiffness": { + "label": "缩放回弹刚度", + "description": "焦点行缩放动画的弹簧刚度。值越大,缩放回弹越迅速" + }, + "amllScaleSpringSoft": { + "label": "缩放强制软弹簧", + "description": "启用后将强制使用较软的焦点行缩放弹簧动效" + }, "autoCenterCover": { "label": "自动居中封面", "description": "无歌词时自动居中封面并隐藏歌词区域" @@ -857,6 +910,14 @@ "label": "显示音译", "description": "显示歌词的音译文本" }, + "amllShowLineRomanization": { + "label": "显示逐行音译", + "description": "显示歌词行的音译文本" + }, + "amllShowWordRomanization": { + "label": "显示逐词音译", + "description": "在逐字歌词中,显示每个字词的音译文本" + }, "enableWordHighlight": { "label": "逐字高亮", "description": "逐字显示歌词高亮进度" diff --git a/src/settings/categories/fullScreenLyric.ts b/src/settings/categories/fullScreenLyric.ts new file mode 100644 index 0000000..6eb1e09 --- /dev/null +++ b/src/settings/categories/fullScreenLyric.ts @@ -0,0 +1,316 @@ +import type { SettingCategory } from "@/types/settings-schema"; +import { useSettingsStore } from "@/stores/settings"; +import IconLucideTv from "~icons/lucide/tv"; + +const fullScreenLyricCategory: SettingCategory = { + id: "fullScreenLyric", + icon: IconLucideTv, + sections: [ + { + id: "lyricEngine", + items: [ + { + key: "engine", + type: "select", + binding: { store: "settings", path: "lyric.engine" }, + options: [ + { value: "physics", labelKey: "settings.lyricEngine.physics" }, + { value: "amll", labelKey: "settings.lyricEngine.amll" }, + ], + defaultValue: "physics", + }, + ], + }, + { + id: "lyricGeneral", + items: [ + { + key: "adaptiveFontSize", + type: "switch", + binding: { store: "settings", path: "lyric.adaptiveFontSize" }, + defaultValue: true, + }, + { + key: "fontSize", + type: "slider", + binding: { store: "settings", path: "lyric.fontSize" }, + min: 30, + max: 64, + step: 1, + defaultValue: 48, + marks: { 30: "30", 48: "48", 64: "64" }, + }, + { + key: "fontWeight", + type: "slider", + binding: { store: "settings", path: "lyric.fontWeight" }, + min: 100, + max: 900, + step: 100, + defaultValue: 700, + marks: { 100: "100", 400: "400", 700: "700", 900: "900" }, + }, + { + key: "showTranslation", + type: "switch", + binding: { store: "settings", path: "lyric.showTranslation" }, + defaultValue: true, + }, + { + key: "showRomanization", + type: "switch", + binding: { store: "settings", path: "lyric.showRomanization" }, + defaultValue: true, + visible: () => useSettingsStore().lyric.engine === "physics", + }, + { + key: "amllShowLineRomanization", + type: "switch", + binding: { store: "settings", path: "lyric.amllShowLineRomanization" }, + defaultValue: true, + visible: () => useSettingsStore().lyric.engine === "amll", + }, + { + key: "amllShowWordRomanization", + type: "switch", + binding: { store: "settings", path: "lyric.amllShowWordRomanization" }, + defaultValue: true, + visible: () => useSettingsStore().lyric.engine === "amll", + }, + ], + }, + { + id: "lyricDisplay", + items: [ + { + key: "enableWordHighlight", + type: "switch", + binding: { store: "settings", path: "lyric.enableWordHighlight" }, + defaultValue: true, + visible: () => useSettingsStore().lyric.engine === "physics", + }, + { + key: "enableFloatAnimation", + type: "switch", + binding: { store: "settings", path: "lyric.enableFloatAnimation" }, + defaultValue: false, + visible: () => useSettingsStore().lyric.engine === "physics", + }, + { + key: "enableEmphasizeEffect", + type: "switch", + binding: { store: "settings", path: "lyric.enableEmphasizeEffect" }, + defaultValue: false, + visible: () => useSettingsStore().lyric.engine === "physics", + }, + { + key: "enableBlur", + type: "switch", + binding: { store: "settings", path: "lyric.enableBlur" }, + defaultValue: false, + }, + { + key: "hidePassedLines", + type: "switch", + binding: { store: "settings", path: "lyric.hidePassedLines" }, + defaultValue: false, + }, + ], + }, + { + id: "lyricSpring", + items: [ + { + key: "springPreset", + type: "select", + binding: { store: "settings", path: "lyric.springPreset" }, + options: [ + { value: "default", labelKey: "settings.springPreset.default" }, + { value: "smooth", labelKey: "settings.springPreset.smooth" }, + { value: "responsive", labelKey: "settings.springPreset.responsive" }, + { value: "jello", labelKey: "settings.springPreset.jello" }, + { value: "heavy", labelKey: "settings.springPreset.heavy" }, + { value: "noBounce", labelKey: "settings.springPreset.noBounce" }, + { value: "custom", labelKey: "settings.springPreset.custom" }, + ], + defaultValue: "default", + visible: () => useSettingsStore().lyric.engine === "physics", + indentChildren: false, + childrenCondition: () => + useSettingsStore().lyric.engine !== "amll" && + useSettingsStore().lyric.springPreset === "custom", + children: [ + { + key: "springMass", + type: "slider", + binding: { store: "settings", path: "lyric.springMass" }, + min: 0.1, + max: 5, + step: 0.1, + defaultValue: 0.9, + marks: { 0.1: "0.1", 0.9: "0.9", 5: "5" }, + }, + { + key: "springDamping", + type: "slider", + binding: { store: "settings", path: "lyric.springDamping" }, + min: 1, + max: 50, + step: 0.5, + defaultValue: 15, + marks: { 1: "1", 15: "15", 50: "50" }, + }, + { + key: "springStiffness", + type: "slider", + binding: { store: "settings", path: "lyric.springStiffness" }, + min: 10, + max: 300, + step: 5, + defaultValue: 90, + marks: { 10: "10", 90: "90", 300: "300" }, + }, + ], + }, + ], + }, + { + id: "lyricLayout", + items: [ + { + key: "alignPosition", + type: "slider", + binding: { store: "settings", path: "lyric.alignPosition" }, + min: 0.1, + max: 0.9, + step: 0.05, + defaultValue: 0.35, + marks: { 0.1: "0.1", 0.35: "0.35", 0.9: "0.9" }, + }, + { + key: "wordFadeWidth", + type: "slider", + binding: { store: "settings", path: "lyric.wordFadeWidth" }, + min: 0.1, + max: 1, + step: 0.1, + defaultValue: 0.5, + marks: { 0.1: "0.1", 0.5: "0.5", 1: "1" }, + }, + { + key: "inactiveAlpha", + type: "slider", + binding: { store: "settings", path: "lyric.inactiveAlpha" }, + min: 0, + max: 1, + step: 0.05, + defaultValue: 0.2, + marks: { 0: "0", 0.2: "0.2", 1: "1" }, + visible: () => useSettingsStore().lyric.engine === "physics", + }, + ], + }, + { + id: "amllLyricSpring", + items: [ + { + key: "useAMSpring", + type: "switch", + binding: { store: "settings", path: "lyric.useAMSpring" }, + defaultValue: true, + visible: () => useSettingsStore().lyric.engine === "amll", + hideChildren: true, + childrenCondition: () => useSettingsStore().lyric.useAMSpring, + children: [ + { + key: "amllVerticalSpringHeader", + type: "title", + children: [ + { + key: "amllVerticalSpringMass", + type: "slider", + binding: { store: "settings", path: "lyric.amllVerticalSpringMass" }, + min: 0.1, + max: 5, + step: 0.1, + defaultValue: 1, + marks: { 0.1: "0.1", 1: "1", 5: "5" }, + }, + { + key: "amllVerticalSpringDamping", + type: "slider", + binding: { store: "settings", path: "lyric.amllVerticalSpringDamping" }, + min: 0, + max: 40, + step: 0.5, + defaultValue: 15, + marks: { 0: "0", 15: "15", 40: "40" }, + }, + { + key: "amllVerticalSpringStiffness", + type: "slider", + binding: { store: "settings", path: "lyric.amllVerticalSpringStiffness" }, + min: 1, + max: 300, + step: 1, + defaultValue: 100, + marks: { 1: "1", 100: "100", 300: "300" }, + }, + { + key: "amllVerticalSpringSoft", + type: "switch", + binding: { store: "settings", path: "lyric.amllVerticalSpringSoft" }, + defaultValue: false, + }, + ], + }, + { + key: "amllScaleSpringHeader", + type: "title", + children: [ + { + key: "amllScaleSpringMass", + type: "slider", + binding: { store: "settings", path: "lyric.amllScaleSpringMass" }, + min: 0.1, + max: 5, + step: 0.1, + defaultValue: 1, + marks: { 0.1: "0.1", 1: "1", 5: "5" }, + }, + { + key: "amllScaleSpringDamping", + type: "slider", + binding: { store: "settings", path: "lyric.amllScaleSpringDamping" }, + min: 0, + max: 40, + step: 0.5, + defaultValue: 20, + marks: { 0: "0", 20: "20", 40: "40" }, + }, + { + key: "amllScaleSpringStiffness", + type: "slider", + binding: { store: "settings", path: "lyric.amllScaleSpringStiffness" }, + min: 1, + max: 300, + step: 1, + defaultValue: 100, + marks: { 1: "1", 100: "100", 300: "300" }, + }, + { + key: "amllScaleSpringSoft", + type: "switch", + binding: { store: "settings", path: "lyric.amllScaleSpringSoft" }, + defaultValue: false, + }, + ], + }, + ], + }, + ], + }, + ], +}; + +export default fullScreenLyricCategory; diff --git a/src/settings/categories/lyric.ts b/src/settings/categories/lyric.ts index 1148095..34dbaac 100644 --- a/src/settings/categories/lyric.ts +++ b/src/settings/categories/lyric.ts @@ -106,172 +106,6 @@ const lyricCategory: SettingCategory = { }, ], }, - { - id: "lyricGeneral", - items: [ - { - key: "adaptiveFontSize", - type: "switch", - binding: { store: "settings", path: "lyric.adaptiveFontSize" }, - defaultValue: true, - }, - { - key: "fontSize", - type: "slider", - binding: { store: "settings", path: "lyric.fontSize" }, - min: 30, - max: 64, - step: 1, - defaultValue: 48, - marks: { 30: "30", 48: "48", 64: "64" }, - }, - { - key: "fontWeight", - type: "slider", - binding: { store: "settings", path: "lyric.fontWeight" }, - min: 100, - max: 900, - step: 100, - defaultValue: 700, - marks: { 100: "100", 400: "400", 700: "700", 900: "900" }, - }, - { - key: "showTranslation", - type: "switch", - binding: { store: "settings", path: "lyric.showTranslation" }, - defaultValue: true, - }, - { - key: "showRomanization", - type: "switch", - binding: { store: "settings", path: "lyric.showRomanization" }, - defaultValue: true, - }, - ], - }, - { - id: "lyricDisplay", - items: [ - { - key: "enableWordHighlight", - type: "switch", - binding: { store: "settings", path: "lyric.enableWordHighlight" }, - defaultValue: true, - }, - { - key: "enableFloatAnimation", - type: "switch", - binding: { store: "settings", path: "lyric.enableFloatAnimation" }, - defaultValue: false, - }, - { - key: "enableEmphasizeEffect", - type: "switch", - binding: { store: "settings", path: "lyric.enableEmphasizeEffect" }, - defaultValue: false, - }, - { - key: "enableBlur", - type: "switch", - binding: { store: "settings", path: "lyric.enableBlur" }, - defaultValue: false, - }, - { - key: "hidePassedLines", - type: "switch", - binding: { store: "settings", path: "lyric.hidePassedLines" }, - defaultValue: false, - }, - ], - }, - { - id: "lyricSpring", - items: [ - { - key: "springPreset", - type: "select", - binding: { store: "settings", path: "lyric.springPreset" }, - options: [ - { value: "default", labelKey: "settings.springPreset.default" }, - { value: "smooth", labelKey: "settings.springPreset.smooth" }, - { value: "responsive", labelKey: "settings.springPreset.responsive" }, - { value: "jello", labelKey: "settings.springPreset.jello" }, - { value: "heavy", labelKey: "settings.springPreset.heavy" }, - { value: "noBounce", labelKey: "settings.springPreset.noBounce" }, - { value: "custom", labelKey: "settings.springPreset.custom" }, - ], - defaultValue: "default", - childrenCondition: () => useSettingsStore().lyric.springPreset === "custom", - children: [ - { - key: "springMass", - type: "slider", - binding: { store: "settings", path: "lyric.springMass" }, - min: 0.1, - max: 5, - step: 0.1, - defaultValue: 0.9, - marks: { 0.1: "0.1", 0.9: "0.9", 5: "5" }, - }, - { - key: "springDamping", - type: "slider", - binding: { store: "settings", path: "lyric.springDamping" }, - min: 1, - max: 50, - step: 0.5, - defaultValue: 15, - marks: { 1: "1", 15: "15", 50: "50" }, - }, - { - key: "springStiffness", - type: "slider", - binding: { store: "settings", path: "lyric.springStiffness" }, - min: 10, - max: 300, - step: 5, - defaultValue: 90, - marks: { 10: "10", 90: "90", 300: "300" }, - }, - ], - }, - ], - }, - { - id: "lyricLayout", - items: [ - { - key: "alignPosition", - type: "slider", - binding: { store: "settings", path: "lyric.alignPosition" }, - min: 0.1, - max: 0.9, - step: 0.05, - defaultValue: 0.35, - marks: { 0.1: "0.1", 0.35: "0.35", 0.9: "0.9" }, - }, - { - key: "wordFadeWidth", - type: "slider", - binding: { store: "settings", path: "lyric.wordFadeWidth" }, - min: 0.1, - max: 1, - step: 0.1, - defaultValue: 0.5, - marks: { 0.1: "0.1", 0.5: "0.5", 1: "1" }, - }, - { - key: "inactiveAlpha", - type: "slider", - binding: { store: "settings", path: "lyric.inactiveAlpha" }, - min: 0, - max: 1, - step: 0.05, - defaultValue: 0.2, - marks: { 0: "0", 0.2: "0.2", 1: "1" }, - }, - ], - }, ], }; diff --git a/src/settings/schema.ts b/src/settings/schema.ts index 5ba42e4..b4a416c 100644 --- a/src/settings/schema.ts +++ b/src/settings/schema.ts @@ -3,6 +3,7 @@ import generalCategory from "./categories/general"; import appearanceCategory from "./categories/appearance"; import playerCategory from "./categories/player"; import lyricCategory from "./categories/lyric"; +import fullScreenLyricCategory from "./categories/fullScreenLyric"; import externalLyricCategory from "./categories/externalLyric"; import hotkeysCategory from "./categories/hotkeys"; import servicesCategory from "./categories/services"; @@ -18,6 +19,7 @@ export const settingsSchema: SettingCategory[] = [ appearanceCategory, playerCategory, lyricCategory, + fullScreenLyricCategory, externalLyricCategory, hotkeysCategory, servicesCategory, diff --git a/src/stores/media.ts b/src/stores/media.ts index 3419739..edadbd0 100644 --- a/src/stores/media.ts +++ b/src/stores/media.ts @@ -4,7 +4,7 @@ import { findLyricIndex } from "@shared/utils/lyric"; import { useSettingsStore } from "@/stores/settings"; import { watchLyricPreference } from "@/services/lyricLoader"; import { parseLyric } from "@/utils/lyric/parse"; -import { extractLyricAuthor } from "@/utils/lyric/author"; +import { extractLyricAuthor, extractLyricAuthors } from "@/utils/lyric/author"; import { applyLyricExclude } from "@/utils/lyric/lyricStripper"; import { normalizeLyricLines } from "@/utils/lyric/normalize"; @@ -38,6 +38,9 @@ export const useMediaStore = defineStore("media", () => { /** 当前歌词文件制作者 */ const lyricAuthor = ref(null); + /** 当前歌词文件制作者列表 */ + const lyricAuthors = ref([]); + /** 同步当前歌词源到主进程 */ const syncToMain = (): void => { try { @@ -86,6 +89,7 @@ export const useMediaStore = defineStore("media", () => { lyricContent.value = null; parsedLyric.value = []; lyricAuthor.value = null; + lyricAuthors.value = []; lyricIndex.value = -1; lyricLoading.value = true; syncToMain(); @@ -115,6 +119,8 @@ export const useMediaStore = defineStore("media", () => { parsedLyric.value = nextLines; lyricAuthor.value = hasContent && source && input ? extractLyricAuthor(input.content, source.format) : null; + lyricAuthors.value = + hasContent && source && input ? extractLyricAuthors(input.content, source.format) : []; lyricIndex.value = -1; lyricLoading.value = false; syncToMain(); @@ -136,6 +142,7 @@ export const useMediaStore = defineStore("media", () => { lyricContent.value = null; parsedLyric.value = []; lyricAuthor.value = null; + lyricAuthors.value = []; lyricLoading.value = false; lyricIndex.value = -1; syncToMain(); @@ -149,6 +156,7 @@ export const useMediaStore = defineStore("media", () => { lyricFormat, parsedLyric, lyricAuthor, + lyricAuthors, lyricLoading, lyricIndex, setTrack, diff --git a/src/stores/settings.ts b/src/stores/settings.ts index 93b01fb..8de9de2 100644 --- a/src/stores/settings.ts +++ b/src/stores/settings.ts @@ -73,6 +73,8 @@ export const useSettingsStore = defineStore( fontFamily: "", showTranslation: true, showRomanization: true, + amllShowLineRomanization: true, + amllShowWordRomanization: true, enableWordHighlight: true, enableFloatAnimation: false, enableEmphasizeEffect: false, @@ -88,6 +90,16 @@ export const useSettingsStore = defineStore( enableExcludeLyrics: true, excludeLyricsUserKeywords: [], excludeLyricsUserRegexes: [], + engine: "physics", + useAMSpring: true, + amllVerticalSpringMass: 1, + amllVerticalSpringDamping: 15, + amllVerticalSpringStiffness: 100, + amllVerticalSpringSoft: false, + amllScaleSpringMass: 1, + amllScaleSpringDamping: 20, + amllScaleSpringStiffness: 100, + amllScaleSpringSoft: false, }); /** 系统配置 - 传递主进程 */ diff --git a/src/types/settings-schema.ts b/src/types/settings-schema.ts index 1099e77..c3d3f57 100644 --- a/src/types/settings-schema.ts +++ b/src/types/settings-schema.ts @@ -8,7 +8,8 @@ export type SettingWidgetType = | "color" | "button" | "custom" - | "number"; + | "number" + | "title"; /** 选择项 */ export interface SettingOption { @@ -51,6 +52,8 @@ export interface SettingItem { hideDescription?: boolean; /** 条件禁用 */ disabled?: () => boolean; + /** 条件隐藏 */ + visible?: () => boolean; /** button 类型的点击回调 */ action?: () => void; /** custom 类型的组件 */ @@ -65,6 +68,8 @@ export interface SettingItem { childrenCondition?: () => boolean; /** 是否完全隐藏子项 */ hideChildren?: boolean; + /** 是否对子项进行左侧边线和缩进(默认 true) */ + indentChildren?: boolean; /** 标题旁的徽标 */ tag?: SettingTag; } diff --git a/src/types/settings.ts b/src/types/settings.ts index aedc492..c86dd3b 100644 --- a/src/types/settings.ts +++ b/src/types/settings.ts @@ -5,7 +5,7 @@ import { ALL_PLATFORMS } from "@shared/types/platform"; import type { QualityLevel } from "@/utils/quality"; /** 播放器背景类型 */ -export type PlayerBgType = "blur" | "solid"; +export type PlayerBgType = "blur" | "solid" | "animation"; export type CoverLayout = "default" | "fullscreen"; /** @@ -79,6 +79,10 @@ export interface LyricSettings { showTranslation: boolean; /** 是否显示音译歌词 */ showRomanization: boolean; + /** AMLL 是否显示逐行音译 */ + amllShowLineRomanization: boolean; + /** AMLL 是否显示逐词音译 */ + amllShowWordRomanization: boolean; /** 逐字高亮效果 */ enableWordHighlight: boolean; /** 逐字上浮动画 */ @@ -109,6 +113,20 @@ export interface LyricSettings { excludeLyricsUserKeywords: string[]; /** 用户自定义正则 */ excludeLyricsUserRegexes: string[]; + /** 歌词引擎类型 */ + engine: "physics" | "amll"; + /** AM 歌词是否启用物理回弹与缩放 */ + useAMSpring: boolean; + /** AMLL 垂直位移弹簧参数 */ + amllVerticalSpringMass: number; + amllVerticalSpringDamping: number; + amllVerticalSpringStiffness: number; + amllVerticalSpringSoft: boolean; + /** AMLL 缩放弹簧参数 */ + amllScaleSpringMass: number; + amllScaleSpringDamping: number; + amllScaleSpringStiffness: number; + amllScaleSpringSoft: boolean; } /** 播放器设置 */ diff --git a/src/utils/lyric/author.ts b/src/utils/lyric/author.ts index d3cd4e5..a2b54e0 100644 --- a/src/utils/lyric/author.ts +++ b/src/utils/lyric/author.ts @@ -1,19 +1,43 @@ import type { LyricFormat } from "@shared/types/lyrics"; /** - * 从歌词原始内容中提取「歌词文件制作者」 + * 从歌词原始内容中提取「歌词文件制作者」列表 * @param content - 歌词原始文本 * @param format - 歌词格式 + * @returns 作者账号/名称的数组 */ -export const extractLyricAuthor = (content: string, format: LyricFormat): string | null => { +export const extractLyricAuthors = (content: string, format: LyricFormat): string[] => { if (format === "ttml") { - // 优先 GitHub 登录名 - const login = content.match(/key="ttmlAuthorGithubLogin"\s+value="([^"]*)"/)?.[1]; - const base = content.match(/key="ttmlAuthorGithub"\s+value="([^"]*)"/)?.[1]; - return (login || base || "").trim() || null; + // 优先提取 ttmlAuthorGithubLogin,作为可以直接用于跳转 GitHub 的账号 + const logins = [...content.matchAll(/key="ttmlAuthorGithubLogin"\s+value="([^"]*)"/g)] + .map((m) => m[1].trim()) + .filter(Boolean); + if (logins.length > 0) { + return Array.from(new Set(logins)); + } + // 如果无 login 标识,从 ttmlAuthorGithub 主页链接中截取最后的用户名 + const bases = [...content.matchAll(/key="ttmlAuthorGithub"\s+value="([^"]*)"/g)] + .map((m) => { + const val = m[1].trim(); + const parts = val.split("/"); + return parts[parts.length - 1] || val; + }) + .filter(Boolean); + return Array.from(new Set(bases)); } if (format === "lrc") { - return content.match(/\[by:([^\]]+)\]/i)?.[1]?.trim() || null; + const match = content.match(/\[by:([^\]]+)\]/i)?.[1]?.trim(); + return match ? [match] : []; } - return null; + return []; +}; + +/** + * 从歌词原始内容中提取首个「歌词文件制作者」 + * @param content - 歌词原始文本 + * @param format - 歌词格式 + * @returns 首个作者名称或 null + */ +export const extractLyricAuthor = (content: string, format: LyricFormat): string | null => { + return extractLyricAuthors(content, format)[0] || null; }; diff --git a/src/utils/lyric/parse.ts b/src/utils/lyric/parse.ts index 98d7b1b..8cf868d 100644 --- a/src/utils/lyric/parse.ts +++ b/src/utils/lyric/parse.ts @@ -4,7 +4,11 @@ import { parseLRC } from "./parseLRC"; import { parseQRC } from "./parseQRC"; import { parseYRC } from "./parseYRC"; import { parseKRC } from "./parseKRC"; -import { parseTTML } from "./parseTTML"; +import { parseTTML, cleanTTMLTranslations } from "./parseTTML"; +import { + parseTTML as parseAMLLTtml, + parseYrc as parseAMLLYrc, +} from "@applemusic-like-lyrics/lyric"; import { parseLyS } from "./parseLyS"; import { parseSRT } from "./parseSRT"; import { parseASS } from "./parseASS"; @@ -70,14 +74,26 @@ export const detectFormat = (text: string): LyricFormat => { */ const parseContent = (text: string, format: LyricFormat, preferredLang = ""): LyricLine[] => { switch (format) { - case "ttml": - return parseTTML(text, preferredLang); + case "ttml": { + const cleaned = cleanTTMLTranslations(text, preferredLang); + try { + return parseAMLLTtml(cleaned).lines || []; + } catch (err) { + console.error("AMLL TTML parse failed, fallback to local parseTTML:", err); + return parseTTML(text, preferredLang); + } + } case "qrc": return parseQRC(text); case "krc": return parseKRC(text); case "yrc": - return parseYRC(text); + try { + return parseAMLLYrc(text) || []; + } catch (err) { + console.error("AMLL YRC parse failed, fallback to local parseYRC:", err); + return parseYRC(text); + } case "lrc": return parseLRC(text); case "lys": diff --git a/src/utils/lyric/parseTTML.ts b/src/utils/lyric/parseTTML.ts index 2fd625f..f84b991 100644 --- a/src/utils/lyric/parseTTML.ts +++ b/src/utils/lyric/parseTTML.ts @@ -424,3 +424,79 @@ export const parseTTML = (text: string, preferredLang = ""): LyricLine[] => { return lines; }; + +/** + * 清洗 TTML 中不需要的翻译,过滤非首选语言节点,避免上游解析器混淆 + * @param ttmlContent 原始 TTML 内容 + * @param preferredLang 偏好的语言(如 zh-CN) + * @returns 清洗后的 TTML 内容 + */ +export const cleanTTMLTranslations = (ttmlContent: string, preferredLang = ""): string => { + /** + * 统计 TTML 中的语言 + */ + const langCounter = (ttml_text: string) => { + const langRegex = /(?<=<(span|translation)[^<>]+)xml:lang="([^"]+)"/g; + const matches = ttml_text.matchAll(langRegex); + const langSet = new Set(); + for (const match of matches) { + if (match[2]) langSet.add(match[2]); + } + return Array.from(langSet); + }; + + /** + * 过滤语言并选择最佳匹配 + */ + const langFilter = (langs: string[]): string | null => { + if (langs.length <= 1) return null; + + const langMatcher = (target: string) => { + return langs.find((lang) => { + try { + return new Intl.Locale(lang).maximize().script === target; + } catch { + return false; + } + }); + }; + + // 优先匹配用户的偏好语言 + if (preferredLang) { + const preferred = preferredLang.toLowerCase().replace(/_/g, "-"); + const matched = langs.find((lang) => lang.toLowerCase().replace(/_/g, "-") === preferred); + if (matched) return matched; + + const prefBase = preferred.split("-")[0]; + const matchedBase = langs.find( + (lang) => lang.toLowerCase().replace(/_/g, "-").split("-")[0] === prefBase, + ); + if (matchedBase) return matchedBase; + } + + // 备选的中文脚本匹配优先级 + const hans_matched = langMatcher("Hans"); + if (hans_matched) return hans_matched; + const hant_matched = langMatcher("Hant"); + if (hant_matched) return hant_matched; + const major = langs.find((key) => key.startsWith("zh")); + if (major) return major; + return langs[0]; + }; + + /** + * 替换清洗标签 + */ + const ttmlCleaner = (ttml_text: string, major_lang: string | null): string => { + if (major_lang === null) return ttml_text; + const replacer = (match: string, lang: string) => (lang === major_lang ? match : ""); + const translationRegex = /]+xml:lang="([^"]+)"[^>]*>[\s\S]*?<\/translation>/g; + const spanRegex = /]+xml:lang="([^" ]+)"[^>]*>[\s\S]*?<\/span>/g; + return ttml_text.replace(translationRegex, replacer).replace(spanRegex, replacer); + }; + + const context_lang = langCounter(ttmlContent); + const major = langFilter(context_lang); + const cleaned_ttml = ttmlCleaner(ttmlContent, major); + return cleaned_ttml.replace(/\n\s*/g, ""); +};