diff --git a/.prettierignore b/.prettierignore index 9481208..4bcade1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,3 +4,6 @@ pnpm-lock.yaml .husky .prettierignore .gitignore +*.ttf +*.otf +*.woff2 diff --git a/package.json b/package.json index 142c93a..fc7bf2b 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "license": "MIT", "devDependencies": { "@crowdin/cli": "^3.7.10", + "@hrgui/libass-wasm-ts": "^1.0.3", "@types/mark.js": "^8.11.8", "@types/node": "^18.7.5", "@types/sha256": "^0.2.0", @@ -43,6 +44,7 @@ "husky": "^8.0.2", "lint-staged": "^13.0.4", "prettier": "3.0.0", + "rollup-plugin-copy": "^3.5.0", "terser": "^5.14.2", "typescript": "^4.7.4", "vite": "^3.0.8", @@ -67,6 +69,7 @@ "flv.js": "^1.6.2", "hls.js": "^1.2.1", "just-once": "^2.2.0", + "libass-wasm": "^4.1.0", "lightgallery": "^2.5.0", "mark.js": "^8.11.1", "mitt": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1bf5bc1..b7f1dcc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ dependencies: just-once: specifier: ^2.2.0 version: 2.2.0 + libass-wasm: + specifier: ^4.1.0 + version: 4.1.0 lightgallery: specifier: ^2.5.0 version: 2.5.0 @@ -109,6 +112,9 @@ devDependencies: '@crowdin/cli': specifier: ^3.7.10 version: 3.7.10 + '@hrgui/libass-wasm-ts': + specifier: ^1.0.3 + version: 1.0.3 '@types/mark.js': specifier: ^8.11.8 version: 8.11.8 @@ -133,6 +139,9 @@ devDependencies: prettier: specifier: 3.0.0 version: 3.0.0 + rollup-plugin-copy: + specifier: ^3.5.0 + version: 3.5.0 terser: specifier: ^5.14.2 version: 5.14.2 @@ -520,6 +529,10 @@ packages: solid-transition-group: 0.0.12(solid-js@1.4.8) dev: false + /@hrgui/libass-wasm-ts@1.0.3: + resolution: {integrity: sha512-n8RbJLrhirfgDun88jVSs0/SeLC5PZz9iost9DXZ9dAXztDzpmjlEfu+k/viM37+EbaC9gWnRdUwcnptDkjtNw==} + dev: true + /@jridgewell/gen-mapping@0.1.1: resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} engines: {node: '>=6.0.0'} @@ -633,6 +646,27 @@ packages: tslib: 2.4.0 dev: false + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + /@solid-primitives/event-listener@2.3.0(solid-js@1.4.8): resolution: {integrity: sha512-0DS7DQZvCExWSpurVZC9/wjI8RmkhuOtWOy6Pp1Woq9ElMT9/bfjNpkwXsOwisLpcTqh9eUs17kp7jtpWcC20w==} peerDependencies: @@ -713,6 +747,19 @@ packages: '@types/ms': 0.7.31 dev: false + /@types/fs-extra@8.1.4: + resolution: {integrity: sha512-OMcQKnlrkrOI0TaZ/MgVDA8LYFl7CykzFsjMj9l5x3un2nFxCY20ZFlnqrM0lcqlbs0Yro2HbnZlmopyRaoJ5w==} + dependencies: + '@types/node': 18.7.5 + dev: true + + /@types/glob@7.2.0: + resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} + dependencies: + '@types/minimatch': 5.1.2 + '@types/node': 18.7.5 + dev: true + /@types/hast@2.3.4: resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==} dependencies: @@ -749,6 +796,10 @@ packages: resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} dev: false + /@types/minimatch@5.1.2: + resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} + dev: true + /@types/ms@0.7.31: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} dev: false @@ -853,6 +904,11 @@ packages: smoothscroll: 0.4.0 dev: false + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + /artplayer-plugin-danmuku@4.4.11: resolution: {integrity: sha512-/4F8IyB29Bdr1LV1RM3FM7CdiAMsVNOOJ/HY4jaJCXnMAKThQiGa++0PhCJ07nhyGvDcZGRL2/UWJRz9zZJFNw==} dev: false @@ -1036,6 +1092,10 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, tarball: https://registry.npm.taobao.org/color-name/-/color-name-1.1.4.tgz} dev: true + /colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + dev: true + /colorette@2.0.19: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==, tarball: https://registry.npm.taobao.org/colorette/-/colorette-2.0.19.tgz} dev: true @@ -1159,6 +1219,13 @@ packages: engines: {node: '>=0.3.1'} dev: false + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + /dom-serializer@1.4.1: resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} dependencies: @@ -1458,6 +1525,23 @@ packages: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} dev: false + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: true + /fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} dependencies: @@ -1503,6 +1587,15 @@ packages: mime-types: 2.1.35 dev: false + /fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: true + /fs-minipass@1.2.7: resolution: {integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==} dependencies: @@ -1535,6 +1628,13 @@ packages: engines: {node: '>=10'} dev: true + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: @@ -1551,6 +1651,24 @@ packages: engines: {node: '>=4'} dev: true + /globby@10.0.1: + resolution: {integrity: sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==} + engines: {node: '>=8'} + dependencies: + '@types/glob': 7.2.0 + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.1 + glob: 7.2.3 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true + /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -1705,6 +1823,11 @@ packages: hasBin: true dev: true + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + /indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==, tarball: https://registry.npm.taobao.org/indent-string/-/indent-string-4.0.0.tgz} engines: {node: '>=8'} @@ -1741,6 +1864,11 @@ packages: has: 1.0.3 dev: true + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, tarball: https://registry.npm.taobao.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz} engines: {node: '>=8'} @@ -1751,6 +1879,13 @@ packages: engines: {node: '>=12'} dev: true + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, tarball: https://registry.npm.taobao.org/is-number/-/is-number-7.0.0.tgz} engines: {node: '>=0.12.0'} @@ -1761,6 +1896,11 @@ packages: engines: {node: '>=12'} dev: false + /is-plain-object@3.0.1: + resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==} + engines: {node: '>=0.10.0'} + dev: true + /is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==, tarball: https://registry.npm.taobao.org/is-stream/-/is-stream-3.0.0.tgz} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1791,6 +1931,12 @@ packages: hasBin: true dev: true + /jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + optionalDependencies: + graceful-fs: 4.2.11 + dev: true + /just-once@2.2.0: resolution: {integrity: sha512-Wo547FgUOUZ98jbrZ1KX8nRezdEdtgIlC2NK1u1RvR1oZ/WoU++FjprP8J8hRbaox776MHyeMZZED4DvhhHVjg==} dev: false @@ -1812,6 +1958,10 @@ packages: engines: {node: '>=6'} dev: false + /libass-wasm@4.1.0: + resolution: {integrity: sha512-+RbYT/uuI6VHExCmGyUuMg3A2gQOaCRTzSn8GGDSf3q4cEoUNiINd9u4RGfZXA1UKafW+Hv8bmcKIX4FKbSh0Q==} + dev: false + /lightgallery@2.5.0: resolution: {integrity: sha512-pzg5gwflLGlKaK1VqDKpb7yqQ/WSV/TxFaHRR7ld7G5iOI9gkJZ2qKKo2lyqvTvu8yAqNYXoU0w9O/Dll6POkw==} engines: {node: '>=6.0.0'} @@ -2043,6 +2193,11 @@ packages: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, tarball: https://registry.npm.taobao.org/merge-stream/-/merge-stream-2.0.0.tgz} dev: true + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + /micromark-core-commonmark@1.0.6: resolution: {integrity: sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==} dependencies: @@ -2505,6 +2660,11 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + /pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} dev: true @@ -2551,6 +2711,10 @@ packages: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: false + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + /rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} @@ -2637,10 +2801,26 @@ packages: signal-exit: 3.0.7 dev: true + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + /rfdc@1.3.0: resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==, tarball: https://registry.npm.taobao.org/rfdc/-/rfdc-1.3.0.tgz} dev: true + /rollup-plugin-copy@3.5.0: + resolution: {integrity: sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==} + engines: {node: '>=8.3'} + dependencies: + '@types/fs-extra': 8.1.4 + colorette: 1.4.0 + fs-extra: 8.1.0 + globby: 10.0.1 + is-plain-object: 3.0.1 + dev: true + /rollup@2.77.3: resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==} engines: {node: '>=10.0.0'} @@ -2649,6 +2829,12 @@ packages: fsevents: 2.3.2 dev: true + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + /rxjs@7.5.7: resolution: {integrity: sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==, tarball: https://registry.npm.taobao.org/rxjs/-/rxjs-7.5.7.tgz} dependencies: @@ -2716,6 +2902,11 @@ packages: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, tarball: https://registry.npm.taobao.org/signal-exit/-/signal-exit-3.0.7.tgz} dev: true + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + /slice-ansi@3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==, tarball: https://registry.npm.taobao.org/slice-ansi/-/slice-ansi-3.0.0.tgz} engines: {node: '>=8'} @@ -3055,6 +3246,11 @@ packages: unist-util-visit-parents: 5.1.0 dev: false + /universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + dev: true + /update-browserslist-db@1.0.5(browserslist@4.21.3): resolution: {integrity: sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==} hasBin: true diff --git a/src/components/artplayer-plugin-ass/fonts/SourceHanSansCN-Bold.woff2 b/src/components/artplayer-plugin-ass/fonts/SourceHanSansCN-Bold.woff2 new file mode 100644 index 0000000..28d1506 Binary files /dev/null and b/src/components/artplayer-plugin-ass/fonts/SourceHanSansCN-Bold.woff2 differ diff --git a/src/components/artplayer-plugin-ass/fonts/TimesNewRoman.ttf b/src/components/artplayer-plugin-ass/fonts/TimesNewRoman.ttf new file mode 100644 index 0000000..eaf5e11 Binary files /dev/null and b/src/components/artplayer-plugin-ass/fonts/TimesNewRoman.ttf differ diff --git a/src/components/artplayer-plugin-ass/index.d.ts b/src/components/artplayer-plugin-ass/index.d.ts new file mode 100644 index 0000000..4b51618 --- /dev/null +++ b/src/components/artplayer-plugin-ass/index.d.ts @@ -0,0 +1,12 @@ +import type Artplayer from "artplayer" +import type SubtitlesOctopus from "libass-wasm" +import { type Options } from "libass-wasm" + +export = artplayerPluginAss +export as namespace artplayerPluginAss +type Ass = { + name: "artplayerPluginAss" + instance: SubtitlesOctopus +} + +declare const artplayerPluginAss: (options: Options) => (art: Artplayer) => Ass diff --git a/src/components/artplayer-plugin-ass/index.js b/src/components/artplayer-plugin-ass/index.js new file mode 100644 index 0000000..8bbefc8 --- /dev/null +++ b/src/components/artplayer-plugin-ass/index.js @@ -0,0 +1,62 @@ +import SubtitlesOctopus from "libass-wasm" +import legacyWorkerUrl from "libass-wasm/dist/js/subtitles-octopus-worker-legacy.js?url" +import workerUrl from "libass-wasm/dist/js/subtitles-octopus-worker.js?url" + +import TimesNewRomanFont from "./fonts/TimesNewRoman.ttf?url" +import fallbackFont from "./fonts/SourceHanSansCN-Bold.woff2?url" + +let instance = null + +function setVisible(visible) { + if (instance.canvasParent) + instance.canvasParent.style.display = visible ? "block" : "none" +} + +function artplayerPluginAss(options) { + return (art) => { + instance = new SubtitlesOctopus({ + // TODO: load available fonts from manage panel + availableFonts: { + "times new roman": TimesNewRomanFont, + }, + fallbackFont, + legacyWorkerUrl, + workerUrl, + video: art.template.$video, + ...options, + }) + + instance.canvasParent.className = "artplayer-plugin-ass" + instance.canvasParent.style.cssText = ` + position: absolute; + width: 100%; + height: 100%; + user-select: none; + pointer-events: none; + z-index: 20; + ` + // switch subtitle track + art.on("artplayer-plugin-ass:switch", (subtitle) => { + instance.freeTrack() + instance.setTrackByUrl(subtitle) + setVisible(true) + }) + + // set subtitle visible + art.on("subtitle", (visible) => setVisible(visible)) + art.on("artplayer-plugin-ass:visible", (visible) => setVisible(visible)) + + // set subtitle offset + art.on("subtitleOffset", (offset) => (instance.timeOffset = offset)) + + // when player destory + art.on("destroy", () => instance.dispose()) + + return { + name: "artplayerPluginAss", + instance: instance, + } + } +} + +export default artplayerPluginAss diff --git a/src/components/icons.tsx b/src/components/icons.tsx index b5900cb..09008ab 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -58,3 +58,16 @@ export function VscodeIconsFileTypeAi2(props: IconProps) { props, ) } + +export function ArtPlayerIconsSubtitle(props: IconProps) { + return IconTemplate( + { + a: { + viewBox: "0 0 48 48", + }, + c: ` + `, + }, + props, + ) +} diff --git a/src/pages/home/previews/video.tsx b/src/pages/home/previews/video.tsx index 17b81fe..ba5907d 100644 --- a/src/pages/home/previews/video.tsx +++ b/src/pages/home/previews/video.tsx @@ -6,11 +6,15 @@ import { ObjType } from "~/types" import { ext } from "~/utils" import Artplayer from "artplayer" import { type Option } from "artplayer/types/option" +import { type Setting } from "artplayer/types/setting" +import { type Events } from "artplayer/types/events" import artplayerPluginDanmuku from "artplayer-plugin-danmuku" +import artplayerPluginAss from "~/components/artplayer-plugin-ass" import flvjs from "flv.js" import Hls from "hls.js" import { currentLang } from "~/app/i18n" import { VideoBox } from "./video_box" +import { ArtPlayerIconsSubtitle } from "~/components/icons" const Preview = () => { const { replace, pathname } = useRouter() @@ -80,7 +84,7 @@ const Preview = () => { }, }, lang: ["en", "zh-cn", "zh-tw"].includes(currentLang().toLowerCase()) - ? (currentLang().toLowerCase() as any) + ? (currentLang().toLowerCase() as string) : "en", lock: true, fastForward: true, @@ -104,51 +108,120 @@ const Preview = () => { } return false }) + + // TODO: add a switch in manage panel to choose whether to enable `libass-wasm` + const enableEnhanceAss = true + if (subtitle.length != 0) { - option.subtitle = { - url: proxyLink(subtitle[0], true), - type: ext(subtitle[0].name) as any, + let isEnhanceAssMode = false + + // set default subtitle + const defaultSubtitle = subtitle[0] + if (enableEnhanceAss && ext(defaultSubtitle.name).toLowerCase() === "ass") { + isEnhanceAssMode = true + option.plugins?.push( + artplayerPluginAss({ + // debug: true, + subUrl: proxyLink(defaultSubtitle, true), + }), + ) + } else { + option.subtitle = { + url: proxyLink(defaultSubtitle, true), + type: ext(defaultSubtitle.name), + } + } + + // render subtitle toggle menu + const innerMenu: Setting[] = [ + { + id: "setting_subtitle_display", + html: "Display", + tooltip: "Show", + switch: true, + onSwitch: function (item: Setting) { + item.tooltip = item.switch ? "Hide" : "Show" + setSubtitleVisible(!item.switch) + + // sync menu subtitle tooltip + const menu_sub = option.settings?.find( + (_) => _.id === "setting_subtitle", + ) + menu_sub && (menu_sub.tooltip = item.tooltip) + + return !item.switch + }, + }, + ] + subtitle.forEach((item, i) => { + innerMenu.push({ + default: i === 0, + html: ( + + {item.name} + + ) as HTMLElement, + name: item.name, + url: proxyLink(item, true), + }) + }) + + option.settings?.push({ + id: "setting_subtitle", + html: "Subtitle", + tooltip: "Show", + icon: ArtPlayerIconsSubtitle({ size: 24 }) as HTMLElement, + selector: innerMenu, + onSelect: function (item: Setting) { + if (enableEnhanceAss && ext(item.name).toLowerCase() === "ass") { + isEnhanceAssMode = true + this.emit("artplayer-plugin-ass:switch" as keyof Events, item.url) + setSubtitleVisible(true) + } else { + isEnhanceAssMode = false + this.subtitle.switch(item.url, { name: item.name }) + this.once("subtitleLoad", setSubtitleVisible.bind(this, true)) + } + + const switcher = innerMenu.find( + (_) => _.id === "setting_subtitle_display", + ) + + if (switcher && !switcher.switch) switcher.$html?.click?.() + + // sync from display switcher + return switcher?.tooltip + }, + }) + + function setSubtitleVisible(visible: boolean) { + const type = isEnhanceAssMode ? "ass" : "webvtt" + + switch (type) { + case "ass": + player.subtitle.show = false + player.emit("artplayer-plugin-ass:visible" as keyof Events, visible) + break + + case "webvtt": + default: + player.subtitle.show = visible + player.emit("artplayer-plugin-ass:visible" as keyof Events, false) + break + } } } - if (subtitle.length != 0) { - const selector = [] - selector.push({ - html: "Display", - tooltip: "Show", - switch: true, - onSwitch: function (item: Setting) { - item.tooltip = item.switch ? "Hide" : "Show" - this.subtitle.show = !item.switch - return !item.switch - }, - }) - subtitle.map((subtitleOne, i) => { - selector.push({ - default: i == 0 ? true : false, - html: - subtitleOne.name.length < 30 - ? subtitleOne.name - : subtitleOne.name.substr(-30, 30), - url: proxyLink(subtitleOne, true), - }) - }) - option.settings.push({ - html: "Subtitle", - tooltip: subtitle[0].name, - icon: '', - selector: selector, - onSelect: function (item: Setting) { - this.subtitle.switch(item.url, { - name: item.html, - }) - return item.html - }, - }) - } - if (danmu) { - option.plugins = [ + option.plugins?.push( artplayerPluginDanmuku({ danmuku: proxyLink(danmu, true), speed: 5, @@ -166,7 +239,7 @@ const Preview = () => { maxWidth: 400, theme: "dark", }), - ] + ) } onMount(() => { player = new Artplayer(option) diff --git a/tsconfig.json b/tsconfig.json index 4b1a5ae..7d8a3ce 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "esModuleInterop": true, "jsx": "preserve", "jsxImportSource": "solid-js", - "types": ["vite/client"], + "types": ["vite/client", "@hrgui/libass-wasm-ts"], "noEmit": true, "isolatedModules": false, "paths": { diff --git a/vite.config.ts b/vite.config.ts index d2c17db..2b5f620 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from "vite" import solidPlugin from "vite-plugin-solid" import legacy from "@vitejs/plugin-legacy" import { dynamicBase } from "vite-plugin-dynamic-base" +import copyPlugin from "rollup-plugin-copy" export default defineConfig({ resolve: { @@ -16,6 +17,12 @@ export default defineConfig({ legacy({ targets: ["defaults"], }), + copyPlugin({ + targets: [ + { src: "node_modules/libass-wasm/**/*.wasm", dest: "dist/assets" }, + ], + hook: "writeBundle", + }), dynamicBase({ // dynamic public path var string, default window.__dynamic_base__ publicPath: " window.__dynamic_base__",