#!/bin/sh # airstrike quick actions: image converters for the Finder right-click menu, macOS only. # # curl -sL https://airstrike.fyi/quick-actions/install.sh | zsh # curl -sL https://airstrike.fyi/quick-actions/install.sh | bash # curl -sL https://airstrike.fyi/quick-actions/install.sh | zsh -s jpg # curl -sL https://airstrike.fyi/quick-actions/install.sh | bash -s -- --yes webp # # Installs Automator workflows into ~/Library/Services. The JPG ones need only # macOS's own sips. The WebP ones need cwebp from Homebrew's webp package; the # script offers to install it, and points each workflow at wherever cwebp is. [ -n "${ZSH_VERSION:-}" ] && emulate -L sh set -eu BASE="${AIRSTRIKE_BASE:-https://airstrike.fyi}" ARCHIVE="$BASE/quick-actions/quick-actions.tar.gz" DEST="${QUICK_ACTIONS_DIR:-$HOME/Library/Services}" # ---- looks ----------------------------------------------------------------- if [ -t 1 ] && [ -z "${NO_COLOR:-}" ] && [ "${TERM:-dumb}" != dumb ]; then B=$(printf '\033[1m'); D=$(printf '\033[2m'); R=$(printf '\033[0m') GREEN=$(printf '\033[32m'); YELLOW=$(printf '\033[33m'); RED=$(printf '\033[31m'); BLUE=$(printf '\033[34m') else B=''; D=''; R=''; GREEN=''; YELLOW=''; RED=''; BLUE='' fi case "${LC_ALL:-${LC_CTYPE:-${LANG:-}}}" in *[Uu][Tt][Ff]-8*|*[Uu][Tt][Ff]8*) OK='โœ“'; DOT='ยท'; ARROW='โ†’'; DOWN='โ†“';; *) OK='+'; DOT='-'; ARROW='>'; DOWN='v';; esac say() { printf '%s\n' "$*"; } die() { printf '%s!%s %s\n' "$RED" "$R" "$*" >&2; exit 1; } tilde() { case "$1" in "$HOME") printf '~\n';; "$HOME"/*) printf '~%s\n' "${1#"$HOME"}";; *) printf '%s\n' "$1";; esac; } INTERACTIVE=0 if [ -t 1 ] && ( exec < /dev/tty ) 2>/dev/null; then INTERACTIVE=1; fi ask() { if [ "$INTERACTIVE" -eq 0 ]; then if [ "$2" = y ]; then return 0; else return 1; fi; fi if [ "$2" = y ]; then hint='[Y/n]'; else hint='[y/N]'; fi printf ' %s?%s %s %s%s%s ' "$YELLOW" "$R" "$1" "$D" "$hint" "$R" ans=''; read -r ans < /dev/tty || ans='' case "$ans" in [yY]*) return 0;; [nN]*) return 1;; esac if [ "$2" = y ]; then return 0; else return 1; fi } # ---- args ------------------------------------------------------------------ usage() { say "${B}airstrike quick actions${R} ${D}$BASE${R}" say say " | zsh install every action (also works with bash)" say " | zsh -s jpg only the JPG actions (sips, no dependencies)" say " | zsh -s webp only the WebP actions (need cwebp from Homebrew)" say " | zsh -s -- --yes install cwebp with Homebrew without asking" say " | zsh -s -- --dir=PATH install somewhere other than ~/Library/Services" say " | zsh -s -- --list show the actions in the archive and stop" say say " after installing: right-click an image in Finder, then Quick Actions." } YES=0; LIST=0; WANT='' for a in "$@"; do case "$a" in --) ;; -y|--yes) YES=1;; --list) LIST=1;; --dir=*) DEST=${a#--dir=}; case "$DEST" in '~') DEST=$HOME;; '~/'*) DEST="$HOME/${DEST#"~/"}";; esac;; -h|--help) usage; exit 0;; all|jpg|webp) WANT="$WANT $a";; -*) die "unknown flag: $a (try --help)";; *) die "unknown selection: $a (use jpg, webp, or all)";; esac done [ -n "$WANT" ] || WANT=all case " $WANT " in *' all '*) WANT='jpg webp';; esac [ "$(uname -s)" = Darwin ] || die "these are macOS Finder Quick Actions; nothing to install here." say "${B}airstrike quick actions${R}" say # ---- fetch ----------------------------------------------------------------- TMP=$(mktemp -d 2>/dev/null || mktemp -d -t airstrike) trap 'rm -rf "$TMP"' EXIT INT TERM printf ' %s%s%s fetching' "$BLUE" "$DOWN" "$R" curl -sfL "$ARCHIVE" -o "$TMP/qa.tgz" || { say; die "could not fetch $ARCHIVE"; } mkdir -p "$TMP/x" && tar -xzf "$TMP/qa.tgz" -C "$TMP/x" || { say; die "could not unpack the archive"; } say # group -> jpg | webp | other, from the action's own name group() { case "$1" in *[Jj][Pp][Gg]*|*[Jj][Pp][Ee][Gg]*) echo jpg;; *[Ww][Ee][Bb][Pp]*) echo webp;; *) echo other;; esac } if [ "$LIST" -eq 1 ]; then for w in "$TMP"/x/*.workflow; do n=${w##*/}; n=${n%.workflow}; say " $DOT $n ${D}($(group "$n"))${R}"; done exit 0 fi # ---- cwebp ----------------------------------------------------------------- CWEBP='' # PATH first, then the usual Homebrew spots (Apple silicon, then Intel). CWEBP_PATHS="${AIRSTRIKE_CWEBP_PATHS:-/opt/homebrew/bin/cwebp /usr/local/bin/cwebp}" find_cwebp() { CWEBP=$(command -v cwebp 2>/dev/null || true) if [ -z "$CWEBP" ]; then for c in $CWEBP_PATHS; do if [ -x "$c" ]; then CWEBP=$c; break; fi done fi return 0 } find_cwebp case " $WANT " in *' webp '*) if [ -n "$CWEBP" ]; then say " ${GREEN}$OK${R} cwebp ${D}$CWEBP${R}" elif command -v brew >/dev/null 2>&1; then say " ${YELLOW}?${R} cwebp ${D}not found; the WebP actions need it (Homebrew: webp)${R}" # Plain if/elif on purpose: bash 3.2 (macOS /bin/sh) does not reliably # suppress set -e inside a function called from a || list in a condition. install=0 if [ "$YES" -eq 1 ]; then install=1 elif ask "Install webp with Homebrew now?" y; then install=1 fi if [ "$install" -eq 1 ]; then brew install webp >/dev/null 2>&1 || die "brew install webp failed" find_cwebp if [ -n "$CWEBP" ]; then say " ${GREEN}$OK${R} cwebp ${D}$CWEBP${R}"; fi fi else say " ${DOT} cwebp ${D}not found, and no Homebrew; WebP actions skipped (brew install webp, then rerun with webp)${R}" fi if [ -z "$CWEBP" ]; then WANT=$(printf '%s' "$WANT" | sed 's/webp//'); fi ;; esac say " ${GREEN}$OK${R} sips ${D}/usr/bin/sips (macOS)${R}" # ---- install --------------------------------------------------------------- mkdir -p "$DEST" say say " ${B}Finder Quick Actions${R} ${D}$(tilde "$DEST")${R}" count=0 for w in "$TMP"/x/*.workflow; do n=${w##*/}; n=${n%.workflow}; g=$(group "$n") case " $WANT " in *" $g "*) ;; *) continue;; esac doc="$w/Contents/document.wflow" if [ "$g" = webp ] && [ "$CWEBP" != /opt/homebrew/bin/cwebp ]; then sed "s#/opt/homebrew/bin/cwebp#$CWEBP#g" "$doc" > "$doc.tmp" && mv "$doc.tmp" "$doc" fi target="$DEST/$n.workflow" if [ ! -d "$target" ]; then verdict=new elif cmp -s "$doc" "$target/Contents/document.wflow" && cmp -s "$w/Contents/Info.plist" "$target/Contents/Info.plist"; then verdict=unchanged else verdict=updated; fi rm -rf "$target" cp -R "$w" "$target" say " ${GREEN}$ARROW${R} $n ${D}($verdict)${R}" count=$((count+1)) done [ "$count" -gt 0 ] || die "nothing selected." # Tell the system about new services so they show up without a re-login. [ -x /System/Library/CoreServices/pbs ] && /System/Library/CoreServices/pbs -update >/dev/null 2>&1 || true say say " ${GREEN}done.${R} right-click an image in Finder ${ARROW} Quick Actions. ${D}If the menu is stale, log out and back in.${R}"