14th of July, 2022 · In Technical · Tagged with dmenu, suckless

Suckless Youtube Client

Watching youtube videos in a browser is often not fun. Between having to get your adblocking / cookie configuration to hit an ever moving target, and mixing your video watching with other productivity tasks it does not have the feel of a simple UNIX like interface. To remedy that we will present a script that acts as a local youtube client by combining suckless tools (and yt-dlp / mpv). With our method searching / watching a new video is actually faster than doing it in the browser, and the entire video caches in RAM case your internet has problems mid-video.

SucklessYT

The Suckless dependencies are: dmenu, tabbed, and a newcomer caoydl (fast youtube lookups). The basic concept is that we launch a tabbed window that will hold everything (search + vidoes in tabs). Then we parent all the applications to that tabbed instance (tabbed is smart enough to make new child windows into new tabs).

The other dependencies are yt-dlp, mpv, xdotool and xprop

Without further ado here is the script (I launch it with Super+y in DWM):

#!/bin/bash

start_tabbed () {
    YTE=$(xdotool search --classname "SucklessYT" | tail -n1)
    if [[ "$YTE" = *[![:space:]]* ]]
    then
      XID="$YTE"
    else
      FIFO="$(mktemp -u)"
      mkfifo "$FIFO"

      tabbed -n "SucklessYT" > "$FIFO" &

      jobs # Get rid of the "Completed" entries

      TABBEDPID="$(jobs -p %%)"

      if [ -z "$TABBEDPID" ] ; then
          echo "Can't start tabbed"
          exit 1
      fi

      read -r XID < "$FIFO"

      rm "$FIFO"
    fi
}

start_tabbed

search_query=$(echo "" |
dmenu -c -w "$XID" -p "Search")

result=$(caoydl -s "$search_query" | dmenu -c -l 30 -i -w "$XID")
yt_id=$(echo "$result" | awk -F"\t" '{print $1}')
TITLE=$(echo "$result" | awk -F"\t" '{print $2}')

mpv "https://youtube.com/watch?v=$yt_id" \
  --ytdl-format="22" \ 
  --keep-open=yes \
  --force-window=immediate \
  --wid="$XID" &
MPV_PID=$!

while [[ $(xdotool search --pid "$MPV_PID" | wc -l)  -eq 0 ]]; do
  sleep 1
done

MPV_WID=$(xdotool search --pid "$MPV_PID")
xprop -id "$MPV_WID" -set WM_NAME "$TITLE"

The difficult part is managing the tabbed instance. We first check to see if there is an existing instance with the classname SucklessYT and if not we spin up a new instance with an empty window (the FIFO part was taken from nnn, just keeps the window open without content in anticipation).

After that we parent a dmenu to the tabbed window for the search query. Then search the results with caoydl (parsing the title and id from the selected result). Then we just load up MPV with the video. Format 22 is used because it does not require using ffmpeg to merge the video / audio streams which depending on your system resources can use a lot (However format 22 is not always available in newly published videos, so this may need some tweaking).

After MPV is spun up with the video we assign the title to the X11 window and tabbed picks up the video name as the tab title.

Quitting a tab can be done with Ctrl+Q. Moving / swapping with the vim keys + control.

It's a pretty minimal / simple client, but works great.

A couple of notes. You might want to tweak some of the mpv settings:

// ~/.config/mpv/mpv.conf

ytdl-format=bestvideo[height<=?720]+bestaudio/best
ytdl-raw-options=concurrent-fragments=50
script-opts=ytdl_hook-ytdl_path=/usr/bin/yt-dlp

cache-secs=10000
stream-buffer-size=512MiB
demuxer-max-bytes=512MiB
demuxer-readahead-secs=10000

This will help with better caching / advanced downloading. If you only want to use this for SucklessYT you can create a new MPV profile.

No thumbnails is a slightly lackluster experience. I would like to write a small Suckless program like DMenu but that can take images in addition to text in the menu entries (and perhaps hide the ids, so something like it takes column data and you can choose which fields to hide / which fields to output).