#!/usr/bin/env bash set -o nounset set -E required_commands="exiftool identify oxipng parallel" missing_counter=0 for command in ${required_commands}; do if ! hash "${command}" >/dev/null 2>&1; then printf "Command not found in PATH: %s\n" "${command}" >&2 ((missing_counter++)) fi done if ((missing_counter > 0)); then printf "%d or more commands are missing from PATH. Exiting.\n" "${missing_counter}" >&2 exit 1 fi MAX_DAYS="${MAX_DAYS:-7}" if [[ "${MAX_DAYS}" =~ ^[0-9]+$ ]]; then max_mins="$(("${MAX_DAYS}"*60*24))" export -p max_mins else echo "MAX_DAYS must be a positive integer value" exit 1 fi DEBUG=${DEBUG:-0} if [[ ${DEBUG} -eq 1 ]]; then echo "DEBUG: Running single threaded" parallel_thread_count=1 oxipng_thread_count=1 else parallel_thread_count=8 oxipng_thread_count=4 fi export -p DEBUG export -p parallel_thread_count export -p oxipng_thread_count lock_file="/run/user/$(id -ru)/optimize-pngs.lock" if [ -f "${lock_file}" ]; then echo "Process already running at pid $(cat "${lock_file}")." exit 0 fi echo "$$" > "${lock_file}" function cleanup { if ! { rm "${lock_file}"; }; then echo "Cleanup failed. You may need to manually remove ${lock_file}." fi } trap cleanup EXIT base_dir="$(mktemp -d)" if [[ ${DEBUG} -eq 1 ]]; then echo "DEBUG: Temporary files will be written to ${base_dir}" fi export -p base_dir commands="${base_dir}/commands.txt" if [[ ${DEBUG} -eq 1 ]]; then echo "DEBUG: Storing commands for $(pwd) in ${commands}" fi export -p commands optimize_png() { if [ "$#" -ne 2 ]; then echo -e "\nUsage: ${0} \n" exit 1 fi local temp_dir temp_dir="${1}" local file_path file_path="${2}" local stored_checksum stored_checksum="$(exiftool -ignoreMinorErrors -short -short -short -Comment "${file_path}")" local current_checksum current_checksum="$(identify -quiet -format '%#\n' "${file_path}")" local new_file new_file="${temp_dir}${file_path}" if [ "${current_checksum}" == "${stored_checksum}" ]; then if [[ ${DEBUG} -eq 1 ]]; then echo "DEBUG: Stored and calculated checksums match for ${file_path}" fi return fi if oxipng --opt 2 --quiet --alpha --keep="tEXt,display" --zopfli --out "${new_file}" --threads "${oxipng_thread_count}" "${file_path}"; then local old_size old_size=$(du -b "${file_path}" | awk '{print $1}') local old_size_human old_size_human=$(du -h "${file_path}" | awk '{print $1}') local new_size new_size=$(du -b "${new_file}" | awk '{print $1}') local new_size_human new_size_human=$(du -h "${new_file}" | awk '{print $1}') local new_percentage new_percentage=$(echo "scale=2; ${new_size}/${old_size}*100" | bc) local rounded_percentage rounded_percentage=$(printf "%.0f" "${new_percentage}") local new_checksum new_checksum=$(identify -quiet -format '%#\n' "${new_file}") exiftool -ignoreMinorErrors -Comment="${new_checksum}" "${new_file}" >/dev/null 2>&1 echo "Reduced file size of ${file_path} from ${old_size_human} to ${new_size_human} (${rounded_percentage}%)" if [[ ${DEBUG} -eq 1 ]]; then SHELLNOCASEMATCH=$(shopt -p nocasematch; true) shopt -s nocasematch read -r -p 'Replace with optimized file? [y/N]: ' keep_file if [[ "${keep_file}" == "n" ]]; then echo "DEBUG: Not modifying ${file_path}." $SHELLNOCASEMATCH return else echo "DEBUG: Replacing ${file_path} with optimized version." fi $SHELLNOCASEMATCH fi mv "${new_file}" "${file_path}" fi } export -f -p optimize_png optimize_pngs() { if [ "$#" -ne 2 ]; then echo -e "\nUsage: ${0} \n" exit 1 fi local base_dir base_dir="${1}" local target_path target_path="${2}" if [[ ${DEBUG} -eq 1 ]]; then echo "DEBUG: Target path is ${target_path}" fi temp_dir="${base_dir}${target_path}" mkdir -p "${temp_dir}" if [[ ${DEBUG} -eq 1 ]]; then echo "DEBUG: Created temporary directory ${temp_dir} for ${target_path}" fi local commands commands="${temp_dir}/commands.txt" if [[ ${DEBUG} -eq 1 ]]; then echo "DEBUG: Storing commands for ${target_path} in ${commands}" fi while read -r input_file; do if [ -f "${input_file}" ]; then printf 'bash -c \"optimize_png %q %q\"\n' "${temp_dir}" "${input_file}" >> "${commands}" fi done <<< "$(find "${target_path}" -mindepth 1 -maxdepth 1 -type f -name '*.png' -mmin -"${max_mins}" -print)" if [ -s "${commands}" ]; then parallel --line-buffer --no-notice --jobs "${parallel_thread_count}" < "${commands}" fi } export -f -p optimize_pngs while read -r path; do printf 'bash -c \"optimize_pngs %q %q\"\n' "${base_dir}" "${path}" >> "${commands}" done <<< "$(find "$(pwd -P)" -type d)" parallel --line-buffer --no-notice --jobs "${parallel_thread_count}" < "${commands}" SHELLNOCASEMATCH=$(shopt -p nocasematch; true) shopt -s nocasematch read -r -p 'Delete temporary directory? [y/N]: ' remove_temp_dir if [[ "${remove_temp_dir}" == "y" ]]; then rm -rf "${base_dir}" fi $SHELLNOCASEMATCH