Skip to content

Instantly share code, notes, and snippets.

@GDXbsv
Created February 1, 2025 10:13
Show Gist options
  • Select an option

  • Save GDXbsv/488e1fc891632cab2e5aa62e645b8689 to your computer and use it in GitHub Desktop.

Select an option

Save GDXbsv/488e1fc891632cab2e5aa62e645b8689 to your computer and use it in GitHub Desktop.
#!/bin/bash
# # For flat structure (all files in one directory):
# ./convert_to_jxl.sh /path/to/source/folder /path/to/destination flat
# # For maintaining directory structure:
# ./convert_to_jxl.sh /path/to/source/folder /path/to/destination structured
# Check if correct number of arguments provided
if [ "$#" -ne 3 ]; then
echo "Usage: $0 <source_directory> <destination_directory> <flat|structured>"
exit 1
fi
# Check if parallel is installed
if ! command -v parallel &> /dev/null; then
echo "GNU parallel is not installed. Please install it first:"
echo "sudo apt-get install parallel # For Debian/Ubuntu"
echo "sudo yum install parallel # For CentOS/RHEL"
exit 1
fi
SOURCE_DIR="$1"
DEST_DIR="$2"
MODE="$3"
# Create destination directory if it doesn't exist
mkdir -p "$DEST_DIR"
# Function to convert bytes to MB
bytes_to_mb() {
local bytes=$1
# Force use of dot as decimal separator and ensure proper number formatting
LC_NUMERIC=C awk -v bytes="$bytes" 'BEGIN {printf "%.2f MB", bytes/1024/1024}'
}
get_extension() {
local filename=$1
local ext=$(echo "$filename" | awk -F. '{ print tolower($NF) }')
echo "$ext"
}
# Function to process files
process_file() {
local src_file="$1"
# Get basename without any extension
local base_name=$(basename "$src_file" | sed 's/\.[^.]*$//')
local rel_path=$(dirname "${src_file#$SOURCE_DIR/}")
# Create a temporary file for conversion
local temp_file=$(mktemp --suffix=.jxl)
local file_extension=$(get_extension "$src_file")
# Convert to temporary file first
echo "Converting: $src_file | ext: $file_extension"
case "$file_extension" in
jpg|jpeg|png|gif)
cjxl --lossless_jpeg=0 -q 80 "$src_file" "$temp_file"
;;
heic|heif)
vips copy "$src_file" "$temp_file[Q=80]"
;;
*)
echo "The file extension is not one of the specified types."
exit 0
;;
esac
# Compare file sizes
local original_size=$(stat -f%z "$src_file" 2>/dev/null || stat -c%s "$src_file")
local converted_size=$(stat -f%z "$temp_file" 2>/dev/null || stat -c%s "$temp_file")
# Calculate size differences
local original_mb=$(bytes_to_mb $original_size)
local converted_mb=$(bytes_to_mb $converted_size)
local saved_mb=$(bytes_to_mb $((original_size-converted_size)))
if [ "$converted_size" -lt "$original_size" ]; then
# Only proceed if the converted file is smaller
if [ "$MODE" = "flat" ]; then
# For flat structure, put all files in destination directory
dest_file="$DEST_DIR/${base_name}.jxl"
# If file with same name exists, append a number
if [ -f "$dest_file" ]; then
counter=1
while [ -f "$DEST_DIR/${base_name}_${counter}.jxl" ]; do
((counter++))
done
dest_file="$DEST_DIR/${base_name}_${counter}.jxl"
fi
else
# For structured output, maintain directory structure
dest_dir="$DEST_DIR/$rel_path"
mkdir -p "$dest_dir"
dest_file="$dest_dir/${base_name}.jxl"
fi
# Move the temporary file to final destination
mv "$temp_file" "$dest_file"
echo "✓ Saved: $dest_file (Original: $original_mb, Converted: $converted_mb, Saved: $saved_mb)"
else
# Remove temporary file if it's not smaller
rm "$temp_file"
echo "✗ Skipped: $src_file (Original: $original_mb, Converted: $converted_mb, No size reduction)"
fi
}
export -f process_file
export -f bytes_to_mb
export -f get_extension
export SOURCE_DIR DEST_DIR MODE
# Find all jpg files and process them in parallel (3 jobs at a time)
find "$SOURCE_DIR" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.gif" -o -iname "*.JPG" -o -iname "*.JPEG" -o -iname "*.PNG" -o -iname "*.GIF" -o -iname "*.heic" -o -iname "*.heif" -o -iname "*.HEIC" -o -iname "*.HEIF" \) -print0 | \
parallel -0 -j 2 process_file
echo "Conversion complete!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment