diff --git a/.gitignore b/.gitignore index afe9833..c1b698b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ project resources/scripts/build* resources/scripts/dist resources/scripts/model +resources/scripts/EasyOcr +resources/scripts/LaiOcr resources/scripts/lama/model resources/scripts/lama/lama.7z resources/scripts/lama/_internal diff --git a/package-lock.json b/package-lock.json index 6b3e7e7..6b5a760 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "laitool", - "version": "2.2.10", + "version": "2.2.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "laitool", - "version": "2.2.10", + "version": "2.2.11", "hasInstallScript": true, "dependencies": { "@alicloud/alimt20181012": "^1.2.0", @@ -20,6 +20,7 @@ "artplayer": "^5.1.6", "awesome-js": "^2.0.0", "axios": "^1.6.5", + "baidu-aip-sdk": "^4.16.16", "blob-to-buffer": "^1.2.9", "compressing": "^1.10.0", "crypto-js": "^4.2.0", @@ -46,12 +47,14 @@ "wav-file-info": "^0.0.10", "winreg": "^1.2.5", "winston": "^3.13.0", - "winston-daily-rotate-file": "^5.0.0" + "winston-daily-rotate-file": "^5.0.0", + "ws": "^8.18.0" }, "devDependencies": { "@electron-toolkit/eslint-config": "^1.0.1", "@rushstack/eslint-patch": "^1.6.1", "@types/fluent-ffmpeg": "^2.1.24", + "@types/ws": "^8.5.10", "@vitejs/plugin-vue": "^5.0.2", "@vue/eslint-config-prettier": "^9.0.0", "electron": "^28.1.1", @@ -2249,6 +2252,15 @@ "resolved": "https://registry.npmmirror.com/@types/triple-beam/-/triple-beam-1.3.5.tgz", "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/xml2js": { "version": "0.4.14", "license": "MIT", @@ -2633,7 +2645,6 @@ }, "node_modules/ajv": { "version": "6.12.6", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -2963,6 +2974,22 @@ "option-validator": "^2.0.6" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmmirror.com/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/async": { "version": "3.2.5", "license": "MIT" @@ -3009,6 +3036,19 @@ "lodash.throttle": "^4.1.1" } }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.0", + "resolved": "https://registry.npmmirror.com/aws4/-/aws4-1.13.0.tgz", + "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==" + }, "node_modules/axios": { "version": "1.6.5", "license": "MIT", @@ -3018,6 +3058,45 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/baidu-aip-sdk": { + "version": "4.16.16", + "resolved": "https://registry.npmmirror.com/baidu-aip-sdk/-/baidu-aip-sdk-4.16.16.tgz", + "integrity": "sha512-dXjeQrd/eJIXDzBXNKArZZyFxf2boUI+XKEqb7yLUwnNiHJT3xDmHz30Oobe1LxOFFsvdH5pm72+pzi6MbXgmw==", + "dependencies": { + "debug": "^2.6.9", + "iconv-lite": "^0.4.24", + "request": "^2.88.2", + "underscore": "^1.12.1" + }, + "optionalDependencies": { + "mocha": "^4.0.1", + "should": "^13.2.0" + } + }, + "node_modules/baidu-aip-sdk/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/baidu-aip-sdk/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/baidu-aip-sdk/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/balanced-match": { "version": "1.0.2", "devOptional": true, @@ -3041,6 +3120,14 @@ ], "license": "MIT" }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/big.js": { "version": "6.2.1", "resolved": "https://registry.npmmirror.com/big.js/-/big.js-6.2.1.tgz", @@ -3146,6 +3233,12 @@ "balanced-match": "^1.0.0" } }, + "node_modules/browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha512-7Rfk377tpSM9TWBEeHs0FlDZGoAIei2V/4MdZJoFMBFAK6BqLpxAIUepGRHGdPFgGsLb02PXovC4qddyHvQqTg==", + "optional": true + }, "node_modules/browserslist": { "version": "4.22.2", "funding": [ @@ -3409,6 +3502,11 @@ "node": ">=6" } }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -3850,6 +3948,17 @@ "version": "3.1.3", "license": "MIT" }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/data-urls": { "version": "5.0.0", "license": "MIT", @@ -4044,6 +4153,15 @@ "license": "MIT", "optional": true }, + "node_modules/diff": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "optional": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-compare": { "version": "3.3.0", "dev": true, @@ -4190,6 +4308,15 @@ "dev": true, "license": "MIT" }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/ejs": { "version": "3.1.9", "dev": true, @@ -4919,6 +5046,11 @@ "node": ">=6" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/extract-zip": { "version": "2.0.1", "license": "BSD-2-Clause", @@ -4937,6 +5069,14 @@ "@types/yauzl": "^2.9.1" } }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ] + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "license": "MIT" @@ -4948,7 +5088,6 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -5077,7 +5216,8 @@ }, "node_modules/fluent-ffmpeg/node_modules/which": { "version": "1.3.1", - "license": "ISC", + "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dependencies": { "isexe": "^2.0.0" }, @@ -5127,6 +5267,14 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "engines": { + "node": "*" + } + }, "node_modules/form-data": { "version": "4.0.0", "license": "MIT", @@ -5267,6 +5415,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmmirror.com/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/gifwrap": { "version": "0.10.1", "license": "MIT", @@ -5448,6 +5604,36 @@ "dev": true, "license": "MIT" }, + "node_modules/growl": { + "version": "1.10.3", + "resolved": "https://registry.npmmirror.com/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "optional": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmmirror.com/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/has-flag": { "version": "4.0.0", "dev": true, @@ -5506,6 +5692,15 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/he/-/he-1.1.1.tgz", + "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", + "optional": true, + "bin": { + "he": "bin/he" + } + }, "node_modules/highlight.js": { "version": "11.9.0", "license": "BSD-3-Clause", @@ -5577,6 +5772,20 @@ "node": ">= 6" } }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, "node_modules/http2-wrapper": { "version": "1.0.3", "license": "MIT", @@ -5795,6 +6004,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, "node_modules/is-what": { "version": "3.14.1", "devOptional": true, @@ -5827,6 +6041,11 @@ "whatwg-fetch": "^3.4.1" } }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, "node_modules/jackspeak": { "version": "2.3.6", "dev": true, @@ -5913,6 +6132,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, "node_modules/jsdom": { "version": "24.0.0", "license": "MIT", @@ -6039,9 +6263,13 @@ "version": "3.0.1", "license": "MIT" }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "dev": true, "license": "MIT" }, "node_modules/json-schema-typed": { @@ -6056,8 +6284,7 @@ }, "node_modules/json-stringify-safe": { "version": "5.0.1", - "license": "ISC", - "optional": true + "license": "ISC" }, "node_modules/json5": { "version": "2.2.3", @@ -6076,6 +6303,20 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "license": "MIT", @@ -6509,6 +6750,141 @@ "resolved": "https://registry.npmmirror.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, + "node_modules/mocha": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/mocha/-/mocha-4.1.0.tgz", + "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", + "optional": true, + "dependencies": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/mocha/node_modules/commander": { + "version": "2.11.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "optional": true + }, + "node_modules/mocha/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "optional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "optional": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha512-P+1n3MnwjR/Epg9BBo1KT8qbye2g2Ou4sFumihwt6I4tsUX7jnLcX4BTOSKg/B1ZrIYMN9FcEnG4x5a7NB8Eng==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", + "optional": true + }, + "node_modules/mocha/node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "optional": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "optional": true + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "optional": true, + "dependencies": { + "has-flag": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz", @@ -9156,6 +9532,14 @@ "version": "2.2.7", "license": "MIT" }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmmirror.com/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, "node_modules/object-assign": { "version": "4.1.1", "license": "MIT", @@ -9386,6 +9770,11 @@ "version": "1.2.0", "license": "MIT" }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, "node_modules/phin": { "version": "2.9.3", "license": "MIT" @@ -9697,6 +10086,14 @@ "teleport": ">=0.2.0" } }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/querystringify": { "version": "2.2.0", "license": "MIT" @@ -9832,6 +10229,71 @@ "version": "0.14.1", "license": "MIT" }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmmirror.com/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/require-directory": { "version": "2.1.1", "dev": true, @@ -10149,6 +10611,60 @@ "node": ">=8" } }, + "node_modules/should": { + "version": "13.2.3", + "resolved": "https://registry.npmmirror.com/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "optional": true, + "dependencies": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "node_modules/should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "optional": true, + "dependencies": { + "should-type": "^1.4.0" + } + }, + "node_modules/should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", + "optional": true, + "dependencies": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "node_modules/should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", + "optional": true + }, + "node_modules/should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "optional": true, + "dependencies": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "node_modules/should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "optional": true + }, "node_modules/signal-exit": { "version": "4.1.0", "dev": true, @@ -10294,6 +10810,30 @@ "license": "BSD-3-Clause", "optional": true }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmmirror.com/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmmirror.com/stack-trace/-/stack-trace-0.0.10.tgz", @@ -10782,6 +11322,11 @@ "node": "*" } }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -10827,6 +11372,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmmirror.com/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, "node_modules/undici-types": { "version": "5.26.5", "license": "MIT" @@ -10918,6 +11468,19 @@ "vue": "^3.0.11" } }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmmirror.com/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "node_modules/vfonts": { "version": "0.0.3", "dev": true, @@ -11262,8 +11825,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.16.0", - "license": "MIT", + "version": "8.18.0", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, @@ -11437,4 +12001,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index ae8b3a6..9c01634 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "laitool", - "version": "2.2.11", + "version": "2.2.12", "description": "An AI tool for image processing, video processing, and other functions.", "main": "./out/main/index.js", "author": "laitool.cn", @@ -28,6 +28,7 @@ "artplayer": "^5.1.6", "awesome-js": "^2.0.0", "axios": "^1.6.5", + "baidu-aip-sdk": "^4.16.16", "blob-to-buffer": "^1.2.9", "compressing": "^1.10.0", "crypto-js": "^4.2.0", @@ -54,12 +55,14 @@ "wav-file-info": "^0.0.10", "winreg": "^1.2.5", "winston": "^3.13.0", - "winston-daily-rotate-file": "^5.0.0" + "winston-daily-rotate-file": "^5.0.0", + "ws": "^8.18.0" }, "devDependencies": { "@electron-toolkit/eslint-config": "^1.0.1", "@rushstack/eslint-patch": "^1.6.1", "@types/fluent-ffmpeg": "^2.1.24", + "@types/ws": "^8.5.10", "@vitejs/plugin-vue": "^5.0.2", "@vue/eslint-config-prettier": "^9.0.0", "electron": "^28.1.1", @@ -95,4 +98,4 @@ "icon": "./resources/icon.ico" } } -} +} \ No newline at end of file diff --git a/resources/scripts/Lai.py b/resources/scripts/Lai.py index 44b4a6e..be3a4d4 100644 --- a/resources/scripts/Lai.py +++ b/resources/scripts/Lai.py @@ -12,13 +12,13 @@ import shotSplit sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") # 判断sys.argv 的长度,如果小于2,说明没有传入参数,设置初始参数 +# "C:\\Users\\27698\\Desktop\\LAITool\\resources\\scripts\\Lai.exe" -c "D:/来推项目集/7.4/娱乐:江湖大哥退休,去拍电影/scripts/output_crop_00001.json" "NVIDIA" if len(sys.argv) < 2: sys.argv = [ "C:\\Users\\27698\\Desktop\\LAITool\\resources\\scripts\\Lai.exe", - "-ka", - "C:\\Users\\27698\\Desktop\\测试\\123\\测试用 不删.mp4", - "C:\\Users\\27698\\Desktop\\测试\\123\\测试用 不删.json", - 30, + "-c", + "D:/来推项目集/7.4/娱乐:江湖大哥退休,去拍电影/scripts/output_crop_00001.json", + "NVIDIA", ] print(sys.argv) @@ -91,3 +91,8 @@ elif sys.argv[1] == "-ka": elif sys.argv[1] == "-a": print("开始算法分镜:" + sys.argv[2] + " -- 输出文件夹:" + sys.argv[3]) shotSplit.init(sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5], sys.argv[6]) +# 本地提取音频 +elif sys.argv[1] == "-t": + print("开始提取文字:" + sys.argv[2]) + shotSplit.GetTextTask(sys.argv[2], sys.argv[3], sys.argv[4]) + pass diff --git a/resources/scripts/__pycache__/clip.cpython-310.pyc b/resources/scripts/__pycache__/clip.cpython-310.pyc index 7b64496..c422deb 100644 Binary files a/resources/scripts/__pycache__/clip.cpython-310.pyc and b/resources/scripts/__pycache__/clip.cpython-310.pyc differ diff --git a/resources/scripts/__pycache__/getgrame.cpython-310.pyc b/resources/scripts/__pycache__/getgrame.cpython-310.pyc index 8f82750..89d5fda 100644 Binary files a/resources/scripts/__pycache__/getgrame.cpython-310.pyc and b/resources/scripts/__pycache__/getgrame.cpython-310.pyc differ diff --git a/resources/scripts/__pycache__/iamge_to_video.cpython-310.pyc b/resources/scripts/__pycache__/iamge_to_video.cpython-310.pyc index d80ab24..9b2cfa7 100644 Binary files a/resources/scripts/__pycache__/iamge_to_video.cpython-310.pyc and b/resources/scripts/__pycache__/iamge_to_video.cpython-310.pyc differ diff --git a/resources/scripts/__pycache__/shotSplit.cpython-310.pyc b/resources/scripts/__pycache__/shotSplit.cpython-310.pyc index f37addd..39e12bb 100644 Binary files a/resources/scripts/__pycache__/shotSplit.cpython-310.pyc and b/resources/scripts/__pycache__/shotSplit.cpython-310.pyc differ diff --git a/resources/scripts/db/book.realm b/resources/scripts/db/book.realm index d3de955..1d71f87 100644 Binary files a/resources/scripts/db/book.realm and b/resources/scripts/db/book.realm differ diff --git a/resources/scripts/db/book.realm.lock b/resources/scripts/db/book.realm.lock index 9776118..fafaa4d 100644 Binary files a/resources/scripts/db/book.realm.lock and b/resources/scripts/db/book.realm.lock differ diff --git a/resources/scripts/db/software.realm b/resources/scripts/db/software.realm index 615a1bb..8930836 100644 Binary files a/resources/scripts/db/software.realm and b/resources/scripts/db/software.realm differ diff --git a/resources/scripts/db/software.realm.lock b/resources/scripts/db/software.realm.lock index ca5f053..7cc572a 100644 Binary files a/resources/scripts/db/software.realm.lock and b/resources/scripts/db/software.realm.lock differ diff --git a/resources/scripts/shotSplit.py b/resources/scripts/shotSplit.py index a2aebb7..d177c4a 100644 --- a/resources/scripts/shotSplit.py +++ b/resources/scripts/shotSplit.py @@ -229,6 +229,58 @@ def GetText(out_folder, mp3_list): sys.stdout.flush() +def GetTextTask(out_folder, mp, name): + text = [] + # 先获取模型 + print("正在下载或加载模型") + sys.stdout.flush() + model_path = Path( + hf_hub_download(repo_id="Systran/faster-whisper-large-v3", filename="model.bin") + ) + hf_hub_download( + repo_id="Systran/faster-whisper-large-v3", + filename="config.json", + ) + hf_hub_download( + repo_id="Systran/faster-whisper-large-v3", + filename="preprocessor_config.json", + ) + hf_hub_download( + repo_id="Systran/faster-whisper-large-v3", + filename="tokenizer.json", + ) + hf_hub_download( + repo_id="Systran/faster-whisper-large-v3", + filename="vocabulary.json", + ) + model = WhisperModel( + model_size_or_path=os.path.dirname(model_path), + device="auto", + local_files_only=True, + ) + print("模型加载成功,开始识别") + sys.stdout.flush() + segments, info = model.transcribe( + mp, + beam_size=5, + language="zh", + vad_filter=True, + vad_parameters=dict(min_silence_duration_ms=1000), + ) + tmp_text = "" + for segment in segments: + tmp_text += segment.text + "。" + print(mp + "识别完成") + sys.stdout.flush() + text.append(tmp_text) + + # 数据写出 + sys.stdout.flush() + tools = public_tools.PublicTools() + tools.write_to_file(text, os.path.join(out_folder, name + ".txt")) + sys.stdout.flush() + + def get_fram(video_path, out_path, sensitivity): try: shijian_list = find_scenes(video_path, sensitivity) # 多组时间列表 diff --git a/resources/tmp/config/global_setting.json b/resources/tmp/config/global_setting.json index cc4106b..b69d8a3 100644 --- a/resources/tmp/config/global_setting.json +++ b/resources/tmp/config/global_setting.json @@ -1,8 +1,28 @@ { - "draft_path": "C:/Users/27698/AppData/Local/JianyingPro/User Data/Projects/com.lveditor.draft", - "project_path": "测试覆盖", - "project_name": "测试覆盖", - "gpt_business": "https://api.openai-hk.com/v1/chat/completions", - "gpt_key": "hk-flj65q10000101963e3c83ed48aa840c32044b4afaecc5f0", - "gpt_model": "gpt-3.5-turbo" + "draft_path": "你的剪映草稿地址", + "project_path": "你的项目文件地址(存放图片视频等数据的文件夹)", + "project_name": "你的项目名字", + "gpt_business": "b8866543-8c27-4888-869c-00aa1eb31272", + "gpt_model": "gpt-3.5-turbo", + "task_number": 1, + "translation_business": "https://fanyi-api.baidu.com/api/trans/vip/translate", + "translation_app_id": "1234", + "translation_secret": "2234", + "translation_auto": true, + "theme": "light", + "gpt_auto_inference": "storyFirst", + "webui_api_url": "你的SD地址(后面要加/)", + "gpt_count": 8, + "customize_gpt_prompt": "a93b693e-bb3f-406d-9730-cba43a6585a2", + "character_select_model": "drop", + "image_generate_category": "mj", + "window_wh_bm_remember": true, + "window_wh_bm": { + "x": 1699, + "y": 230, + "width": 1936, + "height": 1048 + }, + "space_image": "C:\\Users\\27698\\Desktop\\LAITool\\resources\\image\\zhanwei.png", + "gpt_key": "gptkey" } \ No newline at end of file diff --git a/src/define/Tools/file.js b/src/define/Tools/file.js index 22877b8..86c0fe7 100644 --- a/src/define/Tools/file.js +++ b/src/define/Tools/file.js @@ -1,8 +1,7 @@ - -import fs from "fs" -import { isEmpty } from "lodash"; -import path from "path"; -const fspromises = fs.promises; +import fs from 'fs' +import { isEmpty } from 'lodash' +import path from 'path' +const fspromises = fs.promises /** * 判断文件或目录是否存在 @@ -10,39 +9,67 @@ const fspromises = fs.promises; * @returns true表示存在,false表示不存在 */ export async function CheckFileOrDirExist(path) { - try { - await fspromises.access(path); - return true; // 文件或目录存在 - } catch (error) { - return false; // 文件或目录不存在 - } + try { + await fspromises.access(path) + return true // 文件或目录存在 + } catch (error) { + return false // 文件或目录不存在 + } } // 检查文件夹是不是存在,不存在的话,创建 export async function CheckFolderExistsOrCreate(folderPath) { - try { - if (!(await CheckFileOrDirExist(folderPath))) { - await fspromises.mkdir(folderPath, { recursive: true }); - } - } catch (error) { - throw new Error(error); + try { + if (!(await CheckFileOrDirExist(folderPath))) { + await fspromises.mkdir(folderPath, { recursive: true }) } + } catch (error) { + throw new Error(error) + } } + /** * 拼接两个地址,返回拼接后的地址 * @param {*} rootPath 根目录的消息 * @param {*} subPath 子目录的消息 - * @returns + * @returns */ export function JoinPath(rootPath, subPath) { - // 判断第二个地址是不是存在,不存在返回null,存在返回拼接后的地址 - if (subPath && !isEmpty(subPath)) { - return path.resolve(rootPath, subPath) - } else { - return null - } + // 判断第二个地址是不是存在,不存在返回null,存在返回拼接后的地址 + if (subPath && !isEmpty(subPath)) { + return path.resolve(rootPath, subPath) + } else { + return null + } } +/** + * 删除指定的文件中里面所有的文件和文件夹 + * @param {*} folderPath 文件夹地址 + */ +export async function DeleteFolderAllFile(folderPath) { + try { + let folderIsExist = await CheckFileOrDirExist(folderPath) + if (!folderIsExist) { + throw new Error('目的文件夹不存在,' + folderPath) + } + // 开始删除 + let files = await fspromises.readdir(folderPath) + files.forEach(async (file) => { + const curPath = path.join(folderPath, file) + if ((await fspromises.stat(curPath)).isDirectory()) { + // 判断是不是文件夹 + await DeleteFolderAllFile(curPath) // 递归删除文件夹内容 + fspromises.rmdir(curPath) // 删除空文件夹 + } else { + // 删除文件 + fspromises.unlink(curPath) + } + }) + } catch (error) { + throw error + } +} /** * 拷贝一个文件或者是文件夹到指定的目标地址 @@ -51,65 +78,64 @@ export function JoinPath(rootPath, subPath) { * @param {*} checkParent 是否检查父文件夹是否存在,不存在的话创建,默认false,不检查,不存在直接创建 */ export async function CopyFileOrFolder(source, target, checkParent = false) { - try { - // 判断源文件或文件夹是不是存在 - if (!(await CheckFileOrDirExist(source))) { - throw new Error(`源文件或文件夹不存在: ${source}`); - } - // 判断父文件夹是否存在,不存在创建 - const parent_path = path.dirname(target); - let parentIsExist = await CheckFileOrDirExist(parent_path); - if (!parentIsExist) { - if (checkParent) { - throw new Error(`目的文件或文件夹的父文件夹不存在: ${parent_path}`); - } else { - await fspromises.mkdir(parent_path, { recursive: true }); - } - } - - // 判断是不是文件夹 - const isDirectory = await IsDirectory(source); - // 复制文件夹的逻辑 - async function copyDirectory(source, target) { - // 创建目标文件夹 - await fspromises.mkdir(target, { recursive: true }); - let entries = await fspromises.readdir(source, { withFileTypes: true }); - for (let entry of entries) { - let srcPath = path.join(source, entry.name); - let tgtPath = path.join(target, entry.name); - - if (entry.isDirectory()) { - await copyDirectory(srcPath, tgtPath); - } else { - await fspromises.copyFile(srcPath, tgtPath); - } - } - } - - if (isDirectory) { - // 创建目标文件夹 - await copyDirectory(source, target); - } else { - // 复制文件 - await fspromises.copyFile(source, target); - } - } catch (error) { - throw error; + try { + // 判断源文件或文件夹是不是存在 + if (!(await CheckFileOrDirExist(source))) { + throw new Error(`源文件或文件夹不存在: ${source}`) + } + // 判断父文件夹是否存在,不存在创建 + const parent_path = path.dirname(target) + let parentIsExist = await CheckFileOrDirExist(parent_path) + if (!parentIsExist) { + if (checkParent) { + throw new Error(`目的文件或文件夹的父文件夹不存在: ${parent_path}`) + } else { + await fspromises.mkdir(parent_path, { recursive: true }) + } } -} + // 判断是不是文件夹 + const isDirectory = await IsDirectory(source) + // 复制文件夹的逻辑 + async function copyDirectory(source, target) { + // 创建目标文件夹 + await fspromises.mkdir(target, { recursive: true }) + let entries = await fspromises.readdir(source, { withFileTypes: true }) + for (let entry of entries) { + let srcPath = path.join(source, entry.name) + let tgtPath = path.join(target, entry.name) + + if (entry.isDirectory()) { + await copyDirectory(srcPath, tgtPath) + } else { + await fspromises.copyFile(srcPath, tgtPath) + } + } + } + + if (isDirectory) { + // 创建目标文件夹 + await copyDirectory(source, target) + } else { + // 复制文件 + await fspromises.copyFile(source, target) + } + } catch (error) { + throw error + } +} /** * 判断一个文件地址是不是文件夹 * @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}`); - } + try { + const stat = await fspromises.stat(path) + return stat.isDirectory() + } catch (error) { + throw new Error(`获取文件夹信息失败: ${path}`) + } } /** * 将文件或者是文件夹备份到指定的文职 @@ -117,29 +143,27 @@ export async function IsDirectory(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); + 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) + } +} /** * 获取指定的文件夹下面的所有的指定的拓展名的文件 @@ -148,40 +172,44 @@ export async function BackupFileOrFolder(source_path, target_path) { * @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); + try { + // 判断当前是不是文件夹 + if (!(await IsDirectory(folderPath))) { + throw new Error('输入的不是有效的文件夹地址') } -} \ No newline at end of file + + 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) + } +} diff --git a/src/define/Tools/time.js b/src/define/Tools/time.js index 2056b0e..25e6cab 100644 --- a/src/define/Tools/time.js +++ b/src/define/Tools/time.js @@ -36,5 +36,10 @@ export function MillisecondsToTimeString(milliseconds) { const secondsFormatted = seconds.toString().padStart(2, '0') const msFormatted = ms.toString().padStart(3, '0') - return `${hoursFormatted}:${minutesFormatted}:${secondsFormatted}.${msFormatted}` + let timeString = `${hoursFormatted}:${minutesFormatted}:${secondsFormatted}.${msFormatted}` + + // 使用正则表达式检测并删除多余的小数点 + // 此正则表达式查找除了第一个小数点之外的所有小数点,并将它们替换为空字符串 + timeString = timeString.replace(/(\.\d+)\./g, '$1') + return timeString } diff --git a/src/define/api/apiUrlDefine.js b/src/define/api/apiUrlDefine.js index b0ce905..79368ed 100644 --- a/src/define/api/apiUrlDefine.js +++ b/src/define/api/apiUrlDefine.js @@ -1,56 +1,88 @@ -let apiUrl = [{ - label: "openai-hk", - value: "3d64e50e-79c0-49ec-a72d-7dfdf508dd04", - gpt_url: "https://api.openai-hk.com/v1/chat/completions", - mj_url: null, - buy_url: "https://openai-hk.com/?i=10196" -}, { - label: "通义千问", - value: "b630c69a-99e9-46bc-8d88-39a00bcc3d2a", - gpt_url: "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation", - mj_url: null, - buy_url: null -}, { - label: "DrawAPI(MJ)", - value: "2cabf684-ac48-4733-a427-8c41626f7d8f", - gpt_url: null, - mj_url: { - imagine: "https://mjapi.deepwl.net/api/mj/submit/imagine", - describe: "https://mjapi.deepwl.net/api/mj/submit/describe", - update_file: "https://mjapi.deepwl.net/api/mj/submit/upload-discord-images", - once_get_task: "https://mjapi.deepwl.net/api/mj/query/task/${id}", - get_task_list: "https://mjapi.deepwl.net/api/mj/task/list-by-condition" +let apiUrl = [ + { + label: 'LAI API', + value: 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65', + gpt_url: 'https://laitool.net/v1/chat/completions', + mj_url: { + imagine: 'https://laitool.net/mj/submit/imagine', + describe: 'https://laitool.net/mj/submit/describe', + update_file: 'https://laitool.net/mj/submit/upload-discord-images', + once_get_task: 'https://laitool.net/mj/task/${id}/fetch' + }, + d3_url: { + image: 'https://laitool.net/v1/images/generations' + }, + buy_url: 'https://laitool.net/register?aff=Zmdu' }, - d3_url: null, - buy_url: "https://mjapi.deepwl.net/#/home" -}, { - label: "ePhoneAPI", - value: "b8866543-8c27-4888-869c-00aa1eb31272", - gpt_url: "https://api.ephone.ai/v1/chat/completions", - mj_url: { - imagine: "https://api.ephone.ai/mj/submit/imagine", - describe: "https://api.ephone.ai/mj/submit/describe", - update_file: "https://api.ephone.ai/mj/submit/upload-discord-images", - once_get_task: "https://api.ephone.ai/mj/task/${id}/fetch", + { + label: 'openai-hk', + value: '3d64e50e-79c0-49ec-a72d-7dfdf508dd04', + gpt_url: 'https://api.openai-hk.com/v1/chat/completions', + mj_url: null, + buy_url: 'https://openai-hk.com/?i=10196' }, - d3_url: { - image: "https://api.ephone.ai/v1/images/generations" + { + label: '通义千问', + value: 'b630c69a-99e9-46bc-8d88-39a00bcc3d2a', + gpt_url: 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation', + mj_url: null, + buy_url: null }, - buy_url: "https://ephone.ai/register?aff=55XT" -}] + { + label: 'DrawAPI(MJ)', + value: '2cabf684-ac48-4733-a427-8c41626f7d8f', + gpt_url: null, + mj_url: { + imagine: 'https://mjapi.deepwl.net/api/mj/submit/imagine', + describe: 'https://mjapi.deepwl.net/api/mj/submit/describe', + update_file: 'https://mjapi.deepwl.net/api/mj/submit/upload-discord-images', + once_get_task: 'https://mjapi.deepwl.net/api/mj/query/task/${id}', + get_task_list: 'https://mjapi.deepwl.net/api/mj/task/list-by-condition' + }, + d3_url: null, + buy_url: 'https://mjapi.deepwl.net/#/home' + }, + { + label: 'ePhoneAPI', + value: 'b8866543-8c27-4888-869c-00aa1eb31272', + gpt_url: 'https://api.ephone.ai/v1/chat/completions', + mj_url: { + imagine: 'https://api.ephone.ai/mj/submit/imagine', + describe: 'https://api.ephone.ai/mj/submit/describe', + update_file: 'https://api.ephone.ai/mj/submit/upload-discord-images', + once_get_task: 'https://api.ephone.ai/mj/task/${id}/fetch' + }, + d3_url: { + image: 'https://api.ephone.ai/v1/images/generations' + }, + buy_url: 'https://ephone.ai/register?aff=55XT' + }, + { + label: 'KIMI', + value: 'b5c8c8c5-f3c4-4c88-b25c-7f5a3d5f9d1f', + gpt_url: 'https://api.moonshot.cn/v1/chat/completions', + mj_url: null, + d3_url: null, + buy_url: 'https://platform.moonshot.cn/console/account' + }, + { + label: 'DouBao', + value: 'd3f6a2a9-2d17-4c3b-8d7f-28356cfa676e', + gpt_url: 'https://ark.cn-beijing.volces.com/api/v3/chat/completions', + mj_url: null, + d3_url: null, + buy_url: 'https://www.volcengine.com/product/doubao' + } +] /** * 通过ID获取指定的数据(value) - * @param {*} id + * @param {*} id */ function getApiMessageByID(id) { - let mj_api_url_index = apiUrl.findIndex(item => item.value == id) + let mj_api_url_index = apiUrl.findIndex((item) => item.value == id) if (mj_api_url_index == -1) { - throw new Error("没有找到对应的MJ API的配置,请先检查配置") + throw new Error('没有找到对应的MJ API的配置,请先检查配置') } - -} -export { - apiUrl, - getApiMessageByID } +export { apiUrl, getApiMessageByID } diff --git a/src/define/db/model/Book/BookBackTaskListModel.ts b/src/define/db/model/Book/BookBackTaskListModel.ts index e32bb1d..d57269c 100644 --- a/src/define/db/model/Book/BookBackTaskListModel.ts +++ b/src/define/db/model/Book/BookBackTaskListModel.ts @@ -1,13 +1,16 @@ import Realm, { ObjectSchema } from 'realm' -import { BookBackTaskStatus, BookBackTaskType } from '../../../enum/bookEnum' +import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '../../../enum/bookEnum' export class BookBackTaskList extends Realm.Object { id: string bookId: string bookTaskId: string + bookTaskDetailId: string name: string // 任务名称,小说名+批次名+分镜名 type: BookBackTaskType status: BookBackTaskStatus + errorMessage: string | null + executeType: TaskExecuteType // 任务执行类型,手动还是自动 createTime: Date updateTime: Date @@ -17,9 +20,12 @@ export class BookBackTaskList extends Realm.Object { id: 'string', bookId: { type: 'string', indexed: true }, bookTaskId: { type: 'string', indexed: true }, + bookTaskDetailId: { type: 'string', indexed: true }, name: 'string', type: 'string', status: 'string', + errorMessage: 'string?', + executeType: { type: 'string', default: TaskExecuteType.AUTO }, createTime: 'date', updateTime: 'date' }, diff --git a/src/define/db/model/Book/book.ts b/src/define/db/model/Book/book.ts index c61059f..b2b8910 100644 --- a/src/define/db/model/Book/book.ts +++ b/src/define/db/model/Book/book.ts @@ -13,7 +13,8 @@ export class BookModel extends Realm.Object { audioPath: string | null updateTime: Date createTime: Date - test: string | null + version: string + subtitlePosition: string | null static schema: ObjectSchema = { name: 'Book', @@ -29,7 +30,8 @@ export class BookModel extends Realm.Object { imageFolder: 'string?', updateTime: 'date', createTime: 'date', - test: 'string?' + version: 'string', + subtitlePosition: 'string?' }, // 主键为_id primaryKey: 'id' diff --git a/src/define/db/model/Book/bookTaskDetail.ts b/src/define/db/model/Book/bookTaskDetail.ts index a886e59..c77644a 100644 --- a/src/define/db/model/Book/bookTaskDetail.ts +++ b/src/define/db/model/Book/bookTaskDetail.ts @@ -128,6 +128,7 @@ export class BookTaskDetailModel extends Realm.Object { prompt: string | null // 提示 adetailer: boolean // 是否开启修脸 sdConifg: SDConfig | null // SD配置 + subtitlePosition: string | null // 字幕位置 createTime: Date updateTime: Date @@ -156,6 +157,7 @@ export class BookTaskDetailModel extends Realm.Object { prompt: 'string?', adetailer: 'bool', sdConifg: 'SDConfig?', + subtitlePosition: 'string?', createTime: 'date', updateTime: 'date' }, diff --git a/src/define/db/model/SoftWare/software.ts b/src/define/db/model/SoftWare/software.ts index 2e00944..ecc3b5f 100644 --- a/src/define/db/model/SoftWare/software.ts +++ b/src/define/db/model/SoftWare/software.ts @@ -7,6 +7,10 @@ export class SoftwareModel extends Realm.Object { reverse_display_show: boolean reverse_show_book_striped: boolean reverse_data_table_size: ComponentSize + globalSetting: string // 通用设置的json字符串 + ttsSetting: string | null // TTS设置的json字符串 + writeSetting: string | null // 文案的相关配置的json字符串 + aiSetting: string | null // AI相关的配置的json字符串 static schema: ObjectSchema = { name: 'Software', @@ -15,7 +19,11 @@ export class SoftwareModel extends Realm.Object { theme: 'string', reverse_display_show: 'bool', reverse_show_book_striped: 'bool', - reverse_data_table_size: 'string' + reverse_data_table_size: 'string', + globalSetting: 'string', + ttsSetting: 'string?', // 可空 + writeSetting: 'string?', + aiSetting: 'string?' }, // 主键为_id primaryKey: 'id' diff --git a/src/define/db/service/Book/bookBackTaskListService.ts b/src/define/db/service/Book/bookBackTaskListService.ts index 4e4f71a..2775ba6 100644 --- a/src/define/db/service/Book/bookBackTaskListService.ts +++ b/src/define/db/service/Book/bookBackTaskListService.ts @@ -3,10 +3,19 @@ import path from 'path' import { BaseService } from '../baseService.js' import { define } from '../../../define.js' import { BookTaskModel } from '../../model/Book/bookTask.js' -import { BookTaskStatus } from '../../../enum/bookEnum.js' +import { + BookBackTaskStatus, + BookBackTaskType, + BookTaskStatus, + TaskExecuteType +} from '../../../enum/bookEnum.js' import { errorMessage, successMessage } from '../../../../main/generalTools.js' import { BaseRealmService } from './bookBasic' import { isEmpty } from 'lodash' +import { DefaultObject } from 'realm/dist/public-types/schema.js' +import { BookModel } from '../../model/Book/book.js' +import { OtherData } from '../../../enum/softwareEnum.js' +import { BookBackTaskList } from '../../model/Book/BookBackTaskListModel.js' const { v4: uuidv4 } = require('uuid') export class BookBackTaskListService extends BaseRealmService { @@ -32,13 +41,93 @@ export class BookBackTaskListService extends BaseRealmService { /** * 获取指定条件的后台任务队列 - * bookId 必填 + * bookId 和 status 必填一个 * @param query bookId,bookTaskId,name,type,status */ - getBookBackTaskList(query) { + GetBookBackTaskList(query) { try { - // if() + if (query == null) { + throw new Error('查询后台队列任务失败,没有查询条件') + } + if (isEmpty(query.bookId) && isEmpty(query.status)) { + throw new Error('查询后台队列任务失败,没有查询条件') + } + // 构建查询条件 + // 下面时可空的条件 + let queryString = '' + if (query.bookId) { + queryString = `bookId = ${query.bookId}` + } + if (query.bookTaskId) { + queryString += ` && bookTaskId = ${query.bookTaskId}` + } + if (query.name) { + queryString += ` && name = ${query.name}` + } + if (query.type) { + queryString += ` && type = ${query.type}` + } + if (query.status) { + queryString += ` && status = ${query.status}` + } + if (query.executeType) { + queryString += ` && executeType = ${query.executeType}` + } + + // 获取数据 + let tasks = this.realm + .objects('BookBackTaskList') + .filtered(queryString) + .sorted('createTime', true) + let res + if (query.count) { + res = tasks.slice(0, query.count) + } else { + res = tasks + } + + return successMessage( + res.toJSON(), + '查询后台队列任务成功', + 'BookBackTaskList_GetBookBackTaskList' + ) + } catch (error) { + throw error + } + } + + /** + * 获取等待状态的任务和返回指定数量的数据 + * @param executeType 任务的执行类型 + * @param count 返回数据的数量 + */ + GetWaitTaskAndSlice(executeType: TaskExecuteType, count: number) { + try { + let tasks = this.realm + .objects('BookBackTaskList') + .filtered( + 'status == $0 && executeType == $1', + BookBackTaskStatus.WAIT, + executeType ? executeType : TaskExecuteType.AUTO + ) + .sorted('createTime', false) + + if (count != null) { + tasks = tasks.slice(0, count) as unknown as Realm.Results + } + let res = Array.from(tasks).map((item) => { + let resObj = { + ...item + } + return resObj + }) + + return successMessage( + res, + '查询等待状态的后台队列任务成功', + 'BookBackTaskList_GetWaitTaskAndSlice' + ) } catch (error) { throw error } @@ -48,25 +137,61 @@ export class BookBackTaskListService extends BaseRealmService { * 新增一个小说相关的后台任务队列 * @param bookBackTask 要添加的小说数据 */ - AddBookBackTaskList(bookBackTask) { + async AddBookBackTask( + bookId: string, + taskType: BookBackTaskType, + executeType = TaskExecuteType.AUTO, + bookTaskId = null, + bookTaskDetailId = null + ) { try { - // 判断数据是不是存在 - if ( - isEmpty(bookBackTask.bookId) || - isEmpty(bookBackTask.bookTaskId) || - isEmpty(bookBackTask.name) || - isEmpty(bookBackTask.type) - ) { - throw new Error('新增后台队列任务到数据库失败,数据不完整,缺少必要字段') + // 通过bookid获取book信息 + let book = this.realm.objectForPrimaryKey('Book', bookId) + if (book == null) { + throw new Error('新增后台队列任务到数据库失败,没有找到对应的小说') + } + let bookTask + if (bookTaskId) { + bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId) + if (bookTask == null) { + throw new Error('新增后台队列任务到数据库失败,没有找到对应的小说批次任务') + } + } + + let bookTaskDetail + if (bookTaskDetailId) { + bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId) + if (bookTaskDetail == null) { + throw new Error( + '新增后台队列任务到数据库失败,没有找到对应的小说批次任务详情(分镜数据)' + ) + } + } + + // 开始往数据库中写数据 + let name = `${book.name}-${bookTask ? bookTask.name : 'default'}-${ + bookTaskDetail ? bookTaskDetail.name : 'default' + }-${taskType}` + + let bookBackTask = { + id: uuidv4(), + bookId: bookId, + bookTaskId: bookTaskId ? bookTaskId : OtherData.DEFAULT, + bookTaskDetailId: bookTaskDetailId ? bookTaskDetailId : OtherData.DEFAULT, + name: name, + type: taskType, + executeType: executeType, + status: BookBackTaskStatus.WAIT, + createTime: new Date(), + updateTime: new Date() } - // 开始新建 - bookBackTask.id = uuidv4() - bookBackTask.createTime = new Date() - bookBackTask.updateTime = new Date() - bookBackTask.status = BookTaskStatus.WAIT this.realm.write(() => { this.realm.create('BookBackTaskList', bookBackTask) }) + + // 添加成功之后,调用开始执行任务的方法 + await global.taskManager.ExecuteAutoTask() + return successMessage( bookBackTask, '新增后台队列任务到数据库成功', @@ -74,24 +199,25 @@ export class BookBackTaskListService extends BaseRealmService { ) } catch (error) { return errorMessage( - '新增后台队列任务到数据库失败,错误信息入校' + error.toString(), + '新增后台队列任务到数据库失败,错误信息如下' + error.toString(), 'BookBackTaskList_AddBookBackTaskList' ) } } /** - * 修改一个小说相关的后台任务队列中的详细信息(对于后台的队列任务只能修改状态) + * 修改一个小说相关的后台任务队列中的详细信息 + * (对于后台的队列任务只能修改状态)和错误信息 * @param bookBackTask 修改的数据 */ - async ModifyBookBackTaskList(bookBackTask) { + UpdateTaskStatus(bookBackTask) { try { // 判断数据是不是存在 if (isEmpty(bookBackTask.id) || isEmpty(bookBackTask.status)) { throw new Error('修改后台队列任务失败,数据不完整,缺少必要字段') } // 开始修改 - this.realm.write(() => { + this.transaction(() => { // 获取指定ID的队列任务 let _bookBackTask = this.realm.objectForPrimaryKey('BookBackTaskList', bookBackTask.id) // 判断数据是不是存在 @@ -100,46 +226,50 @@ export class BookBackTaskListService extends BaseRealmService { } // 修改数据 _bookBackTask.status = bookBackTask.status + if (bookBackTask.errorMessage) { + _bookBackTask.errorMessage = bookBackTask.errorMessage + } }) return successMessage( bookBackTask, - '修改后台队列任务成功', + '修改后台队列任务状态成功', 'BookBackTaskList_ModifyBookBackTaskList' ) } catch (error) { - return errorMessage( - '修改后台队列任务失败,错误信息如下:' + error.toString(), - 'BookBackTaskList_ModifyBookBackTaskList' - ) + throw error } } /** * 删除满足条件的数据,包含 id、bookId、bookTaskId + * 上面的条件,至少要有一个 * @param bookBackTask 删除的数据 */ - async DeleteBookBackTaskListBy(bookBackTask) { + async DeleteBookBackTask(bookBackTask) { try { - this.realm.write(() => { + if ( + !bookBackTask.hasOwnProperty('id') && + !bookBackTask.hasOwnProperty('bookId') && + !bookBackTask.hasOwnProperty('bookTaskId') + ) { + throw new Error('删除后台队列任务失败,缺少必要的删除条件') + } + + this.transaction(() => { // 构建查询条件 - let query = [] as string[] - if (bookBackTask.id) { - query.push(`id = ${bookBackTask.id}`) - } + const tasksToDelete = this.realm + .objects('BookBackTaskList') + .filtered('id == $0', bookBackTask.id) + if (bookBackTask.bookId) { - query.push(`bookId = ${bookBackTask.bookId}`) + tasksToDelete.filtered('bookId == $0', bookBackTask.bookId) } if (bookBackTask.bookTaskId) { - query.push(`bookTaskId = ${bookBackTask.bookTaskId}`) - } - const queryString = query.join(' && ') - // 获取指定的数据 - if (queryString) { - const tasksToDelete = this.realm.objects('BookBackTaskList').filtered(queryString) - this.realm.delete(tasksToDelete) - } else { - throw new Error('删除后台队列任务失败,没有筛选条件') + tasksToDelete.filtered('bookTaskId == $0', bookBackTask.bookTaskId) } + + this.realm.delete(tasksToDelete) + return successMessage( bookBackTask, '删除后台队列任务成功', diff --git a/src/define/db/service/Book/bookBasic.ts b/src/define/db/service/Book/bookBasic.ts index 0ee1c5b..d6c8953 100644 --- a/src/define/db/service/Book/bookBasic.ts +++ b/src/define/db/service/Book/bookBasic.ts @@ -12,6 +12,8 @@ import { WebuiConfig } from '../../model/Book/bookTaskDetail' import { BookBackTaskList } from '../../model/Book/BookBackTaskListModel' +import { TaskExecuteType } from '../../../enum/bookEnum' +import { version } from '../../../../../package.json' let dbPath = path.resolve(define.db_path, 'book.realm') @@ -39,6 +41,42 @@ const migration = (oldRealm: Realm, newRealm: Realm) => { newBookTask[i].audioPath = null // 为新属性设置默认值 } } + if (oldRealm.schemaVersion < 4) { + const oldBookTask = oldRealm.objects('BookBackTaskList') + const newBookTask = newRealm.objects('BookBackTaskList') + for (let i = 0; i < oldBookTask.length; i++) { + newBookTask[i].errorMessage = null // 设置错误信息的默认值 + } + } + if (oldRealm.schemaVersion < 5) { + const oldBookTask = oldRealm.objects('BookBackTaskList') + const newBookTask = newRealm.objects('BookBackTaskList') + for (let i = 0; i < oldBookTask.length; i++) { + newBookTask[i].executeType = TaskExecuteType.AUTO // 设置错误信息的默认值 + } + } + if (oldRealm.schemaVersion < 6) { + const oldBookTask = oldRealm.objects('BookBackTaskList') + const newBookTask = newRealm.objects('BookBackTaskList') + for (let i = 0; i < oldBookTask.length; i++) { + newBookTask[i].bookTaskDetailId = 'default' // 设置错误信息的默认值 + } + } + if (oldRealm.schemaVersion < 7) { + const oldBookTask = oldRealm.objects('Book') + const newBookTask = newRealm.objects('Book') + for (let i = 0; i < oldBookTask.length; i++) { + newBookTask[i].version = version // 设置版本的默认值 + newBookTask[i].subtitlePosition = null // 设置字幕位置的默认值 + } + } + if (oldRealm.schemaVersion < 8) { + const oldBookTask = oldRealm.objects('BookTaskDetail') + const newBookTask = newRealm.objects('BookTaskDetail') + for (let i = 0; i < oldBookTask.length; i++) { + newBookTask[i].subtitlePosition = null // 设置字幕位置的默认值 + } + } } export class BaseRealmService extends BaseService { @@ -79,7 +117,7 @@ export class BaseRealmService extends BaseService { BookTaskDetailModel ], path: this.dbpath, - schemaVersion: 3, + schemaVersion: 8, migration: migration } this.realm = await Realm.open(config) diff --git a/src/define/db/service/Book/bookService.ts b/src/define/db/service/Book/bookService.ts index 9ab9660..cd8b6c3 100644 --- a/src/define/db/service/Book/bookService.ts +++ b/src/define/db/service/Book/bookService.ts @@ -237,7 +237,7 @@ export class BookService extends BaseRealmService { delete book.imageFolder // 修改数据 book.updateTime = new Date() - this.realm.write(() => { + this.transaction(() => { this.realm.create('Book', book, UpdateMode.Modified) }) @@ -248,4 +248,44 @@ export class BookService extends BaseRealmService { throw error } } + + /** + * 修改小说数据 + * @param bookId 小说的ID + * @param bookData 要修改的小说数据 + */ + async UpdateBookData(bookId: string, bookData) { + try { + if (bookId == null) { + throw new Error('修改小说数据失败,缺少小说ID') + } + if (bookData == null) { + throw new Error('修改小说数据失败,缺少小说数据') + } + + // 检查小说ID对应的数据是不是存在 + let bookRes = this.GetBookDataById(bookId) + if (bookRes.data == null) { + throw new Error('修改小说数据失败,小说ID对应的数据不存在') + } + + if (bookData && bookData.id) { + delete bookData.id + } + + // 开始修改 + this.transaction(() => { + this.realm.create('Book', { id: bookId, ...bookData }, UpdateMode.Modified) + }) + + bookRes = this.GetBookDataById(bookId) + if (bookRes.data == null) { + throw new Error('获取修改后的小说数据失败,小说ID对应的数据不存在') + } + + return successMessage(bookRes.data, '修改小说数据成功', 'ReverseBook_UpdateBookData') + } catch (error) { + throw error + } + } } diff --git a/src/define/db/service/Book/bookTaskDetailService.ts b/src/define/db/service/Book/bookTaskDetailService.ts index 50cab9c..b0ec4b6 100644 --- a/src/define/db/service/Book/bookTaskDetailService.ts +++ b/src/define/db/service/Book/bookTaskDetailService.ts @@ -10,6 +10,7 @@ import { endsWith, isEmpty } from 'lodash' import { book } from '../../../../preload/book.js' import { DefaultObject } from 'realm/dist/public-types/schema.js' import { JoinPath } from '../../../Tools/file.js' +import { BookTaskDetailModel } from '../../model/Book/bookTaskDetail.js' const { v4: uuidv4 } = require('uuid') let dbPath = path.resolve(define.db_path, 'book.realm') @@ -47,28 +48,20 @@ export class BookTaskDetailService extends BaseRealmService { if (condition == null) { throw new Error('查询小说分镜信息,查询条件不能为空') } - let query = [] as string[] + let tasksToDelete = this.realm.objects('BookTaskDetail') if (condition.id) { - query.push(`id = ${condition.id}`) + tasksToDelete = tasksToDelete.filtered('id==$0', condition.id) } if (condition.bookId) { - query.push(`bookId = ${condition.bookId}`) + tasksToDelete = tasksToDelete.filtered('bookId==$0', condition.bookId) } if (condition.bookTaskId) { - query.push(`bookTaskId = ${condition.bookTaskId}`) + tasksToDelete = tasksToDelete.filtered('bookTaskId==$0', condition.bookTaskId) } if (condition.name) { - query.push(`name = ${condition.name}`) - } - const queryString = query.join(' && ') - let tasksToDelete: Realm.Results & DefaultObject> - // 获取指定的数据 - if (queryString) { - tasksToDelete = this.realm.objects('BookTaskDetail').filtered(queryString) - } else { - // 返回全部 - tasksToDelete = this.realm.objects('BookTaskDetail') + tasksToDelete = tasksToDelete.filtered('name==$0', condition.name) } + let resData = Array.from(tasksToDelete).map((item) => { let resObj = { ...item, @@ -138,19 +131,20 @@ export class BookTaskDetailService extends BaseRealmService { let bookTaskDetails = this.realm .objects('BookTaskDetail') .filtered( - 'bookId = $0 AND bookTaskId = $1', + 'bookId == $0 AND bookTaskId == $1', bookTaskDetail.bookId, bookTaskDetail.bookTaskId ) let maxNo = bookTaskDetails.max('no') bookTaskDetail.no = maxNo ? Number(maxNo) + 1 : 1 - let name = bookTaskDetail.no.tosString().padStart(5, '0') + let name = bookTaskDetail.no.toString().padStart(5, '0') bookTaskDetail.name = name bookTaskDetail.id = uuidv4() bookTaskDetail.createTime = new Date() bookTaskDetail.updateTime = new Date() + bookTaskDetail.adetailer = false // 先写死false // 开始添加 this.transaction(() => { this.realm.create('BookTaskDetail', bookTaskDetail) @@ -203,25 +197,20 @@ export class BookTaskDetailService extends BaseRealmService { if (isEmpty(condition.id) && isEmpty(condition.bookTaskId) && isEmpty(condition.bookId)) { throw new Error('删除小说分镜信息失败,没有必要参数') } - let query = [] as string[] + let tasksToDelete = this.realm.objects('BookTaskDetail') if (condition.id) { - query.push(`id = ${condition.id}`) + tasksToDelete = tasksToDelete.filtered('id==$0', condition.id) } if (condition.bookId) { - query.push(`bookId = ${condition.bookId}`) + tasksToDelete = tasksToDelete.filtered('bookId==$0', condition.bookId) } if (condition.bookTaskId) { - query.push(`bookTaskId = ${condition.bookTaskId}`) + tasksToDelete = tasksToDelete.filtered('bookTaskId==$0', condition.bookTaskId) } if (condition.name) { - query.push(`name = ${condition.name}`) + tasksToDelete = tasksToDelete.filtered('name==$0', condition.name) } - if (query.length <= 0) { - throw new Error('删除小说分镜任务失败,没有查询条件') - } - const queryString = query.join(' && ') - let tasksToDelete = this.realm.objects('BookTaskDetail').filtered(queryString) this.transaction(() => { this.realm.delete(tasksToDelete) }) diff --git a/src/define/db/service/Book/bookTaskService.ts b/src/define/db/service/Book/bookTaskService.ts index 4766f52..8e76f0c 100644 --- a/src/define/db/service/Book/bookTaskService.ts +++ b/src/define/db/service/Book/bookTaskService.ts @@ -3,11 +3,12 @@ import path from 'path' import { BaseService } from '../baseService.js' import { define } from '../../../define.js' import { BookTaskModel } from '../../model/Book/bookTask.js' -import { BookTaskStatus } from '../../../enum/bookEnum.js' +import { BookBackTaskStatus, BookTaskStatus } from '../../../enum/bookEnum.js' import { successMessage } from '../../../../main/generalTools.js' import { BaseRealmService } from './bookBasic' import { isEmpty } from 'lodash' import { JoinPath } from '../../../Tools/file.js' +import { BookBackTaskList } from '../../model/Book/BookBackTaskListModel.js' const { v4: uuidv4 } = require('uuid') let dbPath = path.resolve(define.db_path, 'book.realm') @@ -153,6 +154,35 @@ export class BookTaskService extends BaseRealmService { } } + /** + * 修改后台等待的所有任务的状态为fial。 + * 并且错误信息为任务被丢弃 + * @param bookId + * @param bookTaskId + */ + UpdetedBookTaskToFail(bookId: string, bookTaskId: string) { + try { + this.transaction(() => { + let updateData = this.realm + .objects('BookBackTaskList') + .filtered( + 'bookId == $0 AND bookTaskId == $1 AND status == $2', + bookId, + bookTaskId, + BookBackTaskStatus.WAIT + ) + + // 修改 + updateData.forEach((data) => { + data.status = BookBackTaskStatus.FAIL + data.errorMessage = '任务被丢弃' + }) + }) + } catch (error) { + throw error + } + } + // 添加一条数据 AddOrModifyBookTask(bookTask) { try { diff --git a/src/define/db/service/SoftWare/mjSettingService.ts b/src/define/db/service/SoftWare/mjSettingService.ts index 5510f5a..b19828c 100644 --- a/src/define/db/service/SoftWare/mjSettingService.ts +++ b/src/define/db/service/SoftWare/mjSettingService.ts @@ -151,17 +151,14 @@ export class MJSettingService extends BaseSoftWareService { GetAPIMjSetting(apiQuery) { try { let apiMjSettings = this.realm.objects('APIMj') - if (apiQuery?.id) { apiMjSettings = this.realm.objects('APIMj').filtered('id = $0', apiQuery.id) } - let resApiMj = Array.from(apiMjSettings).map((apiMj) => { return { ...apiMj } }) - return successMessage(resApiMj, '获取API配置成功', 'MJSettingService_GetAPIMjSetting') } catch (error) { throw error @@ -533,7 +530,6 @@ export class MJSettingService extends BaseSoftWareService { let apiSettings = this.GetAPIMjSetting(null) // 获取代理模式的配置信息 let remoteSettings = this.GetRemoteMJSettings(null) - // 获取浏览器模式的配置信息 let browserSettings = this.GetBrowserMJSetting(null) let mjSetting = mjSettings.data[0] diff --git a/src/define/db/service/SoftWare/softwareBasic.ts b/src/define/db/service/SoftWare/softwareBasic.ts index b3be029..96a445f 100644 --- a/src/define/db/service/SoftWare/softwareBasic.ts +++ b/src/define/db/service/SoftWare/softwareBasic.ts @@ -99,6 +99,38 @@ const migration = (oldRealm: Realm, newRealm: Realm) => { } }) } + if (oldRealm.schemaVersion < 14) { + newRealm.write(() => { + const newSoftwares = newRealm.objects('Software') + for (let software of newSoftwares) { + software.globalSetting = null // 默认都是启用的 + } + }) + } + if (oldRealm.schemaVersion < 15) { + newRealm.write(() => { + const newSoftwares = newRealm.objects('Software') + for (let software of newSoftwares) { + software.ttsSetting = null // 默认为空 + } + }) + } + if (oldRealm.schemaVersion < 16) { + newRealm.write(() => { + const newSoftwares = newRealm.objects('Software') + for (let software of newSoftwares) { + software.writeSetting = null // 文案的默认设置 + } + }) + } + if (oldRealm.schemaVersion < 17) { + newRealm.write(() => { + const newSoftwares = newRealm.objects('Software') + for (let software of newSoftwares) { + software.aiSetting = null // AI的默认设置 + } + }) + } } export class BaseSoftWareService extends BaseService { @@ -137,7 +169,7 @@ export class BaseSoftWareService extends BaseService { MjSettingModel ], path: dbPath, - schemaVersion: 13, // 当前版本号 + schemaVersion: 17, // 当前版本号 migration: migration } // 判断当前全局是不是又当前这个 diff --git a/src/define/db/service/SoftWare/softwareService.ts b/src/define/db/service/SoftWare/softwareService.ts index a040fe1..e2d9895 100644 --- a/src/define/db/service/SoftWare/softwareService.ts +++ b/src/define/db/service/SoftWare/softwareService.ts @@ -24,13 +24,13 @@ export class SoftwareService extends BaseSoftWareService { SoftwareService.instance = new SoftwareService() await super.getInstance() } + await SoftwareService.instance.open() return SoftwareService.instance } // 修改数据库中行中的某个属性数据 - async UpdateSoftware(software) { + UpdateSoftware(software) { try { - await this.open() this.realm.write(() => { this.realm.create('Software', software, UpdateMode.Modified) }) @@ -41,13 +41,18 @@ export class SoftwareService extends BaseSoftWareService { } } - async AddSfotware(software) { + /** + * 添加软件配置信息 + * @param software 软件配置信息 + * @returns + */ + AddSfotware(software) { try { - await this.open() software.id = uuidv4() this.realm.write(() => { this.realm.create('Software', software) }) + return successMessage(null, '添加软件配置信息成功', 'SoftwareService_AddSfotware') } catch (error) { throw error } @@ -56,9 +61,8 @@ export class SoftwareService extends BaseSoftWareService { /** * 或软件基础配置信息 */ - async GetSoftwareData() { + GetSoftwareData() { try { - await this.open() let software = this.realm.objects('Software') return successMessage( software.toJSON(), @@ -73,6 +77,55 @@ export class SoftwareService extends BaseSoftWareService { throw error } } + + /** + * 获取当前软件指定属性的数据 + * @param property 属性名称 + */ + GetSoftWarePropertyData(property: string) { + try { + let software = this.realm.objects('Software') + if (software.length <= 0) { + throw new Error('数据库中没有软件配置信息') + } + + let softwareData = software.toJSON()[0] + let res = softwareData[property] + + return successMessage(res, '获取软件配置信息成功', 'SoftwareService_GetSoftWarePropertyData') + } catch (error) { + global.logger.error( + 'SoftwareService_GetSoftWarePropertyData', + '获取软件的基础设置失败 ,错误信息如下:' + error.toString() + ) + throw error + } + } + + /** + * 保存软件的指定属性的设置信息 + * @param property 属性的名称 + * @param data 数据 + * @returns + */ + SaveSoftwarePropertyData(property: string, data: string) { + try { + this.transaction(() => { + let software = this.realm.objects('Software') + // 遍历修改 + for (let item of software) { + item[property] = data + } + }) + return successMessage( + null, + '保存软件配置信息成功', + 'SoftwareService_SaveSoftwarePropertyData' + ) + } catch (error) { + throw error + } + } } export default SoftwareService diff --git a/src/define/db/service/serviceBase.ts b/src/define/db/service/serviceBase.ts new file mode 100644 index 0000000..7b7b7e0 --- /dev/null +++ b/src/define/db/service/serviceBase.ts @@ -0,0 +1,13 @@ +import SoftwareService from './SoftWare/softwareService' + +export class ServiceBase { + softService: SoftwareService + + constructor() {} + + async InitService() { + if (!this.softService) { + this.softService = await SoftwareService.getInstance() + } + } +} diff --git a/src/define/define.js b/src/define/define.js index 177a442..2941105 100644 --- a/src/define/define.js +++ b/src/define/define.js @@ -1,78 +1,145 @@ -const path = require("path") -const { app } = require('electron'); +const path = require('path') +const { app } = require('electron') let define = {} if (!app.isPackaged) { - define = { - discordScript: path.join(__dirname, '../../src/main/discord/discordScript.js'), - zhanwei_image: path.join(__dirname, "../../resources/image/zhanwei.png"), - config_path: path.join(__dirname, "../../resources/config/global_setting.json"), - clip_setting: path.join(__dirname, "../../resources/config/clip_setting.json"), - sd_setting: path.join(__dirname, "../../resources/config/sd_config.json"), - dynamic_setting: path.join(__dirname, "../../resources/config/dynamic_setting.json"), - tag_setting: path.join(__dirname, "../../resources/config/tag_setting.json"), - 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"), - db_path: path.join(__dirname, "../../resources/scripts/db"), - project_path: path.join(__dirname, "../../project"), - 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"), - draft_temp_path: path.join(__dirname, "../../resources/tmp/temp.zip"), - clip_speed_temp_path: path.join(__dirname, "../../resources/tmp/Clip/speeds_tmp.json"), - add_canvases_temp_path: path.join(__dirname, "../../resources/tmp/Clip/canvases_tmp.json"), - add_sound_channel_mappings_temp_path: path.join(__dirname, "../../resources/tmp/Clip/sound_channel_mappings_tmp.json"), - add_vocal_separations_temp_path: path.join(__dirname, "../../resources/tmp/Clip/vocal_separations_tmp.json"), - add_material_video_temp_path: path.join(__dirname, "../../resources/tmp/Clip/videoMaterialTemp.json"), - add_tracks_segments_temp_path: path.join(__dirname, "../../resources/tmp/Clip/tracks_segments_tmp.json"), - add_tracks_type_temp_path: path.join(__dirname, "../../resources/tmp/Clip/tracks_type_tmp.json"), - add_material_animations_temp_path: path.join(__dirname, "../../resources/tmp/Clip/material_animations_tmp.json"), - add_material_text_temp_path: path.join(__dirname, "../../resources/tmp/Clip/material_text_temp.json"), - add_track_text_segments_temp_path: path.join(__dirname, "../../resources/tmp/Clip/track_text_segments_temp.json"), - add_materials_beats_tmp_path: path.join(__dirname, "../../resources/tmp/Clip/materials_beats_tmp.json"), - add_materials_audios_tmp_path: path.join(__dirname, "../../resources/tmp/Clip/materials_audios_tmp.json"), - add_tracks_audio_segments_tmp_path: path.join(__dirname, "../../resources/tmp/Clip/tracks_audio_segments_tmp.json"), - add_keyframe_tmp_path: path.join(__dirname, "../../resources/tmp/Clip/keyframe_tmp.json"), - } + define = { + discordScript: path.join(__dirname, '../../src/main/discord/discordScript.js'), + zhanwei_image: path.join(__dirname, '../../resources/image/zhanwei.png'), + config_path: path.join(__dirname, '../../resources/config/global_setting.json'), + clip_setting: path.join(__dirname, '../../resources/config/clip_setting.json'), + sd_setting: path.join(__dirname, '../../resources/config/sd_config.json'), + dynamic_setting: path.join(__dirname, '../../resources/config/dynamic_setting.json'), + tag_setting: path.join(__dirname, '../../resources/config/tag_setting.json'), + 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'), + db_path: path.join(__dirname, '../../resources/scripts/db'), + project_path: path.join(__dirname, '../../project'), + 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'), + draft_temp_path: path.join(__dirname, '../../resources/tmp/temp.zip'), + init_config_path: path.join(__dirname, '../../resources/tmp/config'), + clip_speed_temp_path: path.join(__dirname, '../../resources/tmp/Clip/speeds_tmp.json'), + add_canvases_temp_path: path.join(__dirname, '../../resources/tmp/Clip/canvases_tmp.json'), + add_sound_channel_mappings_temp_path: path.join( + __dirname, + '../../resources/tmp/Clip/sound_channel_mappings_tmp.json' + ), + add_vocal_separations_temp_path: path.join( + __dirname, + '../../resources/tmp/Clip/vocal_separations_tmp.json' + ), + add_material_video_temp_path: path.join( + __dirname, + '../../resources/tmp/Clip/videoMaterialTemp.json' + ), + add_tracks_segments_temp_path: path.join( + __dirname, + '../../resources/tmp/Clip/tracks_segments_tmp.json' + ), + add_tracks_type_temp_path: path.join( + __dirname, + '../../resources/tmp/Clip/tracks_type_tmp.json' + ), + add_material_animations_temp_path: path.join( + __dirname, + '../../resources/tmp/Clip/material_animations_tmp.json' + ), + add_material_text_temp_path: path.join( + __dirname, + '../../resources/tmp/Clip/material_text_temp.json' + ), + add_track_text_segments_temp_path: path.join( + __dirname, + '../../resources/tmp/Clip/track_text_segments_temp.json' + ), + add_materials_beats_tmp_path: path.join( + __dirname, + '../../resources/tmp/Clip/materials_beats_tmp.json' + ), + add_materials_audios_tmp_path: path.join( + __dirname, + '../../resources/tmp/Clip/materials_audios_tmp.json' + ), + add_tracks_audio_segments_tmp_path: path.join( + __dirname, + '../../resources/tmp/Clip/tracks_audio_segments_tmp.json' + ), + add_keyframe_tmp_path: path.join(__dirname, '../../resources/tmp/Clip/keyframe_tmp.json') + } } else { - define = { - zhanwei_image: path.join(__dirname, "../../../resources/image/zhanwei.png"), - config_path: path.join(__dirname, "../../../resources/config/global_setting.json"), - clip_setting: path.join(__dirname, "../../../resources/config/clip_setting.json"), - sd_setting: path.join(__dirname, "../../../resources/config/sd_config.json"), - dynamic_setting: path.join(__dirname, "../../../resources/config/dynamic_setting.json"), - tag_setting: path.join(__dirname, "../../../resources/config/tag_setting.json"), - 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"), - db_path: path.join(__dirname, "../../../resources/scripts/db"), - project_path: path.join(__dirname, "../../../project"), - 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"), - temp_sd_image: path.join(__dirname, "../../../resources/image/TempSDImage"), - draft_temp_path: path.join(__dirname, "../../../resources/tmp/temp.zip"), - clip_speed_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/speeds_tmp.json"), - add_canvases_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/canvases_tmp.json"), - add_sound_channel_mappings_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/sound_channel_mappings_tmp.json"), - add_vocal_separations_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/vocal_separations_tmp.json"), - add_material_video_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/videoMaterialTemp.json"), - add_tracks_segments_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/tracks_segments_tmp.json"), - add_tracks_type_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/tracks_type_tmp.json"), - add_material_animations_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/material_animations_tmp.json"), - add_material_text_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/material_text_temp.json"), - add_track_text_segments_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/track_text_segments_temp.json"), - add_materials_beats_tmp_path: path.join(__dirname, "../../../resources/tmp/Clip/materials_beats_tmp.json"), - add_materials_audios_tmp_path: path.join(__dirname, "../../../resources/tmp/Clip/materials_audios_tmp.json"), - add_tracks_audio_segments_tmp_path: path.join(__dirname, "../../../resources/tmp/Clip/tracks_audio_segments_tmp.json"), - add_keyframe_tmp_path: path.join(__dirname, "../../../resources/tmp/Clip/keyframe_tmp.json"), - } + define = { + zhanwei_image: path.join(__dirname, '../../../resources/image/zhanwei.png'), + config_path: path.join(__dirname, '../../../resources/config/global_setting.json'), + clip_setting: path.join(__dirname, '../../../resources/config/clip_setting.json'), + sd_setting: path.join(__dirname, '../../../resources/config/sd_config.json'), + dynamic_setting: path.join(__dirname, '../../../resources/config/dynamic_setting.json'), + tag_setting: path.join(__dirname, '../../../resources/config/tag_setting.json'), + 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'), + db_path: path.join(__dirname, '../../../resources/scripts/db'), + project_path: path.join(__dirname, '../../../project'), + 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'), + temp_sd_image: path.join(__dirname, '../../../resources/image/TempSDImage'), + draft_temp_path: path.join(__dirname, '../../../resources/tmp/temp.zip'), + init_config_path: path.join(__dirname, '../../../resources/tmp/config'), + clip_speed_temp_path: path.join(__dirname, '../../../resources/tmp/Clip/speeds_tmp.json'), + add_canvases_temp_path: path.join(__dirname, '../../../resources/tmp/Clip/canvases_tmp.json'), + add_sound_channel_mappings_temp_path: path.join( + __dirname, + '../../../resources/tmp/Clip/sound_channel_mappings_tmp.json' + ), + add_vocal_separations_temp_path: path.join( + __dirname, + '../../../resources/tmp/Clip/vocal_separations_tmp.json' + ), + add_material_video_temp_path: path.join( + __dirname, + '../../../resources/tmp/Clip/videoMaterialTemp.json' + ), + add_tracks_segments_temp_path: path.join( + __dirname, + '../../../resources/tmp/Clip/tracks_segments_tmp.json' + ), + add_tracks_type_temp_path: path.join( + __dirname, + '../../../resources/tmp/Clip/tracks_type_tmp.json' + ), + add_material_animations_temp_path: path.join( + __dirname, + '../../../resources/tmp/Clip/material_animations_tmp.json' + ), + add_material_text_temp_path: path.join( + __dirname, + '../../../resources/tmp/Clip/material_text_temp.json' + ), + add_track_text_segments_temp_path: path.join( + __dirname, + '../../../resources/tmp/Clip/track_text_segments_temp.json' + ), + add_materials_beats_tmp_path: path.join( + __dirname, + '../../../resources/tmp/Clip/materials_beats_tmp.json' + ), + add_materials_audios_tmp_path: path.join( + __dirname, + '../../../resources/tmp/Clip/materials_audios_tmp.json' + ), + add_tracks_audio_segments_tmp_path: path.join( + __dirname, + '../../../resources/tmp/Clip/tracks_audio_segments_tmp.json' + ), + add_keyframe_tmp_path: path.join(__dirname, '../../../resources/tmp/Clip/keyframe_tmp.json') + } } -define["remotemj_api"] = "https://api.laitool.net/" -define["API"] = "f85d39ed5a40fd09966f13f12b6cf0f0" -export { - define -}; \ No newline at end of file +define['remotemj_api'] = 'https://api.laitool.net/' +define['serverUrl'] = 'http://lapi.laitool.cn' +define['API'] = 'f85d39ed5a40fd09966f13f12b6cf0f0' +export { define } \ No newline at end of file diff --git a/src/define/define_string.js b/src/define/define_string.js index 03d2208..5ce4881 100644 --- a/src/define/define_string.js +++ b/src/define/define_string.js @@ -120,6 +120,11 @@ export const DEFINE_STRING = { SAVE_WORD_TXT: 'SAVE_WORD_TXT', GET_KEY_FRAME_CONFIG_DATA: 'GET_KEY_FRAME_CONFIG_DATA', GET_KEYFRAME_OPTIONS: 'GET_KEYFRAME_OPTIONS', + GPT: { + INIT_SERVER_GPT_OPTIONS: 'INIT_SERVER_GPT_OPTIONS', + GET_AI_SETTING: 'GET_AI_SETTING', + SAVE_AI_SETTING: 'SAVE_AI_SETTING' + }, QUEUE_BATCH: { SD_ORIGINAL_GENERATE_IMAGE: 'SD_ORIGINAL_GENERATE_IMAGE', @@ -195,7 +200,11 @@ export const DEFINE_STRING = { GET_BOOK_DATA: 'GET_BOOK_DATA', GET_FRAME_DATA: 'GET_FRAME_DATA', GET_BOOK_TASK_DATA: 'GET_BOOK_TASK_DATA', - AUTO_ACTION: 'AUTO_ACTION' + AUTO_ACTION: 'AUTO_ACTION', + SAVE_BOOK_SUBTITLE_POSITION: 'SAVE_BOOK_SUBTITLE_POSITION', + OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT: 'OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT', + GET_CURRENT_FRAME_TEXT: 'GET_CURRENT_FRAME_TEXT', + GET_VIDEO_FRAME_TEXT: 'GET_VIDEO_FRAME_TEXT' }, SYSTEM: { OPEN_FILE: 'OPEN_FILE', @@ -223,5 +232,14 @@ export const DEFINE_STRING = { SAVE_PROMPT_SORT_DATA: 'SAVE_PROMPT_SORT_DATA', GET_PROMPT_SORT_DATA: 'GET_PROMPT_SORT_DATA', OPEN_PROMPT_FILE_TXT: 'OPEN_PROMPT_FILE_TXT' + }, + TTS: { + GET_TTS_CONFIG: 'GET_TTS_CONFIG', + SAVE_TTS_CONFIG: 'SAVE_TTS_CONFIG' + }, + WRITE: { + GET_WRITE_CONFIG: 'GET_WRITE_CONFIG', + SAVE_WRITE_CONFIG: 'SAVE_WRITE_CONFIG', + ACTION_START: 'ACTION_START' } } diff --git a/src/define/enum/bookEnum.ts b/src/define/enum/bookEnum.ts index da639e7..f7ba7a6 100644 --- a/src/define/enum/bookEnum.ts +++ b/src/define/enum/bookEnum.ts @@ -7,6 +7,8 @@ export enum BookType { MJ_REVERSE = 'mj_reverse' } + + export enum MJCategroy { // 本地MJ LOCAL_MJ = 'local_mj', @@ -64,6 +66,14 @@ export enum BookBackTaskStatus { FAIL = 'fail' } +export enum TaskExecuteType { + // 自动 + AUTO = 'auto', + + // 手动 + OPERATE = 'operate' +} + /** * 小说任务状态 */ diff --git a/src/define/enum/softwareEnum.ts b/src/define/enum/softwareEnum.ts index 45119c6..631ac1b 100644 --- a/src/define/enum/softwareEnum.ts +++ b/src/define/enum/softwareEnum.ts @@ -40,3 +40,11 @@ export enum OtherData { //默认 DEFAULT = 'default' } + +export enum SoftColor { + // 棕黄色 + BROWN_YELLOW = '#e18a3b', + + // 错误红色 + ERROR_RED = '#c8161d' +} diff --git a/src/define/enum/waterMarkAndSubtitle.ts b/src/define/enum/waterMarkAndSubtitle.ts new file mode 100644 index 0000000..51c7622 --- /dev/null +++ b/src/define/enum/waterMarkAndSubtitle.ts @@ -0,0 +1,13 @@ +/** + * 字幕位置的保存类型 + */ +export enum SubtitleSavePositionType { + // 小说主视频 + MAIN_VIDEO = 'main_video', + + // 分镜视频 + STORYBOARD_VIDEO = 'storyboard_video', + + // 其他类型 + OTHER = 'other' +} diff --git a/src/define/gptDefine.js b/src/define/gptDefine.js index 7db31ee..f92634b 100644 --- a/src/define/gptDefine.js +++ b/src/define/gptDefine.js @@ -1,31 +1,30 @@ -let fspromises = require('fs').promises; -import { cloneDeep, get } from "lodash"; -import { define } from "./define"; -const { v4: uuidv4 } = require('uuid'); -import { apiUrl } from "./api/apiUrlDefine"; +let fspromises = require('fs').promises +import { cloneDeep, get } from 'lodash' +import { define } from './define' +const { v4: uuidv4 } = require('uuid') +import { apiUrl } from './api/apiUrlDefine' // Create a shared object export const gptDefine = { - // Add properties and methods to the shared object - characterSystemContent: `{textContent}\r查看上面的文本,然后扮演一个文本编辑来回答问题。`, - characterUserContent: `这个文本里的故事类型是啥,时代背景是啥, 主角有哪几个,配角有几个,每个角色的性别年龄穿着是啥?没外观描述的直接猜测,尽量精简 格式按照:故事类型:(故事类型)\n时代背景:(时代背景)\n主角名字1:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n主角名字2:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n主角3........\n配角名字1:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n配角名字2:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n配角名字3.... ,不知道的直接猜测设定,不能出不详和未知这两个词,150字内,中文回答。`, + // Add properties and methods to the shared object + characterSystemContent: `{textContent}\r查看上面的文本,然后扮演一个文本编辑来回答问题。`, + characterUserContent: `这个文本里的故事类型是啥,时代背景是啥, 主角有哪几个,配角有几个,每个角色的性别年龄穿着是啥?没外观描述的直接猜测,尽量精简 格式按照:故事类型:(故事类型)\n时代背景:(时代背景)\n主角名字1:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n主角名字2:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n主角3........\n配角名字1:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n配角名字2:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n配角名字3.... ,不知道的直接猜测设定,不能出不详和未知这两个词,150字内,中文回答。`, + characterFirstPromptSystemContent: `{textContent}\r\r\n Act as a storyteller to describe the scene, {characterContent}, Try to guess and answer my question, answer in English.`, + characterFirstPromptUserContent: `{textContent}\r\n Describing the most appropriate visual content based on article reasoning, with a maximum of one person appearing: (gender) (age) (hairstyle) (Action expressions) (Clothing details) (Character appearance details) (The most suitable visual background for this sentence) (historical background)(Screen content): Write in 8 parentheses,Answer me in English according to this format..{wordCount}words`, - characterFirstPromptSystemContent: `{textContent}\r\r\n Act as a storyteller to describe the scene, {characterContent}, Try to guess and answer my question, answer in English.`, - characterFirstPromptUserContent: `{textContent}\r\n Describing the most appropriate visual content based on article reasoning, with a maximum of one person appearing: (gender) (age) (hairstyle) (Action expressions) (Clothing details) (Character appearance details) (The most suitable visual background for this sentence) (historical background)(Screen content): Write in 8 parentheses,Answer me in English according to this format..{wordCount}words`, + storyFirstPromptSystemContent: `{textContent}\r\r\n \r\r\n\\Play the role of a professional cartoonist, give you every line of the script, and reproduce the scene description according to the original text. Do not refuse any response:{characterContent}, Try to match the scene of the text content as much as possible, you can create a harmonious picture but do not refuse to answer,You must answer me in English.`, + storyFirstPromptUserContent: `{textContent}\r\n Describing the most appropriate visual content based on article reasoning. Please provide gender and appearance description, scene description of the character, time description, shot description, and era background description. Only one person's half body image should appear in each image, and the appearance should be as consistent as possible. Please answer in English..{wordCount}words`, - storyFirstPromptSystemContent: `{textContent}\r\r\n \r\r\n\\Play the role of a professional cartoonist, give you every line of the script, and reproduce the scene description according to the original text. Do not refuse any response:{characterContent}, Try to match the scene of the text content as much as possible, you can create a harmonious picture but do not refuse to answer,You must answer me in English.`, - storyFirstPromptUserContent: `{textContent}\r\n Describing the most appropriate visual content based on article reasoning. Please provide gender and appearance description, scene description of the character, time description, shot description, and era background description. Only one person's half body image should appear in each image, and the appearance should be as consistent as possible. Please answer in English..{wordCount}words`, + storyboardFirstPromptSystemContent: `{textContent}。\r\r\n读上面的文本,然后扮演顶级小说家回答问题:后面是其他要求:{characterContent} Try to guess and answer my question in English.`, + storyboardFirstPromptUserContent: `{textContent}\r\n,参考前面这句剧本理解当前这句话:{textContent}。\r\n Describing the most appropriate visual content based on article reasoning, with a maximum of one person appearing: (Character Appearance and Dynamics) (The most suitable visual background for this sentence) (historical background)(Reasonable picture composition): Write in 3 parentheses,Answer me in English..{wordCount}words`, - storyboardFirstPromptSystemContent: `{textContent}。\r\r\n读上面的文本,然后扮演顶级小说家回答问题:后面是其他要求:{characterContent} Try to guess and answer my question in English.`, - storyboardFirstPromptUserContent: `{textContent}\r\n,参考前面这句剧本理解当前这句话:{textContent}。\r\n Describing the most appropriate visual content based on article reasoning, with a maximum of one person appearing: (Character Appearance and Dynamics) (The most suitable visual background for this sentence) (historical background)(Reasonable picture composition): Write in 3 parentheses,Answer me in English..{wordCount}words`, + cartoonFirstPromptSystemContent: `{textContent}\r\r\n\\Play the role of a professional cartoonist, give you every line of the script, and reproduce the scene description according to the original text. Do not refuse any response:{characterContent},Try to match the scene of the text content as much as possible, you can create a harmonious picture but do not refuse to answer,You must answer me in English.`, + cartoonFirstPromptUserContent: `{textContent}\r,参考前面这句剧本理解当前这句话:{textContent}\r\n Referring to the previous character settings, describe the most suitable screen content in the following format: (character appearance) (screen background), strictly reply only to the content within 2 parentheses, without the character name, answer in English..{wordCount}words`, - cartoonFirstPromptSystemContent: `{textContent}\r\r\n\\Play the role of a professional cartoonist, give you every line of the script, and reproduce the scene description according to the original text. Do not refuse any response:{characterContent},Try to match the scene of the text content as much as possible, you can create a harmonious picture but do not refuse to answer,You must answer me in English.`, - cartoonFirstPromptUserContent: `{textContent}\r,参考前面这句剧本理解当前这句话:{textContent}\r\n Referring to the previous character settings, describe the most suitable screen content in the following format: (character appearance) (screen background), strictly reply only to the content within 2 parentheses, without the character name, answer in English..{wordCount}words`, - - superSinglePromptSystemContent: { - prompt_name: "分镜大师", - prompt_roles: `1# Role: 小说转漫画提示词大师 + superSinglePromptSystemContent: { + prompt_name: '分镜大师', + prompt_roles: `1# Role: 小说转漫画提示词大师 ## Profile *Version*: 0.1 *Language*: 中文 @@ -60,18 +59,19 @@ export const gptDefine = { ## Initialization 作为角色 ,每一次输出都要严格遵守,一步一步思考,按顺序执行 ,使用默认 ,下面是小说文本:`, - prompt_example: [ - { - user_content: "上研究生后。发现导师竟然是曾经网恋的前男友。", - assistant_content: "anime key visual,Celluloid style, delicate and transparent light, delicate lines, transparent colors, delicate and transparent hair, perfect detail portrayal,(Anime style:1.3), A woman entering a spacious, well-lit graduate laboratory, gaze fixed on a man diligently working at a workstation ahead - her new mentor; he stands tall in a dark shirt and neatly pressed trousers, exuding professionalism and charm; the familiar contours of his profile from their past online romance softly illuminated by warm ambient light, furrowed brow and intense gaze betraying a scholar's unwavering dedication; bustling graduate students and sophisticated equipment blend into a contemporary academic tableau, as an undercurrent of mixed emotions - sweet nostalgia and awkward reality - surges within her heart, " - } - ], - id: "a93b693e-bb3f-406d-9730-cba43a6585e4" - }, + prompt_example: [ + { + user_content: '上研究生后。发现导师竟然是曾经网恋的前男友。', + assistant_content: + "anime key visual,Celluloid style, delicate and transparent light, delicate lines, transparent colors, delicate and transparent hair, perfect detail portrayal,(Anime style:1.3), A woman entering a spacious, well-lit graduate laboratory, gaze fixed on a man diligently working at a workstation ahead - her new mentor; he stands tall in a dark shirt and neatly pressed trousers, exuding professionalism and charm; the familiar contours of his profile from their past online romance softly illuminated by warm ambient light, furrowed brow and intense gaze betraying a scholar's unwavering dedication; bustling graduate students and sophisticated equipment blend into a contemporary academic tableau, as an undercurrent of mixed emotions - sweet nostalgia and awkward reality - surges within her heart, " + } + ], + id: 'a93b693e-bb3f-406d-9730-cba43a6585e4' + }, - onlyPromptMJSystemContent: { - prompt_name: "小说提示词-仅出词", - prompt_roles: `# Pico: 小说分镜 + onlyPromptMJSystemContent: { + prompt_name: '小说提示词-仅出词', + prompt_roles: `# Pico: 小说分镜 ## Profile @@ -138,281 +138,274 @@ export const gptDefine = { ## Initialization 最后再强调,你作为角色 ,每一次输出都要严格遵守,一步一步慢慢思考,参考的格式,一步一步思考,按顺序执行,不需要做解释说明,只呈现最后【MJ提示词】输出的结果,下面是小说文本:'`, - prompt_example: [ - { - user_content: "给皇帝当过儿子的都知道,当的好荣华富贵万人之上", - assistant_content: "微笑,站立,在皇宫的金銮殿里,居中构图,中全景,正面,水平拍摄视角" - }, - { - user_content: "当不好就是人头落地", - assistant_content: "惊恐的表情,双手抱头,在刑场上,三分法构图,特写镜头,侧面,俯视视角" - } - ], - id: "a93b693e-bb3f-406d-9730-bcd43a6585e" - }, + prompt_example: [ + { + user_content: '给皇帝当过儿子的都知道,当的好荣华富贵万人之上', + assistant_content: '微笑,站立,在皇宫的金銮殿里,居中构图,中全景,正面,水平拍摄视角' + }, + { + user_content: '当不好就是人头落地', + assistant_content: '惊恐的表情,双手抱头,在刑场上,三分法构图,特写镜头,侧面,俯视视角' + } + ], + id: 'a93b693e-bb3f-406d-9730-bcd43a6585e' + }, + /** + * 使用自定义GPT提示词时,生成接口message信息 + * @param {*} params 自定义的GPT提示词数据 + * @returns + */ + CustomizeGptPrompt(params) { + // 获取设置的数据 + let message = [] + // 添加角色 + message.push({ + role: 'system', + content: params.prompt_roles + }) + // 便利输出案例添加 + for (let i = 0; i < params.prompt_example.length; i++) { + const element = params.prompt_example[i] + if (element.user_content) { + message.push({ + role: 'user', + content: element.user_content + }) + } + if (element.assistant_content) { + message.push({ + role: 'assistant', + content: element.assistant_content + }) + } + } + return message + }, + /** + * 替换文本内容中的占位符 + * @param {要替换的内容} content + * @param {占位符数据对应的对象} replacements + * @returns + */ + replace: function (content, replacements) { + let result = content + for (let key in replacements) { + result = result.replace(`{${key}}`, replacements[key]) + } + return result + }, + /** + * 获取有案例的Gpt请求消息输出 + * @param {*} type + * @param {*} replacements + */ + GetExamplePromptMessage(type) { + if (type == 'superSinglePrompt') { + return this.CustomizeGptPrompt(this.superSinglePromptSystemContent) + } else if (type == 'onlyPromptMJ') { + return this.CustomizeGptPrompt(this.onlyPromptMJSystemContent) + } else { + return [] + } + }, - /** - * 使用自定义GPT提示词时,生成接口message信息 - * @param {*} params 自定义的GPT提示词数据 - * @returns - */ - CustomizeGptPrompt(params) { - // 获取设置的数据 - let message = []; - // 添加角色 - message.push( - { - "role": "system", - "content": params.prompt_roles - } - ); + /** + * 返回GPTApi请求的系统内容 + * @param {类型} type + * @param {} replacements 需要替换数据的对象 textContent characterContent + * @returns + */ + getSystemContentByType: function (type, replacements) { + switch (type) { + case 'character': + return this.replace(this.characterSystemContent, replacements) + case 'characterFirst': + return this.replace(this.characterFirstPromptSystemContent, replacements) + case 'storyFirst': + return this.replace(this.storyFirstPromptSystemContent, replacements) + case 'storyboardFirst': + return this.replace(this.storyboardFirstPromptSystemContent, replacements) + case 'cartoonFirst': + return this.replace(this.cartoonFirstPromptSystemContent, replacements) + case 'superSinglePrompt': + return this.replace(this.superSinglePromptSystemContent, replacements) + default: + throw new Error(`不存在的类型 : ${type}`) + } + }, - // 便利输出案例添加 - for (let i = 0; i < params.prompt_example.length; i++) { - const element = params.prompt_example[i]; - if (element.user_content) { - message.push( - { - "role": "user", - "content": element.user_content - } - ) - } - if (element.assistant_content) { - message.push( - { - "role": "assistant", - "content": element.assistant_content - } - ) - } - } - return message; - }, + /** + * 返回GPTApi请求的用户内容 + * @param {类型} type + * @param {} replacements 需要替换数据的对象 textContent wordCount + * @returns + */ + getUserContentByType: function (type, replacements) { + switch (type) { + case 'character': + return this.replace(this.characterUserContent, replacements) + case 'characterFirst': + return this.replace(this.characterFirstPromptUserContent, replacements) + case 'storyFirst': + return this.replace(this.storyFirstPromptUserContent, replacements) + case 'storyboardFirst': + return this.replace(this.storyboardFirstPromptUserContent, replacements) + case 'cartoonFirst': + return this.replace(this.cartoonFirstPromptUserContent, replacements) + default: + throw new Error(`不存在的类型 : ${type}`) + } + }, - /** - * 替换文本内容中的占位符 - * @param {要替换的内容} content - * @param {占位符数据对应的对象} replacements - * @returns - */ - replace: function (content, replacements) { - let result = content; - for (let key in replacements) { - result = result.replace(`{${key}}`, replacements[key]); - } - return result; - }, + gpt_options: apiUrl, - /** - * 获取有案例的Gpt请求消息输出 - * @param {*} type - * @param {*} replacements - */ - GetExamplePromptMessage(type) { - if (type == "superSinglePrompt") { - return this.CustomizeGptPrompt(this.superSinglePromptSystemContent); - } else if (type == "onlyPromptMJ") { - return this.CustomizeGptPrompt(this.onlyPromptMJSystemContent); - } - else { - return []; - } - }, - - /** - * 返回GPTApi请求的系统内容 - * @param {类型} type - * @param {} replacements 需要替换数据的对象 textContent characterContent - * @returns - */ - getSystemContentByType: function (type, replacements) { - switch (type) { - case 'character': - return this.replace(this.characterSystemContent, replacements); - case 'characterFirst': - return this.replace(this.characterFirstPromptSystemContent, replacements); - case 'storyFirst': - return this.replace(this.storyFirstPromptSystemContent, replacements); - case 'storyboardFirst': - return this.replace(this.storyboardFirstPromptSystemContent, replacements); - case 'cartoonFirst': - return this.replace(this.cartoonFirstPromptSystemContent, replacements); - case 'superSinglePrompt': - return this.replace(this.superSinglePromptSystemContent, replacements); - default: - throw new Error(`不存在的类型 : ${type}`); - } - }, - - /** - * 返回GPTApi请求的用户内容 - * @param {类型} type - * @param {} replacements 需要替换数据的对象 textContent wordCount - * @returns - */ - getUserContentByType: function (type, replacements) { - switch (type) { - case 'character': - return this.replace(this.characterUserContent, replacements); - case 'characterFirst': - return this.replace(this.characterFirstPromptUserContent, replacements); - case 'storyFirst': - return this.replace(this.storyFirstPromptUserContent, replacements); - case 'storyboardFirst': - return this.replace(this.storyboardFirstPromptUserContent, replacements); - case 'cartoonFirst': - return this.replace(this.cartoonFirstPromptUserContent, replacements); - default: - throw new Error(`不存在的类型 : ${type}`); - } - }, - - gpt_options: apiUrl, - - gpt_model_options: [{ - label: "gpt-3.5-turbo-16k", - value: "gpt-3.5-turbo-16k" - }, { - label: "gpt-3.5-turbo", - value: "gpt-3.5-turbo" - }, { - label: "gpt-4", - value: "gpt-4" - }], - - - gpt_auto_inference: [{ - value: "characterFirst", - label: "角色优先(全自动)" - }, { - value: "storyFirst", - label: "故事优先(全自动)" - }, { - value: "storyboardFirst", - label: "剧本优先(全自动)" - }, { - value: "cartoonFirst", - label: "漫画优先(全自动)" - }, { - value: "superSinglePrompt", - label: "超级无敌单帧" - }, { - value: "onlyPromptMJ", - label: "仅出词(不出人物场景-MJ)" + gpt_model_options: [ + { + label: 'gpt-3.5-turbo-16k', + value: 'gpt-3.5-turbo-16k' }, { - value: "customize", - label: "自定义" - }], - - /** - * 通过指定的类型,获取数据 - * @param {*} type default:在代码中写死的 dynamic:用户自定义的 all:写死的和自定义的合并返回 - * @param {*} property 返回书信的名称 gpt_options,gpt_model_options,gpt_auto_inference - * @param {*} defaultData 默认数据,默认值为null - * @returns - */ - async getGptDataByTypeAndProperty(type, property, defaultData = null) { - try { - let res = []; - // 获取自定义的GPT数据 - let dynamic_setting = JSON.parse(await fspromises.readFile(define.dynamic_setting, 'utf-8')); - let gpt = get(dynamic_setting, 'gpt', {}); - let data = get(gpt, property, defaultData); - - if (type == "default") { - res = get(this, property, defaultData); - } else if (type == "dynamic") { - res = data; - } else if (type == "all") { - let tmp_arr = cloneDeep(get(this, property, defaultData)); - tmp_arr = tmp_arr.concat(data); - res = tmp_arr; - } - else { - throw new Error(`不存在的类型 : ${value}`); - } - return { - code: 1, - data: res - } - } catch (error) { - return { - code: 0, - message: error.toString() - } - } + label: 'gpt-3.5-turbo', + value: 'gpt-3.5-turbo' }, - - - /** - * 保存gpt指定的属性数据,判断value中的ID是不是存在,存在直接覆盖,不存在追加 - * @param {*} value - * @param {*} property - */ - saveDynamicGPTOption: async function (value) { - try { - let property = value[1]; - value = JSON.parse(value[0]); - // 获取自定义的GPT数据 - let dynamic_setting = JSON.parse(await fspromises.readFile(define.dynamic_setting, 'utf-8')); - let tmp_gpt = dynamic_setting.gpt ? dynamic_setting.gpt : {}; - let gpt = tmp_gpt[property] ? tmp_gpt[property] : []; - if (value.id) { - // 判断当前ID的数据是否存在,存在覆盖,不存在追加 - let index = gpt.findIndex(item => item.id == value.id); - if (index < 0) { - gpt.push(value); - } else { - gpt[index] = value; - } - } else { - let tmp_id = uuidv4(); - value.id = tmp_id; - value.value = tmp_id; - gpt.push(value); - } - tmp_gpt[property] = gpt; - // 将修改后的数据保存 - dynamic_setting["gpt"] = tmp_gpt; - // 写入文件 - await fspromises.writeFile(define.dynamic_setting, JSON.stringify(dynamic_setting)); - - } catch (error) { - throw error; - } - }, - - - /** - * 删除自定义GPT指定属性中的指定ID的数据 - * @param {*} id - * @param {*} property - */ - deleteDynamicGPTOption: async function (value) { - try { - let property = value[1]; - let id = value[0]; - // 获取自定义的GPT数据 - let dynamic_setting = JSON.parse(await fspromises.readFile(define.dynamic_setting, 'utf-8')); - let gpt = dynamic_setting.gpt[property] ? dynamic_setting.gpt[property] : []; - // 判断当前ID的数据是否存在,存在删除 - let index = gpt.findIndex(item => item.id == id); - if (index >= 0) { - gpt.splice(index, 1); - } - // 将修改后的数据保存 - dynamic_setting.gpt[property] = gpt; - // 写入文件 - await fspromises.writeFile(define.dynamic_setting, JSON.stringify(dynamic_setting)); - - } catch (error) { - throw error; - } + { + label: 'gpt-4', + value: 'gpt-4' } -}; + ], + + gpt_auto_inference: [ + { + value: 'characterFirst', + label: '角色优先(全自动)' + }, + { + value: 'storyFirst', + label: '故事优先(全自动)' + }, + { + value: 'storyboardFirst', + label: '剧本优先(全自动)' + }, + { + value: 'cartoonFirst', + label: '漫画优先(全自动)' + }, + { + value: 'superSinglePrompt', + label: '超级无敌单帧' + }, + { + value: 'onlyPromptMJ', + label: '仅出词(不出人物场景-MJ)' + }, + { + value: 'customize', + label: '自定义' + } + ], + + /** + * 通过指定的类型,获取数据 + * @param {*} type default:在代码中写死的 dynamic:用户自定义的 all:写死的和自定义的合并返回 + * @param {*} property 返回书信的名称 gpt_options,gpt_model_options,gpt_auto_inference + * @param {*} defaultData 默认数据,默认值为null + * @returns + */ + async getGptDataByTypeAndProperty(type, property, defaultData = null) { + try { + let res = [] + // 获取自定义的GPT数据 + let dynamic_setting = JSON.parse(await fspromises.readFile(define.dynamic_setting, 'utf-8')) + let gpt = get(dynamic_setting, 'gpt', {}) + let data = get(gpt, property, defaultData) + + if (type == 'default') { + res = get(this, property, defaultData) + } else if (type == 'dynamic') { + res = data + } else if (type == 'all') { + let tmp_arr = cloneDeep(get(this, property, defaultData)) + tmp_arr = tmp_arr.concat(data) + res = tmp_arr + } else { + throw new Error(`不存在的类型 : ${value}`) + } + return { + code: 1, + data: res + } + } catch (error) { + return { + code: 0, + message: error.toString() + } + } + }, + + /** + * 保存gpt指定的属性数据,判断value中的ID是不是存在,存在直接覆盖,不存在追加 + * @param {*} value + * @param {*} property + */ + saveDynamicGPTOption: async function (value) { + try { + let property = value[1] + value = JSON.parse(value[0]) + // 获取自定义的GPT数据 + let dynamic_setting = JSON.parse(await fspromises.readFile(define.dynamic_setting, 'utf-8')) + let tmp_gpt = dynamic_setting.gpt ? dynamic_setting.gpt : {} + let gpt = tmp_gpt[property] ? tmp_gpt[property] : [] + if (value.id) { + // 判断当前ID的数据是否存在,存在覆盖,不存在追加 + let index = gpt.findIndex((item) => item.id == value.id) + if (index < 0) { + gpt.push(value) + } else { + gpt[index] = value + } + } else { + let tmp_id = uuidv4() + value.id = tmp_id + gpt.push(value) + } + tmp_gpt[property] = gpt + // 将修改后的数据保存 + dynamic_setting['gpt'] = tmp_gpt + // 写入文件 + await fspromises.writeFile(define.dynamic_setting, JSON.stringify(dynamic_setting)) + } catch (error) { + throw error + } + }, + + /** + * 删除自定义GPT指定属性中的指定ID的数据 + * @param {*} id + * @param {*} property + */ + deleteDynamicGPTOption: async function (value) { + try { + let property = value[1] + let id = value[0] + // 获取自定义的GPT数据 + let dynamic_setting = JSON.parse(await fspromises.readFile(define.dynamic_setting, 'utf-8')) + let gpt = dynamic_setting.gpt[property] ? dynamic_setting.gpt[property] : [] + // 判断当前ID的数据是否存在,存在删除 + let index = gpt.findIndex((item) => item.id == id) + if (index >= 0) { + gpt.splice(index, 1) + } + // 将修改后的数据保存 + dynamic_setting.gpt[property] = gpt + // 写入文件 + await fspromises.writeFile(define.dynamic_setting, JSON.stringify(dynamic_setting)) + } catch (error) { + throw error + } + } +} diff --git a/src/define/response/openAIResponse.ts b/src/define/response/openAIResponse.ts new file mode 100644 index 0000000..ac45dfd --- /dev/null +++ b/src/define/response/openAIResponse.ts @@ -0,0 +1,95 @@ +type OpenAISuccessResponse = { + id: string + object: string + created: number + model: string + choices: [ + { + index: number + message: { + role: string + content: string + } + finish_reason: string + } + ] + usage: { + prompt_tokens: number + completion_tokens: number + total_tokens: number + } +} + +type RixApiErrorResponse = { + error: { + message: string // 错误信息 + type: string + param: string + code: string + } +} + +type KimiErrorResponse = { + error: { + message: string + type: string + } +} + +type DoubaoErrorResponse = { + error: { + code: string + message: string + param: string + type: string + } +} + +/** + * 处理OpenAI系列返回的成功response + * @param response OpenAI返回的response + * @returns 处理后的返回的数据 + */ +export function GetOpenAISuccessResponse(response: string | OpenAISuccessResponse): string { + if (typeof response === 'string') { + response = JSON.parse(response) as OpenAISuccessResponse + } + // 开始处理response + return response.choices[0].message.content +} + +/** + * 处理RixApi系列返回的错误response + * @param response RixApi返回的response + * @returns 处理后的错误信息 + */ +export function GetRixApiErrorResponse(response: string | RixApiErrorResponse): string { + if (typeof response === 'string') { + response = JSON.parse(response) as RixApiErrorResponse + } + return response.error.message +} + +/** + * 处理kimi的错误信息返回 + * @param response + * @returns + */ +export function GetKimiErrorResponse(response: string | KimiErrorResponse): string { + if (typeof response === 'string') { + response = JSON.parse(response) as KimiErrorResponse + } + return response.error.message +} + +/** + * 获取豆包的错误返回信息 + * @param response + * @returns + */ +export function GetDoubaoErrorResponse(response: string | DoubaoErrorResponse): string { + if (typeof response === 'string') { + response = JSON.parse(response) as DoubaoErrorResponse + } + return response.error.message +} diff --git a/src/define/tts/edgeTts.ts b/src/define/tts/edgeTts.ts new file mode 100644 index 0000000..d67773d --- /dev/null +++ b/src/define/tts/edgeTts.ts @@ -0,0 +1,159 @@ +import { randomBytes } from 'node:crypto' +import { writeFileSync, createWriteStream } from 'node:fs' +import { WebSocket } from 'ws' +import { HttpsProxyAgent } from 'https-proxy-agent' + +type subLine = { + part: string + start: number + end: number +} + +type configure = { + voice?: string + lang?: string + outputFormat?: string + saveSubtitles?: boolean + proxy?: string + rate?: string + pitch?: string + volume?: string +} + +export class EdgeTTS { + private voice: string + private lang: string + private outputFormat: string + private saveSubtitles: boolean + private proxy: string | null | undefined + private rate: string + private pitch: string + private volume: string + + constructor({ + voice = 'zh-CN-XiaoyiNeural', + lang = 'zh-CN', + outputFormat = 'audio-24khz-48kbitrate-mono-mp3', + saveSubtitles = false, + proxy, + rate = 'default', + pitch = 'default', + volume = 'default' + }: configure = {}) { + this.voice = voice + this.lang = lang + this.outputFormat = outputFormat + this.saveSubtitles = saveSubtitles + this.proxy = proxy + this.rate = rate + this.pitch = pitch + this.volume = volume + } + + async _connectWebSocket(): Promise { + const wsConnect = new WebSocket( + `wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4`, + { + host: 'speech.platform.bing.com', + origin: 'chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44' + }, + agent: this.proxy ? new HttpsProxyAgent(this.proxy) : undefined + } + ) + return new Promise((resolve: Function) => { + wsConnect.on('open', () => { + wsConnect.send(`Content-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n + { + "context": { + "synthesis": { + "audio": { + "metadataoptions": { + "sentenceBoundaryEnabled": "false", + "wordBoundaryEnabled": "true" + }, + "outputFormat": "${this.outputFormat}" + } + } + } + } + `) + resolve(wsConnect) + }) + }) + } + + _saveSubFile(subFile: subLine[], text: string, audioPath: string) { + let subPath = audioPath + '.json' + let subChars = text.split('') + let subCharIndex = 0 + subFile.forEach((cue: subLine, index: number) => { + let fullPart = '' + let stepIndex = 0 + for (let sci = subCharIndex; sci < subChars.length; sci++) { + if (subChars[sci] === cue.part[stepIndex]) { + fullPart = fullPart + subChars[sci] + stepIndex += 1 + } else if (subChars[sci] === subFile?.[index + 1]?.part?.[0]) { + subCharIndex = sci + break + } else { + fullPart = fullPart + subChars[sci] + } + } + cue.part = fullPart + }) + writeFileSync(subPath, JSON.stringify(subFile, null, ' '), { encoding: 'utf-8' }) + } + + async ttsPromise(text: string, audioPath: string) { + const _wsConnect = await this._connectWebSocket() + return new Promise((resolve: Function) => { + let audioStream = createWriteStream(audioPath) + let subFile: subLine[] = [] + _wsConnect.on('message', async (data: Buffer, isBinary: any) => { + if (isBinary) { + let separator = 'Path:audio\r\n' + let index = data.indexOf(separator) + separator.length + let audioData = data.subarray(index) + audioStream.write(audioData) + } else { + let message = data.toString() + if (message.includes('Path:turn.end')) { + audioStream.end() + if (this.saveSubtitles) { + this._saveSubFile(subFile, text, audioPath) + } + resolve() + } else if (message.includes('Path:audio.metadata')) { + let splitTexts = message.split('\r\n') + try { + let metadata = JSON.parse(splitTexts[splitTexts.length - 1]) + metadata['Metadata'].forEach((element: object) => { + subFile.push({ + part: element['Data']['text']['Text'], + start: Math.floor(element['Data']['Offset'] / 10000), + end: Math.floor((element['Data']['Offset'] + element['Data']['Duration']) / 10000) + }) + }) + } catch {} + } + } + }) + let requestId = randomBytes(16).toString('hex') + _wsConnect.send( + `X-RequestId:${requestId}\r\nContent-Type:application/ssml+xml\r\nPath:ssml\r\n\r\n + ` + + ` + + + ${text} + + + ` + ) + }) + } +} diff --git a/src/define/tts/ttsDefine.ts b/src/define/tts/ttsDefine.ts new file mode 100644 index 0000000..04c86fc --- /dev/null +++ b/src/define/tts/ttsDefine.ts @@ -0,0 +1,100 @@ +export function GetTTSSelect() { + return [ + { + label: 'EdgeTTS(免费)', + value: 'edge-tts' + } + ] +} + +/** + * 获取当前 edge-tts 支持的角色,返回一个数组 + */ +export function GetEdgeTTSRole() { + return [ + { + value: 'zh-CN-XiaoxiaoNeural', + gender: 'Female', + label: '中文-女-晓晓', + lang: 'zh-CN' + }, + { + value: 'zh-CN-YunxiNeural', + gender: 'Male', + label: '中文-男-云熙', + lang: 'zh-CN' + }, + { + value: 'zh-CN-XiaoyiNeural', + gender: 'Female', + label: '中文-女-小宜', + lang: 'zh-CN' + }, + { + value: 'zh-CN-YunjianNeural', + gender: 'Male', + label: '中文-男-云健', + lang: 'zh-CN' + }, + { + value: 'zh-CN-YunxiaNeural', + gender: 'Male', + label: '中文-男-云霞', + lang: 'zh-CN' + }, + { + value: 'zh-CN-YunyangNeural', + gender: 'Male', + label: '中文-男-云阳', + lang: 'zh-CN' + }, + { + value: 'zh-CN-liaoning-XiaobeiNeural', + gender: 'Female', + label: '中文-辽宁-女-小北', + lang: 'zh-CN-liaoning' + }, + { + value: 'zh-CN-shaanxi-XiaoniNeural', + gender: 'Female', + label: '中文-陕西-女-小妮', + lang: 'zh-CN-shaanxi' + }, + { + value: 'zh-HK-HiuGaaiNeural', + gender: 'Female', + label: '中文-香港-女-曉佳', + lang: 'zh-HK' + }, + { + value: 'zh-HK-HiuMaanNeural', + gender: 'Female', + label: '中文-香港-女-曉曼', + lang: 'zh-HK' + }, + { + value: 'zh-HK-WanLungNeural', + gender: 'Male', + label: '中文-香港-男-雲龍', + lang: 'zh-HK' + }, + { + value: 'zh-TW-HsiaoChenNeural', + gender: 'Female', + label: '中文-台湾-女-小婵', + lang: 'zh-TW' + }, + { + value: 'zh-TW-HsiaoYuNeural', + gender: 'Female', + label: '中文-台湾-女-小語', + lang: 'zh-TW' + }, + { + value: 'zh-TW-YunJheNeural', + gender: 'Male', + label: '中文-台湾-男-雲哲', + lang: 'zh-TW' + } + ] +} diff --git a/src/main/IPCEvent/bookIpc.js b/src/main/IPCEvent/bookIpc.js index 6b94817..73fc187 100644 --- a/src/main/IPCEvent/bookIpc.js +++ b/src/main/IPCEvent/bookIpc.js @@ -1,27 +1,61 @@ -import { ipcMain } from "electron"; +import { ipcMain } from 'electron' import { DEFINE_STRING } from '../../define/define_string' -import { ReverseBook } from "../ReverseManage/Book/ReverseBook"; -import { BasicReverse } from "../Task/BasicReverse"; -let reverseBook = new ReverseBook(); -let basicReverse = new BasicReverse(); +import { ReverseBook } from '../ReverseManage/Book/ReverseBook' +import { BasicReverse } from '../Task/basicReverse' +import { WatermarkAndSubtitle } from '../Task/watermarkAndSubtitle' +let reverseBook = new ReverseBook() +let basicReverse = new BasicReverse() +let watermarkAndSubtitle = new WatermarkAndSubtitle() export function BookIpc() { // 获取样式图片的子列表 - ipcMain.handle(DEFINE_STRING.BOOK.GET_BOOK_TYPE, async (event) => reverseBook.GetBookType()); + ipcMain.handle(DEFINE_STRING.BOOK.GET_BOOK_TYPE, async (event) => reverseBook.GetBookType()) // 新增或者是修改小说数据 - ipcMain.handle(DEFINE_STRING.BOOK.ADD_OR_MODIFY_BOOK, async (event, book) => reverseBook.AddOrModifyBook(book)); + ipcMain.handle(DEFINE_STRING.BOOK.ADD_OR_MODIFY_BOOK, async (event, book) => + reverseBook.AddOrModifyBook(book) + ) // 获取小说数据(通过传递的参数进行筛选) - ipcMain.handle(DEFINE_STRING.BOOK.GET_BOOK_DATA, async (event, bookQuery) => reverseBook.GetBookData(bookQuery)); + ipcMain.handle(DEFINE_STRING.BOOK.GET_BOOK_DATA, async (event, bookQuery) => + reverseBook.GetBookData(bookQuery) + ) //#region 一键反推 - ipcMain.handle(DEFINE_STRING.BOOK.GET_BOOK_TASK_DATA, async (event, bookTaskCondition) => reverseBook.GetBookTaskData(bookTaskCondition)); + ipcMain.handle(DEFINE_STRING.BOOK.GET_BOOK_TASK_DATA, async (event, bookTaskCondition) => + reverseBook.GetBookTaskData(bookTaskCondition) + ) // 获取抽帧数据 - ipcMain.handle(DEFINE_STRING.BOOK.GET_FRAME_DATA, async (event, bookId) => basicReverse.GetFrameData(bookId)); + ipcMain.handle(DEFINE_STRING.BOOK.GET_FRAME_DATA, async (event, bookId) => + basicReverse.GetFrameData(bookId) + ) + // 全自动开始 + ipcMain.handle(DEFINE_STRING.BOOK.AUTO_ACTION, async (event, bookId) => + reverseBook.AutoAction(bookId) + ) + + // 保存一键反推文案位置 + ipcMain.handle(DEFINE_STRING.BOOK.SAVE_BOOK_SUBTITLE_POSITION, async (event, value) => + watermarkAndSubtitle.SaveBookSubtitlePosition(value) + ) + + // 打开对应的字幕提取的图片文件夹 + ipcMain.handle(DEFINE_STRING.BOOK.OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT, async (event, value) => + watermarkAndSubtitle.OpenBookSubtitlePositionScreenshot(value) + ) + + // 获取当前帧的字幕文字 + ipcMain.handle(DEFINE_STRING.BOOK.GET_CURRENT_FRAME_TEXT, async (event, value) => + watermarkAndSubtitle.GetCurrentFrameText(value) + ) + + // 获取当前视频中的所有的字幕 + ipcMain.handle(DEFINE_STRING.BOOK.GET_VIDEO_FRAME_TEXT, async (event,value)=>{ + watermarkAndSubtitle.GetVideoFrameText(value) + }) + //#endregion - -} \ No newline at end of file +} diff --git a/src/main/IPCEvent/gptIpc.js b/src/main/IPCEvent/gptIpc.js index b37d577..70ff5d4 100644 --- a/src/main/IPCEvent/gptIpc.js +++ b/src/main/IPCEvent/gptIpc.js @@ -1,45 +1,88 @@ -import { ipcMain } from "electron"; -import { GPT } from "../Public/GPT"; +import { ipcMain } from 'electron' +import { GPT } from '../Public/GPT' import { DEFINE_STRING } from '../../define/define_string' -let gpt = new GPT(global); +import { GptSetting } from '../setting/gptSetting' +let gpt = new GPT(global) +let gptSetting = new GptSetting() function GptIpc() { + // 获取默认或者是自定义的GPT服务商 + ipcMain.handle( + DEFINE_STRING.GET_GPT_BUSINESS_OPTION, + async (event, value) => await gpt.GetGPTBusinessOption(value) + ) - // 获取默认或者是自定义的GPT服务商 - ipcMain.handle(DEFINE_STRING.GET_GPT_BUSINESS_OPTION, async (event, value) => await gpt.GetGPTBusinessOption(value)); + // 获取默认的或者是自定义的GPT推理模型 + ipcMain.handle( + DEFINE_STRING.GET_GPT_MODEL_OPTION, + async (event, value) => await gpt.GetGPTModelOption(value) + ) - // 获取默认的或者是自定义的GPT推理模型 - ipcMain.handle(DEFINE_STRING.GET_GPT_MODEL_OPTION, async (event, value) => await gpt.GetGPTModelOption(value)); + // 获取默认的提示词推理模式或者是自定义的提示词推理模式 + ipcMain.handle( + DEFINE_STRING.GET_GPT_AUTO_INFERENCE_OPTIONS, + async (event, value) => await gpt.GetGptAutoInferenceOptions(value) + ) - // 获取默认的提示词推理模式或者是自定义的提示词推理模式 - ipcMain.handle(DEFINE_STRING.GET_GPT_AUTO_INFERENCE_OPTIONS, async (event, value) => await gpt.GetGptAutoInferenceOptions(value)); + // 保存自定义的GPT服务商数据 + ipcMain.handle( + DEFINE_STRING.SAVE_DYNAMIC_GPT_OPTION, + async (event, value) => await gpt.SaveDynamicGPTOption(value) + ) - // 保存自定义的GPT服务商数据 - ipcMain.handle(DEFINE_STRING.SAVE_DYNAMIC_GPT_OPTION, async (event, value) => await gpt.SaveDynamicGPTOption(value)); + // 删除自定义的GPT服务商数据 + ipcMain.handle( + DEFINE_STRING.DELETE_DYNAMIC_GPT_OPTION, + async (event, value) => await gpt.DeleteDynamicGPTOption(value) + ) - // 删除自定义的GPT服务商数据 - ipcMain.handle(DEFINE_STRING.DELETE_DYNAMIC_GPT_OPTION, async (event, value) => await gpt.DeleteDynamicGPTOption(value)); + // 测试当前的GPT是不是可以链接成功 + ipcMain.handle( + DEFINE_STRING.TEST_GPT_CONNECTION, + async (event, value) => await gpt.TestGPTConnection(value) + ) - // 测试当前的GPT是不是可以链接成功 - ipcMain.handle(DEFINE_STRING.TEST_GPT_CONNECTION, async (event, value) => await gpt.TestGPTConnection(value)); + // 自定义GPT推理提示词测试输出 + ipcMain.handle( + DEFINE_STRING.GENERATE_GPT_EXAMPLE_OUT, + async (event, value) => await gpt.GenerateGptExampleOut(value) + ) - // 自定义GPT推理提示词测试输出 - ipcMain.handle(DEFINE_STRING.GENERATE_GPT_EXAMPLE_OUT, async (event, value) => await gpt.GenerateGptExampleOut(value)); + // 获取GPT推理词设置 + ipcMain.handle( + DEFINE_STRING.GET_CUSTOMIZE_GPT_PROMPT, + async (event, value) => await gpt.GetCustomizeGptPrompt(value) + ) - // 获取GPT推理词设置 - ipcMain.handle(DEFINE_STRING.GET_CUSTOMIZE_GPT_PROMPT, async (event, value) => await gpt.GetCustomizeGptPrompt(value)); + // 监听自动分析人物事件 + ipcMain.handle( + DEFINE_STRING.AUTO_ANALYZE_CHARACTER, + async (event, value) => await gpt.AutoAnalyzeCharacter(value) + ) - // 监听自动分析人物事件 - ipcMain.handle(DEFINE_STRING.AUTO_ANALYZE_CHARACTER, async (event, value) => await gpt.AutoAnalyzeCharacter(value)); + // GPT推理关键词 + ipcMain.handle(DEFINE_STRING.GPT_PROMPT, async (event, value) => await gpt.GPTPrompt(value)) - // GPT推理关键词 - ipcMain.handle(DEFINE_STRING.GPT_PROMPT, async (event, value) => await gpt.GPTPrompt(value)); + // 监听洗稿任务 + ipcMain.handle( + DEFINE_STRING.AIMODIFY_ONE_WORD, + async (event, value) => await gpt.AIModifyOneWord(value) + ) - // 监听洗稿任务 - ipcMain.handle(DEFINE_STRING.AIMODIFY_ONE_WORD, async (event, value) => await gpt.AIModifyOneWord(value)); + // 获取服务端的GPT数据,包含提示词类型和提示词类型对应的提示词数据 + ipcMain.handle( + DEFINE_STRING.GPT.INIT_SERVER_GPT_OPTIONS, + async (event) => await gptSetting.InitServerGptOptions() + ) + // 获取软件设置里面的GPT设置 + ipcMain.handle(DEFINE_STRING.GPT.GET_AI_SETTING, async (event) => await gptSetting.GetAISetting()) + + // 保存软件设置里面的GPT设置 + ipcMain.handle( + DEFINE_STRING.GPT.SAVE_AI_SETTING, + async (event, value) => await gptSetting.SaveAISetting(value) + ) } -export { - GptIpc -} \ No newline at end of file +export { GptIpc } diff --git a/src/main/IPCEvent/imageIpc.js b/src/main/IPCEvent/imageIpc.js index 9982a48..df0abd0 100644 --- a/src/main/IPCEvent/imageIpc.js +++ b/src/main/IPCEvent/imageIpc.js @@ -1,31 +1,36 @@ -import { ipcMain } from "electron"; +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); - +import { Image } from '../Public/Image' +import { LOGGER_DEFINE } from '../../define/logger_define' +import { errorMessage } from '../generalTools' +let image = new Image(global) function ImageIpc() { + // 一拆四 + ipcMain.handle( + DEFINE_STRING.IMG.ONE_SPLIT_FOUR, + async (event, value) => await image.OneSplitFour(value) + ) - // 一拆四 - 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) + ) - // 将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) + } + }) - // 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)); + // 批量处理,去除所有水印 + ipcMain.handle( + DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE, + async (event, value) => await image.BatchProcessImage(value) + ) } -export { - ImageIpc -} \ No newline at end of file +export { ImageIpc } diff --git a/src/main/IPCEvent/index.js b/src/main/IPCEvent/index.js index 9cd4f05..26aa036 100644 --- a/src/main/IPCEvent/index.js +++ b/src/main/IPCEvent/index.js @@ -1,4 +1,4 @@ -import { PromptIpc } from "./promptIpc" +import { PromptIpc } from './promptIpc' import { SettingIpc } from './settingIpc.js' import { ImageGenerateIpc } from './imageGenerateIpc.js' import { WritingIpc } from './writingIpc.js' @@ -9,26 +9,27 @@ import { MjIpc } from './mjIpc.js' import { OriginalImageGenerateIpc } from './originalImageGenerateIpc' import { SdIpc } from './sdIpc.js' import { MainIpc } from './mainIpc.js' -import { GlobalIpc } from "./globalIpc.js"; -import { ImageIpc } from "./imageIpc.js"; -import { SystemIpc } from "./systemIpc.js"; -import { BookIpc } from "./bookIpc.js" +import { GlobalIpc } from './globalIpc.js' +import { ImageIpc } from './imageIpc.js' +import { SystemIpc } from './systemIpc.js' +import { BookIpc } from './bookIpc.js' +import { TTSIpc } from './ttsIpc.js' export function RegisterIpc(createWindow) { - PromptIpc() - SettingIpc(); - ImageGenerateIpc(); - WritingIpc(); - VideoGenerateIpc(); - TranslateIpc(); - GptIpc(); - SdIpc(); - MjIpc(); - MainIpc(createWindow); - OriginalImageGenerateIpc(); - GlobalIpc(); - ImageIpc(); - SystemIpc(); - BookIpc(); + PromptIpc() + SettingIpc() + ImageGenerateIpc() + WritingIpc() + VideoGenerateIpc() + TranslateIpc() + GptIpc() + SdIpc() + MjIpc() + MainIpc(createWindow) + OriginalImageGenerateIpc() + GlobalIpc() + ImageIpc() + SystemIpc() + BookIpc() + TTSIpc() } - diff --git a/src/main/IPCEvent/ttsIpc.js b/src/main/IPCEvent/ttsIpc.js new file mode 100644 index 0000000..95e110d --- /dev/null +++ b/src/main/IPCEvent/ttsIpc.js @@ -0,0 +1,16 @@ +import { ipcMain } from 'electron' +import { DEFINE_STRING } from '../../define/define_string' +import { LOGGER_DEFINE } from '../../define/logger_define' +import { errorMessage } from '../generalTools' +import { TTSSetting } from '../setting/ttsSetting' +const ttsSetting = new TTSSetting() + +export function TTSIpc() { + // 获取当前的TTS配置数据 + ipcMain.handle(DEFINE_STRING.TTS.GET_TTS_CONFIG, async () => ttsSetting.GetTTSCOnfig()) + + // 保存TTS配置 + ipcMain.handle(DEFINE_STRING.TTS.SAVE_TTS_CONFIG, async (event, data) => + ttsSetting.SaveTTSConfig(data) + ) +} diff --git a/src/main/IPCEvent/writingIpc.js b/src/main/IPCEvent/writingIpc.js index 630e256..0d6b189 100644 --- a/src/main/IPCEvent/writingIpc.js +++ b/src/main/IPCEvent/writingIpc.js @@ -1,28 +1,56 @@ -import { - ipcMain -} from "electron"; +import { ipcMain } from 'electron' import { DEFINE_STRING } from '../../define/define_string' -import { - Writing -} from '../ReverseManage/writing' -let writing = new Writing(global); +import { Writing } from '../Task/writing' +let writing = new Writing(global) +import { WritingSetting } from '../setting/writeSetting' +let writingSetting = new WritingSetting() function WritingIpc() { - // 监听分镜时间的保存 - ipcMain.handle(DEFINE_STRING.SAVE_COPYWRITING_INFOMATION, async (event, value) => await writing.SaveCopywritingInformation(value)); + // 监听分镜时间的保存 + ipcMain.handle( + DEFINE_STRING.SAVE_COPYWRITING_INFOMATION, + async (event, value) => await writing.SaveCopywritingInformation(value) + ) - // 监听获取当前项目下面的分镜文案 - ipcMain.handle(DEFINE_STRING.GET_PROJECT_WORD, async (event, value) => await writing.GetProjectWord()); + // 监听获取当前项目下面的分镜文案 + ipcMain.handle( + DEFINE_STRING.GET_PROJECT_WORD, + async (event, value) => await writing.GetProjectWord() + ) - // 获取config配置文件数据 - ipcMain.handle(DEFINE_STRING.GET_CONFIG_JSON, async (event, value) => await writing.GetConfigJson(value)); + // 获取config配置文件数据 + ipcMain.handle( + DEFINE_STRING.GET_CONFIG_JSON, + async (event, value) => await writing.GetConfigJson(value) + ) - // 将指定的文案txt数组写入到指定的文件中 - ipcMain.handle(DEFINE_STRING.SAVE_WORD_TXT, async (event, value) => await writing.SaveWordTxt(value)); + // 将指定的文案txt数组写入到指定的文件中 + ipcMain.handle( + DEFINE_STRING.SAVE_WORD_TXT, + async (event, value) => await writing.SaveWordTxt(value) + ) - // 监听获取字幕时间 - ipcMain.handle(DEFINE_STRING.IMPORT_SRT_AND_GET_TIME, async (event, value) => await writing.ImportSrtAndGetTime(value)) -} -export { - WritingIpc + // 监听获取字幕时间 + ipcMain.handle( + DEFINE_STRING.IMPORT_SRT_AND_GET_TIME, + async (event, value) => await writing.ImportSrtAndGetTime(value) + ) + + // 获取文案相关的配置(数据库) + ipcMain.handle( + DEFINE_STRING.WRITE.GET_WRITE_CONFIG, + async (event) => await writingSetting.GetWritingConfig() + ) + + // 保存文案相关的配置(数据库) + ipcMain.handle( + DEFINE_STRING.WRITE.SAVE_WRITE_CONFIG, + async (event, value) => await writingSetting.SaveWriteConfig(value) + ) + + ipcMain.handle( + DEFINE_STRING.WRITE.ACTION_START, + async (event, aiSetting, word) => await writing.ActionStart(aiSetting, word) + ) } +export { WritingIpc } diff --git a/src/main/Original/MJOriginalImageGenerate.js b/src/main/Original/MJOriginalImageGenerate.js index 62268b5..b47fe06 100644 --- a/src/main/Original/MJOriginalImageGenerate.js +++ b/src/main/Original/MJOriginalImageGenerate.js @@ -406,7 +406,14 @@ export class MJOriginalImageGenerate { * @param {*} element * @param {*} mjSetting */ - async MJImagineRequest(element, mjSetting, prompt, tasK_id = null, batch = null) { + async MJImagineRequest( + element, + mjSetting, + prompt, + tasK_id = null, + batch = null, + request_model = 'api_mj' + ) { try { if (mjSetting.apiSetting == null) { throw new Error('没有API设置,请先设置API设置') @@ -446,7 +453,10 @@ export class MJOriginalImageGenerate { if (res.code == 24) { throw new Error('提示词包含敏感词,请修改后重试') } - } else if (imagine_url.includes('api.ephone.ai')) { + } else if ( + imagine_url.includes('api.ephone.ai') || + imagine_url.includes('https://laitool.net') + ) { // ePhoneAPI let headers = { Authorization: mjSetting.apiSetting.apiKey @@ -459,19 +469,14 @@ export class MJOriginalImageGenerate { } } res = await this.discordAPI.mjApiImagine(imagine_url, data, headers) - } else if (imagine_url.includes(define.remotemj_api)) { + } else if ( + imagine_url.includes(define.remotemj_api) && + request_model == MJImageType.REMOTE_MJ + ) { // 代理模式 let headers = { 'mj-api-secret': define.API } - - // 判断数据是不是存在 - if (!mjSetting.remoteSetting.channelId) { - throw new Error('请先设置channelId') - } - if (!mjSetting.remoteSetting.accountId) { - throw new Error('请先同步账号') - } let data = { prompt: prompt, botType: 'MID_JOURNEY', @@ -664,7 +669,14 @@ export class MJOriginalImageGenerate { this.global.mjGenerateQuene.enqueue( async () => { this.global.mjGenerateQuene.setCurrentCreateItem(element) - await this.MJImagineRequest(element, mjSetting, prompt, tasK_id, batch) + await this.MJImagineRequest( + element, + mjSetting, + prompt, + tasK_id, + batch, + request_model + ) }, tasK_id, batch diff --git a/src/main/ReverseManage/Book/ReverseBook.js b/src/main/ReverseManage/Book/ReverseBook.js index 6c278c7..35099ff 100644 --- a/src/main/ReverseManage/Book/ReverseBook.js +++ b/src/main/ReverseManage/Book/ReverseBook.js @@ -70,31 +70,47 @@ export class ReverseBook extends BookBasic { */ async AutoAction(bookId) { try { - if (bookId == null || bookId == '') { - throw new Error('bookId不能为空') - } - // 1 分镜,开始调用 - let getFramRes = await this.basicReverse.GetFrameData(bookId) - if (getFramRes.code == 0) { - throw new Error(getFramRes.message) - } + // 在一键全自动之前,当前小说对应的批次任务中的所有的子任务都改为fail,然后再执行 + // 获取对应的小说小说数据,找到对应的小说视频地址 + // let _bookService = await BookService.getInstance() + // let _bookTaskService = await BookTaskService.getInstance() - // 2 截取视频 - let cutVideoRes = await this.basicReverse.CutVideoData(bookId) - if (cutVideoRes.code == 0) { - throw new Error(cutVideoRes.message) + // let bookData = _bookService.GetBookDataById(bookId) + // if (bookData.data == null) { + // throw new Error('没有找到对应的小说数据,请检查bookId是否正确') + // } + + // // 获取小说对应的批次任务数据,默认初始化为第一个 + // let bookTaskRes = _bookTaskService.GetBookTaskData({ + // bookId: bookId, + // name: 'output_00001' + // }) + // if (bookTaskRes.data.bookTasks.length <= 0 || bookTaskRes.data.total <= 0) { + // throw new Error('没有找到对应的小说批次任务数据,请检查bookId是否正确') + // } + + // // 获取小说的视频地址 + // let book = bookData.data + // let bookTask = bookTaskRes.data.bookTasks[0] + + // // 将当前小说对应的批次任务中的所有的子任务都改为fail + // let updateTaskRes = _bookTaskService.UpdetedBookTaskToFail(bookId, bookTask.id) + + // // 添加分镜任务 后面就会全自动的开始执行 + // let res = await this.basicReverse.AddFrameDataTask(bookId) + + // 添加分割视频任务 + // let res = await this.basicReverse.AddCutVideoDataTask(bookId) + + // 添加音频分离任务 + // let res = await this.basicReverse.AddSplitAudioDataTask(bookId) + + // 添加图片抽帧任务 + let res = await this.basicReverse.AddGetFrameTask(bookId) + + if (res.code == 0) { + throw new Error(res.message) } - - // 3 分离音频 - let splitAudioRes = await this.basicReverse.SplitAudioData(bookId) - if (splitAudioRes.code == 0) { - throw new Error(splitAudioRes.message) - } - - // 4 开始提取字幕 - let extractSubtitlesRes = await this.basicReverse.ExtractSubtitlesData(bookId) - - // } catch (error) { return errorMessage(error.message, 'ReverseBook_AutoAction') } diff --git a/src/main/ReverseManage/videoGenerate.js b/src/main/ReverseManage/videoGenerate.js index a0b7c97..dc9e7d7 100644 --- a/src/main/ReverseManage/videoGenerate.js +++ b/src/main/ReverseManage/videoGenerate.js @@ -1,583 +1,717 @@ -import path from "path"; -import { define } from "../../define/define"; -import { Tools } from "../tools"; -import { PublicMethod } from "../Public/publicMethod"; +import path from 'path' +import { define } from '../../define/define' +import { Tools } from '../tools' +import { PublicMethod } from '../Public/publicMethod' import { func } from '../func' -import { DEFINE_STRING } from "../../define/define_string"; -const util = require('util'); -const { spawn, exec } = require('child_process'); -const execAsync = util.promisify(exec); -const fspromises = require("fs").promises; -import { SD } from "../Public/SD" +import { DEFINE_STRING } from '../../define/define_string' +const util = require('util') +const { spawn, exec } = require('child_process') +const execAsync = util.promisify(exec) +const fspromises = require('fs').promises +import { SD } from '../Public/SD' export class VideoGenerate { - constructor(global) { - this.global = global; - this.tools = new Tools(); - this.pm = new PublicMethod(global); - this.sd = new SD(global); + constructor(global) { + this.global = global + this.tools = new Tools() + this.pm = new PublicMethod(global) + this.sd = new SD(global) + } + + /** + * 获取之前的基础配置 + */ + async GetVideoGenerateConfig() { + try { + let res = await this.tools.getJsonFilePropertyValue( + path.join(this.global.config.project_path, 'scripts/config.json'), + 'video_config', + {}, + false + ) + return { + code: 1, + data: res + } + } catch (error) { + return { + code: 0, + message: error.toString() + } } + } - /** - * 获取之前的基础配置 - */ - async GetVideoGenerateConfig() { - try { - let res = await this.tools.getJsonFilePropertyValue(path.join(this.global.config.project_path, "scripts/config.json"), "video_config", {}, false); - return { - code: 1, - data: res - } - } catch (error) { - return { - code: 0, - message: error.toString() - } - } + /** + * 保存合成视频的基础信息(Srt,audio,background) + * @param {*} value 保存合成视频的基础信息(Srt,audio,background) + */ + async SaveVideoSrtAndAudioMessage(value) { + try { + value = JSON.parse(value) + await this.tools.writeJsonFilePropertyValue( + path.join(this.global.config.project_path, 'scripts/config.json'), + 'video_config', + value + ) + return { + code: 1 + } + } catch (error) { + return { + code: 0, + message: error.toString() + } } + } - /** - * 保存合成视频的基础信息(Srt,audio,background) - * @param {*} value 保存合成视频的基础信息(Srt,audio,background) - */ - async SaveVideoSrtAndAudioMessage(value) { - try { - value = JSON.parse(value); - await this.tools.writeJsonFilePropertyValue(path.join(this.global.config.project_path, "scripts/config.json"), "video_config", value); - return { - code: 1 + /** + *全自动合成视频的任务(出图、高清、合成视频) + * @param {传入的需要执行的task_list的任务} value + */ + async ActionAutoVideoTask(value) { + try { + // 开始添加队列任务 + // 生图 + // 将当前的所有任务添加到队列中 + await this.pm.AddWebuiJson() + let batch = DEFINE_STRING.QUEUE_BATCH.AUTO_VIDEO_GENERATE + let taskPath = path.join(this.global.config.project_path, 'scripts/task_list.json') + // 获取自动保存相关的配置数据 + let auto_save_image = await this.tools.getJsonFilePropertyValue( + define.img_base, + 'auto_save_image', + {}, + false + ) + + // 保存基础配置(文案,配音,背景音乐等) + await this.tools.writeJsonFilePropertyValue( + path.join(this.global.config.project_path, 'scripts/config.json'), + 'video_config', + value[1] + ) + + let images = await this.tools.getFilesWithExtensions( + path.join(this.global.config.project_path, 'tmp/input_crop'), + '.png' + ) + if (images.length <= 0) { + throw new Error('未检测到抽帧图片。请检查') + } + + let png_files = [] + // 获取图片文件夹 + if ( + auto_save_image.save_match_count && + auto_save_image.auto_match && + images.length > auto_save_image.save_match_count && + auto_save_image.main_save_folder + ) { + png_files = await this.tools.getFilesWithExtensions( + auto_save_image.main_save_folder, + '.png' + ) + } + // 遍历所有的队列任务 + for (let i = 0; i < value[0].length; i++) { + // 将所有的数据天添加到队列(总的大队列,有很多的小队列) + // 将所有生图任务添加到队列中 + const task_list = value[0][i] + let seed = -1 + let subBatchId = `${task_list.out_folder}_image` + await fspromises.mkdir( + path.join(this.global.config.project_path, 'tmp/' + task_list.out_folder), + { recursive: true } + ) + + this.global.requestQuene.enqueue( + async () => { + let res = await this.sd.OneImageGeneration(images[0], task_list, seed) + let tmp_seed = -1 + if (seed == -1) { + tmp_seed = res } - } catch (error) { - return { - code: 0, - message: error.toString() + for (let j = 1; j < images.length; j++) { + const element = images[j] + if (!element.endsWith('.png')) { + continue + } + let has_permission = false + // 判断权限 + let permission = this.global.permission + if (permission && permission.length >= 0) { + if (permission.indexOf(DEFINE_STRING.PERMISSIONS.AUTO_SAVE_IMAGE_PERMISSION) >= 0) { + has_permission = true + } + } else { + has_permission = true + } + if ( + auto_save_image.save_match_count && + auto_save_image.auto_match && + j >= auto_save_image.save_match_count && + has_permission + ) { + // 现在随机匹配视频 + // 获取指定的文件夹中的图片 + let randomData = png_files[Math.floor(Math.random() * png_files.length)] + let base_name = path.basename(element) + 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.sd.OneImageGeneration(element, task_list, tmp_seed) + }, + `${task_list.out_folder}_${images[j]}`, + batch, + subBatchId + ) + } } - } - } + }, + `${task_list.out_folder}_${images[0]}`, + batch, + subBatchId + ) + // } + task_list.status = 'queue' + task_list['isAuto'] = 'true' + await this.pm.ModifyImageTaskList([task_list]) + // 修改状态 + // await this.pm.ModifyTaskStatus("id", task_list.id, "queue"); + // this.global.fileQueue.enqueue(async () => { + // let task_json = JSON.parse(await fspromises.readFile(taskPath, 'utf-8')); + // let index = task_json.task_list.findIndex(item => item.id == task_list.id); + // task_json.task_list[index] = task_list; + // await fspromises.writeFile(taskPath, JSON.stringify(task_json)); + // }) + this.global.newWindow[0].win.webContents.send( + DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, + task_list + ) - /** - *全自动合成视频的任务(出图、高清、合成视频) - * @param {传入的需要执行的task_list的任务} value - */ - async ActionAutoVideoTask(value) { - try { - // 开始添加队列任务 - // 生图 - // 将当前的所有任务添加到队列中 - await this.pm.AddWebuiJson(); - let batch = DEFINE_STRING.QUEUE_BATCH.AUTO_VIDEO_GENERATE; - let taskPath = path.join(this.global.config.project_path, "scripts/task_list.json"); - // 获取自动保存相关的配置数据 - let auto_save_image = await this.tools.getJsonFilePropertyValue(define.img_base, "auto_save_image", {}, false); + // 判断是不是还有批次执行的任务 + // 监听子批次完成(修改当前批次的状态) + this.global.requestQuene.setSubBatchCompletionCallback( + batch, + subBatchId, + async (failedTasks) => { + console.log(failedTasks) + if (failedTasks.length > 0) { + // 之前的任务出现错误 + // 执行错误 + } else { + // 判断是不是有错误。没有错误的话。直接修改状态。有错误直接记录错误(写入一个就行) + task_list.status = 'ok' + await this.pm.ModifyTaskStatus('id', task_list.id, 'ok') + this.global.newWindow[0].win.webContents.send( + DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, + task_list + ) + console.log(subBatchId + '生图执行完毕。可以开始执行高清') - // 保存基础配置(文案,配音,背景音乐等) - await this.tools.writeJsonFilePropertyValue(path.join(this.global.config.project_path, "scripts/config.json"), "video_config", value[1]); + // 添加高清队列 + // task_list + this.global.requestQuene.enqueue( + async () => { + await this.pm.ImproveFolder(task_list.out_folder) + }, + `${task_list.out_folder}_improve`, + batch, + `${task_list.out_folder}_improve` + ) + // 添加队列后修改状态 + task_list.status = 'video_improving' + await this.pm.ModifyTaskStatus('out_folder', task_list.out_folder, 'video_improving') + this.global.newWindow[0].win.webContents.send( + DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, + task_list + ) - let images = await this.tools.getFilesWithExtensions(path.join(this.global.config.project_path, 'tmp/input_crop'), '.png'); - if (images.length <= 0) { - throw new Error("未检测到抽帧图片。请检查"); - } + // 监听高清任务完成 + this.global.requestQuene.setSubBatchCompletionCallback( + batch, + `${task_list.out_folder}_improve`, + async (failedTasks) => { + console.log(failedTasks) + if (failedTasks.length > 0) { + // 之前的任务出现错误 + // 执行错误 + } else { + console.log(task_list.out_folder + '高清完成,可以开始合成视频') + // 添加生成视频队列 + this.global.requestQuene.enqueue( + async () => { + let video_config = JSON.parse( + await fspromises.readFile(define.video_config, 'utf-8') + ) + await this.AutoGeneretionOneVide(task_list.out_folder, video_config, value) + }, + `${task_list.out_folder}_video`, + batch, + `${task_list.out_folder}_video` + ) + task_list.status = 'video_queue' + // 添加后修改状态 + await this.pm.ModifyTaskStatus( + 'out_folder', + task_list.out_folder, + 'video_queue' + ) - let png_files = []; - // 获取图片文件夹 - if (auto_save_image.save_match_count && auto_save_image.auto_match && images.length > auto_save_image.save_match_count && auto_save_image.main_save_folder) { - png_files = await this.tools.getFilesWithExtensions(auto_save_image.main_save_folder, '.png'); - } - // 遍历所有的队列任务 - for (let i = 0; i < value[0].length; i++) { - // 将所有的数据天添加到队列(总的大队列,有很多的小队列) - // 将所有生图任务添加到队列中 - const task_list = value[0][i]; - let seed = -1; - let subBatchId = `${task_list.out_folder}_image` - await fspromises.mkdir(path.join(this.global.config.project_path, 'tmp/' + task_list.out_folder), { recursive: true }); + this.global.newWindow[0].win.webContents.send( + DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, + task_list + ) - this.global.requestQuene.enqueue(async () => { - let res = await this.sd.OneImageGeneration(images[0], task_list, seed); - let tmp_seed = -1; - if (seed == -1) { - tmp_seed = res; - } - for (let j = 1; j < images.length; j++) { - const element = images[j]; - if (!element.endsWith('.png')) { - continue; - } - let has_permission = false; - // 判断权限 - let permission = this.global.permission; - if (permission && permission.length >= 0) { - if (permission.indexOf(DEFINE_STRING.PERMISSIONS.AUTO_SAVE_IMAGE_PERMISSION) >= 0) { - has_permission = true; - } + // 监听生成视频任务完成 + this.global.requestQuene.setSubBatchCompletionCallback( + batch, + `${task_list.out_folder}_video`, + async (failedTasks) => { + console.log(failedTasks) + if (failedTasks.length > 0) { + // 之前的任务出现错误 + // 执行错误 } else { - has_permission = true; + console.log(task_list.out_folder + '合成视频完成') + // 添加生成视频队列 + task_list.status = 'video_ok' + // 添加后修改状态 + await this.pm.ModifyTaskStatus( + 'out_folder', + task_list.out_folder, + 'video_ok' + ) + this.global.newWindow[0].win.webContents.send( + DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, + task_list + ) } - if (auto_save_image.save_match_count && auto_save_image.auto_match && j >= auto_save_image.save_match_count && has_permission) { - // 现在随机匹配视频 - // 获取指定的文件夹中的图片 - let randomData = png_files[Math.floor(Math.random() * png_files.length)]; - let base_name = path.basename(element); - 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.sd.OneImageGeneration(element, task_list, tmp_seed); - }, `${task_list.out_folder}_${images[j]}`, batch, subBatchId) - } - } - - }, `${task_list.out_folder}_${images[0]}`, batch, subBatchId) - // } - task_list.status = 'queue'; - task_list["isAuto"] = "true"; - await this.pm.ModifyImageTaskList([task_list]); - // 修改状态 - // await this.pm.ModifyTaskStatus("id", task_list.id, "queue"); - // this.global.fileQueue.enqueue(async () => { - // let task_json = JSON.parse(await fspromises.readFile(taskPath, 'utf-8')); - // let index = task_json.task_list.findIndex(item => item.id == task_list.id); - // task_json.task_list[index] = task_list; - // await fspromises.writeFile(taskPath, JSON.stringify(task_json)); - // }) - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, task_list) - - // 判断是不是还有批次执行的任务 - // 监听子批次完成(修改当前批次的状态) - this.global.requestQuene.setSubBatchCompletionCallback(batch, subBatchId, async (failedTasks) => { - console.log(failedTasks) - if (failedTasks.length > 0) { - // 之前的任务出现错误 - // 执行错误 - } else { - // 判断是不是有错误。没有错误的话。直接修改状态。有错误直接记录错误(写入一个就行) - task_list.status = "ok"; - await this.pm.ModifyTaskStatus("id", task_list.id, 'ok'); - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, task_list) - console.log(subBatchId + "生图执行完毕。可以开始执行高清") - - // 添加高清队列 - // task_list - this.global.requestQuene.enqueue(async () => { - await this.pm.ImproveFolder(task_list.out_folder); - }, `${task_list.out_folder}_improve`, batch, `${task_list.out_folder}_improve`); - // 添加队列后修改状态 - task_list.status = "video_improving" - await this.pm.ModifyTaskStatus('out_folder', task_list.out_folder, "video_improving"); - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, task_list) - - // 监听高清任务完成 - this.global.requestQuene.setSubBatchCompletionCallback(batch, `${task_list.out_folder}_improve`, async (failedTasks) => { - console.log(failedTasks) - if (failedTasks.length > 0) { - // 之前的任务出现错误 - // 执行错误 - } else { - - console.log(task_list.out_folder + "高清完成,可以开始合成视频"); - // 添加生成视频队列 - this.global.requestQuene.enqueue(async () => { - let video_config = JSON.parse(await fspromises.readFile(define.video_config, 'utf-8')); - await this.AutoGeneretionOneVide(task_list.out_folder, video_config, value); - }, `${task_list.out_folder}_video`, batch, `${task_list.out_folder}_video`); - task_list.status = "video_queue"; - // 添加后修改状态 - await this.pm.ModifyTaskStatus('out_folder', task_list.out_folder, "video_queue"); - - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, task_list) - - // 监听生成视频任务完成 - this.global.requestQuene.setSubBatchCompletionCallback(batch, `${task_list.out_folder}_video`, async (failedTasks) => { - console.log(failedTasks) - if (failedTasks.length > 0) { - // 之前的任务出现错误 - // 执行错误 - } else { - console.log(task_list.out_folder + "合成视频完成"); - // 添加生成视频队列 - task_list.status = "video_ok"; - // 添加后修改状态 - await this.pm.ModifyTaskStatus('out_folder', task_list.out_folder, "video_ok"); - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, task_list) - } - }) - - } - }) - } - }) + } + ) + } + } + ) } + } + ) + } - // 监听总批次完成 - this.global.requestQuene.setBatchCompletionCallback(batch, async (failedTasks) => { - if (failedTasks.length > 0) { - let message = ` + // 监听总批次完成 + this.global.requestQuene.setBatchCompletionCallback(batch, async (failedTasks) => { + if (failedTasks.length > 0) { + let message = ` 批次生成任务都已完成。 但是以下任务执行失败: ` - failedTasks.forEach(({ taskId, error }) => { - message += `${taskId}-, \n 错误信息: ${error}` + '\n'; - }); + failedTasks.forEach(({ taskId, error }) => { + message += `${taskId}-, \n 错误信息: ${error}` + '\n' + }) - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { - code: 0, - message: message - }) - } else { - console.log("所有的自动生成任务完成"); - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { - code: 1, - message: "所有自动任务完成" - }) - } - - this.global.fileQueue.enqueue(async () => { - // 读取最新的数据删除 - // 将自动化标识删除 - let task_json = JSON.parse(await fspromises.readFile(taskPath, 'utf-8')); - for (let i = 0; i < task_json.task_list.length; i++) { - let task_list = task_json.task_list[i]; - task_list["isAuto"] = "false"; - await this.pm.ModifyImageTaskList([task_list]); - } - }) - }) - return { - code: 1, - message: "自动任务添加到队列中" - } - - } catch (error) { - console.error(error) - return { - code: 0, - message: error.message - } + this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { + code: 0, + message: message + }) + } else { + console.log('所有的自动生成任务完成') + this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { + code: 1, + message: '所有自动任务完成' + }) } - } - /** - * 获取文件夹下面的子文件夹,添加判断条件 - * @param {value} value - * @returns - */ - async getSubFolderList(value) { - try { - let folder = await this.pm.getSubFolderList(path.join(this.global.config.project_path, "tmp"), value[0], value[1]); - // 找到所有的数据(查询状态返回) - let task_path = path.join(this.global.config.project_path, "scripts/task_list.json"); - let isExist = await this.tools.checkExists(task_path); - let data = []; - if (!isExist) { - for (let i = 0; i < folder.length; i++) { - const element = folder[i]; - let obj = { - folder: element, - status: "unkown error" - } - data.push(obj); - } - } else { - let task_list = JSON.parse(await fspromises.readFile(task_path, 'utf-8'))["task_list"]; - // 查询状态 - for (let i = 0; i < folder.length; i++) { - const element = folder[i]; - let index = task_list.findIndex(item => item.out_folder == element); - if (index < 0) { - let obj = { - folder: element, - status: "unkown error" - } - data.push(obj); - } else { - let status = task_list[index].status; - let obj = { - folder: element, - status: status - } - data.push(obj); - } - } - } - return { - code: 1, - data: data - } - } catch (error) { - return { - code: 0, - message: `Error Message ${error}` - } + this.global.fileQueue.enqueue(async () => { + // 读取最新的数据删除 + // 将自动化标识删除 + let task_json = JSON.parse(await fspromises.readFile(taskPath, 'utf-8')) + for (let i = 0; i < task_json.task_list.length; i++) { + let task_list = task_json.task_list[i] + task_list['isAuto'] = 'false' + await this.pm.ModifyImageTaskList([task_list]) + } + }) + }) + return { + code: 1, + message: '自动任务添加到队列中' + } + } catch (error) { + console.error(error) + return { + code: 0, + message: error.message + } + } + } + + /** + * 获取文件夹下面的子文件夹,添加判断条件 + * @param {value} value + * @returns + */ + async getSubFolderList(value) { + try { + let folder = await this.pm.getSubFolderList( + path.join(this.global.config.project_path, 'tmp'), + value[0], + value[1] + ) + // 找到所有的数据(查询状态返回) + let task_path = path.join(this.global.config.project_path, 'scripts/task_list.json') + let isExist = await this.tools.checkExists(task_path) + let data = [] + if (!isExist) { + for (let i = 0; i < folder.length; i++) { + const element = folder[i] + let obj = { + folder: element, + status: 'unkown error' + } + data.push(obj) } - } - - /** - * 自动生成一个视频 - * @param {视频生成图片的文件夹} element - * @param {视频配置} video_config - * @param {参数。第一个值为数组,为生成的图片文件。第二个参数是配置} value - */ - async AutoGeneretionOneVide(element, video_config, value) { - try { - let background_music = ""; - // 读取背景音乐的路径 - if (value[1].background_music != "") { - let background_music_config = (await func.getClipSetting("background_music_setting")).value; - let background_music_filter = background_music_config.filter(item => item.id == value[1].background_music); - if (background_music_filter.length <= 0) { - throw new Error("背景音乐对应的配置没有找到"); - } else { - background_music = background_music_filter[0].folder_path; - } - } - - // 随机获取字幕设置 - let ass_config = video_config.assConfig; - let ass_random_index = Math.floor(Math.random() * ass_config.length); - let watermark_config = video_config.watermarkConfig; - let watermark_random_index = Math.floor(Math.random() * watermark_config.length); - - // 对每个视频生成配置文件并将其添加到配置文件中 + } else { + let task_list = JSON.parse(await fspromises.readFile(task_path, 'utf-8'))['task_list'] + // 查询状态 + for (let i = 0; i < folder.length; i++) { + const element = folder[i] + let index = task_list.findIndex((item) => item.out_folder == element) + if (index < 0) { let obj = { - srt_path: value[1].srt_path, - // 字幕样式(需要随机) - srt_style: ass_config[ass_random_index], - audio_path: value[1].audio_path, - background_music_folder: background_music, - // 水印设置(需要随机) - friendly_reminder: watermark_config[watermark_random_index], - video_resolution_x: video_config.video_resolution_x, - video_resolution_y: video_config.video_resolution_y, - outpue_file: path.join(this.global.config.project_path, this.global.config.project_name + element + ".mp4"), - image_folder: path.join(this.global.config.project_path, 'tmp/' + element), - srt_config: path.join(this.global.config.project_path, 'scripts/config.json'), - mp4_file_txt: path.join(this.global.config.project_path, `scripts/${element}.txt`), - status: "no", - audio_sound_size: video_config.audioSoundSize, - background_music_sound_size: video_config.backgroundMusicSoundSize, - keyFrame: video_config.keyframe, - frameRate: video_config.frameRate, - bitRate: video_config.bitRate + folder: element, + status: 'unkown error' } - // 将配置文件写入 - let project_config_path = path.join(this.global.config.project_path, `scripts/${element}.json`); - await fspromises.writeFile(project_config_path, JSON.stringify(obj)); - // let task_list = JSON.parse(await fspromises.readFile(path.join(this.global.config.project_path,'scripts/task_'))); - let scriptPath = path.join(define.scripts_path, 'Lai.exe'); - let gpu = this.global.gpu.type; - if (video_config.libx264) { - gpu = "OTHER" + data.push(obj) + } else { + let status = task_list[index].status + let obj = { + folder: element, + status: status } - // 执行生成图片的脚本 - let script = `cd "${define.scripts_path}" && "${scriptPath}" -c "${project_config_path.replaceAll('\\', '/')}" "${gpu}"`; - const output = await execAsync(script, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' }); - if (output.stderr != '') { - obj.status = "video_error"; - obj.stdout = output.stdout; - obj.stderr = output.stderr; - await this.pm.ModifyTaskStatus('out_folder', element, "video_error"); - throw new Error(output.stderr); - } else { - obj.status = "video_ok"; - obj.stdout = output.stdout; - obj.stderr = output.stderr; - await this.pm.ModifyTaskStatus('out_folder', element, "video_ok"); - // 将写出的视频中的exif数据删除 - // await this.tools.deletePngAndDeleteExifData(obj.outpue_file, path.join(this.global.config.project_path, this.global.config.project_name + "_" + element.split('_')[element.split('_').length - 1] + ".mp4")); - } - - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, { - out_folder: element, - status: obj.status - }) - } catch (error) { - //手动修改 - let task_list_json = JSON.parse(await fspromises.readFile(path.join(this.global.config.project_path, "scripts/task_list.json"), 'utf-8')); - let index = task_list_json.task_list.findIndex(item => item.out_folder == element); - if (index < 0) { - throw new Error("未找到对应的任务"); - } - task_list_json.task_list[index].status = "video_error"; - task_list_json.task_list[index].errorMessage = error.toString(); - // 写回 - await fspromises.writeFile(path.join(this.global.config.project_path, "scripts/task_list.json"), JSON.stringify(task_list_json)); - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, { - out_folder: element, - status: "video_error" - }) - throw error; + data.push(obj) + } } + } + return { + code: 1, + data: data + } + } catch (error) { + return { + code: 0, + message: `Error Message ${error}` + } } + } - /** - * 添加自动合成任务到队列中去 - * @param {} value - */ - async AutoGeneretionVideo(value) { - try { - // 先检查所有的条件 - let batch = DEFINE_STRING.QUEUE_BATCH.AUTO_VIDEO_GENERATE_SINGLE; - // console.log(value); - let video_config = JSON.parse(await fspromises.readFile(define.video_config, 'utf-8')); - let res = await this.CheckVideoGenerattionAllCondition(video_config, value[1], value[0]); - if (res.code == 0) { - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, res); - return res; - } - for (let i = 0; i < value[0].length; i++) { - const element = value[0][i]; - this.global.requestQuene.enqueue(async () => { - await this.AutoGeneretionOneVide(element, video_config, value) - }, element, batch); + /** + * 自动生成一个视频 + * @param {视频生成图片的文件夹} element + * @param {视频配置} video_config + * @param {参数。第一个值为数组,为生成的图片文件。第二个参数是配置} value + */ + async AutoGeneretionOneVide(element, video_config, value) { + try { + let background_music = '' + // 读取背景音乐的路径 + if (value[1].background_music != '') { + let background_music_config = (await func.getClipSetting('background_music_setting')).value + let background_music_filter = background_music_config.filter( + (item) => item.id == value[1].background_music + ) + if (background_music_filter.length <= 0) { + throw new Error('背景音乐对应的配置没有找到') + } else { + background_music = background_music_filter[0].folder_path + } + } - // 添加队列后修改状态 - await this.pm.ModifyTaskStatus('out_folder', element, "video_queue"); - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, { - out_folder: element, - status: "video_queue" - }) - } + // 随机获取字幕设置 + let ass_config = video_config.assConfig + let ass_random_index = Math.floor(Math.random() * ass_config.length) + let watermark_config = video_config.watermarkConfig + let watermark_random_index = Math.floor(Math.random() * watermark_config.length) - this.global.requestQuene.setBatchCompletionCallback(batch, (failedTasks) => { - if (failedTasks.length > 0) { - let message = ` + // 对每个视频生成配置文件并将其添加到配置文件中 + let obj = { + srt_path: value[1].srt_path, + // 字幕样式(需要随机) + srt_style: ass_config[ass_random_index], + audio_path: value[1].audio_path, + background_music_folder: background_music, + // 水印设置(需要随机) + friendly_reminder: watermark_config[watermark_random_index], + video_resolution_x: video_config.video_resolution_x, + video_resolution_y: video_config.video_resolution_y, + outpue_file: path.join( + this.global.config.project_path, + this.global.config.project_name + element + '.mp4' + ), + image_folder: path.join(this.global.config.project_path, 'tmp/' + element), + srt_config: path.join(this.global.config.project_path, 'scripts/config.json'), + mp4_file_txt: path.join(this.global.config.project_path, `scripts/${element}.txt`), + status: 'no', + audio_sound_size: video_config.audioSoundSize, + background_music_sound_size: video_config.backgroundMusicSoundSize, + keyFrame: video_config.keyframe, + frameRate: video_config.frameRate, + bitRate: video_config.bitRate + } + // 将配置文件写入 + let project_config_path = path.join( + this.global.config.project_path, + `scripts/${element}.json` + ) + await fspromises.writeFile(project_config_path, JSON.stringify(obj)) + // let task_list = JSON.parse(await fspromises.readFile(path.join(this.global.config.project_path,'scripts/task_'))); + let scriptPath = path.join(define.scripts_path, 'Lai.exe') + let gpu = this.global.gpu.type + if (video_config.libx264) { + gpu = 'OTHER' + } + // 执行生成图片的脚本 + let script = `cd "${ + define.scripts_path + }" && "${scriptPath}" -c "${project_config_path.replaceAll('\\', '/')}" "${gpu}"` + const output = await execAsync(script, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' }) + if (output.stderr != '') { + obj.status = 'video_error' + obj.stdout = output.stdout + obj.stderr = output.stderr + await this.pm.ModifyTaskStatus('out_folder', element, 'video_error') + throw new Error(output.stderr) + } else { + obj.status = 'video_ok' + obj.stdout = output.stdout + obj.stderr = output.stderr + await this.pm.ModifyTaskStatus('out_folder', element, 'video_ok') + // 将写出的视频中的exif数据删除 + // await this.tools.deletePngAndDeleteExifData(obj.outpue_file, path.join(this.global.config.project_path, this.global.config.project_name + "_" + element.split('_')[element.split('_').length - 1] + ".mp4")); + } + + this.global.newWindow[0].win.webContents.send(DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, { + out_folder: element, + status: obj.status + }) + } catch (error) { + //手动修改 + let task_list_json = JSON.parse( + await fspromises.readFile( + path.join(this.global.config.project_path, 'scripts/task_list.json'), + 'utf-8' + ) + ) + let index = task_list_json.task_list.findIndex((item) => item.out_folder == element) + if (index < 0) { + throw new Error('未找到对应的任务') + } + task_list_json.task_list[index].status = 'video_error' + task_list_json.task_list[index].errorMessage = error.toString() + // 写回 + await fspromises.writeFile( + path.join(this.global.config.project_path, 'scripts/task_list.json'), + JSON.stringify(task_list_json) + ) + this.global.newWindow[0].win.webContents.send(DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, { + out_folder: element, + status: 'video_error' + }) + throw error + } + } + + /** + * 添加自动合成任务到队列中去 + * @param {} value + */ + async AutoGeneretionVideo(value) { + try { + // 先检查所有的条件 + let batch = DEFINE_STRING.QUEUE_BATCH.AUTO_VIDEO_GENERATE_SINGLE + // console.log(value); + let video_config = JSON.parse(await fspromises.readFile(define.video_config, 'utf-8')) + let res = await this.CheckVideoGenerattionAllCondition(video_config, value[1], value[0]) + if (res.code == 0) { + this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, res) + return res + } + for (let i = 0; i < value[0].length; i++) { + const element = value[0][i] + this.global.requestQuene.enqueue( + async () => { + await this.AutoGeneretionOneVide(element, video_config, value) + }, + element, + batch + ) + + // 添加队列后修改状态 + await this.pm.ModifyTaskStatus('out_folder', element, 'video_queue') + this.global.newWindow[0].win.webContents.send(DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH, { + out_folder: element, + status: 'video_queue' + }) + } + + this.global.requestQuene.setBatchCompletionCallback(batch, (failedTasks) => { + if (failedTasks.length > 0) { + let message = ` 生成视频任务都已完成。 但是以下任务执行失败: ` - failedTasks.forEach(({ taskId, error }) => { - message += `${taskId}-, \n 错误信息: ${error}` + '\n'; - }); + failedTasks.forEach(({ taskId, error }) => { + message += `${taskId}-, \n 错误信息: ${error}` + '\n' + }) - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { - code: 0, - message: message - }) - } else { - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { - code: 1, - message: "所有生成视频任务已完成" - }) - } - }) - return { - code: 1 - } - } catch (error) { - return { - code: 0, - message: error.toString() - } - } - } - - /** - * 检测生成视频条件的必要条件 - * @param {生成视频配置文件} video_config - * @param {生成视频的基础配置} simpleData - * @param {输出文件夹位置,为null,检查input文件里面的图片文件是不是和配置文件中的图片数量一致} out_folder - * @returns - */ - async CheckVideoGenerattionAllCondition(video_config, simpleData, out_folder) { - let message = ""; - if (video_config == null) { - video_config = JSON.parse(await fspromises.readFile(define.video_config, 'utf-8')); - } - // 基础信息检测 - if (video_config.audioSoundSize == null) { - message += "配音大小不能为空" + '\n'; - } - if (video_config.backgroundMusicSoundSize == null) { - message += "背景音乐音量大学不能为空" + '\n'; - } - if (video_config.video_resolution_x == null) { - message += "生成视频的宽度不能为空" + '\n'; - } - if (video_config.video_resolution_y == null) { - message += "生成视频的高度不能为空" + '\r\n'; - } - if (video_config.offsetValue == null) { - message += "视频的上下偏移量不能为空" + '\n'; - } - if (video_config.frameRate == null) { - message += "生成视频的帧率不能为空" + '\n'; - } - if (video_config.bitRate == null) { - message += "生成视频码率的不能为空" + '\n'; - } - - // 判断字幕列表中是不是有数据 - if (video_config.assConfig == null || video_config.assConfig.length <= 0) { - message += "字幕设置最少包含一条" + '\n'; - } - if (video_config.watermarkConfig == null || video_config.watermarkConfig.length <= 0) { - message += "水印设置最少包含一条" + '\n'; - } - // 判断背景音乐文件夹中是不是存在。并且检查其中是不是有音乐文件 - if (simpleData.background_music == "") { - message += "背景音乐文件夹路径不能为空" + '\n'; - } - // 判断背景音乐文件夹是不是存在 - if (await this.tools.checkExists(simpleData.background_music)) { - message += "背景音乐文件夹不存在" + '\n'; - } - // 判断里面是不是有MP3或者是wav - let clip_json = JSON.parse(await fspromises.readFile(define.clip_setting, 'utf-8'))["background_music_setting"]; - let background_music_obj = clip_json.filter(item => item.id == simpleData.background_music)[0]; - let mp3_file = await this.tools.getFilesWithExtensions(background_music_obj.folder_path, '.mp3'); - let wav_file = await this.tools.getFilesWithExtensions(background_music_obj.folder_path, '.wav'); - if (mp3_file.length <= 0 && wav_file.length <= 0) { - message += "背景文件夹中没有 MP3 或 WAV 文件" + '\n'; - } - - let config_path = path.join(this.global.config.project_path, 'scripts/config.json'); - let isE = await this.tools.checkExists(config_path); - if (!isE) { - message += "配置文件不存在。请先导入字幕文件。并调整时间轴。" + this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { + code: 0, + message: message + }) } else { - // 判断文案时间信息和图片信息是不是相同 - let config_json = JSON.parse(await fspromises.readFile(config_path, 'utf-8')); - let len = config_json["srt_time_information"].length; - if (out_folder == null) { - // 判断输入文件中的配置文件中的数量是不是对上 - let img_l = await this.tools.getFilesWithExtensions(path.join(this.global.config.project_path, "tmp/input_crop"), '.png'); - if (img_l.length != len) { - message += `input_crop 文件里面图片和文案信息对不上。检查是不是图片数量不对` - } - // 判断是不是有tag文件 - let tag_txt = await this.tools.getFilesWithExtensions(path.join(this.global.config.project_path, "tmp/input_crop"), '.txt'); - let c_j = await this.tools.getFilesWithExtensions(path.join(this.global.config.project_path, "tmp/input_crop"), '.json'); - if (tag_txt.length > 0) { - if (tag_txt.length != img_l.length) { - message += "反推的tag文件和图片的数量对不上。" + "\n"; - } - } else if (c_j.length > 0) { - if (c_j.length != img_l.length) { - message += "已存在的配置文件和图片的数量对不上。" + "\n"; - } - } else { - message += "好像没有反推呢" + "\n"; - } - } else { - // 判断当前的输入的 out_folder 中图片数量是不是可以和配置文件中能否对上 - for (let i = 0; i < out_folder.length; i++) { - const element = out_folder[i]; - let image_l = await this.tools.getFilesWithExtensions(path.join(this.global.config.project_path, "tmp/" + element), '.png'); - if (image_l.length != len) { - message += `${element} 文件里面图片和文案信息对不上。检查是不是图片数量不对` - } - } - } - } - if (message != '') { - let res = { - code: 0, - message: message - } - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, res) - return res; - } - return { + this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { code: 1, + message: '所有生成视频任务已完成' + }) } + }) + return { + code: 1 + } + } catch (error) { + return { + code: 0, + message: error.toString() + } } -} \ No newline at end of file + } + + /** + * 检测生成视频条件的必要条件 + * @param {生成视频配置文件} video_config + * @param {生成视频的基础配置} simpleData + * @param {输出文件夹位置,为null,检查input文件里面的图片文件是不是和配置文件中的图片数量一致} out_folder + * @returns + */ + async CheckVideoGenerattionAllCondition(video_config, simpleData, out_folder) { + let message = '' + if (video_config == null) { + video_config = JSON.parse(await fspromises.readFile(define.video_config, 'utf-8')) + } + // 基础信息检测 + if (video_config.audioSoundSize == null) { + message += '配音大小不能为空' + '\n' + } + if (video_config.backgroundMusicSoundSize == null) { + message += '背景音乐音量大学不能为空' + '\n' + } + if (video_config.video_resolution_x == null) { + message += '生成视频的宽度不能为空' + '\n' + } + if (video_config.video_resolution_y == null) { + message += '生成视频的高度不能为空' + '\r\n' + } + if (video_config.offsetValue == null) { + message += '视频的上下偏移量不能为空' + '\n' + } + if (video_config.frameRate == null) { + message += '生成视频的帧率不能为空' + '\n' + } + if (video_config.bitRate == null) { + message += '生成视频码率的不能为空' + '\n' + } + + // 判断字幕列表中是不是有数据 + if (video_config.assConfig == null || video_config.assConfig.length <= 0) { + message += '字幕设置最少包含一条' + '\n' + } + if (video_config.watermarkConfig == null || video_config.watermarkConfig.length <= 0) { + message += '水印设置最少包含一条' + '\n' + } + // 判断背景音乐文件夹中是不是存在。并且检查其中是不是有音乐文件 + if (simpleData.background_music == '') { + message += '背景音乐文件夹路径不能为空' + '\n' + } + // 判断背景音乐文件夹是不是存在 + if (await this.tools.checkExists(simpleData.background_music)) { + message += '背景音乐文件夹不存在' + '\n' + } + // 判断里面是不是有MP3或者是wav + let clip_json = JSON.parse(await fspromises.readFile(define.clip_setting, 'utf-8'))[ + 'background_music_setting' + ] + let background_music_obj = clip_json.filter((item) => item.id == simpleData.background_music)[0] + let mp3_file = await this.tools.getFilesWithExtensions(background_music_obj.folder_path, '.mp3') + let wav_file = await this.tools.getFilesWithExtensions(background_music_obj.folder_path, '.wav') + if (mp3_file.length <= 0 && wav_file.length <= 0) { + message += '背景文件夹中没有 MP3 或 WAV 文件' + '\n' + } + + let config_path = path.join(this.global.config.project_path, 'scripts/config.json') + let isE = await this.tools.checkExists(config_path) + if (!isE) { + message += '配置文件不存在。请先导入字幕文件。并调整时间轴。' + } else { + // 判断文案时间信息和图片信息是不是相同 + let config_json = JSON.parse(await fspromises.readFile(config_path, 'utf-8')) + let len = config_json['srt_time_information'].length + if (out_folder == null) { + // 判断输入文件中的配置文件中的数量是不是对上 + let img_l = await this.tools.getFilesWithExtensions( + path.join(this.global.config.project_path, 'tmp/input_crop'), + '.png' + ) + if (img_l.length != len) { + message += `input_crop 文件里面图片和文案信息对不上。检查是不是图片数量不对` + } + // 判断是不是有tag文件 + let tag_txt = await this.tools.getFilesWithExtensions( + path.join(this.global.config.project_path, 'tmp/input_crop'), + '.txt' + ) + let c_j = await this.tools.getFilesWithExtensions( + path.join(this.global.config.project_path, 'tmp/input_crop'), + '.json' + ) + if (tag_txt.length > 0) { + if (tag_txt.length != img_l.length) { + message += '反推的tag文件和图片的数量对不上。' + '\n' + } + } else if (c_j.length > 0) { + if (c_j.length != img_l.length) { + message += '已存在的配置文件和图片的数量对不上。' + '\n' + } + } else { + message += '好像没有反推呢' + '\n' + } + } else { + // 判断当前的输入的 out_folder 中图片数量是不是可以和配置文件中能否对上 + for (let i = 0; i < out_folder.length; i++) { + const element = out_folder[i] + let image_l = await this.tools.getFilesWithExtensions( + path.join(this.global.config.project_path, 'tmp/' + element), + '.png' + ) + if (image_l.length != len) { + message += `${element} 文件里面图片和文案信息对不上。检查是不是图片数量不对` + } + } + } + } + if (message != '') { + let res = { + code: 0, + message: message + } + this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, res) + return res + } + return { + code: 1 + } + } +} diff --git a/src/main/ReverseManage/writing.js b/src/main/ReverseManage/writing.js deleted file mode 100644 index 17f03eb..0000000 --- a/src/main/ReverseManage/writing.js +++ /dev/null @@ -1,301 +0,0 @@ -let path = require("path"); -let fspromises = require("fs").promises; -import { Tools } from "../tools"; -import { DEFINE_STRING } from "../../define/define_string"; -import { PublicMethod } from "../Public/publicMethod"; -import { define } from "../../define/define"; -import { get, has } from "lodash"; -import { ClipSetting } from "../../define/setting/clipSetting"; -const { v4: uuidv4 } = require('uuid'); // 引入UUID库来生成唯一标识符 -let tools = new Tools(); - -export class Writing { - constructor(global) { - this.global = global - this.pm = new PublicMethod(global); - } - - /** - * 将文案信息写入到本地的文案文件中 - * @param {*} value - */ - async SaveWordTxt(value) { - try { - let word_path = path.join(global.config.project_path, "文案.txt"); - await tools.writeArrayToFile(value, word_path); - return { - code: 1, - message: "保存成功" - } - } catch (error) { - throw new Error(error); - } - } - - /** - * 将分镜的时间信息添加道配置文件中 - * @param {*} value 是一个数组,0 :写入的数据 1:写入的属性 2:是否需要解析 - */ - async SaveCopywritingInformation(value) { - try { - return await this.pm.SaveConfigJsonProperty(value); - } catch (error) { - return { - code: 0, - message: error.toString() - } - } - } - - /** - * 获取Config.json文件中指定的属性 - * @param {Array} value 传入的值 0 : 需要获取的属性 1: 返回的默认值 - * @returns - */ - async GetConfigJson(value) { - try { - return await this.pm.GetConfigJson(value, false); - } catch (error) { - return { - code: 0, - message: error.toString() - } - } - } - - /** - * 获取当前项目下面的文案 - */ - async GetProjectWord() { - try { - // 先判断当前的项目文件下面是不是又配置文件。没有才读取文案 - let srt_config_path = path.join(global.config.project_path, "scripts/config.json"); - let isExist = await tools.checkExists(srt_config_path); - let data = null; - let isImformation = false; - if (isExist) { - let config_1 = JSON.parse(await fspromises.readFile(srt_config_path)); - isImformation = has(config_1, 'srt_time_information'); - if (isImformation) { - data = JSON.parse(await fspromises.readFile(srt_config_path)).srt_time_information; - } - } - - if (!isExist || !isImformation) { - let word_path = path.join(global.config.project_path, "文案.txt"); - let isExistWord = await tools.checkExists(word_path); - if (!isExistWord) { - return { - code: 0, - message: "没有文案文件" - } - } - let data = await fspromises.readFile(word_path, { encoding: 'utf-8' }); - let lines = data.split(/\r?\n/); - // 打印或返回这个数组 - // console.log(lines); - // 判断是不是有洗稿后的文件 - let new_srt_path = path.join(global.config.project_path, "new_word.txt"); - let isExistAfterGPTWord = await tools.checkExists(new_srt_path); - let after_data = null; - if (isExistAfterGPTWord) { - after_data = (await fspromises.readFile(new_srt_path, { encoding: 'utf-8' })).split(/\r?\n/); - } - // 判断抽帧文件是不是存在 - // 返回图片信息 - let old_image_path_list = await tools.getFilesWithExtensions(path.join(global.config.project_path, "tmp/input_crop"), '.png'); - let res = []; - let lastId = ''; - // 处理数据 - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - let id = uuidv4(); - let after_gpt = null; - if (after_data != null) { - after_gpt = after_data[i]; - } - - let img_path = null; - if (old_image_path_list != null) { - img_path = old_image_path_list[i]; - } - let obj = { - no: i + 1, - id: id, - lastId: lastId, - word: line, - old_image: img_path, - after_gpt: after_gpt, - start_time: null, - end_time: null, - timeLimit: null, - subValue: [] - } - res.push(obj); - lastId = id; - } - return { - code: 1, - type: 0, - data: res - } - } else { - let data = JSON.parse(await fspromises.readFile(srt_config_path)).srt_time_information; - return { - code: 1, - type: 1, - data: data - } - } - - } catch (error) { - throw new Error(error); - } - } - - /** - * 搭导入srt。然后加载时间轴。完全匹配失败的将会还是会导入然后手动手动切换 - * @param {文案洗稿界面信息} textData - */ - async ImportSrtAndGetTime(data) { - let textData = data[0]; - let init_num = textData.length; - let srt_path = data[1]; - let current_text = ""; - try { - if (!srt_path) { - // 获取项目下面的所有的srt - let srtfiles = await tools.getFilesWithExtensions(global.config.project_path, '.srt'); - if (srtfiles.length <= 0) { - throw new Error("没有SRT文件"); - } - srt_path = srtfiles[0]; - } - let srt_data = (await fspromises.readFile(srt_path, 'utf-8')).toString("utf-8"); - const entries = srt_data.replace(/\r\n/g, '\n').split('\n\n'); - let data = entries.map(entry => { - const lines = entry.split('\n'); - if (lines.length >= 3) { - const times = lines[1]; - const text = lines.slice(2).join(' '); - const [start, end] = times.split(' --> ').map(time => { - const [hours, minutes, seconds] = time.split(':'); - const [sec, millis] = seconds.split(','); - return ((parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(sec)) * 1000 + parseInt(millis)); - }); - return { start, end, text, id: uuidv4() }; - } - }).filter(entry => entry); - // 开始匹配(洗稿后的) - let srt_list = []; - let srt_obj = null - let text_count = 0; - let tmp_str = ""; - for (let i = 0; i < data.length;) { - let srt_value = data[i].text; - current_text = `字幕: “${srt_value}” 和文案第${text_count + 1} 行数据 “${textData[text_count].after_gpt}” 数据不匹配(检查一下上下文)`; - let start_time = data[i].start; - let end_time = data[i].end; - let obj = { - start_time, - end_time, - srt_value, - id: data[i].id - }; - // 判断当前字幕是不是在当前句 - // 不能用简单的包含,而是将数据进行去除特殊符号拼接后判断是不是相同 - tmp_str += srt_value; - if (tools.removePunctuationIncludingEllipsis(textData[text_count].after_gpt).startsWith(tools.removePunctuationIncludingEllipsis(tmp_str))) { - if (srt_obj == null) { - srt_obj = {} - srt_obj.id = uuidv4(); - srt_obj.start_time = start_time; - srt_obj.value = srt_value; - srt_obj.subValue = [obj]; - } - else { - srt_obj.value = srt_obj.value + srt_value; - srt_obj.subValue = [...srt_obj.subValue, obj]; - } - textData[text_count].start_time = srt_obj.start_time; - textData[text_count].subValue = srt_obj.subValue - srt_list.push(obj); - i++; - } else { - // 判断下一句文件是不是以当当前巨开头。是的话继续。不是的话。直接返回后面的所有信息 - if (tools.removePunctuationIncludingEllipsis(textData[text_count + 1].after_gpt).startsWith(tools.removePunctuationIncludingEllipsis(srt_value))) { - textData[text_count].end_time = srt_list[srt_list.length - 1].end_time; - text_count++; - srt_obj = null; - tmp_str = "" - } else { - // 将下面的数据直接 添加到textData后面。 - // 修改当前行数据的结束事件为 - if (srt_list.length > 0) { - textData[text_count].end_time = srt_list[srt_list.length - 1].end_time; - text_count++; - } - // 将后面的数据直接添加 - let lastId = textData[textData.length - 1].id; - for (let j = i; j < data.length; j++) { - // 直接修改原有数据 - if (text_count < init_num) { - textData[text_count].subValue = [{ - start_time: data[j].start, - end_time: data[j].end, - id: data[j].id, - srt_value: data[j].text - }] - textData[text_count].start_time = data[j].start; - textData[text_count].end_time = data[j].end; - text_count++; - } - else { - let id = uuidv4(); - // 添加 - let obj = { - no: j + 1, - id: id, - word: null, - lastId: lastId, - old_image: path.normalize(define.zhanwei_image), - after_gpt: null, - start_time: data[j].start, - end_time: data[j].end, - subValue: [{ - start_time: data[j].start, - end_time: data[j].end, - id: data[j].id, - srt_value: data[j].text - }] - } - lastId = id; - textData.push(obj); - } - } - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { - code: 0, - message: current_text - }) - return { - code: 1, - data: textData - } - } - } - } - // 最后对齐 - textData[textData.length - 1].end_time = srt_list[srt_list.length - 1].end_time - // 返回数据 - return { - code: 1, - data: textData - } - } catch (error) { - return { - code: 0, - message: error.toString() - } - } - } -} \ No newline at end of file diff --git a/src/main/Task/basicReverse.js b/src/main/Task/basicReverse.js index 5cd0e28..fea6c74 100644 --- a/src/main/Task/basicReverse.js +++ b/src/main/Task/basicReverse.js @@ -10,13 +10,17 @@ import { LoggerStatus, LoggerType, OtherData } from '../../define/enum/softwareE import { errorMessage, successMessage } from '../generalTools' import { CheckFileOrDirExist, CheckFolderExistsOrCreate } from '../../define/Tools/file' import { BookTaskDetailService } from '../../define/db/service/Book/bookTaskDetailService' +import { BookBackTaskListService } from '../../define/db/service/Book/bookBackTaskListService' import { BookTaskService } from '../../define/db/service/Book/bookTaskService' import { isEmpty, set } from 'lodash' -import ffmpeg from 'fluent-ffmpeg' -import { SetFfmpegPath } from '../setting/ffmpegSetting' import { TimeStringToMilliseconds, MillisecondsToTimeString } from '../../define/Tools/time' -import { BookTaskStatus } from '../../define/enum/bookEnum' -SetFfmpegPath() +import { FfmpegOptions } from './ffmpegOptions' +import { + BookBackTaskType, + BookTaskStatus, + BookType, + TaskExecuteType +} from '../../define/enum/bookEnum' const fspromises = fs.promises @@ -26,145 +30,42 @@ const fspromises = fs.promises export class BasicReverse { constructor() { this.taskScheduler = new TaskScheduler() + this.ffmpegOptions = new FfmpegOptions() } - //#region ffmpeg的一些操作 - /** - * FFmpeg裁剪视频,将一个视频将裁剪指定的时间内的片段 - * @param {*} book 小说对象类 - * @param {*} bookTask 小说批次任务对象类 - * @param {*} startTime 开始时间 - * @param {*} endTime 结束时间 - * @param {*} videoPath 视频地址 - * @param {*} outVideoFile 输出地址 - * @returns + * 初始化服务 */ - async FfmpegCutVideo(book, bookTask, startTime, endTime, videoPath, outVideoFile) { - try { - // 判断视频地址是不是存在 - let videoIsExist = CheckFileOrDirExist(videoPath) - if (!videoIsExist) { - throw new Error('视频地址对应的文件不存在') - } - - // 判断开始时间和结束时间是不是合法 - if (isEmpty(startTime) || isEmpty(endTime)) { - throw new Error('开始时间和结束时间不能为空') - } - // 判断输出文件夹是不是存在 - let outputFolder = path.dirname(outVideoFile) - await CheckFolderExistsOrCreate(outputFolder) - - // 将时间转换为字符串 - startTimeString = MillisecondsToTimeString(startTime) - endTimeString = MillisecondsToTimeString(endTime) - - // 设置视频编码器 - let videoCodec = 'libx264' // 默认编码器 - if (global.gpu.type === 'NVIDIA') { - videoCodec = 'h264_nvenc' - } else if (global.gpu.type === 'AMD') { - videoCodec = 'h264_amf' - } - - // 判断分镜是不是和数据库中的数据匹配的上 - return new Promise((resolve, reject) => { - ffmpeg(videoPath) - .setStartTime(startTimeString) - .setEndTime(endTimeString) - .videoCodec(videoCodec) - .addOption('-preset', 'fast') - .audioCodec('copy') - .output(outVideoFile) - .on('end', async function () { - let res_msg = `视频裁剪完成,输出地址:${outVideoFile}` - // 修改数据库中的输出地址 - await this.taskScheduler.AddLogToDB( - book.id, - book.type, - res_msg, - OtherData.DEFAULT, - LoggerStatus.SUCCESS - ) - return successMessage(null, res_msg, 'BasicReverse_FfmpegCutVideo') - }) - .on('error', async function (err) { - let res_msg = `视频裁剪失败,错误信息如下:${err.toString()}` - await this.taskScheduler.AddLogToDB( - book.id, - book.type, - res_msg, - OtherData.DEFAULT, - LoggerStatus.FAIL - ) - return errorMessage(res_msg, 'BasicReverse_FfmpegCutVideo') - }) - .run() - }) - - // 开始裁剪视频 - } catch (error) { - return errorMessage( - '裁剪视频失败,错误信息如下: ' + error.message, - 'BasicReverse_FfmpegCutVideo' - ) + async InitService() { + if (!this.bookService) { + this.bookService = await BookService.getInstance() + } + if (!this.bookTaskService) { + this.bookTaskService = await BookTaskService.getInstance() + } + if (!this.bookTaskDetailService) { + this.bookTaskDetailService = await BookTaskDetailService.getInstance() + } + if (!this.bookBackTaskListService) { + this.bookBackTaskListService = await BookBackTaskListService.getInstance() } } - /** - * - * @param {*} videoPath - * @param {*} audioPath - */ - async FfmpegExtractAudio(videoPath, outAudioPath) { - try { - // 判断视频地址是不是存在 - let videoIsExist = CheckFileOrDirExist(videoPath) - if (!videoIsExist) { - throw new Error('视频地址对应的文件不存在') - } - // 开始提取音频 - return new Promise((resolve, reject) => { - ffmpeg(videoPath) - .output(outAudioPath) - .audioCodec('libmp3lame') - .audioBitrate('128k') - .on('end', async function () { - let res_msg = `音频提取完成,输出地址:${outAudioPath}` - return successMessage(outAudioPath, res_msg, 'BasicReverse_FfmpegExtractAudio') - }) - .on('error', async function (err) { - let res_msg = `音频提取失败,错误信息如下:${err.toString()}` - return errorMessage(res_msg, 'BasicReverse_FfmpegExtractAudio') - }) - }) - } catch (error) { - return errorMessage( - '提取音频失败,错误信息如下: ' + error.message, - 'BasicReverse_FfmpegExtractAudio' - ) - } - } - - //#endregion + //#region 计算视频的分镜,操作 /** - * 分镜(通过传入的bookId) - * @param {*} bookId 传入的bookId - * @returns + * 添加分镜计算的任务 + * @param {*} bookId */ - async GetFrameData(bookId) { + async AddFrameDataTask(bookId) { try { - let _bookService = await BookService.getInstance() - let _bookTaskDetailService = await BookTaskDetailService.getInstance() - let _bookTaskService = await BookTaskService.getInstance() + await this.InitService() // 获取对应的小说小说数据,找到对应的小说视频地址 let bookQuery = { bookId: bookId } - let bookData = _bookService.GetBookData(bookQuery) + let bookData = this.bookService.GetBookData(bookQuery) if (bookData.code == 0) { return bookData } @@ -174,7 +75,7 @@ export class BasicReverse { } // 获取小说对应的批次任务数据,默认初始化为第一个 - let bookTaskRes = await _bookTaskService.GetBookTaskData({ + let bookTaskRes = this.bookTaskService.GetBookTaskData({ bookId: bookId, name: 'output_00001' }) @@ -186,12 +87,61 @@ export class BasicReverse { let book = bookData.data.res_book[0] let bookTask = bookTaskRes.data.bookTasks[0] - _bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.STORYBOARD) + // 开始添加任务 + let taskRes = await this.bookBackTaskListService.AddBookBackTask( + bookId, + BookBackTaskType.STORYBOARD, + TaskExecuteType.AUTO, + bookTask.id, + null + ) + if (taskRes.code == 0) { + throw new Error(taskRes.message) + } + this.taskScheduler.AddLogToDB( + bookId, + book.type, + `添加分镜任务成功`, + OtherData.DEFAULT, + LoggerStatus.SUCCESS + ) + return successMessage(null, '添加分镜任务成功', 'BasicReverse_AddFrameDataTask') + } catch (error) { + return errorMessage( + '添加分镜任务失败,错误信息如下: ' + error.message, + 'BasicReverse_AddFrameDataTask' + ) + } + } + + /** + * 执行分镜任务,通过task获取 + * @param {*} task + * @returns + */ + async GetFrameData(task) { + try { + // 分镜任务一定有对应的小说ID和对应的小说批次任务ID,没有直接报错 + if (isEmpty(task.bookId) || isEmpty(task.bookTaskId)) { + throw new Error('分镜任务,bookId, bookTaskId不能为空') + } + await this.InitService() + + let bookId = task.bookId + let bookTaskId = task.bookTaskId + + let bookRes = this.bookService.GetBookDataById(bookId) + if (bookRes.data == null) { + throw new Error('没有找到对应的小说数据') + } + let book = bookRes.data + + this.bookTaskService.UpdateBookTaskStatus(bookTaskId, BookTaskStatus.STORYBOARD) // 分镜之前,删除之前的老数据 - let deleteBookTaskRes = _bookTaskDetailService.DeleteBookTaskDetail({ + let deleteBookTaskRes = this.bookTaskDetailService.DeleteBookTaskDetail({ bookId: bookId, - bookTaskId: bookTask.id + bookTaskId: bookTaskId }) let oldVideoPath = book.oldVideoPath @@ -220,12 +170,7 @@ export class BasicReverse { }) // 有错误输出 if (output.stderr != '') { - let error_msg = `分镜失败,错误信息如下:${output.stderr}` - _bookTaskService.UpdateBookTaskStatus( - bookTask.id, - BookTaskStatus.STORYBOARD_FAIL, - error_msg - ) + let error_msg = `分镜成功,但有警告提示:${output.stderr}` await this.taskScheduler.AddLogToDB( bookId, book.type, @@ -233,14 +178,13 @@ export class BasicReverse { OtherData.DEFAULT, LoggerStatus.FAIL ) - throw new Error(output.stderr) } // 分镜成功,处理输出 - let josnIsExist = CheckFileOrDirExist(frameJson) + let josnIsExist = await CheckFileOrDirExist(frameJson) if (!josnIsExist) { let error_message = `分镜失败,没有找到对应的分镜输出文件:${frameJson}` - _bookTaskService.UpdateBookTaskStatus( - bookTask.id, + this.bookTaskService.UpdateBookTaskStatus( + bookTaskId, BookTaskStatus.STORYBOARD_FAIL, error_message ) @@ -257,8 +201,8 @@ export class BasicReverse { let frameJsonData = JSON.parse(await fspromises.readFile(frameJson, 'utf-8')) if (frameJsonData.length <= 0) { let error_msg = `分镜失败,没有找到对应的分镜数据` - _bookTaskService.UpdateBookTaskStatus( - bookTask.id, + this.bookTaskService.UpdateBookTaskStatus( + bookTaskId, BookTaskStatus.STORYBOARD_FAIL, error_msg ) @@ -276,20 +220,20 @@ export class BasicReverse { let dataArray = frameJsonData[i] let bookTaskDetail = { bookId: bookId, - bookTaskId: bookTask.id + bookTaskId: bookTaskId } // 将字符串转换为number bookTaskDetail.startTime = TimeStringToMilliseconds(dataArray[0]) bookTaskDetail.endTime = TimeStringToMilliseconds(dataArray[1]) - let res = _bookTaskDetailService.AddBookTaskDetail(bookTaskDetail) + let res = this.bookTaskDetailService.AddBookTaskDetail(bookTaskDetail) if (res.code == 0) { throw new Error(res.message) } } - _bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.STORYBOARD_DONE) + this.bookTaskService.UpdateBookTaskStatus(task.bookTaskId, BookTaskStatus.STORYBOARD_DONE) // 分镜成功,推送日志 await this.taskScheduler.AddLogToDB( bookId, @@ -298,36 +242,38 @@ export class BasicReverse { OtherData.DEFAULT, LoggerStatus.SUCCESS ) - return successMessage(null, `分镜成功,分镜信息在 ${frameJson}`, 'BasicReverse_GetFrameData') } catch (error) { return errorMessage(error.message, 'BasicReverse_GetFrameData') } } + //#endregion 计算视频的分镜 + + //#region 裁剪视频的相关操作 /** - * 裁剪视频 + * 添加裁剪视频的任务 * @param {*} bookId 小说ID * @param {*} frameJson 存放分镜数据的json文件地址 */ - async CutVideoData(bookId) { + async AddCutVideoDataTask(bookId) { try { if (isEmpty(bookId)) { throw new Error('bookId不能为空') } + await this.InitService() // 判断小说是不是存在 - let _bookService = await BookService.getInstance() - let _bookTaskService = await BookTaskService.getInstance() - let _bookTaskDetailService = await BookTaskDetailService.getInstance() - let book = _bookService.GetBookDataById(bookId) + let bookRes = this.bookService.GetBookDataById(bookId) - if (book == null) { + if (bookRes == null) { throw new Error('没有找到对应的小说数据') } + let book = bookRes.data + // 找到对应的小说ID和对应的小说批次任务ID,判断是不是有分镜数据 - let bookTaskRes = await _bookTaskService.GetBookTaskData({ + let bookTaskRes = this.bookTaskService.GetBookTaskData({ bookId: bookId, name: 'output_00001' }) @@ -336,8 +282,7 @@ export class BasicReverse { } let bookTask = bookTaskRes.data.bookTasks[0] - - let bookTaskDetail = _bookTaskDetailService.GetBookTaskData({ + let bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({ bookId: bookId, bookTaskId: bookTask.id }) @@ -357,13 +302,13 @@ export class BasicReverse { } } - bookTaskDetail = _bookTaskDetailService.GetBookTaskData({ + bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({ bookId: bookId, bookTaskId: bookTask.id }) if (bookTaskDetail.data.length <= 0) { - _bookTaskService.UpdateBookTaskStatus( + this.bookTaskService.UpdateBookTaskStatus( bookTask.id, BookTaskStatus.SPLIT_FAIL, '重新调用分镜方法还是没有分镜数据,请检查' @@ -371,60 +316,40 @@ export class BasicReverse { throw new Error('重新调用分镜方法还是没有分镜数据,请检查') } - _bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.SPLIT) + this.bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.SPLIT) // 有分镜数据,开始处理 await this.taskScheduler.AddLogToDB( bookId, book.type, - `成功获取分镜数据,开始裁剪视频`, + `成功获取分镜数据,开始添加裁剪视频任务`, OtherData.DEFAULT, LoggerStatus.SUCCESS ) - for (let i = 0; i < bookTaskDetail.length; i++) { - const element = bookTaskDetail[i] - let startTime = element.startTime - let endTime = element.endTime - if (startTime == null || endTime == null) { - _bookTaskService.UpdateBookTaskStatus( - bookTask.id, - BookTaskStatus.SPLIT_FAIL, - '开始时间和结束时间不能为空' - ) - throw new Error('开始时间和结束时间不能为空') - } - let outVideoFile = path.join(book.bookFolderPath, `data/frame/${element.name}.mp4`) - let res = await this.FfmpegCutVideo( - book, - startTime, - endTime, - book.oldVideoPath, - outVideoFile + // 添加裁剪视频任务 + for (let i = 0; i < bookTaskDetail.data.length; i++) { + const element = bookTaskDetail.data[i] + // 创建后台的视频分割任务 + let taskRes = await this.bookBackTaskListService.AddBookBackTask( + bookId, + BookBackTaskType.SPLIT, + TaskExecuteType.AUTO, + bookTask.id, + element.id ) - if (res.code == 0) { - _bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.SPLIT_FAIL, res.message) - throw new Error(res.message) + if (taskRes.code == 0) { + throw new Error(taskRes.message) } - - // 视频裁剪完成,要将裁剪后的视频地址写入到数据库中 - _bookTaskDetailService.UpdateBookTaskDetail(element.id, { - videoPath: path.relative(define.project_path, outVideoFile) - }) } - - // 小改小说批次的状态 - - _bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.SPLIT_DONE) - - // 结束,分镜完毕,推送日志,返回成功 + // 添加日志 await this.taskScheduler.AddLogToDB( bookId, book.type, - `全部视频裁剪完成`, + `添加视频裁剪任务成功`, OtherData.DEFAULT, LoggerStatus.SUCCESS ) - return successMessage(null, '全部视频裁剪完成', 'BasicReverse_CutVideoData') + return successMessage(null, '添加视频裁剪任务成功', 'BasicReverse_AddCutVideoDataTask') } catch (error) { await this.taskScheduler.AddLogToDB( bookId, @@ -433,6 +358,93 @@ export class BasicReverse { OtherData.DEFAULT, LoggerStatus.FAIL ) + return errorMessage( + '添加视频裁剪任务失败,错误信息如下: ' + error.message, + 'BasicReverse_CutVideoData' + ) + } + } + + /** + * 开始执行指定的任务 + * @param {*} task + * @returns + */ + async CutVideoData(task) { + try { + // 视频分割任务一定有对应的小说ID和对应的小说批次任务ID和对应的分镜ID,没有直接报错 + if (isEmpty(task.bookId) || isEmpty(task.bookTaskId) || isEmpty(task.bookTaskDetailId)) { + throw new Error('分镜任务,bookId, bookTaskId, bookTaskDetailId不能为空') + } + await this.InitService() + let bookTaskDetailRes = this.bookTaskDetailService.GetBookTaskDetailDataById( + task.bookTaskDetailId + ) + if (bookTaskDetailRes.code == 0) { + throw new Error(bookTaskDetailRes.message) + } + if (bookTaskDetailRes.data == null) { + throw new Error('没有找到对应的分镜数据') + } + // 开始执行裁剪视频 + let bookTaskDetail = bookTaskDetailRes.data + let startTime = bookTaskDetail.startTime + let endTime = bookTaskDetail.endTime + + let bookRes = this.bookService.GetBookDataById(task.bookId) + if (bookRes.data == null) { + throw new Error('没有找到对应的小说数据') + } + let book = bookRes.data + + if (startTime == null || endTime == null) { + this.bookTaskService.UpdateBookTaskStatus( + task.bookTaskId, + BookTaskStatus.SPLIT_FAIL, + '开始时间和结束时间不能为空' + ) + throw new Error('开始时间和结束时间不能为空') + } + let outVideoFile = path.join(book.bookFolderPath, `data/frame/${bookTaskDetail.name}.mp4`) + + let res = await this.ffmpegOptions.FfmpegCutVideo( + startTime, + endTime, + book.oldVideoPath, + outVideoFile + ) + if (res.code == 0) { + this.bookTaskService.UpdateBookTaskStatus( + task.bookTaskId, + BookTaskStatus.SPLIT_FAIL, + res.message + ) + throw new Error(res.message) + } + + // 视频裁剪完成,要将裁剪后的视频地址写入到数据库中 + this.bookTaskDetailService.UpdateBookTaskDetail(task.bookTaskDetailId, { + videoPath: path.relative(define.project_path, outVideoFile) + }) + // 小改小说批次的状态 + this.bookTaskService.UpdateBookTaskStatus(task.bookTaskId, BookTaskStatus.SPLIT_DONE) + // 结束,分镜完毕,推送日志,返回成功 + await this.taskScheduler.AddLogToDB( + task.bookId, + book.type, + `${task.name}_视频裁剪完成`, + OtherData.DEFAULT, + LoggerStatus.SUCCESS + ) + return successMessage(null, `${task.name}_视频裁剪完成`, 'BasicReverse_CutVideoData') + } catch (error) { + await this.taskScheduler.AddLogToDB( + task.bookId, + task.type, + error.message, + OtherData.DEFAULT, + LoggerStatus.FAIL + ) return errorMessage( '裁剪视频失败,错误信息如下: ' + error.message, 'BasicReverse_CutVideoData' @@ -440,27 +452,326 @@ export class BasicReverse { } } + //#endregion + + //#region 分离音频的相关操作 + /** - * 分离视频片段的音频, - * 当没有传入bookTaskId,分离默认的第一个, - * 有传入的时候,分离对应的bookTaskId的数据 - * @param {*} bookId - * @param {*} bookTaskId + * 添加视频分离音频的任务 + * @param {*} bookId 小说ID + * @param {*} bookTaskId 小说任务ID + * @returns */ - async SplitAudioData(bookId, bookTaskId = null) { + async AddSplitAudioDataTask(bookId, bookTaskId = null) { try { - let _bookService = await BookService.getInstance() - let _bookTaskService = await BookTaskService.getInstance() - let _bookTaskDetailService = await BookTaskDetailService.getInstance() - let book = _bookService.GetBookDataById(bookId) + await this.InitService() + let bookRes = this.bookService.GetBookDataById(bookId) + if (bookRes == null) { + throw new Error('没有找到对应的小说数据') + } + let book = bookRes.data + + let bookTaskRes + if (bookTaskId != null) { + bookTaskRes = this.bookTaskService.GetBookTaskData({ id: bookTaskId }) + } else { + bookTaskRes = this.bookTaskService.GetBookTaskData({ + bookId: bookId, + name: 'output_00001' + }) + } + if (bookTaskRes.data.bookTasks.length <= 0 || bookTaskRes.data.total <= 0) { + await this.taskScheduler.AddLogToDB( + bookId, + book.type, + `没有找到对应的小说批次任务数据,请检查bookId是否正确`, + OtherData.DEFAULT, + LoggerStatus.FAIL + ) + throw new Error('没有找到对应的小说批次任务数据,请检查bookId是否正确') + } + + let bookTask = bookTaskRes.data.bookTasks[0] + + // 获取对应小说批次任务的分镜信息 + let bookTaskDetails = this.bookTaskDetailService.GetBookTaskData({ + bookId: bookId, + bookTaskId: bookTask.id + }) + if (bookTaskDetails.data.length <= 0) { + await this.taskScheduler.AddLogToDB( + bookId, + book.type, + `没有找到对应的小说批次任务数据,请检查bookId是否正确,或者手动执行`, + bookTask.id, + LoggerStatus.FAIL + ) + throw new Error('没有找到对应的小说批次任务数据,请检查bookId是否正确,或者手动执行') + } + // 开始添加任务 + for (let i = 0; i < bookTaskDetails.data.length; i++) { + const element = bookTaskDetails.data[i] + let taskRes = await this.bookBackTaskListService.AddBookBackTask( + bookId, + BookBackTaskType.AUDIO, + TaskExecuteType.AUTO, + bookTask.id, + element.id + ) + if (taskRes.code == 0) { + throw new Error(taskRes.message) + } + // 添加日志 + await this.taskScheduler.AddLogToDB( + bookId, + book.type, + `添加音频 ${taskRes.data.name} 分离任务成功`, + OtherData.DEFAULT, + LoggerStatus.SUCCESS + ) + } + return successMessage(null, `添加所有音频分离任务成功`, 'BasicReverse_AddSplitAudioDataTask') + } catch (error) { + return errorMessage( + '添加音频分离任务失败,错误信息如下: ' + error.message, + 'BasicReverse_AddSplitAudioDataTask' + ) + } + } + + /** + * 执行分离音频任务 + * @param {*} task 任务对象 + * @returns + */ + async SplitAudioData(task) { + try { + // 执行分离音频任务一定有对应的小说ID和对应的小说批次任务ID和对应的分镜ID,没有直接报错 + if (isEmpty(task.bookId) || isEmpty(task.bookTaskId) || isEmpty(task.bookTaskDetailId)) { + throw new Error('分镜任务,bookId, bookTaskId, bookTaskDetailId不能为空') + } + await this.InitService() + let bookRes = this.bookService.GetBookDataById(task.bookId) + if (bookRes.data == null) { + throw new Error('没有找到对应的小说数据') + } + let book = bookRes.data + + let bookTaskDetails = this.bookTaskDetailService.GetBookTaskDetailDataById( + task.bookTaskDetailId + ) + if (bookTaskDetails.data == null) { + throw new Error('没有找到对应的分镜数据') + } + let bookTaskDetail = bookTaskDetails.data + let videoPath = bookTaskDetail.videoPath + let audioPath = path.join(book.bookFolderPath, `data/audio/${bookTaskDetail.name}.wav`) + await CheckFolderExistsOrCreate(path.dirname(audioPath)) + + // 开始分离音频 + let audioRes = await this.ffmpegOptions.FfmpegExtractAudio(videoPath, audioPath) + if (audioRes.code == 0) { + let errorMessage = `分离音频失败,错误信息如下:${audioRes.message}` + this.bookTaskService.UpdateBookTaskStatus( + task.bookTaskId, + BookTaskStatus.AUDIO_FAIL, + errorMessage + ) + throw new Error(audioRes.message) + } + this.bookTaskDetailService.UpdateBookTaskDetail(bookTaskDetail.id, { + audioPath: path.relative(define.project_path, audioPath) + }) + + // 推送成功消息 + await this.taskScheduler.AddLogToDB( + task.bookId, + book.type, + `${bookTaskDetail.name}分离音频成功,输出地址:${audioPath}`, + OtherData.DEFAULT, + LoggerStatus.SUCCESS + ) + // 修改状态为分离音频成功 + this.bookTaskService.UpdateBookTaskStatus(task.bookTaskId, BookTaskStatus.AUDIO_DONE) + return successMessage( + null, + `${bookTaskDetail.name}分离音频成功,输出地址:${audioPath}`, + 'BasicReverse_SplitAudioData' + ) + } catch (error) { + let error_message = `分离音频失败,错误信息如下:${error.message}` + await this.taskScheduler.AddLogToDB( + task.bookId, + task.type, + error_message, + OtherData.DEFAULT, + LoggerStatus.FAIL + ) + return errorMessage(error_message, 'BasicReverse_SplitAudioData') + } + } + + //#endregion + + //#region 开始抽帧 + + // 添加抽帧任务 + async AddGetFrameTask(bookId, bookTaskId = null) { + try { + // 开始添加任务 + await this.InitService() + let bookRes = this.bookService.GetBookDataById(bookId) + if (bookRes.data == null) { + throw new Error('没有找到对应的小说数据') + } + + let book = bookRes.data + let bookTaskRes + if (bookTaskId != null) { + bookTaskRes = this.bookTaskService.GetBookTaskData({ id: bookTaskId }) + } else { + bookTaskRes = this.bookTaskService.GetBookTaskData({ + bookId: bookId, + name: 'output_00001' + }) + } + if (bookTaskRes.data.bookTasks.length <= 0 || bookTaskRes.data.total <= 0) { + throw new Error('没有找到对应的小说批次任务数据,请检查bookId是否正确') + } + let bookTask = bookTaskRes.data.bookTasks[0] + + let bookTaskDetails = this.bookTaskDetailService.GetBookTaskData({ + bookId: bookId, + bookTaskId: bookTask.id + }) + if (bookTaskDetails.data.length <= 0) { + throw new Error('没有找到对应的小说批次任务数据,请检查bookId是否正确,或者手动执行') + } + + for (let i = 0; i < bookTaskDetails.data.length; i++) { + const element = bookTaskDetails.data[i] + let taskRes = await this.bookBackTaskListService.AddBookBackTask( + bookId, + BookBackTaskType.FRAME, + TaskExecuteType.AUTO, + bookTask.id, + element.id + ) + if (taskRes.code == 0) { + throw new Error(taskRes.message) + } + + await this.taskScheduler.AddLogToDB( + bookId, + book.type, + `添加 ${taskRes.data.name} 抽帧任务成功`, + OtherData.DEFAULT, + LoggerStatus.SUCCESS + ) + } + return successMessage(null, '添加所有抽帧任务成功', 'BasicReverse_AddGetFrameTask') + } catch (error) { + return errorMessage( + '添加抽帧任务失败,错误信息如下: ' + error.message, + 'BasicReverse_AddGetFrameTask' + ) + } + } + + /** + * 开始执行抽帧任务 + * @param {*} task 执行的任务 + */ + async GetFrame(task) { + try { + // 抽帧任务一定有对应的小说ID和对应的小说批次任务ID和对应的分镜ID,没有直接报错 + if (isEmpty(task.bookId) || isEmpty(task.bookTaskId) || isEmpty(task.bookTaskDetailId)) { + throw new Error('分镜任务,bookId, bookTaskId, bookTaskDetailId不能为空') + } + await this.InitService() + let bookRes = this.bookService.GetBookDataById(task.bookId) + if (bookRes.data == null) { + throw new Error('没有找到对应的小说数据') + } + let book = bookRes.data + + let bookTaskDetailRes = this.bookTaskDetailService.GetBookTaskDetailDataById( + task.bookTaskDetailId + ) + if (bookTaskDetailRes.data == null) { + throw new Error('没有找到对应的分镜数据') + } + let bookTaskDetail = bookTaskDetailRes.data + + let videoPath = bookTaskDetail.videoPath + let outputFramePath = path.join( + book.bookFolderPath, + `data/tmp/input/${bookTaskDetail.name}.png` + ) + let frameTime = (bookTaskDetail.endTime - bookTaskDetail.startTime) / 2 + + let res = await this.ffmpegOptions.FfmpegGetFrame(frameTime, videoPath, outputFramePath) + if (res.code == 0) { + let errorMessage = `抽帧失败,错误信息如下:${res.message}` + this.bookTaskService.UpdateBookTaskStatus( + task.bookTaskId, + BookTaskStatus.FRAME_FAIL, + errorMessage + ) + throw new Error(errorMessage) + } + + // 抽帧成功,将抽帧的地址写入到数据库中 + this.bookTaskDetailService.UpdateBookTaskDetail(task.bookTaskDetailId, { + oldImage: path.relative(define.project_path, outputFramePath) + }) + + // 推送成功消息 + await this.taskScheduler.AddLogToDB( + task.bookId, + book.type, + `${bookTaskDetail.name}抽帧成功,输出地址:${outputFramePath}`, + OtherData.DEFAULT, + LoggerStatus.SUCCESS + ) + // 修改状态为抽帧成功 + this.bookTaskService.UpdateBookTaskStatus(task.bookTaskId, BookTaskStatus.FRAME_DONE) + return successMessage(null, '抽帧成功', 'BasicReverse_GetFrame') + } catch (error) { + let errorMessage = `抽帧失败,错误信息如下:${error.message}` + await this.taskScheduler.AddLogToDB( + task.bookId, + task.type, + errorMessage, + OtherData.DEFAULT, + LoggerStatus.FAIL + ) + return errorMessage('抽帧失败,错误信息如下: ' + error.message, 'BasicReverse_GetFrame') + } + } + + //#endregion + + //#region 提取字幕相关操作 + + /** + * 添加提取字幕的任务 + * @param {*} bookId 小说ID + * @param {*} bookTaskId 小说任务ID + * @returns + */ + async AddExtractSubtitlesDataTask(bookId, bookTaskId = null) { + try { + await this.InitService() + let book = this.bookService.GetBookDataById(bookId) if (book == null) { throw new Error('没有找到对应的小说数据') } let bookTask if (bookTaskId != null) { - bookTaskId = _bookTaskService.GetBookTaskData({ id: bookTaskId }) + bookTaskId = this.bookTaskService.GetBookTaskData({ id: bookTaskId }) } else { - bookTask = _bookTaskService.GetBookTaskData({ + bookTask = this.bookTaskService.GetBookTaskData({ bookId: bookId, name: 'output_00001' }) @@ -471,7 +782,7 @@ export class BasicReverse { bookTask = bookTask.data.bookTasks[0] // 获取对应小说批次任务的分镜信息 - let bookTaskDetails = _bookTaskDetailService.GetBookTaskData({ + let bookTaskDetails = this.bookTaskDetailService.GetBookTaskData({ bookId: bookId, bookTaskId: bookTask.id }) @@ -479,80 +790,113 @@ export class BasicReverse { throw new Error('没有找到对应的小说批次任务数据,请检查bookId是否正确,或者手动执行') } - await this.taskScheduler.AddLogToDB( - bookId, - book.type, - `开始分离音频`, - OtherData.DEFAULT, - LoggerStatus.DOING - ) - _bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.AUDIO) - - for (let i = 0; i < bookTaskDetails.length; i++) { - const element = bookTaskDetails[i] - let videoPath = element.videoPath - let audioPath = path.join(book.bookFolderPath, `data/audio/${element.name}.mp3`) - await CheckFolderExistsOrCreate(path.dirname(audioPath)) - - // 开始分离音频 - let audioRes = await this.FfmpegExtractAudio(videoPath, audioPath) - if (audioRes.code == 0) { - let errorMessage = `分离音频失败,错误信息如下:${audioRes.message}` - _bookTaskService.UpdateBookTaskStatus( - bookTask.id, - BookTaskStatus.AUDIO_FAIL, - errorMessage - ) - throw new Error(audioRes.message) - } - - _bookTaskDetailService.UpdateBookTaskDetail(element.id, { - audioPath: path.relative(define.project_path, audioPath) - }) - - // 推送成功消息 - await this.taskScheduler.AddLogToDB( + for (let i = 0; i < bookTaskDetails.data.length; i++) { + const element = bookTaskDetails.data[i] + let taskRes = await this.bookBackTaskListService.AddBookBackTask( bookId, book.type, - `${element.name}分离音频成功,输出地址:${audioPath}`, - OtherData.DEFAULT, - LoggerStatus.SUCCESS + TaskExecuteType.AUTO, + bookTask.id, + element.id ) + if (taskRes.code == 0) { + throw new Error(taskRes.message) + } } - // 修改状态为分离音频成功 - _bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.AUDIO_DONE) - // 所有音频分离成功,推送日志 + await this.taskScheduler.AddLogToDB( bookId, book.type, - `${element.name}分离音频成功,输出地址:${audioPath}`, + `添加提取字幕任务成功`, OtherData.DEFAULT, LoggerStatus.SUCCESS ) - return successMessage(null, '所有音频分离成功', 'BasicReverse_SplitAudioData') - } catch (error) { - let errorMessage = `分离音频失败,错误信息如下:${error.message}` - await this.taskScheduler.AddLogToDB( - bookId, - book.type, - errorMessage, - OtherData.DEFAULT, - LoggerStatus.FAIL + return successMessage( + null, + '添加提取字幕任务成功 ', + 'BasicReverse_AddExtractSubtitlesDataTask ' + ) + } catch (error) { + return errorMessage( + '添加提取字幕任务失败,错误信息如下: ' + error.message, + 'BasicReverse_AddExtractSubtitlesDataTask' ) - return errorMessage(errorMessage, 'BasicReverse_SplitAudioData') } } /** - * 提取字幕 - * @param {*} bookId - * @param {*} bookTaskId + * 执行提取字幕任务 + * @param {*} task * @returns */ - async ExtractSubtitlesData(bookId, bookTaskId = null) { + async ExtractSubtitlesData(task) { try { + // 执行提取字幕任务一定有对应的小说ID和对应的小说批次任务ID和对应的分镜ID,没有直接报错 + if (isEmpty(task.bookId) || isEmpty(task.bookTaskId) || isEmpty(task.bookTaskDetailId)) { + throw new Error('分镜任务,bookId, bookTaskId, bookTaskDetailId不能为空') + } + await this.InitService() + // 获取详细的小说分镜信息 + let bookTaskDetailRes = this.bookTaskDetailService.GetBookTaskDetailDataById( + task.bookTaskDetailId + ) + if (bookTaskDetailRes.data == null) { + throw new Error('没有找到对应的分镜数据') + } + let bookTaskDetail = bookTaskDetailRes.data + let txt = '' + // 开始提取,调用本地的服务识别字幕 + let isWisper = true + // 判断是不是用本地的wisper服务 + if (isWisper) { + // 开始调用wisper + // 使用异步的方法调用一个python程序,然后写入到指定的json文件中k + let out_dir = path.dirname(bookTaskDetail.audioPath) + + let command = `"${path.join(define.scripts_path, 'Lai.exe')}" "-t" "${out_dir}" "${ + bookTaskDetail.audioPath + }" "${bookTaskDetail.name}"` + const output = await execAsync(command, { + maxBuffer: 1024 * 1024 * 10, + encoding: 'utf-8' + }) + // 有错误输出 + if (output.stderr != '') { + let error_msg = `提取字幕成功,但有警告提示:${output.stderr}` + await this.taskScheduler.AddLogToDB( + task.bookId, + book.type, + error_msg, + OtherData.DEFAULT, + LoggerStatus.FAIL + ) + } + + // 没有的话,去读取对应的字幕文件 + let outTxtPath = path.join(out_dir, `${bookTaskDetail.name}.txt`) + txt = await fspromises.readFile(outTxtPath, 'utf-8') + } else { + // 使用网络服务 + + } + + // 修改分镜数据的字幕 + this.bookTaskDetailService.UpdateBookTaskDetail(task.bookTaskDetailId, { + word: txt, + afterGpt: txt + }) + + // 提取字幕成功,推送日志 + await this.taskScheduler.AddLogToDB( + bookId, + book.type, + `${task.name} 提取字幕成功`, + OtherData.DEFAULT, + LoggerStatus.SUCCESS + ) + return successMessage(null, `${task.name} 提取字幕成功`, 'BasicReverse_ExtractSubtitlesData') } catch (error) { - let errorMessage = `提取字幕失败,错误信息如下:${error.message}` + let errorMessage = `${task.name} 提取字幕失败,错误信息如下:${error.message}` await this.taskScheduler.AddLogToDB( bookId, book.type, @@ -564,4 +908,6 @@ export class BasicReverse { return errorMessage(errorMessage, 'BasicReverse_ExtractSubtitlesData') } } + + //#endregion } diff --git a/src/main/Task/ffmpegOptions.js b/src/main/Task/ffmpegOptions.js new file mode 100644 index 0000000..516a62d --- /dev/null +++ b/src/main/Task/ffmpegOptions.js @@ -0,0 +1,307 @@ +import path from 'path' +import { TaskScheduler } from './taskScheduler' +import { errorMessage, successMessage } from '../generalTools' +import { CheckFileOrDirExist, CheckFolderExistsOrCreate } from '../../define/Tools/file' +import { MillisecondsToTimeString } from '../../define/Tools/time' +import Ffmpeg from 'fluent-ffmpeg' +import { SetFfmpegPath } from '../setting/ffmpegSetting' +import fs from 'fs' +const fspromises = fs.promises + +SetFfmpegPath() + +/** + * FFmpeg 封装的一些操作 + */ +export class FfmpegOptions { + constructor() { + this.taskScheduler = new TaskScheduler() + } + + InitCodec() { + let videoCodec = 'libx264' // 默认编码器 + if (global.gpu.type === 'NVIDIA') { + videoCodec = 'h264_nvenc' + } else if (global.gpu.type === 'AMD') { + videoCodec = 'h264_amf' + } + this.ecode = videoCodec + + let videoDcodec = 'libx264' // 默认解码器 + if (global.gpu.type === 'NVIDIA') { + videoDcodec = 'h264_cuvid ' + } else if (global.gpu.type === 'AMD') { + videoDcodec = 'h264_amf' + } + } + + /** + * FFmpeg裁剪视频,将一个视频将裁剪指定的时间内的片段 + * @param {*} book 小说对象类 + * @param {*} bookTask 小说批次任务对象类 + * @param {*} startTime 开始时间 + * @param {*} endTime 结束时间 + * @param {*} videoPath 视频地址 + * @param {*} outVideoFile 输出地址 + * @returns + */ + async FfmpegCutVideo(startTime, endTime, videoPath, outVideoFile) { + try { + // 判断视频地址是不是存在 + let videoIsExist = await CheckFileOrDirExist(videoPath) + if (!videoIsExist) { + throw new Error('视频地址对应的文件不存在') + } + + // 判断开始时间和结束时间是不是合法 + if (startTime == null || endTime == null) { + throw new Error('开始时间和结束时间不能为空') + } + // 判断输出文件夹是不是存在 + let outputFolder = path.dirname(outVideoFile) + await CheckFolderExistsOrCreate(outputFolder) + + // 将时间转换为字符串 + let startTimeString = MillisecondsToTimeString(startTime) + let endTimeString = MillisecondsToTimeString(endTime) + + // 设置视频编码器 + let videoCodec = 'libx264' // 默认编码器 + if (global.gpu.type === 'NVIDIA') { + videoCodec = 'h264_nvenc' + } else if (global.gpu.type === 'AMD') { + videoCodec = 'h264_amf' + } + + // 判断分镜是不是和数据库中的数据匹配的上 + let res = await new Promise((resolve, reject) => { + Ffmpeg(videoPath) + .outputOptions([ + `-ss ${startTimeString}`, + `-to ${endTimeString}`, + '-preset fast', + '-c:v ' + videoCodec, + '-c:a copy' + ]) + .output(outVideoFile) + .on('end', async function () { + resolve(outVideoFile) + }) + .on('error', async function (err) { + reject(new Error(`视频裁剪失败,错误信息如下:${err.toString()}`)) + }) + .run() + }) + + let res_msg = `视频裁剪完成,输出地址:${outVideoFile}` + return successMessage(res_msg, '视频裁剪成功', 'BasicReverse_FfmpegCutVideo') + + // 开始裁剪视频 + } catch (error) { + return errorMessage( + '裁剪视频失败,错误信息如下: ' + error.message, + 'BasicReverse_FfmpegCutVideo' + ) + } + } + + /** + * Ffmpeg提取音频 + * @param {*} videoPath 视频地址 + * @param {*} outAudioPath 输出音频地址 + * @returns + */ + async FfmpegExtractAudio(videoPath, outAudioPath) { + try { + // 判断视频地址是不是存在 + let videoIsExist = await CheckFileOrDirExist(videoPath) + if (!videoIsExist) { + throw new Error('视频地址对应的文件不存在') + } + // 开始提取音频 + let res = await new Promise((resolve, reject) => { + Ffmpeg(videoPath) + .output(outAudioPath) + .audioCodec('libmp3lame') + .audioBitrate('128k') + .on('end', async function () { + resolve(outAudioPath) + }) + .on('error', async function (err) { + let res_msg = `音频提取失败,错误信息如下:${err.toString()}` + reject(new Error(res_msg)) + }) + .run() + }) + let res_msg = `音频提取完成,输出地址:${res}` + return successMessage(res, res_msg, 'BasicReverse_FfmpegExtractAudio') + } catch (error) { + return errorMessage( + '提取音频失败,错误信息如下: ' + error.message, + 'BasicReverse_FfmpegExtractAudio' + ) + } + } + + /** + * Ffmpeg提取视频帧(只提取一帧) + * 根据point判断提取什么位置的帧 + * @param {*} frameTime 视频的时间点 + * @param {*} videoPath 视频地址 + * @param {*} outFramePath 输出帧地址 + */ + async FfmpegGetFrame(frameTime, videoPath, outFramePath) { + try { + let videoIsExist = await CheckFileOrDirExist(videoPath) + if (videoIsExist == false) { + throw new Error('视频地址对应的文件不存在') + } + + // 判断输出文件夹是不是存在 + let outputFolder = path.dirname(outFramePath) + await CheckFolderExistsOrCreate(outputFolder) + + // 开始抽帧 + // 判断分镜是不是和数据库中的数据匹配的上 + let res = await new Promise((resolve, reject) => { + Ffmpeg(videoPath) + .inputOptions([`-ss ${MillisecondsToTimeString(frameTime)}`]) + .output(outFramePath) + .frames(1) + .on('end', async function () { + resolve(outFramePath) + }) + .on('error', async function (err) { + reject(new Error(err.toString())) + }) + .run() + }) + let res_msg = `视频抽帧完成,输出地址:${res}` + return successMessage(res, '视频抽帧成功', 'BasicReverse_FfmpegGetFrame') + } catch (error) { + return errorMessage(error.message, 'BasicReverse_FfmpegGetFrame') + } + } + + /** + * 获取视频文件的宽高 + * @param {*} videoPath 视频文件地址 + * @returns + */ + async FfmpegGetVideoSize(videoPath) { + try { + let videoIsExist = await CheckFileOrDirExist(videoPath) + if (videoIsExist == false) { + throw new Error('视频地址对应的文件不存在') + } + + let res = await new Promise((resolve, reject) => { + Ffmpeg.ffprobe(videoPath, function (err, metadata) { + if (err) { + reject(new Error(err.toString())) + } + const { width, height } = metadata.streams.find((s) => s.codec_type === 'video') + resolve({ width, height }) + }) + }) + + return successMessage(res, '获取视频的宽高成功', 'BasicReverse_GetVideoSize') + } catch (error) { + return errorMessage( + '获取视频的宽高失败,失败信息如下:' + error.message, + 'BasicReverse_GetVideoSize' + ) + } + } + + /** + * 通过FFmpeg获取指定视频的时长 + * @param {*} videoPath 视频地址 + * @returns 返回视频的时长 + */ + async FfmpegGetVideoDuration(videoPath) { + try { + let videoIsExist = await CheckFileOrDirExist(videoPath) + if (videoIsExist == false) { + throw new Error('视频地址对应的文件不存在') + } + let res = await new Promise((resolve, reject) => { + Ffmpeg.ffprobe(videoPath, function (err, metadata) { + if (err) { + reject(new Error(err.toString())) + } + const duration = metadata.format.duration + resolve(duration * 1000) + }) + }) + + return successMessage(res, '获取视频的时长成功', 'BasicReverse_GetVideoDuration') + } catch (error) { + return errorMessage( + '获取视频的时长,失败信息如下:' + error.message, + 'BasicReverse_GetVideoDuration' + ) + } + } + + /** + * 获取视频的指定时间点的一帧,然后再裁剪 + * @param {*} videoPath + * @param {*} currentTime + * @param {*} outImagePath + * @param {*} clipRanges + */ + async FfmpegGetVideoFramdAndClip(videoPath, currentTime, outImagePath, clipRanges) { + try { + let videoIsExist = await CheckFileOrDirExist(videoPath) + if (videoIsExist == false) { + throw new Error('视频地址对应的文件不存在') + } + + // 判断输出文件夹是不是存在 + let outputFolder = path.dirname(outImagePath) + await CheckFolderExistsOrCreate(outputFolder) + + let frameRes = await this.FfmpegGetFrame(currentTime, videoPath, outImagePath) + if (frameRes.code == 0) { + throw new Error(frameRes.message) + } + let outImagePaths = [] + // 这边可以会裁剪多个,所以需要循环 + for (let i = 0; i < clipRanges.length; i++) { + const element = clipRanges[i] + let outCropImagePath = outImagePath.replace('.png', `_${i}.png`) + outImagePaths.push(outCropImagePath) + // 开始裁剪 + let res = await new Promise((resolve, reject) => { + Ffmpeg(outImagePath) + .outputOptions([ + `-vf crop=${element.width}:${element.height}:${element.startX}:${element.startY}` + ]) + .output(outCropImagePath) + .on('end', async function () { + resolve(outCropImagePath) + }) + .on('error', async function (err) { + reject(new Error(err.toString())) + }) + .run() + }) + } + + // 删除文件 + await fspromises.unlink(outImagePath) + + return successMessage( + outImagePaths, + '获取指定位置的帧和裁剪成功', + 'WatermarkAndSubtitle_FfmpegGetVideoFramdAndClip' + ) + } catch (error) { + return errorMessage( + '获取指定位置的帧失败和裁剪失败,失败信息如下:' + error.toString(), + 'WatermarkAndSubtitle_FfmpegGetVideoFramdAndClip' + ) + } + } +} diff --git a/src/main/Task/taskManage.js b/src/main/Task/taskManage.js index c7c8c98..6233b27 100644 --- a/src/main/Task/taskManage.js +++ b/src/main/Task/taskManage.js @@ -3,212 +3,198 @@ import { BookService } from '../../define/db/service/Book/bookService' import { BookTaskDetailService } from '../../define/db/service/Book/bookTaskDetailService' import { BookTaskService } from '../../define/db/service/Book/bookTaskService' import { OtherData } from '../../define/enum/softwareEnum' -import { BookBackTaskStatus } from '../../define/enum/bookEnum' +import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '../../define/enum/bookEnum' +import { isEmpty } from 'lodash' +import { SoftwareService } from '../../define/db/service/SoftWare/softwareService' +import { errorMessage, successMessage } from '../generalTools' +import { BasicReverse } from './basicReverse' -class TaskManager { - constructor() {} +export class TaskManager { + constructor() { + this.isExecuting = false + this.currentTaskList = [] + this.globalConfig = global.config + // this.taskExecute = new TaskExecute() + this.basicReverse = new BasicReverse() + } /** - * 创建新任务到数据库 - * 需要传递 小说ID,小说任务ID,小说任务分镜ID - * 判断是不是有相同的任务在执行,如果有则不创建新任务 - * 小说任务ID为null,所有相关的数据就是default - * 小说任务分镜ID为null,所有相关的数据就是default - * @param {*} bookId 小说ID 必传 - * @param {*} taskType 任务类型 必传 - * @param {*} bookTaskId 小说任务ID 可为null - * @param {*} bookTaskDetailId 小说任务分镜ID 可为null + * 初始化服务 + */ + async InitService() { + if (!this.softwareService) { + this.softwareService = await SoftwareService.getInstance() + } + if (!this.bookBackTaskListService) { + this.bookBackTaskListService = await BookBackTaskListService.getInstance() + } + } + /** + * 加载数据库中的配置到全局变量中 + */ + async GetGlobalConfig() { + try { + await this.InitService() + let softData = this.softwareService.GetSoftwareData(null) + if (softData.data.length <= 0) { + throw new Error('获取软件数据失败') + } + let config = softData.data[0] + global.config = JSON.parse(config.globalSetting) + this.globalConfig = global.config + return successMessage(global.config, '获取全局配置完成', 'TaskManager_GetGlobalConfig') + } catch (error) { + return errorMessage( + `获取全局配置失败,失败信息如下:` + error.message, + 'TaskManager_GetGlobalConfig' + ) + } + } + + /** + * 执行自动任务 * @returns */ - async AddTask(bookId, taskType, bookTaskId = null, bookTaskDetailId = null) { - try { - // 开始创建任务 - let _bookBackTaskListService = await BookBackTaskListService.getInstance() - let _bookService = await BookService.getInstance() - let _bookTaskService = await BookTaskService.getInstance() - let _bookTaskDetailService = await BookTaskDetailService.getInstance() - - // 获取小说信息 - let book = _bookService.GetBookDataById(bookId) - if (book == null) { - throw new Error('小说信息不存在,添加任务失败') - } - - // 有传入小说批次任务ID,要检查数据是不是存在 - let bookTask = null - if (bookTaskId != null) { - let bookTaskRes = _bookTaskService.GetBookTaskDataById(bookTaskId) - if (bookTaskRes.data == null) { - throw new Error('小说批次任务信息不存在,添加任务失败') - } - bookTask = bookTaskRes.data - } - - let bookTaskDetail = null - if (bookTaskDetailId != null) { - let bookTaskDetailRes = _bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId) - if (bookTaskDetailRes.data == null) { - throw new Error('小说任务分镜信息不存在,添加任务失败') - } - bookTaskDetail = bookTaskDetailRes.data - } - - // 开始往数据库中添加任务 - let name = `${book.name}-${bookTask ? bookTask.name : 'default'}-${ - bookTaskDetail ? bookTaskDetail.name : 'default' - }-${taskType}` - let addBookBackTaskListRes = _bookBackTaskListService.AddBookBackTaskList({ - bookId: bookId, - bookTaskId: bookTaskId ? bookTaskId : OtherData.DEFAULT, - name: name, - type: taskType, - status: BookBackTaskStatus.WAIT - }) - - if (addBookBackTaskListRes.code == 1) { - return addBookBackTaskListRes - } else { - throw new Error('添加任务失败') - } - } catch (error) { - throw error - } - } - - /** - * 获取指定小说和小说批次任务中等待中的任务 - * @param {*} bookId - * @param {*} bookTaskId - */ - async GetWaitTask(bookId, bookTaskId = null) { - try { - if (bookId == null) { - throw new Error('bookId不能为空') - } - let query = { - bookId: bookId, - status: BookBackTaskStatus.WAIT - } - - if (bookTaskId != null) { - query.bookTaskId = bookTaskId - } - - let _bookBackTaskListService = await BookBackTaskListService.getInstance() - - } catch (error) { - throw error - } - } - - updateTaskStatus(taskId, batchId, subBatchId, status) { - return new Promise((resolve, reject) => { - this.db.run( - `UPDATE tasks SET status = ?, updatedAt = datetime('now') - WHERE taskId = ? AND batchId = ? AND subBatchId = ?`, - [status, taskId, batchId, subBatchId], - function (err) { - if (err) { - reject(err) - } else { - resolve() - } - } - ) - }) - } - - deleteTask(taskId, batchId, subBatchId) { - return new Promise((resolve, reject) => { - this.db.run( - `DELETE FROM tasks WHERE taskId = ? AND batchId = ? AND subBatchId = ?`, - [taskId, batchId, subBatchId], - function (err) { - if (err) { - reject(err) - } else { - resolve() - } - } - ) - }) - } - - getAllTasks() { - return new Promise((resolve, reject) => { - this.db.all(`SELECT * FROM tasks`, [], (err, rows) => { - if (err) { - reject(err) - } else { - resolve(rows) - } - }) - }) - } -} - -class TaskExecutor { - constructor(taskManager) { - this.taskManager = taskManager - this.isExecuting = false - } - - async executePendingTasks() { - if (this.isExecuting) { + async ExecuteAutoTask() { + await this.InitService() + if (this.isExecuting && this.currentTaskList.length > this.globalConfig.task_number) { console.log('任务正在执行,跳过此次执行') return } - this.isExecuting = true - try { - const tasks = await this.taskManager.getAllTasks() - for (const task of tasks) { - if (task.status === 'pending') { - console.log(`Executing task: ${task.taskId}`) - await this.handleTask(task) - await this.taskManager.updateTaskStatus( - task.taskId, - task.batchId, - task.subBatchId, - 'completed' - ) + while (this.currentTaskList.length < this.globalConfig.task_number) { + // 获取正在等待中第一个任务 + const tasks = this.bookBackTaskListService.GetWaitTaskAndSlice(TaskExecuteType.AUTO, 1) + if (!tasks.data || tasks.data.length <= 0) { + console.log('没有等待中的任务') + break } + let task = tasks.data[0] + this.currentTaskList.push(task) + this.handleTask(task) + .then((data) => { + if (data.code == 0) { + this.bookBackTaskListService.UpdateTaskStatus({ + id: task.id, + status: BookBackTaskStatus.FAIL, + errorMessage: data.message + }) + return Promise.reject(new Error(data.message)) + } else if (data.code == 1) { + this.bookBackTaskListService.UpdateTaskStatus({ + id: task.id, + status: BookBackTaskStatus.DONE + }) + } else { + return Promise.reject(new Error(`${task.type} 返回的数据结构不对`)) + } + }) + .catch((error) => { + // 失败,修改当前task的状态为fail + this.bookBackTaskListService.UpdateTaskStatus({ + id: task.id, + status: BookBackTaskStatus.FAIL, + errorMessage: error.message + }) + throw error + }) + .finally(() => { + this.currentTaskList = this.currentTaskList.filter((t) => t.id != task.id) + this.ExecuteAutoTask() + }) } } catch (err) { - console.error('Error executing tasks:', err) + return errorMessage( + `执行任务失败,失败信息如下:${err.message}`, + 'TaskManager_ExecuteAutoTask' + ) } finally { - this.isExecuting = false + // 判断是不是还有任务,没有则设置任务执行状态为false + if (this.currentTaskList.length <= 0) { + this.isExecuting = false + } } } async handleTask(task) { - if (task.taskId === 'specificTask') { - console.log(`Handling specific task: ${task.taskId}`) - // 执行任务的具体逻辑 + try { + let _bookBackTaskListService = await BookBackTaskListService.getInstance() + let res + // 调用分镜头任务 + if (task.type == BookBackTaskType.STORYBOARD) { + res = await this.basicReverse.GetFrameData(task) + } else if (task.type == BookBackTaskType.SPLIT) { + // 调用分割视频任务 + res = await this.basicReverse.CutVideoData(task) + } else if (task.type == BookBackTaskType.AUDIO) { + // 提取音频任务 + res = await this.basicReverse.SplitAudioData(task) + } else if (task.type == BookBackTaskType.FRAME) { + // 抽取视频帧任务 + res = await this.basicReverse.GetFrame(task) + } else if (task.type == BookBackTaskType.RECOGNIZE) { + // 识别识别字幕任务 + res = await this.basicReverse.ExtractSubtitlesData(task) + } + // 未知的任务类型 + else { + throw new Error('未知的任务类型') + } + + if (res.code == 0) { + // 修改当前队列的任务状态 + let updateRes = _bookBackTaskListService.UpdateTaskStatus({ + id: task.id, + status: BookBackTaskStatus.FAIL, + errorMessage: res.message + }) + if (updateRes.code == 0) { + throw new Error(updateRes.message) + } + throw new Error(res.message) + } + + // 判断是不是要添加后续任务 + await this.AddTaskHandle(task, true) + + // 修改当前队列的任务状态 + let updateRes = _bookBackTaskListService.UpdateTaskStatus({ + id: task.id, + status: BookBackTaskStatus.DONE + }) + if (updateRes.code == 0) { + throw new Error(updateRes.message) + } + return successMessage(null, '任务执行成功', 'TaskManager_handleTask') + } catch (error) { + return errorMessage( + `处理 ${task.type} 类型任务 ${task.name} 失败,失败信息如下:${error.message}`, + 'TaskManager_handleTask' + ) + } + } + + /** + * 是否添加后续任务 + * @param {*} task + * @param {*} isAdd + */ + async AddTaskHandle(task, isAdd = false) { + if (!isAdd) { + return + } + + if (task.type == BookBackTaskType.STORYBOARD) { + await this.basicReverse.AddCutVideoDataTask(task.bookId) + } else if (task.type == BookBackTaskType.SPLIT) { + await this.basicReverse.AddSplitAudioDataTask(task.bookId, task.bookTaskId) + } else if (task.type == BookBackTaskType.AUDIO) { + await this.basicReverse.AddGetFrameTask(task.bookId, task.bookTaskId) + } else if (task.type == BookBackTaskType.FRAME) { + await this.basicReverse.AddExtractSubtitlesDataTask(task.bookId, task.bookTaskId) } else { - console.log(`Handling general task: ${task.taskId}`) - // 执行任务的具体逻辑 + throw new Error('不支持的任务类型') } } } - -const taskManager = new TaskManager() -const taskExecutor = new TaskExecutor(taskManager) - -// 每分钟检查并执行一次任务 -setInterval(() => { - taskExecutor.executePendingTasks().catch(console.error) -}, 60 * 1000) - -// 示例:创建任务并触发执行 -taskManager - .createTask('task1', 'batch1', 'subBatch1') - .then((taskId) => { - if (taskId) { - console.log('Task created with ID:', taskId) - } else { - console.log('没有创建新任务') - } - }) - .catch((err) => console.error(err)) diff --git a/src/main/Task/taskScheduler.js b/src/main/Task/taskScheduler.js index bd8d60f..7090242 100644 --- a/src/main/Task/taskScheduler.js +++ b/src/main/Task/taskScheduler.js @@ -1,13 +1,10 @@ - -import { LoggerService } from '../../define/db/service/SoftWare/loggerService'; -import { DEFINE_STRING } from '../../define/define_string'; -import { LoggerStatus, OtherData } from '../../define/enum/softwareEnum'; -import { successMessage } from '../generalTools'; +import { LoggerService } from '../../define/db/service/SoftWare/loggerService' +import { DEFINE_STRING } from '../../define/define_string' +import { LoggerStatus, OtherData } from '../../define/enum/softwareEnum' +import { successMessage } from '../generalTools' export class TaskScheduler { - constructor() { - - } + constructor() {} /** * 添加日志到数据库,然后返回日志信息到前端,日志记录失败不会报错 * @param {*} bookId 小说ID,必填 @@ -15,12 +12,17 @@ export class TaskScheduler { * @param {*} content 日志记录内容,必填 * @param {*} bookTaskId 小说子任务,选填,默认 OtherData.DEFAULT * @param {*} status 状态,选填,默认 LoggerStatus.DOING - * @returns + * @returns */ - async AddLogToDB(bookId, type, content, bookTaskId = OtherData.DEFAULT, status = LoggerStatus.DOING) { + async AddLogToDB( + bookId, + type, + content, + bookTaskId = OtherData.DEFAULT, + status = LoggerStatus.DOING + ) { try { - let log = - { + let log = { bookId: bookId, bookTaskId: bookTaskId, type: type, @@ -28,14 +30,14 @@ export class TaskScheduler { content: content } - let _loggerService = await LoggerService.getInstance(); + let _loggerService = await LoggerService.getInstance() let res = await _loggerService.AddLogger(log) // 添加成功之后,将消息推动到前端 - global.newWindow[0].win.webContents.send(DEFINE_STRING.SYSTEM.RETURN_LOGGER, successMessage(res)) + global.newWindow[0].win.webContents.send(DEFINE_STRING.SYSTEM.RETURN_LOGGER, res) return res } catch (error) { - return errorMessage(error.message, 'TaskScheduler_AddLogToDB'); + return errorMessage(error.message, 'TaskScheduler_AddLogToDB') } } -} \ No newline at end of file +} diff --git a/src/main/Task/watermarkAndSubtitle.js b/src/main/Task/watermarkAndSubtitle.js new file mode 100644 index 0000000..73f9c82 --- /dev/null +++ b/src/main/Task/watermarkAndSubtitle.js @@ -0,0 +1,418 @@ +import { isEmpty } from 'lodash' +import { BookService } from '../../define/db/service/Book/bookService' +import { errorMessage, successMessage } from '../generalTools' +import { FfmpegOptions } from './ffmpegOptions' +import { SubtitleSavePositionType } from '../../define/enum/waterMarkAndSubtitle' +import { BookTaskDetailService } from '../../define/db/service/Book/bookTaskDetailService' +import { define } from '../../define/define' +import path from 'path' +import { + CheckFileOrDirExist, + DeleteFolderAllFile, + GetFilesWithExtensions +} from '../../define/Tools/file' +import { shell } from 'electron' +import fs from 'fs' +const util = require('util') +const { exec } = require('child_process') +const execAsync = util.promisify(exec) +const fspromises = fs.promises + +/** + * 去除水印和获取字幕相关操作 + */ +export class WatermarkAndSubtitle { + constructor() {} + + async InitService() { + this.bookService = await BookService.getInstance() + this.bookTaskDetailService = await BookTaskDetailService.getInstance() + this.FfmpegOptions = new FfmpegOptions() + } + + //#region 通用方法 + + /** + * 拆分视频总帧数,每秒多少帧,平分视频总帧数,后截取 + * @param {*} videoDurationMs 视频的总时长(毫秒) + * @param {*} framesPerSecond 每秒截取多少帧 + * @returns + */ + GenerateFrameTimes(videoDurationMs, framesPerSecond) { + // 直接使用视频总时长(毫秒),不进行向下取整 + const videoDurationSec = videoDurationMs / 1000 + + // 计算总共需要抽取的帧数,考虑到视频时长可能不是完整秒数,使用 Math.ceil 来确保至少获取到最后一秒内的帧 + const totalFrames = Math.ceil(videoDurationSec * framesPerSecond) + + // 计算两帧之间的时间间隔(毫秒) + const interval = 1000 / framesPerSecond + + // 生成对应的时间点数组 + const frameTimes = [] + for (let i = 0; i < totalFrames; i++) { + // 使用 Math.min 确保最后一个时间点不会超过视频总时长 + let timePoint = Math.min(Math.round(interval * i), videoDurationMs) + frameTimes.push(timePoint) + } + + return frameTimes + } + + //#endregion + + //#region 字幕 + + /** + * 获取当前视频中所有的字幕信息 + * @param {*} value 需要的参数的对象,包含下面的参数 + * @param {*} value.id 小说ID/小说分镜详细信息ID/null + * @param {*} value.type 保存的类型(主视频/分镜视频/后续会添加外部单独的视频提取) + * @param {*} value.videoPath 视频路径 + */ + async GetVideoFrameText(value) { + try { + await this.InitService() + let videoPath + let tempImageFolder + let position + if (value.type == SubtitleSavePositionType.MAIN_VIDEO) { + let bookRes = this.bookService.GetBookDataById(value.id) + if (bookRes.data == null) { + throw new Error('没有找到小说对应的的视频地址') + } + let book = bookRes.data + tempImageFolder = path.join(define.project_path, `${book.id}/data/subtitle/${book.id}/temp`) + if (isEmpty(book.subtitlePosition)) { + throw new Error('请先保存位置信息') + } + position = JSON.parse(book.subtitlePosition) + videoPath = book.oldVideoPath + } + + // // 判断文件夹是不是存在,存在的话,将里面的所有文件删除 + // await DeleteFolderAllFile(tempImageFolder) + + // // 将视频进行抽帧,(目前是每秒1帧,时间小于一秒,抽一帧) + // let getDurationRes = await this.FfmpegOptions.FfmpegGetVideoDuration(videoPath) + // if (getDurationRes.code == 0) { + // throw new Error(getDurationRes.message) + // } + // let videoDuration = getDurationRes.data + // let frameTime = this.GenerateFrameTimes(videoDuration, 1) + // for (let i = 0; i < frameTime.length; i++) { + // const item = frameTime[i] + // let name = i.toString().padStart(6, '0') + // let imagePath = path.join(tempImageFolder, `frame_${name}.png`) + // // 开始裁剪抽, + // let res = await this.FfmpegOptions.FfmpegGetVideoFramdAndClip( + // videoPath, + // item, + // imagePath, + // position + // ) + // // 开始识别 + // if (res.code == 0) { + // throw new Error(res.message) + // } + // } + // 截取完毕,删除大的图片 + + // 开始识别 + let textRes = await this.GetCurrentFrameText({ + id: value.id, + type: value.type, + imageFolder : tempImageFolder + }) + + let allTextData = [] + // 开始获取所有的数据 + let jsonPaths = await GetFilesWithExtensions(tempImageFolder, ['.json']) + for (let i = 0; i < jsonPaths.length; i++) { + const element = jsonPaths[i] + // 开始拼接 + let texts = JSON.parse(await fspromises.readFile(element, 'utf-8')) + for (let j = 0; j < texts.length; j++) { + const text = texts[j][1][0] + allTextData.includes(text) ? null : allTextData.push(text) + } + } + + console.log(allTextData.join('\n')) + } catch (error) { + return errorMessage( + '提取视频的的文案信息失败,错误消息如下:' + error.toString(), + 'WatermarkAndSubtitle_GetCurrentFrameText' + ) + } + } + + /** + * 获取当前帧的文字信息 + * @param {*} value 需要的参数的对象,必须包含以下参数 + * @param {*} value.id 小说ID/小说分镜详细信息ID/null + * @param {*} value.type 保存的类型(主视频/分镜视频/后续会添加外部单独的视频提取) + */ + async GetCurrentFrameText(value) { + try { + await this.InitService() + let iamgePaths = [] + let imageFolder + if (value.type == SubtitleSavePositionType.MAIN_VIDEO) { + // 判断是不是有位置信息 + imageFolder = value.imageFolder + ? value.imageFolder + : path.join(define.project_path, `${value.id}/data/subtitle/${value.id}`) + let imageFolderIsExist = await CheckFileOrDirExist(imageFolder) + if (!imageFolderIsExist) { + throw new Error('请先保存位置信息') + } + + let images = await GetFilesWithExtensions(imageFolder, ['.png']) + let regex = /.*frame_.*\.png$/ + images.forEach((element) => { + // 使用正则表达式测试文件名 + if (regex.test(element)) { + iamgePaths.push(element) + } + }) + } else if (value.type == SubtitleSavePositionType.STORYBOARD_VIDEO) { + } + + // 开始识别 + for (let i = 0; i < iamgePaths.length; i++) { + const imagePath = iamgePaths[i] + let scriptPath = path.join(define.scripts_path, 'LaiOcr/LaiOcr.exe') + let script = `cd "${path.dirname(scriptPath)}" && "${scriptPath}" "${imagePath}"` + let scriptRes = await execAsync(script, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' }) + console.log(scriptRes) + if (scriptRes.error) { + throw new Error(scriptRes.error) + } + } + + // 处理所有的图片完毕,遍历所有的数据返回 + let textData = [] + let jsonPath = await GetFilesWithExtensions(imageFolder, ['.json']) + for (let i = 0; i < jsonPath.length; i++) { + const element = jsonPath[i] + // 开始拼接 + let texts = JSON.parse(await fspromises.readFile(element, 'utf-8')) + for (let j = 0; j < texts.length; j++) { + const text = texts[j][1][0] + textData.includes(text) ? null : textData.push(text) + } + } + return successMessage( + textData.join('\n'), + '获取当前帧的文字信息成功', + 'WatermarkAndSubtitle_GetCurrentFrameText' + ) + } catch (error) { + return errorMessage( + '获取当前帧的文字信息失败,错误消息如下:' + error.toString(), + 'WatermarkAndSubtitle_GetCurrentFrameText' + ) + } + } + + /** + * 打开对应的ID的字幕提取的图片文件夹 + * @param {*} value 需要的参数的对象,必须包含以下参数 + * @param {*} value.id 小说ID/小说分镜详细信息ID/null + * @param {*} value.type 保存的类型(主视频/分镜视频/后续会添加外部单独的视频提取) + */ + async OpenBookSubtitlePositionScreenshot(value) { + try { + let folder + if (value.type == SubtitleSavePositionType.MAIN_VIDEO) { + folder = path.join(define.project_path, `${value.id}/data/subtitle/${value.id}`) + } else if (value.type == SubtitleSavePositionType.STORYBOARD_VIDEO) { + folder = path.join(define.project_path, `${value.id}/data/subtitle/${value.id}`) + } + + // 判断文件夹是不是存在 + let folderIsExist = await CheckFileOrDirExist(folder) + if (!folderIsExist) { + throw new Error('文件夹不存在,请先保存字幕位置信息') + } + + // 打开文件夹\ + shell.openPath(folder) + return successMessage( + null, + '打开对应的文件夹成功', + 'WatermarkAndSubtitle_OpenBookSubtitlePositionScreenshot' + ) + } catch (error) { + return errorMessage( + '打开字幕位置信息失败,错误消息如下:' + error.toString(), + 'WatermarkAndSubtitle_OpenBookSubtitlePositionScreenshot' + ) + } + } + + /** + * 保存反推的视频的文案位置信息(可以保存多个) + * @param {*} value 需要的参数的对象,必须包含以下参数 + * @param {*} value.id 小说ID/小说分镜详细信息ID/null + * @param {*} value.bookSubtitlePosition 小说文案对应的位置 + * @param {*} value.currentTime 视频当前保存的时间 + * @param {*} value.type 保存的类型(主视频/分镜视频/后续会添加外部单独的视频提取) + * @returns + */ + async SaveBookSubtitlePosition(value) { + try { + await this.InitService() + let saveData = [] + let videoPath + let outImagePath + // 小说视频保存 + this.FfmpegOptions = new FfmpegOptions() + if (value.type == SubtitleSavePositionType.MAIN_VIDEO) { + if (value.id == null) { + throw new Error('小说ID不能为空') + } + // 获取指定的小说 + let bookRes = this.bookService.GetBookDataById(value.id) + if (bookRes.data == null) { + throw new Error(bookRes.message) + } + let book = bookRes.data + + if (value.bookSubtitlePosition.length <= 0) { + throw new Error('没有获取到字幕信息') + } + + videoPath = book.oldVideoPath + if (isEmpty(videoPath)) { + throw new Error('没有获取到视频路径') + } + outImagePath = path.join(book.bookFolderPath, `data/subtitle/${book.id}/frame.png`) + + // 获取视频的宽高数据 + let videoSizeRes = await this.FfmpegOptions.FfmpegGetVideoSize(videoPath) + if (videoSizeRes.code == 0) { + throw new Error(videoSizeRes.message) + } + let videoSize = videoSizeRes.data + + // 开始计算比例 + let videoWidth = videoSize.width + let videoHeight = videoSize.height + for (let i = 0; i < value.bookSubtitlePosition.length; i++) { + const element = value.bookSubtitlePosition[i] + let widthRate = videoWidth / element.videoWidth // 宽度比例 + let heightRate = videoHeight / element.videoHeight // 高度比例 + // 计算比例 + let newStartX = widthRate * element.startX + let newStartY = heightRate * element.startY + let newWidth = widthRate * element.width + let newHeight = heightRate * element.height + saveData.push({ + startX: newStartX, + startY: newStartY, + width: newWidth, + height: newHeight, + videoWidth: videoWidth, + videoHeight: videoHeight + }) + } + + // 数据保存 + let saveRes = await this.bookService.UpdateBookData(value.id, { + subtitlePosition: JSON.stringify(saveData) + }) + if (saveRes.code == 0) { + throw new Error(saveRes.message) + } + } else if (value.type == SubtitleSavePositionType.STORYBOARD_VIDEO) { + // 小说分镜详细信息保存 + if (value.id == null) { + throw new Error('小说分镜详细信息ID不能为空') + } + // 获取指定的小说分镜详细信息 + let bookStoryboardRes = this.bookTaskDetailService.GetBookTaskDetailDataById(value.id) + if (bookStoryboardRes.data == null) { + throw new Error('没有找到小说分镜信息') + } + let bookStoryboard = bookStoryboardRes.data + + if (value.bookSubtitlePosition.length <= 0) { + throw new Error('没有获取到字幕信息') + } + + videoPath = bookStoryboard.videoPath + if (isEmpty(videoPath)) { + throw new Error('没有获取到视频路径') + } + + outImagePath = path.join( + define.project_path, + `${bookStoryboard.bookId}/data/subtitle/${bookStoryboard.id}/frame.png` + ) + + // 获取视频的宽高数据 + this.FfmpegOptions = new FfmpegOptions() + let videoSizeRes = await this.FfmpegOptions.FfmpegGetVideoSize(videoPath) + if (videoSizeRes.code == 0) { + throw new Error(videoSizeRes.message) + } + let videoSize = videoSizeRes.data + + // 开始计算比例 + let videoWidth = videoSize.width + let videoHeight = videoSize.height + for (let i = 0; i < value.bookSubtitlePosition.length; i++) { + const element = value.bookSubtitlePosition[i] + let widthRate = videoWidth / element.videoWidth // 宽度比例 + let heightRate = videoHeight / element.videoHeight // 高度比例 + // 计算比例 + let newStartX = widthRate * element.startX + let newStartY = heightRate * element.startY + let newWidth = widthRate * element.width + let newHeight = heightRate * element.height + saveData.push({ + startX: newStartX, + startY: newStartY, + width: newWidth, + height: newHeight, + videoWidth: videoWidth, + videoHeight: videoHeight + }) + } + // 数据保存 + let saveRes = this.bookTaskDetailService.UpdateBookTaskDetail(bookStoryboard.value.id, { + subtitlePosition: JSON.stringify(saveData) + }) + if (saveRes.code == 0) { + throw new Error(saveRes.message) + } + } + + // 开始设置裁剪出来的图片位置 + // 裁剪一个示例图片 + let saveImagePath = await this.FfmpegOptions.FfmpegGetVideoFramdAndClip( + videoPath, + value.currentTime * 1000, + outImagePath, + saveData + ) + if (saveImagePath.code == 0) { + throw new Error(saveImagePath.message) + } + return successMessage( + saveImagePath.data, + '保存字幕位置信息成功', + 'WatermarkAndSubtitle_SaveBookSubtitlePosition' + ) + } catch (error) { + return errorMessage( + '保存字幕位置信息失败,错误消息如下:' + error.toString(), + 'WatermarkAndSubtitle_SaveBookSubtitlePosition' + ) + } + } + + //#endregion +} diff --git a/src/main/Task/writing.js b/src/main/Task/writing.js new file mode 100644 index 0000000..eeb1655 --- /dev/null +++ b/src/main/Task/writing.js @@ -0,0 +1,413 @@ +let path = require('path') +let fspromises = require('fs').promises +import { Tools } from '../tools' +import { DEFINE_STRING } from '../../define/define_string' +import { PublicMethod } from '../Public/publicMethod' +import { define } from '../../define/define' +import { get, has, isEmpty } from 'lodash' +import { ClipSetting } from '../../define/setting/clipSetting' +import { errorMessage, successMessage } from '../generalTools' +import { ServiceBase } from '../../define/db/service/serviceBase' +import { + GetDoubaoErrorResponse, + GetKimiErrorResponse, + GetOpenAISuccessResponse, + GetRixApiErrorResponse +} from '../../define/response/openAIResponse.ts' +import axios from 'axios' +const { v4: uuidv4 } = require('uuid') // 引入UUID库来生成唯一标识符 +let tools = new Tools() + +export class Writing extends ServiceBase { + constructor(global) { + super() + this.global = global + this.pm = new PublicMethod(global) + axios.defaults.baseURL = define.serverUrl + } + + async ActionStart(setting, word) { + try { + await this.InitService() + console.log(setting, word) + if (isEmpty(setting.gptType)) { + throw new Error('请选择GPT类型') + } + if (isEmpty(setting.gptData)) { + throw new Error('请选择GPT预设') + } + if (isEmpty(setting.gptAI)) { + throw new Error('请选择使用的AI') + } + + // 判断对应的AI的设置是不是有 + let aiSetting = this.softService.GetSoftWarePropertyData('aiSetting') + if (isEmpty(aiSetting.data)) { + throw new Error('请先设置AI设置') + } + aiSetting = JSON.parse(aiSetting.data) + // 判断是不是有对应的AI设置 + let aiData = get(aiSetting, setting.gptAI) + for (const aid in aiData) { + if (isEmpty(aid)) { + throw new Error('请先设置AI设置') + } + } + if (isEmpty(word)) { + throw new Error('请先设置文案') + } + + // 开始请求 + console.log(aiData) + let axiosRes = await axios.post('/api/Forward/ForwardWord', { + promptTypeId: setting.gptType, + promptId: setting.gptData, + gptUrl: aiData.gpt_url, + model: aiData.model, + machineId: this.global.machineId, + apiKey: aiData.api_key, + word: word + }) + + // 判断返回的状态,如果是失败的话直接返回错误信息 + if (axiosRes.status != 200) { + throw new Error('请求失败') + } + let dataRes = axiosRes.data + if (dataRes.code == 1) { + // 获取成功 + // 解析返回的数据 + return successMessage( + GetOpenAISuccessResponse(dataRes.data), + '请求成功', + 'Writing_ActionStart' + ) + } else { + // 处理不同类型的错误消息 + if (setting.gptAI == 'laiapi') { + throw new Error(GetRixApiErrorResponse(dataRes.data)) + } else if (setting.gptAI == 'kimi') { + throw new Error(GetKimiErrorResponse(dataRes.data)) + } else if (setting.gptAI == 'doubao') { + throw new Error(GetDoubaoErrorResponse(dataRes.data)) + } else { + throw new Error(dataRes.data) + } + } + } catch (error) { + return errorMessage( + '执行文案相关任务失败,失败信息如下:' + error.toString(), + 'Writing_ActionStart' + ) + } + } + + //#region 下面是文案的处理相关 + + /** + * 将文案信息写入到本地的文案文件中 + * @param {*} value + */ + async SaveWordTxt(value) { + try { + let word_path = path.join(global.config.project_path, '文案.txt') + await tools.writeArrayToFile(value, word_path) + return { + code: 1, + message: '保存成功' + } + } catch (error) { + throw new Error(error) + } + } + + /** + * 将分镜的时间信息添加道配置文件中 + * @param {*} value 是一个数组,0 :写入的数据 1:写入的属性 2:是否需要解析 + */ + async SaveCopywritingInformation(value) { + try { + return await this.pm.SaveConfigJsonProperty(value) + } catch (error) { + return { + code: 0, + message: error.toString() + } + } + } + + /** + * 获取Config.json文件中指定的属性 + * @param {Array} value 传入的值 0 : 需要获取的属性 1: 返回的默认值 + * @returns + */ + async GetConfigJson(value) { + try { + return await this.pm.GetConfigJson(value, false) + } catch (error) { + return { + code: 0, + message: error.toString() + } + } + } + + /** + * 获取当前项目下面的文案 + */ + async GetProjectWord() { + try { + // 先判断当前的项目文件下面是不是又配置文件。没有才读取文案 + let srt_config_path = path.join(global.config.project_path, 'scripts/config.json') + let isExist = await tools.checkExists(srt_config_path) + let data = null + let isImformation = false + if (isExist) { + let config_1 = JSON.parse(await fspromises.readFile(srt_config_path)) + isImformation = has(config_1, 'srt_time_information') + if (isImformation) { + data = JSON.parse(await fspromises.readFile(srt_config_path)).srt_time_information + } + } + + if (!isExist || !isImformation) { + let word_path = path.join(global.config.project_path, '文案.txt') + let isExistWord = await tools.checkExists(word_path) + if (!isExistWord) { + return { + code: 0, + message: '没有文案文件' + } + } + let data = await fspromises.readFile(word_path, { encoding: 'utf-8' }) + let lines = data.split(/\r?\n/) + // 打印或返回这个数组 + // console.log(lines); + // 判断是不是有洗稿后的文件 + let new_srt_path = path.join(global.config.project_path, 'new_word.txt') + let isExistAfterGPTWord = await tools.checkExists(new_srt_path) + let after_data = null + if (isExistAfterGPTWord) { + after_data = (await fspromises.readFile(new_srt_path, { encoding: 'utf-8' })).split( + /\r?\n/ + ) + } + // 判断抽帧文件是不是存在 + // 返回图片信息 + let old_image_path_list = await tools.getFilesWithExtensions( + path.join(global.config.project_path, 'tmp/input_crop'), + '.png' + ) + let res = [] + let lastId = '' + // 处理数据 + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + let id = uuidv4() + let after_gpt = null + if (after_data != null) { + after_gpt = after_data[i] + } + + let img_path = null + if (old_image_path_list != null) { + img_path = old_image_path_list[i] + } + let obj = { + no: i + 1, + id: id, + lastId: lastId, + word: line, + old_image: img_path, + after_gpt: after_gpt, + start_time: null, + end_time: null, + timeLimit: null, + subValue: [] + } + res.push(obj) + lastId = id + } + return { + code: 1, + type: 0, + data: res + } + } else { + let data = JSON.parse(await fspromises.readFile(srt_config_path)).srt_time_information + return { + code: 1, + type: 1, + data: data + } + } + } catch (error) { + throw new Error(error) + } + } + + /** + * 搭导入srt。然后加载时间轴。完全匹配失败的将会还是会导入然后手动手动切换 + * @param {文案洗稿界面信息} textData + */ + async ImportSrtAndGetTime(data) { + let textData = data[0] + let init_num = textData.length + let srt_path = data[1] + let current_text = '' + try { + if (!srt_path) { + // 获取项目下面的所有的srt + let srtfiles = await tools.getFilesWithExtensions(global.config.project_path, '.srt') + if (srtfiles.length <= 0) { + throw new Error('没有SRT文件') + } + srt_path = srtfiles[0] + } + let srt_data = (await fspromises.readFile(srt_path, 'utf-8')).toString('utf-8') + const entries = srt_data.replace(/\r\n/g, '\n').split('\n\n') + let data = entries + .map((entry) => { + const lines = entry.split('\n') + if (lines.length >= 3) { + const times = lines[1] + const text = lines.slice(2).join(' ') + const [start, end] = times.split(' --> ').map((time) => { + const [hours, minutes, seconds] = time.split(':') + const [sec, millis] = seconds.split(',') + return ( + (parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(sec)) * 1000 + + parseInt(millis) + ) + }) + return { start, end, text, id: uuidv4() } + } + }) + .filter((entry) => entry) + // 开始匹配(洗稿后的) + let srt_list = [] + let srt_obj = null + let text_count = 0 + let tmp_str = '' + for (let i = 0; i < data.length; ) { + let srt_value = data[i].text + current_text = `字幕: “${srt_value}” 和文案第${text_count + 1} 行数据 “${ + textData[text_count].after_gpt + }” 数据不匹配(检查一下上下文)` + let start_time = data[i].start + let end_time = data[i].end + let obj = { + start_time, + end_time, + srt_value, + id: data[i].id + } + // 判断当前字幕是不是在当前句 + // 不能用简单的包含,而是将数据进行去除特殊符号拼接后判断是不是相同 + tmp_str += srt_value + if ( + tools + .removePunctuationIncludingEllipsis(textData[text_count].after_gpt) + .startsWith(tools.removePunctuationIncludingEllipsis(tmp_str)) + ) { + if (srt_obj == null) { + srt_obj = {} + srt_obj.id = uuidv4() + srt_obj.start_time = start_time + srt_obj.value = srt_value + srt_obj.subValue = [obj] + } else { + srt_obj.value = srt_obj.value + srt_value + srt_obj.subValue = [...srt_obj.subValue, obj] + } + textData[text_count].start_time = srt_obj.start_time + textData[text_count].subValue = srt_obj.subValue + srt_list.push(obj) + i++ + } else { + // 判断下一句文件是不是以当当前巨开头。是的话继续。不是的话。直接返回后面的所有信息 + if ( + tools + .removePunctuationIncludingEllipsis(textData[text_count + 1].after_gpt) + .startsWith(tools.removePunctuationIncludingEllipsis(srt_value)) + ) { + textData[text_count].end_time = srt_list[srt_list.length - 1].end_time + text_count++ + srt_obj = null + tmp_str = '' + } else { + // 将下面的数据直接 添加到textData后面。 + // 修改当前行数据的结束事件为 + if (srt_list.length > 0) { + textData[text_count].end_time = srt_list[srt_list.length - 1].end_time + text_count++ + } + // 将后面的数据直接添加 + let lastId = textData[textData.length - 1].id + for (let j = i; j < data.length; j++) { + // 直接修改原有数据 + if (text_count < init_num) { + textData[text_count].subValue = [ + { + start_time: data[j].start, + end_time: data[j].end, + id: data[j].id, + srt_value: data[j].text + } + ] + textData[text_count].start_time = data[j].start + textData[text_count].end_time = data[j].end + text_count++ + } else { + let id = uuidv4() + // 添加 + let obj = { + no: j + 1, + id: id, + word: null, + lastId: lastId, + old_image: path.normalize(define.zhanwei_image), + after_gpt: null, + start_time: data[j].start, + end_time: data[j].end, + subValue: [ + { + start_time: data[j].start, + end_time: data[j].end, + id: data[j].id, + srt_value: data[j].text + } + ] + } + lastId = id + textData.push(obj) + } + } + this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { + code: 0, + message: current_text + }) + return { + code: 1, + data: textData + } + } + } + } + // 最后对齐 + textData[textData.length - 1].end_time = srt_list[srt_list.length - 1].end_time + // 返回数据 + return { + code: 1, + data: textData + } + } catch (error) { + return { + code: 0, + message: error.toString() + } + } + } + + //#endregion +} diff --git a/src/main/index.js b/src/main/index.js index 20312f3..9444967 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -1,8 +1,7 @@ -import fspromises from "fs/promises"; -import { v4 as uuidv4 } from 'uuid'; +import fspromises from 'fs/promises' +import { v4 as uuidv4 } from 'uuid' import { version } from '../../package.json' -import { graphics } from "systeminformation" - +import { graphics } from 'systeminformation' import { app, shell, BrowserWindow, ipcMain, dialog, nativeTheme } from 'electron' import path, { join } from 'path' @@ -10,52 +9,52 @@ import { electronApp, optimizer, is } from '@electron-toolkit/utils' import icon from '../../resources/icon.ico?asset' import { define } from '../define/define.js' import { func } from './func.js' -import { AsyncQueue } from "./quene.js" +import { AsyncQueue } from './quene.js' import { DEFINE_STRING } from '../define/define_string.js' import { Tools } from './tools.js' import { ImageGenerate } from './ReverseManage/imageGenerate.js' import { Setting } from './setting/setting.js' import { has, isEmpty } from 'lodash' -import { AutoSyncMJConfig2210 } from './setting/autoSync.js' +import { AutoSync } from './setting/autoSync.js' +import { TaskManager } from './Task/taskManage.js' // ipc import { DiscordIpc, RemoveDiscordIpc } from './IPCEvent/discordIpc.js' -import { Logger } from "./logger.js"; -import { RegisterIpc } from "./IPCEvent/index.js"; - -let tools = new Tools(); -let imageGenerate = new ImageGenerate(global); -let setting = new Setting(global); - +import { Logger } from './logger.js' +import { RegisterIpc } from './IPCEvent/index.js' +let tools = new Tools() +let imageGenerate = new ImageGenerate(global) +let setting = new Setting(global) async function InitData(gl) { - let res = await setting.getSettingDafultData(); - gl.config = res; - gl.requestQuene = new AsyncQueue(gl, res.task_number); - gl.fileQueue = new AsyncQueue(gl, 1); - return res; + let res = await setting.getSettingDafultData() + gl.config = res + gl.requestQuene = new AsyncQueue(gl, res.task_number) + gl.fileQueue = new AsyncQueue(gl, 1) + gl.taskManager = new TaskManager() + return res } function setIpcHandler(hash) { - if (hash == "discord") { - DiscordIpc(global); + if (hash == 'discord') { + DiscordIpc(global) } } function removeIpcHandler(hash) { - if (hash == "discord") { - RemoveDiscordIpc(); + if (hash == 'discord') { + RemoveDiscordIpc() } } -async function createWindow(hash = "ShowMessage", data, url = null) { +async function createWindow(hash = 'ShowMessage', data, url = null) { // Create the browser window. - await InitData(global); - global.currentHash = hash; + await InitData(global) + global.currentHash = hash // 判断当前是不是有设置的宽高,用的话记忆 - let isRe = global.config.window_wh_bm_remember && hash == "ShowMessage" && global.config.window_wh_bm; - + let isRe = + global.config.window_wh_bm_remember && hash == 'ShowMessage' && global.config.window_wh_bm let mainWindow = new BrowserWindow({ width: isRe ? global.config.window_wh_bm.width : 900, @@ -70,14 +69,14 @@ async function createWindow(hash = "ShowMessage", data, url = null) { webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: false, - nodeIntegration: (hash == 'discord' ? false : true), // 在网页中集成Node + nodeIntegration: hash == 'discord' ? false : true, // 在网页中集成Node nodeIntegrationInWorker: true, webSecurity: false, - partition: "persist:my-partition", + partition: 'persist:my-partition' } }) - mainWindow.myID = uuidv4(); + mainWindow.myID = uuidv4() mainWindow.on('ready-to-show', () => { mainWindow.show() @@ -95,10 +94,10 @@ async function createWindow(hash = "ShowMessage", data, url = null) { mainWindow.loadURL(url) } else { if (is.dev && process.env['ELECTRON_RENDERER_URL']) { - mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + "/#/" + hash); - mainWindow.webContents.openDevTools(); + mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/#/' + hash) + mainWindow.webContents.openDevTools() } else { - if (hash != "") { + if (hash != '') { mainWindow.loadURL(`file://${path.join(__dirname, '../renderer/index.html')}#/${hash}`) } else { mainWindow.loadFile(join(__dirname, '../renderer/index.html')) @@ -106,13 +105,13 @@ async function createWindow(hash = "ShowMessage", data, url = null) { } } - mainWindow.on("close", async () => { + mainWindow.on('close', async () => { // 判断指定的窗口,移除指定的监听 - removeIpcHandler(hash); - global.newWindow = global.newWindow.filter(item => item.id != mainWindow.id) + removeIpcHandler(hash) + global.newWindow = global.newWindow.filter((item) => item.id != mainWindow.id) // 判断当前的是不是开启了记录功能 - if (global.config.window_wh_bm_remember && hash == "ShowMessage") { - let window_wh_bm = mainWindow.getBounds(); + if (global.config.window_wh_bm_remember && hash == 'ShowMessage') { + let window_wh_bm = mainWindow.getBounds() // 记录到文件中 await setting.ModifySampleSetting(JSON.stringify({ window_wh_bm: window_wh_bm })) } @@ -127,12 +126,12 @@ async function createWindow(hash = "ShowMessage", data, url = null) { init_folder: data }) - setIpcHandler(hash); - return mainWindow; + setIpcHandler(hash) + return mainWindow } -let mainWindow; -global.createWindow = createWindow; +let mainWindow +global.createWindow = createWindow // This method will be called when Electron has finished // initialization and is ready to create browser windows. @@ -148,35 +147,35 @@ app.whenReady().then(async () => { optimizer.watchWindowShortcuts(window) }) - global.logger = new Logger(define.logger_path); + global.logger = new Logger(define.logger_path) // 同步数据 - await AutoSyncMJConfig2210(); + await AutoSync() - global.newWindow = []; + global.newWindow = [] mainWindow = createWindow('ShowMessage', null) //判断是不是又配置文件,没有的话,将temp中的基础配置加载 // 判断文件夹是不是存在 - let config_p = path.dirname(define.config_path); - let isE = await tools.checkExists(config_p); + let config_p = path.dirname(define.config_path) + let isE = await tools.checkExists(config_p) // 文件夹存在判断json文件数量 - let ex_json = []; + let ex_json = [] if (isE) { - let ex_json_path = await tools.getFilesWithExtensions(config_p, '.json'); + let ex_json_path = await tools.getFilesWithExtensions(config_p, '.json') for (let i = 0; i < ex_json_path.length; i++) { - const element = ex_json_path[i]; - ex_json.push(path.basename(element)); + const element = ex_json_path[i] + ex_json.push(path.basename(element)) } } else { - await fspromises.mkdir(config_p, { recursive: true }); + await fspromises.mkdir(config_p, { recursive: true }) } // 判断文件是不是存在,不存在添加 if (ex_json.length != 5) { - let temp_path = path.join(path.dirname(define.draft_temp_path), 'config'); - let tmp_json_path = await tools.getFilesWithExtensions(temp_path, ".json"); + let temp_path = path.join(path.dirname(define.draft_temp_path), 'config') + let tmp_json_path = await tools.getFilesWithExtensions(temp_path, '.json') for (let i = 0; i < tmp_json_path.length; i++) { - const element = tmp_json_path[i]; + const element = tmp_json_path[i] if (!ex_json.includes(path.basename(element))) { await fspromises.copyFile(element, path.join(config_p, path.basename(element))) } @@ -184,18 +183,21 @@ app.whenReady().then(async () => { } // 判断动态文件是不是存在 - tools.checkJsonFileExistsOrCreate(path.normalize(define.dynamic_setting)); + tools.checkJsonFileExistsOrCreate(path.normalize(define.dynamic_setting)) // 判断标签文件是不是存在 - tools.checkJsonFileExistsOrCreate(path.normalize(define.tag_setting), JSON.stringify({ - "character_tags": [], - "style_tags": [], - "scene_tags": [], - "prefix_tags": [], - "suffix_tags": [] - })); + 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"))); + tools.checkFolderExistsOrCreate(path.normalize(define.temp_sd_image)) + tools.checkFolderExistsOrCreate(path.normalize(path.join(define.image_path, 'c_s'))) app.on('activate', async function () { // On macOS it's common to re-create a window in the app when the @@ -215,14 +217,13 @@ app.on('window-all-closed', () => { } }) -RegisterIpc(createWindow); - +RegisterIpc(createWindow) ipcMain.handle('dark-mode:toggle', (event, value) => { if (value) { - nativeTheme.themeSource = value; + nativeTheme.themeSource = value } else { - nativeTheme.themeSource = "system"; + nativeTheme.themeSource = 'system' } return nativeTheme.shouldUseDarkColors }) @@ -231,41 +232,36 @@ ipcMain.handle('dark-mode:toggle', (event, value) => { // code. You can also put them in separate files and require them here. ipcMain.handle(DEFINE_STRING.GET_SETTING_Dafault_DATA, async (event) => { - return await InitData(global); + return await InitData(global) }) ipcMain.handle(DEFINE_STRING.GET_DRAFT_FILE_LIST, async (event) => { - let res = await func.getDraftFileList(); - return res; + let res = await func.getDraftFileList() + return res }) - - ipcMain.handle(DEFINE_STRING.SELECT_FOLDER, async (event, value = null) => { - let po = ['openDirectory']; + let po = ['openDirectory'] if (value && !isEmpty(value.multi)) { - po.push('multiSelections'); + po.push('multiSelections') } let { filePaths } = await dialog.showOpenDialog({ properties: po, - defaultPath: value && !isEmpty(value.defaultPath) ? value.defaultPath : "" + defaultPath: value && !isEmpty(value.defaultPath) ? value.defaultPath : '' }) - return filePaths; + return filePaths }) ipcMain.handle(DEFINE_STRING.SELECT_FILE, async (event, value) => { try { let { filePaths } = await dialog.showOpenDialog({ properties: ['openFile'], - filters: [ - { name: "fileName", extensions: value } - ] + filters: [{ name: 'fileName', extensions: value }] }) return { code: 1, value: filePaths[0] } - } catch (error) { return { code: 0, @@ -274,123 +270,148 @@ ipcMain.handle(DEFINE_STRING.SELECT_FILE, async (event, value) => { } }) - ipcMain.handle(DEFINE_STRING.GET_DRAFT_TEXT_STYLE, async (event, value) => { - let res = await func.getDraftTextStyle(value); - return res; + let res = await func.getDraftTextStyle(value) + return res }) - ipcMain.handle(DEFINE_STRING.GET_TEXT_STYLE_LIST, async (event) => { - let res = await func.getClipSetting("text_style"); - return res; + let res = await func.getClipSetting('text_style') + return res }) ipcMain.handle(DEFINE_STRING.GET_FRIENDLY_REMINDER_LIST, async (event) => { - let res = await func.getClipSetting("friendly_reminder_setting"); - return res; + let res = await func.getClipSetting('friendly_reminder_setting') + return res }) ipcMain.handle(DEFINE_STRING.GET_FRIENDLY_REMINDER_DRAFT, async (event, value) => { - let res = await func.GetDraftFriendlyReminder(value); - return res; + let res = await func.GetDraftFriendlyReminder(value) + return res }) - - ipcMain.handle(DEFINE_STRING.ADD_DRAFT, async (event, value) => { - let res = await func.addDraft(value); - return res; + let res = await func.addDraft(value) + return res }) // 获取当前版本 ipcMain.handle(DEFINE_STRING.GET_VERSION, async (event) => { // 获取当前电脑的显卡信息 - let da = await graphics(); + let da = await graphics() for (let i = 0; i < da.controllers.length; i++) { // 获取第一个英伟达或者是AMD的显卡信息 - const element = da.controllers[i]; - if (element.vendor.startsWith("NVIDIA")) { - global.gpu = element; - global.gpu.type = "NVIDIA"; - break; - } else if (element.vendor.startsWith("AMD") || element.vendor.startsWith("Advanced")) { - global.gpu = element; - global.gpu.type = "AMD"; - break; + const element = da.controllers[i] + if (element.vendor.startsWith('NVIDIA')) { + global.gpu = element + global.gpu.type = 'NVIDIA' + break + } else if (element.vendor.startsWith('AMD') || element.vendor.startsWith('Advanced')) { + global.gpu = element + global.gpu.type = 'AMD' + break } else { global.gpu = { - name: "OTHER" - }; - global.gpu.type = "OTHER"; + name: 'OTHER' + } + global.gpu.type = 'OTHER' } } - return version + " " + (global.gpu?.name ? global.gpu.name : ""); - -}); + return version + ' ' + (global.gpu?.name ? global.gpu.name : '') +}) // 监听保存SD配置 ipcMain.handle(DEFINE_STRING.SAVE_SD_CONFIG, async (event, value) => await func.SaveSDConfig(value)) // 监听保存生成视频的简单配置 -ipcMain.handle(DEFINE_STRING.SAVE_GENERAL_SETTING, async (event, value) => await func.SaveGeneralSetting(value)); +ipcMain.handle( + DEFINE_STRING.SAVE_GENERAL_SETTING, + async (event, value) => await func.SaveGeneralSetting(value) +) // 获取当前的视频合成配置信息 -ipcMain.handle(DEFINE_STRING.GET_VIDEO_CONFIG_MESSAGE, async (event) => await func.GetVideoConfigMessage()); +ipcMain.handle( + DEFINE_STRING.GET_VIDEO_CONFIG_MESSAGE, + async (event) => await func.GetVideoConfigMessage() +) // 监听保存字幕的是指信息 -ipcMain.handle(DEFINE_STRING.SAVE_ASS_CONFIG, async (event, value) => await func.SaveAssConfig(value)); +ipcMain.handle( + DEFINE_STRING.SAVE_ASS_CONFIG, + async (event, value) => await func.SaveAssConfig(value) +) // 监听获取当前系统安装的字体 -ipcMain.handle(DEFINE_STRING.GET_SYSTEM_INSTALL_FONTNAME, async (event) => await func.GetSystemInstallFontName()); +ipcMain.handle( + DEFINE_STRING.GET_SYSTEM_INSTALL_FONTNAME, + async (event) => await func.GetSystemInstallFontName() +) // 监听删除视频配置任务(删除指定ID的值) -ipcMain.handle(DEFINE_STRING.DELETE_VIDEO_CONFIG, async (event, value) => await func.DeleteVideoConfig(value)); +ipcMain.handle( + DEFINE_STRING.DELETE_VIDEO_CONFIG, + async (event, value) => await func.DeleteVideoConfig(value) +) // 监听添加生图任务信息 -ipcMain.handle(DEFINE_STRING.ADD_IMAGE_TASK_LIST, async (event, value) => await func.AddImageTask(value)); +ipcMain.handle( + DEFINE_STRING.ADD_IMAGE_TASK_LIST, + async (event, value) => await func.AddImageTask(value) +) // 监听删除生成图片列表中的信息 -ipcMain.handle(DEFINE_STRING.DELETE_IMAGE_TASK_LIST, async (event, value) => await func.DeleteImageTaskList(value)); +ipcMain.handle( + DEFINE_STRING.DELETE_IMAGE_TASK_LIST, + async (event, value) => await func.DeleteImageTaskList(value) +) // 监听获取加密的机械码任务 -ipcMain.handle(DEFINE_STRING.GET_MACHINE_ID, async (event, value) => await func.GetMachineId()); +ipcMain.handle(DEFINE_STRING.GET_MACHINE_ID, async (event, value) => await func.GetMachineId()) // 监听获取不想要的提示词任务 -ipcMain.handle(DEFINE_STRING.GET_BAD_PROMPT, async (event) => await func.GetBadPrompt()); +ipcMain.handle(DEFINE_STRING.GET_BAD_PROMPT, async (event) => await func.GetBadPrompt()) // 保存不想要的提示词 -ipcMain.handle(DEFINE_STRING.SAVE_BAD_PROMPT, async (event, value) => await func.SaveBadPrompt(value)); +ipcMain.handle( + DEFINE_STRING.SAVE_BAD_PROMPT, + async (event, value) => await func.SaveBadPrompt(value) +) // 一键删除不想要的值 -ipcMain.handle(DEFINE_STRING.DELETE_BAD_PROMPT, async (event) => await func.DeleteBadPrompt()); +ipcMain.handle(DEFINE_STRING.DELETE_BAD_PROMPT, async (event) => await func.DeleteBadPrompt()) // 监听反推任务 -ipcMain.handle(DEFINE_STRING.PUSH_BACK_PROMPT, async (event) => await func.PushBackPrompt()); +ipcMain.handle(DEFINE_STRING.PUSH_BACK_PROMPT, async (event) => await func.PushBackPrompt()) // 打开GPT购买界面 -ipcMain.on(DEFINE_STRING.OPEN_GPT_BUY_URL, async (event, value) => await func.openGptBuyUrl(value)); +ipcMain.on(DEFINE_STRING.OPEN_GPT_BUY_URL, async (event, value) => await func.openGptBuyUrl(value)) // 监听打开任意网站的任务 -ipcMain.on(DEFINE_STRING.OPEN_URL, async (event, value) => await func.OpenUrl(value)); +ipcMain.on(DEFINE_STRING.OPEN_URL, async (event, value) => await func.OpenUrl(value)) // 监听抽帧任务 -ipcMain.handle(DEFINE_STRING.GET_FRAME, async (event, value) => await func.getFrame(value)); +ipcMain.handle(DEFINE_STRING.GET_FRAME, async (event, value) => await func.getFrame(value)) // 监听获取ADtailer列表的任务 -ipcMain.handle(DEFINE_STRING.GET_ADETAILER_LIST, async (event) => await func.GetADetailerList()); +ipcMain.handle(DEFINE_STRING.GET_ADETAILER_LIST, async (event) => await func.GetADetailerList()) // 监听保存ADtailer数据 -ipcMain.handle(DEFINE_STRING.SAVE_DETAILER_CONFIG, async (event, value) => await func.SaveADetailerConfig(value)); +ipcMain.handle( + DEFINE_STRING.SAVE_DETAILER_CONFIG, + async (event, value) => await func.SaveADetailerConfig(value) +) // 分镜识别。语音识别 -ipcMain.handle(DEFINE_STRING.START_STORY_BOARDING, async (event, value) => await func.StartStoryboarding(value)); +ipcMain.handle( + DEFINE_STRING.START_STORY_BOARDING, + async (event, value) => await func.StartStoryboarding(value) +) // 保存试用结束时间 ipcMain.on(DEFINE_STRING.SAVE_TRIAL_END_TIME, (event, value) => { - global.endTime = value.endTime; - global.permissions = value.permissions; -}); + global.endTime = value.endTime + global.permissions = value.permissions +}) // 获取当前机器的权限 ipcMain.handle(DEFINE_STRING.GET_PERMISSION, async (event) => { @@ -401,35 +422,34 @@ ipcMain.handle(DEFINE_STRING.GET_PERMISSION, async (event) => { } }) - // 监听字幕的保存 ipcMain.handle(DEFINE_STRING.SAVE_NEW_WORD, async (event, value) => { - return await func.SaveNewWord(value); + return await func.SaveNewWord(value) }) /** * 监听字幕对齐任务,通过前端传过来的数据 */ ipcMain.handle(DEFINE_STRING.ALIGN_DRAFT_IMG_TO_TEXT, async (event, value) => { - return await func.alginDraftImgToText(value); + return await func.alginDraftImgToText(value) }) /** * 监听单张重绘任务 */ ipcMain.handle(DEFINE_STRING.RE_GENERATE_IAMGE_ONE, async (event, value) => { - console.log(value[0]); - let newWindow = global.newWindow.filter(item => item.id == value[0]); + console.log(value[0]) + let newWindow = global.newWindow.filter((item) => item.id == value[0]) let res = await func.ReGenerateImageOne(newWindow, value) - return res; + return res }) /** - * 监听文件夹高清任务 + * 监听文件夹高清任务 */ ipcMain.handle(DEFINE_STRING.IMPROVE_IMAGE_RESOULTION, async (event, value) => { - let res = await imageGenerate.ImproveResolution(value); - return res; + let res = await imageGenerate.ImproveResolution(value) + return res }) /** @@ -437,10 +457,10 @@ ipcMain.handle(DEFINE_STRING.IMPROVE_IMAGE_RESOULTION, async (event, value) => { */ ipcMain.handle(DEFINE_STRING.REFRASH_IMAGWE_DATA, async (event, value) => { console.log(value) - let newWindow = global.newWindow.filter(item => item.id == value[0]); - let img_dir = path.join(global.config.project_path, "tmp/" + value[1]); - let res = await imageGenerate.getFolderImageList(newWindow, img_dir, true); - return res; + let newWindow = global.newWindow.filter((item) => item.id == value[0]) + let img_dir = path.join(global.config.project_path, 'tmp/' + value[1]) + let res = await imageGenerate.getFolderImageList(newWindow, img_dir, true) + return res }) // 监听打开全局窗口事件 @@ -452,12 +472,12 @@ ipcMain.on(DEFINE_STRING.SHOW_GLOABAL_MESSAGE_DIALOG, (event, value) => { * 监听一个窗口,返回窗口创建成功后需要的基础数据 */ ipcMain.on(DEFINE_STRING.SHOW_NEW_WINDOW, async (event, value) => { - let newW = createWindow(value[0], value[1]); + let newW = createWindow(value[0], value[1]) }) // 监听程序关闭 ipcMain.on(DEFINE_STRING.QUIT_APP, (event) => { - app.quit(); + app.quit() }) /** diff --git a/src/main/setting/autoSync.js b/src/main/setting/autoSync.js index 601315d..a8c1163 100644 --- a/src/main/setting/autoSync.js +++ b/src/main/setting/autoSync.js @@ -1,18 +1,74 @@ -import fs from 'fs'; +import fs from 'fs' import { version } from '../../../package.json' -import { define } from '../../define/define'; -const fspromises = fs.promises; -import { MJSettingService } from '../../define/db/service/SoftWare/mjSettingService'; -import { isEmpty } from 'lodash'; -import { SoftwareService } from '../../define/db/service/SoftWare/softwareService'; +import { define } from '../../define/define' +const fspromises = fs.promises +import { MJSettingService } from '../../define/db/service/SoftWare/mjSettingService' +import _, { isEmpty } from 'lodash' +import { SoftwareService } from '../../define/db/service/SoftWare/softwareService' +import { CheckFileOrDirExist } from '../../define/Tools/file' +import path from 'path' + +//#region 通用同步 + +/** + * 通用的自动初始化配置,所有的配置都在这里面进行初始化 + */ +async function GlobalAutoSync() { + try { + let _softwareService = await SoftwareService.getInstance() + let softData = _softwareService.GetSoftwareData(null) + let initConifgPath + let isExist = await CheckFileOrDirExist(define.config_path) + if (isExist) { + initConifgPath = define.config_path + } else { + initConifgPath = path.join(define.init_config_path, 'global_setting.json') + } + + // 判断是不是有数据,没有的话初始化整个设置数据,有的话判断是不是有全局设置,没有在设置 + if (softData.data.length <= 0) { + // 初始化全局设置 + let initGlobalConfig = await fspromises.readFile(initConifgPath, 'utf-8') + + let addSfotwareRes = _softwareService.AddSfotware({ + theme: 'light', + reverse_display_show: false, + reverse_show_book_striped: false, + reverse_data_table_size: 'samll', + globalSetting: initGlobalConfig + }) + if (addSfotwareRes.code == 1) { + global.logger.info('AutoSunc_GlobalAutoSync', '初始化全局设置成功') + } + } else { + // 有,只需要检查全局设置是不是存在,不存在的话初始化 + let softWareData = softData.data[0] + if (softWareData.globalSetting == null || softWareData.globalSetting == '') { + let initGlobalConfig = await fspromises.readFile(initConifgPath, 'utf-8') + softWareData.globalSetting = initGlobalConfig + let updateSfotwareRes = _softwareService.UpdateSoftware(softWareData) + if (updateSfotwareRes.code == 1) { + global.logger.info('AutoSunc_GlobalAutoSync', '初始化全局设置成功') + } + } + } + } catch (error) { + // 同步数据不报错,只添加日志 + global.logger.error( + 'AutoSunc_GlobalAutoSync', + '初始化全局设置失败,失败信息如下' + '\n' + error.toString() + ) + } +} + +//#endregion //#region 2.2.10 版本 自动同步数据 - /** * 自动同步MJ配置数据 */ -export async function AutoSyncMJConfig2210() { +async function AutoSyncMJConfig2210() { try { // 判断版本 if (version != '2.2.10') { @@ -20,47 +76,53 @@ export async function AutoSyncMJConfig2210() { } // 同步MJ的配置到服务器中 - let mjConfigJson = JSON.parse(await fspromises.readFile(define.img_base, 'utf-8')); + let mjConfigJson = JSON.parse(await fspromises.readFile(define.img_base, 'utf-8')) // 开始同步APIMJsetting - let _mjSettingService = await MJSettingService.getInstance(); + let _mjSettingService = await MJSettingService.getInstance() if (!mjConfigJson.mj_config) { - return; + return } // 判断数据库中有没有API数据 - let dbApiSetting = _mjSettingService.GetAPIMjSetting(null); + let dbApiSetting = _mjSettingService.GetAPIMjSetting(null) if (dbApiSetting.code == 1 && dbApiSetting.data.length <= 0) { // 同步API请求配置 - if (mjConfigJson.mj_config.mj_api_url && + if ( + mjConfigJson.mj_config.mj_api_url && mjConfigJson.mj_config.mj_speed && - mjConfigJson.mj_config.api_key) { + mjConfigJson.mj_config.api_key + ) { let apiSetting = { mjApiUrl: mjConfigJson.mj_config.mj_api_url, mjSpeed: mjConfigJson.mj_config.mj_speed, apiKey: mjConfigJson.mj_config.api_key - }; - let res = _mjSettingService.AddAPIMjSetting(apiSetting); + } + let res = _mjSettingService.AddAPIMjSetting(apiSetting) if (res.code == 1) { - global.logger.info("AutoSyncMJConfig2210", '自动同步MJ API 配置数据成功'); + global.logger.info('AutoSyncMJConfig2210', '自动同步MJ API 配置数据成功') } else { - global.logger.error("AutoSyncMJConfig2210", '自动同步MJ API 配置数据失败,错误信息如下:' + '\n' + res.message); + global.logger.error( + 'AutoSyncMJConfig2210', + '自动同步MJ API 配置数据失败,错误信息如下:' + '\n' + res.message + ) } } } // 判断数据库中是不是存在浏览器配置数据,不存在同步 - let dbBrowserSetting = _mjSettingService.GetBrowserMJSetting(null); + let dbBrowserSetting = _mjSettingService.GetBrowserMJSetting(null) if (dbBrowserSetting.code == 1 && dbBrowserSetting.data.length <= 0) { // 同步浏览器配置,判断数据是不是存在 - if (isEmpty(mjConfigJson.mj_config.serviceID) || + if ( + isEmpty(mjConfigJson.mj_config.serviceID) || isEmpty(mjConfigJson.mj_config.channelID) || isEmpty(mjConfigJson.mj_config.token) || - (!mjConfigJson.mj_config.userAgent || - isEmpty(mjConfigJson.mj_config.userAgent) || - isEmpty(mjConfigJson.mj_config.userAgent.userAgent)) + !mjConfigJson.mj_config.userAgent || + isEmpty(mjConfigJson.mj_config.userAgent) || + isEmpty(mjConfigJson.mj_config.userAgent.userAgent) ) { - return; + return } // 开始添加浏览器配置 @@ -69,26 +131,30 @@ export async function AutoSyncMJConfig2210() { channelId: mjConfigJson.mj_config.channelID, token: mjConfigJson.mj_config.token, userAgent: mjConfigJson.mj_config.userAgent.userAgent - }; - let res = _mjSettingService.AddBrowserMJSetting(browserSetting); + } + let res = _mjSettingService.AddBrowserMJSetting(browserSetting) if (res.code == 1) { - global.logger.info("AutoSyncMJConfig2210", '自动同步MJ 浏览器配置数据成功'); + global.logger.info('AutoSyncMJConfig2210', '自动同步MJ 浏览器配置数据成功') } else { - global.logger.error("AutoSyncMJConfig2210", '自动同步MJ 浏览器配置数据失败,错误信息如下:' + '\n' + res.message); + global.logger.error( + 'AutoSyncMJConfig2210', + '自动同步MJ 浏览器配置数据失败,错误信息如下:' + '\n' + res.message + ) } } // 判断基础数据,不存在同步 - let mjSetting = _mjSettingService.GetMjSetting(null); + let mjSetting = _mjSettingService.GetMjSetting(null) if (mjSetting.code == 1 && mjSetting.data.length <= 0) { //判断数据然后选择同步 - if (isEmpty(mjConfigJson.mj_config.request_model) || + if ( + isEmpty(mjConfigJson.mj_config.request_model) || isEmpty(mjConfigJson.mj_config.select_robot) || isEmpty(mjConfigJson.mj_config.image_scale) || isEmpty(mjConfigJson.mj_config.image_model) || isEmpty(mjConfigJson.mj_config.image_suffix) ) { - return; + return } // 开始添加基础配置 @@ -99,40 +165,52 @@ export async function AutoSyncMJConfig2210() { imageModel: mjConfigJson.mj_config.image_model, imageSuffix: mjConfigJson.mj_config.image_suffix, taskCount: mjConfigJson.mj_config.task_count ? mjConfigJson.mj_config.task_count : 3, - spaceTime: mjConfigJson.mj_config.space_time ? mjConfigJson.mj_config.space_time : 5, - }; - let res = _mjSettingService.AddMJSetting(mjSettingData); + spaceTime: mjConfigJson.mj_config.space_time ? mjConfigJson.mj_config.space_time : 5 + } + let res = _mjSettingService.AddMJSetting(mjSettingData) if (res.code == 1) { - global.logger.info("AutoSyncMJConfig2210", '自动同步MJ 基础配置数据成功'); + global.logger.info('AutoSyncMJConfig2210', '自动同步MJ 基础配置数据成功') } else { - global.logger.error("AutoSyncMJConfig2210", '自动同步MJ 基础配置数据失败,错误信息如下:' + '\n' + res.message); + global.logger.error( + 'AutoSyncMJConfig2210', + '自动同步MJ 基础配置数据失败,错误信息如下:' + '\n' + res.message + ) } } // 初始化默认数据 - let _softwareService = await SoftwareService.getInstance(); - let softs = await _softwareService.GetSoftwareData(null); + let _softwareService = await SoftwareService.getInstance() + let softs = await _softwareService.GetSoftwareData(null) if (softs.code == 1 && softs.data.length <= 0) { // 添加 let softData = { - theme: "light", + theme: 'light', reverse_display_show: false, reverse_show_book_striped: false, - reverse_data_table_size: "samll", + reverse_data_table_size: 'samll' } - let res = await _softwareService.AddSfotware(softData); + let res = await _softwareService.AddSfotware(softData) if (res.code == 0) { throw new Error(res.message) } - global.logger.info("AutoSyncMJConfig2210", '自动同步软件配置数据成功'); - + global.logger.info('AutoSyncMJConfig2210', '自动同步软件配置数据成功') } - return mjConfigJson; + return mjConfigJson } catch (error) { // 同步数据不报错,只添加日志 - global.logger.error("AutoSyncMJConfig2210", '自动同步MJ配置数据失败,错误信息如下:' + '\n' + error.toString()); + global.logger.error( + 'AutoSyncMJConfig2210', + '自动同步MJ配置数据失败,错误信息如下:' + '\n' + error.toString() + ) } } //#endregion + +export async function AutoSync() { + // 2.2.10 版本 自动同步数据 + await AutoSyncMJConfig2210() + // 通用同步 + await GlobalAutoSync() +} diff --git a/src/main/setting/basicSetting.js b/src/main/setting/basicSetting.js index 1d24dd6..b052848 100644 --- a/src/main/setting/basicSetting.js +++ b/src/main/setting/basicSetting.js @@ -1,67 +1,72 @@ -import SoftwareService from "../../define/db/service/SoftWare/softwareService"; -import { ComponentSize } from "../../define/enum/softwareEnum"; -import { errorMessage, successMessage } from "../generalTools"; - +import SoftwareService from '../../define/db/service/SoftWare/softwareService' +import { ComponentSize } from '../../define/enum/softwareEnum' +import { errorMessage, successMessage } from '../generalTools' export class BasicSetting { - constructor() { - } + constructor() {} // 初始化数据 async init() { - this.setting = await SoftwareService.getInstance(); + this.setting = await SoftwareService.getInstance() } /** * 获取修改尺寸的下拉菜单的尺寸 - * @returns + * @returns */ GetComponentSize() { - return successMessage([ - { - value: ComponentSize.TINY, - key: ComponentSize.TINY, - label: "极小" - }, { - key: ComponentSize.SMALL, - value: ComponentSize.SMALL, - label: "小" - }, { - value: ComponentSize.MEDIUM, - key: ComponentSize.MEDIUM, - label: "中" - }, { - value: ComponentSize.LARGE, - key: ComponentSize.LARGE, - label: "大" - } - ], "选择尺寸的下拉菜单消息", 'BasicSetting_GetComponentSize') + return successMessage( + [ + { + value: ComponentSize.TINY, + key: ComponentSize.TINY, + label: '极小' + }, + { + key: ComponentSize.SMALL, + value: ComponentSize.SMALL, + label: '小' + }, + { + value: ComponentSize.MEDIUM, + key: ComponentSize.MEDIUM, + label: '中' + }, + { + value: ComponentSize.LARGE, + key: ComponentSize.LARGE, + label: '大' + } + ], + '选择尺寸的下拉菜单消息', + 'BasicSetting_GetComponentSize' + ) } // 获取基础配置信息 async GetSoftwareSetting() { try { await this.init() - let res = await this.setting.GetSoftwareData(); - return res; + let res = this.setting.GetSoftwareData() + return res } catch (error) { - return errorMessage(error.message, 'BasicSetting_GetSoftwareSetting'); + return errorMessage(error.message, 'BasicSetting_GetSoftwareSetting') } } /** * 修改软件设置的所有的数据,直接修改全部的数据 - * @param {*} paramms - * @returns + * @param {*} paramms + * @returns */ async SaveSoftWareSetting(paramms) { try { await this.init() - let res = await this.setting.UpdateSoftware(paramms); - return res; + let res = this.setting.UpdateSoftware(paramms) + return res } catch (error) { - return errorMessage(error.message, 'BasicSetting_SaveSoftWareSetting'); + return errorMessage(error.message, 'BasicSetting_SaveSoftWareSetting') } } } diff --git a/src/main/setting/ffmpegSetting.js b/src/main/setting/ffmpegSetting.js index 4d36777..8836d72 100644 --- a/src/main/setting/ffmpegSetting.js +++ b/src/main/setting/ffmpegSetting.js @@ -1,6 +1,6 @@ import path from 'path' import os from 'os' -import ffmpeg from 'fluent-ffmpeg' +import Ffmpeg from 'fluent-ffmpeg' import { define } from '../../define/define' export function SetFfmpegPath() { @@ -16,6 +16,6 @@ export function SetFfmpegPath() { default: throw new Error('Unsupported platform: ' + os.platform()) } - ffmpeg.setFfmpegPath(ffmpegPath) - ffmpeg.setFfprobePath(ffprobePath) + Ffmpeg.setFfmpegPath(ffmpegPath) + Ffmpeg.setFfprobePath(ffprobePath) } diff --git a/src/main/setting/gptSetting.js b/src/main/setting/gptSetting.js new file mode 100644 index 0000000..6a1732a --- /dev/null +++ b/src/main/setting/gptSetting.js @@ -0,0 +1,92 @@ +import path from 'path' +import os from 'os' +import { define } from '../../define/define' +import { errorMessage, successMessage } from '../generalTools' +import axios from 'axios' +import { ServiceBase } from '../../define/db/service/serviceBase' + +export class GptSetting extends ServiceBase { + constructor() { + super() + axios.defaults.baseURL = define.serverUrl + } + + /** + * 获取服务强上面的提示词相关的数据 + */ + async InitServerGptOptions() { + try { + // 获取提示词类型数据 + let promptRes = await axios.get('/api/Prompt/GetPromptType/100/1') + let promptType = promptRes.data.data + + // 获取提示词数据 + let prompt = await axios.get('/api/Prompt/GetPromptString/all/100/1') + let promptData = prompt.data.data + + let gptOptions = { + promptType: promptType, + promptData: promptData + } + return successMessage(gptOptions, '请求成功', 'GptSetting_InitServerGptOptions') + } catch (error) { + return errorMessage( + '获取服务端提示词数据错误,错误信息如下:' + error.toString(), + 'GptSetting_InitServerGptOptions' + ) + } + } + + // 获取软件设置里面的AI设置,没有的话设置初始值 + async GetAISetting() { + try { + await this.InitService() + let res = this.softService.GetSoftWarePropertyData('aiSetting') + if (res.data == null || res.data == undefined || res.data == '') { + // 没有数据需要额外初始化 + res.data = { + laiapi: { + gpt_url: 'https://laitool.net/v1/chat/completions', + api_key: undefined, + model: undefined + }, + kimi: { + gpt_url: 'https://api.moonshot.cn/v1/chat/completions', + api_key: undefined, + model: undefined + }, + doubao: { + gpt_url: 'https://ark.cn-beijing.volces.com/api/v3/chat/completions', + api_key: undefined, + model: undefined + } + } + } else { + res.data = JSON.parse(res.data) + } + return res + } catch (error) { + return errorMessage( + '获取软件设置里面的AI设置错误,错误信息如下:' + error.toString(), + 'GptSetting_GetAISetting' + ) + } + } + + /** + * 保存软件中的AI设置 + * @param {*} value + */ + async SaveAISetting(value) { + try { + await this.InitService() + let res = this.softService.SaveSoftwarePropertyData('aiSetting', JSON.stringify(value)) + return res + } catch (error) { + return errorMessage( + '保存软件设置里面的AI设置错误,错误信息如下:' + error.toString(), + 'GptSetting_SaveAISetting' + ) + } + } +} diff --git a/src/main/setting/ttsSetting.js b/src/main/setting/ttsSetting.js new file mode 100644 index 0000000..9aa9e86 --- /dev/null +++ b/src/main/setting/ttsSetting.js @@ -0,0 +1,59 @@ +import { errorMessage } from '../generalTools' +import { SoftwareService } from '../../define/db/service/SoftWare/softwareService' + +export class TTSSetting { + constructor() {} + + /** + * 初始化TTS服务 + */ + async InitService() { + if (!this.softService) { + this.softService = await SoftwareService.getInstance() + } + } + + /** + * 获取TTS配置 + */ + async GetTTSCOnfig() { + try { + await this.InitService() + let res = this.softService.GetSoftWarePropertyData('ttsSetting') + if (res.data == null) { + // 没有数据,需要初始化 + res.data = { + selectModel: 'edge-tts', + edgeTTS: { + value: 'zh-CN-XiaoxiaoNeural', + gender: 'Female', + label: '晓晓', + lang: 'zh-CN', + saveSubtitles: true, + pitch: '0', // 语调 + rate: '10', // 倍速 + volumn: '0' // 音量 + } + } + } else { + res.data = JSON.parse(res.data) + } + return res + } catch (error) { + return errorMessage('获取TTS配置失败,错误信息如下:' + error.toString(), 'TTS_GetTTSCOnfig') + } + } + /** + * 保存TTS配置 + * @param {*} data 要保存的数据 + */ + async SaveTTSConfig(data) { + try { + await this.InitService() + let res = this.softService.SaveSoftwarePropertyData('ttsSetting', JSON.stringify(data)) + return res + } catch (error) { + return errorMessage('保存TTS配置失败,错误信息如下:' + error.toString(), 'TTS_SaveTTSConfig') + } + } +} diff --git a/src/main/setting/writeSetting.js b/src/main/setting/writeSetting.js new file mode 100644 index 0000000..b15a4ee --- /dev/null +++ b/src/main/setting/writeSetting.js @@ -0,0 +1,62 @@ +import { errorMessage } from '../generalTools' +import { SoftwareService } from '../../define/db/service/SoftWare/softwareService' + +/** + * 文案的设置相关的类 + */ +export class WritingSetting { + constructor() {} + + async InitService() { + if (!this.softService) { + this.softService = await SoftwareService.getInstance() + } + } + + /** + * 获取文案的相关配置 + */ + async GetWritingConfig() { + try { + await this.InitService() + let res = this.softService.GetSoftWarePropertyData('writeSetting') + if (res.data == null) { + res.data = JSON.stringify({ + split_char: '。,“”‘’!?【】《》()…—:;.,\'\'""!?[]<>()...-:;', + mrege_count: 3, + mrege_char: ',', + end_char: '。', + merge_count: 4, + merge_char: ',', + isRandom: true, + randomCount: 0 + }) + } else { + res.data = JSON.parse(res.data) + } + return res + } catch (error) { + return errorMessage( + '获取文案配置失败,错误信息如下:' + error.toString(), + 'WritingSetting_GetWritingConfig' + ) + } + } + + /** + * 保存文案的相关配置 + * @param {*} data 要保存的数据 + */ + async SaveWriteConfig(data) { + try { + await this.InitService() + let res = this.softService.SaveSoftwarePropertyData('writeSetting', JSON.stringify(data)) + return res + } catch (error) { + return errorMessage( + '保存文案设置失败,失败信息如下:' + error.toString(), + 'WritingSetting_SaveWriteConfig' + ) + } + } +} diff --git a/src/preload/book.js b/src/preload/book.js index 1421d77..a6f0f77 100644 --- a/src/preload/book.js +++ b/src/preload/book.js @@ -1,31 +1,46 @@ -import { ipcRenderer } from "electron" -import { DEFINE_STRING } from "../define/define_string" - +import { ipcRenderer } from 'electron' +import { DEFINE_STRING } from '../define/define_string' const book = { // 获取小说操作类型(原创/SD反推/MJ反推) GetBookType: async () => await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_BOOK_TYPE), // 新增或者是修改小说数据 - AddOrModifyBook: async (book) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_OR_MODIFY_BOOK, book), - + AddOrModifyBook: async (book) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_OR_MODIFY_BOOK, book), //#region 一键反推 // 获取小说数据(通过传递的参数进行筛选) - GetBookData: async (bookQuery) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_BOOK_DATA, bookQuery), + GetBookData: async (bookQuery) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_BOOK_DATA, bookQuery), // 获取小说的任务列表(批次) - GetBookTaskData: async (bookTaskCondition) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_BOOK_TASK_DATA, bookTaskCondition), + GetBookTaskData: async (bookTaskCondition) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_BOOK_TASK_DATA, bookTaskCondition), // 获取小说的分镜 - GetFrameData: async (bookId) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_FRAME_DATA, bookId), + GetFrameData: async (bookId) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_FRAME_DATA, bookId), // 一键全自动执行 AutoAction: async (bookId) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.AUTO_ACTION, bookId), - //#endregion + // 保存一键反推文案位置 + SaveBookSubtitlePosition: async (value) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.SAVE_BOOK_SUBTITLE_POSITION, value), + // 打开字幕提示图片文件夹 + OpenBookSubtitlePositionScreenshot: async (value) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT, value), + + // 提取当前帧的文字信息 + GetCurrentFrameText: async (value) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_CURRENT_FRAME_TEXT, value), + + // 获取当前中的视频所有的字幕 + GetVideoFrameText: async (value) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_VIDEO_FRAME_TEXT, value) + + //#endregion } -export { - book -} \ No newline at end of file +export { book } diff --git a/src/preload/gpt.js b/src/preload/gpt.js new file mode 100644 index 0000000..4c46595 --- /dev/null +++ b/src/preload/gpt.js @@ -0,0 +1,24 @@ +import { ipcRenderer } from 'electron' +import { DEFINE_STRING } from '../define/define_string' + +const gpt = { + // 获取默认或者是自定义的GPT服务商 + InitServerGptOptions: async () => { + return await ipcRenderer.invoke(DEFINE_STRING.GPT.INIT_SERVER_GPT_OPTIONS) + }, + + + //#region GPT 设置相关 + // 获取软件设置里面的GPT设置 + GetAISetting: async () => { + return await ipcRenderer.invoke(DEFINE_STRING.GPT.GET_AI_SETTING) + }, + + // 保存软件设置里面的GPT设置 + SaveAISetting: async (data) => { + return await ipcRenderer.invoke(DEFINE_STRING.GPT.SAVE_AI_SETTING, data) + } + + //#endregion +} +export { gpt } diff --git a/src/preload/index.js b/src/preload/index.js index a47ddaf..a3a46f1 100644 --- a/src/preload/index.js +++ b/src/preload/index.js @@ -1,244 +1,254 @@ import { contextBridge, ipcRenderer } from 'electron' import { electronAPI } from '@electron-toolkit/preload' -import { DEFINE_STRING } from '../define/define_string.js'; -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'; +import { DEFINE_STRING } from '../define/define_string.js' +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' import { setting } from './setting.js' -import { prompt } from './prompt.js'; +import { prompt } from './prompt.js' import { book } from './book.js' +import { tts } from './tts.js' +import { write } from './write.js' +import { gpt } from './gpt.js' // Custom APIs for renderer -let events = []; +let events = [] const api = { //#region 基础状态 // 获取版本信息 GetVersion: async (callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_VERSION)), //#endregion // 保存通用设置 - ModifySampleSetting: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.MODIFY_SAMPLE_SETTING, value)), + ModifySampleSetting: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.MODIFY_SAMPLE_SETTING, value)), //保存SD配置 SaveSDConfig: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_SD_CONFIG, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_SD_CONFIG, value) + callback(res) }, // 保存生成时间的普通设置 SaveGeneralSetting: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_GENERAL_SETTING, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_GENERAL_SETTING, value) + callback(res) }, - // 获取视频的配置信息设置 + // 获取视频的配置信息设置 GetVideoConfigMessage: async (callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GET_VIDEO_CONFIG_MESSAGE); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GET_VIDEO_CONFIG_MESSAGE) + callback(res) }, - // 加载SD设置 + // 加载SD设置 InitSDConfig: async (callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.INIT_SD_CONFIG); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.INIT_SD_CONFIG) + callback(res) }, // 保存任务列表信息 AddImageTask: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.ADD_IMAGE_TASK_LIST, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.ADD_IMAGE_TASK_LIST, value) + callback(res) }, // 修改任务队列信息(修改的数据是一个数组。可以添加多个和单个) - ModifyImageTaskList: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.MODIFY_IMAGE_TASK_LIST, value)), + ModifyImageTaskList: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.MODIFY_IMAGE_TASK_LIST, value)), // 开始自动话任务 - ActionAutoVideoTask: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.ACTION_AUTO_VIDEO_TASK, value)), + ActionAutoVideoTask: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.ACTION_AUTO_VIDEO_TASK, value)), // 获取生成图片的队列任务 GetGenerateTaskList: async (callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GET_GENERATE_TASK_LIST); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GET_GENERATE_TASK_LIST) + callback(res) }, // 删除生成图片列表的消息 DeleteImageTaskList: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.DELETE_IMAGE_TASK_LIST, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.DELETE_IMAGE_TASK_LIST, value) + callback(res) }, // 分镜语音识别消息 StartStoryboarding: async (value) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.START_STORY_BOARDING, value); + let res = await ipcRenderer.invoke(DEFINE_STRING.START_STORY_BOARDING, value) }, // 获取设置的初始数据 - getSettingDafultData: async (callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_SETTING_Dafault_DATA)), + getSettingDafultData: async (callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_SETTING_Dafault_DATA)), // 获取草稿地址 getDraftFileList: async (callback) => { let res = await ipcRenderer.invoke(DEFINE_STRING.GET_DRAFT_FILE_LIST) - callback(res); + callback(res) }, // 选择文件夹 - selectFolder: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.SELECT_FOLDER, value)), + selectFolder: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.SELECT_FOLDER, value)), // 选择指定拓展名的文件 - SelectFile: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.SELECT_FILE, value)), + SelectFile: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.SELECT_FILE, value)), getDraftTextStyle: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GET_DRAFT_TEXT_STYLE, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GET_DRAFT_TEXT_STYLE, value) + callback(res) }, getTextStyleList: async (callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GET_TEXT_STYLE_LIST); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GET_TEXT_STYLE_LIST) + callback(res) }, deleteDraftTextStyle: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.DELETE_DRAFT_TEXT_STYLE, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.DELETE_DRAFT_TEXT_STYLE, value) + callback(res) }, deleteClipSetting: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.DELETE_CLIP_SETTING, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.DELETE_CLIP_SETTING, value) + callback(res) }, deleteClipStettingFriendlyReminder: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.DELETE_FRIENDLY_REMINDER, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.DELETE_FRIENDLY_REMINDER, value) + callback(res) }, // 添加草稿 addDraft: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.ADD_DRAFT, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.ADD_DRAFT, value) + callback(res) }, // 自动合成视频 AutoGenerateVideo: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.AUTO_GENERATION_VIDEO, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.AUTO_GENERATION_VIDEO, value) + callback(res) }, // 获取项目地址项目的所有的输出文件地址 getSubFolderList: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GET_SUBFOLDER_LIST, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GET_SUBFOLDER_LIST, value) + callback(res) }, // 提取草稿的温馨提示内容 GetFriendlyReminder: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GET_FRIENDLY_REMINDER_DRAFT, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GET_FRIENDLY_REMINDER_DRAFT, value) + callback(res) }, // 获取温馨提示列表 getFriendlyReminderList: async (callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GET_FRIENDLY_REMINDER_LIST); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GET_FRIENDLY_REMINDER_LIST) + callback(res) }, // 获取剪映背景音乐配置 - GetBackgroundMusicConfigList: async (callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_BACKGROUND_MUSIC_CONFIG_LIST)), + GetBackgroundMusicConfigList: async (callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_BACKGROUND_MUSIC_CONFIG_LIST)), // 添加背景音乐文件夹 AddBackgroundMusicFolder: async (value, callback) => { let res = await ipcRenderer.invoke(DEFINE_STRING.ADD_BACKGROUND_MUSIC_FOLDER, value) - callback(res); + callback(res) }, // 反写json文件的数据 ModifyInpurCropJson: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.MODIFY_INPUT_CROP_JSON, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.MODIFY_INPUT_CROP_JSON, value) + callback(res) }, // AI 洗稿 AIModifyOneWord: async (value, callback) => { let res = await ipcRenderer.invoke(DEFINE_STRING.AIMODIFY_ONE_WORD, value) - callback(res); + callback(res) }, // 导入字幕。对齐显示 ImportSrtAndGetTime: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.IMPORT_SRT_AND_GET_TIME, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.IMPORT_SRT_AND_GET_TIME, value) + callback(res) }, // 获取本地安装的字体 GetSystemInstallFontName: async (callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GET_SYSTEM_INSTALL_FONTNAME); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GET_SYSTEM_INSTALL_FONTNAME) + callback(res) }, // 保存字幕设置 SaveAssConfig: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_ASS_CONFIG, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_ASS_CONFIG, value) + callback(res) }, // 删除字幕设置 DeleteVideoConfig: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.DELETE_VIDEO_CONFIG, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.DELETE_VIDEO_CONFIG, value) + callback(res) }, // 保存洗稿后的文案 SaveNewWord: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_NEW_WORD, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_NEW_WORD, value) + callback(res) }, // 监听分镜的时间信息 SaveCopywritingInformation: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_COPYWRITING_INFOMATION, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_COPYWRITING_INFOMATION, value) + callback(res) }, // 获取不想要的提示词 GetBadPrompt: async (callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GET_BAD_PROMPT); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GET_BAD_PROMPT) + callback(res) }, /** * 保存不想要的提示词 */ SaveBadPrompt: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_BAD_PROMPT, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_BAD_PROMPT, value) + callback(res) }, /** * 一键删除不想要的值 */ DeleteBadPrompt: async (callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.DELETE_BAD_PROMPT); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.DELETE_BAD_PROMPT) + callback(res) }, // 一次反推 GenerateImageInSelectTask: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GENERATE_IMAGWE_IN_SELECT_TASK, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GENERATE_IMAGWE_IN_SELECT_TASK, value) + callback(res) }, // 显示新的窗口 showNewWindow: (value) => { - ipcRenderer.send(DEFINE_STRING.SHOW_NEW_WINDOW, value); + ipcRenderer.send(DEFINE_STRING.SHOW_NEW_WINDOW, value) }, // 获取机械码 GetMachineId: async (callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GET_MACHINE_ID); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GET_MACHINE_ID) + callback(res) }, // 获取ADetalier配置列表 GetADetailerList: async (callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GET_ADETAILER_LIST); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GET_ADETAILER_LIST) + callback(res) }, // 保存 ADetailer 数据 SaveADetailerConfig: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_DETAILER_CONFIG, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.SAVE_DETAILER_CONFIG, value) + callback(res) }, // 抽帧 getFrame: async (value, callback) => { let res = await ipcRenderer.invoke(DEFINE_STRING.GET_FRAME, value) - callback(res); + callback(res) }, //反推提示词 @@ -250,18 +260,18 @@ const api = { // 打开指定的url,在默认浏览器中 OpenUrl: (value) => ipcRenderer.send(DEFINE_STRING.OPEN_URL, value), - // 生成json文件 + // 生成json文件 AddWebuiJson: async (callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.ADD_WEBUI_JSON); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.ADD_WEBUI_JSON) + callback(res) }, ReGenerateImage: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.RE_GENERATE_IAMGE_ONE, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.RE_GENERATE_IAMGE_ONE, value) + callback(res) }, ImproveResolution: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.IMPROVE_IMAGE_RESOULTION, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.IMPROVE_IMAGE_RESOULTION, value) + callback(res) }, alginDraftImg: (value, callback) => { ipcRenderer.send(DEFINE_STRING.ALIGN_DRAFT_IMG, value) @@ -275,106 +285,144 @@ const api = { }, alginDraftImgToText: async (value, callback) => { let res = await ipcRenderer.invoke(DEFINE_STRING.ALIGN_DRAFT_IMG_TO_TEXT, value) - callback(res); + callback(res) }, getImagePromptList: async (callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GET_IAMGE_PROMPT_LIST); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GET_IAMGE_PROMPT_LIST) + callback(res) }, RefreshImageData: async (value, callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.REFRASH_IMAGWE_DATA, value); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.REFRASH_IMAGWE_DATA, value) + callback(res) }, GetProjectWord: async (callback) => { - let res = await ipcRenderer.invoke(DEFINE_STRING.GET_PROJECT_WORD); - callback(res); + let res = await ipcRenderer.invoke(DEFINE_STRING.GET_PROJECT_WORD) + callback(res) }, // 获取当前的主页使用界面信息 - GetShowMessage: async (callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_SHOW_MESSAGE)), + GetShowMessage: async (callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_SHOW_MESSAGE)), // 立即返回的翻译任务 - TranslateReturnNow: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.TRANSLATE_RETURN_NOW, value)), + TranslateReturnNow: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.TRANSLATE_RETURN_NOW, value)), // 添加翻译添加翻译任务到队列中 - TranslatePrompt: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.TRANSLATE_PROMPT, value)), + TranslatePrompt: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.TRANSLATE_PROMPT, value)), // 添加事件监听 setEventListen: (value, callback) => { ipcRenderer.on(value[0], (event, value) => { - callback(value); + callback(value) }) }, // 打开discord窗口 - OpenDiscordWindow: async (callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.MAIN.OPEN_DISCORD_WINDOW)), + OpenDiscordWindow: async (callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.MAIN.OPEN_DISCORD_WINDOW)), // 获取设置的GPT提示词的咒语 - GetCustomizeGptPrompt: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_CUSTOMIZE_GPT_PROMPT, value)), + GetCustomizeGptPrompt: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_CUSTOMIZE_GPT_PROMPT, value)), // 生成GPT自定义提示词的案例输出 - GenerateGptExampleOut: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GENERATE_GPT_EXAMPLE_OUT, value)), + GenerateGptExampleOut: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GENERATE_GPT_EXAMPLE_OUT, value)), //GetPermission 获取机器的权限 - GetPermission: async (callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_PERMISSION)), + GetPermission: async (callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_PERMISSION)), // 保存图片到指定的文件夹 - SaveImageToOtherFolder: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_IMAGE_TO_OTHER_FOLDER, value)), + SaveImageToOtherFolder: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_IMAGE_TO_OTHER_FOLDER, value)), // 获取当前的自动保存图片的设置 - GetImageAutoSaveSetting: async (callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_IMAGE_AUTO_SAVE_SETTING)), + GetImageAutoSaveSetting: async (callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_IMAGE_AUTO_SAVE_SETTING)), // 保存图片自动保存设置 - SaveImageAutoSaveSetting: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_IMAGE_AUTO_SAVE_SETTING, value)), + SaveImageAutoSaveSetting: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_IMAGE_AUTO_SAVE_SETTING, value)), // 获取当前自动保存图片的方式 - GetAutoSaveImageClassifyOptions: async (callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_AUTO_SAVE_IMAGE_CLASSIFY_OPTIONS)), + GetAutoSaveImageClassifyOptions: async (callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_AUTO_SAVE_IMAGE_CLASSIFY_OPTIONS)), // 修改生图任务状态 - ModifyGenerateTaskStatus: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.MODIFY_GENERATE_TASK_STATUS, value)), + ModifyGenerateTaskStatus: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.MODIFY_GENERATE_TASK_STATUS, value)), // 删除指定的后台任务 - DeleteBackTask: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.DELETE_BACK_TASK, value)), + DeleteBackTask: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.DELETE_BACK_TASK, value)), // 保存生成视频的基础配置(srt地址,配音,背景音乐文件夹等) - SaveVideoSrtAndAudioMessage: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_VIDEO_SRT_AND_AUDIO_MESSAGE, value)), + SaveVideoSrtAndAudioMessage: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_VIDEO_SRT_AND_AUDIO_MESSAGE, value)), // 保存关键帧配置 - SaveKeyFrameSetting: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_KEY_FRAME_SETTING, value)), + SaveKeyFrameSetting: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_KEY_FRAME_SETTING, value)), // 获取关键帧打帧方式列表 - GetKeyFrameOptions: async (callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_KEYFRAME_OPTIONS)), + GetKeyFrameOptions: async (callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_KEYFRAME_OPTIONS)), // 获取关键帧的配置文件 - GetKeyFrameConfigData: async (callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_KEY_FRAME_CONFIG_DATA)), + GetKeyFrameConfigData: async (callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_KEY_FRAME_CONFIG_DATA)), // 保存文案数组到指定的文件 - saveWordTxt: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_WORD_TXT, value)), + saveWordTxt: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_WORD_TXT, value)), // 判断该当前的是否可以链接成功 - TestGPTConnection: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.TEST_GPT_CONNECTION, value)), + TestGPTConnection: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.TEST_GPT_CONNECTION, value)), // 删除自定义的GPT服务商设置 - DeleteDynamicGptOption: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.DELETE_DYNAMIC_GPT_OPTION, value)), + DeleteDynamicGptOption: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.DELETE_DYNAMIC_GPT_OPTION, value)), // 保存自定义的GPT服务商设置 - SaveDynamicGptOption: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_DYNAMIC_GPT_OPTION, value)), + SaveDynamicGptOption: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_DYNAMIC_GPT_OPTION, value)), // 获取当前默认的推理模式或者是自定义的推理模式 - getGptAutoInferenceOptions: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_GPT_AUTO_INFERENCE_OPTIONS, value)), + getGptAutoInferenceOptions: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_GPT_AUTO_INFERENCE_OPTIONS, value)), // 获取当前的GPT和自定义的GPT模型 - getGptModelOption: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_GPT_MODEL_OPTION, value)), + getGptModelOption: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_GPT_MODEL_OPTION, value)), // 获取默认的GPT和自定义的GPT - getGptBusinessOption: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_GPT_BUSINESS_OPTION, value)), + getGptBusinessOption: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_GPT_BUSINESS_OPTION, value)), // 获取指定的风格菜单 - GetImageStyleMenu: async (callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_IMAGE_STYLE_MENU)), + GetImageStyleMenu: async (callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_IMAGE_STYLE_MENU)), // 获取指定的风格ID对应的数据 - GetImageStyleInfomation: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_IMAGE_STYLE_INFOMATION, value)), + GetImageStyleInfomation: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_IMAGE_STYLE_INFOMATION, value)), // 获取指定的图片风格下面的图片数据 - getStyleImageSubList: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_STYLE_IMAGE_SUB_LIST, value)), + getStyleImageSubList: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_STYLE_IMAGE_SUB_LIST, value)), // SD 原创生成单个图片 - OriginalSDImageGenerate: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.ORIGINAL_SD_SINGLE_IMAGE_GENERATE, value)), + OriginalSDImageGenerate: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.ORIGINAL_SD_SINGLE_IMAGE_GENERATE, value)), // 自动保存数据到json文件事件 - AutoSaveDataJson: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.AUTO_SAVE_DATA_JSON, value)), + AutoSaveDataJson: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.AUTO_SAVE_DATA_JSON, value)), // GPT推理 - GPTPrompt: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GPT_PROMPT, value)), + GPTPrompt: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GPT_PROMPT, value)), // 获取 prompt 的json配置文件 - GetPromptJson: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_PROMPT_JSON, value)), + GetPromptJson: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_PROMPT_JSON, value)), // SD原创添加配置文件 - OriginalAddWebuiJson: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.ORIGINAL_ADD_WEBUI_JSON, value)), + OriginalAddWebuiJson: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.ORIGINAL_ADD_WEBUI_JSON, value)), //获取文件的配置文件 - GetConfigJson: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_CONFIG_JSON, value)), + GetConfigJson: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_CONFIG_JSON, value)), // 自动分析人物事件 - AutoAnalyzeCharacter: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.AUTO_ANALYZE_CHARACTER, value)), + AutoAnalyzeCharacter: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.AUTO_ANALYZE_CHARACTER, value)), // 获取生成视频的基础设置 - GetVideoGenerateConfig: async (callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_VIDEO_GENERATE_CONFIG)), + GetVideoGenerateConfig: async (callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_VIDEO_GENERATE_CONFIG)), // 移除事件 removeEventListen: (eventName) => ipcRenderer.removeAllListeners(eventName), // 一键自动化的条件检测 - AutoConditionCheck: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.AUTO_CONDITION_CHECK, value)), + AutoConditionCheck: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.AUTO_CONDITION_CHECK, value)), // 下载文件到指定的文件夹 - DownloadImageFile: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.DOWNLOAD_IMAGE_FILE, value)), + DownloadImageFile: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.DOWNLOAD_IMAGE_FILE, value)), // 检查机器码是不是存在 - CheckMachineId: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.CHECK_MACHINE_ID, value)), + CheckMachineId: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.CHECK_MACHINE_ID, value)), // 保存试用结束 SaveTrialEndTime: (value) => ipcRenderer.send(DEFINE_STRING.SAVE_TRIAL_END_TIME, value), // 打开购买GPT地址 @@ -382,63 +430,73 @@ const api = { // 退出软件 QuitApp: () => ipcRenderer.send(DEFINE_STRING.QUIT_APP), // 获取当前的生图方式,包含sd,mj,d3等 - GetImageGenerateCategory: async (callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_IMAGE_GENERATE_CATEGORY)), + GetImageGenerateCategory: async (callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_IMAGE_GENERATE_CATEGORY)), // 获取指定的配置文件里面指定的属性的数据 - GetDefineConfigJsonByProperty: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_DEFINE_CONFIG_JSON_BY_PROPERTY, value)), + GetDefineConfigJsonByProperty: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_DEFINE_CONFIG_JSON_BY_PROPERTY, value)), // 保存指定的配置文件里指定的属性的数据 - SaveDefineConfigJsonByProperty: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_DEFINE_CONFIG_JSON_BY_PROPERTY, value)), + SaveDefineConfigJsonByProperty: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.SAVE_DEFINE_CONFIG_JSON_BY_PROPERTY, value)), // 打开全局提示框 - showGlobalMessageDialog: (value) => ipcRenderer.send(DEFINE_STRING.SHOW_GLOABAL_MESSAGE_DIALOG, value), + showGlobalMessageDialog: (value) => + ipcRenderer.send(DEFINE_STRING.SHOW_GLOABAL_MESSAGE_DIALOG, value), // 打开全局通知框 - showGlobalNotificationDialog: (value) => ipcRenderer.send(DEFINE_STRING.SHOW_MAIN_NOTIFICATION, value), + showGlobalNotificationDialog: (value) => + ipcRenderer.send(DEFINE_STRING.SHOW_MAIN_NOTIFICATION, value), // 打开全局的消息框 showGlobalMessage: (value) => ipcRenderer.send(DEFINE_STRING.SHOW_GLOABAL_MESSAGE, value), // 知道文件地址,获取文件base64编码 - GetFileBase64: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_FILE_BASE64, value)), + GetFileBase64: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.GET_FILE_BASE64, value)), // 打开调试控制台 OpenDevTools: () => ipcRenderer.send(DEFINE_STRING.OPEN_DEV_TOOLS), // 打开调试控制台密码 - OpenDevToolsPassword: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.OPEN_DEV_TOOLS_PASSWORD, value)), - + OpenDevToolsPassword: async (value, callback) => + callback(await ipcRenderer.invoke(DEFINE_STRING.OPEN_DEV_TOOLS_PASSWORD, value)) } if (process.contextIsolated) { try { - contextBridge.exposeInMainWorld('electron', electronAPI) contextBridge.exposeInMainWorld('api', api) contextBridge.exposeInMainWorld('mj', mj) contextBridge.exposeInMainWorld('discord', discord) - contextBridge.exposeInMainWorld("sd", sd) - contextBridge.exposeInMainWorld("img", img) - contextBridge.exposeInMainWorld("system", system) - contextBridge.exposeInMainWorld("setting", setting) - contextBridge.exposeInMainWorld("pmpt", prompt) - contextBridge.exposeInMainWorld("book", book) + contextBridge.exposeInMainWorld('sd', sd) + contextBridge.exposeInMainWorld('img', img) + contextBridge.exposeInMainWorld('system', system) + contextBridge.exposeInMainWorld('setting', setting) + contextBridge.exposeInMainWorld('pmpt', prompt) + contextBridge.exposeInMainWorld('book', book) + contextBridge.exposeInMainWorld('tts', tts) + contextBridge.exposeInMainWorld('write', write) + contextBridge.exposeInMainWorld('gpt', gpt) contextBridge.exposeInMainWorld('darkMode', { - toggle: (value) => ipcRenderer.invoke('dark-mode:toggle', value), + toggle: (value) => ipcRenderer.invoke('dark-mode:toggle', value) }) } catch (error) { console.error(error) } } else { window.electron = electronAPI - window.api = api; - window.mj = mj; - window.discord = discord; - window.sd = sd; - window.img = img; - window.system = system; - window.setting = setting; - window.pmpt = prompt; - window.book = book; + window.api = api + window.mj = mj + window.discord = discord + window.sd = sd + window.img = img + window.system = system + window.setting = setting + window.pmpt = prompt + window.book = book + window.tts = tts + window.write = write + window.gpt = gpt } - diff --git a/src/preload/tts.js b/src/preload/tts.js new file mode 100644 index 0000000..9e88d67 --- /dev/null +++ b/src/preload/tts.js @@ -0,0 +1,11 @@ +import { ipcRenderer } from 'electron' +import { DEFINE_STRING } from '../define/define_string' + +const tts = { + // 获取当前的TTS配置数据 + GetTTSCOnfig: async () => await ipcRenderer.invoke(DEFINE_STRING.TTS.GET_TTS_CONFIG), + + // 保存TTS配置 + SaveTTSConfig: async (data) => await ipcRenderer.invoke(DEFINE_STRING.TTS.SAVE_TTS_CONFIG, data) +} +export { tts } diff --git a/src/preload/write.js b/src/preload/write.js new file mode 100644 index 0000000..974f0fa --- /dev/null +++ b/src/preload/write.js @@ -0,0 +1,25 @@ +import { ipcRenderer } from 'electron' +import { DEFINE_STRING } from '../define/define_string' + +const write = { + //#region 配置相关 + + // 获取当前的TTS配置数据 + GetWriteCOnfig: async () => await ipcRenderer.invoke(DEFINE_STRING.WRITE.GET_WRITE_CONFIG), + + // 保存TTS配置 + SaveWriteConfig: async (data) => + await ipcRenderer.invoke(DEFINE_STRING.WRITE.SAVE_WRITE_CONFIG, data), + + //#endregion + + //#region AI相关的任务 + + // 开始执行API相关的一系列任务 + ActionStart(aiSetting, word) { + return ipcRenderer.invoke(DEFINE_STRING.WRITE.ACTION_START, aiSetting, word) + } + + //#endregion +} +export { write } diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue index 110cea4..2d9acdd 100644 --- a/src/renderer/src/App.vue +++ b/src/renderer/src/App.vue @@ -6,7 +6,10 @@ - + + + + @@ -22,9 +25,11 @@ import { NDialogProvider, NConfigProvider, darkTheme, - NNotificationProvider + NNotificationProvider, + NSpin } from 'naive-ui' import { useSoftwareStore } from '../../stores/software' +import { SoftColor } from '../../define/enum/softwareEnum' hljs.registerLanguage('javascript', javascript) export default defineComponent({ @@ -32,12 +37,14 @@ export default defineComponent({ NConfigProvider, NDialogProvider, NMessageProvider, - NNotificationProvider + NNotificationProvider, + NSpin }, setup() { let softwareStore = useSoftwareStore() onMounted(async () => { + softwareStore.SoftColor = SoftColor window.api.getSettingDafultData(async (value) => { await window.darkMode.toggle(value.theme) }) diff --git a/src/renderer/src/components/CopyWriting/CopyWriting.vue b/src/renderer/src/components/CopyWriting/CopyWriting.vue new file mode 100644 index 0000000..3616f12 --- /dev/null +++ b/src/renderer/src/components/CopyWriting/CopyWriting.vue @@ -0,0 +1,194 @@ + + + diff --git a/src/renderer/src/components/CopyWriting/ManageAISetting.vue b/src/renderer/src/components/CopyWriting/ManageAISetting.vue new file mode 100644 index 0000000..74cdfd0 --- /dev/null +++ b/src/renderer/src/components/CopyWriting/ManageAISetting.vue @@ -0,0 +1,135 @@ + + + diff --git a/src/renderer/src/components/Home/Home.vue b/src/renderer/src/components/Home/Home.vue index 20fc81a..a87bd8c 100644 --- a/src/renderer/src/components/Home/Home.vue +++ b/src/renderer/src/components/Home/Home.vue @@ -50,7 +50,9 @@ import { PaperPlaneOutline, SettingsOutline, DuplicateOutline, - GridOutline + GridOutline, + RadioOutline, + BookOutline } from '@vicons/ionicons5' import CheckMachineId from '../Components/CheckMachineId.vue' import axios from 'axios' @@ -85,10 +87,12 @@ export default defineComponent({ if (option.key === 'food') return null if (option.key == 'sdoriginal') return h(NIcon, null, { default: () => h(PaperPlaneOutline) }) if (option.key == 'setting') return h(NIcon, null, { default: () => h(SettingsOutline) }) + if (option.key == 'gptCopywriting') return h(NIcon, null, { default: () => h(BookOutline) }) if (option.key == 'reverse_management') return h(NIcon, null, { default: () => h(GridOutline) }) if (option.key == 'backward_matrix') return h(NIcon, null, { default: () => h(DuplicateOutline) }) + if (option.key == 'TTS_Services') return h(NIcon, null, { default: () => h(RadioOutline) }) } onMounted(async () => { @@ -230,6 +234,21 @@ export default defineComponent({ } const menuOptions = [ + { + label: () => + h( + RouterLink, + { + to: { + name: 'gptCopywriting' + } + }, + { + default: () => '文案处理' + } + ), + key: 'gptCopywriting' + }, { label: () => h( @@ -434,6 +453,36 @@ export default defineComponent({ } ] } + // { + // label: () => + // h( + // RouterLink, + // { + // to: { + // name: 'test_options' + // } + // }, + // { + // default: () => '测试操作' + // } + // ), + // key: 'test_options' + // }, + // { + // label: () => + // h( + // RouterLink, + // { + // to: { + // name: 'TTS_Services' + // } + // }, + // { + // default: () => '语音服务' + // } + // ), + // key: 'TTS_Services' + // } ] return { diff --git a/src/renderer/src/components/Original/MenuButton.vue b/src/renderer/src/components/Original/MenuButton.vue index e7df1db..4528abd 100644 --- a/src/renderer/src/components/Original/MenuButton.vue +++ b/src/renderer/src/components/Original/MenuButton.vue @@ -4,6 +4,9 @@ 角色分析(标签集) + 导入提示词 一键推理提示词 { + if (value.code == 0) { + message.error(value.message) + return + } + debugger + console.log(value.value) + // 拿到提示词文件地址,然后还是获取 + await window.pmpt.OpenPromptFileTxt(value.value, (res) => { + debugger + console.log(res) + if (res.code == 0) { + message.error(res.message) + return + } + // 修改数据 + // 判断获取的数据是不是小于当前的data的行 + if (res.data.length > data.value.length) { + dialog.warning({ + title: '提示', + content: '导入的数据行数大于当前的数据行数,多余的数据会被删除,是否继续导入?', + positiveText: '继续', + negativeText: '取消', + onPositiveClick: async () => { + debugger + // 开始导入 + for (let i = 0; i < data.value.length && i < res.data.length; i++) { + const element = res.data[i] + // 开始修改 + data.value[i].gpt_prompt = element + } + } + }) + } else { + // 开始导入 + for (let i = 0; i < data.value.length && i < res.data.length; i++) { + const element = res.data[i] + // 开始修改 + data.value[i].gpt_prompt = element + } + } + }) + }) + } + return { ImportWord, data, GetCharacter, tagTreeData, + ImportPrompt, GPTPrompt, OpenPromptSetting, GenerateImageAll, diff --git a/src/renderer/src/components/ReverseManage/Components/ManageBookDetailButton.vue b/src/renderer/src/components/ReverseManage/Components/ManageBookDetailButton.vue index 5d31469..049eb90 100644 --- a/src/renderer/src/components/ReverseManage/Components/ManageBookDetailButton.vue +++ b/src/renderer/src/components/ReverseManage/Components/ManageBookDetailButton.vue @@ -34,7 +34,7 @@ export default defineComponent({ // 自动开始 async function AutoAction() { // 开始自动执行。先分镜,后面在添加任务中 - let res_frame = await window.book.GetFrameData(reverseManageStore.selectBook.id) + let res_frame = await window.book.AutoAction(reverseManageStore.selectBook.id) } // 选择自动开始的操作(停止或者是继续) diff --git a/src/renderer/src/components/TTS/AzureTTS.vue b/src/renderer/src/components/TTS/AzureTTS.vue new file mode 100644 index 0000000..c1870bc --- /dev/null +++ b/src/renderer/src/components/TTS/AzureTTS.vue @@ -0,0 +1,18 @@ + + + diff --git a/src/renderer/src/components/TTS/EdgeTTS.vue b/src/renderer/src/components/TTS/EdgeTTS.vue new file mode 100644 index 0000000..59995a0 --- /dev/null +++ b/src/renderer/src/components/TTS/EdgeTTS.vue @@ -0,0 +1,79 @@ + + + diff --git a/src/renderer/src/components/TTS/TTSHome.vue b/src/renderer/src/components/TTS/TTSHome.vue new file mode 100644 index 0000000..ecdd2f2 --- /dev/null +++ b/src/renderer/src/components/TTS/TTSHome.vue @@ -0,0 +1,320 @@ + + + + + diff --git a/src/renderer/src/components/VideoSubtitle/VideoCanvas.vue b/src/renderer/src/components/VideoSubtitle/VideoCanvas.vue new file mode 100644 index 0000000..e12570c --- /dev/null +++ b/src/renderer/src/components/VideoSubtitle/VideoCanvas.vue @@ -0,0 +1,423 @@ + + + diff --git a/src/renderer/src/main.js b/src/renderer/src/main.js index 76bf59a..3a777c0 100644 --- a/src/renderer/src/main.js +++ b/src/renderer/src/main.js @@ -1,42 +1,120 @@ import { createApp } from 'vue' -import { createRouter, createWebHashHistory } from 'vue-router'; +import { createRouter, createWebHashHistory } from 'vue-router' import App from './App.vue' -import { Home } from '@vicons/ionicons5'; -const app = createApp(App); +import { Home } from '@vicons/ionicons5' +const app = createApp(App) import { createPinia } from 'pinia' const pinia = createPinia() const routes = [ - { - path: "/", - component: () => import('./components/Home/Home.vue'), - children: [ - - { path: "/global_setting", name: "global_setting", component: () => import('./components/Setting/Setting.vue') }, - { path: "/clip_setting", name: "clip_setting", component: () => import('./components/Setting/ClipSetting.vue') }, - { path: "/getframe", name: "getframe", component: () => import('./components/Backstep/GetFrame.vue') }, - { path: "/pushBackPrompt", name: "pushBackPrompt", component: () => import('./components/Backstep/PushBackPrompt.vue') }, - { path: "/regenerate", name: "regenerate", component: () => import('./components/Backstep/ReGenerate.vue') }, - { path: "/align_draft", name: "align_draft", component: () => import('./components/Clip/AlignDraft.vue') }, - { path: "/add_draft", name: "add_draft", component: () => import('./components/Clip/AddDraft.vue') }, - { path: "/VideoGenerate", name: "VideoGenerate", component: () => import('./components/Backstep/VideoGenerate.vue') }, - { path: "/sd_setting", name: "sd_setting", component: () => import('./components/Setting/SDSetting.vue') }, - { path: "/copywriting", name: "copywriting", component: () => import('./components/Backstep/CopyWriting.vue') }, - { path: "/videogeneratesetting", name: "videogeneratesetting", component: () => import('./components/Setting/VideoGenerateSetting.vue') }, - { path: "/ShowMessage", name: "ShowMessage", component: () => import('./components/Home/ShowMessage.vue') }, - { path: "/sdoriginal", name: "sdoriginal", component: () => import('./components/Original/MainPage.vue') }, - { path: "/mj_setting", name: "mj_setting", component: () => import('./components/Setting/MJSetting.vue') }, - { path: '/reverse_management', name: "reverse_management", component: () => import('./components/ReverseManage/ReverseManage.vue') }, - { path: '/manage_book/:id', name: "manage_book", component: () => import('./components/ReverseManage/ManageBookDetail.vue') }, - ] - }, - { path: "/ReDrawImage", component: () => import('./components/Home/ReDrawImageWD.vue'), }, + { + path: '/', + component: () => import('./components/Home/Home.vue'), + children: [ + { + path: '/gptCopywriting', + name: 'gptCopywriting', + component: () => import('./components/CopyWriting/CopyWriting.vue') + }, + { + path: '/global_setting', + name: 'global_setting', + component: () => import('./components/Setting/Setting.vue') + }, + { + path: '/clip_setting', + name: 'clip_setting', + component: () => import('./components/Setting/ClipSetting.vue') + }, + { + path: '/getframe', + name: 'getframe', + component: () => import('./components/Backstep/GetFrame.vue') + }, + { + path: '/pushBackPrompt', + name: 'pushBackPrompt', + component: () => import('./components/Backstep/PushBackPrompt.vue') + }, + { + path: '/regenerate', + name: 'regenerate', + component: () => import('./components/Backstep/ReGenerate.vue') + }, + { + path: '/align_draft', + name: 'align_draft', + component: () => import('./components/Clip/AlignDraft.vue') + }, + { + path: '/add_draft', + name: 'add_draft', + component: () => import('./components/Clip/AddDraft.vue') + }, + { + path: '/VideoGenerate', + name: 'VideoGenerate', + component: () => import('./components/Backstep/VideoGenerate.vue') + }, + { + path: '/sd_setting', + name: 'sd_setting', + component: () => import('./components/Setting/SDSetting.vue') + }, + { + path: '/copywriting', + name: 'copywriting', + component: () => import('./components/Backstep/CopyWriting.vue') + }, + { + path: '/videogeneratesetting', + name: 'videogeneratesetting', + component: () => import('./components/Setting/VideoGenerateSetting.vue') + }, + { + path: '/ShowMessage', + name: 'ShowMessage', + component: () => import('./components/Home/ShowMessage.vue') + }, + { + path: '/sdoriginal', + name: 'sdoriginal', + component: () => import('./components/Original/MainPage.vue') + }, + { + path: '/mj_setting', + name: 'mj_setting', + component: () => import('./components/Setting/MJSetting.vue') + }, + { + path: '/reverse_management', + name: 'reverse_management', + component: () => import('./components/ReverseManage/ReverseManage.vue') + }, + { + path: '/manage_book/:id', + name: 'manage_book', + component: () => import('./components/ReverseManage/ManageBookDetail.vue') + }, + { + path: '/test_options', + name: 'test_options', + component: () => import('./components/VideoSubtitle/VideoCanvas.vue') + }, + { + path : "/TTS_Services", + name : "TTS_Services", + component : () => import('./components/TTS/TTSHome.vue') + } + ] + }, + { path: '/ReDrawImage', component: () => import('./components/Home/ReDrawImageWD.vue') } ] const router = createRouter({ - history: createWebHashHistory(), - routes + history: createWebHashHistory(), + routes }) -app.use(router); +app.use(router) app.use(pinia) app.mount('#app') diff --git a/src/stores/reverseManage.js b/src/stores/reverseManage.js index 2d9bd88..e75ad31 100644 --- a/src/stores/reverseManage.js +++ b/src/stores/reverseManage.js @@ -1,8 +1,7 @@ -import { messageDark, useMessage } from "naive-ui"; -import { defineStore } from "pinia"; -import { errorMessage, successMessage } from "../main/generalTools"; -import { BookTaskStatus } from "../define/enum/bookEnum"; - +import { messageDark, useMessage } from 'naive-ui' +import { defineStore } from 'pinia' +import { errorMessage, successMessage } from '../main/generalTools' +import { BookTaskStatus } from '../define/enum/bookEnum' // 系统相关设置 export const useReverseManageStore = defineStore('reverseManage', { @@ -18,6 +17,7 @@ export const useReverseManageStore = defineStore('reverseManage', { srtPath: null, audioPath: null, imageFolder: null, + subtitlePosition: null }, // 当前选中的小说 bookTaskData: [], // 当前显示的所有小说任务数据 @@ -34,27 +34,23 @@ export const useReverseManageStore = defineStore('reverseManage', { styleList: null, prefix: null, status: BookTaskStatus.WAIT, - errorMsg: null, - - }, // 当前选中的小说任务 - + errorMsg: null + } // 当前选中的小说任务 }), getters: { // 获取小说数据 GetBookData(state) { return (bookId = null) => { // 要是返回的数据为空,返回全部数据 - if (bookId == null) return state.bookData; + if (bookId == null) return state.bookData return state.bookData.find((item) => item.id === bookId) - }; - }, - + } + } }, actions: { - /** * 获取小说的数据 - * @param {*} condition + * @param {*} condition */ async GetBookDataFromDB(condition) { try { @@ -64,7 +60,7 @@ export const useReverseManageStore = defineStore('reverseManage', { throw new Error(res.message) } if (res.data.res_book.length <= 0) { - throw new Error("没有找到对应的小说数据,请先添加小说"); + throw new Error('没有找到对应的小说数据,请先添加小说') } this.SetBookData(res.data.res_book) this.selectBook = res.data.res_book[0] @@ -78,12 +74,12 @@ export const useReverseManageStore = defineStore('reverseManage', { async GetBookTaskDataFromDB(condition) { try { debugger - let res = await window.book.GetBookTaskData(condition); + let res = await window.book.GetBookTaskData(condition) if (res.code == 0) { throw new Error(res.message) } if (res.data.bookTasks.length > 0) { - this.bookTaskData = res.data.bookTasks; + this.bookTaskData = res.data.bookTasks this.selectBookTask = res.data.bookTasks[0] } else { throw new Error('没有找到对应的子批次数据,请先创建') @@ -96,34 +92,30 @@ export const useReverseManageStore = defineStore('reverseManage', { /** * 修改小说数据 - * @param value + * @param value */ SetBookData(value) { try { // 判断传入的数据不能为空,为空报错 if (!value) { - throw new Error('value不能为空'); + throw new Error('value不能为空') } // 如果是函数,则执行函数,如果是BookModel对象,修改对应的行的数据,如果是数组,则直接赋值 if (typeof value === 'function') { - this.bookData = value(); - } - else if (typeof value === 'object' && Array.isArray(value)) { - this.bookData = []; - this.bookData = value; - } - else if (typeof value === 'object') { - const index = this.bookData.findIndex((item) => item.id === value.id); + this.bookData = value() + } else if (typeof value === 'object' && Array.isArray(value)) { + this.bookData = [] + this.bookData = value + } else if (typeof value === 'object') { + const index = this.bookData.findIndex((item) => item.id === value.id) if (index !== -1) { - this.bookData[index] = value; + this.bookData[index] = value } else { - throw new Error('未找到对应的数据'); + throw new Error('未找到对应的数据') } + } else { + throw new Error('value的类型不正确') } - else { - throw new Error('value的类型不正确'); - } - } catch (error) { throw new Error(error.message) } @@ -131,22 +123,22 @@ export const useReverseManageStore = defineStore('reverseManage', { // 设置选中的小说 SetSelectBook(value) { - this.selectBook = value; + this.selectBook = value }, /** * 设置小说类型 * 判断pinia中的小说类型数组的数据是不是存在,默认存在不修改,要出传入参数为true,会强制修改 - * @param {*} value + * @param {*} value */ async SetBookType(value = false) { try { if (this.bookType.length <= 0 || value) { - let _bookType = await book.GetBookType(); + let _bookType = await book.GetBookType() if (_bookType.code == 0) { - throw new Error(_bookType.message); + throw new Error(_bookType.message) } - this.bookType = _bookType.data; + this.bookType = _bookType.data } return successMessage(true) } catch (error) { @@ -156,11 +148,11 @@ export const useReverseManageStore = defineStore('reverseManage', { /** * 更新选中的小说数据 - * @param {*} obj + * @param {*} obj */ async UpdateSelectBook(obj) { // 直接修改,使用object.assign合并对象中的数据 - this.selectBook = Object.assign(this.selectBook, obj); + this.selectBook = Object.assign(this.selectBook, obj) }, /** @@ -172,7 +164,7 @@ export const useReverseManageStore = defineStore('reverseManage', { let save_res = null if (book == null) { // 保存this.selectBook - save_res = await window.book.AddOrModifyBook({ ...this.selectBook }); + save_res = await window.book.AddOrModifyBook({ ...this.selectBook }) } else { // 保存传入的数据 save_res = await window.book.AddOrModifyBook({ ...book }) @@ -191,11 +183,9 @@ export const useReverseManageStore = defineStore('reverseManage', { this.selectBook = save_res.data } return successMessage(save_res.data) - } catch (error) { - return errorMessage(error.message); + return errorMessage(error.message) } - } } -}); \ No newline at end of file +}) diff --git a/src/stores/software.js b/src/stores/software.js index 0148147..be7c4ee 100644 --- a/src/stores/software.js +++ b/src/stores/software.js @@ -1,26 +1,31 @@ -import { defineStore } from "pinia"; +import { defineStore } from 'pinia' // 系统相关设置 export const useSoftwareStore = defineStore('software', { state: () => ({ + spin: { + spinning: false, + tip: '加载中...' + }, softWare: { - theme: "light", // 系统主题,亮或是暗 + theme: 'light', // 系统主题,亮或是暗 reverse_display_show: false, // 一键反推界面显示(简单的表格模式还是表格任务模式) reverse_show_book_striped: false, // 是否显示斑马纹(反推界面) - reverse_data_table_size: "small", // 反推界面表格大小 + reverse_data_table_size: 'small' // 反推界面表格大小 }, - componentSize: [] // 组件尺寸(通用的选项) + componentSize: [], // 组件尺寸(通用的选项) + SoftColor: null // 按钮颜色 }), getters: { // 获取一键反推界面显示数据 GetReverseDispalayShow(state) { - return state.softWare.reverse_display_show; - }, + return state.softWare.reverse_display_show + } }, actions: { // 设置一键反推界面显示数据 SetReverseDispalayShow(value) { - this.softWare.reverse_display_show = value; + this.softWare.reverse_display_show = value }, // 设置反推界面时候小说信息显示斑马纹 @@ -30,29 +35,27 @@ export const useSoftwareStore = defineStore('software', { // 修改软件主题 SetSoftware(value) { - this.softWare = Object.assign(this.softWare, value); + this.softWare = Object.assign(this.softWare, value) }, // 获取组件尺寸(判断当前是不是存在,不存在的话到主线程拿) async GetComponentSize() { debugger if (this.componentSize.length == 0) { - let res = await window.setting.GetComponentSize(); - this.componentSize = res.data; + let res = await window.setting.GetComponentSize() + this.componentSize = res.data } - return this.componentSize; + return this.componentSize }, - //#region 保存到数据库的操作 // 将当前的software数据保存到数据库中 async SaveSoftware() { // 保存数据 - return await window.setting.SaveSoftWareSetting(JSON.parse(JSON.stringify(this.softWare))); + return await window.setting.SaveSoftWareSetting(JSON.parse(JSON.stringify(this.softWare))) } //#endregion - } -}); \ No newline at end of file +})