#!/usr/bin/env bash # exit if a command fails set -o errexit # exit if required variables are not set set -o nounset # number of threads to use if [[ ${DEBUG} -eq 1 ]]; then echo "Running single thread for debugging" parallel_thread_count=1 export oxipng_thread_count=1 else parallel_thread_count=8 export oxipng_thread_count=4 fi # commands required for script execution required_commands="oxipng parallel" function check_dependencies() { local missing_counter 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 } # make sure the correct number of arguments are passed; if not, output syntax and exit if [ "$#" -ne 1 ]; then echo -e "\nUsage: ${0} \n" exit 1 fi # set base directory (passed from invoking command) base_dir="${1}" # skip optimizing the optimization directories if [[ "$(pwd)" == *".optimized" ]]; then if [[ ${DEBUG} -eq 1 ]]; then echo "Skipping optimization directory" fi exit 0 fi # store current directory current_dir="$(pwd)" export current_dir if [[ ${DEBUG} -eq 1 ]]; then echo "Current directory is ${current_dir}" fi # create directory for file hashes hash_dir="${current_dir}/.optimized" export hash_dir mkdir -p "${hash_dir}" if [[ ${DEBUG} -eq 1 ]]; then echo "Hash directory is ${hash_dir}" fi # create matching filesystem hierarchy in tempdir temp_dir="${base_dir}$(readlink -f .)" export temp_dir mkdir -p "${temp_dir}" if [[ ${DEBUG} -eq 1 ]]; then echo "Created temporary directory ${temp_dir} for ${current_dir}" fi # create temporary file to store pending commands commands="${temp_dir}/commands.txt" if [[ ${DEBUG} -eq 1 ]]; then echo "Storing commands for ${current_dir} in ${commands}" fi # attempt to optimize png, and store hash of output file if successful function optimize_png() { local file_name file_name="${1}" local stored_hash stored_hash="${hash_dir}/${file_name}.hash" local current_hash current_hash="${temp_dir}/${file_name}.hash" local new_file new_file="${temp_dir}/${file_name}" local old_file old_file="${current_dir}/${file_name}" sha256sum "${old_file}" | cut -d " " -f 1 > "${current_hash}" if [ -f "${stored_hash}" ] && cmp -s "${current_hash}" "${stored_hash}"; then if [[ ${DEBUG} -eq 1 ]]; then echo "${current_hash} matches ${stored_hash}" fi return fi if [[ ${DEBUG} -eq 1 ]]; then echo "${current_hash} does not match ${stored_hash}" return fi if oxipng --opt 2 --quiet --zopfli --out "${new_file}" --strip safe --threads "${oxipng_thread_count}" "${old_file}"; then mv "${new_file}" "${old_file}" sha256sum "${old_file}" | cut -d " " -f 1 > "${current_hash}" mv "${current_hash}" "${stored_hash}" fi if [[ ${DEBUG} -eq 1 ]]; then sleep 1 fi } export -f optimize_png # generate list of commands to run for input_file in *.png; do if [ -f "${input_file}" ]; then echo "optimize_png \"${input_file}\"" >> "${commands}" fi done # run commands in parallel if [ -s "${commands}" ]; then parallel --no-notice --jobs "${parallel_thread_count}" < "${commands}" fi