Last active
September 1, 2020 18:48
-
-
Save alexmarkley/696e9788e61995e5f0a936cc33faa2b9 to your computer and use it in GitHub Desktop.
generateImageDerivatives.sh -- That Feeling When You Realize You Should Have Used A Higher-Level Language But You've Already Written Too Much Bash And You Can't Stop Now...
This file contains 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 characters
#!/bin/bash | |
set -o pipefail | |
# Requested image sizes describes the size breakpoints we will resize down to. Sizes larger than the input image are ignored. | |
REQUESTED_IMAGE_SIZES=(3840 2880 1920 1440 1280 1024 640 320 128 64) | |
# Manifest format version should be incremented only if the format changes enough that a client should not try to parse it. | |
MANIFEST_FORMAT_VERSION=1 | |
# Manifest generation version should be incremented every time the derivative generation rules change (for example if REQUESTED_IMAGE_SIZES is updated or if the resampling algorithm changes) to force a regeneration of the derivatives even if the source image has not changed. | |
MANIFEST_GENERATION_VERSION=1 | |
MYTEMPDIR=$(mktemp -d) | |
function _cleanup() { | |
if [ ! -z "${MYTEMPDIR}" ]; then | |
rm -rf "${MYTEMPDIR}" | |
fi | |
} | |
function _log() { | |
echo generateImageDerivatives.sh: "${@}" 1>&2 | |
} | |
function _die() { | |
_log FATAL: "${@}" | |
_cleanup | |
exit 1 | |
} | |
BASEPATH="$( cd "$(dirname "${0}")/.." && pwd -P )" || _die "Failed to identify BASEPATH." | |
# _log "Base Path: ${BASEPATH}" | |
CONTENTPATH="${BASEPATH}/content/" | |
# _log "Content Path: ${CONTENTPATH}" | |
REALPATH="$(which realpath)" | |
if [ -z "${REALPATH}" ]; then | |
REALPATH="$(which grealpath)" | |
if [ -z "${REALPATH}" ]; then | |
_die "Could not find realpath binary." | |
fi | |
fi | |
SHA256SUM="$(which sha256sum)" | |
if [ -z "${SHA256SUM}" ]; then | |
SHA256SUM="$(which gsha256sum)" | |
if [ -z "${SHA256SUM}" ]; then | |
_die "Could not find sha256sum binary." | |
fi | |
fi | |
if [ -z "${DESTINATION_BUCKET}" ]; then | |
_die "Missing required environment variable DESTINATION_BUCKET." | |
fi | |
INPUT_IMAGE="${1}" | |
_log "Checking Input Image: ${INPUT_IMAGE}" | |
INPUT_IMAGE_FULLPATH="$("${REALPATH}" "${INPUT_IMAGE}")" || _die "Could not find the image you asked for." | |
# _log "Input Image Full Path: ${INPUT_IMAGE_FULLPATH}" | |
INPUT_IMAGE_PATHPREFIX="${INPUT_IMAGE_FULLPATH:0:${#CONTENTPATH}}" | |
if [ "${CONTENTPATH}" != "${INPUT_IMAGE_PATHPREFIX}" ]; then | |
_die "Input Image must exist within the Content Path: ${CONTENTPATH}" | |
fi | |
CONTENT_KEY="${INPUT_IMAGE_FULLPATH:${#CONTENTPATH}}" | |
_log "Image Content Key: ${CONTENT_KEY}" | |
INPUT_IMAGE_SHA256SUM=$("${SHA256SUM}" "${INPUT_IMAGE_FULLPATH}" | cut -f1 -d' ') || _die "Failed to calculate image sha256sum" | |
if [ -z "${INPUT_IMAGE_SHA256SUM}" ]; then | |
_die "Image sha256sum cannot be an empty string" | |
fi | |
_log "Calculated image sha256sum: ${INPUT_IMAGE_SHA256SUM}" | |
INPUT_IMAGE_ATTRIBUTES=$(identify -format "%[w] %[h] %[e]" "${INPUT_IMAGE_FULLPATH}[0]") || _die "Failed to identify input image" | |
INPUT_IMAGE_WIDTH=$(echo "${INPUT_IMAGE_ATTRIBUTES}" | cut -f1 -d' ') || _die "Failed to parse input image width" | |
if ! [ "${INPUT_IMAGE_WIDTH}" -eq "${INPUT_IMAGE_WIDTH}" ] 2>/dev/null; then | |
_die "input image width is apparently not an integer" | |
fi | |
INPUT_IMAGE_HEIGHT=$(echo "${INPUT_IMAGE_ATTRIBUTES}" | cut -f2 -d' ') || _die "Failed to parse input image height" | |
if ! [ "${INPUT_IMAGE_HEIGHT}" -eq "${INPUT_IMAGE_HEIGHT}" ] 2>/dev/null; then | |
_die "input image height is apparently not an integer" | |
fi | |
INPUT_IMAGE_EXTENSION=$(echo "${INPUT_IMAGE_ATTRIBUTES}" | cut -f3 -d' ') || _die "Failed to parse input image extension" | |
if [ -z "${INPUT_IMAGE_EXTENSION}" ]; then | |
_die "input image apparently has no extension" | |
fi | |
_log "Image has extension \"${INPUT_IMAGE_EXTENSION}\" and size ${INPUT_IMAGE_WIDTH} x ${INPUT_IMAGE_HEIGHT}" | |
IMAGE_PATHCOMPONENTS_DIR=$(dirname "${CONTENT_KEY}") || _die "dirname failed" | |
IMAGE_PATHCOMPONENTS_BASE=$(basename "${CONTENT_KEY}" "${INPUT_IMAGE_EXTENSION}") || _die "basename failed" | |
IMAGE_PATHCOMPONENTS_EXTENSION="${INPUT_IMAGE_EXTENSION}" | |
MANIFEST_FILENAME="${IMAGE_PATHCOMPONENTS_BASE}${IMAGE_PATHCOMPONENTS_EXTENSION}.manifest.json" | |
MANIFEST_FILEPATH_LOCAL="${MYTEMPDIR}/${MANIFEST_FILENAME}" | |
MANIFEST_S3_URL="s3://${DESTINATION_BUCKET}/derivatives/manifest/${IMAGE_PATHCOMPONENTS_DIR}/${MANIFEST_FILENAME}" | |
_log "Checking for existing manifest for this image..." | |
aws s3 cp "${MANIFEST_S3_URL}" "${MANIFEST_FILEPATH_LOCAL}" | |
FETCHED="${?}" | |
if [ "${FETCHED}" -eq 0 ]; then | |
EXISTINGMANIFEST_FORMAT_VERSION=$(cat "${MANIFEST_FILEPATH_LOCAL}" | jq -r '.formatVersion') | |
EXISTINGMANIFEST_GENERATION_VERSION=$(cat "${MANIFEST_FILEPATH_LOCAL}" | jq -r '.generationVersion') | |
EXISTINGMANIFEST_CONTENT_KEY=$(cat "${MANIFEST_FILEPATH_LOCAL}" | jq -r '.contentKey') | |
EXISTINGMANIFEST_SOURCE_SHA256SUM=$(cat "${MANIFEST_FILEPATH_LOCAL}" | jq -r '.sourceSHA256SUM') | |
_log "Existing manifest has formatVersion: ${EXISTINGMANIFEST_FORMAT_VERSION}, generationVersion: ${EXISTINGMANIFEST_GENERATION_VERSION}, contentKey: ${EXISTINGMANIFEST_CONTENT_KEY}, sourceSHA256SUM: ${EXISTINGMANIFEST_SOURCE_SHA256SUM}" | |
if [ "${EXISTINGMANIFEST_FORMAT_VERSION}" = "${MANIFEST_FORMAT_VERSION}" -a "${EXISTINGMANIFEST_GENERATION_VERSION}" = "${MANIFEST_GENERATION_VERSION}" -a "${EXISTINGMANIFEST_CONTENT_KEY}" = "${CONTENT_KEY}" -a "${EXISTINGMANIFEST_SOURCE_SHA256SUM}" = "${INPUT_IMAGE_SHA256SUM}" ]; then | |
_log "Nothing has changed with this image, declining to update derivatives." | |
_cleanup | |
exit 0 | |
else | |
_log "Existing manifest file has properties that don't match. Going ahead with creating derivatives..." | |
fi | |
else | |
_log "No existing manifest file. Going ahead with creating derivatives..." | |
fi | |
IMAGE_LONGEST_LENGTH="" | |
if [ "${INPUT_IMAGE_WIDTH}" -ge "${INPUT_IMAGE_HEIGHT}" ]; then | |
IMAGE_LONGEST_LENGTH="${INPUT_IMAGE_WIDTH}" | |
_log "Input image's longest dimension is width: ${IMAGE_LONGEST_LENGTH}" | |
else | |
IMAGE_LONGEST_LENGTH="${INPUT_IMAGE_HEIGHT}" | |
_log "Input image's longest dimension is height: ${IMAGE_LONGEST_LENGTH}" | |
fi | |
DERIVATIVEIMAGE_JSONTEMPLATE='{key: $key, isOriginal: $isOriginal, longestSide: $longestSide, width: $width, height: $height}' | |
ORIGINAL_IMAGE_JSON=$(jq -n -r --arg key "content/${CONTENT_KEY}" --argjson isOriginal true --argjson longestSide "${IMAGE_LONGEST_LENGTH}" --argjson width "${INPUT_IMAGE_WIDTH}" --argjson height "${INPUT_IMAGE_HEIGHT}" "${DERIVATIVEIMAGE_JSONTEMPLATE}") || _die "failed to generate original image json" | |
IMAGES_JSON=("${ORIGINAL_IMAGE_JSON}") | |
for SIZE in "${REQUESTED_IMAGE_SIZES[@]}"; do | |
_log "Handling image size ${SIZE}..." | |
if [ "${SIZE}" -lt "${IMAGE_LONGEST_LENGTH}" ]; then | |
OUTPUT_FILENAME="${IMAGE_PATHCOMPONENTS_BASE}${SIZE}.${IMAGE_PATHCOMPONENTS_EXTENSION}" | |
OUTPUT_FILEPATH_LOCAL="${MYTEMPDIR}/${OUTPUT_FILENAME}" | |
OUTPUT_S3_KEY="derivatives/generatedImages/${IMAGE_PATHCOMPONENTS_DIR}/${OUTPUT_FILENAME}" | |
OUTPUT_S3_URL="s3://${DESTINATION_BUCKET}/${OUTPUT_S3_KEY}" | |
_log "Generating resized [ fit to ${SIZE}x${SIZE} ] image: ${OUTPUT_FILEPATH_LOCAL}" | |
convert "${INPUT_IMAGE_FULLPATH}" -resize "${SIZE}"x"${SIZE}" "${OUTPUT_FILEPATH_LOCAL}" || _die "Failed to generate output image." | |
OUTPUT_IMAGE_ATTRIBUTES=$(identify -format "%[w] %[h]" "${OUTPUT_FILEPATH_LOCAL}[0]") || _die "Failed to identify output image" | |
OUTPUT_IMAGE_WIDTH=$(echo "${OUTPUT_IMAGE_ATTRIBUTES}" | cut -f1 -d' ') || _die "Failed to parse output image width" | |
if ! [ "${OUTPUT_IMAGE_WIDTH}" -eq "${OUTPUT_IMAGE_WIDTH}" ] 2>/dev/null; then | |
_die "output image width is apparently not an integer" | |
fi | |
OUTPUT_IMAGE_HEIGHT=$(echo "${OUTPUT_IMAGE_ATTRIBUTES}" | cut -f2 -d' ') || _die "Failed to parse output image height" | |
if ! [ "${OUTPUT_IMAGE_HEIGHT}" -eq "${OUTPUT_IMAGE_HEIGHT}" ] 2>/dev/null; then | |
_die "output image height is apparently not an integer" | |
fi | |
_log "Uploading generated image: ${OUTPUT_S3_URL}" | |
aws s3 cp "${OUTPUT_FILEPATH_LOCAL}" "${OUTPUT_S3_URL}" || _die "Failed to upload generated image." | |
DERIV_IMAGE_JSON=$(jq -n -r --arg key "${OUTPUT_S3_KEY}" --argjson isOriginal false --argjson longestSide "${SIZE}" --argjson width "${OUTPUT_IMAGE_WIDTH}" --argjson height "${OUTPUT_IMAGE_HEIGHT}" "${DERIVATIVEIMAGE_JSONTEMPLATE}") || _die "Failed to generate derivative image json" | |
IMAGES_JSON+=("${DERIV_IMAGE_JSON}") | |
else | |
_log "Ignoring this size, because it would result in upsampling." | |
fi | |
done | |
echo "${IMAGES_JSON[@]}" | jq -r -s --argjson formatVersion "${MANIFEST_FORMAT_VERSION}" --argjson generationVersion "${MANIFEST_GENERATION_VERSION}" --arg contentKey "${CONTENT_KEY}" --arg sourceSHA256SUM "${INPUT_IMAGE_SHA256SUM}" '{ formatVersion: $formatVersion, generationVersion: $generationVersion, contentKey: $contentKey, sourceSHA256SUM: $sourceSHA256SUM, variants: . }' >"${MANIFEST_FILEPATH_LOCAL}" || _die "failed to generate manifest json" | |
_log "Uploading manifest file: ${MANIFEST_S3_URL}" | |
aws s3 cp "${MANIFEST_FILEPATH_LOCAL}" "${MANIFEST_S3_URL}" || _die "failed to upload manifest json" | |
_cleanup | |
_log "All done." | |
exit 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment