I recently made a backup of photos
I've been taking over the last few years on a Fujifilm x100 (2010).
And I wanted to find the best format and way to compress it.
This can act as both a guide and a blog if I've written it well enough,
so you can compress images on nixos (or linux) yourself.
Why JXL?
For this process,
I will not just be making a .zip or .tar.gz
I also will be converting the JPG/JPEG files,
and will do so with .jxl.
I believe that
.jxl is an incredible format,
and it's really too bad Google is yet to support it in chrome
despite high demand
.
Google had instead pushed for
AVIF a competitor to JXL
which in my opinion is inferior.
AV1 however,
is incredible!
and I am patiently awaitng the release of AV2
and it's adoption.
JXL in particular is built around JPEG interoperability,
so converting files from JPEG to JXL can preserve visual quality
while significantly reducing file size.
But most importantly It also supports
Progressive loading.
Years later,
and google
may support it very soon!
Firefox is likely to follow suit,
and safari already does, along with a bunch of software like
Gimp,
Krita and Photoshop do.
Hopefully I am not just being overly optimistic,
because I do believe that JXL is the future.
JXL compression
Firstly, you need libjxl installed.
On nixos this is easy to do in a shell:
nix-shell -p libjxl
cjxl input.jpg output.jxl
Installing packages like libjxl depends on package manager,
nearly all unix systems can
install nix though
(and i'd recommend it).
The command above may be all you need depending on your use case.
For me I want a short script to automate this.
Automate it!
I've been
wanting to learn
nushell for scripting,
and I don't regret it one bit.
It has structured data,
batteries included
and rust like errors.
In my experience languages with great error messages
(and a cult following)
are
all amazing.
In my case the DCIM folder has multiple folders inside that has the JPEGS,
so I want to recursively go through every file
and recreate the same file tree in a new directory
for the compressed files.
Here is the script (./compress.nu)
I ended up making and how to use it.
That last line does a lot of the heavy lifting.
Right now it’s set to maximum effort (9) and lossy output (0).
Lower the 9 to reduce effort (e.g. 3).
Flip the last 0 to 1 to enable lossless,
also then remove -d 2 segment.
nix-shell -p nushell libjxl
nu compress.nu out_dir/
This will work recursively into all sub directories of where the script is
executed.
It will also ignore all non JPG/JPEG files.
So if you have other file formats as well mixed in (like I do RAF)
then you need to convert that too.
But you need not do it first,
because this script will detect what files have already been done and not redo.
Tarball compression
Lastly compressing everything with a tarball.
Zip works fine,
nix-shell -p zip
zip -r archive.zip myfolder
and in practice you should proabbly do both,
but tar balls work better for lots of files.
Zip losslessly compresses on a file-per-file basis
and wouldn't actually help that much here
given each file is already so efficiently compressed.
Earlier I mentioned .tar.gz
which is generated via gzip on a .tar,
but there are more efficient compression algorithms but run for longer
and use more power.
There are lots of options
These are in increasing order of least to most compute time for smaller results.
.
I was planning on trying each and have it run overnight,
but this part of the process ended up being a so much quicker than I expected.
Using p7zip here were the final results.
I compressed 14.1 GB of photos down to 1.8 GB (lossy), and 9.2 GB (lossless).
Only using p7zip without JXL then only compresses to 13.3 GB,
so JXL ened up being well worth it.
Nix flake stuff
Especially if you don't use nix,
then you can stop reading here.
The script most likely worked fine.
Eventually I will make another blog post about why I bother with nix.
Either way, here are two methods for running nushell via nix.
One solution is you run it inline like this:
{
inputs.nixpkgs.url="github:NixOS/nixpkgs/nixos-unstable";outputs={ self, nixpkgs }:letsystem="x86_64-linux";pkgs=import nixpkgs {inherit system;};app= pkgs.writeShellScriptBin "run-nu"'' exec ${pkgs.nushell}/bin/nu ${pkgs.writeText "hello.nu"''def main [out_dir: path] { mkdir $out_dir let files = ( ls **/* | where type == file | where name =~ '(?i)\.(jpg|jpeg)$' | get name ) if ($files | is-empty) { print "No jpg/jpeg files found" exit 1 } for f in $files { let base = ($f | path parse | reject extension | path join) let out = ($out_dir | path join ($base + ".jxl")) mkdir ($out | path dirname) if ($out | path exists) { continue } print $"Converting: ($f) -> ($out)" ${pkgs.cjxl} $f $out -d 2 -e 9 --lossless_jpeg=0 }} ''} '';in{
apps.${system}.default={type="app";program="${app}/bin/run-nu";};};}
nix runs -- ./path/to/dir
The other solution is to put the nushell script in another file and link to it
(./scipt.nu).
This is best if you want to modify and debug things,
as you get actual syntax highlighting, LSP
and better erros running it from the dev shell.
There is actually a simpler way to do this,
but this is better setup in case you want to use it in other places
or modify it.
Essentially libjxl is included here in the runtime inputs,
because we aren't referring to packages as pkgs.libjxl for example,
we instead need to declare them before trying to run the script.