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: Provideswl-pasteto read the Wayland system clipboard.libnotify: Providesnotify-sendfor non-blocking desktop notifications.yt-dlp: Core engine for parsing metadata and extracting video/playlist streams.ffmpeg: Required byyt-dlpfor 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
- Clipboard Extraction & Validation: The script pulls the current text
buffer from
wl-pasteand applies a regex validation match to ensure the content is a valid YouTube domain (youtube.comoryoutu.be). If invalid, a Zenity error window terminates execution. - Visual Confirmation: A temporary background desktop notification is
triggered via
notify-sendto confirm download initialization. - Stream Optimization:
yt-dlptargets the highest available video quality under1080pup to4Krestricted to an.mp4format, pulling matching.m4aaudio and embedding available English subtitles natively into the track list. - Target Naming & Output:
- Single Videos: Land directly at the root of
~/Downloads/Youtube/with a flat name format (Video_Title.mp4). - Playlists: Auto-generate a parent sub-folder mirroring the playlist
title, formatting sequence indexes safely (
Playlist_Title/01_Video_Title.mp4). - Post-Download Sweep: A native
findexecution systematically searches and purges raw sidecar artifacts (.vtt,.m4a, and split segment files like.f398.mp4) that are occasionally left behind byyt-dlp's external track merging steps. - 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
celluloidplaylist.
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