feat(gemini-ops): 新增图片去水印功能,支持文件和 base64 数据处理
This commit is contained in:
531
package-lock.json
generated
531
package-lock.json
generated
@@ -12,7 +12,18 @@
|
|||||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||||
"puppeteer-core": "^24.39.1",
|
"puppeteer-core": "^24.39.1",
|
||||||
"puppeteer-extra": "^3.3.6",
|
"puppeteer-extra": "^3.3.6",
|
||||||
"puppeteer-extra-plugin-stealth": "^2.11.2"
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||||
|
"sharp": "^0.34.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emnapi/runtime": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@emnapi/runtime/-/runtime-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@hono/node-server": {
|
"node_modules/@hono/node-server": {
|
||||||
@@ -27,6 +38,471 @@
|
|||||||
"hono": "^4"
|
"hono": "^4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@img/colour": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/colour/-/colour-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-darwin-arm64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-darwin-arm64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-darwin-x64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-darwin-x64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-riscv64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-arm": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-arm": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-arm64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-arm64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-ppc64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-ppc64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-riscv64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-riscv64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-s390x": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-s390x": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-x64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-x64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-wasm32": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
|
||||||
|
"cpu": [
|
||||||
|
"wasm32"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/runtime": "^1.7.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-win32-arm64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-win32-ia32": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-win32-x64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@modelcontextprotocol/sdk": {
|
"node_modules/@modelcontextprotocol/sdk": {
|
||||||
"version": "1.27.1",
|
"version": "1.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz",
|
||||||
@@ -622,6 +1098,15 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/detect-libc": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/devtools-protocol": {
|
"node_modules/devtools-protocol": {
|
||||||
"version": "0.0.1581282",
|
"version": "0.0.1581282",
|
||||||
"resolved": "https://mirrors.cloud.tencent.com/npm/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz",
|
"resolved": "https://mirrors.cloud.tencent.com/npm/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz",
|
||||||
@@ -2043,6 +2528,50 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sharp": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://mirrors.cloud.tencent.com/npm/sharp/-/sharp-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@img/colour": "^1.0.0",
|
||||||
|
"detect-libc": "^2.1.2",
|
||||||
|
"semver": "^7.7.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-darwin-arm64": "0.34.5",
|
||||||
|
"@img/sharp-darwin-x64": "0.34.5",
|
||||||
|
"@img/sharp-libvips-darwin-arm64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-darwin-x64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-arm": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-arm64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-ppc64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-riscv64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-s390x": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-x64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
|
||||||
|
"@img/sharp-linux-arm": "0.34.5",
|
||||||
|
"@img/sharp-linux-arm64": "0.34.5",
|
||||||
|
"@img/sharp-linux-ppc64": "0.34.5",
|
||||||
|
"@img/sharp-linux-riscv64": "0.34.5",
|
||||||
|
"@img/sharp-linux-s390x": "0.34.5",
|
||||||
|
"@img/sharp-linux-x64": "0.34.5",
|
||||||
|
"@img/sharp-linuxmusl-arm64": "0.34.5",
|
||||||
|
"@img/sharp-linuxmusl-x64": "0.34.5",
|
||||||
|
"@img/sharp-wasm32": "0.34.5",
|
||||||
|
"@img/sharp-win32-arm64": "0.34.5",
|
||||||
|
"@img/sharp-win32-ia32": "0.34.5",
|
||||||
|
"@img/sharp-win32-x64": "0.34.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||||
"puppeteer-core": "^24.39.1",
|
"puppeteer-core": "^24.39.1",
|
||||||
"puppeteer-extra": "^3.3.6",
|
"puppeteer-extra": "^3.3.6",
|
||||||
"puppeteer-extra-plugin-stealth": "^2.11.2"
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||||
|
"sharp": "^0.34.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src/assets/bg_48.png
Normal file
BIN
src/assets/bg_48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/assets/bg_96.png
Normal file
BIN
src/assets/bg_96.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
@@ -9,6 +9,7 @@ import { createOperator } from './operator.js';
|
|||||||
import { sleep } from './util.js';
|
import { sleep } from './util.js';
|
||||||
import config from './config.js';
|
import config from './config.js';
|
||||||
import { mkdirSync } from 'node:fs';
|
import { mkdirSync } from 'node:fs';
|
||||||
|
import { removeWatermarkFromFile, removeWatermarkFromDataUrl } from './watermark-remover.js';
|
||||||
|
|
||||||
// ── Gemini 页面元素选择器 ──
|
// ── Gemini 页面元素选择器 ──
|
||||||
const SELECTORS = {
|
const SELECTORS = {
|
||||||
@@ -658,6 +659,18 @@ export function createOps(page) {
|
|||||||
const dataUrl = `data:${mime};base64,${base64Full}`;
|
const dataUrl = `data:${mime};base64,${base64Full}`;
|
||||||
|
|
||||||
console.log(`[extractImageBase64] ✅ CDP 提取成功 (mime=${mime}, size=${(base64Full.length * 0.75 / 1024).toFixed(1)}KB)`);
|
console.log(`[extractImageBase64] ✅ CDP 提取成功 (mime=${mime}, size=${(base64Full.length * 0.75 / 1024).toFixed(1)}KB)`);
|
||||||
|
|
||||||
|
// 去水印处理
|
||||||
|
const wmResult = await removeWatermarkFromDataUrl(dataUrl);
|
||||||
|
if (wmResult.ok && !wmResult.skipped) {
|
||||||
|
console.log(`[extractImageBase64] 🍌 水印已移除 (${wmResult.width}×${wmResult.height}, logo=${wmResult.logoSize}px)`);
|
||||||
|
return { ok: true, dataUrl: wmResult.dataUrl, method: 'cdp' };
|
||||||
|
} else if (wmResult.skipped) {
|
||||||
|
console.log(`[extractImageBase64] 跳过去水印: ${wmResult.reason}`);
|
||||||
|
} else {
|
||||||
|
console.warn(`[extractImageBase64] 去水印失败(不影响提取结果): ${wmResult.error}`);
|
||||||
|
}
|
||||||
|
|
||||||
return { ok: true, dataUrl, method: 'cdp' };
|
return { ok: true, dataUrl, method: 'cdp' };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errMsg = err.message || String(err);
|
const errMsg = err.message || String(err);
|
||||||
@@ -833,6 +846,16 @@ export function createOps(page) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 去水印处理
|
||||||
|
const wmResult = await removeWatermarkFromFile(filePath);
|
||||||
|
if (wmResult.ok && !wmResult.skipped) {
|
||||||
|
console.log(`[ops] 水印已移除 (${wmResult.width}×${wmResult.height}, logo=${wmResult.logoSize}px)`);
|
||||||
|
} else if (wmResult.skipped) {
|
||||||
|
console.log(`[ops] 跳过去水印: ${wmResult.reason}`);
|
||||||
|
} else {
|
||||||
|
console.warn(`[ops] 去水印失败(不影响下载结果): ${wmResult.error}`);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
filePath,
|
filePath,
|
||||||
|
|||||||
287
src/watermark-remover.js
Normal file
287
src/watermark-remover.js
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
/**
|
||||||
|
* watermark-remover.js — Gemini 图片水印移除
|
||||||
|
*
|
||||||
|
* 基于反向 Alpha 混合算法,精确还原被 Gemini 添加水印的图片。
|
||||||
|
*
|
||||||
|
* 算法移植自 gemini-watermark-remover(by journey-ad / Jad)
|
||||||
|
* 原始仓库:https://github.com/journey-ad/gemini-watermark-remover
|
||||||
|
* 许可证:MIT - Copyright (c) 2025 Jad
|
||||||
|
*
|
||||||
|
* 原理:
|
||||||
|
* Gemini 水印叠加公式: watermarked = α × 255 + (1 - α) × original
|
||||||
|
* 反向求解: original = (watermarked - α × 255) / (1 - α)
|
||||||
|
*/
|
||||||
|
import sharp from 'sharp';
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { join, dirname } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
// ── 常量 ──
|
||||||
|
const ALPHA_THRESHOLD = 0.002; // 忽略极小的 alpha 值(噪声)
|
||||||
|
const MAX_ALPHA = 0.99; // 避免除以接近零的值
|
||||||
|
const LOGO_VALUE = 255; // 白色水印的颜色值
|
||||||
|
|
||||||
|
// ── Alpha Map 缓存 ──
|
||||||
|
const alphaMapCache = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从水印背景捕获图中计算 Alpha Map
|
||||||
|
* @param {Buffer} pngBuffer - 水印背景捕获图的 PNG 数据
|
||||||
|
* @param {number} size - 水印尺寸(48 或 96)
|
||||||
|
* @returns {Promise<Float32Array>} alpha 值数组(0.0 ~ 1.0)
|
||||||
|
*/
|
||||||
|
async function calculateAlphaMap(pngBuffer, size) {
|
||||||
|
const { data, info } = await sharp(pngBuffer)
|
||||||
|
.resize(size, size)
|
||||||
|
.raw()
|
||||||
|
.ensureAlpha()
|
||||||
|
.toBuffer({ resolveWithObject: true });
|
||||||
|
|
||||||
|
const pixelCount = info.width * info.height;
|
||||||
|
const alphaMap = new Float32Array(pixelCount);
|
||||||
|
const channels = info.channels; // 4 (RGBA)
|
||||||
|
|
||||||
|
for (let i = 0; i < pixelCount; i++) {
|
||||||
|
const idx = i * channels;
|
||||||
|
const r = data[idx];
|
||||||
|
const g = data[idx + 1];
|
||||||
|
const b = data[idx + 2];
|
||||||
|
// 取 RGB 三通道最大值归一化
|
||||||
|
alphaMap[i] = Math.max(r, g, b) / 255.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return alphaMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定尺寸的 Alpha Map(带缓存)
|
||||||
|
* @param {number} size - 48 或 96
|
||||||
|
* @returns {Promise<Float32Array>}
|
||||||
|
*/
|
||||||
|
async function getAlphaMap(size) {
|
||||||
|
if (alphaMapCache[size]) return alphaMapCache[size];
|
||||||
|
|
||||||
|
const bgFile = size === 48 ? 'bg_48.png' : 'bg_96.png';
|
||||||
|
const bgPath = join(__dirname, 'assets', bgFile);
|
||||||
|
const bgBuffer = readFileSync(bgPath);
|
||||||
|
|
||||||
|
const alphaMap = await calculateAlphaMap(bgBuffer, size);
|
||||||
|
alphaMapCache[size] = alphaMap;
|
||||||
|
return alphaMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据图片尺寸检测水印配置
|
||||||
|
* @param {number} width - 图片宽度
|
||||||
|
* @param {number} height - 图片高度
|
||||||
|
* @returns {{ logoSize: number, marginRight: number, marginBottom: number }}
|
||||||
|
*/
|
||||||
|
function detectWatermarkConfig(width, height) {
|
||||||
|
// Gemini 规则:宽高都 > 1024 用 96×96,否则用 48×48
|
||||||
|
if (width > 1024 && height > 1024) {
|
||||||
|
return { logoSize: 96, marginRight: 64, marginBottom: 64 };
|
||||||
|
}
|
||||||
|
return { logoSize: 48, marginRight: 32, marginBottom: 32 };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算水印在图片中的位置(固定右下角)
|
||||||
|
* @param {number} imgWidth
|
||||||
|
* @param {number} imgHeight
|
||||||
|
* @param {{ logoSize: number, marginRight: number, marginBottom: number }} config
|
||||||
|
* @returns {{ x: number, y: number, width: number, height: number }}
|
||||||
|
*/
|
||||||
|
function calculateWatermarkPosition(imgWidth, imgHeight, config) {
|
||||||
|
const { logoSize, marginRight, marginBottom } = config;
|
||||||
|
return {
|
||||||
|
x: imgWidth - marginRight - logoSize,
|
||||||
|
y: imgHeight - marginBottom - logoSize,
|
||||||
|
width: logoSize,
|
||||||
|
height: logoSize,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对原始像素数据执行反向 Alpha 混合,移除水印
|
||||||
|
*
|
||||||
|
* @param {Buffer} pixels - RGBA 原始像素 Buffer(会被原地修改)
|
||||||
|
* @param {number} imgWidth - 图片宽度
|
||||||
|
* @param {Float32Array} alphaMap - Alpha 通道数据
|
||||||
|
* @param {{ x: number, y: number, width: number, height: number }} position - 水印位置
|
||||||
|
*/
|
||||||
|
function removeWatermarkPixels(pixels, imgWidth, alphaMap, position) {
|
||||||
|
const { x, y, width, height } = position;
|
||||||
|
|
||||||
|
for (let row = 0; row < height; row++) {
|
||||||
|
for (let col = 0; col < width; col++) {
|
||||||
|
const imgIdx = ((y + row) * imgWidth + (x + col)) * 4;
|
||||||
|
const alphaIdx = row * width + col;
|
||||||
|
|
||||||
|
let alpha = alphaMap[alphaIdx];
|
||||||
|
|
||||||
|
// 跳过噪声
|
||||||
|
if (alpha < ALPHA_THRESHOLD) continue;
|
||||||
|
|
||||||
|
// 限制 alpha 避免除零
|
||||||
|
alpha = Math.min(alpha, MAX_ALPHA);
|
||||||
|
const oneMinusAlpha = 1.0 - alpha;
|
||||||
|
|
||||||
|
// 对 R / G / B 三通道分别反向混合
|
||||||
|
for (let c = 0; c < 3; c++) {
|
||||||
|
const watermarked = pixels[imgIdx + c];
|
||||||
|
const original = (watermarked - alpha * LOGO_VALUE) / oneMinusAlpha;
|
||||||
|
pixels[imgIdx + c] = Math.max(0, Math.min(255, Math.round(original)));
|
||||||
|
}
|
||||||
|
// Alpha 通道不动
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除图片文件中的 Gemini 水印并覆盖保存
|
||||||
|
*
|
||||||
|
* @param {string} filePath - 图片文件路径(会被原地覆盖)
|
||||||
|
* @returns {Promise<{ ok: boolean, width?: number, height?: number, logoSize?: number, error?: string }>}
|
||||||
|
*/
|
||||||
|
export async function removeWatermarkFromFile(filePath) {
|
||||||
|
try {
|
||||||
|
console.log(`[watermark-remover] 开始处理: ${filePath}`);
|
||||||
|
|
||||||
|
// 1. 读取图片原始像素
|
||||||
|
const image = sharp(filePath);
|
||||||
|
const metadata = await image.metadata();
|
||||||
|
const { width, height } = metadata;
|
||||||
|
|
||||||
|
if (!width || !height) {
|
||||||
|
return { ok: false, error: 'invalid_image_metadata' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检测水印配置
|
||||||
|
const config = detectWatermarkConfig(width, height);
|
||||||
|
const position = calculateWatermarkPosition(width, height, config);
|
||||||
|
|
||||||
|
// 校验水印位置合法性
|
||||||
|
if (position.x < 0 || position.y < 0) {
|
||||||
|
console.log(`[watermark-remover] 图片太小(${width}×${height}),跳过去水印`);
|
||||||
|
return { ok: true, width, height, skipped: true, reason: 'image_too_small' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 获取 Alpha Map
|
||||||
|
const alphaMap = await getAlphaMap(config.logoSize);
|
||||||
|
|
||||||
|
// 4. 提取原始像素、执行反向混合
|
||||||
|
const { data: pixels, info } = await sharp(filePath)
|
||||||
|
.raw()
|
||||||
|
.ensureAlpha()
|
||||||
|
.toBuffer({ resolveWithObject: true });
|
||||||
|
|
||||||
|
removeWatermarkPixels(pixels, info.width, alphaMap, position);
|
||||||
|
|
||||||
|
// 5. 写回文件(保持原格式)
|
||||||
|
const ext = (filePath.match(/\.(\w+)$/)?.[1] || 'png').toLowerCase();
|
||||||
|
let outputPipeline = sharp(pixels, {
|
||||||
|
raw: { width: info.width, height: info.height, channels: info.channels },
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (ext) {
|
||||||
|
case 'jpg':
|
||||||
|
case 'jpeg':
|
||||||
|
outputPipeline = outputPipeline.jpeg({ quality: 95 });
|
||||||
|
break;
|
||||||
|
case 'webp':
|
||||||
|
outputPipeline = outputPipeline.webp({ quality: 95 });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
outputPipeline = outputPipeline.png();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await outputPipeline.toFile(filePath);
|
||||||
|
|
||||||
|
console.log(`[watermark-remover] ✅ 去水印完成: ${width}×${height}, logo=${config.logoSize}px`);
|
||||||
|
return { ok: true, width, height, logoSize: config.logoSize };
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[watermark-remover] ❌ 去水印失败: ${err.message}`);
|
||||||
|
return { ok: false, error: err.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除 base64 图片数据中的 Gemini 水印
|
||||||
|
*
|
||||||
|
* @param {string} dataUrl - data:image/xxx;base64,... 格式的图片
|
||||||
|
* @returns {Promise<{ ok: boolean, dataUrl?: string, width?: number, height?: number, logoSize?: number, error?: string }>}
|
||||||
|
*/
|
||||||
|
export async function removeWatermarkFromDataUrl(dataUrl) {
|
||||||
|
try {
|
||||||
|
console.log('[watermark-remover] 开始处理 base64 图片');
|
||||||
|
|
||||||
|
// 1. 解析 dataUrl
|
||||||
|
const mimeMatch = dataUrl.match(/^data:(image\/\w+);base64,/);
|
||||||
|
if (!mimeMatch) {
|
||||||
|
return { ok: false, error: 'invalid_data_url' };
|
||||||
|
}
|
||||||
|
const mime = mimeMatch[1];
|
||||||
|
const base64Data = dataUrl.slice(mimeMatch[0].length);
|
||||||
|
const inputBuffer = Buffer.from(base64Data, 'base64');
|
||||||
|
|
||||||
|
// 2. 读取图片信息
|
||||||
|
const metadata = await sharp(inputBuffer).metadata();
|
||||||
|
const { width, height } = metadata;
|
||||||
|
|
||||||
|
if (!width || !height) {
|
||||||
|
return { ok: false, error: 'invalid_image_metadata' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检测水印配置
|
||||||
|
const config = detectWatermarkConfig(width, height);
|
||||||
|
const position = calculateWatermarkPosition(width, height, config);
|
||||||
|
|
||||||
|
if (position.x < 0 || position.y < 0) {
|
||||||
|
console.log(`[watermark-remover] 图片太小(${width}×${height}),跳过去水印`);
|
||||||
|
return { ok: true, dataUrl, width, height, skipped: true, reason: 'image_too_small' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 获取 Alpha Map
|
||||||
|
const alphaMap = await getAlphaMap(config.logoSize);
|
||||||
|
|
||||||
|
// 5. 提取像素、反向混合
|
||||||
|
const { data: pixels, info } = await sharp(inputBuffer)
|
||||||
|
.raw()
|
||||||
|
.ensureAlpha()
|
||||||
|
.toBuffer({ resolveWithObject: true });
|
||||||
|
|
||||||
|
removeWatermarkPixels(pixels, info.width, alphaMap, position);
|
||||||
|
|
||||||
|
// 6. 编码回原格式
|
||||||
|
const ext = mime.split('/')[1];
|
||||||
|
let outputPipeline = sharp(pixels, {
|
||||||
|
raw: { width: info.width, height: info.height, channels: info.channels },
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (ext) {
|
||||||
|
case 'jpeg':
|
||||||
|
case 'jpg':
|
||||||
|
outputPipeline = outputPipeline.jpeg({ quality: 95 });
|
||||||
|
break;
|
||||||
|
case 'webp':
|
||||||
|
outputPipeline = outputPipeline.webp({ quality: 95 });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
outputPipeline = outputPipeline.png();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputBuffer = await outputPipeline.toBuffer();
|
||||||
|
const outputBase64 = outputBuffer.toString('base64');
|
||||||
|
const outputDataUrl = `data:${mime};base64,${outputBase64}`;
|
||||||
|
|
||||||
|
console.log(`[watermark-remover] ✅ base64 去水印完成: ${width}×${height}, logo=${config.logoSize}px`);
|
||||||
|
return { ok: true, dataUrl: outputDataUrl, width, height, logoSize: config.logoSize };
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[watermark-remover] ❌ base64 去水印失败: ${err.message}`);
|
||||||
|
return { ok: false, error: err.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user