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