GithubHelp home page GithubHelp logo

ajslater / picopt Goto Github PK

View Code? Open in Web Editor NEW
105.0 105.0 11.0 17.88 MB

A multi format lossless image optimizer that uses external tools

License: GNU General Public License v3.0

Python 88.34% Shell 3.30% Dockerfile 1.04% JavaScript 3.04% Makefile 4.28%

picopt's People

Contributors

ajslater avatar crass00 avatar darwinawardwinner avatar dmgawel avatar terite avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

picopt's Issues

Picopt crashes on broken files in format detection

don't have a log, unfortunately, as my computer overheated and rebooted in an unrelated event, and my terminal windows closed. I'm using picopt 1.4.4 as installed by pip.

I'm running it with the version of Pillow installed by pip, and previously it would crash when PIL/Pillow threw an IndexError: string index out of range. After checking StackOverflow for a possible solution, I set LOAD_TRUNCATED_IMAGES to True in Pillow's ImageFile.py, and now it crashes (not sure if it's on the same files) when PIL throws an exception about a .png file's IDAT checksum being incorrect. Both crashes take place in the format detection stage

Seeing as I have 300 gigs of nearly unsorted images to optimize for a project, it'd be nice if it gracefully handled errors, maybe with an option to log the filenames of broken files.

Enhancement: use temporary directory

The storage on which picopt is being run can be a bottleneck when generating all the optimised files.

It would be nice if picopt could take a --tempdir flag to copy the initial file there, optimise it, and replace the original file with the optimised one if it's better.

Unable to run on Windows with Python 3.12

I am unable to use this package on Windows, despite it being flagged as "OS Independent" on the PyPi package page.

Error:

PS D:\Test picopt> picopt ?
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "C:\Users\<REDACTED>\AppData\Local\Programs\Python\Python312\Scripts\picopt.exe\__main__.py", line 4, in <module>
  File "C:\Users\<REDACTED>\AppData\Local\Programs\Python\Python312\Lib\site-packages\picopt\cli.py", line 9, in <module>
    from picopt import PROGRAM_NAME, walk
  File "C:\Users\<REDACTED>\AppData\Local\Programs\Python\Python312\Lib\site-packages\picopt\walk.py", line 17, in <module>
    from picopt.handlers.factory import create_handler
  File "C:\Users\<REDACTED>\AppData\Local\Programs\Python\Python312\Lib\site-packages\picopt\handlers\factory.py", line 27, in <module>
    from picopt.pillow.webp_lossless import is_lossless
  File "C:\Users\<REDACTED>\AppData\Local\Programs\Python\Python312\Lib\site-packages\picopt\pillow\webp_lossless.py", line 8, in <module>
    from mmap import PROT_READ, mmap
ImportError: cannot import name 'PROT_READ' from 'mmap' (unknown location)

I have Python 3.12 installed, and I installed picopt using 'pip'. The installation succeeded without any warnings.

From what I was able to gather, mmap is a Linux-only utility.

AttributeError: 'dict' object has no attribute 'iteritems'

hi, i've installed picopt on my mac via pip but it doesn't seems to work here

osx 10.11 python 3.5

did
pip install picopt in my venv
then when i launch it i have

File "/dir/venv/bin/picopt", line 11, in <module>
  sys.exit(main())
File "/dir/venv/lib/python3.5/site-packages/picopt/cli.py", line 185, in main
  run(sys.argv)
File "/dir/venv/lib/python3.5/site-packages/picopt/cli.py", line 179, in run
  process_arguments(raw_arguments)
File "/dir/venv/lib/python3.5/site-packages/picopt/cli.py", line 132, in process_arguments
  Settings.update(arguments)
File "/dir/venv/lib/python3.5/site-packages/picopt/settings.py", line 39, in update
  for key, val in settings.__dict__.iteritems():
AttributeError: 'dict' object has no attribute 'iteritems'

is there smth i forgot?

thanks for your great work ;-)

Decreases File Size

If used with jpegrescan the file size is increased:

albums/cvjm-kornmarkt/internationale_arbeit/Romania-Mission-Trip-2013/Savinesti (20).JPG: 6.01% (70.9 kiB)
albums/cvjm-kornmarkt/internationale_arbeit/Romania-Mission-Trip-2013/CVJM_Cristuru (51).JPG: 2.01% (21.6 kiB)
albums/cvjm-kornmarkt/internationale_arbeit/Romania-Mission-Trip-2013/Roma_Dorf_Csekefalva (82).JPG: 5.57% (57.1 kiB)
albums/cvjm-kornmarkt/internationale_arbeit/Romania-Mission-Trip-2013/Roma_im_CVJM_Befalva (45).JPG: 6.49% (64.3 kiB)
albums/cvjm-kornmarkt/internationale_arbeit/Romania-Mission-Trip-2013/Roma_Dorf_Csekefalva (116).JPG: 5.70% (74.0 kiB)
albums/cvjm-kornmarkt/internationale_arbeit/Romania-Mission-Trip-2013/Roma_Dorf_Csekefalva (81).JPG: 6.34% (57.0 kiB)
albums/cvjm-kornmarkt/internationale_arbeit/Romania-Mission-Trip-2013/Roma_im_CVJM_Befalva (78).JPG: 3.55% (36.1 kiB)
albums/cvjm-kornmarkt/internationale_arbeit/Romania-Mission-Trip-2013/Piatra_Neamt (45).JPG: 2.95% (30.9 kiB)
albums/cvjm-kornmarkt/internationale_arbeit/Romania-Mission-Trip-2013/Schaessburg (5).JPG: 6.41% (57.2 kiB)

I had not a single decrease in file size.

SyntaxError: broken PNG file (incomplete checksum in 'IDAT')

Can't optimize file:

Getting error:
SyntaxError: broken PNG file (incomplete checksum in 'IDAT')

I can see that the file is not really broken. Imageoptim eats it normally...
But i can not force picopt to eat it.
Recursive optimization on my server is stuck
0f3147b7f932f467a518e1bb4fa7384751710c30 1

Support MPO

Almost all of my jpeg pictures from various cameras aren't compressing:

picopt -r *
Didn't optimize any files.

Stepping through the code the file is being recognized by PIL as MPO. These are jpeg files with a smaller photo embedded as a thumbnail, commonly done by cameras to speed viewing.

Here's some discussion:
python-pillow/Pillow#1138

  1. Can picopt be updated to compress these jpg files? I have tried jpegrescan and it successfully compresses the files.
  2. Figuring this out was a PITA because the error message is not helpful:
picopt -r P1000223.JPG
Didn't optimize any files.

Can picopt better describe why it won't optimize the file, at least when a single file targeted? Maybe something like one of "Not an image or "Image type foo not recognized"

git clone jpegrescan

The
git clone [email protected]:kud/jpegrescan.git
from the README should be
git clone https://github.com/kud/jpegrescan.git
as suggested on the github page.

The difference is the first link can run afoul if public keys are not setup whereas the second still works.

Support oxipng

oxipng is a PNG optimizer written in Rust that began as a rewrite of optipng during the years when it was thought dead. Its CLI syntax is very similar and from what I've seen, it's almost a drop-in replacement and just better than optipng in every way.

The biggest improvement is in the speed. Below are some quick tests on a set of 40 PNG frames extracted from a video, with execution time measured using /usr/bin/time -p. optipng is version 0.7.7 and oxipng is 8.0.0.

optipng -o1: 32.8% size reduction, time:

real 158.23
user 157.75
sys 0.32

oxipng -o1: 38.4% size reduction, time:

real 66.70
user 65.69
sys 0.95

optipng -o2: 32.7% size reduction, time:

real 314.14
user 313.22
sys 0.35

oxipng -o2: 39.3% size reduction, time:

real 117.64
user 137.36
sys 1.02

optipng -o3: 32.9% size reduction, time:

real 633.62
user 632.56
sys 0.44

oxipng -o3: 41.2% size reduction, time:

real 204.86
user 615.39
sys 3.33

(I couldn't be asked to test further, since optipng's -o3 is already annoyingly slow.)

So with its fastest setting, oxipng is 2.4x faster than optipng and achieves better compression than optipng can get at even -o3. And as can be seen from the real vs user/sys time, oxipng has multithreading, though it only really seems to be effective at some -o levels. This can also be limited with --threads.

oxipng can also use Zopfli (invoked with -Z), but that's extremely slow and more of a "for fun" / out of interest option rather than something practical for anything but low resolution files.

Recompress images in .zip and .rar archive files

I really like the ability to recompress images in comic book archives but I also find that sometimes I'd like to do the same thing with regular, non-comic archive files (i.e. regular .zip files). This would be a useful feature to have.

rubbish files

after running picopt several times (with errors) i have rubbish files:

drwxrwxrwx 2 www www 1024 May 13 01:55 .
drwxrwxrwx 9 www www 512 Jun 20 2016 ..
-rwxrwxrwx 1 www www 317020 Jun 20 2016 CCB0734_12.PNG
-rwxrwxrwx 1 www www 317020 Jun 20 2016 CCB0734_12.PNG.picopt-optimized.png
-rwxrwxrwx 1 www www 317020 Jun 20 2016 CCB0734_12.PNG.picopt-optimized.png.picopt-optimized.png
-rwxrwxrwx 1 www www 317020 Jun 20 2016 CCB0734_12.PNG.picopt-optimized.png.picopt-optimized.png.picopt-optimized.png
-rwxrwxrwx 1 www www 317020 Jun 20 2016 CCB0734_12.PNG.picopt-optimized.png.picopt-optimized.png.picopt-optimized.png.picopt-optimized.png
-rwxrwxrwx 1 www www 317020 Jun 20 2016 CCB0734_12.PNG.picopt-optimized.png.picopt-optimized.png.picopt-optimized.png.picopt-optimized.png.picopt-optimized.png

i run

find . -name "picopt-optimized" -type f -delete

to fix it.

I think it's not ok that picopt multiplies it's own temporary files

Format selection broken; easy fix

Trying to use the instructions given with --help in 1.4.4 gives an error about an unsupported operation as it's trying to intersect a set and a list.

Changing intersection = formats & Settings.formats to intersection = formats & set(Settings.formats) on line 29 of detect_format.py seems to fix it.

Animated PNG support (or warning/ignore)

I forgot I had some APNG files in the directory where I ran picopt, and it "optimized" those by just keeping the first frame... which isn't very nice.
I had backups, at least, but I suggest trying to identify and filtering out APNG files before calling optipng/pngout (until those tools end up supporting APNGs).

Support lossless conversion of JPEG and PNG files to JPEG XL

A useful feature of JPEG XL is support for losslessly transcoding JPEG files into JPEG XL. This reduces file size by 20% on average, and the process is also reversible, so you can losslessly convert back to the original JPEG data.

JPEG XL also does "normal" lossless compression, so PNG files can be losslessly converted as well (about 35% size reduction, 50% for HDR).

The reference implementation is libjxl, and it's the best encoder so far. The conversion executable is cjxl, and the usage is cjxl INPUT OUTPUT [OPTIONS...]. The effort/CPU time used for conversion is controlled with -e. Higher numbers result in more processing time and higher compression efficiency. The default is 7, and if necessary, conversion can be made significantly faster with lower values. In my experience, libjxl is impressively fast.

This might be a long-term feature request, but being able to convert to JPEG XL for lossless optimization would be useful. Even though JPEG XL support isn't universal yet, it's so much superior to the competition that it's only a matter of time. The codec is in it for the long haul and was designed as a long-term replacement for JPEG (which is now 30 years old) as well as PNG, unlike the WebM/AVIF style of web-centric codecs that want to introduce a new codec every few years if possible.

libjxl itself is easy to install, with static builds for Windows and Linux as well as .deb packages for Debian and Ubuntu available on the releases section. It's also available on Homebrew for MacOS.

Modifying argument list for external tools via command line arguments

It would be a nice feature to have control over the argument list sent to different external tools. For example i would like to set the mozjpeg quality to 60%.
At the moment i would have to change the

_MOZJPEG_ARGS = ['mozjpeg']
to
_MOZJPEG_ARGS = ['mozjpeg','-quality','60']

and make a fork. Any hints or ideas? Thanks!

AttributeError: (dict) object has not attribute 'iteritems'

picopt gives me following error when i tried to run the following command:

$: picopt * jpg
AttributeError: (dict) object has not attribute 'iteritems'

I am using Ubuntu with Python3. I tried removing iteritems to items but then it throws different error:

TypeError: unorderable types: NoneType() >NoneType()

skip broken flag

  1. Can you add an option to skip broken images?

We have thousands of images and few of them are broken. Can we skip them somehow?

  1. I'm not sure if my images were broken BEFORE running picopt. Or may be it is picopt that breaks them. Can you add a dry run flag that will show problematic images?

Mozjpeg not found crash

I get the following error on any run of the program:

> picopt --help

Traceback (most recent call last):
  File "/home/calum/.local/bin/picopt", line 5, in <module>
    from picopt.cli import main
  File "/home/calum/.local/pipx/venvs/picopt/lib/python3.11/site-packages/picopt/cli.py", line 10, in <module>
    from picopt import PROGRAM_NAME, walk
  File "/home/calum/.local/pipx/venvs/picopt/lib/python3.11/site-packages/picopt/walk.py", line 16, in <module>
    from picopt.config import TIMESTAMPS_CONFIG_KEYS
  File "/home/calum/.local/pipx/venvs/picopt/lib/python3.11/site-packages/picopt/config.py", line 37, in <module>
    from picopt.handlers.jpeg import Jpeg
  File "/home/calum/.local/pipx/venvs/picopt/lib/python3.11/site-packages/picopt/handlers/jpeg.py", line 12, in <module>
    class Jpeg(ImageHandler):
  File "/home/calum/.local/pipx/venvs/picopt/lib/python3.11/site-packages/picopt/handlers/jpeg.py", line 22, in Jpeg
    PROGRAMS["mozjpeg"],
    ~~~~~~~~^^^^^^^^^^^
KeyError: 'mozjpeg'

I'm running on a Debian Stable system, without mozjpeg installed. I instaled picopt with "pipx install pipopt" about 5 minutes ago. I think there's some issue in the support for running without mozjpeg.

Freebsd

What about freebsd? Can't find how to install python-imaging

ValueError: Decompressed Data Too Large

When bulk optimizing images on my server i've got error for some of my files:

ValueError: Decompressed Data Too Large for originals.

Can i do anything about it? I have 64 of GB Ram...

Doesn't convert tiff, bmp, or pnm to png

The documentation claims that various lossless formats will be converted to png, but this doesn't seem to be the case:

$ ls 0001*
0001.bmp  0001.pnm  0001.tiff

# Try picopt on each file
$ picopt 0001.tiff
0001.tiff: 0.00% (no bytes)
Evened out a total of no bytes or 0.00%
$ picopt 0001.bmp
Didn't optimize any files.
$ picopt 0001.pnm
Didn't optimize any files.

# No png file created
$ ls 0001*
0001.bmp  0001.pnm  0001.tiff

Some tests are not passing

Hi, I would like to report that some tests are not passing. Not sure if some there are critical, but I am leaving this record. The sources are pulled from pypy (e.g. https://pypi.org/packages/source/p/picopt/picopt-4.0.1.tar.gz)

==> Starting check()...
================================================================================ test session starts ================================================================================
platform linux -- Python 3.11.7, pytest-7.4.4, pluggy-1.4.0
rootdir: /tmp/makepkg/picopt/src/picopt-4.0.1
configfile: pyproject.toml
testpaths: tests
plugins: cov-4.1.0
collected 30 items                                                                                                                                                                  

tests/integration/test_containers.py .FF                                                                                                                                      [ 10%]
tests/integration/test_images_dir.py FFFF                                                                                                                                     [ 23%]
tests/integration/test_mpo.py .F.                                                                                                                                             [ 33%]
tests/integration/test_old_timestamps.py ...                                                                                                                                  [ 43%]
tests/integration/test_one_container.py F                                                                                                                                     [ 46%]
tests/integration/test_preserve.py F                                                                                                                                          [ 50%]
tests/integration/test_timestamps.py F.F.....                                                                                                                                 [ 76%]
tests/unit/test_cli.py ...                                                                                                                                                    [ 86%]
tests/unit/test_png_bit_depth.py ....                                                                                                                                         [100%]/usr/lib/python3.11/site-packages/coverage/data.py:171: CoverageWarning: Data file '/tmp/makepkg/picopt/src/picopt-4.0.1/.coverage.474c0f849c6e.18719.XBFGRFkx' doesn't seem to be a coverage data file: 
  data._warn(str(exc))


===================================================================================== FAILURES ======================================================================================
___________________________________________________________________ TestContainersDir.test_containers_no_convert ____________________________________________________________________

self = <tests.integration.test_containers.TestContainersDir object at 0x7fe47d222650>

    def test_containers_no_convert(self) -> None:
        """Test containers no convert."""
        args = (PROGRAM_NAME, "-rx", "GIF,CBZ,ZIP,EPUB", "-c WEBP", str(TMP_ROOT))
        cli.main(args)
        for name, sizes in FNS.items():
            path = TMP_ROOT / name
            if name == EPUB_FN:
                with ZipFile(path, "r") as zf:
                    namelist = zf.namelist()
                assert BMP_FN in namelist
>           assert path.stat().st_size == sizes[1]
E           AssertionError: assert 85273 == 84544
E            +  where 85273 = os.stat_result(st_mode=33188, st_ino=2613060667, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=85273, st_atime=1709238059, st_mtime=1709238059, st_ctime=1709238059).st_size
E            +    where os.stat_result(st_mode=33188, st_ino=2613060667, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=85273, st_atime=1709238059, st_mtime=1709238059, st_ctime=1709238059) = <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_containers/test_cbz.cbz')>()
E            +      where <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_containers/test_cbz.cbz')> = PosixPath('/tmp/picopt-tests.integration.test_containers/test_cbz.cbz').stat

tests/integration/test_containers.py:66: AssertionError
------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------
Optimizing formats: CBZ, EPUB, GIF, JPEG, PNG, WEBP, ZIP
Converting GIF, PNG to WEBP
Unpacking /tmp/picopt-tests.integration.test_containers/igp-twss.epub...................................................................../tmp/picopt-tests.integration.test_containers/igp-twss.epub - OPS/images/igp-twss.jpg: 6.19% (5.6 kB) saved
............done
.Unpacking /tmp/picopt-tests.integration.test_containers/test_cbz.cbz...Repacking /tmp/picopt-tests.integration.test_containers/igp-twss.epub...done
.................................../tmp/picopt-tests.integration.test_containers/test_cbz.cbz - test_jpg.jpg: 9.04% (8.8 kB) saved
.......Repacking /tmp/picopt-tests.integration.test_containers/test_cbz.cbz........Unpacking /tmp/picopt-tests.integration.test_containers/test_zip.zip...........done
.........Repacking /tmp/picopt-tests.integration.test_containers/test_zip.zip................../tmp/picopt-tests.integration.test_containers/test_zip.zip: 0.10% (8 Bytes) saved
done
...../tmp/picopt-tests.integration.test_containers/test_cbz.cbz: 8.71% (8.1 kB) saved
done
./tmp/picopt-tests.integration.test_containers/igp-twss.epub: 2.26% (6.6 kB) saved
done
done.

Saved a total of 14.8 kB or 3.75%
_________________________________________________________________ TestContainersDir.test_containers_convert_to_zip __________________________________________________________________

self = <tests.integration.test_containers.TestContainersDir object at 0x7fe47d222c90>

    def test_containers_convert_to_zip(self) -> None:
        """Test containers convert to zip."""
        args = (
            PROGRAM_NAME,
            "-rx",
            "ZIP,CBZ,RAR,CBR,EPUB",
            "-c",
            "ZIP,CBZ",
            str(TMP_ROOT),
        )
        cli.main(args)
        for name, sizes in FNS.items():
            print(name)
            path = (TMP_ROOT / name).with_suffix("." + sizes[2][0])
>           assert path.stat().st_size == sizes[2][1]
E           AssertionError: assert 85273 == 84544
E            +  where 85273 = os.stat_result(st_mode=33188, st_ino=2615149255, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=85273, st_atime=1709238059, st_mtime=1709238059, st_ctime=1709238059).st_size
E            +    where os.stat_result(st_mode=33188, st_ino=2615149255, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=85273, st_atime=1709238059, st_mtime=1709238059, st_ctime=1709238059) = <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_containers/test_cbz.cbz')>()
E            +      where <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_containers/test_cbz.cbz')> = PosixPath('/tmp/picopt-tests.integration.test_containers/test_cbz.cbz').stat

tests/integration/test_containers.py:82: AssertionError
------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------
Optimizing formats: CBR, CBZ, EPUB, GIF, JPEG, PNG, RAR, WEBP, ZIP
Converting RAR to ZIP
Converting CBR to CBZ
Unpacking /tmp/picopt-tests.integration.test_containers/igp-twss.epub.................................................../tmp/picopt-tests.integration.test_containers/igp-twss.epub - OPS/images/igp-twss.jpg: 6.19% (5.6 kB) saved
..............................done
Unpacking /tmp/picopt-tests.integration.test_containers/test_cbr.cbr...Repacking /tmp/picopt-tests.integration.test_containers/igp-twss.epub.................................................................done
................./tmp/picopt-tests.integration.test_containers/igp-twss.epub: 2.26% (6.6 kB) saved
done
/tmp/picopt-tests.integration.test_containers/test_cbr.cbr - test_jpg.jpg: 9.04% (8.8 kB) saved
Unpacking /tmp/picopt-tests.integration.test_containers/test_cbz.cbz...Repacking /tmp/picopt-tests.integration.test_containers/test_cbr.cbz...done
../tmp/picopt-tests.integration.test_containers/test_cbr.cbz: 5.35% (5.0 kB) saved
done
/tmp/picopt-tests.integration.test_containers/test_cbz.cbz - test_jpg.jpg: 9.04% (8.8 kB) saved
Unpacking /tmp/picopt-tests.integration.test_containers/test_rar.rar...Repacking /tmp/picopt-tests.integration.test_containers/test_cbz.cbz..../tmp/picopt-tests.integration.test_containers/test_cbz.cbz: 8.71% (8.1 kB) saved
done
done
/tmp/picopt-tests.integration.test_containers/test_rar.rar - test_jpg.jpg: 9.04% (8.8 kB) saved
Unpacking /tmp/picopt-tests.integration.test_containers/test_zip.zip...Repacking /tmp/picopt-tests.integration.test_containers/test_rar.zip......done
/tmp/picopt-tests.integration.test_containers/test_rar.zip: 5.31% (5.0 kB) saved
done
Repacking /tmp/picopt-tests.integration.test_containers/test_zip.zip...../tmp/picopt-tests.integration.test_containers/test_zip.zip: 0.10% (8 Bytes) saved
done
done.

Saved a total of 24.7 kB or 4.26%
test_cbz.cbz
___________________________________________________________________________ TestImagesDir.test_no_convert ___________________________________________________________________________

self = <tests.integration.test_images_dir.TestImagesDir object at 0x7fe47d1ff8d0>

    def test_no_convert(self) -> None:
        """Test no convert."""
        args = (PROGRAM_NAME, "-rvvx SVG", str(self.TMP_ROOT))
        cli.main(args)
        for name, sizes in self.FNS.items():
            path = self.TMP_ROOT / name
>           assert path.stat().st_size == sizes[1]
E           AssertionError: assert 16383 == 16358
E            +  where 16383 = os.stat_result(st_mode=33188, st_ino=2617267376, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=16383, st_atime=1709238060, st_mtime=1709238060, st_ctime=1709238060).st_size
E            +    where os.stat_result(st_mode=33188, st_ino=2617267376, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=16383, st_atime=1709238060, st_mtime=1709238060, st_ctime=1709238060) = <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.gif')>()
E            +      where <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.gif')> = PosixPath('/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.gif').stat

tests/integration/test_images_dir.py:91: AssertionError
------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------
Optimizing formats: GIF, JPEG, PNG, WEBP
Not setting timestamps.
Skipped /tmp/picopt-tests.integration.test_images_dir/07themecamplist.pdf: (unknown) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.test_images_dir/eight.tif: (TIFF lossless) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.test_images_dir/mri.tif: (TIFF lossless animated) is not an enabled image or container.
/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.gif: 0.00% (0 Bytes), kept original
Unpacking /tmp/picopt-tests.integration.test_images_dir/test_animated_png.png....................../tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#005.png: 91.13% (36.6 kB) saved
.done
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#006.png: 91.92% (36.9 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#002.png: 89.07% (35.8 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#004.png: 90.30% (36.3 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#007.png: 92.69% (37.2 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#003.png: 90.00% (36.2 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#001.png: 88.84% (35.7 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#008.png: 93.21% (37.4 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#009.png: 92.97% (37.4 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#017.png: 92.42% (37.1 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#015.png: 92.74% (37.3 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#014.png: 92.77% (37.3 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#013.png: 93.00% (37.4 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#016.png: 92.60% (37.2 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#018.png: 91.90% (36.9 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#020.png: 90.09% (36.2 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#010.png: 92.87% (37.3 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#019.png: 91.27% (36.7 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#011.png: 92.95% (37.3 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#012.png: 93.12% (37.4 kB) saved
Repacking /tmp/picopt-tests.integration.test_images_dir/test_animated_png.png...Skipped /tmp/picopt-tests.integration.test_images_dir/test_animated_webp.webp: (unknown) is not an enabled image or container.
.Skipped /tmp/picopt-tests.integration.test_images_dir/test_bmp.bmp: (BMP lossless) is not an enabled image or container..
................./tmp/picopt-tests.integration.test_images_dir/test_gif.gif: 0.00% (0 Bytes), kept original
Skipped /tmp/picopt-tests.integration.test_images_dir/test_pnm.pnm: (PPM lossless) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.test_images_dir/test_svg.svg: (SVG lossless) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.test_images_dir/test_txt.txt: (unknown) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.test_images_dir/test_webp_lossless.webp: (unknown) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.test_images_dir/test_webp_lossless_pre-optimized.webp: (unknown) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.test_images_dir/test_webp_lossy.webp: (unknown) is not an enabled image or container.
/tmp/picopt-tests.integration.test_images_dir/test_png_16rgba.png: 38.95% (1.3 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_jpg.jpg: 9.04% (8.8 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_pre-optimized_jpg.jpg: -2.25% (-510 Bytes) lost, kept original
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png: -13.65% (-8.7 kB) lost, kept original
done
/tmp/picopt-tests.integration.test_images_dir/test_png.png: 45.75% (3.6 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_pre-optimized_png.png: 0.69% (1.8 kB) saved
done.
Saved a total of 15.5 kB or 2.56%
_________________________________________________________________________ TestImagesDir.test_convert_to_png _________________________________________________________________________

self = <tests.integration.test_images_dir.TestImagesDir object at 0x7fe47d3fe690>

    def test_convert_to_png(self) -> None:
        """Test convert to PNG."""
        args = (
            PROGRAM_NAME,
            "-rvvx",
            "BMP,PPM,SVG,TIFF",
            "-c",
            "PNG",
            str(self.TMP_ROOT),
        )
        cli.main(args)
        for name, sizes in self.FNS.items():
            path = (self.TMP_ROOT / name).with_suffix("." + sizes[2][0])
>           assert path.stat().st_size == sizes[2][1]
E           AssertionError: assert 13391 == 13389
E            +  where 13391 = os.stat_result(st_mode=33188, st_ino=2619346916, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=13391, st_atime=1709238061, st_mtime=1709238061, st_ctime=1709238061).st_size
E            +    where os.stat_result(st_mode=33188, st_ino=2619346916, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=13391, st_atime=1709238061, st_mtime=1709238061, st_ctime=1709238061) = <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.png')>()
E            +      where <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.png')> = PosixPath('/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.png').stat

tests/integration/test_images_dir.py:106: AssertionError
------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------
Optimizing formats: BMP, GIF, JPEG, PNG, PPM, TIFF, WEBP
Converting BMP, GIF, PPM, TIFF to PNG
Not setting timestamps.
Skipped /tmp/picopt-tests.integration.test_images_dir/07themecamplist.pdf: (unknown) is not an enabled image or container.
Unpacking /tmp/picopt-tests.integration.test_images_dir/mri.tif.........../tmp/picopt-tests.integration.test_images_dir/eight.png: 48.54% (28.9 kB) saved
./tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#001.png: 74.92% (13.0 kB) saved
./tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#006.png: 69.32% (12.0 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#005.png: 69.80% (12.1 kB) saved
./tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#007.png: 67.94% (11.8 kB) saved
..../tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#004.png: 70.76% (12.3 kB) saved
../tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#011.png: 68.77% (11.9 kB) saved/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#008.png: 67.36% (11.7 kB) saved

/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#002.png: 72.96% (12.7 kB) saved
./tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#010.png: 68.41% (11.9 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#009.png: 67.86% (11.8 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#015.png: 68.08% (11.8 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#003.png: 71.43% (12.4 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#016.png: 68.49% (11.9 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#012.png: 68.57% (11.9 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#014.png: 67.93% (11.8 kB) saved
./tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#017.png: 69.22% (12.0 kB) saved
./tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#013.png: 68.20% (11.8 kB) saved
.../tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#018.png: 69.95% (12.1 kB) saved
./tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#019.png: 70.87% (12.3 kB) saved
...done
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#020.png: 71.58% (12.4 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#021.png: 73.04% (12.7 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#022.png: 74.25% (12.9 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#023.png: 75.92% (13.2 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#024.png: 77.46% (13.4 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#025.png: 79.89% (13.9 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#027.png: 84.45% (14.7 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.tif - frame_#026.png: 82.24% (14.3 kB) saved
Repacking /tmp/picopt-tests.integration.test_images_dir/mri.png...Unpacking /tmp/picopt-tests.integration.test_images_dir/test_animated_gif.gif.................................done
/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.gif - frame_#002.png: 94.91% (66.0 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.gif - frame_#003.png: 94.91% (66.0 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.gif - frame_#001.png: 80.36% (14.2 kB) saved
/tmp/picopt-tests.integration.test_images_dir/mri.png: 39.90% (92.0 kB) saved
done
/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.gif - frame_#004.png: 94.91% (66.0 kB) saved
Repacking /tmp/picopt-tests.integration.test_images_dir/test_animated_gif.png......Unpacking /tmp/picopt-tests.integration.test_images_dir/test_animated_png.png....../tmp/picopt-tests.integration.test_images_dir/test_animated_gif.png: 18.26% (3.0 kB) saved
done
.../tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#002.png: 89.07% (35.8 kB) saved
./tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#004.png: 90.30% (36.3 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#001.png: 88.84% (35.7 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#003.png: 90.00% (36.2 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#005.png: 91.13% (36.6 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#006.png: 91.92% (36.9 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#007.png: 92.69% (37.2 kB) saved
........../tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#011.png: 92.95% (37.3 kB) saved
...done
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#010.png: 92.87% (37.3 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#012.png: 93.12% (37.4 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#017.png: 92.42% (37.1 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#013.png: 93.00% (37.4 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#009.png: 92.97% (37.4 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#018.png: 91.90% (36.9 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#014.png: 92.77% (37.3 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#008.png: 93.21% (37.4 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#019.png: 91.27% (36.7 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#020.png: 90.09% (36.2 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#016.png: 92.60% (37.2 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png - frame_#015.png: 92.74% (37.3 kB) saved
Repacking /tmp/picopt-tests.integration.test_images_dir/test_animated_png.png...Skipped /tmp/picopt-tests.integration.test_images_dir/test_animated_webp.webp: (unknown) is not an enabled image or container.
...................Skipped /tmp/picopt-tests.integration.test_images_dir/test_svg.svg: (SVG lossless) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.test_images_dir/test_txt.txt: (unknown) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.test_images_dir/test_webp_lossless.webp: (unknown) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.test_images_dir/test_webp_lossless_pre-optimized.webp: (unknown) is not an enabled image or container.
/tmp/picopt-tests.integration.test_images_dir/test_png_16rgba.png: 38.95% (1.3 kB) saved
Skipped /tmp/picopt-tests.integration.test_images_dir/test_webp_lossy.webp: (unknown) is not an enabled image or container.
/tmp/picopt-tests.integration.test_images_dir/test_pre-optimized_jpg.jpg: -2.25% (-510 Bytes) lost, kept original
/tmp/picopt-tests.integration.test_images_dir/test_jpg.jpg: 9.04% (8.8 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_animated_png.png: -13.65% (-8.7 kB) lost, kept original
done
/tmp/picopt-tests.integration.test_images_dir/test_pnm.png: 44.09% (12.2 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_png.png: 45.75% (3.6 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_bmp.png: 53.16% (75.2 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_gif.png: 19.37% (26.9 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_pre-optimized_png.png: 0.69% (1.8 kB) saved
done.
Saved a total of 253.8 kB or 23.80%
________________________________________________________________________ TestImagesDir.test_convert_to_webp _________________________________________________________________________

self = <tests.integration.test_images_dir.TestImagesDir object at 0x7fe47d26a350>

    def test_convert_to_webp(self) -> None:
        """Test convert to WEBP."""
        args = (
            PROGRAM_NAME,
            "-rvvx",
            "BMP,PPM,SVG,TIFF",
            "-c",
            "WEBP",
            str(self.TMP_ROOT),
        )
        cli.main(args)
        for name, sizes in self.FNS.items():
            path = (self.TMP_ROOT / name).with_suffix("." + sizes[3][0])
>           assert path.stat().st_size == sizes[3][1]

tests/integration/test_images_dir.py:121: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = PosixPath('/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.webp')

    def stat(self, *, follow_symlinks=True):
        """
        Return the result of the stat() system call on this path, like
        os.stat() does.
        """
>       return os.stat(self, follow_symlinks=follow_symlinks)
E       FileNotFoundError: [Errno 2] No such file or directory: '/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.webp'

/usr/lib/python3.11/pathlib.py:1013: FileNotFoundError
------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------
Optimizing formats: BMP, GIF, JPEG, PNG, PPM, TIFF, WEBP
Converting BMP, GIF, PNG, PPM, TIFF to WEBP
Converting PPM & TIFF with an extra step for older cwebp 
Not setting timestamps.
Skipped /tmp/picopt-tests.integration.test_images_dir/07themecamplist.pdf: (unknown) is not an enabled image or container.
/tmp/picopt-tests.integration.test_images_dir/eight.webp: 46.08% (27.5 kB) saved
Unpacking /tmp/picopt-tests.integration.test_images_dir/mri.tif...Unpacking /tmp/picopt-tests.integration.test_images_dir/test_animated_gif.gif...Unpacking /tmp/picopt-tests.integration.test_images_dir/test_animated_png.png...Skipped /tmp/picopt-tests.integration.test_images_dir/test_animated_webp.webp: (unknown) is not an enabled image or container.
/tmp/picopt-tests.integration.test_images_dir/test_png.webp: 0.00% (0 Bytes), kept original
/tmp/picopt-tests.integration.test_images_dir/test_png_16rgba.webp: 0.00% (0 Bytes), kept original
Skipped /tmp/picopt-tests.integration.test_images_dir/test_svg.svg: (SVG lossless) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.test_images_dir/test_txt.txt: (unknown) is not an enabled image or container.
/tmp/picopt-tests.integration.test_images_dir/test_pre-optimized_png.webp: 0.00% (0 Bytes), kept original
Skipped /tmp/picopt-tests.integration.test_images_dir/test_webp_lossless.webp: (unknown) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.test_images_dir/test_webp_lossless_pre-optimized.webp: (unknown) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.test_images_dir/test_webp_lossy.webp: (unknown) is not an enabled image or container.
ERROR: /tmp/picopt-tests.integration.test_images_dir/mri.tif
    'WEBP'
ERROR: /tmp/picopt-tests.integration.test_images_dir/test_animated_gif.gif
    'WEBP'
ERROR: /tmp/picopt-tests.integration.test_images_dir/test_animated_png.png
    'WEBP'
/tmp/picopt-tests.integration.test_images_dir/test_pnm.webp: 43.78% (12.1 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_pre-optimized_jpg.jpg: -2.25% (-510 Bytes) lost, kept original
/tmp/picopt-tests.integration.test_images_dir/test_jpg.jpg: 9.04% (8.8 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_bmp.webp: 50.08% (70.8 kB) saved
/tmp/picopt-tests.integration.test_images_dir/test_gif.webp: 13.20% (18.3 kB) saved
done.
Saved a total of 137.6 kB or 18.20%
Errors with the following files:
ERROR: /tmp/picopt-tests.integration.test_images_dir/mri.tif
    'WEBP'
ERROR: /tmp/picopt-tests.integration.test_images_dir/test_animated_gif.gif
    'WEBP'
ERROR: /tmp/picopt-tests.integration.test_images_dir/test_animated_png.png
    'WEBP'
------------------------------------------------------------------------------- Captured stderr call --------------------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/makepkg/picopt/src/picopt-4.0.1/picopt/walk.py", line 210, in _walk_container
    for path_info in handler.unpack():
  File "/tmp/makepkg/picopt/src/picopt-4.0.1/picopt/handlers/container.py", line 63, in unpack
    yield from self.unpack_into()
  File "/tmp/makepkg/picopt/src/picopt-4.0.1/picopt/handlers/image_animated.py", line 47, in unpack_into
    frame.save(
  File "/usr/lib/python3.11/site-packages/PIL/Image.py", line 2426, in save
    save_handler = SAVE[format.upper()]
                   ~~~~^^^^^^^^^^^^^^^^
KeyError: 'WEBP'
Traceback (most recent call last):
  File "/tmp/makepkg/picopt/src/picopt-4.0.1/picopt/walk.py", line 210, in _walk_container
    for path_info in handler.unpack():
  File "/tmp/makepkg/picopt/src/picopt-4.0.1/picopt/handlers/container.py", line 63, in unpack
    yield from self.unpack_into()
  File "/tmp/makepkg/picopt/src/picopt-4.0.1/picopt/handlers/image_animated.py", line 47, in unpack_into
    frame.save(
  File "/usr/lib/python3.11/site-packages/PIL/Image.py", line 2426, in save
    save_handler = SAVE[format.upper()]
                   ~~~~^^^^^^^^^^^^^^^^
KeyError: 'WEBP'
Traceback (most recent call last):
  File "/tmp/makepkg/picopt/src/picopt-4.0.1/picopt/walk.py", line 210, in _walk_container
    for path_info in handler.unpack():
  File "/tmp/makepkg/picopt/src/picopt-4.0.1/picopt/handlers/container.py", line 63, in unpack
    yield from self.unpack_into()
  File "/tmp/makepkg/picopt/src/picopt-4.0.1/picopt/handlers/image_animated.py", line 47, in unpack_into
    frame.save(
  File "/usr/lib/python3.11/site-packages/PIL/Image.py", line 2426, in save
    save_handler = SAVE[format.upper()]
                   ~~~~^^^^^^^^^^^^^^^^
KeyError: 'WEBP'
____________________________________________________________ TestNearLosslessImageDir.test_convert_to_webp_near_lossless ____________________________________________________________

self = <tests.integration.test_images_dir.TestNearLosslessImageDir object at 0x7fe47d26a190>

    def test_convert_to_webp_near_lossless(self) -> None:
        """Test convert to WEBP."""
        args = (
            PROGRAM_NAME,
            "-rvvvnc",
            "WEBP",
            str(self.TMP_ROOT),
        )
        cli.main(args)
        for name, sizes in self.FNS.items():
            path = (self.TMP_ROOT / name).with_suffix("." + sizes[3][0])
>           assert path.stat().st_size == sizes[3][1]

tests/integration/test_images_dir.py:138: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = PosixPath('/tmp/picopt-tests.integration.base_test_images/test_png_16rgba.webp')

    def stat(self, *, follow_symlinks=True):
        """
        Return the result of the stat() system call on this path, like
        os.stat() does.
        """
>       return os.stat(self, follow_symlinks=follow_symlinks)
E       FileNotFoundError: [Errno 2] No such file or directory: '/tmp/picopt-tests.integration.base_test_images/test_png_16rgba.webp'

/usr/lib/python3.11/pathlib.py:1013: FileNotFoundError
------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------
Optimizing formats: GIF, JPEG, PNG, WEBP
Converting GIF, PNG to WEBP
Not setting timestamps.
Skipped /tmp/picopt-tests.integration.base_test_images/test_webp_lossless.webp: (unknown) is not an enabled image or container.
Skipped /tmp/picopt-tests.integration.base_test_images/test_webp_lossless_pre-optimized.webp: (unknown) is not an enabled image or container.
/tmp/picopt-tests.integration.base_test_images/test_png_16rgba.webp: 0.00% (0 Bytes), kept original
done.
Evened out a total of 0 Bytes or 0.00%
___________________________________________________________________________ TestMPO.test_convert_to_jpeg ____________________________________________________________________________

self = <tests.integration.test_mpo.TestMPO object at 0x7fe47d223610>

    def test_convert_to_jpeg(self) -> None:
        """Test convert to PNG."""
        args = (PROGRAM_NAME, "-rvvvx", "MPO", "-c", "JPEG", str(self.TMP_ROOT))
        cli.main(args)
        for name, sizes in self.FNS.items():
            path = (self.TMP_ROOT / name).with_suffix("." + sizes[2][0])
>           assert path.stat().st_size == sizes[2][1]
E           AssertionError: assert 5982732 == 5963686
E            +  where 5982732 = os.stat_result(st_mode=33188, st_ino=2627745006, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=5982732, st_atime=1709238063, st_mtime=1709238063, st_ctime=1709238063).st_size
E            +    where os.stat_result(st_mode=33188, st_ino=2627745006, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=5982732, st_atime=1709238063, st_mtime=1709238063, st_ctime=1709238063) = <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_mpo/test_mpo.jpeg')>()
E            +      where <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_mpo/test_mpo.jpeg')> = PosixPath('/tmp/picopt-tests.integration.test_mpo/test_mpo.jpeg').stat

tests/integration/test_mpo.py:48: AssertionError
------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------
Optimizing formats: GIF, JPEG, MPO, PNG, WEBP
Converting MPO to JPEG
Not setting timestamps.
/tmp/picopt-tests.integration.test_mpo/test_pre-optimized_jpg.jpg: -2.25% (-510 Bytes) lost, kept original
/tmp/picopt-tests.integration.test_mpo/test_mpo.jpeg: 15.81% (1.1 MB) saved
done.
Saved a total of 1.1 MB or 15.76%
____________________________________________________________________ TestOneContainer.test_containers_no_convert ____________________________________________________________________

self = <tests.integration.test_one_container.TestOneContainer object at 0x7fe47d220590>

    def test_containers_no_convert(self) -> None:
        """Test containers no convert."""
        args = (PROGRAM_NAME, "-x", "ZIP", str(TMP_ROOT / FN))
        cli.main(args)
        for name, sizes in FNS.items():
            path = TMP_ROOT / name
>           assert path.stat().st_size == sizes[1]
E           AssertionError: assert 7775 == 7015
E            +  where 7775 = os.stat_result(st_mode=33188, st_ino=2642414790, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=7775, st_atime=1709238065, st_mtime=1709238065, st_ctime=1709238065).st_size
E            +    where os.stat_result(st_mode=33188, st_ino=2642414790, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=7775, st_atime=1709238065, st_mtime=1709238065, st_ctime=1709238065) = <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_one_container/test_zip.zip')>()
E            +      where <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_one_container/test_zip.zip')> = PosixPath('/tmp/picopt-tests.integration.test_one_container/test_zip.zip').stat

tests/integration/test_one_container.py:39: AssertionError
------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------
Optimizing formats: GIF, JPEG, PNG, WEBP, ZIP
Unpacking /tmp/picopt-tests.integration.test_one_container/test_zip.zip.....done
Repacking /tmp/picopt-tests.integration.test_one_container/test_zip.zip...../tmp/picopt-tests.integration.test_one_container/test_zip.zip: 0.10% (8 Bytes) saved
done
done.

Saved a total of 8 Bytes or 0.10%
____________________________________________________________________________ TestPreserve.test_preserve _____________________________________________________________________________

self = <tests.integration.test_preserve.TestPreserve object at 0x7fe47d242510>

    def test_preserve(self) -> None:
        """Test no convert."""
        stats = {}
        for name in self.FNS:
            path = self.TMP_ROOT / name
            stats[path] = path.stat()
        args = (PROGRAM_NAME, "-rpvvv", str(self.TMP_ROOT))
        cli.main(args)
        for name, sizes in self.FNS.items():
            path = self.TMP_ROOT / name
            old_stat = stats[path]
            new_stat = path.stat()
>           assert new_stat.st_size == sizes[1]
E           assert 88575 == 87913
E            +  where 88575 = os.stat_result(st_mode=33188, st_ino=2644514465, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=88575, st_atime=1709238065, st_mtime=1709238065, st_ctime=1709238065).st_size

tests/integration/test_preserve.py:45: AssertionError
------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------
Optimizing formats: GIF, JPEG, PNG, WEBP
Not setting timestamps.
/tmp/picopt-tests.integration.test_preserve/test_pre-optimized_jpg.jpg: -2.25% (-510 Bytes) lost, kept original
/tmp/picopt-tests.integration.test_preserve/test_jpg.jpg: 9.04% (8.8 kB) saved
done.
Saved a total of 8.8 kB or 7.33%
test_pre-optimized_jpg.jpg
        st_uid
        st_gid
        st_mode
        st_mtime_ns
_________________________________________________________________________ TestTimestamps.test_no_timestamp __________________________________________________________________________

self = <tests.integration.test_timestamps.TestTimestamps object at 0x7fe47ccce750>

    def test_no_timestamp(self) -> None:
        """Test no timestamp."""
        args = (PROGRAM_NAME, "-rtvvvx SVG", TMP_FN)
        cli.main(args)
>       self._assert_sizes(1)

tests/integration/test_timestamps.py:81: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

index = 1, root = PosixPath('/tmp/picopt-tests.integration.test_timestamps')

    @staticmethod
    def _assert_sizes(index, root=TMP_ROOT):
        """Assert sizes."""
        for name, sizes in FNS.items():
            path = root / name
>           assert path.stat().st_size == sizes[index]
E           AssertionError: assert 88575 == 87913
E            +  where 88575 = os.stat_result(st_mode=33188, st_ino=2646609131, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=88575, st_atime=1709238065, st_mtime=1709238065, st_ctime=1709238065).st_size
E            +    where os.stat_result(st_mode=33188, st_ino=2646609131, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=88575, st_atime=1709238065, st_mtime=1709238065, st_ctime=1709238065) = <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_timestamps/test_jpg.jpg')>()
E            +      where <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_timestamps/test_jpg.jpg')> = PosixPath('/tmp/picopt-tests.integration.test_timestamps/test_jpg.jpg').stat

tests/integration/test_timestamps.py:45: AssertionError
------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------
Optimizing formats: GIF, JPEG, PNG, WEBP
Setting a timestamp file at the top of each directory tree: /tmp/picopt-tests.integration.test_timestamps
/tmp/picopt-tests.integration.test_timestamps/test_jpg.jpg: 9.04% (8.8 kB) saved
done.
Saving timestamps for /tmp/picopt-tests.integration.test_timestamps
Saved a total of 8.8 kB or 9.04%
----------------------------------------------------------------------------- Captured stdout teardown ------------------------------------------------------------------------------
[PosixPath('/tmp/picopt-tests.integration.test_timestamps/.picopt_treestamps.yaml'), PosixPath('/tmp/picopt-tests.integration.test_timestamps/test_jpg.jpg')]
_______________________________________________________________________ TestTimestamps.test_different_config ________________________________________________________________________

self = <tests.integration.test_timestamps.TestTimestamps object at 0x7fe47cccf110>

    def test_different_config(self):
        """Test different config."""
        self._write_timestamp(FN)
        args = (PROGRAM_NAME, "-brtvvvx SVG", TMP_FN)
        cli.main(args)
>       self._assert_sizes(1)

tests/integration/test_timestamps.py:95: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

index = 1, root = PosixPath('/tmp/picopt-tests.integration.test_timestamps')

    @staticmethod
    def _assert_sizes(index, root=TMP_ROOT):
        """Assert sizes."""
        for name, sizes in FNS.items():
            path = root / name
>           assert path.stat().st_size == sizes[index]
E           AssertionError: assert 88575 == 87913
E            +  where 88575 = os.stat_result(st_mode=33188, st_ino=2650801281, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=88575, st_atime=1709238065, st_mtime=1709238065, st_ctime=1709238065).st_size
E            +    where os.stat_result(st_mode=33188, st_ino=2650801281, st_dev=2097343, st_nlink=1, st_uid=1000, st_gid=1000, st_size=88575, st_atime=1709238065, st_mtime=1709238065, st_ctime=1709238065) = <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_timestamps/test_jpg.jpg')>()
E            +      where <bound method Path.stat of PosixPath('/tmp/picopt-tests.integration.test_timestamps/test_jpg.jpg')> = PosixPath('/tmp/picopt-tests.integration.test_timestamps/test_jpg.jpg').stat

tests/integration/test_timestamps.py:45: AssertionError
------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------
{'config': {'bigger': False, 'convert_to': [], 'formats': ['GIF', 'JPEG', 'PNG', 'SVG', 'WEBP'], 'keep_metadata': True, 'ignore': [], 'near_lossless': False, 'recurse': True, 'symlinks': True}, 'treestamps_config': {'ignore': [], 'symlinks': True}, 'test_jpg.jpg': 1709238065.823189}
Optimizing formats: GIF, JPEG, PNG, WEBP
Setting a timestamp file at the top of each directory tree: /tmp/picopt-tests.integration.test_timestamps
Read timestamps from /tmp/picopt-tests.integration.test_timestamps/.picopt_treestamps.yaml
/tmp/picopt-tests.integration.test_timestamps/test_jpg.jpg: 9.04% (8.8 kB) saved
done.
Saving timestamps for /tmp/picopt-tests.integration.test_timestamps
Evened out a total of 0 Bytes or 0.00%
----------------------------------------------------------------------------- Captured stdout teardown ------------------------------------------------------------------------------
[PosixPath('/tmp/picopt-tests.integration.test_timestamps/.picopt_treestamps.yaml'), PosixPath('/tmp/picopt-tests.integration.test_timestamps/test_jpg.jpg')]
================================================================================= warnings summary ==================================================================================
tests/integration/test_containers.py::TestContainersDir::test_containers_no_convert
tests/integration/test_containers.py::TestContainersDir::test_containers_convert_to_zip
tests/integration/test_images_dir.py::TestImagesDir::test_no_convert
tests/integration/test_images_dir.py::TestImagesDir::test_convert_to_png
tests/integration/test_images_dir.py::TestImagesDir::test_convert_to_webp
tests/integration/test_images_dir.py::TestNearLosslessImageDir::test_convert_to_webp_near_lossless
tests/integration/test_one_container.py::TestOneContainer::test_containers_no_convert
  /usr/lib/python3.11/site-packages/PIL/Image.py:3307: UserWarning: image file could not be identified because WEBP support not installed
    warnings.warn(message)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
---------------------------------------------- generated xml file: /tmp/makepkg/picopt/src/picopt-4.0.1/test-results/pytest/junit.xml -----------------------------------------------

---------- coverage: platform linux, python 3.11.7-final-0 -----------
Name                                Stmts   Miss Branch BrPart  Cover
---------------------------------------------------------------------
picopt/__init__.py                      1      0      0      0   100%
picopt/cli.py                          76      9     12      3    86%
picopt/config.py                      217     38     92      9    81%
picopt/exceptions.py                    1      0      0      0   100%
picopt/formats.py                      41      0     12      0   100%
picopt/handlers/__init__.py             0      0      0      0   100%
picopt/handlers/container.py           55      0     22      4    95%
picopt/handlers/factory.py            135     12     54      5    91%
picopt/handlers/gif.py                 19      2      0      0    89%
picopt/handlers/handler.py            179     59     74     12    64%
picopt/handlers/image.py               41      4     14      3    87%
picopt/handlers/image_animated.py      62      1     28      3    96%
picopt/handlers/jpeg.py                70     10     16      5    83%
picopt/handlers/non_pil.py             10      0      4      0   100%
picopt/handlers/png.py                 41     10      8      2    71%
picopt/handlers/png_animated.py        10      0      0      0   100%
picopt/handlers/svg.py                 19      4      0      0    79%
picopt/handlers/webp.py                51     21     10      2    52%
picopt/handlers/webp_animated.py       16      0      0      0   100%
picopt/handlers/zip.py                106      8     46     10    88%
picopt/old_timestamps.py               37      2     14      2    92%
picopt/path.py                        100      9     44      9    85%
picopt/pillow/__init__.py               0      0      0      0   100%
picopt/pillow/header.py                11      0      2      0   100%
picopt/pillow/jpeg_xmp.py              33     13     12      1    60%
picopt/pillow/png_bit_depth.py         22      1      6      1    93%
picopt/pillow/webp_lossless.py         30     19     10      1    30%
picopt/stats.py                       109     18     48      8    77%
picopt/walk.py                        214     39    100     19    79%
---------------------------------------------------------------------
TOTAL                                1706    279    628     99    81%
Coverage HTML written to dir test-results/coverage

============================================================================== short test summary info ==============================================================================
FAILED tests/integration/test_containers.py::TestContainersDir::test_containers_no_convert - AssertionError: assert 85273 == 84544
FAILED tests/integration/test_containers.py::TestContainersDir::test_containers_convert_to_zip - AssertionError: assert 85273 == 84544
FAILED tests/integration/test_images_dir.py::TestImagesDir::test_no_convert - AssertionError: assert 16383 == 16358
FAILED tests/integration/test_images_dir.py::TestImagesDir::test_convert_to_png - AssertionError: assert 13391 == 13389
FAILED tests/integration/test_images_dir.py::TestImagesDir::test_convert_to_webp - FileNotFoundError: [Errno 2] No such file or directory: '/tmp/picopt-tests.integration.test_images_dir/test_animated_gif.webp'
FAILED tests/integration/test_images_dir.py::TestNearLosslessImageDir::test_convert_to_webp_near_lossless - FileNotFoundError: [Errno 2] No such file or directory: '/tmp/picopt-tests.integration.base_test_images/test_png_16rgba.webp'
FAILED tests/integration/test_mpo.py::TestMPO::test_convert_to_jpeg - AssertionError: assert 5982732 == 5963686
FAILED tests/integration/test_one_container.py::TestOneContainer::test_containers_no_convert - AssertionError: assert 7775 == 7015
FAILED tests/integration/test_preserve.py::TestPreserve::test_preserve - assert 88575 == 87913
FAILED tests/integration/test_timestamps.py::TestTimestamps::test_no_timestamp - AssertionError: assert 88575 == 87913
FAILED tests/integration/test_timestamps.py::TestTimestamps::test_different_config - AssertionError: assert 88575 == 87913
==================================================================== 11 failed, 19 passed, 7 warnings in 10.22s =====================================================================
==> ERROR: A failure occurred in check().
    Aborting...

TypeError: unorderable types: NoneType() > NoneType()

picopt -r *.jpg
Traceback (most recent call last):
File "/Users/msoedov/pyenvs/picopt/bin/picopt", line 11, in
sys.exit(main())
File "/Users/msoedov/pyenvs/picopt/lib/python3.5/site-packages/picopt/cli.py", line 185, in main
run(sys.argv)
File "/Users/msoedov/pyenvs/picopt/lib/python3.5/site-packages/picopt/cli.py", line 180, in run
walk.run()
File "/Users/msoedov/pyenvs/picopt/lib/python3.5/site-packages/picopt/walk.py", line 173, in run
record_dirs, bytes_in, bytes_out, nag_about_gifs = _walk_all_files()
File "/Users/msoedov/pyenvs/picopt/lib/python3.5/site-packages/picopt/walk.py", line 150, in _walk_all_files
walk_after = timestamp.get_walk_after(filename_full)
File "/Users/msoedov/pyenvs/picopt/lib/python3.5/site-packages/picopt/timestamp.py", line 64, in get_walk_after
optimize_after = _get_parent_timestamp(dirname, optimize_after)
File "/Users/msoedov/pyenvs/picopt/lib/python3.5/site-packages/picopt/timestamp.py", line 45, in _get_parent_timestamp
mtime = max(_get_timestamp(parent_pathname, False), mtime)
TypeError: unorderable types: NoneType() > NoneType()

Change optipng optimization level

optipng's own default is -o3, which is a good compromise between compression efficiency and CPU time requirement. Picopt sets this to -o6, however, which takes much longer to process and offers diminishing returns over lower values. It might be fine for small, icon size files, but compression time increases significantly for high res, multi-megabyte images. I often find myself using optipng manually for these cases, because a single process using -o3 or lower values is faster than multi-threaded -o6 when dealing with sets of large files.

Having an option to choose the optipng compression level would be useful. IMO the default should also be changed to -o3. Some more complex logic of using different compression levels based on file size would also be cool.

Missing imports

I just ran:

apt-get install optipng gifsicle python-imaging
pip install picopt

and got this as output. Are there any extra installation steps necessary?

$ picopt -r *                                                           
Traceback (most recent call last):
  File "/root/Envs/bin/picopt", line 7, in <module>
    from picopt.cli import main
  File "/root/Envs/lib/python3.4/site-packages/picopt/cli.py", line 13, in <module>
    import comic
ImportError: No module named 'comic'

Thanks!!

picopt could use a --preserve option

When compressing 10 to 20 year old photo collections, I don't want their attributes like time changed. I want the files to be the same, only smaller.

The --preserve option usually controls this. I see this from 'man cp':

       -p     same as --preserve=mode,ownership,timestamps

       --preserve[=ATTR_LIST]
              preserve the specified attributes (default: mode,ownership,time‐
              stamps),  if  possible  additional  attributes:  context, links,
              xattr, all

Preserving the mode and ownership are important too, so that other users can access the files after shrinkage.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.