Compare commits

...

5 Commits

Author SHA1 Message Date
af5dc3b865 yt: increases speed 2025-07-01 13:31:08 -06:00
bde3e58618 updated README 2023-12-12 22:42:25 -07:00
70127c6918 cleanup 2023-12-12 22:23:59 -07:00
7797c47195 fixed some edge cases 2023-12-12 22:17:56 -07:00
c34856d134 refactor and added sync functionality 2023-12-12 18:41:31 -07:00
2 changed files with 167 additions and 127 deletions

56
README
View File

@@ -4,42 +4,66 @@ viewing and interacting with YouTube videos.
Dependencies: Dependencies:
- curl(1) - curl(1)
- jq(1) - jq(1)
- mpv(1)
- yt-dlp(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 $YTPICK
- Set this variable to a dmenu-compatible picker for use with the pick - 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: 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 - Adds a video by URI to a playlist
archive uri... archive uri...
- Archives a video on the Wayback Machine - Archives videos on the Wayback Machine
cache uri... cache video...
- Caches a video title - Caches video titles
clone uri file clone uri [playlist]
- Clones a YouTube playlist to a file - Clones a YouTube playlist to a file
music uri... list
- Downloads a video as audio, splitting it by chapter - 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 - Opens $PICKER to a list of videos in a file
play uri... play uri...
- Plays a video, caching its title first - Plays videos, caching their titles first
queue file... queue playlist...
- Queues a 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 - Verifies that all the videos in a playlist are existent on YouTube, and if
they arent, attempts to replace the URI with one from the Wayback Machine. If they arent, attempts to replace the URI with one from the Wayback Machine. If
the video isnt available on the Wayback Machine, it comments the video link. It the video isnt available on the Wayback Machine, it comments the video link. It

238
yt
View File

@@ -1,6 +1,6 @@
#!/bin/sh #!/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 # SPDX-License-Identifier: AGPL-3.0-or-later
# #
# This program is free software: you can redistribute it and/or modify it under # 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" \ || test -n "$XDG_DATA_HOME" && YT_PL_DIR="$XDG_DATA_HOME/yt" \
|| YT_PL_DIR="$HOME/.local/share/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" argv0="$0"
com="$1" 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]' FMT='%(title)s %(channel)s (%(duration>%H:%M:%S)s) [%(webpage_url)s]'
WBAPI='https://archive.org/wayback/available?url=' WBAPI='https://archive.org/wayback/available?url='
add() { # adds a video to a playlist file 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 while test -f "$YT_PL_DIR/$2.m3u"; do
file="$YT_PL_DIR/$2.m3u" file="$YT_PL_DIR/$2.m3u"
video="$(printf '%s\n' "$1" \ video="$(printf '%s\n' "$1" \
@@ -47,8 +67,8 @@ add() { # adds a video to a playlist file
"$argv0" "$file" "$video" "$argv0" "$file" "$video"
exit exit
else else
cache "$video" cache "$video" &
archive "$video" archive "$video" &
printf "%s\n" "$video" >> "$file" printf "%s\n" "$video" >> "$file"
fi fi
@@ -57,6 +77,8 @@ add() { # adds a video to a playlist file
} }
archive() { # archives a video to the Wayback Machine archive() { # archives a video to the Wayback Machine
test -z "$1" && usage 'archive uri...'
while test -n "$1"; do while test -n "$1"; do
if test -n "$(printf '%s\n' "$1" | grep -e 'web\.archive\.org')" if test -n "$(printf '%s\n' "$1" | grep -e 'web\.archive\.org')"
then then
@@ -78,6 +100,8 @@ archive() { # archives a video to the Wayback Machine
} }
cache() { # cache the video title for faster retrieval cache() { # cache the video title for faster retrieval
test -z "$1" && usage 'cache video...'
test -e "$cachefile" || touch "$cachefile" test -e "$cachefile" || touch "$cachefile"
while test -n "$1"; do 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 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" \ n=1
|| file="$YT_PL_DIR/$(yt-dlp -s -I '1:1' --print '%(playlist)s' "$1").m3u"
yt-dlp --flat-playlist "$1" --print url > "$file" while test -z "$title"; do
verify "$file" 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() { lines() {
@@ -105,19 +144,19 @@ lines() {
} }
list() { list() {
if test -z "$YTPICK"; then test -n "$1" && usage 'list'
printf "%s: Please set \$YTPICK to your preferred picking tool." \
"$argv0" 1>&2
exit 78 # sysexits.h(3) EX_CONFIG
fi
choices="$(menu)" choices="$(menu)"
test -z "$choices" || pick "$choices" test -z "$choices" || pick "$choices"
} }
localsearch() {
test -n "$1" && usage 'localsearch'
$YTPICK <"$cachefile"
}
menu() { 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 while test -d "$YT_PL_DIR/$playlist"; do
dir="$playlist" dir="$playlist"
@@ -128,21 +167,22 @@ menu() {
printf '%s/%s\n' "$dir" "$playlist" printf '%s/%s\n' "$dir" "$playlist"
} }
music() { # downloads a video, splitting by chapter and only saving the audio new() {
while test -n "$1"; do test -z "$1" && usage 'new playlist...'
yt-dlp -vx --split-chapters -o \
"chapter:%(fulltitle)s - %(section_number)s %(section_title)s.%(ext)s" \ file="$YT_PL_DIR/$1.m3u"
"$1" --audio-quality 0 >> log 2>&1
shift if test -f "$file"; then
done 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 pick() { # Pick a video to play from a playlist of videos
if test -z "$YTPICK"; then test -z "$1" && usage 'pick playlist...'
printf "%s: Please set \$YTPICK to your preferred picking tool." \
"$argv0" 1>&2
exit 78 # sysexits.h(3) EX_CONFIG
fi
if test -f "$YT_PL_DIR/$1.m3u"; then if test -f "$YT_PL_DIR/$1.m3u"; then
file="$YT_PL_DIR/$1.m3u" file="$YT_PL_DIR/$1.m3u"
@@ -152,7 +192,7 @@ pick() { # Pick a video to play from a playlist of videos
then then
continue continue
else else
cache "$line" cache "$line" &
fi fi
if test -z "$list"; then 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 play() { # play a video after caching its title
if test -z "$PLAYER"; then test -z "$1" && usage 'play uri...'
printf "%s: Please set \$PLAYER to your preferred video player." \
"$argv0" 1>&2
exit 78 # sysexits.h(3) EX_CONFIG
fi
cache "$@" cache "$@" &
"$PLAYER" "$@" "$PLAYER" "$@"
} }
queue() { queue() {
if test -z "$PLAYER"; then test -z "$1" && usage 'queue playlist...'
printf "%s: Please set \$PLAYER to your preferred video player." \
"$argv0" 1>&2
exit 78 # sysexits.h(3) EX_CONFIG
fi
while test -f "$YT_PL_DIR/$1.m3u"; do while test -f "$YT_PL_DIR/$1.m3u"; do
"$PLAYER" "$YT_PL_DIR/$1.m3u" "$PLAYER" "$YT_PL_DIR/$1.m3u"
@@ -196,13 +228,12 @@ queue() {
} }
search() { search() {
if test -z "$YTPICK"; then test -z "$1" && usage 'search term [count]'
printf "%s: Please set \$YTPICK to your preferred picking tool." \
"$argv0" 1>&2
exit 78 # sysexits.h(3) EX_CONFIG
fi
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" \ selection="$(printf '%s\n' "$results" \
| $YTPICK \ | $YTPICK \
@@ -216,9 +247,6 @@ search() {
"copy") "copy")
wl-copy "$selection" wl-copy "$selection"
;; ;;
"music")
music "$selection"
;;
"play") "play")
play "$selection" play "$selection"
;; ;;
@@ -229,16 +257,48 @@ search() {
fi 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() { usage() {
printf "Usage: %s %s\n" "$argv0" "$@" 1>&2 printf "Usage: %s %s\n" "$argv0" "$@" 1>&2
exit 64 # sysexits.h(3) EX_USAGE exit 64 # sysexits.h(3) EX_USAGE
} }
verify() { # replaces videos with archived versions if they are not available verify() { # replaces videos with archived versions if they are not available
while test -f "$YT_PL_DIR/$1.m3u"; do test -z "$1" && usage 'verify playlist...'
file="$YT_PL_DIR/$1.m3u"
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 \ if test -n "$(yt-dlp -s "$video" 2>&1 \
| grep -i -e 'video unavailable' -e 'private video' -e 'been removed')" | grep -i -e 'video unavailable' -e 'private video' -e 'been removed')"
then 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" \ printf "%s: %s: Video not available on the Wayback Machine.\n" \
"$argv0" "$video" 1>&2 "$argv0" "$video" 1>&2
# replace the video link with a comment containing link # 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 # write the new content buffer to file
printf '%s\n' "$content" > "$file" printf '%s\n' "$content" > "$1"
else else
printf "%s: %s: Replacing link with Wayback link.\n" \ printf "%s: %s: Replacing link with Wayback link.\n" \
"$argv0" "$video" 1>&2 "$argv0" "$video" 1>&2
content="$(sed "s;^$video;$wayback_url;g" "$file")" content="$(sed "s;^$video;$wayback_url;g" "$1")"
printf '%s\n' "$content" >"$file" printf '%s\n' "$content" >"$1"
cache "$wayback_url" cache "$wayback_url" &
fi fi
else else
printf '%s: %s: Video available.\n' "$argv0" "$video" 1>&2 printf '%s: %s: Video available.\n' "$argv0" "$video" 1>&2
cache "$video" cache "$video" &
archive "$video" archive "$video" &
fi fi
done } & done
shift shift
done done
@@ -284,57 +344,13 @@ for dep in \
do do
if ! command -v "$dep" >/dev/null if ! command -v "$dep" >/dev/null
then 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 exit 69 # syexits.h(3) EX_UNAVAILABLE
fi fi
done done
case "$com" in test -n "$com" && shift || usage "$u"
add)
shift 2>/dev/null || usage 'add uri file' printf '%s\n' "$u" | grep "$com" >/dev/null || usage "$u"
add "$@"
;; "$com" "$@"
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