Skip to content

Instantly share code, notes, and snippets.

@claunia
Last active March 6, 2025 20:54
Show Gist options
  • Save claunia/5b7b77ee5c49cdb91d596cf29b579152 to your computer and use it in GitHub Desktop.
Save claunia/5b7b77ee5c49cdb91d596cf29b579152 to your computer and use it in GitHub Desktop.
Losslessly convert all JPGs in current folder to JPEG-XL preserving EXIF and mtime. (Bash script)
#!/bin/bash
# Check files with `jpg` extension
if [[ -n $(shopt -s nullglob; echo *.jpg) ]]; then
# Create a directory to work in
mkdir -p wrk
# Move all files with `jpg` extension, as well as XMP sidecars to the working directory
mv ./*.jpg wrk
if [[ -n $(shopt -s nullglob; echo *.jpg.xmp) ]]; then
mv ./*.jpg.xmp wrk
fi
cd wrk || exit
# Convert all JPEG files to JPEG-XL format losslessly
parallel 'cjxl --num_threads 1 -j 1 -e 10 {} {.}.jxl' ::: *.jpg
# For each JPEG-XL file, copy metadata from the original JPEG file, and then delete the original JPEG file
if [[ -n $(shopt -s nullglob; echo *.jxl) ]]; then
parallel 'exiftool -m -overwrite_original -api compress=1 -tagsfromfile {.}.jpg {}; touch -r {.}.jpg {}; rm {.}.jpg' ::: *.jxl
fi
# If there are still non converted JPEG files
if [[ -n $(shopt -s nullglob; echo *.jpg) ]]; then
mkdir -p out
# Transform then, without changes, this usually remakes the Huffman tables, that is the most common source of errors when converting to JPEG-XL (commonly found in WhatsApp images)
parallel 'echo {}; jpegtran -copy all -optimize -perfect -strict -outfile out/{} {}' ::: *.jpg
# For each JPEG-XL file, copy metadata from the original JPEG file, and then delete the original JPEG file
if [[ -n $(shopt -s nullglob; echo out/*.jpg) ]]; then
parallel 'cjxl --num_threads 1 -j 1 -e 10 out/{} {.}.jxl; exiftool -m -overwrite_original -api compress=1 -tagsfromfile {} {.}.jxl; touch -r {} {.}.jxl; rm {} out/{}' ::: *.jpg
fi
rmdir out
fi
# Rename XMP sidecars to match the new JPEG-XL files, if any
if [[ -n $(shopt -s nullglob; echo *.jpg.xmp) ]]; then
for i in *.jpg.xmp
do k="${i%.jpg.xmp}.jxl.xmp"
if [[ -f "${i%.jpg.xmp}.jxl" ]]; then
mv "$i" "$k"
fi
done
fi
# Move all files back to the parent directory
find . -empty -delete
mv ./* ..
cd ..
rmdir wrk
fi
# Check files with `JPG` extension
if [[ -n $(shopt -s nullglob; echo *.JPG) ]]; then
# Create a directory to work in
mkdir -p wrk
# Move all files with `JPG` extension, as well as XMP sidecars to the working directory
mv ./*.JPG wrk
if [[ -n $(shopt -s nullglob; echo *.JPG.xmp) ]]; then
mv ./*.JPG.xmp wrk
fi
if [[ -n $(shopt -s nullglob; echo *.JPG.XMP) ]]; then
mv ./*.JPG.XMP wrk
fi
cd wrk || exit
# Convert all JPEG files to JPEG-XL format losslessly
parallel 'cjxl --num_threads 1 -j 1 -e 10 {} {.}.JXL' ::: *.JPG
# For each JPEG-XL file, copy metadata from the original JPEG file, and then delete the original JPEG file
if [[ -n $(shopt -s nullglob; echo *.JXL) ]]; then
parallel 'exiftool -m -overwrite_original -api compress=1 -tagsfromfile {.}.JPG {}; touch -r {.}.JPG {}; rm {.}.JPG' ::: *.JXL
fi
# If there are still non converted JPEG files
if [[ -n $(shopt -s nullglob; echo *.JPG) ]]; then
mkdir -p out
# Transform then, without changes, this usually remakes the Huffman tables, that is the most common source of errors when converting to JPEG-XL (commonly found in WhatsApp images)
parallel 'echo {}; jpegtran -copy all -optimize -perfect -strict -outfile out/{} {}' ::: *.JPG
# For each JPEG-XL file, copy metadata from the original JPEG file, and then delete the original JPEG file
if [[ -n $(shopt -s nullglob; echo out/*.JPG) ]]; then
parallel 'cjxl --num_threads 1 -j 1 -e 10 out/{} {.}.JXL; exiftool -m -overwrite_original -api compress=1 -tagsfromfile {} {.}.JXL; touch -r {} {.}.JXL; rm {} out/{}' ::: *.JPG
fi
rmdir out
fi
# Rename XMP sidecars to match the new JPEG-XL files, if any
if [[ -n $(shopt -s nullglob; echo *.JPG.xmp) ]]; then
for i in *.JPG.xmp
do k="${i%.JPG.xmp}.JXL.xmp"
if [[ -f "${i%.JPG.xmp}.JXL" ]]; then
mv "$i" "$k"
fi
done
fi
if [[ -n $(shopt -s nullglob; echo *.JPG.XMP) ]]; then
for i in *.JPG.XMP
do k="${i%.JPG.XMP}.JXL.XMP"
if [[ -f "${i%.JPG.XMP}.JXL" ]]; then
mv "$i" "$k"
fi
done
fi
# Move all files back to the parent directory
find . -empty -delete
mv ./* ..
cd ..
rmdir wrk
fi
# Check files with `jpeg` extension
if [[ -n $(shopt -s nullglob; echo *.jpeg) ]]; then
# Create a directory to work in
mkdir -p wrk
# Move all files with `jpeg` extension, as well as XMP sidecars to the working directory
mv ./*.jpeg wrk
if [[ -n $(shopt -s nullglob; echo *.jpeg.xmp) ]]; then
mv ./*.jpeg.xmp wrk
fi
cd wrk || exit
# Convert all JPEG files to JPEG-XL format losslessly
parallel 'cjxl --num_threads 1 -j 1 -e 10 {} {.}.jxl' ::: *.jpeg
# For each JPEG-XL file, copy metadata from the original JPEG file, and then delete the original JPEG file
if [[ -n $(shopt -s nullglob; echo *.jxl) ]]; then
parallel 'exiftool -m -overwrite_original -api compress=1 -tagsfromfile {.}.jpeg {}; touch -r {.}.jpeg {}; rm {.}.jpeg' ::: *.jxl
fi
# If there are still non converted JPEG files
if [[ -n $(shopt -s nullglob; echo *.jpeg) ]]; then
mkdir -p out
# Transform then, without changes, this usually remakes the Huffman tables, that is the most common source of errors when converting to JPEG-XL (commonly found in WhatsApp images)
parallel 'echo {}; jpegtran -copy all -optimize -perfect -strict -outfile out/{} {}' ::: *.jpeg
# For each JPEG-XL file, copy metadata from the original JPEG file, and then delete the original JPEG file
if [[ -n $(shopt -s nullglob; echo out/*.jpeg) ]]; then
parallel 'cjxl --num_threads 1 -j 1 -e 10 out/{} {.}.jxl; exiftool -m -overwrite_original -api compress=1 -tagsfromfile {} {.}.jxl; touch -r {} {.}.jxl; rm {} out/{}' ::: *.jpeg
fi
rmdir out
fi
# Rename XMP sidecars to match the new JPEG-XL files, if any
if [[ -n $(shopt -s nullglob; echo *.jpeg.xmp) ]]; then
for i in *.jpeg.xmp
do k="${i%.jpeg.xmp}.jxl.xmp"
if [[ -f "${i%.jpeg.xmp}.jxl" ]]; then
mv "$i" "$k"
fi
done
fi
# Move all files back to the parent directory
find . -empty -delete
mv ./* ..
cd ..
rmdir wrk
fi
# Check files with `JPEG` extension
if [[ -n $(shopt -s nullglob; echo *.JPEG) ]]; then
# Create a directory to work in
mkdir -p wrk
# Move all files with `JPEG` extension, as well as XMP sidecars to the working directory
mv ./*.JPEG wrk
if [[ -n $(shopt -s nullglob; echo *.JPEG.xmp) ]]; then
mv ./*.JPEG.xmp wrk
fi
if [[ -n $(shopt -s nullglob; echo *.JPEG.XMP) ]]; then
mv ./*.JPEG.XMP wrk
fi
cd wrk || exit
# Convert all JPEG files to JPEG-XL format losslessly
parallel 'cjxl --num_threads 1 -j 1 -e 10 {} {.}.JXL' ::: *.JPEG
# For each JPEG-XL file, copy metadata from the original JPEG file, and then delete the original JPEG file
if [[ -n $(shopt -s nullglob; echo *.JXL) ]]; then
parallel 'exiftool -m -overwrite_original -api compress=1 -tagsfromfile {.}.JPEG {}; touch -r {.}.JPEG {}; rm {.}.JPEG' ::: *.JXL
fi
# If there are still non converted JPEG files
if [[ -n $(shopt -s nullglob; echo *.JPEG) ]]; then
mkdir -p out
# Transform then, without changes, this usually remakes the Huffman tables, that is the most common source of errors when converting to JPEG-XL (commonly found in WhatsApp images)
parallel 'echo {}; jpegtran -copy all -optimize -perfect -strict -outfile out/{} {}' ::: *.JPEG
# For each JPEG-XL file, copy metadata from the original JPEG file, and then delete the original JPEG file
if [[ -n $(shopt -s nullglob; echo out/*.JPEG) ]]; then
parallel 'cjxl --num_threads 1 -j 1 -e 10 out/{} {.}.JXL; exiftool -m -overwrite_original -api compress=1 -tagsfromfile {} {.}.JXL; touch -r {} {.}.JXL; rm {} out/{}' ::: *.JPEG
fi
rmdir out
fi
# Rename XMP sidecars to match the new JPEG-XL files, if any
if [[ -n $(shopt -s nullglob; echo *.JPEG.xmp) ]]; then
for i in *.JPEG.xmp
do k="${i%.JPEG.xmp}.JXL.xmp"
if [[ -f "${i%.JPEG.xmp}.JXL" ]]; then
mv "$i" "$k"
fi
done
fi
if [[ -n $(shopt -s nullglob; echo *.JPEG.XMP) ]]; then
for i in *.JPEG.XMP
do k="${i%.JPEG.XMP}.JXL.XMP"
if [[ -f "${i%.JPEG.XMP}.JXL" ]]; then
mv "$i" "$k"
fi
done
fi
# Move all files back to the parent directory
find . -empty -delete
mv ./* ..
cd ..
rmdir wrk
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment