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