mirror of https://github.com/VERT-sh/VERT.git
feat: ico, heic, dng, cur, ani
This commit is contained in:
parent
0d330f221a
commit
118aaa1745
14
bun.lock
14
bun.lock
|
@ -11,11 +11,13 @@
|
||||||
"@fontsource/lexend": "^5.1.2",
|
"@fontsource/lexend": "^5.1.2",
|
||||||
"@fontsource/radio-canada-big": "^5.1.1",
|
"@fontsource/radio-canada-big": "^5.1.1",
|
||||||
"@imagemagick/magick-wasm": "^0.0.34",
|
"@imagemagick/magick-wasm": "^0.0.34",
|
||||||
|
"byte-data": "^19.0.1",
|
||||||
"client-zip": "^2.4.6",
|
"client-zip": "^2.4.6",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-svelte": "^0.475.0",
|
"lucide-svelte": "^0.475.0",
|
||||||
"music-metadata": "^11.0.0",
|
"music-metadata": "^11.0.0",
|
||||||
"p-queue": "^8.1.0",
|
"p-queue": "^8.1.0",
|
||||||
|
"riff-file": "^1.0.3",
|
||||||
"vite-plugin-static-copy": "^2.2.0",
|
"vite-plugin-static-copy": "^2.2.0",
|
||||||
"wasm-vips": "^0.0.11",
|
"wasm-vips": "^0.0.11",
|
||||||
},
|
},
|
||||||
|
@ -302,6 +304,8 @@
|
||||||
|
|
||||||
"browserslist": ["browserslist@4.24.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A=="],
|
"browserslist": ["browserslist@4.24.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A=="],
|
||||||
|
|
||||||
|
"byte-data": ["byte-data@19.0.1", "", {}, "sha512-xRvkTvO28wr0+0rErSETHD8Cw+P444Az3/jkTezaMw5R+TTW8ZNXuvPZf9/ZhnSRRvlMnJsVhc+ecYvOMy/MQQ=="],
|
||||||
|
|
||||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||||
|
|
||||||
"camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
|
"camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
|
||||||
|
@ -368,6 +372,8 @@
|
||||||
|
|
||||||
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||||
|
|
||||||
|
"endianness": ["endianness@8.0.2", "", {}, "sha512-IU+77+jJ7lpw2qZ3NUuqBZFy3GuioNgXUdsL1L9tooDNTaw0TgOnwNuc+8Ns+haDaTifK97QLzmOANJtI/rGvw=="],
|
||||||
|
|
||||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||||
|
|
||||||
"esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
|
"esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
|
||||||
|
@ -458,6 +464,8 @@
|
||||||
|
|
||||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||||
|
|
||||||
|
"ieee754-buffer": ["ieee754-buffer@2.0.0", "", {}, "sha512-AXUAT0nMEi7h1Is8HXGXof3eejl/GabZFKSj8Ym6kVRUSwrAb52EkAXywiCQYSHGQMRn7lvfY7vhPMjVc+Kybg=="],
|
||||||
|
|
||||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||||
|
|
||||||
"immutable": ["immutable@5.0.3", "", {}, "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw=="],
|
"immutable": ["immutable@5.0.3", "", {}, "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw=="],
|
||||||
|
@ -640,6 +648,8 @@
|
||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
|
"riff-file": ["riff-file@1.0.3", "", { "dependencies": { "byte-data": "^18.0.3" } }, "sha512-Vv8wwGr0BCks7VMI3Lv0houZee4DaHFjjTT0LMhMJKio2YmLncLeIVpK63ydSverngNk8XQPU3fbeP3bWgSIig=="],
|
||||||
|
|
||||||
"rollup": ["rollup@4.34.9", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.9", "@rollup/rollup-android-arm64": "4.34.9", "@rollup/rollup-darwin-arm64": "4.34.9", "@rollup/rollup-darwin-x64": "4.34.9", "@rollup/rollup-freebsd-arm64": "4.34.9", "@rollup/rollup-freebsd-x64": "4.34.9", "@rollup/rollup-linux-arm-gnueabihf": "4.34.9", "@rollup/rollup-linux-arm-musleabihf": "4.34.9", "@rollup/rollup-linux-arm64-gnu": "4.34.9", "@rollup/rollup-linux-arm64-musl": "4.34.9", "@rollup/rollup-linux-loongarch64-gnu": "4.34.9", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.9", "@rollup/rollup-linux-riscv64-gnu": "4.34.9", "@rollup/rollup-linux-s390x-gnu": "4.34.9", "@rollup/rollup-linux-x64-gnu": "4.34.9", "@rollup/rollup-linux-x64-musl": "4.34.9", "@rollup/rollup-win32-arm64-msvc": "4.34.9", "@rollup/rollup-win32-ia32-msvc": "4.34.9", "@rollup/rollup-win32-x64-msvc": "4.34.9", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ=="],
|
"rollup": ["rollup@4.34.9", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.9", "@rollup/rollup-android-arm64": "4.34.9", "@rollup/rollup-darwin-arm64": "4.34.9", "@rollup/rollup-darwin-x64": "4.34.9", "@rollup/rollup-freebsd-arm64": "4.34.9", "@rollup/rollup-freebsd-x64": "4.34.9", "@rollup/rollup-linux-arm-gnueabihf": "4.34.9", "@rollup/rollup-linux-arm-musleabihf": "4.34.9", "@rollup/rollup-linux-arm64-gnu": "4.34.9", "@rollup/rollup-linux-arm64-musl": "4.34.9", "@rollup/rollup-linux-loongarch64-gnu": "4.34.9", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.9", "@rollup/rollup-linux-riscv64-gnu": "4.34.9", "@rollup/rollup-linux-s390x-gnu": "4.34.9", "@rollup/rollup-linux-x64-gnu": "4.34.9", "@rollup/rollup-linux-x64-musl": "4.34.9", "@rollup/rollup-win32-arm64-msvc": "4.34.9", "@rollup/rollup-win32-ia32-msvc": "4.34.9", "@rollup/rollup-win32-x64-msvc": "4.34.9", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ=="],
|
||||||
|
|
||||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||||
|
@ -718,6 +728,8 @@
|
||||||
|
|
||||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||||
|
|
||||||
|
"utf8-buffer": ["utf8-buffer@1.0.0", "", {}, "sha512-ueuhzvWnp5JU5CiGSY4WdKbiN/PO2AZ/lpeLiz2l38qwdLy/cW40XobgyuIWucNyum0B33bVB0owjFCeGBSLqg=="],
|
||||||
|
|
||||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||||
|
|
||||||
"vite": ["vite@5.4.14", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA=="],
|
"vite": ["vite@5.4.14", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA=="],
|
||||||
|
@ -764,6 +776,8 @@
|
||||||
|
|
||||||
"postcss-load-config/lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
|
"postcss-load-config/lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
|
||||||
|
|
||||||
|
"riff-file/byte-data": ["byte-data@18.1.1", "", { "dependencies": { "endianness": "^8.0.2", "ieee754-buffer": "^2.0.0", "utf8-buffer": "^1.0.0" } }, "sha512-Kv/B0r7adgnCcrs/y703sac2XFLdHW5kPfis1j8+Ij/hmEcWhBKf+1pNTv+vsNqXb207Uiyri8bpnogNxR/4Lg=="],
|
||||||
|
|
||||||
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
|
@ -41,11 +41,13 @@
|
||||||
"@fontsource/lexend": "^5.1.2",
|
"@fontsource/lexend": "^5.1.2",
|
||||||
"@fontsource/radio-canada-big": "^5.1.1",
|
"@fontsource/radio-canada-big": "^5.1.1",
|
||||||
"@imagemagick/magick-wasm": "^0.0.34",
|
"@imagemagick/magick-wasm": "^0.0.34",
|
||||||
|
"byte-data": "^19.0.1",
|
||||||
"client-zip": "^2.4.6",
|
"client-zip": "^2.4.6",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-svelte": "^0.475.0",
|
"lucide-svelte": "^0.475.0",
|
||||||
"music-metadata": "^11.0.0",
|
"music-metadata": "^11.0.0",
|
||||||
"p-queue": "^8.1.0",
|
"p-queue": "^8.1.0",
|
||||||
|
"riff-file": "^1.0.3",
|
||||||
"vite-plugin-static-copy": "^2.2.0",
|
"vite-plugin-static-copy": "^2.2.0",
|
||||||
"wasm-vips": "^0.0.11"
|
"wasm-vips": "^0.0.11"
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,27 +15,35 @@ export class VipsConverter extends Converter {
|
||||||
private id = 0;
|
private id = 0;
|
||||||
public name = "libvips";
|
public name = "libvips";
|
||||||
public ready = $state(false);
|
public ready = $state(false);
|
||||||
public supportedFormats = [
|
public static supportedFormatsStatic = [
|
||||||
".png",
|
...new Set([
|
||||||
".jpeg",
|
".png",
|
||||||
".jpg",
|
".jpeg",
|
||||||
".webp",
|
".jpg",
|
||||||
".gif",
|
".webp",
|
||||||
".hdr",
|
".gif",
|
||||||
".jpe",
|
".ico",
|
||||||
".dng",
|
".cur",
|
||||||
".mat",
|
".ani",
|
||||||
".pbm",
|
".heic",
|
||||||
".pfm",
|
".hdr",
|
||||||
".pgm",
|
".jpe",
|
||||||
".pnm",
|
".dng",
|
||||||
".ppm",
|
".mat",
|
||||||
".raw",
|
".pbm",
|
||||||
".tif",
|
".pfm",
|
||||||
".tiff",
|
".pgm",
|
||||||
".jfif",
|
".pnm",
|
||||||
|
".ppm",
|
||||||
|
".raw",
|
||||||
|
".tif",
|
||||||
|
".tiff",
|
||||||
|
".jfif",
|
||||||
|
]),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public supportedFormats = VipsConverter.supportedFormatsStatic;
|
||||||
|
|
||||||
public readonly reportsProgress = false;
|
public readonly reportsProgress = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -87,7 +95,7 @@ export class VipsConverter extends Converter {
|
||||||
log(["converters", this.name], `converted ${input.name} to ${to}`);
|
log(["converters", this.name], `converted ${input.name} to ${to}`);
|
||||||
return new VertFile(
|
return new VertFile(
|
||||||
new File([res.output as unknown as BlobPart], input.name),
|
new File([res.output as unknown as BlobPart], input.name),
|
||||||
to,
|
res.zip ? ".zip" : to,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
// THIS CODE IS FROM https://github.com/captbaritone/webamp/blob/15b0312cb794973a0e615d894df942452e920c36/packages/ani-cursor/src/parser.ts
|
||||||
|
// LICENSED UNDER MIT. (c) Jordan Eldredge and Webamp contributors
|
||||||
|
|
||||||
|
// this code is ripped from their project because i didn't want to
|
||||||
|
// re-invent the wheel, BUT the library they provide (ani-cursor)
|
||||||
|
// doesn't expose the internals.
|
||||||
|
|
||||||
|
import { RIFFFile } from "riff-file";
|
||||||
|
import { unpackArray, unpackString } from "byte-data";
|
||||||
|
|
||||||
|
type Chunk = {
|
||||||
|
format: string;
|
||||||
|
chunkId: string;
|
||||||
|
chunkData: {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
};
|
||||||
|
subChunks: Chunk[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://www.informit.com/articles/article.aspx?p=1189080&seqNum=3
|
||||||
|
type AniMetadata = {
|
||||||
|
cbSize: number; // Data structure size (in bytes)
|
||||||
|
nFrames: number; // Number of images (also known as frames) stored in the file
|
||||||
|
nSteps: number; // Number of frames to be displayed before the animation repeats
|
||||||
|
iWidth: number; // Width of frame (in pixels)
|
||||||
|
iHeight: number; // Height of frame (in pixels)
|
||||||
|
iBitCount: number; // Number of bits per pixel
|
||||||
|
nPlanes: number; // Number of color planes
|
||||||
|
iDispRate: number; // Default frame display rate (measured in 1/60th-of-a-second units)
|
||||||
|
bfAttributes: number; // ANI attribute bit flags
|
||||||
|
};
|
||||||
|
|
||||||
|
type ParsedAni = {
|
||||||
|
rate: number[] | null;
|
||||||
|
seq: number[] | null;
|
||||||
|
images: Uint8Array[];
|
||||||
|
metadata: AniMetadata;
|
||||||
|
artist: string | null;
|
||||||
|
title: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DWORD = { bits: 32, be: false, signed: false, fp: false };
|
||||||
|
|
||||||
|
export function parseAni(arr: Uint8Array): ParsedAni {
|
||||||
|
const riff = new RIFFFile();
|
||||||
|
|
||||||
|
riff.setSignature(arr);
|
||||||
|
|
||||||
|
const signature = riff.signature as Chunk;
|
||||||
|
if (signature.format !== "ACON") {
|
||||||
|
throw new Error(
|
||||||
|
`Expected format. Expected "ACON", got "${signature.format}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get a chunk by chunkId and transform it if it's non-null.
|
||||||
|
function mapChunk<T>(
|
||||||
|
chunkId: string,
|
||||||
|
mapper: (chunk: Chunk) => T,
|
||||||
|
): T | null {
|
||||||
|
const chunk = riff.findChunk(chunkId) as Chunk | null;
|
||||||
|
return chunk == null ? null : mapper(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readImages(chunk: Chunk, frameCount: number): Uint8Array[] {
|
||||||
|
return chunk.subChunks.slice(0, frameCount).map((c) => {
|
||||||
|
if (c.chunkId !== "icon") {
|
||||||
|
throw new Error(`Unexpected chunk type in fram: ${c.chunkId}`);
|
||||||
|
}
|
||||||
|
return arr.slice(c.chunkData.start, c.chunkData.end);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = mapChunk("anih", (c) => {
|
||||||
|
const words = unpackArray(
|
||||||
|
arr,
|
||||||
|
DWORD,
|
||||||
|
c.chunkData.start,
|
||||||
|
c.chunkData.end,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
cbSize: words[0],
|
||||||
|
nFrames: words[1],
|
||||||
|
nSteps: words[2],
|
||||||
|
iWidth: words[3],
|
||||||
|
iHeight: words[4],
|
||||||
|
iBitCount: words[5],
|
||||||
|
nPlanes: words[6],
|
||||||
|
iDispRate: words[7],
|
||||||
|
bfAttributes: words[8],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (metadata == null) {
|
||||||
|
throw new Error("Did not find anih");
|
||||||
|
}
|
||||||
|
|
||||||
|
const rate = mapChunk("rate", (c) => {
|
||||||
|
return unpackArray(arr, DWORD, c.chunkData.start, c.chunkData.end);
|
||||||
|
});
|
||||||
|
// chunkIds are always four chars, hence the trailing space.
|
||||||
|
const seq = mapChunk("seq ", (c) => {
|
||||||
|
return unpackArray(arr, DWORD, c.chunkData.start, c.chunkData.end);
|
||||||
|
});
|
||||||
|
|
||||||
|
const lists = riff.findChunk("LIST", true) as Chunk[] | null;
|
||||||
|
const imageChunk = lists?.find((c) => c.format === "fram");
|
||||||
|
if (imageChunk == null) {
|
||||||
|
throw new Error("Did not find fram LIST");
|
||||||
|
}
|
||||||
|
|
||||||
|
let images = readImages(imageChunk, metadata.nFrames);
|
||||||
|
|
||||||
|
let title = null;
|
||||||
|
let artist = null;
|
||||||
|
|
||||||
|
const infoChunk = lists?.find((c) => c.format === "INFO");
|
||||||
|
if (infoChunk != null) {
|
||||||
|
infoChunk.subChunks.forEach((c) => {
|
||||||
|
switch (c.chunkId) {
|
||||||
|
case "INAM":
|
||||||
|
title = unpackString(
|
||||||
|
arr,
|
||||||
|
c.chunkData.start,
|
||||||
|
c.chunkData.end,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "IART":
|
||||||
|
artist = unpackString(
|
||||||
|
arr,
|
||||||
|
c.chunkData.start,
|
||||||
|
c.chunkData.end,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "LIST":
|
||||||
|
// Some cursors with an artist of "Created with Take ONE 3.5 (unregisterred version)" seem to have their frames here for some reason?
|
||||||
|
if (c.format === "fram") {
|
||||||
|
images = readImages(c, metadata.nFrames);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Unexpected subchunk
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { images, rate, seq, metadata, artist, title };
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ interface ConvertMessage {
|
||||||
interface FinishedMessage {
|
interface FinishedMessage {
|
||||||
type: "finished";
|
type: "finished";
|
||||||
output: ArrayBufferLike;
|
output: ArrayBufferLike;
|
||||||
|
zip?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LoadedMessage {
|
interface LoadedMessage {
|
||||||
|
|
|
@ -3,9 +3,13 @@ import {
|
||||||
initializeImageMagick,
|
initializeImageMagick,
|
||||||
MagickFormat,
|
MagickFormat,
|
||||||
MagickImage,
|
MagickImage,
|
||||||
|
MagickImageCollection,
|
||||||
MagickReadSettings,
|
MagickReadSettings,
|
||||||
|
type IMagickImage,
|
||||||
} from "@imagemagick/magick-wasm";
|
} from "@imagemagick/magick-wasm";
|
||||||
|
import { makeZip } from "client-zip";
|
||||||
import wasm from "@imagemagick/magick-wasm/magick.wasm?url";
|
import wasm from "@imagemagick/magick-wasm/magick.wasm?url";
|
||||||
|
import { parseAni } from "$lib/parse/ani";
|
||||||
|
|
||||||
const vipsPromise = Vips({
|
const vipsPromise = Vips({
|
||||||
dynamicLibraries: [],
|
dynamicLibraries: [],
|
||||||
|
@ -13,9 +17,9 @@ const vipsPromise = Vips({
|
||||||
|
|
||||||
const magickPromise = initializeImageMagick(new URL(wasm, import.meta.url));
|
const magickPromise = initializeImageMagick(new URL(wasm, import.meta.url));
|
||||||
|
|
||||||
const magickRequiredFormats = [".dng"];
|
const magickRequiredFormats = [".dng", ".heic", ".ico", ".cur", ".ani"];
|
||||||
const unsupportedFrom: string[] = [];
|
const unsupportedFrom: string[] = [];
|
||||||
const unsupportedTo = [".dng"];
|
const unsupportedTo = [...magickRequiredFormats];
|
||||||
|
|
||||||
vipsPromise
|
vipsPromise
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -31,6 +35,7 @@ const handleMessage = async (message: any): Promise<any> => {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case "convert": {
|
case "convert": {
|
||||||
if (!message.to.startsWith(".")) message.to = `.${message.to}`;
|
if (!message.to.startsWith(".")) message.to = `.${message.to}`;
|
||||||
|
console.log(message);
|
||||||
if (unsupportedFrom.includes(message.input.from)) {
|
if (unsupportedFrom.includes(message.input.from)) {
|
||||||
return {
|
return {
|
||||||
type: "error",
|
type: "error",
|
||||||
|
@ -52,7 +57,101 @@ const handleMessage = async (message: any): Promise<any> => {
|
||||||
) {
|
) {
|
||||||
// only wait when we need to
|
// only wait when we need to
|
||||||
await magickPromise;
|
await magickPromise;
|
||||||
const magick = MagickImage.create(
|
|
||||||
|
// special ico handling to split them all into separate images
|
||||||
|
if (message.input.from === ".ico") {
|
||||||
|
const imgs = MagickImageCollection.create();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
const img = MagickImage.create(
|
||||||
|
new Uint8Array(buffer),
|
||||||
|
new MagickReadSettings({
|
||||||
|
format: MagickFormat.Ico,
|
||||||
|
frameIndex: imgs.length,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
imgs.push(img);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
} catch (_) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imgs.length === 0) {
|
||||||
|
return {
|
||||||
|
type: "error",
|
||||||
|
error: `Failed to read ICO -- no images found inside?`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const convertedImgs: Uint8Array[] = [];
|
||||||
|
await Promise.all(
|
||||||
|
imgs.map(async (img, i) => {
|
||||||
|
const output = await magickConvert(img, message.to);
|
||||||
|
convertedImgs[i] = output;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const zip = makeZip(
|
||||||
|
convertedImgs.map(
|
||||||
|
(img, i) =>
|
||||||
|
new File(
|
||||||
|
[img],
|
||||||
|
`image${i}.${message.to.slice(1)}`,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"images.zip",
|
||||||
|
);
|
||||||
|
|
||||||
|
// read the ReadableStream to the end
|
||||||
|
const zipBytes = await readToEnd(zip.getReader());
|
||||||
|
|
||||||
|
imgs.dispose();
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "finished",
|
||||||
|
output: zipBytes,
|
||||||
|
zip: true,
|
||||||
|
};
|
||||||
|
} else if (message.input.from === ".ani") {
|
||||||
|
console.log("Parsing ANI file");
|
||||||
|
try {
|
||||||
|
const parsedAni = parseAni(new Uint8Array(buffer));
|
||||||
|
const files: File[] = [];
|
||||||
|
await Promise.all(
|
||||||
|
parsedAni.images.map(async (img, i) => {
|
||||||
|
const blob = await magickConvert(
|
||||||
|
MagickImage.create(
|
||||||
|
img,
|
||||||
|
new MagickReadSettings({
|
||||||
|
format: MagickFormat.Ico,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
message.to,
|
||||||
|
);
|
||||||
|
files.push(
|
||||||
|
new File([blob], `image${i}${message.to}`),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const zip = makeZip(files, "images.zip");
|
||||||
|
const zipBytes = await readToEnd(zip.getReader());
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "finished",
|
||||||
|
output: zipBytes,
|
||||||
|
zip: true,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(message.input.from);
|
||||||
|
|
||||||
|
const img = MagickImage.create(
|
||||||
new Uint8Array(buffer),
|
new Uint8Array(buffer),
|
||||||
new MagickReadSettings({
|
new MagickReadSettings({
|
||||||
format: message.input.from
|
format: message.input.from
|
||||||
|
@ -61,17 +160,14 @@ const handleMessage = async (message: any): Promise<any> => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const dngBuffer = await new Promise<Uint8Array>((resolve) =>
|
const converted = await magickConvert(img, message.to);
|
||||||
magick.write(message.to.slice(1).toUpperCase(), (data) => {
|
|
||||||
resolve(data);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "finished",
|
type: "finished",
|
||||||
output: dngBuffer,
|
output: converted,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let image = vips.Image.newFromBuffer(buffer, "");
|
let image = vips.Image.newFromBuffer(buffer, "");
|
||||||
|
|
||||||
// check if animated image & keep it animated when converting
|
// check if animated image & keep it animated when converting
|
||||||
|
@ -94,6 +190,67 @@ const handleMessage = async (message: any): Promise<any> => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const readToEnd = async (reader: ReadableStreamDefaultReader<Uint8Array>) => {
|
||||||
|
const chunks: Uint8Array[] = [];
|
||||||
|
let done = false;
|
||||||
|
while (!done) {
|
||||||
|
const { value, done: d } = await reader.read();
|
||||||
|
if (value) chunks.push(value);
|
||||||
|
done = d;
|
||||||
|
}
|
||||||
|
const blob = new Blob(chunks, { type: "application/zip" });
|
||||||
|
const arrayBuffer = await blob.arrayBuffer();
|
||||||
|
return new Uint8Array(arrayBuffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
const magickToBlob = async (img: IMagickImage): Promise<Blob> => {
|
||||||
|
const canvas = new OffscreenCanvas(img.width, img.height);
|
||||||
|
return new Promise<Blob>((resolve, reject) =>
|
||||||
|
img.getPixels(async (p) => {
|
||||||
|
const area = p.getArea(0, 0, img.width, img.height);
|
||||||
|
const chunkSize = img.hasAlpha ? 4 : 3;
|
||||||
|
const chunks = Math.ceil(area.length / chunkSize);
|
||||||
|
const data = new Uint8ClampedArray(chunks * 4);
|
||||||
|
|
||||||
|
for (let j = 0, k = 0; j < area.length; j += chunkSize, k += 4) {
|
||||||
|
data[k] = area[j];
|
||||||
|
data[k + 1] = area[j + 1];
|
||||||
|
data[k + 2] = area[j + 2];
|
||||||
|
data[k + 3] = img.hasAlpha ? area[j + 3] : 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
if (!ctx) {
|
||||||
|
reject(new Error("Failed to get canvas context"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.putImageData(new ImageData(data, img.width, img.height), 0, 0);
|
||||||
|
|
||||||
|
const blob = await canvas.convertToBlob({
|
||||||
|
type: "image/png",
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(blob);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const magickConvert = async (img: IMagickImage, to: string) => {
|
||||||
|
const vips = await vipsPromise;
|
||||||
|
|
||||||
|
const intermediary = await magickToBlob(img);
|
||||||
|
const buf = await intermediary.arrayBuffer();
|
||||||
|
|
||||||
|
const imgVips = vips.Image.newFromBuffer(buf);
|
||||||
|
const output = imgVips.writeToBuffer(to);
|
||||||
|
|
||||||
|
imgVips.delete();
|
||||||
|
img.dispose();
|
||||||
|
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
onmessage = async (e) => {
|
onmessage = async (e) => {
|
||||||
const message = e.data;
|
const message = e.data;
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in New Issue