From f27ec087244793428d6bb8221e1f5a4b0986be7b Mon Sep 17 00:00:00 2001 From: lq1405 <2769838458@qq.com> Date: Thu, 6 Jun 2024 13:12:04 +0800 Subject: [PATCH] =?UTF-8?q?V=202.2.7=20lama=20iopaint=20=E5=8E=BB=E6=B0=B4?= =?UTF-8?q?=E5=8D=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 + package-lock.json | 244 +++++++- package.json | 10 +- .../scripts/__pycache__/clip.cpython-310.pyc | Bin 11533 -> 11533 bytes .../iamge_to_video.cpython-310.pyc | Bin 9029 -> 9029 bytes .../__pycache__/shotSplit.cpython-310.pyc | Bin 5039 -> 5039 bytes resources/scripts/lama/install.bat | 2 + resources/scripts/lama/lama_inpaint.py | 173 ++++++ resources/scripts/lama/lama_inpaint.spec | 43 ++ resources/scripts/lama_inpaint.spec | 37 ++ src/api/apiBasic.js | 35 +- src/define/Tools/common.js | 4 + src/define/Tools/file.js | 105 ++++ src/define/Tools/image.js | 72 +++ src/define/Tools/index.js | 9 + src/define/define.js | 2 + src/define/define_string.js | 9 +- src/define/logger_define.js | 3 + src/main/IPCEvent/imageIpc.js | 17 + src/main/IPCEvent/system.js | 17 + src/main/Public/Image.js | 289 ++++++++- src/main/backPrompt/imageGenerate.js | 11 +- src/main/func.js | 2 +- src/main/generalTools.js | 20 +- src/main/index.js | 12 +- src/main/logger.js | 59 ++ src/main/quene.js | 10 +- src/main/tools.js | 10 +- src/preload/img.js | 9 + src/preload/index.js | 3 + src/preload/system.js | 11 + .../src/components/Backstep/GetFrame.vue | 400 +++++++------ .../components/Backstep/PushBackPrompt.vue | 202 ++++--- .../components/Components/ProgressDialog.vue | 50 ++ .../src/components/Original/MenuButton.vue | 2 +- .../Setting/Components/AddGptOption.vue | 550 +++++++++-------- .../src/components/Setting/SDSetting.vue | 2 +- .../src/components/Watermark/GetWaterMask.vue | 555 ++++++++++++++++++ .../components/Watermark/ImageFileSelect.vue | 55 ++ 39 files changed, 2495 insertions(+), 547 deletions(-) create mode 100644 resources/scripts/lama/install.bat create mode 100644 resources/scripts/lama/lama_inpaint.py create mode 100644 resources/scripts/lama/lama_inpaint.spec create mode 100644 resources/scripts/lama_inpaint.spec create mode 100644 src/define/Tools/common.js create mode 100644 src/define/Tools/file.js create mode 100644 src/define/Tools/image.js create mode 100644 src/define/Tools/index.js create mode 100644 src/define/logger_define.js create mode 100644 src/main/IPCEvent/system.js create mode 100644 src/main/logger.js create mode 100644 src/preload/system.js create mode 100644 src/renderer/src/components/Components/ProgressDialog.vue create mode 100644 src/renderer/src/components/Watermark/GetWaterMask.vue create mode 100644 src/renderer/src/components/Watermark/ImageFileSelect.vue diff --git a/.gitignore b/.gitignore index 225cb64..c8f44e2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,14 @@ out resources/scripts/build* resources/scripts/dist resources/scripts/model +resources/scripts/lama/model +resources/scripts/lama/lama.7z +resources/scripts/lama/_internal +resources/scripts/lama/dist +resources/scripts/lama/build +resources/scripts/lama/lama_inpaint.exe +resources/scripts/virtual py +resources/logger resources/scripts/Temp resources/image/Temp* resources/package/ffmpeg-2023* diff --git a/package-lock.json b/package-lock.json index df5b82e..83f9875 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "laitool", - "version": "2.2.6", + "version": "2.2.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "laitool", - "version": "2.2.5", + "version": "2.2.6", "hasInstallScript": true, "dependencies": { "@alicloud/alimt20181012": "^1.2.0", @@ -19,6 +19,7 @@ "7zip-min": "^1.4.4", "awesome-js": "^2.0.0", "axios": "^1.6.5", + "blob-to-buffer": "^1.2.9", "compressing": "^1.10.0", "crypto-js": "^4.2.0", "electron-store": "^9.0.0", @@ -33,13 +34,16 @@ "node-machine-id": "^1.1.12", "node-reg": "^0.2.4", "npm": "^10.7.0", + "paddle": "^1.0.0", "sharp": "^0.33.2", "systeminformation": "^5.22.10", "tencentcloud-sdk-nodejs": "^4.0.821", "uuid": "^9.0.1", "vue-router": "^4.2.5", "wav-file-info": "^0.0.10", - "winreg": "^1.2.5" + "winreg": "^1.2.5", + "winston": "^3.13.0", + "winston-daily-rotate-file": "^5.0.0" }, "devDependencies": { "@electron-toolkit/eslint-config": "^1.0.1", @@ -849,6 +853,14 @@ "node": ">=6.9.0" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@css-render/plugin-bem": { "version": "0.15.12", "license": "MIT", @@ -863,6 +875,16 @@ "vue": "^3.0.11" } }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@develar/schema-utils": { "version": "2.6.5", "dev": true, @@ -2201,6 +2223,11 @@ "@types/node": "*" } }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, "node_modules/@types/xml2js": { "version": "0.4.14", "license": "MIT", @@ -3036,6 +3063,25 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/blob-to-buffer": { + "version": "1.2.9", + "resolved": "https://registry.npmmirror.com/blob-to-buffer/-/blob-to-buffer-1.2.9.tgz", + "integrity": "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/bluebird": { "version": "3.7.2", "dev": true, @@ -3309,8 +3355,9 @@ }, "node_modules/canvas": { "version": "2.11.2", + "resolved": "https://registry.npmmirror.com/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", "hasInstallScript": true, - "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -3429,6 +3476,37 @@ "color-support": "bin.js" } }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/colorspace/node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/colorspace/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/colorspace/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, "node_modules/combined-stream": { "version": "1.0.8", "license": "MIT", @@ -4401,6 +4479,11 @@ "devOptional": true, "license": "MIT" }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "node_modules/end-of-stream": { "version": "1.4.4", "license": "MIT", @@ -4839,6 +4922,11 @@ "pend": "^1.2.0" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "dev": true, @@ -4850,6 +4938,14 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-stream-rotator": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", + "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", + "dependencies": { + "moment": "^2.29.1" + } + }, "node_modules/file-type": { "version": "16.5.4", "license": "MIT", @@ -4931,6 +5027,11 @@ "version": "1.0.0", "license": "MIT" }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "node_modules/follow-redirects": { "version": "1.15.5", "funding": [ @@ -5926,6 +6027,11 @@ "version": "12.20.55", "license": "MIT" }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "node_modules/lazy-val": { "version": "1.0.5", "license": "MIT" @@ -6125,6 +6231,22 @@ "license": "MIT", "peer": true }, + "node_modules/logform": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/logform/-/logform-2.6.0.tgz", + "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/long": { "version": "5.2.3", "license": "Apache-2.0" @@ -6307,6 +6429,14 @@ "node": ">=10" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "license": "MIT" @@ -8927,6 +9057,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-keys": { "version": "1.1.1", "license": "MIT", @@ -8946,6 +9084,14 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/optionator": { "version": "0.9.3", "dev": true, @@ -8996,6 +9142,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/paddle": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/paddle/-/paddle-1.0.0.tgz", + "integrity": "sha512-zqzikf2FmYZZNR2QBb3+B1nYF60jJBWke9B8WknDHLViY/ergS3/rgq8eVL5W5KIZ48Lj+NVN77J56bfqJFYzA==", + "engines": { + "node": "*" + } + }, "node_modules/pako": { "version": "1.0.11", "license": "(MIT AND Zlib)" @@ -9574,6 +9728,14 @@ ], "license": "MIT" }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "license": "MIT" @@ -9867,6 +10029,14 @@ "license": "BSD-3-Clause", "optional": true }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmmirror.com/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, "node_modules/stat-mode": { "version": "1.0.0", "dev": true, @@ -10182,6 +10352,11 @@ "version": "1.13.0", "license": "0BSD" }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "node_modules/text-table": { "version": "0.2.0", "dev": true, @@ -10269,6 +10444,14 @@ "version": "0.3.11", "license": "MIT" }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", "dev": true, @@ -10673,6 +10856,57 @@ "version": "1.2.5", "license": "BSD-2-Clause" }, + "node_modules/winston": { + "version": "3.13.0", + "resolved": "https://registry.npmmirror.com/winston/-/winston-3.13.0.tgz", + "integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-daily-rotate-file": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz", + "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==", + "dependencies": { + "file-stream-rotator": "^0.6.1", + "object-hash": "^3.0.0", + "triple-beam": "^1.4.1", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "winston": "^3" + } + }, + "node_modules/winston-transport": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/winston-transport/-/winston-transport-4.7.0.tgz", + "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "dev": true, @@ -10886,4 +11120,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 21cd043..a95cbe8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "laitool", - "version": "2.2.6", + "version": "2.2.7", "description": "An Electron application with Vue", "main": "./out/main/index.js", "author": "example.com", @@ -27,6 +27,7 @@ "7zip-min": "^1.4.4", "awesome-js": "^2.0.0", "axios": "^1.6.5", + "blob-to-buffer": "^1.2.9", "compressing": "^1.10.0", "crypto-js": "^4.2.0", "electron-store": "^9.0.0", @@ -41,13 +42,16 @@ "node-machine-id": "^1.1.12", "node-reg": "^0.2.4", "npm": "^10.7.0", + "paddle": "^1.0.0", "sharp": "^0.33.2", "systeminformation": "^5.22.10", "tencentcloud-sdk-nodejs": "^4.0.821", "uuid": "^9.0.1", "vue-router": "^4.2.5", "wav-file-info": "^0.0.10", - "winreg": "^1.2.5" + "winreg": "^1.2.5", + "winston": "^3.13.0", + "winston-daily-rotate-file": "^5.0.0" }, "devDependencies": { "@electron-toolkit/eslint-config": "^1.0.1", @@ -78,6 +82,8 @@ "resources/image/zhanwei.png", "resources/scripts/model/**", "resources/scripts/Lai.exe", + "resources/scripts/lama/lama_inpaint.exe", + "resources/scripts/lama/model/**", "resources/scripts/discordScript.js", "resources/tmp/**", "resources/icon.ico" diff --git a/resources/scripts/__pycache__/clip.cpython-310.pyc b/resources/scripts/__pycache__/clip.cpython-310.pyc index 5ecb3eb4917365ab9286be880b2350d0cf0e216b..e80c4de6483081420deb8ce0975b8b5cef0e6388 100644 GIT binary patch delta 19 YcmeB;>W$*c=jG*M00P538@ZTu0Wci|L;wH) delta 19 YcmeB;>W$*c=jG*M0D^{%8@ZTu0WrV?hyVZp diff --git a/resources/scripts/__pycache__/iamge_to_video.cpython-310.pyc b/resources/scripts/__pycache__/iamge_to_video.cpython-310.pyc index 8ff1a2d9a7416db82688ffb3fa2f715fcfecf8f3..22ed7a5649a0cafa8b87ef95e1f0b9d49b6934ff 100644 GIT binary patch delta 19 ZcmX@=cGQh4pO=@50SLNwZ{)I81^_qA1rh)N delta 19 ZcmX@=cGQh4pO=@50SH!X+{k6E3;;Mz1ttIh diff --git a/resources/scripts/__pycache__/shotSplit.cpython-310.pyc b/resources/scripts/__pycache__/shotSplit.cpython-310.pyc index c318cf3810750a1df0e03af194cbd72731290d19..22fa7951b901eecf40d1beb5940f2686971104ff 100644 GIT binary patch delta 20 acmZ3lzFwU>pO=@50SK1Pj@!t+L>K@z%>`Tl delta 20 acmZ3lzFwU>pO=@50SFSdL~i6>A`AdEX9X(& diff --git a/resources/scripts/lama/install.bat b/resources/scripts/lama/install.bat new file mode 100644 index 0000000..1b71356 --- /dev/null +++ b/resources/scripts/lama/install.bat @@ -0,0 +1,2 @@ +@echo off +pyinstaller -F --upx-dir="C:\\Users\\27698\\Desktop\\upx-4.2.4-win64\upx.exe" lama_inpaint.py \ No newline at end of file diff --git a/resources/scripts/lama/lama_inpaint.py b/resources/scripts/lama/lama_inpaint.py new file mode 100644 index 0000000..130f273 --- /dev/null +++ b/resources/scripts/lama/lama_inpaint.py @@ -0,0 +1,173 @@ +import io +import os +import sys +from typing import Union +import cv2 +import torch +import numpy as np +from PIL import Image + + +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + +# 判断sys.argv 的长度,如果小于2,说明没有传入参数,设置初始参数 +# if len(sys.argv) < 2: +# sys.argv = [ +# "C:/Users/27698/Desktop/LAITool/resources/scripts/lama/lama_inpaint.exe", +# "-l", +# "C:\\Users\\27698\\Desktop\\测试\\mjTest\\data\\mask\\temp\\1717508661218.png", +# "C:\\Users\\27698\\Desktop\\测试\\mjTest\\data\\mask\\mask_temp_1717508662659.png", +# "C:\\Users\\27698\\Desktop\\测试\\mjTest\\data\\mask\\temp\\1717508564042.png", +# ] +print(sys.argv) + +if getattr(sys, "frozen", False): + cript_directory = os.path.dirname(sys.executable) +elif __file__: + cript_directory = os.path.dirname(__file__) + +link_name = os.path.join(os.path.expanduser("~"), "big_lama.pt") +cu_name = os.path.join(cript_directory, "model\\big-lama.pt") +mode_pa = link_name + +if len(sys.argv) < 2: + # # 判断model_path是否存在,如果不存在,设置默认值 + if not os.path.exists(link_name): + os.system(f'mklink "{link_name}" "{cu_name}"') + print("Params: ") + sys.exit(0) + + +def get_image(image): + if isinstance(image, Image.Image): + img = np.array(image) + elif isinstance(image, np.ndarray): + img = image.copy() + else: + raise Exception("Input image should be either PIL Image or numpy array!") + + if img.ndim == 3: + img = np.transpose(img, (2, 0, 1)) # chw + elif img.ndim == 2: + img = img[np.newaxis, ...] + + assert img.ndim == 3 + + img = img.astype(np.float32) / 255 + return img + + +def ceil_modulo(x, mod): + if x % mod == 0: + return x + return (x // mod + 1) * mod + + +def scale_image(img, factor, interpolation=cv2.INTER_AREA): + if img.shape[0] == 1: + img = img[0] + else: + img = np.transpose(img, (1, 2, 0)) + + img = cv2.resize(img, dsize=None, fx=factor, fy=factor, interpolation=interpolation) + + if img.ndim == 2: + img = img[None, ...] + else: + img = np.transpose(img, (2, 0, 1)) + return img + + +def pad_img_to_modulo(img, mod): + channels, height, width = img.shape + out_height = ceil_modulo(height, mod) + out_width = ceil_modulo(width, mod) + return np.pad( + img, + ((0, 0), (0, out_height - height), (0, out_width - width)), + mode="symmetric", + ) + + +def prepare_img_and_mask(image, mask, device, pad_out_to_modulo=8, scale_factor=None): + out_image = get_image(image) + out_mask = get_image(mask) + + if scale_factor is not None: + out_image = scale_image(out_image, 1) + out_mask = scale_image(out_mask, scale_factor, interpolation=cv2.INTER_NEAREST) + + if pad_out_to_modulo is not None and pad_out_to_modulo > 1: + out_image = pad_img_to_modulo(out_image, pad_out_to_modulo) + out_mask = pad_img_to_modulo(out_mask, pad_out_to_modulo) + + out_image = torch.from_numpy(out_image).unsqueeze(0).to(device) + out_mask = torch.from_numpy(out_mask).unsqueeze(0).to(device) + + out_mask = (out_mask > 0) * 1 + + return out_image, out_mask + + +class LamaInpaint: + def __init__( + self, + device, + model_path=None, + ) -> None: + if model_path is None: + model_path = os.path.join(cript_directory, "model\\big-lama.pt") + + self.model = torch.jit.load(model_path, map_location=device) + self.model.eval() + self.model.to(device) + self.device = device + + def run( + self, + image: Union[Image.Image, np.ndarray], + mask: Union[Image.Image, np.ndarray], + ): + if isinstance(image, np.ndarray): + orig_height, orig_width = image.shape[:2] + else: + orig_height, orig_width = np.array(image).shape[:2] + + # image_width = image.shape[1] + # mask_width = mask.shape[1] + scale = image.width / mask.width + image, mask = prepare_img_and_mask(image, mask, self.device, 8, scale) + with torch.inference_mode(): + inpainted = self.model(image, mask) + cur_res = inpainted[0].permute(1, 2, 0).detach().cpu().numpy() + cur_res = np.clip(cur_res * 255, 0, 255).astype("uint8") + cur_res = cur_res[:orig_height, :orig_width] + return cur_res + + +try: + de = "cpu" + if torch.cuda.is_available(): + de = "cuda" + + lama = LamaInpaint(de, mode_pa) + + image_path = sys.argv[2] + mask_path = sys.argv[3] + output_path = sys.argv[4] + + # 若是没有传递mask_path,需要自己计算mask区域 + # 使用Image.open打开图片 + image = Image.open(image_path).convert("RGB") + mask = Image.open(mask_path).convert("L") + + res = lama.run(image, mask) + # 将修复后的图片保存到本地 + img = Image.fromarray(res) + # 使用 save 方法将图像保存到文件 + img.save(output_path) + sys.exit(0) + +except Exception as e: + print(e) + sys.exit(str(e)) diff --git a/resources/scripts/lama/lama_inpaint.spec b/resources/scripts/lama/lama_inpaint.spec new file mode 100644 index 0000000..c19cb54 --- /dev/null +++ b/resources/scripts/lama/lama_inpaint.spec @@ -0,0 +1,43 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['lama_inpaint.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='lama_inpaint', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) +coll = COLLECT( + exe, + a.binaries, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='lama_inpaint', +) diff --git a/resources/scripts/lama_inpaint.spec b/resources/scripts/lama_inpaint.spec new file mode 100644 index 0000000..521ec45 --- /dev/null +++ b/resources/scripts/lama_inpaint.spec @@ -0,0 +1,37 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['lama_inpaint.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='lama_inpaint', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/src/api/apiBasic.js b/src/api/apiBasic.js index b17189f..816359e 100644 --- a/src/api/apiBasic.js +++ b/src/api/apiBasic.js @@ -51,27 +51,45 @@ let basicApi = { /** * 使用electron的net模块实现的post方法 * @param {*} url 请求的url - * @param {*} data 传输的数据(json格式) + * @param {*} data 传输的数据,,所有格式 * @param {*} headers 请求头 * @returns */ - post: (url, data = {}, headers = {}) => { + post: (url, data = {}, headers = {}, others = {}) => { return new Promise((resolve, reject) => { - const request = net.request({ + let req_obj = { method: 'POST', url: url, headers: Object.assign({ 'Content-Type': 'application/json', }, headers) - }); + } + req_obj = Object.assign(req_obj, others); + const request = net.request(req_obj); request.write(JSON.stringify(data)); request.on('response', (response) => { - let responseData = ''; + let responseData; + if (response.headers["content-type"] === "application/json") { + + responseData = ''; + } else if (response.headers["content-type"].startsWith("image/")) { + // 处理图片数据 + responseData = [] + } + else { + throw new Error("未知的请求返回数据类型"); + } response.on('data', (chunk) => { - responseData += chunk; + if (response.headers['content-type'] === 'application/json') { + // 处理 JSON 数据 + responseData += chunk; + } else if (response.headers['content-type'].startsWith('image/')) { + // 处理图片数据 + responseData.push(chunk); + } }); response.on('end', () => { @@ -83,8 +101,11 @@ let basicApi = { let parsedData; if (response.headers['content-type'].includes('application/json')) { parsedData = JSON.parse(responseData); + } else if (response.headers['content-type'].startsWith('image/')) { + // parsedData = responseData; + parsedData = Buffer.concat(responseData); } else { - parsedData = responseData; + throw new Error("未知的请求返回数据类型"); } resolve({ diff --git a/src/define/Tools/common.js b/src/define/Tools/common.js new file mode 100644 index 0000000..37777e7 --- /dev/null +++ b/src/define/Tools/common.js @@ -0,0 +1,4 @@ +import fs from 'node:fs'; + +const fspromises = fs.promises; + diff --git a/src/define/Tools/file.js b/src/define/Tools/file.js new file mode 100644 index 0000000..23d1091 --- /dev/null +++ b/src/define/Tools/file.js @@ -0,0 +1,105 @@ + +import fs from "fs" +import path from "path"; +const fspromises = fs.promises; + +/** + * 判断文件或目录是否存在 + * @param {*} path 文件或目录的路径 + * @returns true表示存在,false表示不存在 + */ +export async function CheckFileOrDirExist(path) { + try { + await fspromises.access(path); + return true; // 文件或目录存在 + } catch (error) { + return false; // 文件或目录不存在 + } +} + +/** * 判断一个文件地址是不是文件夹 + * @param {*} path 输入的文件地址 + * @returns true 是 false 不是 + */ +export async function IsDirectory(path) { + try { + const stat = await fspromises.stat(path); + return stat.isDirectory(); + } catch (error) { + throw new Error(`获取文件夹信息失败: ${path}`); + } +} +/** + * 将文件或者是文件夹备份到指定的文职 + * @param {*} source_path 源文件/文件夹地址 + * @param {*} target_path 目标文件/文件夹地址 + */ +export async function BackupFileOrFolder(source_path, target_path) { + try { + + // 判断父文件夹是否存在,不存在创建 + const parent_path = path.dirname(target_path); + if (!(await CheckFileOrDirExist(parent_path))) { + await fspromises.mkdir(parent_path, { recursive: true }); + } + + // 判断是不是文件夹 + const isDirectory = await IsDirectory(source_path); + + if (isDirectory) { + // 复制文件夹 + await fspromises.rename(source_path, target_path); + } else { + // 复制文件 + await fspromises.copyFile(source, target); + } + } catch (error) { + throw new Error(error); + } +} + + +/** + * 获取指定的文件夹下面的所有的指定的拓展名的文件 + * @param {*} folderPath 文件夹地址 + * @param {*} extensions 拓展地址 + * @returns 返回文件中指定的后缀文件地址(绝对地址) + */ +export async function GetFilesWithExtensions(folderPath, extensions) { + try { + // 判断当前是不是文件夹 + if (!(await IsDirectory(folderPath))) { + throw new Error("输入的不是有效的文件夹地址") + } + + let entries = await fspromises.readdir(folderPath, { withFileTypes: true }); + let files = []; + // 使用Promise.all来并行处理所有的stat调用 + const fileStats = await Promise.all(entries.map(async (entry) => { + const entryPath = path.join(folderPath, entry.name); + if (entry.isFile()) { + return { + name: entry.name, + path: entryPath, + isFile: true, + }; + } else { + return { + isFile: false, + }; + } + })); + + // 过滤出文件并且满足扩展名要求的文件 + files = fileStats.filter(fileStat => fileStat.isFile && extensions.includes(path.extname(fileStat.name).toLowerCase())); + + // 对files数组进行排序,基于文件名 + files.sort((a, b) => a.name.localeCompare(b.name)); + + // 返回文件名数组(完整的) + return files.map(fileStat => path.join(folderPath, fileStat.name)); + + } catch (error) { + throw new Error(error); + } +} \ No newline at end of file diff --git a/src/define/Tools/image.js b/src/define/Tools/image.js new file mode 100644 index 0000000..f88c466 --- /dev/null +++ b/src/define/Tools/image.js @@ -0,0 +1,72 @@ + +import sharp from "sharp"; +import { CheckFileOrDirExist } from "./file" + + +/** + * 将指定的图片的尺寸修改,返回修改后的图片数据(base64或buffer) + * @param {*} image_path + * @param {*} width + * @param {*} height + * @param {*} type + * @returns 返回修改后的图片数据(base64或buffer) + */ +export async function ResizeImage(image_path, width, height, type) { + try { + // 检查 type 参数 + if (type !== 'base64' && type !== 'buffer') { + throw new Error('type 参数必须是 "base64" 或 "buffer"'); + } + + // 判断是不是图片文件 + if (!image_path.match(/\.(jpg|jpeg|png)$/)) { + throw new Error("输入的文件地址不是图片文件地址"); + } + + // 判断文件是否存在 + if (!(await CheckFileOrDirExist(image_path))) { + throw new Error("文件不存在"); + } + + // 修改图片尺寸 + const image = sharp(image_path); + image.resize(width, height); + let data = await image.toBuffer(); + if (type === 'base64') { + return data.toString('base64'); + } else { + return data; + } + } catch (error) { + throw error; + } +} + +/** + * 获取指定图片文件的宽高 + * @param {*} image_path 图片文件的路径 + * @returns 返回以一个对象,包含width和height属性 + */ + +export async function GetImageSize(image_path) { + try { + // 判断文件是否存在 + if (!(await CheckFileOrDirExist(image_path))) { + throw new Error("文件不存在"); + } + + // 判断是不是图片文件 + if (!image_path.match(/\.(jpg|jpeg|png)$/)) { + throw new Error("输入的文件地址不是图片文件地址"); + } + + // 获取图片的宽高 + const metadata = await sharp(image_path).metadata(); + return { + width: metadata.width, + height: metadata.height + } + } catch (error) { + throw error; + } +} \ No newline at end of file diff --git a/src/define/Tools/index.js b/src/define/Tools/index.js new file mode 100644 index 0000000..87d8c66 --- /dev/null +++ b/src/define/Tools/index.js @@ -0,0 +1,9 @@ +import * as image from './image'; +import * as common from './common'; +import * as file from './file' + +export { + image, + common, + file +}; \ No newline at end of file diff --git a/src/define/define.js b/src/define/define.js index 7e971fc..e853d0c 100644 --- a/src/define/define.js +++ b/src/define/define.js @@ -13,6 +13,7 @@ if (!app.isPackaged) { img_base: path.join(__dirname, "../../resources/config/img_base.json"), video_config: path.join(__dirname, "../../resources/config/video_config.json"), scripts_path: path.join(__dirname, "../../resources/scripts"), + logger_path: path.join(__dirname, "../../resources/logger"), package_path: path.join(__dirname, "../../resources/package"), image_path: path.join(__dirname, "../../resources/image"), temp_sd_image: path.join(__dirname, "../../resources/image/TempSDImage"), @@ -43,6 +44,7 @@ if (!app.isPackaged) { video_config: path.join(__dirname, "../../../resources/config/video_config.json"), img_base: path.join(__dirname, "../../../resources/config/img_base.json"), scripts_path: path.join(__dirname, "../../../resources/scripts"), + logger_path: path.join(__dirname, "../../../resources/logger"), package_path: path.join(__dirname, "../../../resources/package"), discordScript: path.join(__dirname, "../../../resources/scripts/discordScript.js"), image_path: path.join(__dirname, "../../../resources/image"), diff --git a/src/define/define_string.js b/src/define/define_string.js index 6145df7..61761c9 100644 --- a/src/define/define_string.js +++ b/src/define/define_string.js @@ -180,6 +180,13 @@ export const DEFINE_STRING = { OPEN_DISCORD_WINDOW: "OPEN_DISCORD_WINDOW" }, IMG: { - ONE_SPLIT_FOUR: "ONE_SPLIT_FOUR" + ONE_SPLIT_FOUR: "ONE_SPLIT_FOUR", + BASE64_TO_FILE: "BASE64_TO_FILE", + PROCESS_IMAGE: "PROCESS_IMAGE", + BATCH_PROCESS_IMAGE: "BATCH_PROCESS_IMAGE", + BATCH_PROCESS_IMAGE_RESULT: "BATCH_PROCESS_IMAGE_RESULT" + }, + SYSTEM: { + OPEN_FILE: "OPEN_FILE", } } \ No newline at end of file diff --git a/src/define/logger_define.js b/src/define/logger_define.js new file mode 100644 index 0000000..da291df --- /dev/null +++ b/src/define/logger_define.js @@ -0,0 +1,3 @@ +export const LOGGER_DEFINE = { + REMOVE_WATERMARK: "去除水印", +} \ No newline at end of file diff --git a/src/main/IPCEvent/imageIpc.js b/src/main/IPCEvent/imageIpc.js index 68d32a0..9982a48 100644 --- a/src/main/IPCEvent/imageIpc.js +++ b/src/main/IPCEvent/imageIpc.js @@ -1,6 +1,8 @@ import { ipcMain } from "electron"; import { DEFINE_STRING } from '../../define/define_string' import { Image } from "../Public/Image"; +import { LOGGER_DEFINE } from "../../define/logger_define"; +import { errorMessage } from "../generalTools"; let image = new Image(global); @@ -8,6 +10,21 @@ function ImageIpc() { // 一拆四 ipcMain.handle(DEFINE_STRING.IMG.ONE_SPLIT_FOUR, async (event, value) => await image.OneSplitFour(value)); + + // 将base64的图片转换为文件 + ipcMain.handle(DEFINE_STRING.IMG.BASE64_TO_FILE, async (event, value) => await image.Base64ToFile(value)); + + // t图片处理,去除水印 + ipcMain.handle(DEFINE_STRING.IMG.PROCESS_IMAGE, async (event, value) => { + try { + return await image.ProcessImage(value) + } catch (error) { + return errorMessage(error, LOGGER_DEFINE.REMOVE_WATERMARK) + } + }); + + // 批量处理,去除所有水印 + ipcMain.handle(DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE, async (event, value) => await image.BatchProcessImage(value)); } export { ImageIpc diff --git a/src/main/IPCEvent/system.js b/src/main/IPCEvent/system.js new file mode 100644 index 0000000..521c54e --- /dev/null +++ b/src/main/IPCEvent/system.js @@ -0,0 +1,17 @@ +import { ipcMain } from "electron"; +import { DEFINE_STRING } from '../../define/define_string' +const { shell } = require('electron') + + + +function SystemIpc() { + + // 打开指定的文件 + ipcMain.on(DEFINE_STRING.SYSTEM.OPEN_FILE, async (event, value) => { + await shell.openPath(value); + }); + +} +export { + SystemIpc +} \ No newline at end of file diff --git a/src/main/Public/Image.js b/src/main/Public/Image.js index 6359f41..29d450b 100644 --- a/src/main/Public/Image.js +++ b/src/main/Public/Image.js @@ -1,6 +1,16 @@ import { errorMessage, successMessage } from "../generalTools"; -import path from "path"; +import path, { resolve } from "path"; import { Tools } from "../tools"; +import fs from "fs"; +import { ImageSetting } from "../../define/setting/imageSetting"; +import { isEmpty, reject } from "lodash"; +import { basicApi } from "../../api/apiBasic"; +import sharp from 'sharp'; +import { file, image } from "../../define/Tools"; +import { define } from "../../define/define"; +import { spawn } from 'child_process' +import { LOGGER_DEFINE } from "../../define/logger_define"; +import { DEFINE_STRING } from "../../define/define_string"; export class Image { constructor(global) { @@ -45,4 +55,281 @@ export class Image { return errorMessage(error.message); } } + + /** + * 将base64转换为文件 + * @param {*} value + * @returns + */ + async Base64ToFile(value) { + try { + value = JSON.parse(value); + let base64 = value[0]; + let out_file_str = value[1]; + let base64Data = base64.replace(/^data:image\/\w+;base64,/, ""); + let dataBuffer = Buffer.from(base64Data, 'base64'); + let out_file = path.join(this.global.config.project_path, out_file_str); + let out_folder = path.dirname(out_file); + await this.tools.checkFolderExistsOrCreate(out_folder); + + await fs.promises.writeFile(out_file, dataBuffer); + // await this.tools.writeArrayToFile(dataBuffer, out_file); + return successMessage(out_file, "base64保存到本地图片成功"); + } catch (error) { + return errorMessage("base64保存到本地图片失败,失败原因如下:" + error.message); + } + } + + /** + * 图片处理,去除水印 + * @param {*} value + * @returns + */ + async ProcessImage(value) { + return new Promise(async (resolve, reject) => { + try { + value = JSON.parse(value); + value.out_file = value.out_file ? value.out_file : `data/mask/temp/${new Date().getTime()}.png`; + // 判断当前使用的是什么 + let mask_setting = (await ImageSetting.GetDefineConfigJsonByProperty(JSON.stringify(["img_base", "mask_setting", false, {}]))).data; + let isRemote = mask_setting.isRemote ? mask_setting.isRemote : false; + let urls = mask_setting.localUrl ? mask_setting.localUrl + "api/v1/inpaint" : ""; + + if (isRemote && isEmpty(urls)) { + throw new Error("使用iopaint图片处理,但是没有配置图片处理地址"); + } + if (!isRemote && isEmpty(value.out_file)) { + throw new Error("水印处理,使用软件直接处理类型为file,需要指定输出的文件地址"); + } + + let out_file; + if (!isEmpty(value.out_file)) { + out_file = path.join(this.global.config.project_path, value.out_file); + let out_folder = path.dirname(out_file); + await this.tools.checkFolderExistsOrCreate(out_folder); + } + + let res; + if (isRemote) { + let headers = { + "accept": '*/*', + 'accept-language': 'zh-CN,zh;q=0.9', + 'content-type': 'application/json' + } + let data = { + "image": value.image, + "mask": value.mask, + "ldm_steps": 30, + "ldm_sampler": "ddim", + "zits_wireframe": true, + "cv2_flag": "INPAINT_NS", + "cv2_radius": 5, + "hd_strategy": "Crop", + "hd_strategy_crop_triger_size": 640, + "hd_strategy_crop_margin": 128, + "hd_trategy_resize_imit": 2048 * 5, + "prompt": "", + "negative_prompt": "out of frame, lowres, error, cropped, worst quality, low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, out of frame, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra limbs, disfigured, gross proportions, malformed limbs, watermark, signature", + "use_croper": false, + "croper_x": 284, + "croper_y": 284, + "croper_height": 512, + "croper_width": 512, + "use_extender": false, + "extender_x": 0, + "extender_y": 0, + "extender_height": 1080, + "extender_width": 1080, + "sd_mask_blur": 12, + "sd_strength": 1, + "sd_steps": 50, + "sd_guidance_scale": 7.5, + "sd_sampler": "DPM++ 2M", + "sd_seed": -1, + "sd_match_histograms": false, + "sd_lcm_lora": false, + "paint_by_example_example_image": null, + "p2p_image_guidance_scale": 1.5, + "enable_controlnet": false, + "controlnet_conditioning_scale": 0.4, + "controlnet_method": "", + "enable_brushnet": false, + "brushnet_method": "random_mask", + "brushnet_conditioning_scale": 1, + "enable_powerpaint_v2": false, + "powerpaint_task": "text-guided" + }; + res = await basicApi.post(urls, data, headers); + this.global.logger.info(LOGGER_DEFINE.REMOVE_WATERMARK, `iopaint去除水印请求成功`); + if (value.type == 'arrayBuffer') { + resolve(successMessage(res.data, "图片处理成功", LOGGER_DEFINE.REMOVE_WATERMARK)); + } else if (value.type == "file") { + let buffer = Buffer.from(res.data); + await fs.promises.writeFile(out_file, buffer) + resolve(successMessage(out_file, "图片处理成功", LOGGER_DEFINE.REMOVE_WATERMARK)); + } + + } else { + let lama_script = path.resolve(define.scripts_path, `lama/lama_inpaint.exe`); + // 就是判断指定的文件和文件夹是不是存在 + let has_exe = await file.CheckFileOrDirExist(lama_script); + if (!has_exe) { + throw new Error("图片水印处理组件不存在,请看教程自行下载"); + } + let has_model = await file.CheckFileOrDirExist(path.resolve(define.scripts_path, 'lama/model/big-lama.pt')) + if (!has_model) { + throw new Error("图片水印处理的模型不存在,请看教程自行下载") + } + + this.global.logger.info(LOGGER_DEFINE.REMOVE_WATERMARK, `开始使用lama去除水印,开始调用lama程序`); + // 先将对应的base64文件写道本地 + let image_path = await this.Base64ToFile(JSON.stringify([value.image, `data/mask/temp/${new Date().getTime()}.png`])) + let mask_path = await this.Base64ToFile(JSON.stringify([value.mask, `data/mask/mask_temp_${new Date().getTime()}.png`])) + if (image_path.code == 0) { + throw new Error(image_path.message) + } + if (mask_path.code == 0) { + throw new Error(mask_path.message) + } + + let child = spawn(lama_script, ['-l', image_path.data, mask_path.data, out_file], { encoding: 'utf-8' }); + // let child = spawn('python', [lama_script, '-l', image_path.data, mask_path.data, out_file], { encoding: 'utf-8' }); + child.on('error', (error) => { + reject(error.toString()) + return + }) + + child.stdout.on('data', (data) => { + console.log(data.toString()) + }) + + child.stderr.on('data', (data) => { + reject(data.toString()) + return + }) + + child.on('close', async (data) => { + if (data != 0) { + this.global.logger.error(LOGGER_DEFINE.REMOVE_WATERMARK, `lama去除水印失败,错误码:${data.toString()}`); + reject("lama去除水印错误。请看日志详细信息!") + return + } + // 判断是不是有输出文件 + let has_out = await file.CheckFileOrDirExist(out_file); + if (!has_out) { + reject("lama去除水印失败,没有输出文件") + return + } + + if (value.type == 'arrayBuffer') { + // 读取导出的文件 + let res_data = await fs.promises.readFile(out_file); + resolve(successMessage(res_data, "图片处理成功", LOGGER_DEFINE.REMOVE_WATERMARK)); + } else if (value.type == "file") { + resolve(successMessage(out_file, "图片处理成功", LOGGER_DEFINE.REMOVE_WATERMARK)); + } + }) + } + } catch (error) { + reject("图片处理失败,失败原因如下:" + error.message) + } + }) + } + + // 批量处理所有的图片,去除水印 + async BatchProcessImage(value) { + try { + let input_folder = value; + input_folder = path.resolve(this.global.config.project_path, input_folder) + if (!(await this.tools.checkExists(input_folder))) { + throw new Error("输入的文件夹不存在"); + } + + let new_input_folder = path.join(this.global.config.project_path, `tmp/bak/${path.basename(input_folder)}`); + + // 在备份之前判断,旧的文件是不是存在,里面是不是有图片,没有图片,直接删除,在备份过去,存在直接开始下面的请求 + let has_files = await file.CheckFileOrDirExist(new_input_folder); + if (has_files) { + let files = await file.GetFilesWithExtensions(new_input_folder, ['.png']); + if (files.length <= 0) { + // 删除指定的文件夹 + await fs.promises.rm(new_input_folder, { recursive: true }); + await file.BackupFileOrFolder(input_folder, new_input_folder); + // 创建新的input_folder + await fs.promises.mkdir(input_folder, { recursive: true }) + } + } else { + await file.BackupFileOrFolder(input_folder, new_input_folder); + // 创建新的input_folder + await fs.promises.mkdir(input_folder, { recursive: true }) + } + // 开始备份 + + + // 获取蒙板 + let mask_setting = (await ImageSetting.GetDefineConfigJsonByProperty(JSON.stringify(["img_base", "mask_setting", false, {}]))).data; + if (mask_setting.isRemote && isEmpty(mask_setting.localUrl)) { + throw new Error("使用iopaint图片处理,但是没有配置图片处理地址"); + } + if (isEmpty(mask_setting.mask_path)) { + throw new Error("没有配置蒙板的路径"); + } + + // 获取文件夹里面所有的图片文件 + // let png_files = await this.tools.getFilesWithExtensions(input_folder, '.png'); + let png_files = await file.GetFilesWithExtensions(new_input_folder, ['.png']); + if (png_files.length == 0) { + throw new Error("没有找到任何的抽帧图片文件"); + } + + // 获取图片的总数,将数据返回前端,更新进度条 + this.global.newWindow[0].win.webContents.send(DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE_RESULT, successMessage({ + total: png_files.length, + current: 0 + })) + + // 默认所有的的宽高都是一样的,获取第一张图片的宽高 + let first_image = png_files[0]; + let first_image_size = await image.GetImageSize(first_image); + + // 重新设置蒙板的宽高,和图片的宽高一样 + let mask_base = await image.ResizeImage(mask_setting.mask_path, first_image_size.width, first_image_size.height, 'base64'); + mask_base = `data:image/png;base64,${mask_base}`; + + // 开始处理所有的图片 + for (let i = 0; i < png_files.length; i++) { + const element = png_files[i]; + // 获取指定的图片,并且转换为base64 + let image_base = await fs.promises.readFile(element); + image_base = image_base.toString('base64'); + image_base = `data:image/png;base64,${image_base}`; + + // 开始处理图片 + let res = await this.ProcessImage(JSON.stringify({ + image: image_base, + mask: mask_base, + type: "file", + out_file: `tmp/input_crop/${path.basename(element)}` + })) + + if (res.code == 0) { + throw new Error(res.message); + } + + // 删除之前的 + await this.tools.deleteFileOrDirectory(element) + await this.tools.delay(1000) + // 当前图片处理成功,将信息返回前端,更新进度条 + this.global.newWindow[0].win.webContents.send(DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE_RESULT, successMessage({ + total: png_files.length, + current: i + 1 + })) + } + + return successMessage("所有的图片处理成功", LOGGER_DEFINE.REMOVE_WATERMARK); + + } catch (error) { + return errorMessage("图片处理失败,失败原因如下:" + error.message, LOGGER_DEFINE.REMOVE_WATERMARK); + } + } } \ No newline at end of file diff --git a/src/main/backPrompt/imageGenerate.js b/src/main/backPrompt/imageGenerate.js index e6dcca5..fecbbff 100644 --- a/src/main/backPrompt/imageGenerate.js +++ b/src/main/backPrompt/imageGenerate.js @@ -263,7 +263,7 @@ export class ImageGenerate { let task_list = task_list_json.task_list.filter(item => item.id == element)[0]; let seed = -1; await fspromises.mkdir(path.join(this.global.config.project_path, 'tmp/' + task_list.out_folder), { recursive: true }); - this.global.requestQuene.enqueue(async () => { + await this.global.requestQuene.enqueue(async () => { let res = await this.sd.OneImageGeneration(images[0], task_list, seed); let tmp_seed = -1; if (seed == -1) { @@ -293,7 +293,7 @@ export class ImageGenerate { let copy_path = path.join(this.global.config.project_path, 'tmp/' + task_list.out_folder, base_name); await this.tools.copyFileOrDirectory(randomData, copy_path); } else { - this.global.requestQuene.enqueue(async () => { + await this.global.requestQuene.enqueue(async () => { await this.sd.OneImageGeneration(item, task_list, tmp_seed); }, `${task_list.out_folder}_${images[j]}`, batch, task_list.out_folder) } @@ -339,6 +339,7 @@ export class ImageGenerate { // 监听总批次完成 this.global.requestQuene.setBatchCompletionCallback(batch, (failedTasks) => { + debugger if (failedTasks.length > 0) { let message = ` 批次生成任务都已完成。 @@ -353,6 +354,12 @@ export class ImageGenerate { message: message }) } else { + + //判断当前batch是不是还有任务没有跑完 + let current_batch = this.global.requestQuene.getBatch(batch); + if (current_batch && current_batch.length > 0) { + return; + } this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { code: 1, message: "所有生成任务完成" diff --git a/src/main/func.js b/src/main/func.js index 6db293c..02c1a91 100644 --- a/src/main/func.js +++ b/src/main/func.js @@ -640,7 +640,7 @@ async function SaveSDConfig(value) { sd_config.webui.width = value.width ? value.width : sd_config.webui.width; sd_config.webui.height = value.height ? value.height : sd_config.webui.height; sd_config.webui.cfg_scale = value.cfg_scale ? value.cfg_scale : sd_config.webui.cfg_scale; - sd_config.webui.adetailer = value.adetailer ? value.adetailer : sd_config.webui.adetailer; + sd_config.webui.adetailer = value.hasOwnProperty("adetailer") ? value.adetailer : sd_config.webui.adetailer; await fspromises.writeFile(define.sd_setting, JSON.stringify(sd_config)); return { diff --git a/src/main/generalTools.js b/src/main/generalTools.js index eb03c30..a3df91f 100644 --- a/src/main/generalTools.js +++ b/src/main/generalTools.js @@ -66,11 +66,15 @@ function checkStringValueDeletePrefix(value, prefix) { } /** * 返回成功的消息,包含code,data,message - * @param {*} data - * @param {*} message + * @param {*} data 返回的数据 + * @param {*} message 成功消息 + * @param {*} service 消息日志类型 * @returns */ -function successMessage(data, message = null) { +function successMessage(data, message = null, service = null) { + if (service && message) { + global.logger.info(service, `成功返回数据`); + } return { code: 1, data: data, @@ -79,11 +83,15 @@ function successMessage(data, message = null) { } /** - * 返回失败的消息,包含code,message - * @param {*} message + * 返回失败的消息, + * @param {*} message 错误信息 + * @param {*} service 错误日志类型 * @returns */ -function errorMessage(message) { +function errorMessage(message, service = null) { + if (service && message) { + global.logger.error(service, message); + } return { code: 0, message: message diff --git a/src/main/index.js b/src/main/index.js index 884d046..a4c4492 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -31,7 +31,9 @@ import { DiscordIpc, RemoveDiscordIpc } from './IPCEvent/discordIpc.js' import { MainIpc } from './IPCEvent/mainIpc.js' import { GlobalIpc } from "./IPCEvent/globalIpc.js"; import { ImageIpc } from "./IPCEvent/imageIpc.js"; +import { SystemIpc } from "./IPCEvent/system.js"; import { system } from "systeminformation"; +import { Logger } from "./logger.js"; let tools = new Tools(); let imageGenerate = new ImageGenerate(global); @@ -44,6 +46,7 @@ async function InitData(gl) { gl.config = res; gl.requestQuene = new AsyncQueue(gl, res.task_number); gl.fileQueue = new AsyncQueue(gl, 1); + gl.logger = new Logger(define.logger_path); return res; } @@ -194,7 +197,13 @@ app.whenReady().then(async () => { // 判断动态文件是不是存在 tools.checkJsonFileExistsOrCreate(path.normalize(define.dynamic_setting)); // 判断标签文件是不是存在 - tools.checkJsonFileExistsOrCreate(path.normalize(define.tag_setting)); + tools.checkJsonFileExistsOrCreate(path.normalize(define.tag_setting), JSON.stringify({ + "character_tags": [], + "style_tags": [], + "scene_tags": [], + "prefix_tags": [], + "suffix_tags": [] + })); // 判断SD图片缓存文件是不是存在(不存在创建) tools.checkFolderExistsOrCreate(path.normalize(define.temp_sd_image)); tools.checkFolderExistsOrCreate(path.normalize(path.join(define.image_path, "c_s"))); @@ -230,6 +239,7 @@ MainIpc(createWindow); OriginalImageGenerateIpc(); GlobalIpc(); ImageIpc(); +SystemIpc(); ipcMain.handle('dark-mode:toggle', (event, value) => { diff --git a/src/main/logger.js b/src/main/logger.js new file mode 100644 index 0000000..6224f24 --- /dev/null +++ b/src/main/logger.js @@ -0,0 +1,59 @@ +// const winston = require('winston'); +import winston from 'winston'; +import path from 'path'; +import DailyRotateFile from 'winston-daily-rotate-file'; + +export class Logger { + constructor(log_folder) { + this.log_folder = log_folder; + this.logger = winston.createLogger({ + level: 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.printf(info => `${info.timestamp} [${info.level.toUpperCase()}] [${info.service}] ${info.message}`) + ), + transports: [ + new DailyRotateFile({ + filename: path.resolve(this.log_folder, `LAITool-%DATE%.log`), + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '10m', + maxFiles: '14d' + }) + ], + }); + + if (process.env.NODE_ENV !== 'production') { + this.logger.add(new winston.transports.Console({ + format: winston.format.simple(), + })); + } + } + + /** + * 保存info级别的日志 + * @param {*} service service 名称 + * @param {*} message 消息 + */ + info(service, message) { + this.logger.info(message, { service }); + } + + /** + * 保存error级别的日志 + * @param {*} service service 名称 + * @param {*} message 消息 + */ + error(service, message) { + this.logger.error(message, { service }); + } + + /** + * 保存warn级别的日志 + * @param {*} service service 名称 + * @param {*} message 消息 + */ + warn(service, message) { + this.logger.warn(message, { service }); + } +} \ No newline at end of file diff --git a/src/main/quene.js b/src/main/quene.js index 2316d7f..6978735 100644 --- a/src/main/quene.js +++ b/src/main/quene.js @@ -38,7 +38,7 @@ export class AsyncQueue { this.batchCompletion[batchId].subBatches[subBatchId].remaining++; this.tasks.push({ task, taskId, batchId, subBatchId }); if (!this.manualMode) { - this.process(); + await this.process(); } } @@ -79,7 +79,7 @@ export class AsyncQueue { while (this.tasks.length > 0 && (this.manualMode ? this.taskProgress.length < task_count : this.currentConcurrency < this.concurrencyLimit)) { const { task, taskId, batchId, subBatchId } = this.tasks.shift(); this.currentConcurrency++; - task().then(() => { + Promise.resolve(task()).then(() => { this.currentConcurrency--; this.handleTaskCompletion(batchId, subBatchId, null); if (!this.manualMode) { @@ -243,6 +243,12 @@ export class AsyncQueue { } } + // 获取指定batch的任务列表 + getTasks(batchId) { + return this.tasks.filter(item => item.batchId === batchId); + } + + // 获取失败的任务 getFailedTasks(batchId, subBatchId = 'default') { if (this.batchCompletion[batchId] && this.batchCompletion[batchId].subBatches[subBatchId]) { diff --git a/src/main/tools.js b/src/main/tools.js index 8786d2b..c90647e 100644 --- a/src/main/tools.js +++ b/src/main/tools.js @@ -45,7 +45,7 @@ export class Tools { * 判断json文件是不是存在,不存在的话,协议一个空的json文件 * @param {*} filePath 文件地址 */ - async checkJsonFileExistsOrCreate(filePath) { + async checkJsonFileExistsOrCreate(filePath, defaultValue = "{}") { try { if (!(await this.checkExists(filePath))) { // 判断传入的json文件的父文件夹是不是存在,不存在的话,创建 @@ -54,7 +54,7 @@ export class Tools { // 创建文件夹 await fspromises.mkdir(parentFolder, { recursive: true }); } - await fspromises.writeFile(filePath, "{}"); + await fspromises.writeFile(filePath, defaultValue); } } catch (error) { throw new Error(error); @@ -101,7 +101,7 @@ export class Tools { /** * 延时多少秒 - * @param {等待时间} time + * @param {*} time * @returns */ async delay(time) { @@ -123,8 +123,8 @@ export class Tools { /** * 获取指定路径下面的指定的拓展的文件 - * @param {指定的文件夹路径} folderPath - * @param {后缀名} extensions + * @param {*} folderPath + * @param {*} extensions * @returns */ async getFilesWithExtensions(folderPath, extensions) { diff --git a/src/preload/img.js b/src/preload/img.js index 546cdb3..a0be509 100644 --- a/src/preload/img.js +++ b/src/preload/img.js @@ -5,6 +5,15 @@ import { DEFINE_STRING } from "../define/define_string" const img = { // 加载当前链接的SD服务数据 OneSplitFour: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.ONE_SPLIT_FOUR, value)), + + // 将base64的图片转换为文件 + Base64ToFile: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.BASE64_TO_FILE, value)), + + // 请求图片处理,去除水印 + ProcessImage: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.PROCESS_IMAGE, value)), + + //批量处理图片,去除水印 + BatchProcessImage: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE, value)), } export { img diff --git a/src/preload/index.js b/src/preload/index.js index a4bd1a6..8c0a124 100644 --- a/src/preload/index.js +++ b/src/preload/index.js @@ -5,6 +5,7 @@ import { discord } from './discord.js'; import { mj } from './mj.js'; import { sd } from './sd.js'; import { img } from './img.js'; +import { system } from './system.js'; // Custom APIs for renderer let events = []; @@ -413,6 +414,7 @@ if (process.contextIsolated) { contextBridge.exposeInMainWorld('discord', discord) contextBridge.exposeInMainWorld("sd", sd) contextBridge.exposeInMainWorld("img", img) + contextBridge.exposeInMainWorld("system", system) contextBridge.exposeInMainWorld('darkMode', { toggle: (value) => ipcRenderer.invoke('dark-mode:toggle', value), }) @@ -426,5 +428,6 @@ if (process.contextIsolated) { window.discord = discord; window.sd = sd; window.img = img; + window.system = system; } diff --git a/src/preload/system.js b/src/preload/system.js new file mode 100644 index 0000000..56916ec --- /dev/null +++ b/src/preload/system.js @@ -0,0 +1,11 @@ +import { ipcRenderer } from "electron" +import { DEFINE_STRING } from "../define/define_string" + + +const system = { + // 打开指定的文件 + OpenFile: async (value, callback) => callback(ipcRenderer.send(DEFINE_STRING.SYSTEM.OPEN_FILE, value)), +} +export { + system +} \ No newline at end of file diff --git a/src/renderer/src/components/Backstep/GetFrame.vue b/src/renderer/src/components/Backstep/GetFrame.vue index 75d979a..6931770 100644 --- a/src/renderer/src/components/Backstep/GetFrame.vue +++ b/src/renderer/src/components/Backstep/GetFrame.vue @@ -1,190 +1,250 @@ \ No newline at end of file + diff --git a/src/renderer/src/components/Backstep/PushBackPrompt.vue b/src/renderer/src/components/Backstep/PushBackPrompt.vue index c254885..f595840 100644 --- a/src/renderer/src/components/Backstep/PushBackPrompt.vue +++ b/src/renderer/src/components/Backstep/PushBackPrompt.vue @@ -1,99 +1,123 @@ \ No newline at end of file + diff --git a/src/renderer/src/components/Components/ProgressDialog.vue b/src/renderer/src/components/Components/ProgressDialog.vue new file mode 100644 index 0000000..87ef0db --- /dev/null +++ b/src/renderer/src/components/Components/ProgressDialog.vue @@ -0,0 +1,50 @@ + + + diff --git a/src/renderer/src/components/Original/MenuButton.vue b/src/renderer/src/components/Original/MenuButton.vue index b49e5ad..0ad3fc3 100644 --- a/src/renderer/src/components/Original/MenuButton.vue +++ b/src/renderer/src/components/Original/MenuButton.vue @@ -63,7 +63,7 @@ export default defineComponent({ let data = ref(props.initData) let AnalyzeCharacter = ref(props.Character) let tagTreeData = ref(props.treeData) - let auto_save = ref(window.config.auto_save ? window.config.auto_save : false) + let auto_save = ref(window.config?.auto_save ? window.config.auto_save : false) watch( () => props.initData, diff --git a/src/renderer/src/components/Setting/Components/AddGptOption.vue b/src/renderer/src/components/Setting/Components/AddGptOption.vue index 7888c94..8a64595 100644 --- a/src/renderer/src/components/Setting/Components/AddGptOption.vue +++ b/src/renderer/src/components/Setting/Components/AddGptOption.vue @@ -1,274 +1,318 @@ \ No newline at end of file + diff --git a/src/renderer/src/components/Setting/SDSetting.vue b/src/renderer/src/components/Setting/SDSetting.vue index a76da17..8c64a4d 100644 --- a/src/renderer/src/components/Setting/SDSetting.vue +++ b/src/renderer/src/components/Setting/SDSetting.vue @@ -14,7 +14,7 @@ +
+
+ +
+ 使用iopaint + +
+ + 保存 +
+
+ lama安装教程 + iopaint安装教程 +
+
+
+ +
+ + +
+
+ + 回撤 + 查看效果 + 保存蒙板 + 查看蒙板 + + + 批量处理的图片的尺寸要和上面的蒙板一致 + +
+
+ + + + + diff --git a/src/renderer/src/components/Watermark/ImageFileSelect.vue b/src/renderer/src/components/Watermark/ImageFileSelect.vue new file mode 100644 index 0000000..11d9c1e --- /dev/null +++ b/src/renderer/src/components/Watermark/ImageFileSelect.vue @@ -0,0 +1,55 @@ + + +