Last active
February 21, 2026 12:16
-
-
Save jvarn/1d635dcdad94e5f3d38c0cbe7ecac780 to your computer and use it in GitHub Desktop.
Quick setup for audible-cli on macOS (setup then download and convert)
This file contains hidden or 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
| #!/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." |
This file contains hidden or 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
| #!/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