Skip to content

v6.8.1 - #132

Merged
ajslater merged 861 commits into
mainfrom
develop
Aug 3, 2026
Merged

v6.8.1#132
ajslater merged 861 commits into
mainfrom
develop

Conversation

@ajslater

@ajslater ajslater commented Aug 3, 2026

Copy link
Copy Markdown
Owner
  • A timestamps file discarded for a config mismatch is now rewritten by the run
    that discards it, even when that run optimizes nothing. Previously the
    rejected file survived unchanged and warned about the same mismatch on every
    subsequent run. Requires treestamps 5.0.1.

ajslater and others added 29 commits July 5, 2026 15:56
img2webp rejects any frame duration <= 0 ("Invalid negative duration"),
aborting the whole animated-WebP repack. Some sources (e.g. scraped
animated WebPs) carry 0ms frame durations, so clamp each duration up to
img2webp's 1ms minimum in Img2WebPAnimatedLossless.img2webp_args.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Use a real PicoptSettings (not SimpleNamespace) in the img2webp
  duration test so config assignment type-checks.
- Mark the module-level pytest autouse fixtures with
  `# pyright: ignore[reportUnusedFunction]`; they are used by pytest,
  not referenced by name.
- Disable reportImplicitStringConcatenation: ruff format canonicalizes
  adjacent string literals to implicit concatenation, so the check can
  never be satisfied.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
radon cc flagged four functions at rank C. Extract behavior-preserving
helpers to bring each to rank A/B:

- WalkSkipper._is_skippable (16): split into _skip_reason and the
  disk-only _skip_reason_fs.
- Walk._count (12): extract the recursion-stop predicate _count_stops_here.
- pick_route_handler (12): fold the convert gate into _pick_convert_handler
  and extract _can_pack_repack for the repack check.
- Handler._cleanup_after_optimize (11): extract the _should_replace predicate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-directory .picopt.yaml resolution rebuilds the confuse config for
every directory in the tree, repeating the timestamps, ignore, after,
and memory-limit log lines each time. Gate them on the existing
print_summary flag, as set_format_handler_map already does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The treestamps program config was recorded from the run-level CLI
config, so identical options expressed via a tree root's .picopt.yaml
instead of flags always mismatched and every stamp was silently
discarded, re-optimizing whole trees.

Each tree now records its own effective config: the new Grove builds
one Treestamps per top path from DirConfig.get_tree_settings, which
resolves the root's .picopt.yaml beneath CLI and env. timestamps: true
at a tree root now enables stamping without -t (dry run and list still
disable it, and subdirectory configs still can't toggle it mid-walk).

The directory-config fingerprint is now per tree and value-based: it
hashes the parsed picopt section of each config strictly below the
root, so comment and formatting edits, -W rewrites, running with a
different set of top paths, and file-vs-directory targets of the same
tree no longer invalidate stamps — only real option changes do. The
root's own config is excluded because its options are recorded as
values.

Stamps discarded on a config mismatch now warn with the differing keys
(treestamps 4.1.1); stdlib logging is intercepted into loguru so the
warning renders through the shared console, and log messages escape
Rich markup so bracketed names like "Title [en]" display intact.

Migration: existing stamp files mismatch once after this upgrade (now
loudly); a single -N run preserves old stamps instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T7R5s6frL7jEykysAzjttq
PIL raises a plain OSError when it recognizes an image format but the
contents are corrupt (e.g. a BMP with pixel depth 0), which escaped the
detection probe and printed a full stack trace. Detection now wraps it
in UnreadableImageError, reported as a one-line warning that is also
counted and listed in the summary; the file is skipped untouched.

The per-file exception guards now skip the stack trace for any
PicoptError — extending the contract cli.main already had: expected
conditions print their message, only unexpected exceptions dump stacks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T7R5s6frL7jEykysAzjttq
- Drop the `cls: type[Any]` annotation on the RarDetector/ZipDetector
  `identify` classmethods; ty flagged the widened first parameter as an
  invalid override of `Detector.identify`.
- Replace remark's `no-duplicate-headings` with
  `no-duplicate-headings-in-section`. A changelog necessarily repeats
  `### Fixes` under each version; the in-section rule still catches real
  duplicates.

Also includes the in-progress dependency bumps and ruff rule additions that
were already in the working tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Add JPEG XL support

Read and write JPEG XL via the pillow-jxl-plugin codec, as a new
auto-discovered plugin. JXL is optimized by default and is now the
preferred still conversion target ahead of WEBP and PNG.

Only lossless JXL is re-encoded; lossy (XYB encoded) JXL is left alone,
the same policy lossy WebP already has. The codec exposes no such signal,
so picopt/pillow/jxl.py parses the codestream image header to read
xyb_encoded, and refuses files carrying JPEG reconstruction data, whose
re-encoding would silently destroy the ability to restore the source JPEG.

New --convert-jpeg-to-jxl converts JPEG to JXL by lossless bitstream
reconstruction, which is reversible byte for byte. It requires -c JXL as
well: picopt otherwise never converts from a lossy source, so -c JXL alone
must not sweep up a whole photo library. The codec only takes its
reconstruction path when PIL knows the source filename, which it does only
for path opens, so the tool opens by path and spills to a temp file for
inputs that have none. That makes it work for archive members too.

Three smaller things the codec needed:

- It registers no save_all handler, and pil_save always passes save_all,
  which raised KeyError. The single-frame handler is registered for it;
  animated JXL is unsupported by the codec regardless.
- Given no exif kwarg it reads EXIF off the opened image, which quietly
  defeated --strip-metadata. prepare_info now passes an empty exif.
- It rejects palette and other modes, so ImageHandler gained a
  prepare_image hook, symmetric with prepare_info, to substitute one.

Handlers can now declare CONFIG_ENABLED_KEY, a config flag that must be
true for them to be probed at all. Failing it leaves the handler out of
the probed handler map, which routing already reads as unavailable, so the
startup conversion banner stays accurate for free.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Gate WebP to JXL conversion behind --convert-webp-to-jxl

JPEG XL support in browsers and image tooling is still thin, so converting
away from WebP, which every browser reads, should be a deliberate act
rather than a side effect of asking for -c JXL.

JPEG and WEBP are both formats picopt processes by default, which means
the usual "name the source with -x" gate cannot express a choice about
them: -x JPEG and -x WEBP are no-ops. Each therefore carries its own flag,
and a plain -c JXL once again converts only GIF and PNG, as the README has
always described. The previous commit had quietly made WEBP a third
convert-by-default source.

The flag hangs off the handler class, and JxlLossless also serves PNG, GIF
and native JXL, which must stay ungated. So the WebP route now points at a
JxlFromWebP subclass that exists only to carry CONFIG_ENABLED_KEY.

Converting out of JXL into a more widely readable format stays ungated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* update deps

* update deps

* update deps

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Adding an option to the recorded timestamps config used to invalidate
every existing timestamp file, re-optimizing whole trees. Picopt now
passes its packaged defaults to treestamps 5.0.0, which fills keys
missing from either side before comparing, so only a changed option
value invalidates. Timestamp files written before 6.7.0 added the JXL
options, and before 6.6.2 added the directory config fingerprint, are
honored again.

Timestamp files now open with a comment saying what they are, that they
are machine-written, and that deleting one re-optimizes that tree. The
redundant treestamps_config block is gone; older files still load.

Discarding timestamps because a sub-directory .picopt.yaml changed now
names it that way instead of printing the internal fingerprint key.

Replace the local directory config fingerprint with the identical
treestamps implementation, pinned by golden digests upstream.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
merge_config_file truncated the target before writing, so a crash, a
full disk, or a kill during -w, -W, or --write-config-file could
destroy a hand-authored .picopt.yaml or user config. Write to a unique
sibling temp file, fsync, then replace, matching the discipline the
image handler and treestamps already use for files that matter less.

The temp name is unique rather than a fixed sibling so two concurrent
writers cannot unlink each other's temp mid-write, and the owner-only
mode is set before the rename so the target is never briefly wider.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Requires treestamps 5.0.1. A run that discarded a timestamps file for a
config mismatch only rewrote it if it also optimized something. A run
that optimized nothing left the rejected file on disk, so it warned
about the same mismatch every time.

uv.lock is left at treestamps 5.0.0 until 5.0.1 publishes to PyPI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ajslater
ajslater merged commit ee1de57 into main Aug 3, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant