#!/usr/bin/env bash set -euo pipefail # POS: media yt-mp4 — Download video as MP4 (smart/interactive format select) # POS_FLAGS: --format --best --worst --output --no-playlist --cookies --dry-run source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh" source "$(dirname "$0")/../lib/yt-lib.sh" 2>/dev/null || source "$(dirname "$0")/yt-lib.sh" # --dry-run is parsed before the deps guards (below): it previews the yt-dlp # command without requiring yt-dlp/ffmpeg to be installed. DRY_RUN=0 for arg in "$@"; do [ "$arg" = "--dry-run" ] && DRY_RUN=1 done # Deps guards sit before -h|--help (help also errors on a box missing the deps). # yt_check_deps with dry-run=1 skips the dep checks (dry-run preview needs none). yt_check_deps "$DRY_RUN" "MP4 merge" # Env seam: YT_OUT_DIR overrides the default (keeps HOME sandbox-compatible). OUT_DIR="${YT_OUT_DIR:-$HOME/Videos}" FORMAT="" BEST=0 WORST=0 EXTRA_ARGS=() usage() { cat < Download video from a URL via yt-dlp. Without -f/--best/--worst, formats are listed and picked interactively. Options: -f, --format Download that format id directly (no prompt) --best Best video + audio (no prompt) --worst Lowest quality (no prompt) -o, --output Output directory (default: $OUT_DIR) --no-playlist Download only the single video --cookies Netscape cookies.txt for age-gated content --dry-run Print the yt-dlp command without running it -h, --help This help Examples: pos media yt mp4 https://youtube.com/watch?v=dQw4w9WgXcQ pos media yt mp4 --best https://youtube.com/watch?v=dQw4w9WgXcQ pos media yt mp4 -f 22 https://youtube.com/watch?v=dQw4w9WgXcQ EOF exit 0 } URL="" while [[ $# -gt 0 ]]; do case "$1" in -h|--help) usage ;; -f|--format) [ $# -ge 2 ] || err "--format needs a value" FORMAT="$2"; shift 2 ;; --best) BEST=1; shift ;; --worst) WORST=1; shift ;; -o|--output) [ $# -ge 2 ] || err "--output needs a value" OUT_DIR="$2"; shift 2 ;; --no-playlist) EXTRA_ARGS+=(--no-playlist); shift ;; --cookies) [ $# -ge 2 ] || err "--cookies needs a value" [ -f "$2" ] || err "cookies file not found: $2" EXTRA_ARGS+=(--cookies "$2"); shift 2 ;; --dry-run) DRY_RUN=1; shift ;; -*) err "Unknown option: $1 (see --help)" ;; *) [ -z "$URL" ] && URL="$1" && shift || err "Unexpected argument: $1" ;; esac done [ -n "$URL" ] || usage [ -n "$FORMAT" ] && { [ "$BEST" -eq 1 ] || [ "$WORST" -eq 1 ]; } \ && err "--format and --best/--worst are mutually exclusive" [ "$BEST" -eq 1 ] && [ "$WORST" -eq 1 ] && err "--best and --worst are mutually exclusive" # Resolve the format selector: explicit flag → smart preset → interactive. # The curated table goes to stderr; stdout carries ONLY the chosen format id # (same ui_pick lesson — a $(...) capture must not swallow the menu). fmt_spec="" if [ -n "$FORMAT" ]; then fmt_spec="$FORMAT" elif [ "$BEST" -eq 1 ]; then fmt_spec="bestvideo*+bestaudio/best" elif [ "$WORST" -eq 1 ]; then fmt_spec="worst" else table="$(yt-dlp -F "$URL" 2>&1)" || { echo "ERROR: no formats listed (unsupported site or age-gated content)" >&2 exit 1 } printf '%s' "$table" | awk -F'[[:space:]][[:space:]]+' ' NR <= 2 { print; next } $0 ~ /^-+$/ { print; next } $0 ~ /audio only/ && $0 !~ /storyboard/ && $1 !~ /^sb/ { print " [audio] " $0; next } $0 ~ /video only/ && $1 !~ /^sb/ { print " [video] " $0; next } $1 ~ /^[0-9]+$/ && $2 ~ /^(mp4|webm)$/ && $3 ~ /x/ { print " [combo] " $0 } ' >&2 echo >&2 read -rp "Format ID ('best'/'worst'/empty = best): " chosen case "${chosen:-best}" in best) fmt_spec="bestvideo*+bestaudio/best" ;; worst) fmt_spec="worst" ;; *) if printf '%s' "$table" | grep -qE "^[[:space:]]*${chosen}[[:space:]]"; then fmt_spec="$chosen" else echo "ERROR: unknown format id '$chosen'" >&2 exit 1 fi ;; esac fi args=(yt-dlp -f "$fmt_spec" --merge-output-format mp4 --embed-metadata --embed-chapters --embed-subs --sub-langs all --embed-thumbnail --no-overwrites -o "$OUT_DIR/%(title)s.%(ext)s" "${EXTRA_ARGS[@]}" "$URL") if [ "$DRY_RUN" -eq 1 ]; then echo "yt-dlp ${args[*]:1}" else spawn "downloading video → $OUT_DIR" "${args[@]}" fi