Skip to content

Instantly share code, notes, and snippets.

@tiagocesar
Created February 25, 2026 23:14
Show Gist options
  • Select an option

  • Save tiagocesar/dbd79a033f2f991da1a7ca02a2d91b4e to your computer and use it in GitHub Desktop.

Select an option

Save tiagocesar/dbd79a033f2f991da1a7ca02a2d91b4e to your computer and use it in GitHub Desktop.
Download videos from your Watch Later playlist on YouTube
#!/bin/bash
# 1. FIX: Export the PATH so Cron can find Node.js, Python, and ffmpeg
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
# Ensure we are in the correct directory so it finds the cookies.txt
cd "$(dirname "$0")"
# --- Lockfile mechanism (prevents concurrent runs) ---
LOCKDIR="$(dirname "$0")/.yt_download.lock"
STALE_THRESHOLD=7200 # 2 hours in seconds – auto-remove stale locks
# Remove stale lock (self-heal if a previous run crashed)
if [ -d "$LOCKDIR" ]; then
lock_age=$(( $(date +%s) - $(stat -f %m "$LOCKDIR") ))
if [ "$lock_age" -gt "$STALE_THRESHOLD" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') - Removing stale lock (age: ${lock_age}s)" >> yt.log
rm -rf "$LOCKDIR"
fi
fi
# Try to acquire lock (mkdir is atomic on all filesystems)
if ! mkdir "$LOCKDIR" 2>/dev/null; then
echo "$(date '+%Y-%m-%d %H:%M:%S') - Another instance is already running, exiting." >> yt.log
exit 0
fi
# Ensure lock is released on exit (normal, error, or signal)
trap 'rm -rf "$LOCKDIR"' EXIT INT TERM HUP
# --- End lockfile mechanism ---
# Path to yt-dlp (Use absolute path just to be safe for Cron)
YTDLP="/opt/homebrew/bin/yt-dlp"
# Check if yt-dlp exists there, otherwise try standard path
if [ ! -f "$YTDLP" ]; then
YTDLP="/usr/local/bin/yt-dlp"
fi
LOGFILE="yt.log"
TIMESTAMP="$(date '+%Y-%m-%d %H:%M:%S')"
echo "" >> "$LOGFILE"
echo "=== Run started: $TIMESTAMP ===" >> "$LOGFILE"
$YTDLP --cookies-from-browser firefox \
--download-archive archive.txt \
--merge-output-format mp4 \
-f "bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[ext=mp4]/best" \
--embed-thumbnail \
--embed-metadata \
--sub-langs "en.*" \
--convert-subs srt \
--embed-subs \
--newline \
--progress \
--print before_dl:"%(id)s: %(title)s" \
-o "../$(date +%Y%m%d) - %(title)s.%(ext)s" \
"https://www.youtube.com/playlist?list=WL" 2>&1 | awk 'BEGIN { last_bucket = -1 } {
if ($0 ~ /\[download\]/ && $0 ~ /[0-9]%/) {
for (i=1; i<=NF; i++) {
if ($i ~ /[0-9]%$/ || $i ~ /[0-9]%[^a-zA-Z]/) {
val = $i
gsub(/%/, "", val)
pct = val + 0
if (pct < last_pct) last_bucket = -1
last_pct = pct
bucket = int(pct / 10)
if (bucket > last_bucket) {
last_bucket = bucket
print; fflush()
}
break
}
}
} else {
print; fflush()
}
}' >> "$LOGFILE"
EXIT_CODE=${PIPESTATUS[0]}
if [ $EXIT_CODE -ne 0 ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: yt-dlp exited with code $EXIT_CODE" >> "$LOGFILE"
fi
echo "=== Run finished: $(date '+%Y-%m-%d %H:%M:%S') (exit code: $EXIT_CODE) ===" >> "$LOGFILE"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment