Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 45 additions & 36 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ jobs:
id-token: write
env:
RELEASE_TAG: ${{ github.ref_name }}
WINDOWS_CERTIFICATE_FILE: ${{ runner.temp }}/trainkit-signing.pfx
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}

steps:
Expand All @@ -46,7 +45,7 @@ jobs:
with:
enable-cache: false

- name: Validate tag and signing configuration
- name: Validate tag and configure optional signing
id: metadata
env:
WINDOWS_CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE_BASE64 }}
Expand All @@ -56,16 +55,22 @@ jobs:
if ($env:RELEASE_TAG -ne "v$version") {
throw "Tag $env:RELEASE_TAG does not match package version v$version"
}
if ([string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE_BASE64)) {
throw "WINDOWS_CERTIFICATE_BASE64 is required for release signing"
$hasCertificate = ![string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE_BASE64)
$hasPassword = ![string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE_PASSWORD)
if ($hasCertificate -ne $hasPassword) {
throw "WINDOWS_CERTIFICATE_BASE64 and WINDOWS_CERTIFICATE_PASSWORD must be configured together"
}
if ([string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE_PASSWORD)) {
throw "WINDOWS_CERTIFICATE_PASSWORD is required for release signing"
if ($hasCertificate) {
$certificateFile = Join-Path $env:RUNNER_TEMP "trainkit-signing.pfx"
[IO.File]::WriteAllBytes(
$certificateFile,
[Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_BASE64)
)
"WINDOWS_CERTIFICATE_FILE=$certificateFile" >> $env:GITHUB_ENV
"signed=true" >> $env:GITHUB_OUTPUT
} else {
"signed=false" >> $env:GITHUB_OUTPUT
}
[IO.File]::WriteAllBytes(
$env:WINDOWS_CERTIFICATE_FILE,
[Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_BASE64)
)
"version=$version" >> $env:GITHUB_ENV
"version=$version" >> $env:GITHUB_OUTPUT

Expand All @@ -78,40 +83,30 @@ jobs:
- name: Verify source
run: npm run verify

- name: Build signed Windows artifacts
- name: Build Windows ZIP
run: npm run make

- name: Verify packaged layout
run: npm run verify:package

- name: Verify Authenticode signatures
if: steps.metadata.outputs.signed == 'true'
shell: pwsh
run: |
$targets = @(
"out/TrainKit-win32-x64/TrainKit.exe"
Get-ChildItem "out/make/squirrel.windows/x64" -Filter "*.exe" |
Select-Object -ExpandProperty FullName
)
if ($targets.Count -lt 2) {
throw "Expected the application and installer executables"
}
foreach ($target in $targets) {
$signature = Get-AuthenticodeSignature $target
if ($signature.Status -ne "Valid") {
throw "Invalid Authenticode signature for ${target}: $($signature.Status)"
}
$target = "out/TrainKit-win32-x64/TrainKit.exe"
$signature = Get-AuthenticodeSignature $target
if ($signature.Status -ne "Valid") {
throw "Invalid Authenticode signature for ${target}: $($signature.Status)"
}

- name: Prepare release artifacts and checksums
shell: pwsh
run: |
$installer = "out/TrainKit-Windows-Installer-$env:version.zip"
Compress-Archive -Path "out/make/squirrel.windows/x64/*" -DestinationPath $installer -Force
$portable = "out/make/zip/win32/x64/TrainKit-win32-x64-$env:version.zip"
if (!(Test-Path $portable)) {
throw "Portable archive was not produced: $portable"
$archive = "out/make/zip/win32/x64/TrainKit-win32-x64-$env:version.zip"
if (!(Test-Path $archive)) {
throw "Windows archive was not produced: $archive"
}
@($installer, $portable) | ForEach-Object {
@($archive) | ForEach-Object {
$hash = (Get-FileHash $_ -Algorithm SHA256).Hash.ToLowerInvariant()
"$hash $(Split-Path $_ -Leaf)"
} | Set-Content "out/SHA256SUMS.txt" -Encoding utf8
Expand All @@ -128,19 +123,31 @@ jobs:
if (!$match.Success) {
throw "CHANGELOG.md does not contain a section for $env:version"
}
$provenance = @"
if ('${{ steps.metadata.outputs.signed }}' -eq 'true') {
$releaseNotice = @"
---

Windows artifacts are Authenticode-signed and accompanied by SHA-256 checksums and a GitHub build-provenance attestation.
The Windows executable is Authenticode-signed. The ZIP is accompanied by a SHA-256 checksum and GitHub build-provenance attestation.
"@
"$($match.Value.Trim())`n`n$($provenance.Trim())" |
} else {
$releaseNotice = @'
---

> [!WARNING]
> This release is currently unsigned. Windows SmartScreen or antivirus software may display a warning or false-positive detection. Download only from this GitHub release and verify the ZIP against `SHA256SUMS.txt` before running it.

Download the Windows ZIP, extract the entire archive to a writable folder on the drive where you want TrainKit stored, then run `TrainKit.exe`. The ZIP is the standard Windows distribution; there is no installer.

The ZIP is accompanied by a SHA-256 checksum and GitHub build-provenance attestation.
'@
}
"$($match.Value.Trim())`n`n$($releaseNotice.Trim())" |
Set-Content "out/RELEASE_NOTES.md" -Encoding utf8

- name: Attest release provenance
uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 # v3
with:
subject-path: |
out/TrainKit-Windows-Installer-${{ steps.metadata.outputs.version }}.zip
out/make/zip/win32/x64/TrainKit-win32-x64-${{ steps.metadata.outputs.version }}.zip
out/SHA256SUMS.txt

Expand All @@ -150,7 +157,6 @@ jobs:
shell: pwsh
run: |
gh release create $env:RELEASE_TAG `
"out/TrainKit-Windows-Installer-$env:version.zip" `
"out/make/zip/win32/x64/TrainKit-win32-x64-$env:version.zip" `
"out/SHA256SUMS.txt" `
--verify-tag `
Expand All @@ -160,4 +166,7 @@ jobs:
- name: Remove signing certificate
if: always()
shell: pwsh
run: Remove-Item -LiteralPath $env:WINDOWS_CERTIFICATE_FILE -Force -ErrorAction SilentlyContinue
run: |
if (![string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE_FILE)) {
Remove-Item -LiteralPath $env:WINDOWS_CERTIFICATE_FILE -Force -ErrorAction SilentlyContinue
}
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

All notable TrainKit changes are documented here. TrainKit follows semantic versioning.

## [1.2.1] - 2026-07-15

TrainKit 1.2.1 fixes NCNN graph compatibility, makes manifests opt-in, and adds individual-image workflows across every operation.

### Added

- Added individual-image selection and processing alongside the existing folder workflow for captioning, upscaling, tagging, and renaming.

### Changed

- NCNN input and output blobs are now discovered from the loaded graph automatically. Manual blob fields remain available only as overrides for graphs with multiple inputs or outputs.
- Resumable manifest files are now opt-in for normal runs. Dry runs still create a manifest, and resumed jobs continue updating the selected manifest.
- The matching NCNN `.bin` file is now discovered beside its `.param` graph unless an override is selected.
- The Windows ZIP is now the sole release format and standard distribution; the Squirrel installer and its runtime hooks were removed.
- Tagged releases can publish unsigned builds with an explicit SmartScreen/antivirus warning, while still signing and verifying the executable when both certificate secrets are configured.

### Fixed

- Fixed single-input NCNN graphs failing with `NCNN input blob not found: in0` when their tensor used another name.
- Kept the first per-image processing error visible when manifest persistence is disabled.

## [1.2.0] - 2026-07-14

TrainKit 1.2.0 completes the original processing roadmap, adds resumable and collision-safe batch execution, hardens the Electron/backend boundary, and restores a self-contained portable runtime layout.
Expand Down Expand Up @@ -148,6 +169,7 @@ TrainKit 1.2.0 completes the original processing roadmap, adds resumable and col

- Initial TrainKit release with local image captioning, Spandrel upscaling, batch renaming, and an Electron desktop interface.

[1.2.1]: https://github.com/LightCyan01/TrainKit/compare/v1.2.0...v1.2.1
[1.2.0]: https://github.com/LightCyan01/TrainKit/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/LightCyan01/TrainKit/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/LightCyan01/TrainKit/releases/tag/v1.0.0
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ User-visible changes must update `CHANGELOG.md` under the target version. The re

## Maintainer release setup

Tagged releases are fail-closed and must match `package.json` exactly (for example, version `1.1.0` requires tag `v1.1.0`). Configure these GitHub Actions secrets:
Tagged releases must match `package.json` exactly (for example, version `1.2.1` requires tag `v1.2.1`). Releases are unsigned by default. To Authenticode-sign the packaged executable, configure both GitHub Actions secrets:

- `WINDOWS_CERTIFICATE_BASE64`: Base64-encoded PFX code-signing certificate.
- `WINDOWS_CERTIFICATE_PASSWORD`: PFX password.
Expand All @@ -57,4 +57,4 @@ PowerShell can encode a PFX without printing binary data:
[Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\path\TrainKit.pfx"))
```

The release workflow verifies the application and installer Authenticode signatures, produces SHA-256 checksums, and creates a GitHub build-provenance attestation before publishing.
When both secrets are configured, the release workflow verifies the application Authenticode signature. Without them, it publishes an explicit unsigned-build warning. Every release contains only the standard Windows ZIP plus its SHA-256 checksum and GitHub build-provenance attestation.
35 changes: 17 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,28 @@
<a href="docs/architecture.md">Architecture</a>
</p>

TrainKit prepares image datasets without uploading them to a hosted service. Every batch is deterministic, cancellable, collision-aware, and recorded in a resumable manifest.
TrainKit prepares individual images or complete image folders without uploading them to a hosted service. Every run is deterministic, cancellable, and collision-aware; resumable manifest files are optional.

## What's new in 1.2.0
## What's new in 1.2.1

- NCNN `.param` + `.bin` upscaling with CPU/Vulkan and tiled inference.
- Local image tagging with JSON, training-text, and paired outputs.
- Standard multimodal, BLIP, and InstructBLIP caption adapters with auto-detection.
- Dry runs, collision policies, atomic manifests, cancellation, and failed-item resume across every operation.
- A self-contained packaged Python runtime and logs stored beside the application.
- A hardened Electron/backend boundary plus signed-release, checksum, and provenance automation.
- Automatic NCNN input/output blob discovery instead of hard-coded `in0` and `out0` names.
- Individual-image selection for captioning, upscaling, tagging, and renaming.
- Manifests disabled by default, with explicit save, dry-run, and resume options.
- First processing errors remain visible even when no manifest is written.
- A single standard Windows ZIP distribution with no installer.

See the [complete 1.2.0 changelog](CHANGELOG.md#120---2026-07-14) for fixes, security changes, migration notes, and verification results.
See the [complete 1.2.1 changelog](CHANGELOG.md#121---2026-07-15) for details.

## Features

- **Captioning:** local standard Transformers multimodal-chat, BLIP, and InstructBLIP models; auto-detection, explicit preload/unload, prompt control, and token-level cancellation.
- **Upscaling:** safe `.safetensors` models through Spandrel, or NCNN `.param` + `.bin` models through the Python bindings with CPU/Vulkan and tiled inference.
- **Upscaling:** safe `.safetensors` models through Spandrel, or NCNN `.param` + `.bin` models through the Python bindings with automatic blob discovery, CPU/Vulkan, and tiled inference.
- **Tagging:** local Hugging Face image-classification models with threshold/top-K controls and scored JSON, training-text, or paired sidecars.
- **Renaming:** natural input ordering, configurable zero padding, optional visual-duplicate filtering, and atomic copies.
- **Safe batch behavior:** dry-run manifests, `fail`/`skip`/`rename`/`overwrite` collision policies, per-file status, atomic output replacement, and failed-item resume.
- **Safe processing:** individual-image or folder input, `fail`/`skip`/`rename`/`overwrite` collision policies, per-file status, atomic output replacement, and optional resumable manifests.
- **Hardened desktop boundary:** sandboxed renderers, narrow IPC, user-granted file capabilities, an authenticated ephemeral loopback backend, and restricted navigation.

Input images can be PNG, JPEG, BMP, or WebP. Upscaled output can be PNG, JPEG, BMP, or WebP.
Input can be one PNG, JPEG, BMP, or WebP image or a folder containing those formats. Upscaled output can be PNG, JPEG, BMP, or WebP.

## Install a release

Expand All @@ -54,7 +53,7 @@ TrainKit currently targets 64-bit Windows 10/11. Before the first launch, instal
1. [uv](https://docs.astral.sh/uv/getting-started/installation/)
2. [Microsoft Visual C++ Redistributable x64](https://aka.ms/vs/17/release/vc_redist.x64.exe)

Download the signed installer or portable archive from [GitHub Releases](https://github.com/LightCyan01/TrainKit/releases). On first launch, TrainKit uses the committed `uv.lock` to install Python 3.12 and the backend dependencies directly under `resources/backend` in the TrainKit folder. Setup downloads and temporary files stay there and are removed after a successful install. This requires a network connection and several gigabytes of free space.
Download the Windows ZIP from [GitHub Releases](https://github.com/LightCyan01/TrainKit/releases), extract the entire archive to a writable folder on the drive where you want TrainKit stored, and run `TrainKit.exe`. The current release is unsigned, so Windows SmartScreen or antivirus software may warn; download only from the GitHub release and verify `SHA256SUMS.txt`. On first launch, TrainKit uses the committed `uv.lock` to install Python 3.12 and the backend dependencies directly under `resources/backend` in the TrainKit folder. Setup downloads and temporary files stay there and are removed after a successful install. This requires a network connection and several gigabytes of free space.

Persistent session logs are written to `logs` beside `TrainKit.exe`. The **Logs** panel shows the exact current path and can open either the folder or current log. Older builds used `%APPDATA%\TrainKit\backend-runtime`; a successful self-contained setup removes that obsolete generated runtime.

Expand All @@ -81,23 +80,23 @@ npm run package
npm run verify:package
```

`npm run make` creates the Squirrel installer and portable ZIP. Local packages are unsigned unless the signing environment variables described in [CONTRIBUTING.md](CONTRIBUTING.md) are set.
`npm run make` creates the standard Windows ZIP. Local packages are unsigned unless the signing environment variables described in [CONTRIBUTING.md](CONTRIBUTING.md) are set.

## Using batch manifests
## Using optional manifests

Each new job writes a manifest to:
Normal runs do not write a manifest. Enable **Save resumable manifest** when you want progress saved to:

```text
<output>/.trainkit/manifests/<job-id>.json
```

Use **Dry run** to preflight source ordering and destinations without processing. A resume uses the same operation and the same input/output directories, preserves completed and skipped items, and retries failed items. Manifests are treated as untrusted input: paths outside the selected roots are rejected.
Dry runs always write a manifest because the plan is their output. A resume uses the same operation and input/output paths, preserves completed and skipped items, retries failed items, and updates the selected manifest. Manifests are treated as untrusted input: paths outside the selected roots are rejected.

See [model support](docs/model-support.md) for model layouts and NCNN assumptions, [architecture](docs/architecture.md) for the desktop/backend trust boundaries, and the [changelog](CHANGELOG.md) for complete release history.

## Project status

Version 1.2.0 completes the original NCNN, additional caption-adapter, and image-tagging roadmap. New feature proposals and model-compatibility reports are welcome through GitHub issues.
Version 1.2.1 completes the original processing roadmap and improves NCNN compatibility, input selection, and output control. New feature proposals and model-compatibility reports are welcome through GitHub issues.

## Contributing and security

Expand Down
9 changes: 5 additions & 4 deletions backend/models/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class BatchRequest(BaseModel):
save_path: str
collision_policy: CollisionPolicy = "fail"
dry_run: bool = False
save_manifest: bool = False
resume_manifest_path: str | None = None


Expand All @@ -32,8 +33,8 @@ class UpscaleRequest(BatchRequest):
tile_size: int = Field(default=512, ge=64, le=4096)
tile_overlap: int = Field(default=16, ge=0, le=512)
ncnn_model_bin_path: str | None = None
ncnn_input_blob: str = "in0"
ncnn_output_blob: str = "out0"
ncnn_input_blob: str | None = None
ncnn_output_blob: str | None = None
ncnn_scale: int = Field(default=4, ge=1, le=16)
ncnn_use_vulkan: bool = True

Expand Down Expand Up @@ -69,8 +70,8 @@ class ModelInfoRequest(BaseModel):
backend: Literal["spandrel", "ncnn"] = "spandrel"
model_bin_path: str | None = None
scale: int = Field(default=4, ge=1, le=16)
input_blob: str = "in0"
output_blob: str = "out0"
input_blob: str | None = None
output_blob: str | None = None
use_vulkan: bool = True


Expand Down
Loading
Loading