Created
February 25, 2026 23:14
-
-
Save tiagocesar/dbd79a033f2f991da1a7ca02a2d91b4e to your computer and use it in GitHub Desktop.
Download videos from your Watch Later playlist on YouTube
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
| #!/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