Repository navigation
feat(engines): offline AI image upscaling and super-resolution up to 4x (NCNN) - #1064
Conversation
01fe528 to
a66f29d
Compare
KnifeLemon
left a comment
There was a problem hiding this comment.
Thanks, @rohitLovesKrishna. Offline AI upscaling is a good fit for Filee: it runs locally, nothing leaves the PC, and the model choice with its live size preview is well thought out. The direction is good. Before it can merge, a few parts need to follow how Filee already handles engines, downloads and presets.
Timing: the next release is planned for around 17:00 UTC today (November 3). To make it into that release, the changes below would need to be pushed by 16:20 UTC. If that's too tight (it's a big PR), no problem: it goes into the following release.
Must fix
1. Pin every download with a SHA-256
UpscaleModelService downloads an executable (upscayl-bin) and ten models without checking what arrives. The models also come from the main branch of upscayl/custom-models, which can change at any time. Every other engine Filee downloads is pinned by version and SHA-256:
- programs in
src/Filee.Engines/Infrastructure/engines.json, installed byEngineInstaller; - per-item data (the Tesseract languages) in
ocr-languages.json, with a URL pinned to a tag or commit and a SHA-256 per file.
Please do the same:
- The binary: add
upscayl-binas an engine package (one component per platform) inengines.json. - The models: add an
upscale-models.jsonpinned to a commit ofcustom-models(notmain), with the size and SHA-256 of each.binand.param.
2. Use the existing engine installer, not a second one
The new download service and model-manager window duplicate EngineInstaller and Settings → Engines. As a package there, the upscaler gets what the rest already has:
- the install / remove / update buttons and their progress;
- locking against a second Filee;
- clean removal of an engine in use, and no leftover
.partfiles (#1058); - the busy message in every language;
filee engineson the command line.
The models can then work like the OCR languages: downloaded on first use, or from a small list in the engine's card.
3. Run the program through ProcessRunner
UpscalerEngine builds one argument string ($"-i \"{actualInput}\" …") and starts Process itself. Please use ProcessRunner.RunAsync with an argument list instead. It escapes each argument (a path with a quote in it breaks today's string), and it applies a time limit, cancellation that ends the whole process tree, lower priority and captured output.
Also:
- Work folder: put the temporary PNGs in
step.WorkDirectory, notPath.GetTempPath(). The job cleans its work folder up; the system temp folder keeps whatever a crash leaves. - Thread safety:
outputLinesis a plainListwritten from two output threads.
4. Read pictures with ImageReader
new MagickImage(inputPath) skips what ImageReader.Read does for camera RAW, layered PSD/XCF, HEIC, LIVP, KRA and the rest. Please read the source through it (with ImageEncoder.SelectFrames), as the other picture tools do.
5. Licences, per model
upscayl-bin: it's AGPL-3.0. That's fine as a separate program downloaded on demand, which is how Ghostscript comes in. Please say so inTHIRD-PARTY-NOTICES.mdthe same way, without the "/ BSD-3-Clause".- The models: "various open-source licenses" isn't enough. Some upscaling models are non-commercial (CC BY-NC…), which Filee shouldn't offer as a plain download. Please list each model's licence in the catalog and in the notices, and leave out any that aren't free to use.
6. Existing users: a settings schema step
UpscalePresets() only reaches new libraries. Existing users need a migration step that adds the tool to the library, palette only, no donut slot (#806). main is at 63 (#1062), so take 64.
7. No special case in DonutMenu
The change in DonutMenu.cs makes the label smaller only when the id is tool-upscale or the text is "Upscale". Labels fit by their length in every language, so please drop this. If "Upscale" doesn't fit somewhere, the label sizing should be fixed for every slice, in its own PR.
Smaller
- ARM64: the release has
windows,macosandlinuxzips. Please check which architectures they hold. Where there's no build (Windows ARM64, Linux ARM64), the engine should say "not available on this PC" (EngineDownloads.UnavailableReason) rather than fail. - Tests: keep anything that needs the real program or network behind
Assert.SkipUnless(...), as the other engine tests do, so CI doesn't download it.
Happy to look again as soon as it's pushed.
a66f29d to
1fca295
Compare
|
Thanks for the thorough review and guidance, @KnifeLemon! All requested changes have been addressed:
|
KnifeLemon
left a comment
There was a problem hiding this comment.
Thanks, @rohitLovesKrishna, a big step forward. Much of the review is done well:
- Pins: I downloaded every pinned file (the three upscayl-bin zips and all 18 model files at commit
4b6d2cfa…). Each size and SHA-256 matches, and the zips hold x64 Windows, x64 Linux and universal macOS binaries. - ProcessRunner, ImageReader and schema 64: in place. Schema 64 is palette only, with tests, and merges cleanly with
main. - Cleanup: the non-commercial model and the
DonutMenuspecial case are gone, and CI is green.
It won't make today's 2.3.0 release, though: a few things still need to change, one of which breaks an existing feature.
Must fix
- Whisper is broken by the installer change. In
EngineInstaller,case "model":now falls into the newncnn-param/ncnn-binblock (onmainit fell into"bin").whisper-modelhas"kind": "model", so it gets a.whisper-modelmarker and returns early.Marker()then looks for.filee-component, so Whisper never counts as installed and re-downloads its 148 MB every time. Please keep"model"with"bin"and give the NCNN files their own kinds only. A test that installs akind: "model"component would catch it. - The Windows binary needs
VCOMP140.DLL, which isn't in the zip. On a PC without the VC++ Redistributable it won't start. Add thevcruntimecomponent to the package (as Ghostscript does) and make sure that DLL is copied next toupscayl-bin.exe. - Upscaling isn't a real conversion step.
UpscalerConverter's edges are plainDirectOnlyedges with no planner flag, so a preset withUpscale.Enabledthat goes throughJobQueueroutes via ImageMagick and is never upscaled. The window also callsUpscalerEngineitself, with its own output path, so there's no toast, no history and no output rules. Please hand the job to the conversion service like the other tools do (XTool.ConversionOf(...)), with a flag on the edge (as Translate, OCR and Captions have) so only upscale presets take it. - Format ids: "Same format" uses the file's extension as the format id (
UpscaleViewModel.cs:214)..jpeg,.tifand camera RAW extensions then reachImageReader.Read/ImageEncoder.ToMagickFormat, which throws. UseFormatRegistry.Detect(...).Id, andUpscaleTool.TargetFor(PNG for sources Filee can't write). The converter's format list also has"jpeg", which isn't a format id. - Deleting a model can crash Filee:
UpscaleWindow.axaml.csdeletes in anasync voidhandler with notry/catch. AnEngineBusyExceptionorIOExceptionthere ends the app. - Closing the window doesn't stop the job: the title-bar ✕ (and opening a second window) leaves
upscayl-binrunning hidden until its 15-minute limit. Cancel on close and dispose the view model. - Notices and docs: the NMKD models are by nmkd (not Philip Hofmann), and their two OpenModelDB links return 404.
docs/ENGINES.mdstill listsuniscale_restoreand the removed model manager. - Leftover keys: 17 keys from the removed manager window are unused in all 19 files (
engines.upscaler.manage,modal_title,search_placeholder,hardware_*,recommended_*,download*,delete_*,no_models,upscale.no_models_installed,engine.reason.needs_models). Please remove them.
Smaller
- Windows on ARM: on Windows 11 ARM64 the x64 zip is offered (the runtime maps to win-x64), not "unavailable" as the PR says. Whether it runs there under emulation with Vulkan is untested, so please say which you intend.
- Scale: the binary takes 2, 3 or 4 (
-s), but the UI allows 1×. - Output: the output is written with a bare
MagickImage.Write, so the preset's quality is ignored. Use the image encoder, which also handles ICO's size limit. - Small fixes: the model download shows no progress, and the window hard-codes colours instead of theme resources.
Happy to look again for the next release.
ea61387 to
0d630cb
Compare
# Conflicts: # src/Filee.Core/Presets/BuiltInData.cs # src/Filee.Core/Presets/Preset.cs # src/Filee.Core/Settings/UserDataStore.cs # tests/Filee.App.Tests/ToolbarPaletteViewTests.cs # Conflicts: # src/Filee.Core/Settings/UserDataStore.cs
0d630cb to
a2749a8
Compare
|
Thanks for the thorough review, @KnifeLemon! All points have been addressed in the latest commit:
|
80195a8 to
f51e07f
Compare
- Restore case 'model' to bin extraction in EngineInstaller to prevent Whisper regression - Add unit test verifying kind 'model' component installation - Add vcruntime to upscayl-bin on Windows and ensure VCOMP140.DLL is copied next to binary - Add Upscale flag to ConversionEdge, RoutePlanner, and route jobs via JobQueue - Use FormatRegistry.Detect and UpscaleTool.TargetFor to avoid raw extensions as format IDs - Remove 'jpeg' from upscaler supported format IDs - Safely handle model deletion with try/catch and error dialog - Add Material trash icon StreamGeometry and fix delete button visibility for installed models - Cancel token and dispose view model on window closing - Use ImageEncoder to respect preset quality and icon constraints - Show live progress percentage during model download in dialog - Use dynamic theme brushes in UpscaleWindow instead of hardcoded hex values - Update NMKD notices author to nmkd and clean up docs/ENGINES.md - Remove 17 leftover keys across all 19 language files
KnifeLemon
left a comment
There was a problem hiding this comment.
Thanks, @rohitLovesKrishna. A lot of this is much better now: everything in the first review is done, the schema step (v69) sits cleanly on main's 68, the zip pins match, the arguments use list form, and all 22 keys are in all 20 languages. CI is green too, but these need fixing, because CI can't reach any of them.
Must fix
1. VCOMP140.DLL is still not installed (EngineDownloads.cs:57)
This is the one from my last review. vc14_runtime leaves it out on purpose: conda-forge's packaging skips vcomp*.dll, and the package depends on vcomp14 for it. So on a PC without the VC++ Redistributable, upscayl-bin won't start. Add vcomp14 (the -h818238b_41 build that goes with the runtime you pin) and copy it in CopyRuntime.
2. Portrait photos come out sideways (UpscalerEngine.cs:52-58)
The frame is written to the temp PNG without AutoOrient, and ImageReader doesn't rotate. PNG has no orientation for ApplyOptions to see later, so a phone's portrait JPEG ends up on its side. EXIF and ICC are dropped there too, whatever KeepMetadata says. Please orient first and copy the profiles to the result.
3. The default "Same format" fails for many pictures (UpscaleTool.cs:29, UpscalerConverter.cs:13)
TargetFor returns any writable format, but the converter has edges for only 8. GIF, JXL, TGA, JP2 and PSD are writable, get their own id back and end in error.no_route. Fall back to PNG unless the id is one of the targets, or add the edges.
4. The window says it worked before anything ran (UpscaleViewModel.cs:214-221)
_start only enqueues, and straight after it the window shows upscale.success, even if the job fails later. Clicking again queues the same pictures again. Please close the window after starting and let the toast report the result.
5. Removing Upscayl can break Ghostscript's status (EngineDownloads.cs:57, EngineInstaller.cs:174)
vcruntime is now shared. Removing one package moves that folder away, so IsInstalled for the other turns false, and Ghostscript shows as not installed (and the other way round). Components that another installed package still uses have to stay.
6. No Vulkan means raw English stderr (UpscalerEngine.cs:93, docs/ENGINES.md:1919)
From its source (main.cpp:1193), upscayl-bin has no CPU mode: with no Vulkan device it exits -1. The user then sees its stderr untranslated, and the docs still claim a CPU route. Refuse with a translated ConversionRefusedException and correct the docs.
Smaller
UpscaleViewModel.cs:114:common.files_countdoesn't exist, so several pictures show the raw key.home.filesis the one.PresetEditorView.axaml:302: the slider allows 1×, which saves 1 and reopens at 4×. Minimum 2.UpscalerEngine.cs:48-49: fixed temp names in the job's work folder. If a later file exits 0 without output, it gets the previous file's picture. Use unique names.UpscalerEngine.cs:17: a 15-minute total limit can kill slow runs that are still working. A stall timeout fits better, like calibre's.upscale-models.json:10,25: the author still says "Philip Hofmann (NMKD)", from the last review.- Unused:
GpuId(Preset.cs:1700) andupscale.processingin the language files. - Comments say schema 65 (
BuiltInData.cs:334,338,Preset.cs:285,UpscaleToolTests.cs:2). - Unrelated to this PR:
ClipWindowTests.cs:919triples a wait, and there are stray blank lines inAppHost.cs,EnginesPageViewModel.csandEnginesPage.axaml.cs. UpscalerTests:Assert.NotNull(status)proves nothing. Items 2 and 3 and the PNG fallback need real tests.
Question still open
Is Windows on ARM meant to work? Please say either way, so we know whether it needs a refusal or an arm64 build.
Most of this was found by reading the code, not by running upscayl-bin, so tell me if something behaves differently on your machine.
- Add vcomp14 conda package to upscayl-bin Windows components and copy OpenMP runtime in CopyRuntime - Auto-orient frames before upscaling and preserve EXIF and ICC profiles on results - Fall back unsupported writable target formats to PNG in UpscaleTool.TargetFor - Connect UpscalerConverter edges from all readable image sources to supported target formats - Close upscale dialog immediately upon job start and delegate progress/completion to conversion toast - Retain shared package components (like vcruntime) during package uninstallation in EngineInstaller - Check AppLocalRuntime for missing DLLs and refuse cleanly when Vulkan GPU is absent - Replace fixed timeout with stall/inactivity timeout and use unique temp file names - Set PresetEditorView scale slider minimum to 2 and use home.files localization key in UpscaleViewModel - Remove unused GpuId property, remove unused upscale.processing key, and update schema 69 comments - Clarify offline Vulkan requirement in docs/ENGINES.md and remove inaccurate CPU claims - Add unit tests for shared component uninstallation, auto-orient, profile preservation, and format fallback - Revert out-of-scope edits in ClipWindowTests, AppHost, EnginesPageViewModel, and EnginesPage
2026-11-04.20-14-31_compressed.mp4Thanks for the thorough review, @KnifeLemon! All points have been addressed in the latest commit: Must Fix
Smaller Items & Cleanups
Regarding Windows on ARM (win-arm64):
|
KnifeLemon
left a comment
There was a problem hiding this comment.
Thanks, @rohitLovesKrishna. This round fixed a lot: the vcomp14 pin matches conda-forge's size and hash, portrait photos stay upright, the window closes after starting, shared components stay on removal, and Whisper still installs. All 22 keys are in all 20 languages, and your answer on Windows on ARM settles that question. A few things still need work, and CI can't reach them.
The merge conflict is on us: main is at schema 70 now, so I'll push the renumber to 71 and the matching test change. Please don't spend time on that part.
Must fix
1. HEIC output always fails (UpscalerEngine.cs:142, UpscaleTool.cs:15, UpscalerConverter.cs:17)
ImageEncoder.WriteImage writes through ImageMagick, which has no HEVC encoder. That's why HeicWriter exists. I ran the same steps with a stand-in for upscayl-bin and got no encode delegate for this image format 'HEIC'. With the default "Same format", a phone's HEIC photo goes through the whole GPU run and then fails.
The simplest fix is to remove heic from the targets, so HEIC falls back to PNG like the other formats, and update the tests and the docs. If you'd rather keep HEIC output, upscale to PNG and let the FFmpeg png → heic step write it.
2. Windows on ARM shows Upscayl as installed (EngineDownloads.cs:57, EngineInstaller.cs:129)
The empty component list means IsInstalled and IsUpToDate are true, because All() on nothing is true. I checked: Status=Installed, CanDownload=False. So the card says Installed with a Remove button, filee engines agrees, and the window tells the user to install it.
Please make an empty package not count as installed, and add a test.
3. The Vulkan refusal only works in part (UpscalerEngine.cs:107-114)
- Linux and macOS: with no usable device, upscayl-bin prints
Invalid GPU Deviceand returns -1. On Unix the exit code is 255, and none of the four strings match that text, so the user still sees raw English. Please check forInvalid GPU Device, and for exit code 255 as well as -1. - Successful runs: the stderr check also runs when the exit code is 0, and
-vprints the temp paths there. A run that worked is refused if a driver warning or a folder name contains "vulkan". Please only look at stderr after a failure.
A small method that takes the exit code and stderr would also be easy to test.
4. Unrelated changes in the language files
All 20 files lost their blank separator lines (250 lines) and had lines re-indented (198). Please restore them, so this PR only adds its keys and doesn't collide with every other language PR. The removed note on v64 in UserDataStore.cs:1003 can come back too.
5. Tests that test something (UpscalerTests.cs)
:179-180:Assert.NotNull(status)is still there and proves nothing.:102-145: this test calls Magick, notUpscalerEngine. RemovingAutoOrientfrom the engine wouldn't fail it, and it never looks at the result. Please move the steps around the program into methods the test can call, and check the written file.- Please add a test that
CopyRuntimeputs the DLLs of bothvcruntimeandvcomp14next to a zip component's program.
6. Attribution
upscale-models.json:10,25: the two NMKD models should saynmkd. They aren't by Philip Hofmann, and the notices already say nmkd.THIRD-PARTY-NOTICES.md:84:vcomp14isn't listed, and the runtime line only mentions Ghostscript.
Smaller
UpscalerEngine.cs:59-63: a multi-page TIFF silently loses every page but the first. Upscale each page, or refuse with a reason.UpscalerEngine.cs:133-138: a CMYK picture ends up as sRGB pixels tagged with its CMYK profile. Only copy the profile back when its colour space matches.UpscaleViewModel.cs:133-134: for a GIF the label says "Same format as source (gif)", but it writes PNG. UseTargetForfor the label.EngineInstaller.cs:288: locks are per package, butvcruntimeis now shared. The app andfilee engines installrunning together could write the same download. Please lock shared components too.- Unused:
upscale.successin the language files,IsCompleted, and_loggerinUpscalerEngineandUpscalerConverter. ko.json:1510: "필요합니다" should be "필요해요", like the rest of the file.
I found these by reading the code and running the steps around the program, not upscayl-bin itself, so tell me if something behaves differently on your machine.
|
Thanks for the detailed and thorough review, @KnifeLemon! All the points have been addressed in Must fix
Smaller
|
KnifeLemon
left a comment
There was a problem hiding this comment.
Thanks, @rohitLovesKrishna. This round is very close: I checked all six must-fix items in the code and they're done. HEIC is gone from the targets, Windows on ARM no longer shows as installed, the Vulkan check handles Linux and macOS and ignores successful runs, the language files only add their keys, the tests now call the engine's own steps, and the attribution is right. I also ran the output steps with a plain PNG standing in for upscayl-bin's result: portrait photos stay upright with their EXIF, and CMYK pictures no longer keep the CMYK profile.
The merge conflict is still on us. main is at schema 71 now, so I'll push the renumber to 72 and the matching test changes. Please don't spend time on that part.
Must fix
The PR doesn't build (UpscaleViewModel.cs:59-61)
Removing _isCompleted left its [ObservableProperty] behind, right above the one for _statusMessage, so Filee.App fails with CS0579 (duplicate attribute). CI hasn't run on this branch because of the conflict, so it couldn't tell you. Please delete that line and build the app before you push.
Smaller
UpscalerEngine.cs:124-134: multi-page TIFF to TIFF now keeps its pages, but TIFF to PNG, JPG or another target still upscales every page and writes only the first, with no message.ImageEncoder.Writealready does this properly (one file per page,_p1,_p2), so handing it the output allocator would cover both cases.UpscalerEngine.cs:47:SelectFramesalways gets"png", so an animated WebP on "Same format" comes out as a still WebP with no message. Please either keep the animation or say so.UpscaleViewModel.cs:173-177: on Windows on ARM the window says to install Upscayl in Settings → Engines, which isn't possible there. Please show that it isn't available on this PC.UserDataStore.cs:1004: the note came back asfirst).andmainhasfirst.), so the PR still changes that line.- Unused: the
ClearAside(EnginePackage)overload (EngineInstaller.cs:224),_executable(UpscalerConverter.cs:21), and the logger parameters in theUpscalerConverterandUpscalerEngineconstructors. UpscalerTests.cs:218: the comment says 8 targets, and there are 7 now.
I found these by building the PR and reading the code, not by running upscayl-bin itself, so tell me if something behaves differently on your machine.
|
Thanks for the thorough review @KnifeLemon ! I've addressed all the points in the latest commit:
|
KnifeLemon
left a comment
There was a problem hiding this comment.
Thanks, @rohitLovesKrishna. This round is very close. It builds, and I checked every point from last time in the code. I also ran the new flow with a plain resize standing in for upscayl-bin:
- portrait photos stay upright with their EXIF;
- CMYK pictures come out right;
- multi-page TIFF writes
_p1,_p2and so on; - animated WebP keeps its frames and timing;
- the work folder is empty afterwards, including after a failure or a cancel.
Handing the writing to ImageEncoder.Write was the right call.
The merge conflict is still on us. main is at schema 72 now, so I'll push the renumber to the next free number and the matching test changes. Please don't spend time on that part.
Must fix
Animations and long TIFFs have no limit (UpscalerEngine.cs:70-77)
Each frame is one run of upscayl-bin, and every upscaled frame stays in memory until the end. A 1000-frame 640×480 GIF at 4× means a thousand model loads and about 20 GB of pixels.
Please refuse this before the first run, right after SelectFrames, when there are more than 300 frames, with a ConversionRefusedException and its own message in all the language files. error.too_many_frames won't fit, because it talks about "Every Nth frame".
Smaller
UpscaleViewModel.cs:131and:184: on Windows on ARM the message now says the right thing, but always in English.UnavailableReasonalways returns its English text there, so the translatedengine.reason.unsupported_platformis never used. Please show the translated key directly and remove theupscayl-binline fromEngineDownloads.cs:70.UpscalerEngine.cs:68-70: an animated PNG saved as.pngcomes out as one frame with no message, even to WebP.MagickImageConverter.cs:85already handles this withApng.IsAnimated, so the same check here would cover it.- Unused: the
Microsoft.Extensions.Loggingusings (UpscalerConverter.cs:8-9), the two empty constructors, andConversionStep.Upscale, which nothing reads. UpscalerEngine.cs:130-138: ifFinishImagethrows,upscaledisn't disposed. Adding it to the list before callingFinishImagefixes that.
I found these by reading the code and running a stand-in, not upscayl-bin itself, so tell me if something behaves differently on your machine.
|
Thanks for the detailed review @KnifeLemon ! All feedback items have been addressed in the latest commit:
|
Conflicts were the flag lists main grew for Metadata (ConversionEdge, RouteStep, RouteRequest, Plan, CanConvert), the tool lists in BuiltInData and Preset, the engine lists and engines.json (ExifTool next to upscayl-bin), and the settings steps v69 to v72. Upscale now comes after Metadata in a new library. PlanThrough passes upscale by name, since Plan has metadata before it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Persian and Bengali came to main while this PR was open, so they had none of its 22 keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main took 69 to 72 (Translate preset, .jif, .java, Metadata). The step now comes last, puts the tool after Metadata as a new library has it, and falls back to after Add background, then the end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e test Main now checks after unpacking that a package holds its programs under the names for the running OS, so on Linux and macOS the fake package needs upscayl-bin, not upscayl-bin.exe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
HI, @KnifeLemon , is everything ok to merge this PR. |
Fix comment formatting in EngineInstallerTests.
KnifeLemon
left a comment
There was a problem hiding this comment.
Thanks, @rohitLovesKrishna. I checked every point from last time and they're all done: the 300-frame refusal comes before the first run with its own message in every language, Windows on ARM now shows the translated reason, animated PNGs keep their frames, the unused code is gone, and upscaled is disposed when FinishImage throws.
As promised, I pushed the merge with main, the schema renumber to 73 (after Metadata, which main added at 72), the upscale keys for the two new languages, Persian and Bengali, and a test fix: main now checks after unpacking that a package holds its program under this OS's name, so the runtime test's fake package needed upscayl-bin on Linux and macOS. CI is green, so I'm merging.
One more thing came from main: it now reads an AVIF image sequence as avis, and the upscaler still reads it as avif, so an animated AVIF comes out as _p1, _p2 stills. I'll fix that in a follow-up PR of ours, with a test.
A few smaller things for a follow-up, none blocking:
UpscalerEngine.cs:66-77: there's no limit on the size of the result. A 50 MP photo at 4× is about 800 MP, over 3 GB in memory before upscayl-bin's own, and 300 frames of 640×480 at 4× is still about 6 GB. A check on frames × width × height × scale², refused above 200 MP likePdfiumConverter.MaxStackedPixels, would cover both.UpscalerEngine.cs:59: the model is downloaded before the frame check, so a file that's refused anyway first downloads a model of up to 67 MB. Checking first would avoid that.UpscalerTests.cs:MaxFrames_limit_is_300only checks the constant. Moving the check into a small static method, likePrepareFrame, would let a test feed it 301 frames.
I found these by reading the code and running the tests, not upscayl-bin itself, so tell me if something behaves differently on your machine. Thanks for sticking with this one!
Main took schema 73 for the Upscale tool (KnifeLemon#1064), so folder inputs' ISO donut is now the v74 step, right after it. The i18n files keep main's text with the ISO keys added; the preset-order test leaves out to-iso as well as Metadata and Upscale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main reads an AVIF image sequence as "avis" so it keeps its frames (KnifeLemon#1230). The upscaler (KnifeLemon#1064) still read it as "avif", so every frame went through the GPU and came out as separate _p1, _p2 stills. It now picks the read format the way MagickImageConverter does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03.19-49-47_compressed.mp4
Hi @KnifeLemon,
This PR adds offline AI image upscaling and super-resolution (up to 4x) using local Vulkan/NCNN neural network models, bringing high-fidelity local image enhancement to Filee while strictly upholding Filee's offline privacy promise.
What this adds
100% Offline AI Super-Resolution Engine (
Filee.Engines.Upscale):upscayl-bin(NCNN).Curated Model Catalog (
UpscaleModelInfo):upscayl/custom-models):4xLSDIRCompactC3,RealESRGAN_General_x4_v3,RealESRGAN_General_WDN_x4_v3.4x_NMKD-Siax_200k,4x_NMKD-Superscale-SP_178000_G,4xLSDIR,4xLSDIRplusC,4xNomos8kSC,4xHFA2k,uniscale_restore.%LOCALAPPDATA%\Filee\engines\upscaler-models\.Engines Page Integration (
Settings → Engines):WindowService.ConfirmAsync) and localized error handling.Donut Wheel & Interactive Workflow (
UpscaleWindow):≤ 11.0 px) so text never clips.300 x 400 px → 1200 x 1600 px (4x).Presets Page & Preset Editor (
Settings → Presets):"tool-upscale"(UpscaleTool) so it can be managed and configured in Settings.PresetEditorViewdisplays an Upscale options card allowing users to configure default models and scales with full model descriptions and a shortcut to the model manager.Localisation & Attribution:
src/Filee.App/Assets/i18n/*.json), maintaining exact formatting conventions (it.jsonnon-indented, Korean in standard polite~해요tone).THIRD-PARTY-NOTICES.mdforupscayl-bin / upscayl-ncnnandUpscayl Custom Models.docs/ENGINES.mdunder## Image Quality Models (Offline AI Upscaler).Tests & Verification
tests/Filee.Engines.Tests/UpscalerTests.csverifying catalog metadata, GPU detection heuristics, argument generation, CLI path resolution, cancellation tokens, and full upscale cycles.dotnet testpassing (20/20).dotnet format --verify-no-changes --severity warnclean with 0 warnings.upstream/main(d4d4c066) with zero conflicts.