Skip to content

Instantly share code, notes, and snippets.

@jvarn
Last active February 21, 2026 12:16
Show Gist options
  • Select an option

  • Save jvarn/1d635dcdad94e5f3d38c0cbe7ecac780 to your computer and use it in GitHub Desktop.

Select an option

Save jvarn/1d635dcdad94e5f3d38c0cbe7ecac780 to your computer and use it in GitHub Desktop.
Quick setup for audible-cli on macOS (setup then download and convert)
#!/usr/bin/env bash
set -euo pipefail
OUTDIR="converted"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
# Load .env if present (simple KEY=VALUE lines)
ENV_FILE="$SCRIPT_DIR/.env"
if [ -f "$ENV_FILE" ]; then
# Export all vars defined in .env
set -a
# shellcheck disable=SC1090
. "$ENV_FILE"
set +a
fi
if [ -z "${ACTIVATION_BYTES:-}" ]; then
echo "Error: ACTIVATION_BYTES is not set."
echo "Run: ./setup_audible_env.sh"
exit 1
fi
# Activate venv (required for audible-cli)
if [ -f "venv/bin/activate" ]; then
# shellcheck disable=SC1091
. "venv/bin/activate"
else
echo "Error: venv/bin/activate not found (expected: $SCRIPT_DIR/venv/bin/activate)"
echo "Run: ./setup_audible_env.sh"
exit 1
fi
# audible command
if command -v audible >/dev/null 2>&1; then
AUDIBLE_CMD=(audible)
else
AUDIBLE_CMD=(python -m audible)
fi
if [ $# -ne 1 ]; then
echo "Usage: $0 <BOOKID/ASIN>"
exit 1
fi
BOOKID="$1"
echo "Running audible cli with ASIN: $BOOKID..."
mkdir -p "$OUTDIR"
LOGFILE="$(mktemp "${TMPDIR:-/tmp}/audible.XXXXXX.log")"
LISTFILE="$(mktemp "${TMPDIR:-/tmp}/aaxlist.XXXXXX")"
cleanup() { rm -f "$LOGFILE" "$LISTFILE"; }
trap cleanup EXIT
# Run download; show output AND save it
"${AUDIBLE_CMD[@]}" download --asin "$BOOKID" --aax 2>&1 | tee "$LOGFILE"
# Normalize carriage returns to newlines, then extract all AAX paths
tr '\r' '\n' < "$LOGFILE" \
| perl -ne 'while(/File\s+(.+?\.aax)\s+(?:downloaded|already exists)/g){ print "$1\n" }' \
| sort -u > "$LISTFILE"
# Fallback if we still got nothing
if [ ! -s "$LISTFILE" ]; then
echo "Warning: Could not parse file paths from audible output; scanning for .aax under $SCRIPT_DIR..."
find "$SCRIPT_DIR" -type f -name '*.aax' 2>/dev/null | sort > "$LISTFILE"
fi
if [ ! -s "$LISTFILE" ]; then
echo "Error: No .aax files found."
deactivate >/dev/null 2>&1 || true
exit 1
fi
echo "Converting AAX files to M4B under: $SCRIPT_DIR/$OUTDIR"
echo "Using ACTIVATION_BYTES=${ACTIVATION_BYTES}"
while IFS= read -r f; do
[ -n "$f" ] || continue
# Safety: only operate inside script dir
case "$f" in
"$SCRIPT_DIR"/*) ;;
*)
echo "Warning: Refusing to process file outside script dir: $f"
continue
;;
esac
if [ ! -f "$f" ]; then
echo "Warning: Missing file, skipping: $f"
continue
fi
# Destination folder:
# - if AAX is in a subfolder under SCRIPT_DIR, use that folder name under converted/
# - otherwise output directly into converted/
src_dir="$(cd "$(dirname "$f")" && pwd)"
if [ "$src_dir" = "$SCRIPT_DIR" ]; then
dest_dir="$SCRIPT_DIR/$OUTDIR"
else
parent_name="$(basename "$src_dir")"
dest_dir="$SCRIPT_DIR/$OUTDIR/$parent_name"
fi
mkdir -p "$dest_dir"
base="$(basename "$f")"
outpath="$dest_dir/${base%.*}.m4b"
if [ -f "$outpath" ]; then
echo "Skipping conversion (already exists): $outpath"
else
echo "Converting: $f -> $outpath"
ffmpeg -hide_banner -y \
-activation_bytes "$ACTIVATION_BYTES" \
-i "$f" \
-c copy \
"$outpath"
fi
if [ -s "$outpath" ]; then
echo "Removing source: $f"
rm -f -- "$f"
else
echo "Warning: Output missing/empty; NOT deleting source: $f"
fi
done < "$LISTFILE"
# Remove empty directories left behind (exclude venv and converted tree)
find "$SCRIPT_DIR" -type d -empty -mindepth 1 -maxdepth 10 2>/dev/null | while IFS= read -r d; do
case "$d" in
"$SCRIPT_DIR/$OUTDIR"|"$SCRIPT_DIR/$OUTDIR"/*|"$SCRIPT_DIR/venv") continue ;;
esac
rmdir "$d" 2>/dev/null || true
done
deactivate >/dev/null 2>&1 || true
echo "Done."
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
VENV_DIR="$SCRIPT_DIR/venv"
ENV_FILE="$SCRIPT_DIR/.env"
echo "== Audible CLI environment setup =="
echo "Working dir: $SCRIPT_DIR"
# --- Homebrew + ffmpeg -------------------------------------------------------
ensure_homebrew() {
if command -v brew >/dev/null 2>&1; then
return 0
fi
echo
echo "Homebrew not found. Installing Homebrew..."
echo "(You may be prompted for your macOS password.)"
echo
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Make brew available in THIS shell session (Apple Silicon vs Intel)
if [ -x /opt/homebrew/bin/brew ]; then
eval "$(/opt/homebrew/bin/brew shellenv)"
elif [ -x /usr/local/bin/brew ]; then
eval "$(/usr/local/bin/brew shellenv)"
fi
if ! command -v brew >/dev/null 2>&1; then
echo "Error: Homebrew install completed but 'brew' is still not on PATH."
echo "Try opening a new terminal, or add brew shellenv to your shell profile."
exit 1
fi
}
ensure_ffmpeg() {
if command -v ffmpeg >/dev/null 2>&1; then
echo "ffmpeg found: $(command -v ffmpeg)"
return 0
fi
echo
echo "ffmpeg not found. Installing via Homebrew..."
ensure_homebrew
brew install ffmpeg
if ! command -v ffmpeg >/dev/null 2>&1; then
echo "Error: ffmpeg install finished but 'ffmpeg' not found on PATH."
exit 1
fi
}
ensure_ffmpeg
# --- Python venv + audible-cli -----------------------------------------------
# 1) Create venv if missing
if [ ! -d "$VENV_DIR" ]; then
echo
echo "Creating venv..."
python3 -m venv "$VENV_DIR"
fi
# Activate venv
# shellcheck disable=SC1091
. "$VENV_DIR/bin/activate"
# Upgrade pip & install audible-cli
echo
echo "Upgrading pip..."
pip install --upgrade pip
echo "Ensuring audible-cli is installed..."
pip install -U audible-cli
if ! command -v audible >/dev/null 2>&1; then
echo "Error: 'audible' not found even after installing audible-cli."
exit 1
fi
# --- Audible quickstart/config ------------------------------------------------
AUDIBLE_HOME="${HOME}/.audible"
AUDIBLE_CONFIG_TOML="${AUDIBLE_HOME}/config.toml"
needs_quickstart=0
if [ ! -d "$AUDIBLE_HOME" ]; then
needs_quickstart=1
elif [ ! -f "$AUDIBLE_CONFIG_TOML" ]; then
needs_quickstart=1
fi
if [ "$needs_quickstart" -eq 1 ]; then
echo
echo "No Audible config detected at:"
echo " $AUDIBLE_CONFIG_TOML"
echo "Running: audible quickstart"
echo "(This is interactive and will open a browser login.)"
echo
audible quickstart
else
echo
echo "Audible config seems present: $AUDIBLE_CONFIG_TOML"
fi
# --- Activation bytes -> .env -------------------------------------------------
echo
echo "Fetching activation bytes..."
ab_out="$(audible activation-bytes 2>&1 | tee /dev/stderr)"
ab="$(printf '%s\n' "$ab_out" \
| tr '\r' '\n' \
| awk '
{ gsub(/^[ \t]+|[ \t]+$/, "", $0) }
$0 ~ /^[0-9a-fA-F]{6,32}$/ { last=$0 }
END { print last }
')"
if [ -z "${ab:-}" ]; then
echo "Error: Could not parse activation bytes from output."
exit 1
fi
# Write/replace ACTIVATION_BYTES line in .env
if [ -f "$ENV_FILE" ]; then
tmp="$(mktemp "${TMPDIR:-/tmp}/env.XXXXXX")"
awk 'BEGIN{IGNORECASE=1} $0 !~ /^ACTIVATION_BYTES=/ {print}' "$ENV_FILE" > "$tmp"
mv "$tmp" "$ENV_FILE"
fi
echo "ACTIVATION_BYTES=$ab" >> "$ENV_FILE"
echo
echo "Wrote $ENV_FILE:"
tail -n 5 "$ENV_FILE" | sed 's/^/ /'
deactivate >/dev/null 2>&1 || true
echo
echo "Setup complete."
echo "Next:"
echo " audible library list"
echo "Then:"
echo " ./getaudible.sh <ASIN>"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment