Repository navigation
Give memory back after conversions and release the main window in the tray - #122
Merged
Merged
Conversation
Co-authored-by: Junie <junie@jetbrains.com>
…ema, tests, and localization
Co-authored-by: Junie <junie@jetbrains.com>
A Filee running as administrator (started by an installer run elevated) ignores drops from Explorer, can't read Explorer's selection, and the Filee that Explorer's menu starts crashed on the elevated Filee's mutex or waited 30 s on its pipe, all without a log line. - Read Filee's and Explorer's integrity levels at start-up. When Filee runs elevated and Explorer doesn't: log a warning, show a banner on the Home page and a one-time notice card, both with "Restart without administrator rights" (relaunch through Explorer's shell, after releasing the single-instance lock; a failed restart says so). - A later launch that can't open the lock or is refused by the pipe logs it and shows one message box (once per burst of launches); the pipe refusal no longer waits the whole 30 s. - Log unhandled exceptions (any thread, unobserved tasks, UI thread) through a logger set up before anything else, and one start-up line with version, path, install kind, integrity levels, OS and argument count. - UI watchdog: logs a UI thread blocked for more than 5 s and when it answers again. - Rate-limited diagnostics: forwarded launches received and handled, drag gestures that never reached the donut, shortcuts that found no selection (with the foreground window), the first drag over the drop zone. - FFmpeg -version / -encoders time out for real (background reads, kill on timeout); engine statuses are checked once in the background at start-up; unreachable network shares on PATH are skipped after 2 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new preset option, off by default, encodes H.264 and HEVC on the GPU: NVENC, Quick Sync and AMF on Windows, VideoToolbox on macOS (quality mode on Apple Silicon, bitrate mode on Intel Macs), NVENC and VA-API on Linux. Only encoders the bundled FFmpeg has are tried, best first, and each must pass a tiny test encode with the real options (cached per FFmpeg, encoder and bit depth) before it is used. Quality levels map to each encoder's constant-quality control, one step coarser than the x264/x265 CRF. HEVC keeps 10 bits, HDR colour tags and the hvc1 tag; H.264 stays 8-bit 4:2:0 as with x264. Stream copy rules are unchanged, and AV1, VP9 and the other codecs stay in software. When a hardware encode fails anyway, the partial output is removed and the file is encoded again with x264/x265; the failure goes to the log, not to the user. Cancellation is never retried. FFmpeg's version and encoder queries now read output asynchronously and kill the process at their time limit, so a hung FFmpeg no longer blocks engine detection. The preset editor shows the option for H.264 and HEVC targets, with English, Korean and Simplified Chinese text. ENGINES.md documents the encoders, the probe, the quality mapping and the software retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The direct video → JPG edge and the MJPEG poster frame changed every video → JPG conversion: rotate and flip presets failed with "can only turn images" and resizing was ignored. Video → JPG is again one PNG still frame that the image engines take on. Saving every frame now has edges of its own (ConversionEdge.FrameSequence) that the route planner takes only when asked for (Plan(..., frames: true)), and then as the whole route. JobQueue and the donut's availability check ask for it when Preset.SavesFramesOf the source: the preset saves every frame, its target is PNG or JPG and the file is a video. The step says so (ConversionStep.FrameSequence), so FFmpeg no longer guesses from extensions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Whether a file's frames are saved is decided once, from the format registry (a Video source, a PNG or JPG target by format id), instead of extension lists and guesses; the old fallback to the preset's target made intermediate steps of other routes (video → WEBP, PDF) write folders. The editor shows the option for "png" and "jpg" (there is no "jpeg" id) and saves it, with its interval, only while it is shown, so a box ticked for PNG and left behind by another target does nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without a frame rate filter the image2 muxer paced frames to a constant rate and wrote the slow parts of a phone video several times (119 pictures for a 60-frame clip). Frames now pass through with their own timestamps (-fps_mode passthrough; vfr with the interval's select filter), only the first real video stream is read (-map 0:V:0, as for the still frame), and the PNG / MJPEG encoder and image2 muxer are named. The test records a clip at 30 then 10 fps with FFmpeg and checks there are as many pictures as decoded frames. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frames folder was named by hand next to a JPG/PNG path the allocator had
reserved: "Skip" and the parallel-file reservation didn't apply, and a
cancelled or failed run left a half-filled folder. It is now a folder output
like "Extract" (Allocate("", "_frames")): "clip_frames", "clip_frames (2)",
skipped or overwritten by the preset's rule. FrameSequenceWriter writes into
a hidden folder next to it and moves the frames into place only when FFmpeg
has finished; whatever happens, the hidden folder is removed.
FfmpegRunner now waits (up to 10 seconds) for a killed FFmpeg to exit, so the
partial output it still held open can be deleted on cancel. A % in the path
is escaped for FFmpeg's number pattern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…holds Before anything is written, the frames are estimated (duration × frame rate ÷ interval). More than 100,000 pictures (an hour at 30 fps) fails with "Too many frames … raise 'Every Nth frame'", and pictures that won't fit on the output drive (a generous size guess per PNG or JPG) with "Not enough free disk space", both in the user's language with the numbers in brackets. A video that doesn't say its length or frame rate is not refused. Engines can now refuse a file with such a reason: ConversionRefusedException carries the error key and detail, which JobQueue shows instead of "The engine reported an error". The editor says below the interval how many pictures a minute of 30 fps video gives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MediaOptions.FramesPerSecond had no control in the editor and no preset set
it: removed, with FFmpeg's fps branch. The preset names are in sentence case
like the others ("Extract frames (PNG)"), the hint and description keys no
screen showed are gone, the Korean texts end in 해요 and the Chinese ones
have no fullwidth colon. The progress test expects three reports between
start and end again, as before this branch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Schema 18 is taken by PDF → PPTX (#98), so "Extract frames (PNG / JPG)" come with 19. They are inserted after "Split PDF", where a new library has them (BuiltInData.FramePresets), instead of at the end, and stay on no donut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ENGINES.md describes the frame route and when it is taken, the folder and file names, the interval, variable frame rate, square pixels, HDR (not tone-mapped) and 16-bit PNG, the JPG quality mapping, the 100,000-picture and disk space limits, and that a cancelled run leaves nothing behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…103) PDF -> PDF presets get a compression level (Smallest 72 dpi, Balanced 150 dpi, High quality 300 dpi), also with a page range or one file per page. Built in with PDFsharp and Magick.NET: pictures drawn above the level's resolution are scaled down (sizes found from the content streams, forms and annotation appearances), photos become JPEG, graphics stay lossless, soft masks follow their pictures, duplicate pictures are stored once, streams are deflated at the highest level, thumbnails and private application data are dropped. Text, fonts and drawings are untouched; a result that isn't smaller is replaced by the original, with a note in the toast and on the command line. Ghostscript (pdfwrite) is an opt-in second try; the smaller result is kept. New "Compress PDF" preset on the presets list (settings schema 20; 18 and 19 are taken by other open changes), editor controls, en/ko/zh-CN texts, ENGINES.md and README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Files of a type Filee doesn't know used to end at "This file type isn't supported". Now: - When the donut closes on files Filee can't read at all, a card says "Filee can't convert .ncm files yet" with "Request this format" (once per extension while Filee runs). - A toast whose files failed for an unknown type has the same button. - Both open the new "Format request" issue form with the title and the extension filled in. Only the extension goes in the link. - The form and the card say up front that encrypted or copy-protected (DRM) files can't be converted, whatever the format. Closes #117. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The variable moves settings, logs and downloaded engines to another folder, gives the copy its own single-instance mutex and pipe, and skips the startup entry, context menu and Explorer menu registration (and their removal in --uninstall-cleanup). Documented in CONTRIBUTING.md as a development aid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue #119: Filee stayed at 400+ MB private after a few image conversions and kept the main window's pages and visuals while in the tray. - Closing the main window to the tray now closes it for real a moment after hiding it (MainWindow.HideToTray); WindowService.ShowMain builds a new one where, and on the page, the last one was. MainWindowViewModel is created per window and disposed with it (subscriptions on UpdateService and the localizer, the current page). - MemoryTrimmer: 30 s after the last conversion finished (JobQueue.IsBusy must be false) and after the main window was released, drop the engines' caches (rhwp and CAD typefaces, Skia's glyph and resource caches), run an aggressive compacting collection and trim the working set on Windows. Logged as "Trimmed memory: private X MB -> Y MB". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The release closes the window after hiding it; a hidden window may report no position (the headless platform gave 0,0 in the full test run), which overwrote the place taken when the user closed it. The two release tests are one now: they share the window service, and headless tests can run while another one pumps the dispatcher. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SKGraphics.PurgeAllCaches ran on the trimmer's timer thread while the UI's render thread could be drawing with Skia, and crashed in native code (access violation in sk_pngencoder_encode on all three CI legs). Skia's caches are shared with the renderer; the trim now drops only Filee's own font caches, then collects and trims the working set as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test process still crashed while encoding a rendered frame (sk_pngencoder_encode, access violation) after the Skia purge was removed. LOH compaction and an Aggressive collection both move large objects, which native code can hold pointers into on the assumption that the large object heap doesn't move. The trim now runs a forced, compacting collection of the small object heaps only (5-8 MB less returned in the earlier measurements). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ost (diagnostic) - RenderTests.Save and the other frame captures dispose the frame at the end, so it can't be collected while Skia encodes it. - DIAGNOSTIC: the shared test host disposes its MemoryTrimmer, to tell whether the RenderTests crash on CI (sk_pngencoder_encode) comes from the idle trim. To be reverted or replaced once known. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the trim off in the shared test host, CI passed; with it on, the App tests crashed in Skia (sk_pngencoder_encode) about half a minute after a trim, on Windows and Linux. A compacting gen2 collection moves old objects that background collections leave in place, and native code can hold pointers into them. The trim now collects without compacting; garbage is freed and empty regions decommitted as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated SVGs often have "Q&A" in a <text> or " "/"©", which
stopped Svg.Skia's XML parser ("An error occurred while parsing
EntityName"). When an SVG can't be read, Filee escapes ampersands that
don't start a reference and turns HTML entities into character
references, then reads it again. Declared DTD entities, the DOCTYPE,
comments and CDATA are left alone; valid SVGs are never changed.
Closes #120.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to HEAD # Conflicts: # src/Filee.App/App.axaml.cs
… into HEAD # Conflicts: # src/Filee.Engines/Media/FfmpegConverter.cs
# Conflicts: # src/Filee.App/ViewModels/PresetEditorViewModel.cs # src/Filee.Core/Conversion/IConverter.cs # src/Filee.Core/Conversion/RoutePlanner.cs # src/Filee.Core/Presets/BuiltInData.cs # src/Filee.Core/Presets/Preset.cs # src/Filee.Core/Settings/AppSettings.cs # src/Filee.Core/Settings/UserDataStore.cs # src/Filee.Engines/Media/FfmpegConverter.cs # tests/Filee.Core.Tests/RoutePlannerTests.cs
# Conflicts: # README.ko.md # README.md # README.zh-CN.md # docs/ENGINES.md # src/Filee.Core/Conversion/IConverter.cs # src/Filee.Core/Conversion/JobQueue.cs # src/Filee.Core/Presets/BuiltInData.cs # src/Filee.Core/Settings/AppSettings.cs # src/Filee.Core/Settings/UserDataStore.cs
# Conflicts: # src/Filee.App/Services/RadialController.cs # src/Filee.App/Services/WindowService.cs # src/Filee.App/ViewModels/JobViewModel.cs # src/Filee.App/Views/ToastWindow.axaml # tests/Filee.App.Tests/RenderTests.cs
# Conflicts: # src/Filee.App/App.axaml.cs # src/Filee.App/Services/SingleInstance.cs # src/Filee.App/Views/MainWindow.axaml.cs
The close-action combo (General) and the PDF compression level (preset editor) came in after the single-selection guard: a null from the combo now puts the previous choice back, as for every other choice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #119.
After conversions Filee kept ~400 MB of private memory (and ~500 MB working set) in the tray until it quit. Now it returns to about the tray baseline 30 s after the last conversion. Closing the main window releases it instead of hiding it.
Measured
Release, self-contained, ReadyToRun build on Windows 11. Each run used a fresh
FILEE_DATA_DIR. Conversions were 12 × 12 MP PNG through two watch folders (→ JPG, → WEBP). Values are working set / private, in MB.A compacting trim (an earlier commit, measured the same day) ended 10–30 MB lower (110 MB private after conversions) but crashed Skia, see below. Conversion time and peak memory are unchanged within noise. Reopening the window takes about 60 ms longer because the window is created again. A trim takes 68–107 ms, for example
Trimmed memory: private 536 MB → 124 MB (managed heap 2 MB) in 107 ms.Why the trim doesn't compact
The first version compacted: LOH
CompactOnce,GCCollectionMode.Aggressive, and a Skia cache purge. On CI the App tests then crashed in native Skia (sk_pngencoder_encode, access violation, Windows and Linux) about half a minute after a trim. Taking the purge and the LOH compaction out didn't help. With the trim off in the test host, CI passed, and with a non-compacting trim it passes too.A compacting gen2 collection moves old objects that background collections leave in place, and native code can hold pointers into them. The trim now runs
GC.Collect(MaxGeneration, Forced, blocking: true, compacting: false)twice aroundWaitForPendingFinalizers(). Skia's caches are left alone.Changes
MemoryTrimmer): asked when a job finishes or the main window closes, and runs 30 s after the last request, never whileJobQueue.IsBusy. It:EngineCaches.Release(): rhwp/CAD font caches). Skia's caches stay: they are shared with the UI's render thread, and purging them while it drew crashed in native code on the first CI run.SetProcessWorkingSetSize(-1, -1)on Windows)MainWindow.HideToTray()hides it at once and closes it on the next dispatcher turn, unless it was shown again in between.WindowService.ShowMaincreates a new one, restoring position, size, maximized state and page for the session (the position is remembered only while the window is on screen).MainWindowViewModelis now per window andIDisposable. It drops its service subscriptions and disposes the current page.FILEE_DATA_DIR(dev/testing): runs a test copy next to the user's Filee.--uninstall-cleanuponly deletes the engine folder.Tried and dropped
GC settings made no difference after the trim (managed heap is 2–4 MB by then):
System.GC.Concurrent=false: 117 MBConserveMemory=7: 116 MBTieredPGO=false: 114 MBA one-off build with Avalonia's software renderer had 20–31 MB private where this has 62–118 MB. Most of what's left is the GPU (ANGLE/D3D) renderer. Changing the renderer is out of scope here; it needs CPU and donut animation checks.
Merging with #116
Only
MainWindow.axaml.csconflicts. Keep #116's code, add|| _releasingto the first condition inOnClosing, and replace its threeHide()calls withHideToTray().Tests
WeakReference), reopening before the release keeps it.MemoryTrimmer: runs once after quiet, not during a job, postponed by new requests.JobQueue.IsBusy. CAD text measures the same after the caches are released.FILEE_DATA_DIRin Core, Engines and App.dotnet formatis clean.Not verified by hand: tray "Open" and the title-bar X with a real mouse, multi-monitor/DPI position restore, macOS/Linux (the working-set trim is a no-op there).
🤖 Generated with Claude Code