Compare commits
5 Commits
f67691e8d7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
af5dc3b865
|
|||
|
bde3e58618
|
|||
|
70127c6918
|
|||
|
7797c47195
|
|||
|
c34856d134
|
56
README
56
README
@@ -4,42 +4,66 @@ viewing and interacting with YouTube videos.
|
||||
Dependencies:
|
||||
- curl(1)
|
||||
- jq(1)
|
||||
- mpv(1)
|
||||
- yt-dlp(1)
|
||||
|
||||
Variables:
|
||||
Environment Variables:
|
||||
$DEBUG
|
||||
- Set this variable to get debug output to stderr
|
||||
|
||||
$PLAYER
|
||||
- Set this variable to a video player that can utilize yt-dlp to playback
|
||||
videos from YouTube
|
||||
|
||||
$YTPICK
|
||||
- Set this variable to a dmenu-compatible picker for use with the pick
|
||||
subcommand.
|
||||
subcommand
|
||||
|
||||
$YT_PL_DIR
|
||||
- Set this variable to the directory where playlists should be stored; defaults
|
||||
to $XDG_DATA_HOME/yt
|
||||
|
||||
|
||||
Usage:
|
||||
yt add | archive | cache | clone | music | pick | play | queue | verify
|
||||
yt add | archive | cache | clone | list | localsearch | new | pick | play | queue | search | sync | verify
|
||||
|
||||
add uri file
|
||||
add uri playlist
|
||||
- Adds a video by URI to a playlist
|
||||
|
||||
archive uri...
|
||||
- Archives a video on the Wayback Machine
|
||||
- Archives videos on the Wayback Machine
|
||||
|
||||
cache uri...
|
||||
- Caches a video title
|
||||
cache video...
|
||||
- Caches video titles
|
||||
|
||||
clone uri file
|
||||
clone uri [playlist]
|
||||
- Clones a YouTube playlist to a file
|
||||
|
||||
music uri...
|
||||
- Downloads a video as audio, splitting it by chapter
|
||||
list
|
||||
- lists local playlists
|
||||
|
||||
pick file...
|
||||
localsearch
|
||||
- uses $YTPICK to search for videos from the local cache
|
||||
|
||||
new playlist...
|
||||
- creates new playlists
|
||||
|
||||
pick playlist...
|
||||
- Opens $PICKER to a list of videos in a file
|
||||
|
||||
play uri...
|
||||
- Plays a video, caching its title first
|
||||
- Plays videos, caching their titles first
|
||||
|
||||
queue file...
|
||||
- Queues a playlist
|
||||
queue playlist...
|
||||
- Queues playlists
|
||||
|
||||
verify file...
|
||||
search term [count]
|
||||
- Searches YouTube for term and returns count results
|
||||
|
||||
sync [playlist]
|
||||
- If a playlist is specified, sync that playlist with its remote YouTube
|
||||
counterpart; otherwise, sync all playlists with remotes
|
||||
|
||||
verify playlist...
|
||||
- Verifies that all the videos in a playlist are existent on YouTube, and if
|
||||
they aren’t, attempts to replace the URI with one from the Wayback Machine. If
|
||||
the video isn’t available on the Wayback Machine, it comments the video link. It
|
||||
|
||||
238
yt
238
yt
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
|
||||
# Copyright (c) 2023, 2025 Emma Tebibyte <emma@tebibyte.media>
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under
|
||||
@@ -27,14 +27,34 @@ test -n "$YT_PL_DIR" \
|
||||
|| test -n "$XDG_DATA_HOME" && YT_PL_DIR="$XDG_DATA_HOME/yt" \
|
||||
|| YT_PL_DIR="$HOME/.local/share/yt"
|
||||
|
||||
test -d "$YT_PL_DIR" \
|
||||
|| mkdir -p "$YT_PL_DIR"
|
||||
|
||||
if test -z "$YTPICK"; then
|
||||
printf "%s: Please set \$YTPICK to your preferred picking tool." \
|
||||
"$argv0" 1>&2
|
||||
exit 78 # sysexits.h(3) EX_CONFIG
|
||||
fi
|
||||
|
||||
if test -z "$PLAYER"; then
|
||||
printf "%s: Please set \$PLAYER to your preferred video player." \
|
||||
"$argv0" 1>&2
|
||||
exit 78 # sysexits.h(3) EX_CONFIG
|
||||
fi
|
||||
|
||||
# formatted $YT_PL_DIR for use with sed
|
||||
P="$(printf '%s\n' "$YT_PL_DIR" | sed 's;\/;\\/;g')"
|
||||
|
||||
argv0="$0"
|
||||
com="$1"
|
||||
|
||||
u='add | archive | clone | list | localsearch | new | pick | play | search | sync | verify'
|
||||
FMT='%(title)s – %(channel)s (%(duration>%H:%M:%S)s) [%(webpage_url)s]'
|
||||
WBAPI='https://archive.org/wayback/available?url='
|
||||
|
||||
|
||||
add() { # adds a video to a playlist file
|
||||
test -z "$2" || test -n "$3" && usage 'add uri playlist'
|
||||
|
||||
while test -f "$YT_PL_DIR/$2.m3u"; do
|
||||
file="$YT_PL_DIR/$2.m3u"
|
||||
video="$(printf '%s\n' "$1" \
|
||||
@@ -47,8 +67,8 @@ add() { # adds a video to a playlist file
|
||||
"$argv0" "$file" "$video"
|
||||
exit
|
||||
else
|
||||
cache "$video"
|
||||
archive "$video"
|
||||
cache "$video" &
|
||||
archive "$video" &
|
||||
printf "%s\n" "$video" >> "$file"
|
||||
fi
|
||||
|
||||
@@ -57,6 +77,8 @@ add() { # adds a video to a playlist file
|
||||
}
|
||||
|
||||
archive() { # archives a video to the Wayback Machine
|
||||
test -z "$1" && usage 'archive uri...'
|
||||
|
||||
while test -n "$1"; do
|
||||
if test -n "$(printf '%s\n' "$1" | grep -e 'web\.archive\.org')"
|
||||
then
|
||||
@@ -78,6 +100,8 @@ archive() { # archives a video to the Wayback Machine
|
||||
}
|
||||
|
||||
cache() { # cache the video title for faster retrieval
|
||||
test -z "$1" && usage 'cache video...'
|
||||
|
||||
test -e "$cachefile" || touch "$cachefile"
|
||||
|
||||
while test -n "$1"; do
|
||||
@@ -88,13 +112,28 @@ cache() { # cache the video title for faster retrieval
|
||||
}
|
||||
|
||||
clone() { # clones a YouTube playlist to a file
|
||||
test -d "$YT_PL_DIR" || mkdir -p "$YT_PL_DIR"
|
||||
test -z "$1" && usage 'clone uri [playlist]'
|
||||
|
||||
test -n "$2" && file="$2" \
|
||||
|| file="$YT_PL_DIR/$(yt-dlp -s -I '1:1' --print '%(playlist)s' "$1").m3u"
|
||||
n=1
|
||||
|
||||
yt-dlp --flat-playlist "$1" --print url > "$file"
|
||||
verify "$file"
|
||||
while test -z "$title"; do
|
||||
title="$(yt-dlp -s -I "$n" --print '%(playlist)s' "$1" 2>/dev/null || true)"
|
||||
n="$((n + 1))"
|
||||
done
|
||||
|
||||
creator="$(yt-dlp -s -I "$n" --print '%(playlist_uploader)s' "$1")"
|
||||
filename="$(printf '%s\n' "$title" | sed 's/ /_/g')"
|
||||
|
||||
file="$YT_PL_DIR/$filename.m3u"
|
||||
|
||||
videos="$(yt-dlp --flat-playlist "$1" --print url)"
|
||||
prefix="$(printf "#EXTM3U\n#EXTART:%s\n#PLAYLIST:%s [%s]\n" \
|
||||
"$creator" "$title" "$1")"
|
||||
|
||||
printf "%s: %s: Saving playlist to %s.\n" "$argv0" "$title" "$file" 1>&2
|
||||
printf '%s\n\n%s\n' "$prefix" "$videos" >"$file"
|
||||
|
||||
verify "$filename"
|
||||
}
|
||||
|
||||
lines() {
|
||||
@@ -105,19 +144,19 @@ lines() {
|
||||
}
|
||||
|
||||
list() {
|
||||
if test -z "$YTPICK"; then
|
||||
printf "%s: Please set \$YTPICK to your preferred picking tool." \
|
||||
"$argv0" 1>&2
|
||||
exit 78 # sysexits.h(3) EX_CONFIG
|
||||
fi
|
||||
|
||||
test -n "$1" && usage 'list'
|
||||
choices="$(menu)"
|
||||
|
||||
test -z "$choices" || pick "$choices"
|
||||
}
|
||||
|
||||
localsearch() {
|
||||
test -n "$1" && usage 'localsearch'
|
||||
$YTPICK <"$cachefile"
|
||||
}
|
||||
|
||||
menu() {
|
||||
playlist="$(ls "$YT_PL_DIR" | sed 's/\.m3u//g' | $YTPICK)"
|
||||
playlist="$(ls "$YT_PL_DIR" | sed 's/\.m3u//g' | sed -n '/[^.old]/p' \
|
||||
| $YTPICK)"
|
||||
|
||||
while test -d "$YT_PL_DIR/$playlist"; do
|
||||
dir="$playlist"
|
||||
@@ -128,21 +167,22 @@ menu() {
|
||||
printf '%s/%s\n' "$dir" "$playlist"
|
||||
}
|
||||
|
||||
music() { # downloads a video, splitting by chapter and only saving the audio
|
||||
while test -n "$1"; do
|
||||
yt-dlp -vx --split-chapters -o \
|
||||
"chapter:%(fulltitle)s - %(section_number)s %(section_title)s.%(ext)s" \
|
||||
"$1" --audio-quality 0 >> log 2>&1
|
||||
shift
|
||||
done
|
||||
new() {
|
||||
test -z "$1" && usage 'new playlist...'
|
||||
|
||||
file="$YT_PL_DIR/$1.m3u"
|
||||
|
||||
if test -f "$file"; then
|
||||
printf "%s: %s: File exists." "$argv0" "$file"
|
||||
elif test -d "$file"; then
|
||||
printf "%s: %s: Is a directory." "$argv0" "$file"
|
||||
else
|
||||
touch "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
pick() { # Pick a video to play from a playlist of videos
|
||||
if test -z "$YTPICK"; then
|
||||
printf "%s: Please set \$YTPICK to your preferred picking tool." \
|
||||
"$argv0" 1>&2
|
||||
exit 78 # sysexits.h(3) EX_CONFIG
|
||||
fi
|
||||
test -z "$1" && usage 'pick playlist...'
|
||||
|
||||
if test -f "$YT_PL_DIR/$1.m3u"; then
|
||||
file="$YT_PL_DIR/$1.m3u"
|
||||
@@ -152,7 +192,7 @@ pick() { # Pick a video to play from a playlist of videos
|
||||
then
|
||||
continue
|
||||
else
|
||||
cache "$line"
|
||||
cache "$line" &
|
||||
fi
|
||||
|
||||
if test -z "$list"; then
|
||||
@@ -172,22 +212,14 @@ pick() { # Pick a video to play from a playlist of videos
|
||||
}
|
||||
|
||||
play() { # play a video after caching its title
|
||||
if test -z "$PLAYER"; then
|
||||
printf "%s: Please set \$PLAYER to your preferred video player." \
|
||||
"$argv0" 1>&2
|
||||
exit 78 # sysexits.h(3) EX_CONFIG
|
||||
fi
|
||||
test -z "$1" && usage 'play uri...'
|
||||
|
||||
cache "$@"
|
||||
cache "$@" &
|
||||
"$PLAYER" "$@"
|
||||
}
|
||||
|
||||
queue() {
|
||||
if test -z "$PLAYER"; then
|
||||
printf "%s: Please set \$PLAYER to your preferred video player." \
|
||||
"$argv0" 1>&2
|
||||
exit 78 # sysexits.h(3) EX_CONFIG
|
||||
fi
|
||||
test -z "$1" && usage 'queue playlist...'
|
||||
|
||||
while test -f "$YT_PL_DIR/$1.m3u"; do
|
||||
"$PLAYER" "$YT_PL_DIR/$1.m3u"
|
||||
@@ -196,13 +228,12 @@ queue() {
|
||||
}
|
||||
|
||||
search() {
|
||||
if test -z "$YTPICK"; then
|
||||
printf "%s: Please set \$YTPICK to your preferred picking tool." \
|
||||
"$argv0" 1>&2
|
||||
exit 78 # sysexits.h(3) EX_CONFIG
|
||||
fi
|
||||
test -z "$1" && usage 'search term [count]'
|
||||
|
||||
results="$(yt-dlp "ytsearch$2:$1" --print "$FMT")"
|
||||
results="$(yt-dlp -isq --flat-playlist --print "$FMT" "ytsearch$2:$1")"
|
||||
|
||||
cache "$(printf '%s\n' "$results" | sed -e 's/.*\[//g' -e 's/\]/ /g' \
|
||||
| tr -d '\n')" &
|
||||
|
||||
selection="$(printf '%s\n' "$results" \
|
||||
| $YTPICK \
|
||||
@@ -216,9 +247,6 @@ search() {
|
||||
"copy")
|
||||
wl-copy "$selection"
|
||||
;;
|
||||
"music")
|
||||
music "$selection"
|
||||
;;
|
||||
"play")
|
||||
play "$selection"
|
||||
;;
|
||||
@@ -229,16 +257,48 @@ search() {
|
||||
fi
|
||||
}
|
||||
|
||||
sync() {
|
||||
if test -z "$1"
|
||||
then
|
||||
set -- "$YT_PL_DIR"/*.m3u
|
||||
else
|
||||
set -- $(printf "$@" | sed "s/.* /$P\/&\.m3u /g")
|
||||
fi
|
||||
|
||||
while test -n "$1"; do
|
||||
file="$1"
|
||||
|
||||
if test -f "$file"; then
|
||||
URL="$(sed -n 's/^\#PLAYLIST:.* \[//p' <"$file" | tr -d ']')"
|
||||
|
||||
if test -n "$URL"; then
|
||||
clone "$URL" && mv "$file" "$file.old"
|
||||
fi
|
||||
fi
|
||||
|
||||
shift
|
||||
done
|
||||
}
|
||||
|
||||
usage() {
|
||||
printf "Usage: %s %s\n" "$argv0" "$@" 1>&2
|
||||
exit 64 # sysexits.h(3) EX_USAGE
|
||||
}
|
||||
|
||||
verify() { # replaces videos with archived versions if they are not available
|
||||
while test -f "$YT_PL_DIR/$1.m3u"; do
|
||||
file="$YT_PL_DIR/$1.m3u"
|
||||
test -z "$1" && usage 'verify playlist...'
|
||||
|
||||
for video in $(lines "$file"); do
|
||||
set -- $(printf '%s\n' "$@" | sed "s/[^ ]*/$P\/&.m3u /g")
|
||||
|
||||
while test -n "$1"; do
|
||||
if ! test -f "$1"; then
|
||||
printf '%s: %s: No such playlist.\n' \ "$argv0" "$1" 1>&2
|
||||
exit 69 # syexits.h(3) EX_UNAVAILABLE
|
||||
fi
|
||||
|
||||
printf "%s: %s: Verifying playlist.\n" "$argv0" "$1" 1>&2
|
||||
|
||||
for video in $(lines "$1"); do {
|
||||
if test -n "$(yt-dlp -s "$video" 2>&1 \
|
||||
| grep -i -e 'video unavailable' -e 'private video' -e 'been removed')"
|
||||
then
|
||||
@@ -254,24 +314,24 @@ verify() { # replaces videos with archived versions if they are not available
|
||||
printf "%s: %s: Video not available on the Wayback Machine.\n" \
|
||||
"$argv0" "$video" 1>&2
|
||||
# replace the video link with a comment containing link
|
||||
content="$(sed "s;^$video;\# $video;g" "$file")"
|
||||
content="$(sed "s;^$video;\# $video;g" "$1")"
|
||||
|
||||
# write the new content buffer to file
|
||||
printf '%s\n' "$content" > "$file"
|
||||
printf '%s\n' "$content" > "$1"
|
||||
else
|
||||
printf "%s: %s: Replacing link with Wayback link.\n" \
|
||||
"$argv0" "$video" 1>&2
|
||||
|
||||
content="$(sed "s;^$video;$wayback_url;g" "$file")"
|
||||
printf '%s\n' "$content" >"$file"
|
||||
cache "$wayback_url"
|
||||
content="$(sed "s;^$video;$wayback_url;g" "$1")"
|
||||
printf '%s\n' "$content" >"$1"
|
||||
cache "$wayback_url" &
|
||||
fi
|
||||
else
|
||||
printf '%s: %s: Video available.\n' "$argv0" "$video" 1>&2
|
||||
cache "$video"
|
||||
archive "$video"
|
||||
cache "$video" &
|
||||
archive "$video" &
|
||||
fi
|
||||
done
|
||||
} & done
|
||||
|
||||
shift
|
||||
done
|
||||
@@ -284,57 +344,13 @@ for dep in \
|
||||
do
|
||||
if ! command -v "$dep" >/dev/null
|
||||
then
|
||||
printf "%s: %s: Missing dependency." "$argv0" "$dep" 1>&2
|
||||
printf "%s: %s: Missing dependency.\n" "$0" "$dep" 1>&2
|
||||
exit 69 # syexits.h(3) EX_UNAVAILABLE
|
||||
fi
|
||||
done
|
||||
|
||||
case "$com" in
|
||||
add)
|
||||
shift 2>/dev/null || usage 'add uri file'
|
||||
add "$@"
|
||||
;;
|
||||
archive)
|
||||
shift 2>/dev/null || usage 'archive uri...'
|
||||
archive "$@"
|
||||
;;
|
||||
cache)
|
||||
shift 2>/dev/null || usage 'cache uri...'
|
||||
cache "$@"
|
||||
;;
|
||||
clone)
|
||||
shift 2>/dev/null || usage 'clone uri [file]'
|
||||
clone "$@"
|
||||
;;
|
||||
list)
|
||||
shift 2>/dev/null || usage 'list'
|
||||
list "$@"
|
||||
;;
|
||||
music)
|
||||
shift 2>/dev/null || usage 'music uri...'
|
||||
music "$@"
|
||||
;;
|
||||
pick)
|
||||
shift 2>/dev/null || usage 'pick file...'
|
||||
pick "$@"
|
||||
;;
|
||||
play)
|
||||
shift 2>/dev/null || usage 'play uri...'
|
||||
play "$@"
|
||||
;;
|
||||
queue)
|
||||
shift 2>/dev/null || usage 'queue file...'
|
||||
queue "$@"
|
||||
;;
|
||||
search)
|
||||
shift 2>/dev/null || usage 'search term [count]'
|
||||
search "$@"
|
||||
;;
|
||||
verify)
|
||||
shift 2>/dev/null || usage 'file...'
|
||||
verify "$@"
|
||||
;;
|
||||
*)
|
||||
usage 'add | clone | list | music | pick | play | verify'
|
||||
;;
|
||||
esac
|
||||
test -n "$com" && shift || usage "$u"
|
||||
|
||||
printf '%s\n' "$u" | grep "$com" >/dev/null || usage "$u"
|
||||
|
||||
"$com" "$@"
|
||||
|
||||
Reference in New Issue
Block a user