Last active
June 10, 2025 17:39
-
-
Save hsandt/d922a14e1f8b10faa1dee2a05894729a to your computer and use it in GitHub Desktop.
Revisions
-
hsandt revised this gist
Jun 10, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,6 @@ #!/bin/bash # Gist: https://gist.github.com/hsandt/d922a14e1f8b10faa1dee2a05894729a help() { echo "Convert all image files in current folder to target format -
hsandt created this gist
Jun 10, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,221 @@ #!/bin/bash # Source: https://stackoverflow.com/questions/5784661/how-do-you-convert-an-entire-directory-with-ffmpeg help() { echo "Convert all image files in current folder to target format ! This doesn't add a suffix to file paths, so if SOURCE_FORMAT == TARGET_FORMAT, ! it will overwrite files in-place. Make sure to backup if needed! This generates a file 'converted_[source_format]_to_[target_format].txt' to remember the operation. Note: this may create and delete a local tmp folder. " usage } usage() { echo "Usage: convert_image.sh SOURCE_FORMAT TARGET_FORMAT [OPTIONS] ARGUMENTS SOURCE_FORMAT Format of files to convert (jpg, etc.) (case-sensitive) TARGET_FORMAT Format of files to convert (avif, etc.) SOURCE_FORMAT == TARGET_FORMAT is supported, but will overwrite existing files. OPTIONS -q, --quality QUALITY Quality of target file (default: 90) CAUTION: ignored when using Krita conversion (for avif) For PNG, the quality value sets the zlib compression level (quality / 10) and filter-type (quality % 10) See https://imagemagick.org/script/command-line-options.php#quality -s, --output-size OUTPUT_SIZE Final image size. Argument format is same as convert / gm mogrify -resize (X%, WxH, etc.). Default: 100% -h, --help Show this help message " } # Default arguments quality=90 output_size="100%" # Read arguments positional_args=() while [[ $# -gt 0 ]]; do case $1 in -h | --help ) help exit 0 ;; -q | --quality ) if [[ $# -lt 2 ]] ; then echo "Missing argument for $1" usage exit 1 fi quality="$2" shift # past argument shift # past value ;; -s | --output-size ) if [[ $# -lt 2 ]] ; then echo "Missing argument for $1" usage exit 1 fi output_size="$2" shift # past argument shift # past value ;; -* ) # unknown option echo "Unknown option: '$1'" usage exit 1 ;; * ) # store positional argument for later positional_args+=("$1") shift # past argument ;; esac done if ! [[ ${#positional_args[@]} -eq 2 ]]; then echo "Wrong number of positional arguments: found ${#positional_args[@]}, expected 2." echo "Passed positional arguments: ${positional_args[@]}" usage exit 1 fi source_format="${positional_args[0]}" target_format="${positional_args[1]}" if [[ "$target_format" == "$source_format" ]]; then echo "WARNING: TARGET_FORMAT is same as SOURCE_FORMAT: '$target_format'. Conversion may still be relevant e.g. to reduce size by adjusting quality, but note that target files will be overwritten." fi # Remember operation, esp. if we converted from a lossy format like .jpg, # to note that user should not expect very high quality even if it's .avif at high quality # (jpg->avif is in fact the most common operation and is meant to spare file size # since the file has undergone lossy compression anyway) # We will fill this with list of converted files conversion_log_filename="converted_${source_format}_to_${target_format}.txt" # Source: https://stackoverflow.com/questions/24577551/in-graphicsmagick-how-can-i-specify-the-output-file-on-a-bulk-of-files # Removed +profile option since we are not converting to thumbnail so we don't remove color profile info echo "= START conversion =" >> "$conversion_log_filename" for f in *.${source_format}; do # if no file ending with .${source_format}, # it will iterate once with f="*.${source_format}" # in which case we must do nothing if ! [[ -f "$f" ]]; then echo "No files found with extension '$f' (note that it is case-sensitive)." # It is not an error, though, so exit with 0 exit 0 fi mkdir -p "tmp" # Create a copy with sanitized name because convert and gm mogrify use colon `:` to indicate format, # so replace each colon with hyphen `-` # Note that it is safe to rename files inside the loop as the list of files has already been evaluated # Edge case: a target file with sanitized name + new format suffix may already exist, but in this case, # it's most likely born from a previous conversation of the original file and can safely be overwritten sanitized_f=`echo $f | tr : -` cp "$f" "tmp/$sanitized_f" # Keep sanitized file name for target (not required since we could rename output file after conversion, but easier) target_f="${sanitized_f%.*}.${target_format}" conversion_info="$f -> $target_f (quality: $quality, output_size: $output_size)" success=true if [[ "$target_format" == "avif" ]]; then # WIP !! # First rescale picture since Krita command-line doesn't support # Export Advanced to target size # To avoid loss before actual conversion, keep same format and maximum quality for now convert "tmp/$sanitized_f" -resize "$output_size" -quality 100 "tmp/resized_$sanitized_f" # `convert` supports avif, but pretty slow # convert "$f" -quality $quality "$target_f" # EXPERIMENTAL: use Krita, a bit faster (3s vs 5s) than convert, for avif # But cannot choose quality, which is around 90 apparently # if Krita is not open, will open a document dimensions prompt window # NOTE: Krita preserves EXIF on export, so no need to use exiftool flatpak run org.kde.krita "tmp/resized_$sanitized_f" --export --export-filename "$target_f" else if hash gm 2> /dev/null && [[ "$OSTYPE" != "msys" ]]; then # For common formats, unless on Windows, use `gm mogrify` (doesn't support avif) # It is faster than `convert` # In case "$target_format" == "$source_format", create new file in tmp dir first # so we can copy EXIT metadata from the original being it's overwritten gm mogrify -format $target_format -resize "$output_size" -quality $quality "tmp/$sanitized_f" elif hash magick 2> /dev/null; then # Some issues with gm on Windows ("No decode delegate for this image format (file.png)") so using magick # (don't use just `convert`, deprecated and conflicting with Windows built-in) magick "tmp/$sanitized_f" -resize "$output_size" -quality $quality "tmp/$target_f" elif hash exiftool 2> /dev/null; then # EXPERIMENTAL: old exiftool doesn't support webp and silently fails with PNG, but latest should work # with both: https://exiftool.org/forum/index.php?topic=13797.msg75207#msg75207 # exiftool -overwrite_original_in_place -tagsFromFile "$f" "tmp/$target_f" echo "WARNING: exiftool is still experimental, currently disabled" success=false else echo "ERROR: no gm, magick, exiftool executable found" success=false fi if [[ "$success" == true ]]; then # In case source_format == target_format, we must save the original file size before it gets overwritten in-place, # to print stats later # Ex: 3214547 f_size=`stat -c %s "$f"` # Ex: 3.2M f_size_readable=`du -h "$f" | awk '{ print $1 }'` # If "$target_format" == "$source_format", the move will overwrite the original file # Else, we still want to overwrite any existing file with target format, so in both cases we want -f mv -f "tmp/$target_f" "$target_f" fi fi # Cleanup tmp dir, which should exist at this point, so no need for -f rm -r "tmp" if [[ "$success" == false ]]; then # Print error to terminal and also log echo "$conversion_info" | tee -a "$conversion_log_filename" echo " Failed, STOP." | tee -a "$conversion_log_filename" exit 1 fi # Log file conversion and info echo "$conversion_info" >> "$conversion_log_filename" # Print size change and relative percentage (using original file size saved earlier) target_f_size=`stat -c %s "$target_f"` target_f_size_readable=`du -h "$target_f" | awk '{ print $1 }'` conversion_percentage=$((100*$target_f_size/$f_size)) size_change_info="$f_size_readable -> $target_f_size_readable (${conversion_percentage}%)" echo " $size_change_info" >> "$conversion_log_filename" if [[ $conversion_percentage -ge 100 ]]; then echo "OOPS, $f -> $target_f gave bigger size! $size_change_info" echo " OOPS, bigger size!" >> "$conversion_log_filename" elif [[ $conversion_percentage -ge 75 ]]; then echo "MEH, $f -> $target_f did not reduce size below 75%! $size_change_info" echo " MEH, new size is not below 75%!" >> "$conversion_log_filename" fi done echo "Finished conversion."