Skip to content

Instantly share code, notes, and snippets.

@tiagocesar
Last active February 26, 2026 15:02
Show Gist options
  • Select an option

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

Select an option

Save tiagocesar/b5d98d086b534ddddc9f4a2a8dba8285 to your computer and use it in GitHub Desktop.
Download videos from your Watch Later playlist on YouTube
#!/bin/bash
# ==============================================================================
# Setup Instructions:
#
# 1. Dependencies:
# Install the required packages using Homebrew:
# brew install yt-dlp ffmpeg
#
# 2. Folder Structure:
# This script expects to be run from inside a 'scripts' directory, which
# should be a subdirectory of your main video download folder.
#
# Example structure:
# /Users/YourUser/Media/Videos/
# ├── scripts/
# │ ├── yt_download.sh
# │ ├── archive.txt (will be created here by yt-dlp)
# │ └── yt.log (will be created here by the script)
# └── <downloaded_video.mp4> (videos will be saved in the parent directory)
# 3. Browser:
# We use Firefox because yt-dlp needs access to your browser cookies so it can
# see the videos on your private playlist. If you use any Chrome based browser,
# cron won't be able to get your cookies and the script will fail. Using Firefox
# is the easiest way to circumvent this (be sure to open Firefox and log in to your
# YouTube account).
# ==============================================================================
# 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"
# Always run from the script's own directory so relative paths
# (yt.log, archive.txt, output path) resolve correctly under Cron
cd "$(dirname "$0")" || exit 1
# --- 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"
@tiagocesar
Copy link
Author

This is the cron command:

*/10 9-20 * * * /Users/YourUser/Path/To/scripts/yt_download.sh

Adjust the absolute path to where your script is. If unsure, run pwd in the same folder and append the script name.

To setup the cron job, run crontab -e, edit the file with the line above, save, and exit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment