feat: harden pos media mp3/mp4 — smart format selection, metadata, dry-run
Both tools: yt-dlp calls via spawn (honor DRY_RUN; --dry-run prints the command and skips dep checks), -o/--output, --no-playlist, --cookies (with file check), clean ffmpeg/yt-dlp guards, POS_FLAGS for completion, full embedded metadata (--embed-metadata --embed-chapters --embed-thumbnail --no-overwrites; mp3 adds --convert-thumbnails jpg + artist-from-uploader parse-metadata). mp3 gains --by-artist (~/Music/<artist>/<title>.mp3). mp4: -f <id>/--best/--worst skip the prompt (mutual-exclusion validated), and the interactive picker now shows a curated [audio]/[video]/[combo] format table on stderr (stdout carries only the chosen id — ui_pick lesson), validates the id against the real -F output, and defaults empty input to best. Docs: howto/media.md rewritten.
This commit is contained in:
+106
-18
@@ -1,38 +1,126 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: media mp4 — Download video as MP4 (interactive format select)
|
||||
# POS: media 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"
|
||||
|
||||
OUT_DIR="$HOME/Videos"
|
||||
FORMAT=""
|
||||
BEST=0
|
||||
WORST=0
|
||||
EXTRA_ARGS=()
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: pos media mp4 <url>
|
||||
Usage: pos media mp4 [options] <url>
|
||||
|
||||
Download video from a URL via yt-dlp with interactive format selection.
|
||||
Download video from a URL via yt-dlp. Without -f/--best/--worst, formats are
|
||||
listed and picked interactively.
|
||||
|
||||
Options:
|
||||
-f, --format <id> Download that format id directly (no prompt)
|
||||
--best Best video + audio (no prompt)
|
||||
--worst Lowest quality (no prompt)
|
||||
-o, --output <dir> Output directory (default: $OUT_DIR)
|
||||
--no-playlist Download only the single video
|
||||
--cookies <file> Netscape cookies.txt for age-gated content
|
||||
--dry-run Print the yt-dlp command without running it
|
||||
-h, --help This help
|
||||
|
||||
Examples:
|
||||
pos media mp4 https://youtube.com/watch?v=dQw4w9WgXcQ
|
||||
pos media mp4 --best https://youtube.com/watch?v=dQw4w9WgXcQ
|
||||
pos media mp4 -f 22 https://youtube.com/watch?v=dQw4w9WgXcQ
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
-h|--help|"") usage ;;
|
||||
esac
|
||||
DRY_RUN=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
|
||||
|
||||
command -v yt-dlp &>/dev/null || err "yt-dlp not found — install it with: sudo apt install yt-dlp"
|
||||
[ -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"
|
||||
|
||||
url="$1"
|
||||
# Dependency checks (skipped under --dry-run: the command is only previewed).
|
||||
if [ "$DRY_RUN" -eq 0 ]; then
|
||||
command -v yt-dlp &>/dev/null || err "yt-dlp not found — install it with: sudo apt install yt-dlp"
|
||||
command -v ffmpeg &>/dev/null || err "ffmpeg not found (needed for MP4 merge) — install it with: sudo apt install ffmpeg"
|
||||
fi
|
||||
|
||||
yt-dlp -F "$url"
|
||||
# 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
|
||||
|
||||
echo
|
||||
read -rp "Enter format ID: " format
|
||||
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")
|
||||
|
||||
yt-dlp \
|
||||
-f "$format" \
|
||||
--merge-output-format mp4 \
|
||||
--embed-thumbnail \
|
||||
--add-metadata \
|
||||
-o "$HOME/Videos/%(title)s.%(ext)s" \
|
||||
"$url"
|
||||
if [ "$DRY_RUN" -eq 1 ]; then
|
||||
echo "yt-dlp ${args[*]:1}"
|
||||
else
|
||||
spawn "downloading video → $OUT_DIR" "${args[@]}"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user