Play YouTube videos from the command line in a convenient way
Find a file
2021-04-18 12:14:34 +02:00
images add new screenshot 2021-04-03 10:23:46 +02:00
flake.lock update flake 2021-04-18 12:14:34 +02:00
flake.nix update flake 2021-04-18 12:14:34 +02:00
README.org update README 2021-04-17 09:23:35 +02:00
tyt update usage 2021-04-14 17:46:07 +02:00

tyt

Terminal YouTube (tyt) is a small bash script that lets you play YouTube videos by query from the command line. It is created via literate programming, you can find the code below.

/denis/tyt/media/commit/73c624fee8a0add3d886fb203d98f4a4025fd041/images/screenshot.png

Features

  • Search and play videos with one command
  • Interactively select a video from a list
  • Download a video to the current working directory

There is a --music flag, so you can substitute video with song in the above list.

Execution

This project is a Nix flake. If you have a recent Nix version and flakes are enabled, you can execute the script via:

  nix run github:Deleh/tyt -- --help

If you are not running the Nix package manager, you should definitely try it out.

Anyway, this is just a shell script so clone the repo, make sure the dependencies listed below are fulfilled and there you go:

  ./tyt --help

Dependencies

If you are running tyt as Nix flake you don't have to care about dependencies. A mpv version with scripts is used by default, this enables MPRIS support while playback and skipping sponsored segments of videos.

If you are not running Nix, make sure the following dependencies are installed on your system and hope that everything works:

Usage

  Usage:
    tyt [options] "<quoted_query>"

  Options:
    -a* --alternative  Play an alternative video (e.g. -aaa for third alternative)
    -h  --help         Show this help message
    -i  --interactive  Interactive mode (overrides --alternative)
    -m  --music        Play only the audio track
    -s  --save         Save video (or audio if -m is provided) to the current directory

  Examples:

    Search for "Elephants Dream" and play the video:
      tyt "Elephants Dream"

    Search for "The Beatles - Yellow Submarine" and play only the music:
      tyt -m "The Beatles - Yellow Submarine"

    Search for "Elephants Dream" interactively and download the selected video:
      tyt -i -s "Elephants Dream"

Script

Dependencies

On the start of the script, it is checked if the dependencies are fulfilled.

  missing_dependencies=false
  if ! command -v jq &> /dev/null
  then
      echo -ne "\e[1mjq\e[0m was not found, please install it\n"
      missing_dependencies=true
  fi
  if ! command -v mpv &> /dev/null
  then
      echo -ne "\e[1mmpv\e[0m was not found, please install it\n"
      missing_dependencies=true
  fi
  if ! command -v youtube-dl &> /dev/null
  then
      echo -ne "\e[1myoutube-dl\e[0m was not found, please install it\n"
      missing_dependencies=true
  fi
  if [ "$missing_dependencies" = true ]
  then
      exit 1
  fi

Usage

This function prints the usage of the script.

  function print_usage {
      echo "Usage:"
      echo "  tyt [options] \"<quoted_query>\""
      echo ""
      echo "Options:"
      echo "  -a* --alternative  Play an alternative video (e.g. -aaa for third alternative)"
      echo "  -h  --help         Show this help message"
      echo "  -i  --interactive  Interactive mode (overrides --alternative)"
      echo "  -m  --music        Play only the audio track"
      echo "  -s  --save         Save video (or audio if -m is provided) to the current directory"
      echo ""
      echo "Examples:"
      echo ""
      echo "  Search for \"Elephants Dream\" and play the video:"
      echo "    tyt \"Elephants Dream\""
      echo ""
      echo "  Search for \"The Beatles - Yellow Submarine\" and play only the music:"
      echo "    tyt -m \"The Beatles - Yellow Submarine\""
      echo ""
      echo "  Search for \"Elephants Dream\" interactively and download the selected video:"
      echo "    tyt -i -s \"Elephants Dream\""
  }

Arguments

At first we parse the arguments. We have the following flags:

-a* | --alternative
Alternative video; You can parse any amount of alternatives (e.g. -aaa)
=-h | help
Show a help message
-i | --interactive
Interactive mode; Shows the first 10 results and queries for a selection; If this flag is set, -a is ignored
-m | --music
Play only the audio track of the video
-s | --save
Save the video (or audio if -m is set) to the current directory

Additionally we have exacly one mandatory quoted string as query.

  alternative=0
  format="flac"
  interactive=false
  music=false
  save=false
  help=false

  for arg in "$@"
  do
    case $arg in
      -a*)
        alternative="${arg:1}"
        alternative="${#alternative}"
        shift
        ;;
      --alternative)
        alternative=1
        shift
        ;;
      -i|--interactive)
        interactive=true
        shift
        ;;
      -m|--music)
        music=true
        shift
        ;;
      -s|--save)
        save=true
        shift
        ;;
      -h|--help)
        help=true
        shift
        ;;
      ,*)
        other_arguments+=("$1")
        shift
        ;;
    esac
  done

  if [ "$help" = true ]
  then
    print_usage
    exit 0
  fi

  if [ "${#other_arguments[@]}" != "1" ]
  then
    print_usage
    exit 1
  fi

  query="${other_arguments[0]}"

Greeter

If the arguments match, print a greeter. Another greeter is printed if the flag -m is set. Make sure your terminal emulator supports Unicode to see the notes.

  if [ "$music" = false ]
  then
    echo -ne "\n      \e[1m\ /\e[0m\n"
    echo -ne "    \e[1m=======\e[0m\n"
    echo -ne "    \e[1m| \e[31mtyt\e[0m \e[1m|\e[0m\n"
    echo -ne "    \e[1m=======\e[0m\n\n"
  else
    echo -ne "\n      \e[1m\ /\e[0m     ♫\n"
    echo -ne "    \e[1m=======\e[0m ♫\n"
    echo -ne "    \e[1m| \e[31mtyt\e[0m \e[1m|\e[0m\n"
    echo -ne "    \e[1m=======\e[0m\n\n"
  fi

Get URL and other data

To play a video, we need to get a valid URL. Since there are sometimes parsing errors of the JSON response, we use an endless loop to try until we get a valid response. The first n URLs are saved if an alternative download is requested.

  i=0

  if [ "$interactive" = true ]
  then
    n=10
  else
    n=$((alternative+1))
  fi

  echo -ne "Searching for: \e[34m\e[1m$query\e[0m    \r"

  until results=$(youtube-dl --default-search "ytsearch" -j "ytsearch$n:$query") &> /dev/null
  do

    case $i in
      0)
    appendix="   "
    ;;
      1)
    appendix=".  "
    ;;
      2)
    appendix=".. "
    ;;
      ,*)
    appendix="..."
    ;;
    esac

    echo -ne "Searching for: \e[34m\e[1m$query\e[0m $appendix\r"

    i=$(((i + 1) % 4))
    sleep 1

  done

  echo -ne "Searching for: \e[34m\e[1m$query\e[0m    \n"

  urls=$(echo $results | jq '.webpage_url' | tr -d '"')
  titles=$(echo $results | jq '.fulltitle' | tr -d '"')
  uploaders=$(echo $results | jq '.uploader' | tr -d '"')

  OLDIFS=$IFS
  IFS=$'\n'
  urls=($urls)
  titles=($titles)
  uploaders=($uploaders)
  IFS=$OLDIFS

Interactive selection

If the interactive flag is present, show the first ten results and query for a video to play.

  if [ "$interactive" = true ]
  then
      echo ""
      selections=(0 1 2 3 4 5 6 7 8 9 q)
      for i in "${selections[@]}"
      do
          if [ ! "$i" = "q" ]
          then
              echo -ne "  \e[1m$i\e[0m: ${titles[$i]} (\e[33m\e[1m${uploaders[$i]}\e[0m)\n"
          fi
      done
      echo -ne "  \e[1mq\e[0m: Quit\n"
      echo -ne "\nSelection: "
      read selection
      while [[ ! "${selections[@]}" =~ "${selection}" ]]
      do
          echo -ne "Not valid, try again: "
          read selection
      done
      if [ "$selection" = "q" ]
      then
          exit
      fi
      echo ""
      url=${urls[$selection]}
      title=${titles[$selection]}
      uploader=${uploaders[$selection]}
  else
      url=${urls[$alternative]}
      title=${titles[$alternative]}
      uploader=${uploaders[$alternative]}
  fi

Play or save video

Finally the video is played via mpv or saved via youtube-dl. If the -m flag is set, only the audio track is played or saved.

In interaction mode, another video is queried to be played.

  function play {
      echo -ne "Playing: \e[32m\e[1m$2\e[0m (\e[33m\e[1m$3\e[0m)\n"
      if [ "$music" = true ]
      then
          mpv --no-video "$1" &> /dev/null
      else
          mpv "$1" &> /dev/null
      fi
  }
  
  function download {
      echo -ne "Downloading: \e[32m\e[1m$2\e[0m (\e[33m\e[1m$3\e[0m)\n"
      if [ "$music" = true ]
      then
          youtube-dl -x -o "%(title)s.%(ext)s" "$1" &> /dev/null
      else
          youtube-dl -o "%(title)s.%(ext)s" "$1" &> /dev/null
      fi
  }

  if [ "$save" = true ]
  then
      download "$url" "$title" "$uploader"
  else
      play "$url" "$title" "$uploader"

      if [ "$interactive" = true ]
      then
          while :
          do
              echo -ne "\nSelect another or enter [q] to quit: "
              read selection
              while [[ ! "${selections[@]}" =~ "${selection}" ]]
              do
                  echo -ne "Not valid, try again: "
                  read selection
              done
              if [ ! "$selection" = "q" ]
              then
                  echo ""
                  url=${urls[$selection]}
                  title=${titles[$selection]}
                  uploader=${uploaders[$selection]}
                  play "$url" "$title" "$uploader"
              else
                exit
              fi
          done
      fi
  fi