Skip to content

Everyday media tools on 2.0.0, with audit fixes - #349

Merged
KnifeLemon merged 5 commits into
KnifeLemon:mainfrom
cre8ive-marketing:feature/media-tools-2.0
Oct 23, 2026
Merged

KnifeLemon merged 5 commits into
KnifeLemon:mainfrom
cre8ive-marketing:feature/media-tools-2.0

Conversation

@cre8ive-marketing

@cre8ive-marketing cre8ive-marketing commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

What does this change?

The everyday media tools from #336, rebased onto 2.0.0, with fixes from an audit and real FFmpeg testing. The eight presets are Compress image, Compress video, Video from images, Extract audio, Merge videos, Compress audio, Merge audio and Remove metadata. #336 has the details of each preset. This PR has the same three commits, rebased onto v2.0.0 with no conflicts, plus one commit of fixes:

Fix What was wrong How it was found
Merge videos copies only identical setups The copy path checked codec, size and pixel format only. Two 1080p H.264 clips from different encoders were copied into one MP4 with a single sample description. FFmpeg plays it, because it writes the second header in-band, but stricter players may not. Now copy also needs the same frame rate and the same codec setup (ffprobe -show_data_hash, the extradata hash); other clips are encoded. Review. A test with a High-profile and a Baseline clip, checked with trace_headers
Slideshow progress Late progress reports from the picture step (thread pool) set the files back to 100% while the video was still being encoded. The picture step now reports only into the first 30% (RunFileAsync(..., ceiling:)). A new test that reads the progress when the join starts
Compress never saves a bigger file Compress image, video and audio could write a copy larger than the source (a JPEG already at 60, a 96 kbit/s AAC). With OutputRule.OnlyIfSmaller, a result in the same format that isn't smaller is replaced by the source, with the notice Compress PDF already shows. Review
Remove metadata keeps MKV fonts MKV attachments (subtitle fonts) were dropped. Mapping them alone fails, because MKV refuses an attachment without its filename tag, so their own tags are kept: -map 0:t -map_metadata:s:t 0:s:t, only when the file has attachments, since FFmpeg rejects a specifier that matches nothing. A real FFmpeg test
Leaner Removed IMediaJoiner.CanJoinInto (never called). The slideshow frame size and fade length were options no UI could set; they are now constants. Review

main and v2.0.0 are still on settings schema 32, so the schema 33 upgrade step needs no renumbering.

How was it tested?

  • dotnet format --verify-no-changes
  • dotnet test on this branch, with Filee's FFmpeg 9.0.2 installed: Core 468, App 766, Engines 1456, Setup 134, Cli 27 and Platform 48 pass.
    • Engines: one fail, PandocMarkupTests.More_markup_languages_and_slides. It fails the same way on untouched v2.0.0 on this PC, because a system Pandoc there changes the Org/Markdown → PDF route.
    • App: MemoryTrimmerTests.A_new_request_moves_the_trim_on failed once while a movie encode loaded the CPU. Rerun alone, it passed 3/3 here and 3/3 on v2.0.0; this PR doesn't touch it.
  • New real FFmpeg tests:
    • a mixed-profile merge decodes with no errors and is re-encoded
    • a same-camera merge is copied, with packets equal from the second packet on
    • an MKV keeps its font attachment
    • a portrait phone video keeps its 90° rotation, and its location tag is gone
  • Stress: 300 photos of 12 MP through filee convert <folder> --preset video-from-images gave a 1920×1080 MP4 of 601 s (300 × 3 s minus 299 one-second fades) in 311 s.
  • Tried it in the app: built with build/build-installer.ps1 and installed over 2.0.0 on Windows 11. Through the installed filee-cli, a High plus a Baseline clip merged into a clean, re-encoded file.

Screenshots (UI changes)

No UI changes beyond #336.

Checklist

  • New UI strings are in all src/Filee.App/Assets/i18n/*.json files (no new strings in this round: the not-smaller case reuses notice.pdf_not_smaller, whose text is general)
  • New third-party code or binaries are listed in THIRD-PARTY-NOTICES.md (none)

cre8ive-marketing and others added 4 commits October 21, 2026 16:54
…remove metadata

New built-in presets (settings schema 33), offered on the back faces of the
images, PDF, video and audio donuts where they have room:

- Compress image: same format, quality 75 (never above a JPEG's own),
  zlib level 9 for PNG, ZIP for TIFF.
- Compress video: H.264 MP4 at the Small quality, at most 1080p.
- Video from images: pictures as a 1080p MP4, 3 s each, with 1 s
  crossfades (seconds and crossfade on/off in the preset editor).
- Extract audio: a video's sound as M4A, copied when the codec allows
  (MediaOptions.CopyAudioWhenPossible).
- Merge videos / Merge audio: clips joined in the order picked in the
  merge order window; same-codec clips are copied, others encoded to match
  the first clip; audio is decoded and joined without gaps.
- Remove metadata: same format; images drop EXIF/XMP/ICC (JPEG kept at its
  own quality), videos and audio copy their streams without tags, chapters
  or data tracks, PDFs drop document info and XMP.

JobQueue joins files through a new IMediaJoiner (FFmpeg); MediaJoining
holds the pure command lines. Strings in all nine languages. Unit tests
for the presets, migration, queue and command lines, and real FFmpeg
tests for every tool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Schema 33 replaces the plain M4A slice of the video donut's back face with Extract audio (the same M4A, the sound copied where it can be), or adds it with the other tools when there is room. Real FFmpeg tests pass the test's cancellation token to file calls (xUnit1051, dotnet format).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pt-BR translation merged since gets the new preset names, the slideshow options and the error for too many pictures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ss never bigger

- Merge videos copies clips only when their codec setup (ffprobe's
  extradata hash), frame rate and size match; others are encoded. FFmpeg
  puts a changed header in-band, but the MP4 keeps one sample description,
  which stricter players may not follow.
- Video from images: the PNG step reports into the first 30% only, so late
  progress reports no longer show the job as done while the video is made.
- Compress image / video / audio keep the source when the copy in the same
  format isn't smaller (OutputRule.OnlyIfSmaller), with the notice Compress
  PDF already shows.
- Remove metadata keeps an MKV's attachments (subtitle fonts) with their file
  name and type tags; MKV refuses an attachment without them.
- Removed IMediaJoiner.CanJoinInto (never called) and the slideshow size and
  fade length options nobody could set (now constants).

Tests: stricter copy check, progress at join time, keep-if-not-smaller, and
real FFmpeg runs for mixed-profile merges, MKV fonts and a portrait video.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@KnifeLemon

Copy link
Copy Markdown
Owner

Thanks a lot for this, @cre8ive-marketing, and for rebasing it onto 2.0.0 so quickly. The eight tools are a nice, coherent set, and the care that went into the copy path (frame rate, extradata hash), the MKV fonts and the real FFmpeg tests shows. The translations in all ten languages read well too.

We’d like to take it in. A few things we’ll fix on your branch before merging, so you don’t need to do anything unless you’d rather do them yourself:

  1. A failed join can delete an existing file. FfmpegConverter.JoinAsync probes the clips, checks the slide count and prepares the slides inside the try, and the catch deletes outputPath. With the “Overwrite” conflict policy that path can be the user’s existing a_merged.mp4, so a probe failure, a cancel or “too many pictures” removes it before FFmpeg wrote anything. We’ll move the probing/validation out of the try (as ConvertAsync does) and let the queue’s allocator undo only paths that were the job’s own.
  2. RunJoinedMediaAsync never discards its allocator. A failed or cancelled join keeps its output name reserved (a retry gets a_merged (2).mp4) and leaves a subfolder it made. It also needs oneFile: true, like the PDF merge, so “subfolder only for several files” puts the one merged file next to the source.
  3. OnlyIfSmaller skips NextToSource: because it sits in the else if chain, a file replaced by its original stays in the subfolder while the compressed ones move next to their source. We’ll make it a separate step.
  4. The 300-picture limit will be checked before the pictures are converted, plus tests for the cases above, and docs (ARCHITECTURE.md for IMediaJoiner, ENGINES.md for the FFmpeg and PDFsharp parts).

Two things we still need to settle on our side, so please hold off on changes there:

  • #326 adds a “Join videos” tool to the same video back face. We’ll decide which join stays, and the back faces have to stay within 12 slices together with #351’s “Trim transparent borders”.
  • Settings schema 33 is also taken by #351 and #381, so whichever lands later is renumbered when we merge.

We’ll also look at keeping the colour profile by default in “Remove metadata” (dropping it shifts the colours of Display P3 photos).

CI for fork runs is waiting for approval. We’ll start it once the fixes are in. Thanks again!

@cre8ive-marketing

Copy link
Copy Markdown
Collaborator Author

Thanks for the careful review! I'll take items 1–4 myself now and push them to this branch, with tests and docs, so there's nothing for you to redo. I'll leave the join choice with #326, the back-face slots and the schema number to you, and won't change the colour profile default either.

…rly picture limit

1. FfmpegConverter.JoinAsync no longer deletes the output: probing, the
   picture limit and preparing the slides happen before FFmpeg writes,
   and a join that fails or is cancelled is undone by the queue's
   allocator, which deletes only paths that were the job's own. With
   "Overwrite", a probe failure left the user's existing a_merged.mp4
   deleted before.
2. RunJoinedMediaAsync uses a one-file allocator (oneFile: true, like the
   PDF merge) and discards it on failure or cancel: the name is free again
   (no "a_merged (2)" on a retry), a subfolder it made goes, and "subfolder
   only for several files" puts the merged file next to the source.
3. OnlyIfSmaller is its own step before ZIP / next-to-source, so a kept
   original moves next to its source like a compressed copy.
4. The 300-picture limit (now MediaOptions.MaxSlides) is checked before any
   picture is converted; the engine keeps it as a backstop for multi-page
   pictures. Tests for each case, and docs: ARCHITECTURE.md for
   IMediaJoiner, ENGINES.md for the FFmpeg and PDFsharp parts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@KnifeLemon

Copy link
Copy Markdown
Owner

That's great, thank you @cre8ive-marketing! Go ahead with 1–4.
Once they're pushed, we'll start the CI run and go through it again.

We'll come back here with the decision on the join tool (this one or #326's), the back-face slots and the schema number before merging, so there's nothing else to change on your side for now.

@KnifeLemon

Copy link
Copy Markdown
Owner

As promised, the decision on the open points, @cre8ive-marketing: we're keeping this PR's "Merge videos" as Filee's join tool, and #326 will drop its own "Join videos". So merge-videos stays as it is.

The back faces and the schema number we'll settle ourselves when merging (images would go to 13 slices together with #351's trim, so one preset moves off that face). Nothing changes for you beyond items 1–4. Thanks!

@cre8ive-marketing

Copy link
Copy Markdown
Collaborator Author

Pushed items 1–4 in b3a663a:

  1. A failed join never deletes a file. JoinAsync probes, checks the limit and prepares the slides before FFmpeg writes, and doesn't delete the output any more. The queue's allocator undoes a failed or cancelled join, deleting only paths that were the job's own. New real-FFmpeg test: with "Overwrite", a join that fails on probing keeps the existing a_merged.mp4. The same test fails on the old code.
  2. RunJoinedMediaAsync uses oneFile: true and discards its allocator on failure or cancel. Tests: a retry gets a_merged.mp4 again, not (2), and the subfolder it made goes. With "subfolder only for several files", the merged file goes next to the source.
  3. OnlyIfSmaller is its own step before ZIP / next-to-source. Test: a kept original lands next to its source.
  4. The 300-picture limit is checked before any picture is converted. It's now MediaOptions.MaxSlides, and the engine keeps it as a backstop for multi-page pictures. Test: 301 pictures all fail with error.too_many_pictures and nothing is converted.
  5. Docs: IMediaJoiner in ARCHITECTURE.md's conversion flow, the FFmpeg and PDFsharp rows in ENGINES.md, and a short "Media tools" section there.

Local run with FFmpeg 9.0.2 and rhwp 0.8.7:

  • dotnet format --verify-no-changes is clean.
  • Core 472, Engines 1472, Setup 134, Cli 27 and Platform 48 pass. App 766 pass, 1 fail: MemoryTrimmerTests.A_new_request_moves_the_trim_on failed once under the full run's load. It passes 3/3 run alone, and this branch doesn't touch it.

Join tool, back-face slots, schema and the colour-profile default are left untouched for you.

@KnifeLemon

Copy link
Copy Markdown
Owner

Thanks @cre8ive-marketing, b3a663a covers all four points with tests and docs, and CI is green.

The rest is on our side before merging: the settings migration gets the next free schema number, one preset moves off the images back face to make room for #351's trim, "Only if smaller" will also apply when the results go into a ZIP (right now it's skipped there), and we'll add the strings to the French and Traditional Chinese files that landed in the meantime. Nothing else is needed from you.

@flexeykinDev flexeykinDev mentioned this pull request Oct 22, 2026
5 tasks done
@KnifeLemon
KnifeLemon merged commit 545c83b into KnifeLemon:main Oct 23, 2026
9 checks passed
KnifeLemon added a commit that referenced this pull request Oct 23, 2026
Merge #393, #432, #381 and #349 with their settings migrations renumbered
@KnifeLemon

Copy link
Copy Markdown
Owner

Merged through #464, thanks @cre8ive-marketing! What we did on top when merging:

  • the settings migration became schema 37;
  • the image donut's built-in back face drops "Extract Live Photo video" to stay within 12 slices with Fix #328, #301, #332, #345: HAR, raw H.265 / H.264, trim transparent borders, Arabic in .xls #351's trim (the preset stays in the library), and the migration only adds what fits;
  • "Only if smaller" now also applies when the results go into a ZIP;
  • the "too many pictures" detail counts the same files as the check;
  • strings for French, Traditional Chinese and Nepali, and ENGINES.md matched to the code.

It'll be in the next release.

KnifeLemon added a commit to cre8ive-marketing/Filee that referenced this pull request Oct 23, 2026
… zh-TW, ne

The everyday-tools step becomes v37 after KnifeLemon#381's v36, AppSettings.CurrentSchemaVersion
goes to 37, its offers table is Version37Offers, and the migration tests start from 36.

French, Traditional Chinese and Nepali get the twelve new strings (the media tool
preset names, the slideshow options and the too-many-pictures error) in en.json's
order, using each file's existing terms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KnifeLemon added a commit to cre8ive-marketing/Filee that referenced this pull request Oct 23, 2026
…d pictures

- The images donut's built-in back face holds 12 slices (ToolbarProfile.MaxSlices)
  again: with main's "Trim transparent borders" on it, KnifeLemon#349's two tools made 13.
  "Extract Live Photo video" leaves it and stays in the preset library, as its
  doc and docs/ENGINES.md already said. The v37 step already adds a tool only while
  a back face has room; new tests check a schema-36 images face (it takes
  "Compress image" and stays at 12) and that an upgraded library's face is the
  new built-in one.
- OnlyIfSmaller also applies under "Save as ZIP": the output there is still in
  the work folder, so the source is copied over it and goes into the ZIP.
- "Too many pictures" shows the number of pictures it counted (downloads that
  failed are left out), not every dropped file.
- docs/ENGINES.md: Video from images is in the library only, crossfades are at
  most half a picture's time, PDFsharp writes its own XMP packet after the
  metadata is removed, and the ZIP case above. CHANGELOG's back-face line no
  longer lists Live Photo video on the image donut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@KnifeLemon KnifeLemon mentioned this pull request Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants