修改了readme文件
This commit is contained in:
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
src-tauri/target/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# Tauri
|
||||||
|
src-tauri/icons/*.png
|
||||||
|
src-tauri/icons/*.ico
|
||||||
|
src-tauri/icons/*.icns
|
||||||
|
!src-tauri/icons/.gitkeep
|
||||||
|
|
||||||
|
# Application packages
|
||||||
|
*.app
|
||||||
|
*.dmg
|
||||||
|
*.msi
|
||||||
|
*.exe
|
||||||
@@ -124,4 +124,4 @@ MIT
|
|||||||
|
|
||||||
- [FFmpeg](https://ffmpeg.org/) - 强大的多媒体处理框架
|
- [FFmpeg](https://ffmpeg.org/) - 强大的多媒体处理框架
|
||||||
- [Tauri 2.0](https://v2.tauri.app/) - 下一代桌面应用开发框架
|
- [Tauri 2.0](https://v2.tauri.app/) - 下一代桌面应用开发框架
|
||||||
- [Tailwind CSS] () -
|
- [Tailwind CSS](https://tailwindcss.com) - 只需书写 HTML 代码即可快速构建美观的网站
|
||||||
611
package-lock.json
generated
Normal file
611
package-lock.json
generated
Normal file
@@ -0,0 +1,611 @@
|
|||||||
|
{
|
||||||
|
"name": "format-converter",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "format-converter",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2.0.0",
|
||||||
|
"lucide": "^0.563.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2.0.0",
|
||||||
|
"concurrently": "^8.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.28.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
|
||||||
|
"integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/api": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==",
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/tauri"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"bin": {
|
||||||
|
"tauri": "tauri.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/tauri"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@tauri-apps/cli-darwin-arm64": "2.10.0",
|
||||||
|
"@tauri-apps/cli-darwin-x64": "2.10.0",
|
||||||
|
"@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0",
|
||||||
|
"@tauri-apps/cli-linux-arm64-gnu": "2.10.0",
|
||||||
|
"@tauri-apps/cli-linux-arm64-musl": "2.10.0",
|
||||||
|
"@tauri-apps/cli-linux-riscv64-gnu": "2.10.0",
|
||||||
|
"@tauri-apps/cli-linux-x64-gnu": "2.10.0",
|
||||||
|
"@tauri-apps/cli-linux-x64-musl": "2.10.0",
|
||||||
|
"@tauri-apps/cli-win32-arm64-msvc": "2.10.0",
|
||||||
|
"@tauri-apps/cli-win32-ia32-msvc": "2.10.0",
|
||||||
|
"@tauri-apps/cli-win32-x64-msvc": "2.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-regex": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chalk": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chalk/node_modules/supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui": {
|
||||||
|
"version": "8.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
|
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.1",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/concurrently": {
|
||||||
|
"version": "8.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz",
|
||||||
|
"integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^4.1.2",
|
||||||
|
"date-fns": "^2.30.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"rxjs": "^7.8.1",
|
||||||
|
"shell-quote": "^1.8.1",
|
||||||
|
"spawn-command": "0.0.2",
|
||||||
|
"supports-color": "^8.1.1",
|
||||||
|
"tree-kill": "^1.2.2",
|
||||||
|
"yargs": "^17.7.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"conc": "dist/bin/concurrently.js",
|
||||||
|
"concurrently": "dist/bin/concurrently.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.13.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "2.30.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
||||||
|
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.21.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.11"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/date-fns"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/escalade": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-fullwidth-code-point": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||||
|
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lucide": {
|
||||||
|
"version": "0.563.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lucide/-/lucide-0.563.0.tgz",
|
||||||
|
"integrity": "sha512-2zBzDJ5n2Plj3d0ksj6h9TWPOSiKu9gtxJxnBAye11X/8gfWied6IYJn6ADYBp1NPoJmgpyOYP3wMrVx69+2AA==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/require-directory": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rxjs": {
|
||||||
|
"version": "7.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||||
|
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/shell-quote": {
|
||||||
|
"version": "1.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
|
||||||
|
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/spawn-command": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strip-ansi": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/supports-color": {
|
||||||
|
"version": "8.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||||
|
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tree-kill": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"tree-kill": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
|
"node_modules/wrap-ansi": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.0.0",
|
||||||
|
"string-width": "^4.1.0",
|
||||||
|
"strip-ansi": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs": {
|
||||||
|
"version": "17.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
|
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^8.0.1",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.3",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^21.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs-parser": {
|
||||||
|
"version": "21.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||||
|
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
package.json
Normal file
27
package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "format-converter",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"description": "跨平台格式转换器 - 基于FFmpeg和Tauri 2.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tauri dev",
|
||||||
|
"build": "tauri build",
|
||||||
|
"tauri": "tauri"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"ffmpeg",
|
||||||
|
"converter",
|
||||||
|
"tauri",
|
||||||
|
"format"
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2.0.0",
|
||||||
|
"concurrently": "^8.2.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2.0.0",
|
||||||
|
"lucide": "^0.563.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src-tauri/.cargo/config.toml
Normal file
2
src-tauri/.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[target.x86_64-pc-windows-gnu]
|
||||||
|
linker = "x86_64-w64-mingw32-gcc"
|
||||||
6754
src-tauri/Cargo.lock
generated
Normal file
6754
src-tauri/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
src-tauri/Cargo.toml
Normal file
38
src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
[package]
|
||||||
|
name = "torch-format-converter"
|
||||||
|
version = "2.0.0"
|
||||||
|
description = "火炬格式转换器 - 跨平台多媒体格式转换工具"
|
||||||
|
authors = [""]
|
||||||
|
license = "MIT"
|
||||||
|
repository = ""
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2.0", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tauri = { version = "2.0", features = ["devtools"] }
|
||||||
|
tauri-plugin-dialog = "2.0"
|
||||||
|
tauri-plugin-fs = "2.0"
|
||||||
|
tauri-plugin-shell = "2.0"
|
||||||
|
tauri-plugin-os = "2.0"
|
||||||
|
tauri-plugin-http = "2.0"
|
||||||
|
rfd = "0.15"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
which = "6.0"
|
||||||
|
dirs = "5.0"
|
||||||
|
reqwest = { version = "0.12", features = ["stream"] }
|
||||||
|
zip = "2.1"
|
||||||
|
tar = "0.4"
|
||||||
|
xz2 = "0.1"
|
||||||
|
walkdir = "2.4"
|
||||||
|
uuid = { version = "1.0", features = ["v4"] }
|
||||||
|
base64 = "0.22"
|
||||||
|
tokio-stream = "0.1"
|
||||||
|
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "gif", "webp", "bmp"] }
|
||||||
|
lofty = "0.22"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
3
src-tauri/build.rs
Normal file
3
src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
1
src-tauri/gen/schemas/acl-manifests.json
Normal file
1
src-tauri/gen/schemas/acl-manifests.json
Normal file
File diff suppressed because one or more lines are too long
1
src-tauri/gen/schemas/capabilities.json
Normal file
1
src-tauri/gen/schemas/capabilities.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
6464
src-tauri/gen/schemas/desktop-schema.json
Normal file
6464
src-tauri/gen/schemas/desktop-schema.json
Normal file
File diff suppressed because it is too large
Load Diff
6464
src-tauri/gen/schemas/macOS-schema.json
Normal file
6464
src-tauri/gen/schemas/macOS-schema.json
Normal file
File diff suppressed because it is too large
Load Diff
368
src-tauri/src/ffmpeg_installer.rs
Normal file
368
src-tauri/src/ffmpeg_installer.rs
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use tokio::process::Command;
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
|
||||||
|
const FFMPEG_VERSION: &str = "7.1";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FFmpegInstaller;
|
||||||
|
|
||||||
|
impl FFmpegInstaller {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取 FFmpeg 存储路径
|
||||||
|
pub fn get_ffmpeg_path() -> PathBuf {
|
||||||
|
let app_dir = Self::get_app_data_dir();
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let ffmpeg_name = "ffmpeg.exe";
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
let ffmpeg_name = "ffmpeg";
|
||||||
|
app_dir.join(ffmpeg_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取应用数据目录
|
||||||
|
fn get_app_data_dir() -> PathBuf {
|
||||||
|
let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let app_dir = home.join("Library/Application Support/FormatConverter");
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let app_dir = home.join("AppData/Local/FormatConverter");
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let app_dir = home.join(".local/share/format-converter");
|
||||||
|
|
||||||
|
fs::create_dir_all(&app_dir).ok();
|
||||||
|
app_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 检查 FFmpeg 是否已安装
|
||||||
|
pub fn is_installed() -> bool {
|
||||||
|
// 1. 检查系统 PATH 中的 FFmpeg
|
||||||
|
if let Ok(output) = std::process::Command::new("which")
|
||||||
|
.arg("ffmpeg")
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
if output.status.success() && !output.stdout.is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查应用目录
|
||||||
|
let app_ffmpeg = Self::get_ffmpeg_path();
|
||||||
|
if app_ffmpeg.exists() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查系统常见路径
|
||||||
|
let common_paths = Self::get_system_ffmpeg_paths();
|
||||||
|
for path in common_paths {
|
||||||
|
if path.exists() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取可能的系统 FFmpeg 路径
|
||||||
|
fn get_system_ffmpeg_paths() -> Vec<PathBuf> {
|
||||||
|
let mut paths = Vec::new();
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
// Homebrew 常见安装路径
|
||||||
|
paths.push(PathBuf::from("/opt/homebrew/bin/ffmpeg")); // Apple Silicon
|
||||||
|
paths.push(PathBuf::from("/usr/local/bin/ffmpeg")); // Intel
|
||||||
|
// 其他可能路径
|
||||||
|
paths.push(PathBuf::from("/usr/bin/ffmpeg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
paths.push(PathBuf::from("/usr/bin/ffmpeg"));
|
||||||
|
paths.push(PathBuf::from("/usr/local/bin/ffmpeg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
paths.push(PathBuf::from("C:\\ffmpeg\\bin\\ffmpeg.exe"));
|
||||||
|
if let Ok(program_files) = std::env::var("ProgramFiles") {
|
||||||
|
paths.push(PathBuf::from(&format!("{}\\ffmpeg\\bin\\ffmpeg.exe", program_files)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paths
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取 FFmpeg 版本
|
||||||
|
pub async fn get_version() -> Option<String> {
|
||||||
|
// 1. 尝试系统 PATH 中的 ffmpeg
|
||||||
|
if let Ok(output) = tokio::time::timeout(
|
||||||
|
std::time::Duration::from_secs(5),
|
||||||
|
Command::new("ffmpeg").args(["-version"]).output()
|
||||||
|
).await {
|
||||||
|
if let Ok(output) = output {
|
||||||
|
if output.status.success() {
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
if let Some(first_line) = stdout.lines().next() {
|
||||||
|
return Some(first_line.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 尝试应用目录中的 ffmpeg
|
||||||
|
let ffmpeg_path = Self::get_ffmpeg_path();
|
||||||
|
if ffmpeg_path.exists() {
|
||||||
|
if let Some(cmd_str) = ffmpeg_path.to_str() {
|
||||||
|
if let Ok(Ok(output)) = tokio::time::timeout(
|
||||||
|
std::time::Duration::from_secs(5),
|
||||||
|
Command::new(cmd_str).args(["-version"]).output()
|
||||||
|
).await {
|
||||||
|
if output.status.success() {
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
if let Some(first_line) = stdout.lines().next() {
|
||||||
|
return Some(first_line.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 尝试系统常见路径
|
||||||
|
let system_paths = Self::get_system_ffmpeg_paths();
|
||||||
|
for path in system_paths {
|
||||||
|
if path.exists() {
|
||||||
|
if let Some(cmd_str) = path.to_str() {
|
||||||
|
if let Ok(Ok(output)) = tokio::time::timeout(
|
||||||
|
std::time::Duration::from_secs(5),
|
||||||
|
Command::new(cmd_str).args(["-version"]).output()
|
||||||
|
).await {
|
||||||
|
if output.status.success() {
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
if let Some(first_line) = stdout.lines().next() {
|
||||||
|
return Some(first_line.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 下载并安装 FFmpeg
|
||||||
|
pub async fn install<F>(progress_callback: F) -> Result<(), String>
|
||||||
|
where
|
||||||
|
F: Fn(f64, String) + Send + 'static,
|
||||||
|
{
|
||||||
|
let platform = Self::detect_platform()?;
|
||||||
|
let download_url = Self::get_download_url(&platform);
|
||||||
|
|
||||||
|
progress_callback(0.0, "准备下载 FFmpeg...".to_string());
|
||||||
|
|
||||||
|
// 创建临时目录
|
||||||
|
let temp_dir = std::env::temp_dir().join("format-converter-ffmpeg");
|
||||||
|
fs::create_dir_all(&temp_dir).map_err(|e: std::io::Error| format!("创建临时目录失败: {}", e))?;
|
||||||
|
|
||||||
|
// 下载文件
|
||||||
|
progress_callback(5.0, "正在下载 FFmpeg...".to_string());
|
||||||
|
let archive_path = temp_dir.join(format!("ffmpeg.{}", platform.extension()));
|
||||||
|
|
||||||
|
Self::download_file(&download_url, &archive_path, |downloaded, total| {
|
||||||
|
let percent = if total > 0 {
|
||||||
|
(downloaded as f64 / total as f64) * 40.0 + 5.0
|
||||||
|
} else {
|
||||||
|
5.0
|
||||||
|
};
|
||||||
|
progress_callback(percent, format!("正在下载 FFmpeg... {:.1}%", percent));
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("下载失败: {}", e))?;
|
||||||
|
|
||||||
|
// 解压
|
||||||
|
progress_callback(50.0, "正在解压...".to_string());
|
||||||
|
Self::extract_archive(&archive_path, &temp_dir, &platform)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("解压失败: {}", e))?;
|
||||||
|
|
||||||
|
// 移动到目标位置
|
||||||
|
progress_callback(90.0, "正在安装...".to_string());
|
||||||
|
let ffmpeg_path = Self::get_ffmpeg_path();
|
||||||
|
let extracted_ffmpeg = Self::find_ffmpeg_binary(&temp_dir, &platform)
|
||||||
|
.ok_or("在解压文件中未找到 FFmpeg 可执行文件")?;
|
||||||
|
|
||||||
|
fs::copy(&extracted_ffmpeg, &ffmpeg_path)
|
||||||
|
.map_err(|e| format!("复制文件失败: {}", e))?;
|
||||||
|
|
||||||
|
// 设置可执行权限(Unix)
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let mut perms = fs::metadata(&ffmpeg_path).unwrap().permissions();
|
||||||
|
perms.set_mode(0o755);
|
||||||
|
fs::set_permissions(&ffmpeg_path, perms).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理临时文件
|
||||||
|
progress_callback(95.0, "正在清理...".to_string());
|
||||||
|
fs::remove_dir_all(&temp_dir).ok();
|
||||||
|
|
||||||
|
progress_callback(100.0, "FFmpeg 安装完成".to_string());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 检测平台
|
||||||
|
fn detect_platform() -> Result<Platform, String> {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
// 检测架构
|
||||||
|
let arch = std::env::consts::ARCH;
|
||||||
|
match arch {
|
||||||
|
"aarch64" => Ok(Platform::MacOSArm64),
|
||||||
|
"x86_64" => Ok(Platform::MacOSX64),
|
||||||
|
_ => Err(format!("不支持的架构: {}", arch)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
let arch = std::env::consts::ARCH;
|
||||||
|
match arch {
|
||||||
|
"x86_64" => Ok(Platform::WindowsX64),
|
||||||
|
"aarch64" => Ok(Platform::WindowsArm64),
|
||||||
|
_ => Err(format!("不支持的架构: {}", arch)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
let arch = std::env::consts::ARCH;
|
||||||
|
match arch {
|
||||||
|
"x86_64" => Ok(Platform::LinuxX64),
|
||||||
|
"aarch64" => Ok(Platform::LinuxArm64),
|
||||||
|
_ => Err(format!("不支持的架构: {}", arch)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||||
|
{
|
||||||
|
Err("不支持的操作系统".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取下载 URL
|
||||||
|
fn get_download_url(platform: &Platform) -> String {
|
||||||
|
match platform {
|
||||||
|
Platform::MacOSArm64 => format!(
|
||||||
|
"https://evermeet.cx/pub/ffmpeg/ffmpeg-{}-macos-arm64.zip",
|
||||||
|
FFMPEG_VERSION
|
||||||
|
),
|
||||||
|
Platform::MacOSX64 => format!(
|
||||||
|
"https://evermeet.cx/pub/ffmpeg/ffmpeg-{}-macos-x86_64.zip",
|
||||||
|
FFMPEG_VERSION
|
||||||
|
),
|
||||||
|
Platform::WindowsX64 => format!(
|
||||||
|
"https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip"
|
||||||
|
),
|
||||||
|
Platform::WindowsArm64 => format!(
|
||||||
|
"https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip"
|
||||||
|
),
|
||||||
|
Platform::LinuxX64 => format!(
|
||||||
|
"https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz"
|
||||||
|
),
|
||||||
|
Platform::LinuxArm64 => format!(
|
||||||
|
"https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-arm64-static.tar.xz"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 下载文件
|
||||||
|
async fn download_file<F>(url: &str, path: &Path, progress: F) -> Result<(), String>
|
||||||
|
where
|
||||||
|
F: Fn(u64, u64),
|
||||||
|
{
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let response = client
|
||||||
|
.get(url)
|
||||||
|
.timeout(std::time::Duration::from_secs(300))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let total_size = response.content_length().unwrap_or(0);
|
||||||
|
|
||||||
|
let mut file = fs::File::create(path).map_err(|e| e.to_string())?;
|
||||||
|
let mut downloaded: u64 = 0;
|
||||||
|
let mut stream = response.bytes_stream();
|
||||||
|
|
||||||
|
while let Some(chunk) = stream.next().await {
|
||||||
|
let chunk = chunk.map_err(|e: reqwest::Error| e.to_string())?;
|
||||||
|
file.write_all(&chunk).map_err(|e: std::io::Error| e.to_string())?;
|
||||||
|
downloaded += chunk.len() as u64;
|
||||||
|
progress(downloaded, total_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解压归档文件
|
||||||
|
async fn extract_archive(
|
||||||
|
archive_path: &Path,
|
||||||
|
output_dir: &Path,
|
||||||
|
platform: &Platform,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
match platform.extension().as_str() {
|
||||||
|
"zip" => {
|
||||||
|
let file = fs::File::open(archive_path).map_err(|e| e.to_string())?;
|
||||||
|
let mut archive = zip::ZipArchive::new(file).map_err(|e| e.to_string())?;
|
||||||
|
archive.extract(output_dir).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
"tar.xz" => {
|
||||||
|
let file = fs::File::open(archive_path).map_err(|e| e.to_string())?;
|
||||||
|
let tar = xz2::read::XzDecoder::new(file);
|
||||||
|
let mut archive = tar::Archive::new(tar);
|
||||||
|
archive.unpack(output_dir).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
_ => return Err("不支持的压缩格式".to_string()),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 查找 FFmpeg 可执行文件
|
||||||
|
fn find_ffmpeg_binary(dir: &Path, _platform: &Platform) -> Option<PathBuf> {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let binary_name = "ffmpeg.exe";
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
let binary_name = "ffmpeg";
|
||||||
|
|
||||||
|
for entry in walkdir::WalkDir::new(dir) {
|
||||||
|
if let Ok(entry) = entry {
|
||||||
|
if entry.file_name() == binary_name {
|
||||||
|
return Some(entry.path().to_path_buf());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Platform {
|
||||||
|
MacOSArm64,
|
||||||
|
MacOSX64,
|
||||||
|
WindowsX64,
|
||||||
|
WindowsArm64,
|
||||||
|
LinuxX64,
|
||||||
|
LinuxArm64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Platform {
|
||||||
|
fn extension(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Platform::MacOSArm64 | Platform::MacOSX64 | Platform::WindowsX64 | Platform::WindowsArm64 => "zip".to_string(),
|
||||||
|
Platform::LinuxX64 | Platform::LinuxArm64 => "tar.xz".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1682
src-tauri/src/main.rs
Normal file
1682
src-tauri/src/main.rs
Normal file
File diff suppressed because it is too large
Load Diff
79
src-tauri/tauri.conf.json
Normal file
79
src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schema.tauri.app/config/2.0.0",
|
||||||
|
"productName": "火炬格式转换器",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"identifier": "cn.meshel.formatconverter",
|
||||||
|
"build": {
|
||||||
|
"frontendDist": "../src"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"withGlobalTauri": true,
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "火炬格式转换器",
|
||||||
|
"width": 1100,
|
||||||
|
"height": 800,
|
||||||
|
"minWidth": 900,
|
||||||
|
"minHeight": 700,
|
||||||
|
"resizable": true,
|
||||||
|
"fullscreen": false,
|
||||||
|
"dragDropEnabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": null,
|
||||||
|
"capabilities": [
|
||||||
|
{
|
||||||
|
"identifier": "main-capability",
|
||||||
|
"description": "Main application capabilities",
|
||||||
|
"windows": ["main"],
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"core:event:default",
|
||||||
|
"core:event:allow-listen",
|
||||||
|
"dialog:default",
|
||||||
|
"fs:default",
|
||||||
|
"shell:default",
|
||||||
|
"os:default",
|
||||||
|
"http:default"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": "all",
|
||||||
|
"category": "Utility",
|
||||||
|
"copyright": "",
|
||||||
|
"shortDescription": "火炬格式转换器",
|
||||||
|
"longDescription": "跨平台格式转换器 - 基于FFmpeg",
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
],
|
||||||
|
"resources": [],
|
||||||
|
"macOS": {
|
||||||
|
"frameworks": [],
|
||||||
|
"minimumSystemVersion": "",
|
||||||
|
"exceptionDomain": "",
|
||||||
|
"signingIdentity": null,
|
||||||
|
"providerShortName": null,
|
||||||
|
"entitlements": null
|
||||||
|
},
|
||||||
|
"windows": {
|
||||||
|
"certificateThumbprint": null,
|
||||||
|
"digestAlgorithm": "sha256",
|
||||||
|
"timestampUrl": ""
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"deb": {
|
||||||
|
"depends": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {}
|
||||||
|
}
|
||||||
338
src/index.html
Normal file
338
src/index.html
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>火炬格式转换器 - Format Converter 0.1.0</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<!-- 首页 -->
|
||||||
|
<div id="home-page" class="page active">
|
||||||
|
<!-- 加载遮罩层 -->
|
||||||
|
<div id="loading-overlay" class="loading-overlay">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
<div class="loading-text">正在分析文件...</div>
|
||||||
|
<div class="loading-subtext">生成缩略图中,请稍候</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="home-content">
|
||||||
|
<!-- 拖放区域 -->
|
||||||
|
<div class="drop-zone" id="drop-zone">
|
||||||
|
<div class="drop-icon">
|
||||||
|
<svg viewBox="0 0 24 24" width="64" height="64">
|
||||||
|
<path fill="currentColor" d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 class="drop-title">拖入文件或点击选择</h2>
|
||||||
|
<p class="drop-subtitle">支持批量选择多个文件</p>
|
||||||
|
<input type="file" id="file-input" multiple hidden accept="video/*,audio/*,image/*">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 支持的格式说明 -->
|
||||||
|
<div class="formats-info">
|
||||||
|
<h3>支持的文件类型</h3>
|
||||||
|
<p class="formats-list">mp4, avi, mkv, mov, webm, mp3, wav, flac, aac, ogg, jpg, png, webp, gif, bmp... <span id="format-count">等 50+ 种格式互相转换</span></p>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部状态栏 -->
|
||||||
|
<div class="status-bar">
|
||||||
|
<div class="status-left">
|
||||||
|
<span id="encoder-status" class="status-indicator loading" title="">
|
||||||
|
<span class="status-dot"></span>
|
||||||
|
<span class="status-text">检测编码器...</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="status-right">
|
||||||
|
<a href="https://www.meshel.cn" target="_blank" class="copyright-link">
|
||||||
|
©️ meshel.cn
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- FFmpeg 安装页面 -->
|
||||||
|
<div id="install-page" class="page">
|
||||||
|
<div class="install-content">
|
||||||
|
<div class="install-icon">📦</div>
|
||||||
|
<h2>正在安装 FFmpeg</h2>
|
||||||
|
<p class="install-desc">首次运行需要下载编码器组件</p>
|
||||||
|
<div class="install-progress">
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" id="install-progress-fill"></div>
|
||||||
|
</div>
|
||||||
|
<span class="progress-text" id="install-progress-text">0%</span>
|
||||||
|
</div>
|
||||||
|
<p class="install-message" id="install-message">准备下载...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 参数配置页面 -->
|
||||||
|
<div id="config-page" class="page">
|
||||||
|
<div class="config-header">
|
||||||
|
<button class="btn-back" id="btn-back">
|
||||||
|
<svg viewBox="0 0 24 24" width="20" height="20">
|
||||||
|
<path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/>
|
||||||
|
</svg>
|
||||||
|
返回
|
||||||
|
</button>
|
||||||
|
<h1>转换设置</h1>
|
||||||
|
<div class="header-actions">
|
||||||
|
<button class="btn-icon" id="btn-settings" title="设置">
|
||||||
|
<svg viewBox="0 0 24 24" width="20" height="20">
|
||||||
|
<path fill="currentColor" d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.68 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="config-content">
|
||||||
|
<!-- 文件类型卡片列表 -->
|
||||||
|
<div class="file-type-cards" id="file-type-cards">
|
||||||
|
<!-- 动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 模板选择弹窗 -->
|
||||||
|
<div id="template-modal" class="modal">
|
||||||
|
<div class="modal-content template-modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>选择输出格式</h3>
|
||||||
|
<button class="btn-close" id="close-template-modal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="template-layout">
|
||||||
|
<div class="template-sidebar">
|
||||||
|
<div class="template-list" id="template-list">
|
||||||
|
<!-- 动态生成 -->
|
||||||
|
</div>
|
||||||
|
<div class="template-actions">
|
||||||
|
<button class="btn-text" id="btn-add-template">+ 新建模板</button>
|
||||||
|
<button class="btn-text btn-danger" id="btn-delete-template">删除</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="template-config" id="template-config">
|
||||||
|
<!-- 参数配置 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn-secondary" id="btn-cancel-template">取消</button>
|
||||||
|
<button class="btn-primary" id="btn-confirm-template">确定</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文件列表弹窗 -->
|
||||||
|
<div id="files-modal" class="modal">
|
||||||
|
<div class="modal-content files-modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 id="files-modal-title">文件列表</h3>
|
||||||
|
<button class="btn-close" id="close-files-modal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="files-list-container">
|
||||||
|
<div class="files-list-header">
|
||||||
|
<div class="file-col-thumb">预览</div>
|
||||||
|
<div class="file-col-name">文件名</div>
|
||||||
|
<div class="file-col-format">格式</div>
|
||||||
|
<div class="file-col-codec">编码参数</div>
|
||||||
|
<div class="file-col-size">大小</div>
|
||||||
|
</div>
|
||||||
|
<div class="files-list" id="files-list">
|
||||||
|
<!-- 动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 输出文件夹设置弹窗 -->
|
||||||
|
<div id="output-modal" class="modal">
|
||||||
|
<div class="modal-content output-modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>输出设置</h3>
|
||||||
|
<button class="btn-close" id="close-output-modal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="output-options">
|
||||||
|
<label class="radio-label">
|
||||||
|
<input type="radio" name="output-location" value="same" checked id="output-same">
|
||||||
|
<span>与原文件相同目录(推荐)</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio-label">
|
||||||
|
<input type="radio" name="output-location" value="custom" id="output-custom">
|
||||||
|
<span>自定义输出目录</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="output-folder-input" id="output-folder-section" style="display: none;">
|
||||||
|
<input type="text" id="output-folder-path" placeholder="选择输出文件夹..." readonly>
|
||||||
|
<button class="btn-secondary" id="btn-select-output">浏览...</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn-secondary" id="btn-cancel-output">取消</button>
|
||||||
|
<button class="btn-primary" id="btn-confirm-output">确定</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 设置弹窗 -->
|
||||||
|
<div id="settings-modal" class="modal">
|
||||||
|
<div class="modal-content settings-modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>设置</h3>
|
||||||
|
<button class="btn-close" id="close-settings-modal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body settings-body">
|
||||||
|
<!-- 左侧菜单 -->
|
||||||
|
<div class="settings-sidebar">
|
||||||
|
<div class="settings-menu-item active" data-section="general">
|
||||||
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<circle cx="12" cy="12" r="3"/>
|
||||||
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||||
|
</svg>
|
||||||
|
<span>通用</span>
|
||||||
|
</div>
|
||||||
|
<div class="settings-menu-item" data-section="output">
|
||||||
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||||||
|
<polyline points="7 10 12 15 17 10"/>
|
||||||
|
<line x1="12" y1="15" x2="12" y2="3"/>
|
||||||
|
</svg>
|
||||||
|
<span>输出设置</span>
|
||||||
|
</div>
|
||||||
|
<div class="settings-menu-item" data-section="notification">
|
||||||
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
||||||
|
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
||||||
|
</svg>
|
||||||
|
<span>通知</span>
|
||||||
|
</div>
|
||||||
|
<div class="settings-menu-item" data-section="about">
|
||||||
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<circle cx="12" cy="12" r="10"/>
|
||||||
|
<line x1="12" y1="16" x2="12" y2="12"/>
|
||||||
|
<line x1="12" y1="8" x2="12.01" y2="8"/>
|
||||||
|
</svg>
|
||||||
|
<span>关于</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧内容区 -->
|
||||||
|
<div class="settings-content">
|
||||||
|
<!-- 通用设置 -->
|
||||||
|
<div class="settings-section active" data-section="general">
|
||||||
|
<h4>通用设置</h4>
|
||||||
|
<div class="setting-item">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="setting-open-folder" checked>
|
||||||
|
<span>转换完成后自动打开输出文件夹</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="setting-show-notification">
|
||||||
|
<span>显示系统通知</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 输出设置 -->
|
||||||
|
<div class="settings-section" data-section="output">
|
||||||
|
<h4>默认输出位置</h4>
|
||||||
|
<div class="setting-item vertical">
|
||||||
|
<label class="radio-label">
|
||||||
|
<input type="radio" name="default-output" value="same" id="setting-output-same" checked>
|
||||||
|
<span>与原文件相同目录(推荐)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item vertical">
|
||||||
|
<label class="radio-label">
|
||||||
|
<input type="radio" name="default-output" value="custom" id="setting-output-custom">
|
||||||
|
<span>自定义目录</span>
|
||||||
|
</label>
|
||||||
|
<div class="setting-custom-path" id="setting-custom-path-wrapper" style="display: none;">
|
||||||
|
<input type="text" id="setting-custom-folder" class="setting-input" placeholder="点击选择文件夹..." readonly>
|
||||||
|
<button class="btn-secondary btn-small" id="btn-setting-browse">浏览</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 通知设置 -->
|
||||||
|
<div class="settings-section" data-section="notification">
|
||||||
|
<h4>通知设置</h4>
|
||||||
|
<div class="setting-item">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="setting-notify-complete" checked>
|
||||||
|
<span>转换完成时通知</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="setting-notify-error" checked>
|
||||||
|
<span>转换失败时通知</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 关于 -->
|
||||||
|
<div class="settings-section" data-section="about">
|
||||||
|
<div class="about-content settings-about">
|
||||||
|
<div class="about-logo">
|
||||||
|
<svg viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||||
|
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
||||||
|
<path d="M2 17l10 5 10-5"/>
|
||||||
|
<path d="M2 12l10 5 10-5"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 class="about-title">火炬格式转换器</h2>
|
||||||
|
<p class="about-version">版本 2.0.0</p>
|
||||||
|
<p class="about-desc">简单易用的多媒体格式转换工具,支持视频、音频、图片等 50+ 种格式互相转换。</p>
|
||||||
|
<div class="about-features">
|
||||||
|
<div class="feature-tag">批量转换</div>
|
||||||
|
<div class="feature-tag">格式丰富</div>
|
||||||
|
<div class="feature-tag">简洁高效</div>
|
||||||
|
</div>
|
||||||
|
<a href="https://www.meshel.cn" target="_blank" class="about-link">
|
||||||
|
<span>访问官网</span>
|
||||||
|
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||||
|
<polyline points="15 3 21 3 21 9"/>
|
||||||
|
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<p class="about-copyright">© 2024-2025 meshel.cn · All rights reserved</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn-secondary" id="btn-cancel-settings">取消</button>
|
||||||
|
<button class="btn-primary" id="btn-save-settings">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="main.js"></script>
|
||||||
|
<!-- Tauri 2.0 API 初始化 -->
|
||||||
|
<script>
|
||||||
|
// 手动初始化 Tauri API
|
||||||
|
window.__TAURI_INTERNALS__ = window.__TAURI_INTERNALS__ || {};
|
||||||
|
|
||||||
|
// 等待 Tauri 运行时加载
|
||||||
|
if (!window.__TAURI__) {
|
||||||
|
console.log('等待 Tauri 运行时...');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1637
src/main.js
Normal file
1637
src/main.js
Normal file
File diff suppressed because it is too large
Load Diff
1747
src/style.css
Normal file
1747
src/style.css
Normal file
File diff suppressed because it is too large
Load Diff
123
src/thumbnail-helper.js
Normal file
123
src/thumbnail-helper.js
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* 缩略图生成辅助函数
|
||||||
|
* 当后端 FFmpeg 生成失败时,使用浏览器原生能力作为兜底
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用浏览器原生 API 生成视频缩略图
|
||||||
|
* @param {File} file - 视频文件对象
|
||||||
|
* @param {number} seekTime - 截取时间点(秒)
|
||||||
|
* @returns {Promise<string>} base64 图片数据
|
||||||
|
*/
|
||||||
|
export async function generateVideoThumbnailNative(file, seekTime = 0.1) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const video = document.createElement('video');
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// 设置视频源
|
||||||
|
const url = URL.createObjectURL(file);
|
||||||
|
video.src = url;
|
||||||
|
video.muted = true;
|
||||||
|
video.playsInline = true;
|
||||||
|
|
||||||
|
video.addEventListener('loadeddata', () => {
|
||||||
|
// seek 到指定时间
|
||||||
|
video.currentTime = Math.min(seekTime, video.duration * 0.1 || 0.1);
|
||||||
|
});
|
||||||
|
|
||||||
|
video.addEventListener('seeked', () => {
|
||||||
|
try {
|
||||||
|
// 计算缩放后的尺寸(最大宽度 320)
|
||||||
|
const maxWidth = 320;
|
||||||
|
const scale = Math.min(1, maxWidth / video.videoWidth);
|
||||||
|
canvas.width = video.videoWidth * scale;
|
||||||
|
canvas.height = video.videoHeight * scale;
|
||||||
|
|
||||||
|
// 绘制帧
|
||||||
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// 转换为 base64
|
||||||
|
const dataUrl = canvas.toDataURL('image/jpeg', 0.85);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
resolve(dataUrl);
|
||||||
|
} catch (e) {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
video.addEventListener('error', (e) => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
reject(new Error('视频加载失败'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 开始加载
|
||||||
|
video.load();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用浏览器原生 API 生成图片缩略图
|
||||||
|
* @param {File} file - 图片文件对象
|
||||||
|
* @returns {Promise<string>} base64 图片数据
|
||||||
|
*/
|
||||||
|
export async function generateImageThumbnailNative(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
const url = URL.createObjectURL(file);
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
try {
|
||||||
|
// 计算缩放后的尺寸(最大宽度 320)
|
||||||
|
const maxWidth = 320;
|
||||||
|
const scale = Math.min(1, maxWidth / img.naturalWidth);
|
||||||
|
canvas.width = img.naturalWidth * scale;
|
||||||
|
canvas.height = img.naturalHeight * scale;
|
||||||
|
|
||||||
|
// 绘制图片
|
||||||
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// 转换为 base64
|
||||||
|
const dataUrl = canvas.toDataURL('image/jpeg', 0.85);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
resolve(dataUrl);
|
||||||
|
} catch (e) {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = () => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
reject(new Error('图片加载失败'));
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = url;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能缩略图生成
|
||||||
|
* 优先使用后端 FFmpeg,失败时尝试浏览器原生方案
|
||||||
|
*/
|
||||||
|
export async function generateThumbnailSmart(filePath, fileType, fileObj = null) {
|
||||||
|
// 首先尝试使用后端 FFmpeg(如果有 file 对象的话)
|
||||||
|
if (fileObj && fileObj instanceof File) {
|
||||||
|
try {
|
||||||
|
if (fileType === 'video') {
|
||||||
|
return await generateVideoThumbnailNative(fileObj);
|
||||||
|
} else if (fileType === 'image') {
|
||||||
|
return await generateImageThumbnailNative(fileObj);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('浏览器原生缩略图生成失败,使用后端:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回 null,让后端处理
|
||||||
|
return null;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user