添加文案处理的功能

This commit is contained in:
lq1405 2024-07-13 15:44:13 +08:00
parent 669e57824d
commit c8a46d59fb
80 changed files with 7384 additions and 2614 deletions

2
.gitignore vendored
View File

@ -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

586
package-lock.json generated
View File

@ -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"
},

View File

@ -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",

View File

@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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) # 多组时间列表

View File

@ -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"
}

View File

@ -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
/**
* 判断文件或目录是否存在
@ -11,10 +10,10 @@ const fspromises = fs.promises;
*/
export async function CheckFileOrDirExist(path) {
try {
await fspromises.access(path);
return true; // 文件或目录存在
await fspromises.access(path)
return true // 文件或目录存在
} catch (error) {
return false; // 文件或目录不存在
return false // 文件或目录不存在
}
}
@ -22,12 +21,13 @@ export async function CheckFileOrDirExist(path) {
export async function CheckFolderExistsOrCreate(folderPath) {
try {
if (!(await CheckFileOrDirExist(folderPath))) {
await fspromises.mkdir(folderPath, { recursive: true });
await fspromises.mkdir(folderPath, { recursive: true })
}
} catch (error) {
throw new Error(error);
throw new Error(error)
}
}
/**
* 拼接两个地址返回拼接后的地址
* @param {*} rootPath 根目录的消息
@ -43,6 +43,33 @@ export function JoinPath(rootPath, subPath) {
}
}
/**
* 删除指定的文件中里面所有的文件和文件夹
* @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
}
}
/**
* 拷贝一个文件或者是文件夹到指定的目标地址
@ -54,61 +81,60 @@ export async function CopyFileOrFolder(source, target, checkParent = false) {
try {
// 判断源文件或文件夹是不是存在
if (!(await CheckFileOrDirExist(source))) {
throw new Error(`源文件或文件夹不存在: ${source}`);
throw new Error(`源文件或文件夹不存在: ${source}`)
}
// 判断父文件夹是否存在,不存在创建
const parent_path = path.dirname(target);
let parentIsExist = await CheckFileOrDirExist(parent_path);
const parent_path = path.dirname(target)
let parentIsExist = await CheckFileOrDirExist(parent_path)
if (!parentIsExist) {
if (checkParent) {
throw new Error(`目的文件或文件夹的父文件夹不存在: ${parent_path}`);
throw new Error(`目的文件或文件夹的父文件夹不存在: ${parent_path}`)
} else {
await fspromises.mkdir(parent_path, { recursive: true });
await fspromises.mkdir(parent_path, { recursive: true })
}
}
// 判断是不是文件夹
const isDirectory = await IsDirectory(source);
const isDirectory = await IsDirectory(source)
// 复制文件夹的逻辑
async function copyDirectory(source, target) {
// 创建目标文件夹
await fspromises.mkdir(target, { recursive: true });
let entries = await fspromises.readdir(source, { withFileTypes: true });
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);
let srcPath = path.join(source, entry.name)
let tgtPath = path.join(target, entry.name)
if (entry.isDirectory()) {
await copyDirectory(srcPath, tgtPath);
await copyDirectory(srcPath, tgtPath)
} else {
await fspromises.copyFile(srcPath, tgtPath);
await fspromises.copyFile(srcPath, tgtPath)
}
}
}
if (isDirectory) {
// 创建目标文件夹
await copyDirectory(source, target);
await copyDirectory(source, target)
} else {
// 复制文件
await fspromises.copyFile(source, target);
await fspromises.copyFile(source, target)
}
} catch (error) {
throw error;
throw error
}
}
/** *
* @param {*} path 输入的文件地址
* @returns true false 不是
*/
export async function IsDirectory(path) {
try {
const stat = await fspromises.stat(path);
return stat.isDirectory();
const stat = await fspromises.stat(path)
return stat.isDirectory()
} catch (error) {
throw new Error(`获取文件夹信息失败: ${path}`);
throw new Error(`获取文件夹信息失败: ${path}`)
}
}
/**
@ -118,29 +144,27 @@ export async function IsDirectory(path) {
*/
export async function BackupFileOrFolder(source_path, target_path) {
try {
// 判断父文件夹是否存在,不存在创建
const parent_path = path.dirname(target_path);
const parent_path = path.dirname(target_path)
if (!(await CheckFileOrDirExist(parent_path))) {
await fspromises.mkdir(parent_path, { recursive: true });
await fspromises.mkdir(parent_path, { recursive: true })
}
// 判断是不是文件夹
const isDirectory = await IsDirectory(source_path);
const isDirectory = await IsDirectory(source_path)
if (isDirectory) {
// 复制文件夹
await fspromises.rename(source_path, target_path);
await fspromises.rename(source_path, target_path)
} else {
// 复制文件
await fspromises.copyFile(source, target);
await fspromises.copyFile(source, target)
}
} catch (error) {
throw new Error(error);
throw new Error(error)
}
}
/**
* 获取指定的文件夹下面的所有的指定的拓展名的文件
* @param {*} folderPath 文件夹地址
@ -151,37 +175,41 @@ export async function GetFilesWithExtensions(folderPath, extensions) {
try {
// 判断当前是不是文件夹
if (!(await IsDirectory(folderPath))) {
throw new Error("输入的不是有效的文件夹地址")
throw new Error('输入的不是有效的文件夹地址')
}
let entries = await fspromises.readdir(folderPath, { withFileTypes: true });
let files = [];
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);
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,
};
isFile: true
}
} else {
return {
isFile: false,
};
isFile: false
}
}));
}
})
)
// 过滤出文件并且满足扩展名要求的文件
files = fileStats.filter(fileStat => fileStat.isFile && extensions.includes(path.extname(fileStat.name).toLowerCase()));
files = fileStats.filter(
(fileStat) =>
fileStat.isFile && extensions.includes(path.extname(fileStat.name).toLowerCase())
)
// 对files数组进行排序基于文件名
files.sort((a, b) => a.name.localeCompare(b.name));
files.sort((a, b) => a.name.localeCompare(b.name))
// 返回文件名数组(完整的)
return files.map(fileStat => path.join(folderPath, fileStat.name));
return files.map((fileStat) => path.join(folderPath, fileStat.name))
} catch (error) {
throw new Error(error);
throw new Error(error)
}
}

View File

@ -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
}

View File

@ -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,
let apiUrl = [
{
label: 'LAI API',
value: 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65',
gpt_url: 'https://laitool.net/v1/chat/completions',
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",
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://api.ephone.ai/v1/images/generations"
image: 'https://laitool.net/v1/images/generations'
},
buy_url: "https://ephone.ai/register?aff=55XT"
}]
buy_url: 'https://laitool.net/register?aff=Zmdu'
},
{
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'
},
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
*/
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 }

View File

@ -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<BookBackTaskList> {
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<BookBackTaskList> {
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'
},

View File

@ -13,7 +13,8 @@ export class BookModel extends Realm.Object<BookModel> {
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<BookModel> {
imageFolder: 'string?',
updateTime: 'date',
createTime: 'date',
test: 'string?'
version: 'string',
subtitlePosition: 'string?'
},
// 主键为_id
primaryKey: 'id'

View File

@ -128,6 +128,7 @@ export class BookTaskDetailModel extends Realm.Object<BookTaskDetailModel> {
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<BookTaskDetailModel> {
prompt: 'string?',
adetailer: 'bool',
sdConifg: 'SDConfig?',
subtitlePosition: 'string?',
createTime: 'date',
updateTime: 'date'
},

View File

@ -7,6 +7,10 @@ export class SoftwareModel extends Realm.Object<SoftwareModel> {
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<SoftwareModel> {
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'

View File

@ -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 bookIdbookTaskIdnametypestatus
*/
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>('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<BookBackTaskList>
}
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) {
try {
// 判断数据是不是存在
if (
isEmpty(bookBackTask.bookId) ||
isEmpty(bookBackTask.bookTaskId) ||
isEmpty(bookBackTask.name) ||
isEmpty(bookBackTask.type)
async AddBookBackTask(
bookId: string,
taskType: BookBackTaskType,
executeType = TaskExecuteType.AUTO,
bookTaskId = null,
bookTaskDetailId = null
) {
throw new Error('新增后台队列任务到数据库失败,数据不完整,缺少必要字段')
try {
// 通过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
}
}
/**
* idbookIdbookTaskId
*
* @param bookBackTask
*/
async DeleteBookBackTaskListBy(bookBackTask) {
async DeleteBookBackTask(bookBackTask) {
try {
this.realm.write(() => {
// 构建查询条件
let query = [] as string[]
if (bookBackTask.id) {
query.push(`id = ${bookBackTask.id}`)
if (
!bookBackTask.hasOwnProperty('id') &&
!bookBackTask.hasOwnProperty('bookId') &&
!bookBackTask.hasOwnProperty('bookTaskId')
) {
throw new Error('删除后台队列任务失败,缺少必要的删除条件')
}
this.transaction(() => {
// 构建查询条件
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}`)
tasksToDelete.filtered('bookTaskId == $0', bookBackTask.bookTaskId)
}
const queryString = query.join(' && ')
// 获取指定的数据
if (queryString) {
const tasksToDelete = this.realm.objects('BookBackTaskList').filtered(queryString)
this.realm.delete(tasksToDelete)
} else {
throw new Error('删除后台队列任务失败,没有筛选条件')
}
return successMessage(
bookBackTask,
'删除后台队列任务成功',

View File

@ -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)

View File

@ -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
}
}
}

View File

@ -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<BookTaskDetailModel>('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<Realm.Object<DefaultObject, never> & 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<BookTaskModel>('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<BookTaskDetailModel>('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)
})

View File

@ -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>('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 {

View File

@ -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]

View File

@ -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
}
// 判断当前全局是不是又当前这个

View File

@ -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

View File

@ -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()
}
}
}

View File

@ -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"),
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"),
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
};
define['remotemj_api'] = 'https://api.laitool.net/'
define['serverUrl'] = 'http://lapi.laitool.cn'
define['API'] = 'f85d39ed5a40fd09966f13f12b6cf0f0'
export { define }

View File

@ -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'
}
}

View File

@ -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'
}
/**
*
*/

View File

@ -40,3 +40,11 @@ export enum OtherData {
//默认
DEFAULT = 'default'
}
export enum SoftColor {
// 棕黄色
BROWN_YELLOW = '#e18a3b',
// 错误红色
ERROR_RED = '#c8161d'
}

View File

@ -0,0 +1,13 @@
/**
*
*/
export enum SubtitleSavePositionType {
// 小说主视频
MAIN_VIDEO = 'main_video',
// 分镜视频
STORYBOARD_VIDEO = 'storyboard_video',
// 其他类型
OTHER = 'other'
}

View File

@ -1,8 +1,8 @@
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 = {
@ -10,7 +10,6 @@ export const gptDefine = {
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`,
@ -24,7 +23,7 @@ export const gptDefine = {
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_name: '分镜大师',
prompt_roles: `1# Role: 小说转漫画提示词大师
## Profile
*Version*: 0.1
@ -62,15 +61,16 @@ export const gptDefine = {
作为角色 <Role>每一次输出都要严格遵守<Rules>一步一步思考按顺序执行<Workflow> 使用默认 <Language> 下面是小说文本:`,
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, "
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"
id: 'a93b693e-bb3f-406d-9730-cba43a6585e4'
},
onlyPromptMJSystemContent: {
prompt_name: "小说提示词-仅出词",
prompt_name: '小说提示词-仅出词',
prompt_roles: `# Pico: 小说分镜
## Profile
@ -140,21 +140,17 @@ export const gptDefine = {
最后再强调你作为角色 <Pico>每一次输出都要严格遵守<Rules>一步一步慢慢思考参考<Examples>的格式一步一步思考按顺序执行<Rules>不需要做解释说明只呈现最后MJ提示词输出的结果下面是小说文本'`,
prompt_example: [
{
user_content: "给皇帝当过儿子的都知道,当的好荣华富贵万人之上",
assistant_content: "微笑,站立,在皇宫的金銮殿里,居中构图,中全景,正面,水平拍摄视角"
user_content: '给皇帝当过儿子的都知道,当的好荣华富贵万人之上',
assistant_content: '微笑,站立,在皇宫的金銮殿里,居中构图,中全景,正面,水平拍摄视角'
},
{
user_content: "当不好就是人头落地",
assistant_content: "惊恐的表情,双手抱头,在刑场上,三分法构图,特写镜头,侧面,俯视视角"
user_content: '当不好就是人头落地',
assistant_content: '惊恐的表情,双手抱头,在刑场上,三分法构图,特写镜头,侧面,俯视视角'
}
],
id: "a93b693e-bb3f-406d-9730-bcd43a6585e"
id: 'a93b693e-bb3f-406d-9730-bcd43a6585e'
},
/**
* 使用自定义GPT提示词时生成接口message信息
* @param {*} params 自定义的GPT提示词数据
@ -162,36 +158,30 @@ export const gptDefine = {
*/
CustomizeGptPrompt(params) {
// 获取设置的数据
let message = [];
let message = []
// 添加角色
message.push(
{
"role": "system",
"content": params.prompt_roles
}
);
message.push({
role: 'system',
content: params.prompt_roles
})
// 便利输出案例添加
for (let i = 0; i < params.prompt_example.length; i++) {
const element = params.prompt_example[i];
const element = params.prompt_example[i]
if (element.user_content) {
message.push(
{
"role": "user",
"content": element.user_content
}
)
message.push({
role: 'user',
content: element.user_content
})
}
if (element.assistant_content) {
message.push(
{
"role": "assistant",
"content": element.assistant_content
}
)
message.push({
role: 'assistant',
content: element.assistant_content
})
}
}
return message;
return message
},
/**
@ -201,11 +191,11 @@ export const gptDefine = {
* @returns
*/
replace: function (content, replacements) {
let result = content;
let result = content
for (let key in replacements) {
result = result.replace(`{${key}}`, replacements[key]);
result = result.replace(`{${key}}`, replacements[key])
}
return result;
return result
},
/**
@ -214,13 +204,12 @@ export const gptDefine = {
* @param {*} replacements
*/
GetExamplePromptMessage(type) {
if (type == "superSinglePrompt") {
return this.CustomizeGptPrompt(this.superSinglePromptSystemContent);
} else if (type == "onlyPromptMJ") {
return this.CustomizeGptPrompt(this.onlyPromptMJSystemContent);
}
else {
return [];
if (type == 'superSinglePrompt') {
return this.CustomizeGptPrompt(this.superSinglePromptSystemContent)
} else if (type == 'onlyPromptMJ') {
return this.CustomizeGptPrompt(this.onlyPromptMJSystemContent)
} else {
return []
}
},
@ -233,19 +222,19 @@ export const gptDefine = {
getSystemContentByType: function (type, replacements) {
switch (type) {
case 'character':
return this.replace(this.characterSystemContent, replacements);
return this.replace(this.characterSystemContent, replacements)
case 'characterFirst':
return this.replace(this.characterFirstPromptSystemContent, replacements);
return this.replace(this.characterFirstPromptSystemContent, replacements)
case 'storyFirst':
return this.replace(this.storyFirstPromptSystemContent, replacements);
return this.replace(this.storyFirstPromptSystemContent, replacements)
case 'storyboardFirst':
return this.replace(this.storyboardFirstPromptSystemContent, replacements);
return this.replace(this.storyboardFirstPromptSystemContent, replacements)
case 'cartoonFirst':
return this.replace(this.cartoonFirstPromptSystemContent, replacements);
return this.replace(this.cartoonFirstPromptSystemContent, replacements)
case 'superSinglePrompt':
return this.replace(this.superSinglePromptSystemContent, replacements);
return this.replace(this.superSinglePromptSystemContent, replacements)
default:
throw new Error(`不存在的类型 : ${type}`);
throw new Error(`不存在的类型 : ${type}`)
}
},
@ -258,57 +247,67 @@ export const gptDefine = {
getUserContentByType: function (type, replacements) {
switch (type) {
case 'character':
return this.replace(this.characterUserContent, replacements);
return this.replace(this.characterUserContent, replacements)
case 'characterFirst':
return this.replace(this.characterFirstPromptUserContent, replacements);
return this.replace(this.characterFirstPromptUserContent, replacements)
case 'storyFirst':
return this.replace(this.storyFirstPromptUserContent, replacements);
return this.replace(this.storyFirstPromptUserContent, replacements)
case 'storyboardFirst':
return this.replace(this.storyboardFirstPromptUserContent, replacements);
return this.replace(this.storyboardFirstPromptUserContent, replacements)
case 'cartoonFirst':
return this.replace(this.cartoonFirstPromptUserContent, replacements);
return this.replace(this.cartoonFirstPromptUserContent, replacements)
default:
throw new Error(`不存在的类型 : ${type}`);
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: "自定义"
}],
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)'
},
{
value: 'customize',
label: '自定义'
}
],
/**
* 通过指定的类型获取数据
@ -319,23 +318,22 @@ export const gptDefine = {
*/
async getGptDataByTypeAndProperty(type, property, defaultData = null) {
try {
let res = [];
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);
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}`);
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,
@ -349,7 +347,6 @@ export const gptDefine = {
}
},
/**
* 保存gpt指定的属性数据判断value中的ID是不是存在存在直接覆盖不存在追加
* @param {*} value
@ -357,38 +354,35 @@ export const gptDefine = {
*/
saveDynamicGPTOption: async function (value) {
try {
let property = value[1];
value = JSON.parse(value[0]);
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] : [];
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);
let index = gpt.findIndex((item) => item.id == value.id)
if (index < 0) {
gpt.push(value);
gpt.push(value)
} else {
gpt[index] = value;
gpt[index] = value
}
} else {
let tmp_id = uuidv4();
value.id = tmp_id;
value.value = tmp_id;
gpt.push(value);
let tmp_id = uuidv4()
value.id = tmp_id
gpt.push(value)
}
tmp_gpt[property] = gpt;
tmp_gpt[property] = gpt
// 将修改后的数据保存
dynamic_setting["gpt"] = tmp_gpt;
dynamic_setting['gpt'] = tmp_gpt
// 写入文件
await fspromises.writeFile(define.dynamic_setting, JSON.stringify(dynamic_setting));
await fspromises.writeFile(define.dynamic_setting, JSON.stringify(dynamic_setting))
} catch (error) {
throw error;
throw error
}
},
/**
* 删除自定义GPT指定属性中的指定ID的数据
* @param {*} id
@ -396,23 +390,22 @@ export const gptDefine = {
*/
deleteDynamicGPTOption: async function (value) {
try {
let property = value[1];
let id = value[0];
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] : [];
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);
let index = gpt.findIndex((item) => item.id == id)
if (index >= 0) {
gpt.splice(index, 1);
gpt.splice(index, 1)
}
// 将修改后的数据保存
dynamic_setting.gpt[property] = gpt;
dynamic_setting.gpt[property] = gpt
// 写入文件
await fspromises.writeFile(define.dynamic_setting, JSON.stringify(dynamic_setting));
await fspromises.writeFile(define.dynamic_setting, JSON.stringify(dynamic_setting))
} catch (error) {
throw error;
throw error
}
}
}
};

View File

@ -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
}

159
src/define/tts/edgeTts.ts Normal file
View File

@ -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<WebSocket> {
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
` +
`<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="${this.lang}">
<voice name="${this.voice}">
<prosody rate="${this.rate}" pitch="${this.pitch}" volume="${this.volume}">
${text}
</prosody>
</voice>
</speak>`
)
})
}
}

100
src/define/tts/ttsDefine.ts Normal file
View File

@ -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'
}
]
}

View File

@ -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
}

View File

@ -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));
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));
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));
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));
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));
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));
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));
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));
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
}
export { GptIpc }

View File

@ -1,18 +1,22 @@
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));
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) => {
@ -21,11 +25,12 @@ function ImageIpc() {
} catch (error) {
return errorMessage(error, LOGGER_DEFINE.REMOVE_WATERMARK)
}
});
})
// 批量处理,去除所有水印
ipcMain.handle(DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE, async (event, value) => await image.BatchProcessImage(value));
}
export {
ImageIpc
ipcMain.handle(
DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE,
async (event, value) => await image.BatchProcessImage(value)
)
}
export { ImageIpc }

View File

@ -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();
SettingIpc()
ImageGenerateIpc()
WritingIpc()
VideoGenerateIpc()
TranslateIpc()
GptIpc()
SdIpc()
MjIpc()
MainIpc(createWindow)
OriginalImageGenerateIpc()
GlobalIpc()
ImageIpc()
SystemIpc()
BookIpc()
TTSIpc()
}

View File

@ -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)
)
}

View File

@ -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));
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));
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 }

View File

@ -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

View File

@ -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')
}

View File

@ -1,21 +1,21 @@
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);
this.global = global
this.tools = new Tools()
this.pm = new PublicMethod(global)
this.sd = new SD(global)
}
/**
@ -23,7 +23,12 @@ export class VideoGenerate {
*/
async GetVideoGenerateConfig() {
try {
let res = await this.tools.getJsonFilePropertyValue(path.join(this.global.config.project_path, "scripts/config.json"), "video_config", {}, false);
let res = await this.tools.getJsonFilePropertyValue(
path.join(this.global.config.project_path, 'scripts/config.json'),
'video_config',
{},
false
)
return {
code: 1,
data: res
@ -42,8 +47,12 @@ export class VideoGenerate {
*/
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);
value = JSON.parse(value)
await this.tools.writeJsonFilePropertyValue(
path.join(this.global.config.project_path, 'scripts/config.json'),
'video_config',
value
)
return {
code: 1
}
@ -64,74 +73,115 @@ export class VideoGenerate {
// 开始添加队列任务
// 生图
// 将当前的所有任务添加到队列中
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");
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);
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]);
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');
let images = await this.tools.getFilesWithExtensions(
path.join(this.global.config.project_path, 'tmp/input_crop'),
'.png'
)
if (images.length <= 0) {
throw new Error("未检测到抽帧图片。请检查");
throw new Error('未检测到抽帧图片。请检查')
}
let png_files = [];
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');
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;
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 });
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;
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;
tmp_seed = res
}
for (let j = 1; j < images.length; j++) {
const element = images[j];
const element = images[j]
if (!element.endsWith('.png')) {
continue;
continue
}
let has_permission = false;
let has_permission = false
// 判断权限
let permission = this.global.permission;
let permission = this.global.permission
if (permission && permission.length >= 0) {
if (permission.indexOf(DEFINE_STRING.PERMISSIONS.AUTO_SAVE_IMAGE_PERMISSION) >= 0) {
has_permission = true;
has_permission = true
}
} else {
has_permission = true;
has_permission = true
}
if (auto_save_image.save_match_count && auto_save_image.auto_match && j >= auto_save_image.save_match_count && has_permission) {
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);
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)
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.out_folder}_${images[0]}`,
batch,
subBatchId
)
// }
task_list.status = 'queue';
task_list["isAuto"] = "true";
await this.pm.ModifyImageTaskList([task_list]);
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 () => {
@ -140,72 +190,117 @@ export class VideoGenerate {
// 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.newWindow[0].win.webContents.send(
DEFINE_STRING.VIDEO_GENERATE_STATUS_REFRESH,
task_list
)
// 判断是不是还有批次执行的任务
// 监听子批次完成(修改当前批次的状态)
this.global.requestQuene.setSubBatchCompletionCallback(batch, subBatchId, async (failedTasks) => {
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.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`);
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)
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) => {
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 + "高清完成,可以开始合成视频");
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";
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");
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.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) => {
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 + "合成视频完成");
console.log(task_list.out_folder + '合成视频完成')
// 添加生成视频队列
task_list.status = "video_ok";
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)
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
)
}
})
}
})
)
}
})
}
)
}
}
)
}
// 监听总批次完成
@ -216,37 +311,36 @@ export class VideoGenerate {
但是以下任务执行失败
`
failedTasks.forEach(({ taskId, error }) => {
message += `${taskId}-, \n 错误信息: ${error}` + '\n';
});
message += `${taskId}-, \n 错误信息: ${error}` + '\n'
})
this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, {
code: 0,
message: message
})
} else {
console.log("所有的自动生成任务完成");
console.log('所有的自动生成任务完成')
this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, {
code: 1,
message: "所有自动任务完成"
message: '所有自动任务完成'
})
}
this.global.fileQueue.enqueue(async () => {
// 读取最新的数据删除
// 将自动化标识删除
let task_json = JSON.parse(await fspromises.readFile(taskPath, 'utf-8'));
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]);
let task_list = task_json.task_list[i]
task_list['isAuto'] = 'false'
await this.pm.ModifyImageTaskList([task_list])
}
})
})
return {
code: 1,
message: "自动任务添加到队列中"
message: '自动任务添加到队列中'
}
} catch (error) {
console.error(error)
return {
@ -263,39 +357,43 @@ export class VideoGenerate {
*/
async getSubFolderList(value) {
try {
let folder = await this.pm.getSubFolderList(path.join(this.global.config.project_path, "tmp"), value[0], value[1]);
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 = [];
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];
const element = folder[i]
let obj = {
folder: element,
status: "unkown error"
status: 'unkown error'
}
data.push(obj);
data.push(obj)
}
} else {
let task_list = JSON.parse(await fspromises.readFile(task_path, 'utf-8'))["task_list"];
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);
const element = folder[i]
let index = task_list.findIndex((item) => item.out_folder == element)
if (index < 0) {
let obj = {
folder: element,
status: "unkown error"
status: 'unkown error'
}
data.push(obj);
data.push(obj)
} else {
let status = task_list[index].status;
let status = task_list[index].status
let obj = {
folder: element,
status: status
}
data.push(obj);
data.push(obj)
}
}
}
@ -319,23 +417,25 @@ export class VideoGenerate {
*/
async AutoGeneretionOneVide(element, video_config, value) {
try {
let background_music = "";
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 (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("背景音乐对应的配置没有找到");
throw new Error('背景音乐对应的配置没有找到')
} else {
background_music = background_music_filter[0].folder_path;
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);
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)
// 对每个视频生成配置文件并将其添加到配置文件中
let obj = {
@ -348,11 +448,14 @@ export class VideoGenerate {
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"),
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",
status: 'no',
audio_sound_size: video_config.audioSoundSize,
background_music_sound_size: video_config.backgroundMusicSoundSize,
keyFrame: video_config.keyframe,
@ -360,28 +463,33 @@ export class VideoGenerate {
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 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;
let scriptPath = path.join(define.scripts_path, 'Lai.exe')
let gpu = this.global.gpu.type
if (video_config.libx264) {
gpu = "OTHER"
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' });
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);
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");
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"));
}
@ -392,20 +500,28 @@ export class VideoGenerate {
})
} 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);
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("未找到对应的任务");
throw new Error('未找到对应的任务')
}
task_list_json.task_list[index].status = "video_error";
task_list_json.task_list[index].errorMessage = error.toString();
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));
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"
status: 'video_error'
})
throw error;
throw error
}
}
@ -416,25 +532,29 @@ export class VideoGenerate {
async AutoGeneretionVideo(value) {
try {
// 先检查所有的条件
let batch = DEFINE_STRING.QUEUE_BATCH.AUTO_VIDEO_GENERATE_SINGLE;
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]);
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;
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 () => {
const element = value[0][i]
this.global.requestQuene.enqueue(
async () => {
await this.AutoGeneretionOneVide(element, video_config, value)
}, element, batch);
},
element,
batch
)
// 添加队列后修改状态
await this.pm.ModifyTaskStatus('out_folder', element, "video_queue");
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"
status: 'video_queue'
})
}
@ -445,8 +565,8 @@ export class VideoGenerate {
但是以下任务执行失败
`
failedTasks.forEach(({ taskId, error }) => {
message += `${taskId}-, \n 错误信息: ${error}` + '\n';
});
message += `${taskId}-, \n 错误信息: ${error}` + '\n'
})
this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, {
code: 0,
@ -455,7 +575,7 @@ export class VideoGenerate {
} else {
this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, {
code: 1,
message: "所有生成视频任务已完成"
message: '所有生成视频任务已完成'
})
}
})
@ -478,90 +598,104 @@ export class VideoGenerate {
* @returns
*/
async CheckVideoGenerattionAllCondition(video_config, simpleData, out_folder) {
let message = "";
let message = ''
if (video_config == null) {
video_config = JSON.parse(await fspromises.readFile(define.video_config, 'utf-8'));
video_config = JSON.parse(await fspromises.readFile(define.video_config, 'utf-8'))
}
// 基础信息检测
if (video_config.audioSoundSize == null) {
message += "配音大小不能为空" + '\n';
message += '配音大小不能为空' + '\n'
}
if (video_config.backgroundMusicSoundSize == null) {
message += "背景音乐音量大学不能为空" + '\n';
message += '背景音乐音量大学不能为空' + '\n'
}
if (video_config.video_resolution_x == null) {
message += "生成视频的宽度不能为空" + '\n';
message += '生成视频的宽度不能为空' + '\n'
}
if (video_config.video_resolution_y == null) {
message += "生成视频的高度不能为空" + '\r\n';
message += '生成视频的高度不能为空' + '\r\n'
}
if (video_config.offsetValue == null) {
message += "视频的上下偏移量不能为空" + '\n';
message += '视频的上下偏移量不能为空' + '\n'
}
if (video_config.frameRate == null) {
message += "生成视频的帧率不能为空" + '\n';
message += '生成视频的帧率不能为空' + '\n'
}
if (video_config.bitRate == null) {
message += "生成视频码率的不能为空" + '\n';
message += '生成视频码率的不能为空' + '\n'
}
// 判断字幕列表中是不是有数据
if (video_config.assConfig == null || video_config.assConfig.length <= 0) {
message += "字幕设置最少包含一条" + '\n';
message += '字幕设置最少包含一条' + '\n'
}
if (video_config.watermarkConfig == null || video_config.watermarkConfig.length <= 0) {
message += "水印设置最少包含一条" + '\n';
message += '水印设置最少包含一条' + '\n'
}
// 判断背景音乐文件夹中是不是存在。并且检查其中是不是有音乐文件
if (simpleData.background_music == "") {
message += "背景音乐文件夹路径不能为空" + '\n';
if (simpleData.background_music == '') {
message += '背景音乐文件夹路径不能为空' + '\n'
}
// 判断背景音乐文件夹是不是存在
if (await this.tools.checkExists(simpleData.background_music)) {
message += "背景音乐文件夹不存在" + '\n';
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');
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';
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);
let config_path = path.join(this.global.config.project_path, 'scripts/config.json')
let isE = await this.tools.checkExists(config_path)
if (!isE) {
message += "配置文件不存在。请先导入字幕文件。并调整时间轴。"
message += '配置文件不存在。请先导入字幕文件。并调整时间轴。'
} else {
// 判断文案时间信息和图片信息是不是相同
let config_json = JSON.parse(await fspromises.readFile(config_path, 'utf-8'));
let len = config_json["srt_time_information"].length;
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');
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');
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";
message += '反推的tag文件和图片的数量对不上。' + '\n'
}
} else if (c_j.length > 0) {
if (c_j.length != img_l.length) {
message += "已存在的配置文件和图片的数量对不上。" + "\n";
message += '已存在的配置文件和图片的数量对不上。' + '\n'
}
} else {
message += "好像没有反推呢" + "\n";
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');
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} 文件里面图片和文案信息对不上。检查是不是图片数量不对`
}
@ -574,10 +708,10 @@ export class VideoGenerate {
message: message
}
this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, res)
return res;
return res
}
return {
code: 1,
code: 1
}
}
}

View File

@ -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()
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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'
)
}
}
}

View File

@ -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 {
// 判断是不是还有任务没有则设置任务执行状态为false
if (this.currentTaskList.length <= 0) {
this.isExecuting = false
}
}
}
async handleTask(task) {
if (task.taskId === 'specificTask') {
console.log(`Handling specific task: ${task.taskId}`)
// 执行任务的具体逻辑
} else {
console.log(`Handling general 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('未知的任务类型')
}
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('没有创建新任务')
}
if (res.code == 0) {
// 修改当前队列的任务状态
let updateRes = _bookBackTaskListService.UpdateTaskStatus({
id: task.id,
status: BookBackTaskStatus.FAIL,
errorMessage: res.message
})
.catch((err) => console.error(err))
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 {
throw new Error('不支持的任务类型')
}
}
}

View File

@ -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必填
@ -17,10 +14,15 @@ export class TaskScheduler {
* @param {*} status 状态选填默认 LoggerStatus.DOING
* @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')
}
}
}

View File

@ -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
}

413
src/main/Task/writing.js Normal file
View File

@ -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
}

View File

@ -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()
})
/**

View File

@ -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 ||
!mjConfigJson.mj_config.userAgent ||
isEmpty(mjConfigJson.mj_config.userAgent) ||
isEmpty(mjConfigJson.mj_config.userAgent.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()
}

View File

@ -1,15 +1,13 @@
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()
}
/**
@ -17,35 +15,42 @@ export class BasicSetting {
* @returns
*/
GetComponentSize() {
return successMessage([
return successMessage(
[
{
value: ComponentSize.TINY,
key: ComponentSize.TINY,
label: "极小"
}, {
label: '极小'
},
{
key: ComponentSize.SMALL,
value: ComponentSize.SMALL,
label: "小"
}, {
label: '小'
},
{
value: ComponentSize.MEDIUM,
key: ComponentSize.MEDIUM,
label: "中"
}, {
label: '中'
},
{
value: ComponentSize.LARGE,
key: ComponentSize.LARGE,
label: "大"
label: '大'
}
], "选择尺寸的下拉菜单消息", 'BasicSetting_GetComponentSize')
],
'选择尺寸的下拉菜单消息',
'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')
}
}
@ -58,10 +63,10 @@ export class BasicSetting {
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')
}
}
}

View File

@ -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)
}

View File

@ -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'
)
}
}
}

View File

@ -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')
}
}
}

View File

@ -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'
)
}
}
}

View File

@ -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
}
export { book }

24
src/preload/gpt.js Normal file
View File

@ -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 }

View File

@ -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设置
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)
},
//反推提示词
@ -252,16 +262,16 @@ const api = {
// 生成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
}

11
src/preload/tts.js Normal file
View File

@ -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 }

25
src/preload/write.js Normal file
View File

@ -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 }

View File

@ -6,7 +6,10 @@
<n-message-provider>
<n-dialog-provider>
<n-notification-provider>
<n-spin :show="softwareStore.spin.spinning">
<RouterView></RouterView>
<template #description> {{ softwareStore.spin.tip }} </template>
</n-spin>
</n-notification-provider>
</n-dialog-provider>
</n-message-provider>
@ -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)
})

View File

@ -0,0 +1,194 @@
<template>
<div style="min-width: 800px; overflow: auto">
<div style="width: 100%">
<n-form ref="formRef" :model="formValue" inline label-placement="left">
<n-form-item label="选择类型" path="gptType">
<n-select
style="width: 160px"
v-model:value="formValue.gptType"
:options="gptTypeOptions"
placeholder="请选择提示词类型"
>
</n-select>
</n-form-item>
<n-form-item label="选择预设" path="gptData">
<n-select
style="width: 160px"
v-model:value="formValue.gptData"
:options="gptDataOptions"
placeholder="请选择提示词数据"
>
</n-select>
</n-form-item>
<n-form-item label="选择请求AI" path="gptAI">
<n-select
style="width: 160px"
v-model:value="formValue.gptAI"
:options="gptOptions"
placeholder="请选择AI"
>
</n-select>
<n-button type="text" style="font-size: 20px" @click="AISetting">
<n-icon> <SettingsOutline /> </n-icon
></n-button>
</n-form-item>
<n-form-item>
<n-button type="primary" @click="ActionStart">开始生成</n-button>
</n-form-item>
</n-form>
</div>
<div style="display: flex; width: 100%">
<div style="flex: 1; margin-right: 10px">
<n-input
type="textarea"
:autosize="{ minRows: 30, maxRows: 30 }"
v-model:value="oldWord"
placeholder="请输入内容"
/>
</div>
<div style="flex: 1">
<n-input
type="textarea"
:autosize="{ minRows: 30, maxRows: 30 }"
v-model:value="newWord"
placeholder="请输入内容"
/>
</div>
</div>
</div>
</template>
<script>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
import { useMessage, useDialog, NForm, NFormItem, NInput, NSelect, NButton, NIcon } from 'naive-ui'
import { SettingsOutline } from '@vicons/ionicons5'
import ManageAISetting from './ManageAISetting.vue'
import { useSoftwareStore } from '../../../../stores/software'
import { isEmpty } from 'lodash'
export default defineComponent({
components: { NForm, NFormItem, NInput, NSelect, NButton, NIcon, SettingsOutline },
setup() {
let message = useMessage()
let dialog = useDialog()
let formValue = ref({
gptType: undefined,
gptData: undefined,
gptAI: undefined
})
let oldWord = ref(undefined)
let newWord = ref(undefined)
let gptTypeOptions = ref([])
let gptDataOptions = ref([])
let gptAllData = undefined
let formRef = ref(null)
let gptOptions = ref([
{ label: 'LAI API', value: 'laiapi' },
{ label: 'KIMI', value: 'kimi' },
{ label: 'DouBao', value: 'doubao' }
])
let softwareStore = useSoftwareStore()
//
async function InitServerGptOptions() {
let gptRes = await window.gpt.InitServerGptOptions()
if (gptRes.code == 0) {
message.error(gptRes.message)
return
}
gptAllData = gptRes.data
//
gptTypeOptions.value = gptRes.data.promptType.map((item) => {
return { label: item.name, value: item.id }
})
gptDataOptions.value = gptRes.data.promptData.map((item) => {
return { label: item.name, value: item.id }
})
}
onMounted(async () => {
await InitServerGptOptions()
})
let ruleObj = (errorMessage) => {
return [
{
required: true,
validator(rule, value) {
if (value == null || value == '') return new Error(errorMessage)
return true
},
trigger: ['input', 'blur', 'change']
}
]
}
let rules = {
gptAI: ruleObj('必选提示词类型'),
gptData: ruleObj('必选提示词预设'),
gptType: ruleObj('必填提示词AI')
}
/**
* AI设置相关
*/
async function AISetting() {
debugger
//
//
let dialogWidth = 800
let dialogHeight = 600
dialog.create({
title: 'AI设置',
showIcon: false,
closeOnEsc: false,
content: () => h(ManageAISetting, { type: formValue.value.gptAI }),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {}
})
}
/**
* 开始执行GPT
*/
async function ActionStart() {
//
if (
isEmpty(formValue.value.gptType) ||
isEmpty(formValue.value.gptData) ||
isEmpty(formValue.value.gptAI)
) {
message.error('请选择完整的数据')
return
}
softwareStore.spin.spinning = true
softwareStore.spin.tip = '生成中......'
let res = await window.write.ActionStart(toRaw(formValue.value), oldWord.value)
softwareStore.spin.spinning = false
if (res.code == 0) {
message.error(res.message)
softwareStore.spin.spinning = false
return
}
newWord.value = res.data
}
return {
formValue,
gptTypeOptions,
gptDataOptions,
oldWord,
newWord,
rules,
formRef,
AISetting,
gptOptions,
softwareStore,
ActionStart
}
}
})
</script>

View File

@ -0,0 +1,135 @@
<template>
<n-space vertical>
<n-card title="LAI API 设置">
<div style="display: flex">
<n-input
v-model:value="aiSetting.laiapi.gpt_url"
style="margin-right: 10px"
type="text"
placeholder="请输入GPT URL"
/>
<n-input
v-model:value="aiSetting.laiapi.api_key"
style="margin-right: 10px"
type="text"
placeholder="请输入API KEY"
/>
<n-input v-model:value="aiSetting.laiapi.model" type="text" placeholder="请输入Model" />
</div>
</n-card>
<n-card title="豆包设置">
<div style="display: flex">
<n-input
v-model:value="aiSetting.doubao.gpt_url"
type="text"
style="margin-right: 10px"
placeholder="请输入豆包的调用网址"
/>
<n-input
v-model:value="aiSetting.doubao.api_key"
type="text"
style="margin-right: 10px"
placeholder="请输入豆包的APIKEY"
/>
<n-input
v-model:value="aiSetting.doubao.model"
type="text"
placeholder="请输入豆包的模型"
/>
</div>
</n-card>
<n-card title="KIMI设置">
<div style="display: flex">
<n-input
v-model:value="aiSetting.kimi.gpt_url"
type="text"
style="margin-right: 10px"
placeholder="请输入KIMI的网址"
/>
<n-input
v-model:value="aiSetting.kimi.api_key"
type="text"
style="margin-right: 10px"
placeholder="请输入KIMI的APIKEY"
/>
<n-input v-model:value="aiSetting.kimi.model" type="text" placeholder="请输入KIMI的模型" />
</div>
</n-card>
<div style="display: flex; justify-content: flex-end; margin: 20px">
<n-button type="primary" @click="SaveAISetting">保存</n-button>
</div>
</n-space>
</template>
<script>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
import { useMessage, NCard, NSpace, NInput, NSelect, NButton } from 'naive-ui'
import { isEmpty } from 'lodash'
export default defineComponent({
components: { NCard, NSpace, NInput, NSelect, NButton },
props: ['type'],
setup(props) {
let message = useMessage()
let aiSetting = ref({
laiapi: {
gpt_url: undefined,
api_key: undefined,
model: undefined
},
kimi: {
gpt_url: undefined,
api_key: undefined,
model: undefined
},
doubao: { gpt_url: undefined, api_key: undefined, model: undefined }
})
onMounted(async () => {
debugger
// AIsetting
let res = await window.gpt.GetAISetting()
if (res.code == 0) {
message.error(res.message)
return
}
aiSetting.value = res.data
})
function checkValue(obj) {
for (const d in obj) {
if (isEmpty(d)) {
return false
}
}
return true
}
async function SaveAISetting() {
// AI
let checkRes = true
if (props.type == 'laiapi') {
checkRes = checkValue(Object.values(aiSetting.value.laiapi))
} else if (props.type == 'kimi') {
checkRes = checkValue(Object.values(aiSetting.value.kimi))
} else if (props.type == 'doubao') {
checkRes = checkValue(Object.values(aiSetting.value.doubao))
}
if (!checkRes) {
message.error('请填写完整选择的AI相关的设置')
return
}
let res = await window.gpt.SaveAISetting(toRaw(aiSetting.value))
if (res.code == 0) {
message.error(res.message)
return
}
message.success('保存成功')
}
return { aiSetting, SaveAISetting }
}
})
</script>

View File

@ -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 {

View File

@ -4,6 +4,9 @@
<n-button color="#ee7959" style="margin-left: 10px" @click="GetCharacter" size="small"
>角色分析标签集</n-button
>
<n-button color="#ee7959" style="margin-left: 10px" @click="ImportPrompt" size="small"
>导入提示词</n-button
>
<n-dropdown trigger="hover" :options="GptButtonOptions" @select="ButtonSelect">
<n-button color="#ee7959" style="margin-left: 10px" @click="GPTPrompt" size="small"
>一键推理提示词</n-button
@ -338,11 +341,60 @@ export default defineComponent({
await ResetImage()
}
//
async function ImportPrompt() {
//
window.api.SelectFile(['txt'], async (value) => {
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,

View File

@ -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)
}
//

View File

@ -0,0 +1,18 @@
<template>
<div>AzureTTS</div>
</template>
<script>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
import { useMessage } from 'naive-ui'
export default defineComponent({
components: {},
setup() {
onMounted(async () => {})
return {}
}
})
</script>

View File

@ -0,0 +1,79 @@
<template>
<div>
<n-form
label-placement="left"
label-width="auto"
require-mark-placement="right-hanging"
:model="edgeTTs"
>
<n-form-item label="合成角色">
<n-select v-model:value="edgeTTs.value" :options="roleOptions" />
</n-form-item>
<n-form-item label="音量">
<n-input-number v-model:value="edgeTTs.volumn" :min="0" :max="100" />
</n-form-item>
<n-form-item label="语速">
<n-input-number v-model:value="edgeTTs.rate" :min="0" :max="100" />
</n-form-item>
<n-form-item label="语调">
<n-input-number v-model:value="edgeTTs.pitch" :min="0" :max="100" />
</n-form-item>
<n-form-item label="生成SRT">
<n-checkbox v-model:checked="edgeTTs.saveSubtitles"> 复选框 </n-checkbox>
</n-form-item>
</n-form>
</div>
</template>
<script>
import { ref, onMounted, defineComponent, watch } from 'vue'
import {
useMessage,
NForm,
NFormItem,
NInputNumber,
NSelect,
NButton,
NIcon,
NPopover,
NCheckbox
} from 'naive-ui'
import { GetEdgeTTSRole } from '../../../../define/tts/ttsDefine'
import { ReaderOutline } from '@vicons/ionicons5'
import { useSoftwareStore } from '../../../../stores/software'
export default defineComponent({
components: {
NForm,
NCheckbox,
NFormItem,
NInputNumber,
NSelect,
NButton,
NIcon,
ReaderOutline,
NPopover
},
props: ['edgeTTs'],
setup(props) {
let edgeTTs = ref(props.edgeTTs)
let message = useMessage()
let softwareStore = useSoftwareStore()
watch(
() => props.edgeTTs,
(val) => {
edgeTTs.value = val
}
)
let roleOptions = ref([])
onMounted(async () => {
//
roleOptions.value = GetEdgeTTSRole()
})
return { edgeTTs, roleOptions, softwareStore }
}
})
</script>

View File

@ -0,0 +1,320 @@
<template>
<div style="display: flex; min-width: 900px; overflow: auto">
<div class="text-input">
<n-input
v-model:value="text"
type="textarea"
placeholder="请输入配音的文本内容"
:autosize="{
minRows: 30,
maxRows: 30
}"
show-count
></n-input>
<div class="tts-options">
<n-button :color="softwareStore.SoftColor.BROWN_YELLOW" size="small" @click="FormatWord">
格式化文档
</n-button>
<n-popover trigger="hover">
<template #trigger>
<n-button quaternary circle color="#b6a014" @click="ModifySplitChar">
<template #icon>
<n-icon size="25"> <AddCircleOutline /> </n-icon>
</template>
</n-button>
</template>
<span>添加分割标识符</span>
</n-popover>
<n-button
style="margin-right: 10px"
:color="softwareStore.SoftColor.BROWN_YELLOW"
size="small"
@click="ClearText"
>
清空内容
</n-button>
</div>
</div>
<div class="audio-setting">
<div class="param-setting">
<n-form label-placement="left">
<n-form-item label="选择配音渠道">
<n-select
placeholder="请选择配音渠道"
v-model:value="ttsConfig.selectModel"
:options="ttsOptions"
></n-select>
</n-form-item>
</n-form>
<EdgeTTS
:edgeTTs="ttsConfig.edgeTTS"
ref="edgettsRef"
v-if="ttsConfig.selectModel == 'edge-tts'"
/>
<AzureTTS
:azureTTS="ttsConfig.azureTTS"
ref="azurettsRef"
v-else-if="ttsConfig.selectModel == 'azure-tts'"
/>
</div>
<div class="autio-button">
<n-button
:color="softwareStore.SoftColor.BROWN_YELLOW"
style="margin-right: 10px"
@click="SaveTTSConfig"
>
保存配置信息
</n-button>
<n-button
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="GenerateAudio"
style="margin-right: 10px"
>
开始合成
</n-button>
<n-button
style="margin-right: 10px"
:color="softwareStore.SoftColor.BROWN_YELLOW"
@click="ShowHistory"
>
查看配音历史
</n-button>
</div>
<div style="display: flex; align-items: center">
<audio
ref="audio"
src="D:\\3.大力\\1\\1719165587206_6cd74afd-ff56-4ba7-abc9-9021a70ac9c7.wav"
controls
style="width: 100%"
></audio>
</div>
</div>
</div>
</template>
<script>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
import {
useMessage,
NInput,
NSelect,
NFormItem,
NForm,
NButton,
NPopover,
NIcon,
useDialog
} from 'naive-ui'
import EdgeTTS from './EdgeTTS.vue'
import AzureTTS from './AzureTTS.vue'
import { GetTTSSelect } from '../../../../define/tts/ttsDefine'
import { useSoftwareStore } from '../../../../stores/software'
import { AddCircleOutline } from '@vicons/ionicons5'
import InputDialogContent from '../Original/Components/InputDialogContent.vue'
export default defineComponent({
components: {
NInput,
NSelect,
NFormItem,
NForm,
EdgeTTS,
AzureTTS,
NButton,
NPopover,
AddCircleOutline,
NIcon,
InputDialogContent
},
setup() {
let message = useMessage()
let dialog = useDialog()
let softwareStore = useSoftwareStore()
let text = ref('你好,我是你的智能语音助手')
let ttsOptions = ref([])
let edgettsRef = ref(null)
let azurettsRef = ref(null)
let splitRef = ref(null)
let ttsConfig = ref({
selectModel: 'edge-tts',
edgeTTS: {
name: 'zh-CN-XiaoxiaoNeural',
gender: 'Female',
label: '晓晓',
lang: 'zh-CN',
pitch: '0', //
rate: '10', //
volumn: '0' //
}
})
let writeSetting = ref({
split_char: '。,“”‘’!?【】《》()…—:;.,\'\'""!?[]<>()...-:;',
merge_count: 3,
merge_char: '',
end_char: '。'
})
onMounted(async () => {
ttsOptions.value = GetTTSSelect()
// TTSTTS
let res = await window.tts.GetTTSCOnfig()
if (res.code == 0) {
message.error(res.message)
} else {
ttsConfig.value = res.data
}
//
let writeSettingRes = await window.write.GetWriteCOnfig()
if (writeSettingRes.code == 0) {
message.error(writeSettingRes.message)
} else {
writeSetting.value = writeSettingRes.data
}
})
/**
* 修改分割符
*/
async function ModifySplitChar() {
//
//
let dialogWidth = 400
let dialogHeight = 150
dialog.create({
title: '添加分割符',
showIcon: false,
closeOnEsc: false,
content: () =>
h(InputDialogContent, {
ref: splitRef,
initData: writeSetting.value.split_char,
placeholder: '请输入分割符'
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
writeSetting.value.split_char = splitRef.value.data
//
let saveRes = await window.write.SaveWriteConfig(toRaw(writeSetting.value))
if (saveRes.code == 0) {
message.error(saveRes.message)
return
}
message.success('分隔符保存成功')
}
})
}
/**
* 解析/格式化文档
*/
async function FormatWord() {
let split_arr = Array.from(writeSetting.value.split_char)
split_arr.forEach((item) => {
let specialCharacters = [
'.',
'*',
'?',
'+',
'^',
'$',
'[',
']',
'(',
')',
'{',
'}',
'|',
'\\'
]
let regex
if (specialCharacters.includes(item)) {
regex = new RegExp('\\' + item, 'g')
} else {
regex = new RegExp(item, 'g')
}
text.value = text.value.replace(regex, '\n')
})
//
let word_arr = text.value.split('\n')
word_arr = word_arr.filter((item) => item != '' && item != null)
text.value = word_arr.join('\n')
}
/**
* 删除文本内容
*/
async function ClearText() {
text.value = ''
}
/**
* 保存TTS配置信息
*/
async function SaveTTSConfig() {}
/**
* 开始合成音频
*/
async function GenerateAudio() {
if (text.value == '') {
message.error('文本内容不能为空')
return
}
if (ttsConfig.value.selectModel == 'edge-tts') {
// let res =
} else if (ttsConfig.value.selectModel == 'azure-tts') {
}
}
/**
* 保存配置信息
*/
async function SaveTTSConfig() {}
return {
text,
azurettsRef,
edgettsRef,
ttsConfig,
writeSetting,
softwareStore,
ModifySplitChar,
SaveTTSConfig,
FormatWord,
ClearText,
splitRef,
GenerateAudio,
ttsOptions: [{ label: 'Edge TTS免费', value: 'edge-tts' }]
}
}
})
</script>
<style scoped>
.autio-button {
margin-top: 10px;
margin-bottom: 10px;
display: flex;
}
.text-input {
flex: 4;
height: 100%;
margin-right: 10px;
}
.audio-setting {
flex: 2;
margin: 0 20px;
}
.tts-options {
margin-top: 10px;
margin-right: 0;
display: flex;
align-items: center;
}
</style>

View File

@ -0,0 +1,423 @@
<template>
<div id="video-canvas" style="position: relative">
<video ref="videoRef" muted style="width: 100%" :src="videoSrc" id="video" autoplay></video>
<canvas
ref="canvasRef"
@mousemove="handleMouseMove"
@mousedown="handleMouseDown"
@mouseup="handleMouseUp"
style="position: absolute; top: 0; left: 0; pointer-events: auto"
/>
<n-divider />
<div class="option-button">
<n-button @click="SetVideoMuted" :color="buttonColor">{{
isMuted ? '取消静音' : '开启静音'
}}</n-button>
<n-button
@click="SaveSelectPosition"
style="margin-left: 5px"
:loading="SaveSelectPositionLodding"
:color="softwareStore.SoftColor.BROWN_YELLOW"
>保存位置</n-button
>
<n-button
@click="OpenBookSubtitlePositionScreenshot"
style="margin-left: 5px"
:color="softwareStore.SoftColor.BROWN_YELLOW"
>查看截图</n-button
>
<n-button
@click="GetCurrentFrameText"
style="margin-left: 5px; width: 150px"
:color="softwareStore.SoftColor.BROWN_YELLOW"
:loading="GetCurrentFrameTextLodding"
>
{{ GetCurrentFrameTextLodding ? '提取文本中' : '提取保存帧' }}</n-button
>
<n-button
@click="GetVideoFrameText"
style="margin-left: 5px; width: 150px"
:color="softwareStore.SoftColor.BROWN_YELLOW"
:loading="GetVideoFrameTextLodding"
>
{{ GetVideoFrameTextLodding ? '视频文案提取中' : '提示视频文案' }}</n-button
>
<n-button
v-if="type == 'storyboard_video'"
@click="GetAllImageText"
style="margin-left: 5px"
:color="softwareStore.SoftColor.BROWN_YELLOW"
>提取所有</n-button
>
</div>
<div class="output-text" style="margin-top: 10px">
<n-input
type="textarea"
placeholder="识别到的字幕文本(可能有错,需要手动看一下)"
:autosize="{
minRows: 5,
maxRows: 7
}"
v-model:value="frameText"
>
</n-input>
</div>
</div>
</template>
<script>
import { ref, onMounted, defineComponent, onUnmounted, computed, toRaw } from 'vue'
import { NDivider, NButton, useMessage, NInput, useDialog } from 'naive-ui'
import { useSoftwareStore } from '../../../../stores/software'
import { useReverseManageStore } from '../../../../stores/reverseManage'
import { SubtitleSavePositionType } from '../../../../define/enum/waterMarkAndSubtitle'
export default defineComponent({
components: {
NDivider,
NButton,
NInput
},
props: ['videoSrc', 'mark', 'videoWidth', 'type', 'height'],
setup(props) {
let message = useMessage()
let dialog = useDialog()
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let videoRef = ref(null)
let canvasRef = ref(null)
let videoSrc = ref(
props.videoSrc ||
'D:\\文\\物价暴跌百万倍,我成了神豪-七猫\\1\\价暴跌百万倍,我成了神豪1_output_crop_00001.mp4'
)
let type = ref(props.type ? props.type : SubtitleSavePositionType.MAIN_VIDEO)
let mark = ref(props.mark)
let videoWidth = ref(props.videoWidth == null ? 800 : props.videoWidth)
let drawing = ref(false)
let moving = ref(false)
let startPos = ref(null)
let rectPos = ref(null)
let offset = ref({ x: 0, y: 0 })
let isPlaying = ref(false)
let buttonColor = ref(softwareStore.SoftColor.BROWN_YELLOW)
let isMuted = ref(false)
let frameText = ref('')
let GetCurrentFrameTextLodding = ref(false)
let SaveSelectPositionLodding = ref(false)
let GetVideoFrameTextLodding = ref(false)
onMounted(() => {
window.addEventListener('resize', updateVideoPosition)
updateVideoPosition() //
// resizeObserver.observe(videoRef.value)
const video = videoRef.value
video.onloadedmetadata = () => {
let { width, height } = video.getBoundingClientRect()
canvasRef.value.width = width
canvasRef.value.height = height
canvasRef.value.style.width = `${width}px`
canvasRef.value.style.height = `${height}px`
}
let videoCanvas = document.getElementById('video-canvas')
videoCanvas.style.width = `${videoWidth.value}px`
videoCanvas.style.height = `${props.height}px`
videoCanvas.style.overflow = 'scroll'
buttonColor.value = videoRef.value.muted
? softwareStore.SoftColor.ERROR_RED
: softwareStore.SoftColor.ERROR_RED
isMuted.value = videoRef.value.muted
})
onUnmounted(() => {
window.removeEventListener('resize', updateVideoPosition)
})
/**
* 获取当前鼠标在canvas中的相对位置
* @param e
*/
function getCanvasRelativePosition(e) {
const rect = canvasRef.value.getBoundingClientRect()
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
}
}
/**
* canvas绘制矩形
* @param rect
*/
const drawRectangle = (rect) => {
const ctx = canvasRef.value.getContext('2d')
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height) //
ctx.strokeStyle = 'red' //
ctx.lineWidth = 2 // 线
ctx.strokeRect(rect.startX, rect.startY, rect.width, rect.height)
}
/**
* 鼠标再canvas中移动事件
* @param e
*/
function handleMouseMove(e) {
if (drawing.value && startPos.value) {
videoRef.value.pause()
isPlaying.value = false
const currentPos = getCanvasRelativePosition(e)
const rect = {
startX: Math.min(startPos.value.x, currentPos.x),
startY: Math.min(startPos.value.y, currentPos.y),
width: Math.abs(startPos.value.x - currentPos.x),
height: Math.abs(startPos.value.y - currentPos.y)
}
drawRectangle(rect)
} else if (moving.value && rectPos.value) {
videoRef.value.pause()
isPlaying.value = false
const currentPos = getCanvasRelativePosition(e)
const newRectPos = {
startX: currentPos.x - offset.value.x,
startY: currentPos.y - offset.value.y,
width: rectPos.value.width,
height: rectPos.value.height,
videoWidth: canvasRef.value.width,
videoHeight: canvasRef.value.height
}
rectPos.value = newRectPos
drawRectangle(newRectPos)
}
}
/**
* 鼠标再canvas中按下事件
* @param e
*/
function handleMouseDown(e) {
const pos = getCanvasRelativePosition(e)
if (
rectPos.value &&
pos.x >= rectPos.value.startX &&
pos.x <= rectPos.value.startX + rectPos.value.width &&
pos.y >= rectPos.value.startY &&
pos.y <= rectPos.value.startY + rectPos.value.height
) {
moving.value = true
offset.value = { x: pos.x - rectPos.value.startX, y: pos.y - rectPos.value.startY }
} else {
startPos.value = pos
drawing.value = true
}
if (videoRef.value.paused) {
videoRef.value.play()
isPlaying.value = true
} else {
videoRef.value.pause()
isPlaying.value = false
}
}
/**
* 鼠标再canvas中抬起事件
* @param e
*/
function handleMouseUp(e) {
if (drawing.value) {
const pos = getCanvasRelativePosition(e)
rectPos.value = {
startX: Math.min(startPos.value.x, pos.x),
startY: Math.min(startPos.value.y, pos.y),
width: Math.abs(startPos.value.x - pos.x),
height: Math.abs(startPos.value.y - pos.y),
videoWidth: canvasRef.value.width,
videoHeight: canvasRef.value.height
}
drawRectangle(rectPos.value)
startPos.value = null
drawing.value = false
}
if (moving.value) {
moving.value = false
}
}
/**
* 监听video的resize事件
*/
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
const { width, height } = entry.contentRect
if (canvasRef.value) {
canvasRef.value.width = width
canvasRef.value.height = height
canvasRef.value.style.width = `${width}px`
canvasRef.value.style.height = `${height}px`
}
}
})
/**
* 更新canvas的位置
*/
const updateVideoPosition = () => {
if (videoRef.value && videoRef.value.parentElement) {
const videoRect = videoRef.value.getBoundingClientRect()
const parentRect = videoRef.value.parentElement.getBoundingClientRect()
const relativeTop = videoRect.top - parentRect.top
const relativeLeft = videoRect.left - parentRect.left
const { width, height } = videoRect
if (canvasRef.value) {
canvasRef.value.style.top = `${relativeTop}px`
canvasRef.value.style.left = `${relativeLeft}px`
canvasRef.value.style.width = `${width}px`
canvasRef.value.style.height = `${height}px`
}
}
}
/**
* 设置静音
*/
function SetVideoMuted() {
videoRef.value.muted = !videoRef.value.muted
isMuted.value = videoRef.value.muted
if (videoRef.value.muted) {
buttonColor.value = softwareStore.SoftColor.ERROR_RED
} else {
buttonColor.value = softwareStore.SoftColor.BROWN_YELLOW
}
}
/**
* 保存当前选中的所有的位置目前只支持单个
*/
async function SaveSelectPosition() {
if (rectPos.value == null) {
message.error('请先选择一个区域')
return
}
SaveSelectPositionLodding.value = true
message.success(JSON.stringify(rectPos.value))
//
let saveRes = await window.book.SaveBookSubtitlePosition({
id: reverseManageStore.selectBook.id
? reverseManageStore.selectBook.id
: '58053f68-da54-4a48-92fc-8a5c3cb75043',
bookSubtitlePosition: [toRaw(rectPos.value)],
currentTime: videoRef.value.currentTime,
type: type.value
})
if (saveRes.code == 0) {
message.error('保存字幕位置并截取示例图片失败')
SaveSelectPositionLodding.value = false
return
}
message.success('保存字幕位置并截取示例图片成功')
SaveSelectPositionLodding.value = false
}
/**
* 查看字幕位置示例截图
*/
async function OpenBookSubtitlePositionScreenshot() {
if (rectPos.value == null) {
message.error('请先选择一个区域')
return
}
let res = await window.book.OpenBookSubtitlePositionScreenshot({
id: reverseManageStore.selectBook.id
? reverseManageStore.selectBook.id
: '58053f68-da54-4a48-92fc-8a5c3cb75043',
type: type.value
})
if (res.code == 0) {
message.error(res.message)
return
}
message.success(res.message)
}
/**
* 获取当前的字幕
*/
async function GetCurrentFrameText() {
GetCurrentFrameTextLodding.value = true
if (type.value == SubtitleSavePositionType.MAIN_VIDEO) {
let res = await window.book.GetCurrentFrameText({
id: reverseManageStore.selectBook.id
? reverseManageStore.selectBook.id
: '58053f68-da54-4a48-92fc-8a5c3cb75043',
currentTime: videoRef.value.currentTime,
type: type.value
})
if (res.code == 0) {
message.error(res.message)
GetCurrentFrameTextLodding.value = false
return
}
message.success(res.data)
frameText.value = res.data
}
GetCurrentFrameTextLodding.value = false
}
/**
* 获取整个视频的文案信息保存到和视频的同级目录中
*/
async function GetVideoFrameText() {
GetVideoFrameTextLodding.value = true
if (type.value == SubtitleSavePositionType.MAIN_VIDEO) {
let res = await window.book.GetVideoFrameText({
id: reverseManageStore.selectBook.id
? reverseManageStore.selectBook.id
: '58053f68-da54-4a48-92fc-8a5c3cb75043',
type: type.value
})
if (res.code == 0) {
message.error(res.message)
GetVideoFrameTextLodding.value = false
return
}
message.success(res.message)
frameText.value = res.data
GetVideoFrameTextLodding.value = false
}
}
return {
videoRef,
canvasRef,
handleMouseMove,
handleMouseDown,
handleMouseUp,
GetCurrentFrameTextLodding,
SaveSelectPositionLodding,
GetVideoFrameTextLodding,
videoSrc,
mark,
videoWidth,
drawing,
moving,
startPos,
rectPos,
offset,
isPlaying,
SetVideoMuted,
buttonColor,
isMuted,
softwareStore,
SaveSelectPosition,
reverseManageStore,
type,
OpenBookSubtitlePositionScreenshot,
GetCurrentFrameText,
frameText,
GetVideoFrameText
}
}
})
</script>

View File

@ -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: "/",
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: '/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'), },
{ path: '/ReDrawImage', component: () => import('./components/Home/ReDrawImageWD.vue') }
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
app.use(router);
app.use(router)
app.use(pinia)
app.mount('#app')

View File

@ -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,24 +34,20 @@ 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
@ -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('没有找到对应的子批次数据,请先创建')
@ -102,28 +98,24 @@ export const useReverseManageStore = defineStore('reverseManage', {
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,7 +123,7 @@ export const useReverseManageStore = defineStore('reverseManage', {
// 设置选中的小说
SetSelectBook(value) {
this.selectBook = value;
this.selectBook = value
},
/**
@ -142,11 +134,11 @@ export const useReverseManageStore = defineStore('reverseManage', {
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) {
@ -160,7 +152,7 @@ export const useReverseManageStore = defineStore('reverseManage', {
*/
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)
}
}
});
}
})

View File

@ -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
}
});
})