Repository: EutropicAI/Final2x
Branch: main
Commit: 0b590138d0bb
Files: 68
Total size: 101.0 KB
Directory structure:
gitextract_eypaha0i/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug.yaml
│ │ ├── config.yml
│ │ └── feature.yaml
│ └── workflows/
│ ├── CI-build.yml
│ ├── CI-test.yml
│ ├── Release.yml
│ ├── issue-helper.yml
│ └── issue-translator.yml
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── README_i18n/
│ └── README_zh.md
├── build/
│ ├── entitlements.mac.plist
│ └── notarize.js
├── electron-builder.yml
├── electron.vite.config.ts
├── eslint.config.js
├── package.json
├── resources/
│ └── download-core.js
├── src/
│ ├── main/
│ │ ├── getCorePath.ts
│ │ ├── index.ts
│ │ ├── openDirectory.ts
│ │ └── runCommand.ts
│ ├── preload/
│ │ ├── index.d.ts
│ │ └── index.ts
│ ├── renderer/
│ │ ├── index.html
│ │ └── src/
│ │ ├── App.vue
│ │ ├── components/
│ │ │ ├── MyDarkMode.vue
│ │ │ ├── MyExternalLink.vue
│ │ │ ├── MyProgress.vue
│ │ │ ├── MySetting.vue
│ │ │ ├── NaiveDarkMode.vue
│ │ │ ├── TrafficLightsButtons.vue
│ │ │ └── bottomNavigation.vue
│ │ ├── env.d.ts
│ │ ├── locales/
│ │ │ ├── en.ts
│ │ │ ├── fr.ts
│ │ │ ├── ja.ts
│ │ │ └── zh.ts
│ │ ├── main.ts
│ │ ├── plugins/
│ │ │ └── i18n.ts
│ │ ├── public/
│ │ │ ├── index.html
│ │ │ └── robots.txt
│ │ ├── router/
│ │ │ └── index.ts
│ │ ├── store/
│ │ │ ├── SRSettingsStore.ts
│ │ │ ├── globalSettingsStore.ts
│ │ │ └── ioPathStore.ts
│ │ ├── utils/
│ │ │ ├── IOPath.ts
│ │ │ ├── SROptions.ts
│ │ │ ├── getFinal2xCoreConfig.ts
│ │ │ ├── index.ts
│ │ │ ├── modelOptions.ts
│ │ │ ├── pathFormat.ts
│ │ │ └── switchLanguage.ts
│ │ └── views/
│ │ ├── Final2xHome.vue
│ │ └── Final2xSettings.vue
│ └── shared/
│ ├── const/
│ │ └── ipc.ts
│ └── type/
│ └── core.ts
├── test/
│ ├── node/
│ │ └── getCorePath.test.ts
│ └── web/
│ ├── IOPath.test.ts
│ ├── index.test.ts
│ ├── pathFormat.test.ts
│ └── switchLanguage.test.ts
├── tsconfig.json
├── tsconfig.node.json
├── tsconfig.web.json
└── vitest.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug.yaml
================================================
name: 🐛 Bug report | 错误报告 | BUG報告
description: Create a bug report to help us improve | 创建bug报告以帮助我们改进 | 改善を支援するためのレポートを作成する
title: '[Bug] '
labels: ['bug']
body:
- type: checkboxes
id: checks
attributes:
label: Please carefully review each item in the checklist below | 请认真检查以下清单中的每一项 | 以下のチェックリストの各項目を注意深く確認してください
options:
- label: Searched and didn't find a similar issue | 已经搜索过,没有发现类似issue | 類似の問題が見つかりませんでした
- label: Searched documentation and didn't find relevant content | 已经搜索过文档,没有发现相关内容 | ドキュメントを検索して関連する内容が見つかりませんでした
- label: Tried with the latest version and the issue still exists | 已经尝试使用过最新版,问题依旧存在 | 最新バージョンを試しましたが問題は解消されませんでした
- type: input
id: app-version
attributes:
label: Software Version | 软件版本 | ソフトウェアバージョン
placeholder: '1.1.4'
validations:
required: true
- type: dropdown
id: system-type
attributes:
label: Operating System | 操作系统 | オペレーティングシステム
options:
- Windows x64
- Windows arm64
- macOS x64 (Intel)
- macOS arm64 (M1,M2...)
- Ubuntu x64
- Debian x64
- Arch Linux x64
- Other Linux x64
validations:
required: true
- type: input
id: system-version
attributes:
label: System Version | 系统版本 | システムバージョン
validations:
required: true
- type: textarea
id: description
attributes:
label: Describe the bug | 描述错误 | BUGの説明
description: |
A clear and concise description of what the bug is
描述错误的详细信息
バグの内容を明確かつ簡潔に説明してください
validations:
required: true
- type: textarea
id: reproduce-steps
attributes:
label: To reproduce | 复现步骤 | 再現方法
description: Steps to reproduce the behavior | 复现行为的步骤 | 不具合の再現手順
value: |
1. Go to '...'
2. Click on '....'
3. See error
validations:
required: true
- type: textarea
id: log
attributes:
label: Error log | 报错日志 | ログ
description: your error log | 您的错误日志 | エラーログ
value: |
```
your error log
```
validations:
required: true
- type: textarea
id: other
attributes:
label: Additional context | 附加内容 | 追加コンテキスト
description: |
Add any other context and screenshots to help explain your problem
添加任何其他上下文和截图,以帮助解释您的问题
問題を説明するために他の文脈やスクリーンショットを追加してください
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
================================================
FILE: .github/ISSUE_TEMPLATE/feature.yaml
================================================
name: 🚀 Feature request | 功能请求 | フィーチャーリクエスト
description: Suggest an idea for this project | 为项目提供一个创意建议 | このプロジェクトにアイデアを提案する
title: '[FEATURE] '
labels: ['enhancement']
body:
- type: checkboxes
id: checks
attributes:
label: Please carefully review each item in the checklist below | 请认真检查以下清单中的每一项 | 以下のチェックリストの各項目を注意深く確認してください
options:
- label: Searched and didn't find a similar issue | 已经搜索过,没有发现类似issue | 類似の問題が見つかりませんでした
- type: textarea
id: is-related
attributes:
label: Is your feature request related to a problem? | 你的feature请求是否与一个问题有关? | あなたのfeatureリクエストは質問に関連していますか?
description: |
A clear and concise description of what the problem is
请清楚而简明地描述问题是什么
問題が何であるかを明確かつ簡潔に説明してください
validations:
required: true
- type: textarea
id: detail
attributes:
label: Detail | 详细描述 | 詳細な説明
description: |
A clear and concise description of what you want to happen
请清楚而简明地描述您想要实现的内容
実現したい内容を明確かつ簡潔に説明してください
validations:
required: true
- type: textarea
id: log
attributes:
label: Additional context | 附加内容 | 追加コンテキスト
description: |
Add any other context or screenshots about the feature request here
在这里添加任何其他上下文或截图,以帮助解释您的功能请求
その他の文脈やスクリーンショットを追加して、機能リクエストについて説明してください
================================================
FILE: .github/workflows/CI-build.yml
================================================
name: CI-build
on:
push:
branches:
- main
paths-ignore:
- '**.md'
- LICENSE
pull_request:
paths-ignore:
- '**.md'
- LICENSE
workflow_dispatch:
jobs:
windows:
strategy:
matrix:
os-version: ['x64', 'arm64']
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: pnpm/action-setup@v4
with:
version: 10
- name: build
run: |
pnpm install
pnpm fetchcore
pnpm run build:win-${{ matrix.os-version }}
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: zip-unpacked-x64
if: matrix.os-version == 'x64'
run: |
cd .\dist\win-unpacked
7z a -r Final2x-windows-${{ matrix.os-version }}-unpacked.7z *
- name: zip-unpacked-arm64
if: matrix.os-version == 'arm64'
run: |
cd .\dist\win-arm64-unpacked
7z a -r Final2x-windows-${{ matrix.os-version }}-unpacked.7z *
- name: upload-unpacked-x64
if: matrix.os-version == 'x64'
uses: actions/upload-artifact@v4
with:
name: Final2x-windows-${{ matrix.os-version }}-unpacked
path: dist/win-unpacked/*.7z
- name: upload-unpacked-arm64
if: matrix.os-version == 'arm64'
uses: actions/upload-artifact@v4
with:
name: Final2x-windows-${{ matrix.os-version }}-unpacked
path: dist/win-arm64-unpacked/*.7z
macos:
strategy:
matrix:
os-version: ['arm64']
runs-on: macos-14
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: pnpm/action-setup@v4
with:
version: 10
- name: build
run: |
pnpm install
pnpm fetchcore
pnpm run build:mac-${{ matrix.os-version }}
env:
ARCH: ${{ matrix.os-version }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: zip-unpacked-arm64
if: matrix.os-version == 'arm64'
run: |
cd ./dist/mac-arm64
7z a -r Final2x-macos-${{ matrix.os-version }}-unpacked.7z *
- name: upload-dmg
uses: actions/upload-artifact@v4
with:
name: Final2x-macos-${{ matrix.os-version }}-dmg
path: dist/*.dmg
- name: upload-unpacked-arm64
if: matrix.os-version == 'arm64'
uses: actions/upload-artifact@v4
with:
name: Final2x-macos-${{ matrix.os-version }}-unpacked
path: dist/mac-arm64/*.7z
linux-pip:
strategy:
matrix:
os-version: ['x64']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: pnpm/action-setup@v4
with:
version: 10
- name: build
run: |
pnpm install
pnpm run build:linux-${{ matrix.os-version }}
env:
SKIP_DOWNLOAD_CORE: true
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: zip-unpacked
run: |
cd ./dist/linux-unpacked
7z a -r Final2x-linux-pip-${{ matrix.os-version }}-unpacked.7z *
- name: upload-snap
uses: actions/upload-artifact@v4
with:
name: Final2x-linux-pip-${{ matrix.os-version }}-snap
path: dist/*.snap
- name: upload-AppImage
uses: actions/upload-artifact@v4
with:
name: Final2x-linux-pip-${{ matrix.os-version }}-AppImage
path: dist/*.AppImage
- name: upload-deb
uses: actions/upload-artifact@v4
with:
name: Final2x-linux-pip-${{ matrix.os-version }}-deb
path: dist/*.deb
- name: upload-unpacked
uses: actions/upload-artifact@v4
with:
name: Final2x-linux-pip-${{ matrix.os-version }}-unpacked
path: dist/linux-unpacked/*.7z
================================================
FILE: .github/workflows/CI-test.yml
================================================
name: CI-test
on:
push:
branches:
- main
paths-ignore:
- '**.md'
- LICENSE
pull_request:
paths-ignore:
- '**.md'
- LICENSE
workflow_dispatch:
jobs:
test:
strategy:
matrix:
os-version: ['ubuntu-latest']
runs-on: ${{ matrix.os-version }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: pnpm/action-setup@v4
with:
version: 10
- name: Test
run: |
pnpm install
pnpm run lint
pnpm run typecheck
pnpm run test
env:
SKIP_DOWNLOAD_CORE: true
GH_TOKEN: ${{ secrets.GH_TOKEN }}
================================================
FILE: .github/workflows/Release.yml
================================================
name: Release
on:
workflow_dispatch:
push:
tags:
- 'v*'
jobs:
windows:
strategy:
matrix:
os-version: ['x64', 'arm64']
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: pnpm/action-setup@v4
with:
version: 10
- name: build
run: |
pnpm install
pnpm fetchcore
pnpm run build:win-${{ matrix.os-version }}
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: zip-unpacked-x64
if: matrix.os-version == 'x64'
run: |
cd .\dist\win-unpacked
7z a -r Final2x-windows-${{ matrix.os-version }}-unpacked.7z *
- name: zip-unpacked-arm64
if: matrix.os-version == 'arm64'
run: |
cd .\dist\win-arm64-unpacked
7z a -r Final2x-windows-${{ matrix.os-version }}-unpacked.7z *
- name: upload-unpacked-x64
if: matrix.os-version == 'x64'
uses: actions/upload-artifact@v4
with:
name: Final2x-windows-${{ matrix.os-version }}-unpacked
path: dist/win-unpacked/*.7z
- name: upload-unpacked-arm64
if: matrix.os-version == 'arm64'
uses: actions/upload-artifact@v4
with:
name: Final2x-windows-${{ matrix.os-version }}-unpacked
path: dist/win-arm64-unpacked/*.7z
macos:
strategy:
matrix:
os-version: ['arm64']
runs-on: macos-14
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: pnpm/action-setup@v4
with:
version: 10
- name: build
run: |
pnpm install
pnpm fetchcore
pnpm run build:mac-${{ matrix.os-version }}
env:
ARCH: ${{ matrix.os-version }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: rename
run: |
cd ./dist
mv *.dmg Final2x-macos-${{ matrix.os-version }}-dmg.dmg
- name: zip-unpacked-arm64
if: matrix.os-version == 'arm64'
run: |
cd ./dist/mac-arm64
7z a -r Final2x-macos-${{ matrix.os-version }}-unpacked.7z *
- name: upload-dmg
uses: actions/upload-artifact@v4
with:
name: Final2x-macos-${{ matrix.os-version }}-dmg
path: dist/*.dmg
- name: upload-unpacked-arm64
if: matrix.os-version == 'arm64'
uses: actions/upload-artifact@v4
with:
name: Final2x-macos-${{ matrix.os-version }}-unpacked
path: dist/mac-arm64/*.7z
linux-pip:
strategy:
matrix:
os-version: ['x64']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: pnpm/action-setup@v4
with:
version: 10
- name: build
run: |
pnpm install
pnpm run build:linux-${{ matrix.os-version }}
env:
SKIP_DOWNLOAD_CORE: true
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: zip-unpacked
run: |
cd ./dist/linux-unpacked
7z a -r Final2x-linux-pip-${{ matrix.os-version }}-unpacked.7z *
- name: rename
run: |
cd ./dist
mv *.snap Final2x-linux-pip-${{ matrix.os-version }}-snap.snap
mv *.AppImage Final2x-linux-pip-${{ matrix.os-version }}-AppImage.AppImage
mv *.deb Final2x-linux-pip-${{ matrix.os-version }}-deb.deb
- name: upload-snap
uses: actions/upload-artifact@v4
with:
name: Final2x-linux-pip-${{ matrix.os-version }}-snap
path: dist/*.snap
- name: upload-AppImage
uses: actions/upload-artifact@v4
with:
name: Final2x-linux-pip-${{ matrix.os-version }}-AppImage
path: dist/*.AppImage
- name: upload-deb
uses: actions/upload-artifact@v4
with:
name: Final2x-linux-pip-${{ matrix.os-version }}-deb
path: dist/*.deb
- name: upload-unpacked
uses: actions/upload-artifact@v4
with:
name: Final2x-linux-pip-${{ matrix.os-version }}-unpacked
path: dist/linux-unpacked/*.7z
github:
needs: [windows, macos, linux-pip]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
path: asset
- name: Flatten asset directory
run: |
tree asset
mkdir dist
find asset -type f -print0 | xargs -0 -I{} cp "{}" dist/
cd dist && ls -l
- name: Create Release and Upload Release Asset
uses: softprops/action-gh-release@v2
with:
files: dist/*
================================================
FILE: .github/workflows/issue-helper.yml
================================================
name: issue-helper
on:
issues:
types: [opened, reopened, edited]
jobs:
check-inactive:
runs-on: ubuntu-latest
steps:
- name: close-issues
uses: actions-cool/issues-helper@v2
with:
actions: 'close-issues'
token: ${{ secrets.GH_TOKEN }}
inactive-day: 100
body: |
Hello! your issue has been closed because it has been inactive for a long time.
你好,你的 issue 因为长时间不活跃而被自动关闭。
こんにちは、お問い合わせは長期間活動がないため、閉じられました。
check-title:
runs-on: ubuntu-latest
if: github.event.issue.title == '[BUG] ' || github.event.issue.title == '[FEATURE] ' || (contains(github.event.issue.title, '[BUG]') == false && contains(github.event.issue.title, '[FEATURE]') == false)
steps:
- name: close issue
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment, add-labels, close-issue'
token: ${{ secrets.GH_TOKEN }}
issue-number: ${{ github.event.issue.number }}
labels: 'Invalid'
body: |
Hello @${{ github.event.issue.user.login }}, your issue has been closed because the title does not conform to our specification.
你好 @${{ github.event.issue.user.login }},为了能够进行高效沟通,我们对 issue 有一定的格式要求,你的 issue 因为标题不符合规范而被自动关闭。
こんにちは、@${{ github.event.issue.user.login }}さん、タイトルが仕様に準拠していないため、ご提案いただいた問題はクローズされました。
================================================
FILE: .github/workflows/issue-translator.yml
================================================
name: 'issue-translator'
on:
issue_comment:
types: [created]
issues:
types: [opened]
jobs:
translate:
runs-on: ubuntu-latest
steps:
- uses: usthe/issues-translate-action@v2.7
with:
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically.
================================================
FILE: .gitignore
================================================
node_modules
dist
out
*.log*
*.DS_Store
/resources/Final2x-core/
/outputs/
/.idea
/coverage/
================================================
FILE: .npmrc
================================================
shamefully-hoist=true
================================================
FILE: LICENSE
================================================
BSD 3-Clause License
Copyright (c) 2023, Tohrusky
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# Final2x



[](https://github.com/EutropicAI/Final2x/actions/workflows/CI-test.yml)
[](https://github.com/EutropicAI/Final2x/actions/workflows/CI-build.yml)
[](https://github.com/EutropicAI/Final2x/actions/workflows/Release.yml)


A cross-platform image super-resolution tool.
- News🎉: Final2x v4.0.0 is now available! It uses the [cccv](https://github.com/EutropicAI/cccv) backend, supporting custom models and more. See [custom model demo](https://github.com/EutropicAI/cccv_demo_remote_model).
- News🎉: Final2x v3.0.0 is now available, support Nvidia 50 series GPUs now!
### Screenshots
### Installation
##### [Download the latest release from here.](https://github.com/EutropicAI/Final2x/releases)
#### Windows
You can also use a package manager like winget or scoop to install and upgrade. Please note that the versions available through package managers may not always be the latest.
#### MacOS
```bash
sudo spctl --master-disable
# Disable Gatekeeper, then allow applications downloaded from anywhere in System Preferences > Security & Privacy > General
xattr -cr /Applications/Final2x.app
```
In first time, you need to run the command above in terminal to allow the app to run.
#### Linux
For Linux User, you need to install the dependencies first.
Make sure you have Python >= 3.9 and PyTorch >= 2.0 installed
```bash
pip install Final2x-core
Final2x-core -h # check if the installation is successful
apt install -y libomp5 xdg-utils
```
### Reference
The following references were referenced in the development of this project:
- [Final2x-core](https://github.com/EutropicAI/Final2x-core)
- [naive-ui](https://github.com/tusen-ai/naive-ui)
- [electron-vite](https://github.com/alex8088/electron-vite)
### License
This project is licensed under the BSD 3-Clause - see
the [LICENSE file](./LICENSE) for details.
### Acknowledgements
Feel free to reach out to the project maintainers with any questions or concerns~
================================================
FILE: README_i18n/README_zh.md
================================================
# Final2x
================================================
FILE: build/entitlements.mac.plist
================================================
com.apple.security.cs.allow-jitcom.apple.security.cs.allow-unsigned-executable-memorycom.apple.security.cs.allow-dyld-environment-variables
================================================
FILE: build/notarize.js
================================================
module.exports = async (context) => {
const { notarize } = require('@electron/notarize')
if (process.platform !== 'darwin')
return
console.log('aftersign hook triggered, start to notarize app.')
if (!process.env.CI) {
console.log(`skipping notarizing, not in CI.`)
return
}
if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) {
console.warn('skipping notarizing, APPLE_ID and APPLE_ID_PASS env variables must be set.')
return
}
const appId = 'com.final2x.app'
const { appOutDir } = context
const appName = context.packager.appInfo.productFilename
try {
await notarize({
appBundleId: appId,
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLEIDPASS,
})
}
catch (error) {
console.error(error)
}
console.log(`done notarizing ${appId}.`)
}
================================================
FILE: electron-builder.yml
================================================
appId: com.final2x.app
productName: Final2x
directories:
buildResources: build
icon: resources/icon.png
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
- '!resources/Final2x-core/**'
asarUnpack:
- resources/*.png
- resources/*.svg
- resources/*.ico
extraResources:
- from: resources/Final2x-core
to: Final2x-core
afterSign: build/notarize.js
win:
executableName: Final2x
nsis:
artifactName: ${name}-${version}-setup.${ext}
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
mac:
entitlementsInherit: build/entitlements.mac.plist
extendInfo:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
dmg:
artifactName: ${name}-${version}.${ext}
background: build/macosDMGbg.jpeg
window:
x: 100
y: 100
width: 480
height: 500
linux:
target:
- AppImage
- snap
- deb
maintainer: Tohrusky
category: Utility
appImage:
artifactName: ${name}-${version}.${ext}
npmRebuild: false
================================================
FILE: electron.vite.config.ts
================================================
import { resolve } from 'node:path'
import vue from '@vitejs/plugin-vue'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
export default defineConfig({
main: {
resolve: {
alias: {
'@main': resolve('src/main'),
'@shared': resolve('src/shared'),
},
},
plugins: [externalizeDepsPlugin()],
},
preload: {
plugins: [externalizeDepsPlugin()],
},
renderer: {
resolve: {
alias: {
'@renderer': resolve('src/renderer/src'),
'@shared': resolve('src/shared'),
},
},
plugins: [vue()],
},
})
================================================
FILE: eslint.config.js
================================================
import antfu from '@antfu/eslint-config'
export default antfu(
{
ignores: [
'dist',
'out',
'node_modules',
'build/*.js',
'resources/*.js',
],
rules: {
'no-console': 'off',
},
},
{
files: ['**/*.md'],
rules: {
'style/no-trailing-spaces': 'off',
},
},
{
files: ['**/*.yaml', '**/*.yml'],
rules: {
'yaml/plain-scalar': 'off',
},
},
{
files: ['**/*.ts'],
rules: {
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': 'allow-with-description' }],
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-empty-function': ['error', { allow: ['arrowFunctions'] }],
'@typescript-eslint/no-explicit-any': ['off'],
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-inferrable-types': 'off',
'node/prefer-global/process': 'off',
},
},
{
files: ['**/*.vue'],
rules: {
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': 'allow-with-description' }],
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-empty-function': ['error', { allow: ['arrowFunctions'] }],
'@typescript-eslint/no-explicit-any': ['off'],
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-inferrable-types': 'off',
'vue/require-default-prop': 'off',
'vue/multi-word-component-names': 'off',
},
},
)
================================================
FILE: package.json
================================================
{
"name": "Final2x",
"productName": "Final2x",
"version": "4.0.0",
"description": "A cross-platform image super-resolution tool.",
"author": "Tohrusky",
"homepage": "https://github.com/EutropicAI/Final2x",
"main": "./out/main/index.js",
"engines": {
"node": ">=18",
"pnpm": ">=8"
},
"scripts": {
"dev": "electron-vite dev",
"test": "vitest run --coverage",
"lint": "eslint . --fix",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "pnpm run typecheck:node && npm run typecheck:web",
"start": "electron-vite preview",
"build": "electron-vite build",
"postinstall": "electron-builder install-app-deps",
"fetchcore": "node ./resources/download-core.js",
"build:mac-arm64": "pnpm run build && electron-builder --mac --arm64 --publish=never",
"build:mac-x64": "pnpm run build && electron-builder --mac --x64 --publish=never",
"build:win-arm64": "pnpm run build && electron-builder --win --arm64 --dir --publish=never",
"build:win-x64": "pnpm run build && electron-builder --win --x64 --dir --publish=never",
"build:linux-x64": "pnpm run build && electron-builder --linux --x64 --publish=never",
"build:linux-arm64": "pnpm run build && electron-builder --linux --arm64 --publish=never"
},
"dependencies": {
"@intlify/unplugin-vue-i18n": "^6.0.8",
"@vicons/antd": "^0.13.0",
"@vicons/ionicons5": "^0.13.0",
"naive-ui": "^2.43.1",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0",
"sass": "^1.93.2",
"systeminformation": "^5.30.8",
"tree-kill": "^1.2.2",
"vfonts": "^0.0.3",
"vue": "^3.5.22",
"vue-i18n": "^11.1.12",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@antfu/eslint-config": "^5.4.1",
"@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/tsconfig": "^1.0.1",
"@electron-toolkit/utils": "^4.0.0",
"@electron/notarize": "^2.5.0",
"@vitejs/plugin-vue": "^5.2.4",
"@vitest/coverage-v8": "^3.2.4",
"@vue/test-utils": "^2.4.6",
"electron": "^27.3.11",
"electron-builder": "^26.0.12",
"electron-vite": "^4.0.1",
"eslint": "^9.37.0",
"extract-zip": "^2.0.1",
"jsdom": "^26.1.0",
"node-fetch": "^3.3.2",
"typescript": "^5.9.3",
"vite": "^7.1.11",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4",
"vue-tsc": "^3.1.0"
},
"pnpm": {
"onlyBuiltDependencies": [
"electron"
],
"overrides": {
"@parcel/watcher": "npm:empty-npm-package@1.0.0"
}
}
}
================================================
FILE: resources/download-core.js
================================================
// download Final2x-core from https://github.com/EutropicAI/Final2x-core/releases
// and put it in resources folder
const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args))
const child_process = require('node:child_process')
const fs = require('node:fs')
const path = require('node:path')
const coreDict = {
'macos-arm64':
'https://github.com/EutropicAI/Final2x-core/releases/download/v4.0.0/Final2x-core-macos-arm64.7z',
'windows-x64':
'https://github.com/EutropicAI/Final2x-core/releases/download/v4.0.0/Final2x-core-windows-x64.7z',
}
console.log('-'.repeat(50))
// 判断当前平台
const PLATFORM = process.env.PLATFORM || process.platform
// 判断当前平台架构
const ARCH = process.env.ARCH || process.arch
console.log(`Platform: ${PLATFORM}`, `| Arch: ${ARCH}`)
if (process.env.SKIP_DOWNLOAD_CORE) {
console.log('Skip download Final2x-core by env SKIP_DOWNLOAD_CORE')
process.exit(0)
}
async function downloadAndUnzip(url, targetPath) {
const zipFileName = path.basename(url)
const zipFilePath = path.join(targetPath, zipFileName)
const res = await fetch(url)
const dest = fs.createWriteStream(zipFilePath)
dest.on('finish', () => {
console.log(`Download ${zipFileName} success!`)
// 解压缩文件, 命令行调用 7z
const Final2xCorePath = path.join(targetPath, 'Final2x-core')
const unzipCmd = `7z x ${zipFilePath} -o${Final2xCorePath}`
console.log(`Unzip command: ${unzipCmd}`)
// 使用异步方式执行解压命令
child_process.exec(unzipCmd, (error) => {
if (error) {
console.error(`Unzip error: ${error}`)
return
}
console.log(`Unzip ${zipFileName} success!`)
// 删除压缩文件
fs.unlinkSync(zipFilePath)
console.log(`Delete ${zipFileName} success!`)
})
})
res.body.pipe(dest)
}
async function downloadAndUnzipCore(platform) {
const url = coreDict[platform]
if (!url) {
console.error('Invalid platform')
return
}
const targetPath = path.join(__dirname)
console.log(`Target path: ${targetPath}`)
if (fs.existsSync(path.join(targetPath, 'Final2x-core'))) {
console.log('Final2x-core already exists, skip download!')
return
}
if (!fs.existsSync(targetPath)) {
fs.mkdirSync(targetPath, { recursive: true })
}
await downloadAndUnzip(url, targetPath)
}
// 选择要下载的平台
let platformToDownload = ''
if (PLATFORM === 'darwin') {
platformToDownload = ARCH === 'arm64' ? 'macos-arm64' : 'macos-x64'
}
else if (PLATFORM === 'linux') {
console.error('Skip download Final2x-core for linux! Please use pip to install Final2x-core')
process.exit(0)
}
else if (PLATFORM === 'win32') {
platformToDownload = 'windows-x64'
}
else {
console.error('Unsupported platform!')
process.exit(1)
}
console.log(`Downloading Final2x-core for ${platformToDownload}...`)
// 执行下载和解压
downloadAndUnzipCore(platformToDownload)
.then()
.catch((err) => {
console.error(err)
})
================================================
FILE: src/main/getCorePath.ts
================================================
import { spawnSync } from 'node:child_process'
import path from 'node:path'
import { app } from 'electron'
const FINAL2X_CORE_NAME = 'Final2x-core'
const FINAL2X_CORE_PATH = 'Final2x-core/Final2x-core'
/**
* 获取 Final2x-core 的路径
* dev模式下,存放在项目根目录下的 resources
* 在 electron-builder 中配置 extraResources,ASAR 打包时将它放入 app.asar 同级目录
* @returns {string} Final2x-core 的路径
*/
export function getCorePath(): string {
if (!checkPipPackage()) {
if (process.env.NODE_ENV === 'development') {
return path.join(app.getAppPath(), 'resources', FINAL2X_CORE_PATH)
}
else {
return path.join(app.getAppPath(), '..', FINAL2X_CORE_PATH)
}
}
else {
return FINAL2X_CORE_NAME
}
}
export function checkPipPackage(): boolean {
const command = `${FINAL2X_CORE_NAME} -h`
const result = spawnSync(command, { shell: true })
return result.status === 0
}
================================================
FILE: src/main/index.ts
================================================
import { join } from 'node:path'
import { electronApp, is, optimizer } from '@electron-toolkit/utils'
import { IpcChannelInvoke, IpcChannelSend } from '@shared/const/ipc'
import { app, BrowserWindow, ipcMain, Menu, nativeImage, shell, Tray } from 'electron'
import appIcon from '../../resources/icon.png?asset'
import trayIcon from '../../resources/tray.png?asset'
import { openDirectory } from './openDirectory'
import { killCommand, runCommand } from './runCommand'
function createWindow(): void {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 670,
height: 470,
maxWidth: 870,
minWidth: 670,
maxHeight: 670,
minHeight: 470,
frame: false,
show: false,
autoHideMenuBar: true,
icon: nativeImage.createFromPath(appIcon),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
},
})
if (process.platform === 'darwin') {
app.dock.setIcon(nativeImage.createFromPath(appIcon))
}
// Ipc events
ipcMain.on(IpcChannelSend.EXECUTE_COMMAND, runCommand)
ipcMain.on(IpcChannelSend.KILL_COMMAND, killCommand)
ipcMain.handle(IpcChannelInvoke.OPEN_DIRECTORY_DIALOG, openDirectory)
ipcMain.on(IpcChannelSend.MINIMIZE, () => {
mainWindow.minimize()
})
ipcMain.on(IpcChannelSend.MAXIMIZE, () => {
if (mainWindow.isMaximized()) {
mainWindow.restore()
}
else {
mainWindow.maximize()
}
})
ipcMain.on(IpcChannelSend.CLOSE, () => {
if (process.platform !== 'darwin') {
app.quit()
}
else {
app.hide()
}
})
// mainWindow
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env.ELECTRON_RENDERER_URL) {
mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL)
mainWindow.webContents.openDevTools()
}
else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
let tray
function setTray(): void {
const Image = nativeImage.createFromPath(trayIcon)
Image.setTemplateImage(true)
tray = new Tray(Image)
const contextMenu = Menu.buildFromTemplate([
{
label: 'Open',
click: (): void => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
else {
BrowserWindow.getAllWindows()[0].show()
}
},
},
{
label: 'Exit',
click: (): void => {
app.quit()
},
},
])
tray.setToolTip('Final2x')
tray.setContextMenu(contextMenu)
}
// disable hardware acceleration for Compatibility for windows
app.disableHardwareAcceleration()
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.final2x.app')
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
setTray()
createWindow()
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0)
createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
let isQuitting = false
app.on('before-quit', async (event) => {
if (isQuitting) {
console.log('Quitting...')
return
}
console.log('Killing child process before quitting...')
event.preventDefault()
isQuitting = true
await killCommand()
app.quit()
})
================================================
FILE: src/main/openDirectory.ts
================================================
import { dialog } from 'electron'
/**
* @description Open a directory or file/multiple files
* @param _ Unused parameter, can be used for context in future
* @param p The properties of the dialog
*/
export async function openDirectory(_, p: Array<'openFile' | 'openDirectory' | 'multiSelections'>): Promise> {
try {
const { canceled, filePaths } = await dialog.showOpenDialog({ properties: p })
return canceled ? [] : filePaths
}
catch (error) {
console.error('Error opening directory dialog:', error)
return []
}
}
================================================
FILE: src/main/runCommand.ts
================================================
import type { Final2xCoreConfig } from '@shared/type/core'
import type { IpcMainEvent } from 'electron'
import type { ChildProcessWithoutNullStreams } from 'node:child_process'
import { spawn } from 'node:child_process'
import { once } from 'node:events'
import { IpcChannelOn } from '@shared/const/ipc'
import kill from 'tree-kill'
import { getCorePath } from './getCorePath'
let child: ChildProcessWithoutNullStreams | null = null
export async function runCommand(event: IpcMainEvent, coreConfig: Final2xCoreConfig): Promise {
let config_json = JSON.stringify(coreConfig.config)
// eslint-disable-next-line node/prefer-global/buffer
config_json = Buffer.from(config_json, 'utf8').toString('base64')
const resourceUrl = getCorePath()
let command = `"${resourceUrl}" -b ${config_json}`
if (!coreConfig.options.open_output_folder) {
command += ' -n'
}
console.log(command)
child = spawn(command, { shell: true })
child.stdout.on('data', (data) => {
event.sender.send(IpcChannelOn.COMMAND_STDOUT, data.toString())
})
child.stderr.on('data', (data) => {
event.sender.send(IpcChannelOn.COMMAND_STDERR, data.toString())
})
const [code] = await once(child, 'close')
event.sender.send(IpcChannelOn.COMMAND_CLOSE, code)
console.log(`Child process exited with code: ${code}`)
child = null
}
export async function killCommand(): Promise {
if (!child || !child.pid) {
console.error('Could not find child process, nothing to kill.')
return
}
const pid = child.pid
console.log(`Kill child process with pid: ${pid}`)
await new Promise((resolve) => {
kill(pid, (err) => {
if (err) {
console.error(`Failed to kill process: ${err.message}`)
}
else {
console.log('Process killed successfully')
}
if (child && child.pid === pid) {
child = null
}
resolve()
})
})
}
================================================
FILE: src/preload/index.d.ts
================================================
import type { ElectronAPI } from '@electron-toolkit/preload'
declare global {
interface Window {
electron: ElectronAPI
api: unknown
}
}
================================================
FILE: src/preload/index.ts
================================================
import { electronAPI } from '@electron-toolkit/preload'
import { contextBridge } from 'electron'
// Custom APIs for renderer
const api = {}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
}
catch (error) {
console.error(error)
}
}
else {
// @ts-ignore (define in dts)
window.electron = electronAPI
// @ts-ignore (define in dts)
window.api = api
}
================================================
FILE: src/renderer/index.html
================================================
Final2x
================================================
FILE: src/renderer/src/App.vue
================================================