2023-10-20 How I watch Twitch VODs
A long time ago when I regularly watched *LIVE* Twitch streams I
wrote a fully-fledged Emacs package[1] for viewing Twitch streams
via Emacs, yt-dlp[2] and mpv[3]. The thing with Twitch is that
nowadays I see so much forced, unskippable ads that it gets to a
point where I decided to don't put up with this crap any longer. And
while I enjoy watching game play and listening to the commentary of
a charismatic streamer I absolutely abhor donation/sub alerts,
text-to-speech messages and stupid sound effects. As I have no need
to interact with the streamer or the chat in any way I switched over
to watching Twitch VODs (= video on demand) exclusively.
Anyways, my package could only be used for live streams, not VODs,
so I needed a solution to continue to use Emacs and to never have to
manually visit this vile abomination of a website which is called
tw***h.tv. Handling JSON data in Elisp was quite a traumatic
experience for me and now that I developed a strange liking to the
AWK programming language I decided to write a quick shell/awk script
to query Twitch for the most recent VODs of my favorite streamer:
macaw45[4].
Macaw45 is a really enthusiastic retro game streamer and video game
collector from Australia who only streams when I'm in bed sleeping
anyways. So watching the VOD is my best option here. As much as I
like the guy his community is pure cancer with TTS circle jerking
and spamming constantly. He also reeeaaaally takes his time to get
the gameplay going, sometimes watching movie trailers and rambling
on for 1,5 hours (which sometimes can also be quite entertaining).
My point is that if I'm watching the VOD I can simply skip any part
of the stream that I don't like.
So what does the script do? It queries the Twitch API via yt-dlp,
parses the json output with jq[5] and then transforms the result to
an org-mode[6] file. This file contains special org-links[7] for the
various stream qualities for the ten most recent VODs. A typical
link looks like this:
[[elisp:(async-shell-command (concat "mpv " "https://.../480p30/index-dvr.m3u8"))][480p]]
In org-mode links can actually run elisp code and
'async-shell-command' simply calls mpv with the appropriate url.
Screenshot
~~~~~~~~~~
* Streams
** DEAD OF THE BRAIN - Violence suspense thriller
360p 480p 720p60
** Swedish Pinball Battle
360p 480p 720p30 720p60
** The final games released by Zain Soft - An extensive look
360p 480p 720p60
** Surf Ninjas MARATHON - All games based on Surf Ninjas
360p 480p 720p60
** CHARLIE SHEEN GAMING
360p 480p 720p60
** Elaborate German Economy Management Games
360p 480p 720p30 720p60
Code
~~~~
#!/bin/sh
# content of ~/.local/bin/twitch_vod
if [ $# -lt 1 ]; then
echo "Please provide a streamer name"
exit 1
fi
yt-dlp -j "
https://www.twitch.tv/$1/videos?filter=archives&sort=time" --playlist-end 10 \
| jq -r '.title, .formats[].url' > /tmp/twitch_vod_tmp
awk '
BEGIN { printf "* Streams" }
/^[^http]/ { printf "\n** %s\n", $0 }
match($0, /360p|480p|720p30|720p60/) {
printf "[[elisp:(async-shell-command (concat \"mpv \" \"%s\"))][%s]] ", $0, substr($0, RSTART, RLENGTH);
}' /tmp/twitch_vod_tmp > ~/$1.org
UPDATE 2023-11-27:
After writing this phlog post I decided to expand the script to work
for multiple streamers. The results are collected in a single
org-file.
Code
~~~~
#!/bin/sh
if [ $# -lt 1 ]; then
echo "Please provide at least one streamer name."
exit 1
fi
output_file="$HOME/Downloads/twitch_vods.org"
echo "#+STARTUP: show2levels" > $output_file
while [ $# -gt 0 ]
do
yt-dlp -j "
https://www.twitch.tv/$1/videos?filter=archives&sort=time" --playlist-end 10 \
| jq -r '([.timestamp, .title] | join(" ")), .formats[].url' > /tmp/twitch_vod_tmp
awk -v streamer=$1 '
BEGIN { printf "\n* %s", streamer }
/^[0-9]/ {
timestamp=strftime("%Y-%m-%d %H:%M",$1)
$1="";printf "\n** %s | %s\n", timestamp, $0; # print all but first field
}
match($0, /360p|480p|720p30|720p60/) {
printf "[[elisp:(async-shell-command (concat \"mpv \" \"%s\"))][%s]] ", $0, substr($0, RSTART, RLENGTH);
}' /tmp/twitch_vod_tmp >> $output_file
shift # next arg
done
Footnotes
~~~~~~~~~
[1] It's on the internet but I posted it under a different username. I
don't want to mingle my online identities so I won't post it here.
[2]
https://github.com/yt-dlp/yt-dlp
[3]
https://mpv.io/
[4]
https://twitch.tv/macaw45
[5]
https://github.com/jqlang/jq
[6]
https://orgmode.org
[7]
https://orgmode.org/manual/External-Links.html