YouTube Downloader & Player Script

A headless, Wayland-native bash script designed to streamline downloading YouTube videos or playlists from the system clipboard, embed English subtitles, clean up temporary media artifacts, and optionally pass the final files to Celluloid for instant playback.

Dependencies

The script relies on the following command-line utilities and applications being present in the system $PATH:

  • wl-clipboard: Provides wl-paste to read the Wayland system clipboard.
  • libnotify: Provides notify-send for non-blocking desktop notifications.
  • yt-dlp: Core engine for parsing metadata and extracting video/playlist streams.
  • ffmpeg: Required by yt-dlp for post-processing and embedding subtitle tracks into containers.
  • zenity: Provides graphical error and confirmation dialog windows.
  • celluloid: The GTK/Wayland media player used for video playback.

Workflow & Mechanics

  1. Clipboard Extraction & Validation: The script pulls the current text buffer from wl-paste and applies a regex validation match to ensure the content is a valid YouTube domain (youtube.com or youtu.be). If invalid, a Zenity error window terminates execution.
  2. Visual Confirmation: A temporary background desktop notification is triggered via notify-send to confirm download initialization.
  3. Stream Optimization: yt-dlp targets the highest available video quality under 1080p up to 4K restricted to an .mp4 format, pulling matching .m4a audio and embedding available English subtitles natively into the track list.
  4. Target Naming & Output:
  5. Single Videos: Land directly at the root of ~/Downloads/Youtube/ with a flat name format (Video_Title.mp4).
  6. Playlists: Auto-generate a parent sub-folder mirroring the playlist title, formatting sequence indexes safely (Playlist_Title/01_Video_Title.mp4 ).
  7. Post-Download Sweep: A native find execution systematically searches and purges raw sidecar artifacts (.vtt, .m4a, and split segment files like .f398.mp4) that are occasionally left behind by yt-dlp's external track merging steps.
  8. Interactive Playback: Parses the resolved file output names, extracts the primary string, and queries the desktop environment with a Zenity question box. Affirmation loads the target vector paths seamlessly into a sequential celluloid playlist.

Script Source

#!/usr/bin/env bash
set -e
BASE_DOWNLOAD_DIR="$HOME/Downloads/Youtube"
mkdir -p "$BASE_DOWNLOAD_DIR"
# 1. Grab URL from Wayland clipboard
URL=$(wl-paste)
# 2. Validate YouTube URL
if [[! "$URL" =~ youtube\.com]] && [[! "$URL" =~ youtu\.be]]; then
    zenity --error --title="YouTube Downloader" --text="Clipboard does not contain a valid YouTube URL.\n\nCaptured text:\n$URL" --width=350
    exit 1
fi
# Send instant desktop notification that processing has started
notify-send "YouTube Downloader" "Download started...\nProcessing video stream." -i video-x-generic -t 3000
# 3. Download via yt-dlp
LOG_FILE=$(mktemp)
set +e
yt-dlp \
    -P "$BASE_DOWNLOAD_DIR" \
    -o "%(playlist_title&{}/|)s%(playlist_index&{:02d}_|)s%(title)s.%(ext)s" \
    --restrict-filenames \
    --no-post-overwrites \
    --ignore-errors \
    --clean-info-json \
    --write-subs \
    --write-auto-subs \
    --sub-langs "en.*" \
    --embed-subs \
    -f "bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4]" \
    --exec "echo {} >> $LOG_FILE" \
    "$URL"
set -e
# 4. Decisively sweep and delete any stray .vtt, .m4a, or split temp files
find "$BASE_DOWNLOAD_DIR" -type f \( \
    -name "*.vtt" \
    -o -name "*.m4a" \
    -o -name "*.f[0-9]*.mp4" \
\) -delete
# 5. Parse completed files
mapfile -t FILES < "$LOG_FILE"
rm "$LOG_FILE"
# 6. Fallback: If files weren't logged as new downloads, resolve their paths natively
if [ ${#FILES[@]} -eq 0 ]; then
    mapfile -t FILES < <(yt-dlp --restrict-filenames \
        -P "$BASE_DOWNLOAD_DIR" \
        -o "%(playlist_title&{}/|)s%(playlist_index&{:02d}_|)s%(title)s.%(ext)s" \
        --print filepath "$URL" 2>/dev/null)
fi
# 7. Prompt to watch and launch Celluloid if confirmed
if [ ${#FILES[@]} -gt 0 ] && [ -e "${FILES[0]}" ]; then

    # Extract clean name string for display
    VIDEO_NAME=$(basename "${FILES[0]}" | sed 's/\.[^.]*$//' | tr '_' ' ')

    if zenity --question \
              --title="Download Complete" \
              --text="Finished downloading:\n<b>${VIDEO_NAME}</b>\n\nWould you like to watch it now?" \
              --ok-label="Watch" \
              --cancel-label="Skip" \
              --width=320; then

        celluloid "${FILES[@]}"
    fi
else
    zenity --error --title="YouTube Downloader" --text="Could not find or resolve the local video files." --width=350
fi
exit 0

Posted

16:44 30-05-2026