Skip to content

Instantly share code, notes, and snippets.

@acorn1010
Created March 24, 2023 17:56
Show Gist options
  • Save acorn1010/2ced01cdb32caa27d34a070f88e0cf10 to your computer and use it in GitHub Desktop.
Save acorn1010/2ced01cdb32caa27d34a070f88e0cf10 to your computer and use it in GitHub Desktop.
generate_images
#!/usr/bin/env bash
set -e
# Creates webp / avif images for images that don't already exist and places them in the public folder
# This script can take a while to run
# Install deps
# sudo apt-get install -f webp ffmpeg opusenc
# MacOS deps
# brew install zopfli
# brew install ffmpeg
# brew install joedrago/repo/avifenc
# Ensure deps are executable
chmod +x ./avifenc
chmod +x ./file_utils
chmod +x ./zopflipng
chmod +x ./magick
chmod +x ./packTexture.sh
# Path variables for conversion
DESIGN_IMG_PATH=design/images
ITEM_DESIGN_IMG_PATH=${DESIGN_IMG_PATH}/items
CURSOR_DESIGN_IMG_PATH=${DESIGN_IMG_PATH}/games/account/items/Cursors
GAME_DESIGN_IMG_PATH=${DESIGN_IMG_PATH}/games
SITE_DESIGN_IMG_PATH=${DESIGN_IMG_PATH}/site
POST_DESIGN_IMG_PATH=${DESIGN_IMG_PATH}/site/posts
AUDIO_DESIGN_PATH=design/audio
PUB_IMG_PATH=client/public/img
ITEM_PUB_IMG_PATH=${PUB_IMG_PATH}/items
CURSOR_PUB_IMG_PATH=${PUB_IMG_PATH}/games/account/items/Cursors
GAME_PUB_IMG_PATH=${PUB_IMG_PATH}/games
SITE_PUB_IMG_PATH=${PUB_IMG_PATH}
POST_PUB_IMG_PATH=${PUB_IMG_PATH}/posts
AUDIO_PUB_PATH=client/public/audio
# Increase degree of parallelism
NUM_PROCESSES=20
# Convert PNG to WEBP and AVIF.
function convert_images() {
input_file=${1?}
design_path=${2?}
pub_path=${3?}
image_type=${4?} # icon, cursor, other
if [ "${image_type}" == 'icon' ]; then
img_basename=$(sed -E "s#${design_path}/(.*)\.png#\1#" <<< ${input_file})
else
img_basename=$(sed -E "s#${design_path}/(.*)\..{3}#\1#" <<< ${input_file})
fi
png_path=${pub_path}/${img_basename}.png
png_path_2x=${pub_path}/${img_basename}.2x.png
webp_path=${pub_path}/${img_basename}.webp
avif_path=${pub_path}/${img_basename}.avif
unset png_source_path[@]
unset magick_options[@]
case "${image_type}" in
'icon')
png_source_path[0]=${png_path_2x}
png_source_path[1]=${png_path}
magick_options[0]="-resize 300x300> -gravity center -trim"
magick_options[1]="-resize 150x150> -gravity center -trim"
avifenc_options="--min 23 --max 53 --minalpha 23 --maxalpha 53"
;;
'cursor')
png_source_path[0]=${png_path}
magick_options[0]="-resize 28x28>"
avifenc_options="--min 14 --max 21 --minalpha 14 --maxalpha 21"
;;
'cursor_pointer')
png_source_path[0]=${png_path}
# NOTE: We resize first, and THEN rotate. This can leave extra whitespace, but we don't
# trim it because this would make the cursor have a different click offset.
magick_options[0]="-resize 28x28> -background transparent -rotate 40"
avifenc_options="--min 14 --max 21 --minalpha 14 --maxalpha 21"
;;
*)
png_source_path[0]=${png_path}
avifenc_options="--min 14 --max 21 --minalpha 14 --maxalpha 21"
;;
esac
# Create paths if it does not exist.
mkdir -p "$(dirname "${png_source_path[0]}")"
# Move file if necessary.
if [ ! -f "${png_source_path[0]}" ]; then
index=0
for i in "${png_source_path[@]}"; do
./magick convert "${input_file}" -profile ./design/color_profiles/sRGBz.icc ${magick_options[index]} "${png_source_path[index]}"
./zopflipng -y --keepchunks=iCCP --lossy_transparent "${png_source_path[index]}" "${png_source_path[index]}"
index=$((index+1))
done
fi
# Convert to WEBP if necssary.
if [ ! -f "${webp_path}" ]; then
cwebp -metadata icc -quiet -q 90 "${png_source_path[0]}" -o "${webp_path}"
fi
# Convert to AVIF if necessary.
if [ ! -f "${avif_path}" ]; then
# Firefox messes up on depth 10. If we use a limited range and depth 8, then we get the correct
# sRGB rednering.
./avifenc -c aom --speed 0 --jobs 12 --range limited --ignore-icc --yuv 444 ${avifenc_options} "${png_source_path[0]}" "${avif_path}"
fi
}
# export to access in bash subshell
export -f convert_images
# Generate OpenGraph images for social sharing of the Foony URLs
function create_og_image() {
input_file=${1?}
design_path=${2?}
pub_path=${3?}
game_id=$(sed -E "s#${design_path}/(.*)#\1#" <<< ${input_file})
echo "input_file = ${input_file}"
echo "game_id = ${game_id}"
original_og_image_path="${input_file}/og_image.png"
hero_image_path="${input_file}/hero_image.png"
thumbnail_path="${input_file}/thumbnail.png"
og_image_path="${pub_path}/${game_id}/og_image.png"
og_image_path_webp="${pub_path}/${game_id}/og_image.webp"
# Create paths if it does not exist.
mkdir -p "$(dirname "${og_image_path}")"
# Check if hero_image.png exists, else use thumbnail.png
if [ -f "${original_og_image_path}" ]; then
image_source="${original_og_image_path}"
elif [ -f "${hero_image_path}" ]; then
image_source="${hero_image_path}"
elif [ -f "${hero_image_path}" ]; then
image_source="${thumbnail_path}"
else
return 0 # No hero_image or thumbnail for path. TODO(acorn1010): Emit warning if not 'account' gameId?
fi
# Create og_image.png if it does not exist.
if [ ! -f "${og_image_path}" ]; then
./magick convert "${image_source}" -profile ./design/color_profiles/sRGBz.icc -resize 1200x630^ -gravity center -extent 1200x630 "${og_image_path}"
cwebp -metadata icc -quiet -q 90 "${og_image_path}" -o "${og_image_path_webp}"
fi
}
export -f create_og_image
function convert_audio() {
input_file=${1?}
design_path=${2?}
pub_path=${3?}
img_basename=$(sed -E "s#${design_path}/(.*)\..{3}#\1#" <<< "${input_file}")
full_path=${pub_path}/${img_basename}
# Create paths if it does not exist.
mkdir -p "$(dirname "${full_path}")"
# Create audio file for normal users if necessary
if [ ! -f "${full_path}.opus" ]; then
opusenc --vbr --bitrate 48 "${input_file}" "${full_path}.opus"
fi
# Create audio file for special Apple users
if [ ! -f "${full_path}.m4a" ]; then
ffmpeg -i "${input_file}" -c:a aac -b:a 96k "${full_path}.m4a"
fi
}
export -f convert_audio
# Convert GIF to WEBM.
function convert_gifs() {
input_file=${1?}
design_path=${2?}
pub_path=${3?}
img_basename=$(sed -E "s#${design_path}/(.*)\..{3}#\1#" <<< "${input_file}")
full_path=${pub_path}/${img_basename}
# Create paths if it does not exist.
mkdir -p "$(dirname "${full_path}")"
# Move file if necessary.
if [ ! -f "${full_path}.webm" ]; then
ffmpeg -y -i "${input_file}" -c vp9 -b:v 0 -crf 40 "${full_path}.webm"
fi
}
# export to access in bash subshell
export -f convert_gifs
# These functions can run in parallel using xargs "-P #" value
find "${ITEM_DESIGN_IMG_PATH}" -type f -and -iname "*.png" -print0 | xargs -0 -P ${NUM_PROCESSES} -I {} bash -c 'convert_images "$@"' _ {} "${ITEM_DESIGN_IMG_PATH}" "${ITEM_PUB_IMG_PATH}" icon
find "${GAME_DESIGN_IMG_PATH}" -maxdepth 1 -mindepth 1 -type d -print0 | xargs -0 -P ${NUM_PROCESSES} -I {} bash -c 'create_og_image "$@"' _ {} "${GAME_DESIGN_IMG_PATH}" "${GAME_PUB_IMG_PATH}"
find "${CURSOR_DESIGN_IMG_PATH}" -type f -and \( -iname "*.png" -o -iname "*.jpg" \) -print0 | xargs -0 -P ${NUM_PROCESSES} -I {} bash -c 'convert_images "$@"' _ {} "${CURSOR_DESIGN_IMG_PATH}" "${CURSOR_PUB_IMG_PATH}" cursor
find "${CURSOR_DESIGN_IMG_PATH}" -type f -and \( -iname "*.png" -o -iname "*.jpg" \) -print0 | xargs -0 -P ${NUM_PROCESSES} -I {} bash -c 'convert_images "$@"' _ {} "${CURSOR_DESIGN_IMG_PATH}" "${CURSOR_PUB_IMG_PATH}Pointer" cursor_pointer
find "${GAME_DESIGN_IMG_PATH}" -type f -and \( -iname "*.png" -o -iname "*.jpg" \) -print0 | xargs -0 -P ${NUM_PROCESSES} -I {} bash -c 'convert_images "$@"' _ {} "${GAME_DESIGN_IMG_PATH}" "${GAME_PUB_IMG_PATH}" other
find "${SITE_DESIGN_IMG_PATH}" -type f -and \( -iname "*.png" -o -iname "*.jpg" \) -print0 | xargs -0 -P ${NUM_PROCESSES} -I {} bash -c 'convert_images "$@"' _ {} "${SITE_DESIGN_IMG_PATH}" "${SITE_PUB_IMG_PATH}" other
find "${POST_DESIGN_IMG_PATH}" -type f -and -iname "*.gif" -print0 | xargs -0 -P ${NUM_PROCESSES} -I {} bash -c 'convert_gifs "$@"' _ {} "${POST_DESIGN_IMG_PATH}" "${POST_PUB_IMG_PATH}"
find "${AUDIO_DESIGN_PATH}" -type f -and -iname "*.wav" -print0 | xargs -0 -P ${NUM_PROCESSES} -I {} bash -c 'convert_audio "$@"' _ {} "${AUDIO_DESIGN_PATH}" "${AUDIO_PUB_PATH}"
./file_utils image "client/public" "client/src/images/SiteImages.ts";
./file_utils audio "client/public/audio" "client/src/audio/AudioFiles.ts";
./file_utils src "client/src" "client/src/io/TypeScriptFiles.ts";
@juaoose
Copy link

juaoose commented Mar 24, 2023

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment