diff --git a/README.org b/README.org index 73d149a..0a0bcc2 100644 --- a/README.org +++ b/README.org @@ -1,377 +1,61 @@ * tyt -:PROPERTIES: -:header-args: :tangle tyt :shebang "#!/usr/bin/env bash" -:END: -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. + Terminal YouTube (*tyt*) is a small bash script that lets you play YouTube videos by query from the command line. -[[./images/screenshot.png]] + [[./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 + - 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. + There is a =--music= flag, so you can substitute /video/ with /song/ in the above list. ** Execution -This project is a [[https://nixos.wiki/wiki/Flakes][Nix flake]]. -If you have a recent *Nix* version and *flakes* are enabled, you can execute the script via: + This project is a [[https://nixos.wiki/wiki/Flakes][Nix flake]]. + If you have a recent [[https://nixos.org/][Nix]] version and *Flakes* are enabled, you can execute the script via: -#+begin_example sh - nix run github:Deleh/tyt -- --help -#+end_example + : nix run github:Deleh/tyt -- --help -If you are not running the [[https://nixos.org/][Nix]] package manager, you should definitely try it out. + If not you can clone the repo, make sure the dependencies listed below are fulfilled and execute /tyt/ manually: -Anyway, this is just a shell script so clone the repo, make sure the dependencies listed below are fulfilled and there you go: - -#+begin_example sh - ./tyt --help -#+end_example + : ./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 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: + If you are not running Nix, make sure the following dependencies are installed on your system and hope that everything works: -- [[https://stedolan.github.io/jq/][jq]] -- [[https://mpv.io/][mpv]] -- [[https://ytdl-org.github.io/youtube-dl/][youtube-dl]] + - [[https://stedolan.github.io/jq/][jq]] + - [[https://mpv.io/][mpv]] + - [[https://ytdl-org.github.io/youtube-dl/][youtube-dl]] ** Usage -#+begin_example text - Usage: - tyt [options] "" + #+begin_example + Usage: tyt [OPTIONS] QUERIES ... [OPTIONS] - 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 + Play YouTube videos from the command line in a convenient way. - Examples: + OPTIONS + -h, --help Show this help message + -a*, --alternative Play an alternative video (e.g. -aaa for third + alternative) + -d, --download Download video (or audio if -m is provided) to the current + directory + -i, --interactive Interactive mode (overrides --alternative) + -m, --music Play only the audio track - 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" -#+end_example - -** Script -*** Dependencies - -On the start of the script, it is checked if the dependencies are fulfilled. - -#+begin_src bash - 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 -#+end_src - -*** Usage - -This function prints the usage of the script. - -#+begin_src bash - function print_usage { - echo "Usage:" - echo " tyt [options] \"\"" - 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\"" - } -#+end_src - -*** 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. - - - -#+begin_src bash - 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]}" -#+end_src - -*** 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. - -#+begin_src bash - 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 -#+end_src - -*** 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. - -#+begin_src bash - 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 -#+end_src - -*** Interactive selection - -If the interactive flag is present, show the first ten results and query for a video to play. - -#+begin_src bash - 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 -#+end_src - -*** 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. - -#+begin_src bash - 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 -#+end_src + EXAMPLES + tyt Elephants Dream # Search for 'Elephants Dream' and play + # the video + tyt -m The Beatles - Yellow Submarine # Search for 'The Beatles - Yellow + # Submarine' and play only the music + tyt -i -s Elephants Dream # Search for 'Elephants Dream' + # interactively and download the + # selected video + #+end_example diff --git a/flake.lock b/flake.lock index 68c7a34..969133d 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "flake-utils": { "locked": { - "lastModified": 1618217525, - "narHash": "sha256-WGrhVczjXTiswQaoxQ+0PTfbLNeOQM6M36zvLn78AYg=", + "lastModified": 1631561581, + "narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=", "owner": "numtide", "repo": "flake-utils", - "rev": "c6169a2772643c4a93a0b5ac1c61e296cba68544", + "rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19", "type": "github" }, "original": { @@ -17,12 +17,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1618734763, - "narHash": "sha256-jxyDdEBt59vqp5t5Ald+i9zgmjf3r+mPYgT4gmEXJR4=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "c6df94ce561456492babc7628042fdc1c5fa0d5a", - "type": "github" + "lastModified": 1633267966, + "narHash": "sha256-gFKvZ5AmV/dDTKXVxacPbXe4R0BsFpwtVaQxuIm2nnk=", + "path": "/nix/store/k13ripsl4n2p6wf2ksy5m017ryykx4qc-source", + "rev": "7daf35532d2d8bf5e6f7f962e6cd13a66d01a71d", + "type": "path" }, "original": { "id": "nixpkgs", diff --git a/flake.nix b/flake.nix index 2943cc9..481a6cb 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "Play YouTube videos from the command line in a convenient way"; - nixConfig.bash-prompt = "\[\\e[1m\\e[31mtyt-develop\\e[0m\]$ "; + nixConfig.bash-prompt = "\[\\e[1m\\e[31mtyt-develop\\e[0m:\\w\]$ "; inputs.flake-utils.url = "github:numtide/flake-utils"; diff --git a/tyt b/tyt index fcc25b3..008dbe5 100755 --- a/tyt +++ b/tyt @@ -1,212 +1,76 @@ #!/usr/bin/env bash -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 + +# Text formatting variables +text_bold="\e[1m" +text_red="\e[31m" +text_reset="\e[0m" +text_yellow="\e[33m" function print_usage { - echo "Usage:" - echo " tyt [options] \"\"" - 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\"" + cat < /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 - -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" ] +function print_logo { + if [ "$music" == true ] then - exit + echo -ne "\n ${text_bold}\ /${text_reset} ♫\n" + echo -ne " ${text_bold}=======${text_reset} ♫\n" + echo -ne " ${text_bold}| ${text_red}tyt${text_reset} ${text_bold}|${text_reset}\n" + echo -ne " ${text_bold}=======${text_reset}\n\n" + else + echo -ne "\n ${text_bold}\ /${text_reset}\n" + echo -ne " ${text_bold}=======${text_reset}\n" + echo -ne " ${text_bold}| ${text_red}tyt${text_reset} ${text_bold}|${text_reset}\n" + echo -ne " ${text_bold}=======${text_reset}\n\n" fi - echo "" - url=${urls[$selection]} - title=${titles[$selection]} - uploader=${uploaders[$selection]} -else - url=${urls[$alternative]} - title=${titles[$alternative]} - uploader=${uploaders[$alternative]} -fi +} + +function print_controls { + echo -e "[${text_bold}p${text_reset}] Play/Pause, [${text_bold}←${text_reset}/${text_bold}→${text_reset}] Fast-Backward/-Forward, [${text_bold}q${text_reset}] Stop" +} + +function error { + echo -ne "${text_bold}ERROR${text_reset} $1\n" >&2 + exit 1 +} function play { - echo -ne "Playing: \e[32m\e[1m$2\e[0m (\e[33m\e[1m$3\e[0m)\n" - if [ "$music" = true ] + print_controls + if [ "$music" == true ] then - mpv --no-video "$1" &> /dev/null + mpv --no-video --msg-level=all=no,statusline=status --term-status-msg="\${time-pos}/\${duration} - ${text_bold}${2//\\/\\\\}${text_reset} (${text_yellow}${text_bold}${3//\\/\\\\}${text_reset})" "$1" else - mpv "$1" &> /dev/null + mpv --msg-level=all=no,statusline=status --term-status-msg="\${time-pos}/\${duration} - ${text_bold}${2//\\/\\\\}${text_reset} (${text_yellow}${text_bold}${3//\\/\\\\}${text_reset})" "$1" fi + echo } function download { - echo -ne "Downloading: \e[32m\e[1m$2\e[0m (\e[33m\e[1m$3\e[0m)\n" - if [ "$music" = true ] + echo -ne "Downloading: ${text_bold}$2${text_reset} (${text_yellow}${text_bold}$3${text_reset})\n" + if [ "$music" == true ] then youtube-dl -x -o "%(title)s.%(ext)s" "$1" &> /dev/null else @@ -214,33 +78,167 @@ function download { fi } -if [ "$save" = true ] +# Set default values +alternative=1 +download=false +interactive=false +music=false +query="" + +# Parse arguments +while (( "$#" )); do + case "$1" in + -a*) + alternative="${#1}" + shift + ;; + --alternative) + alternative=2 + shift + ;; + -d|--download) + download=true + shift + ;; + -h|--help) + print_usage + ;; + -i|--interactive) + interactive=true + shift + ;; + -m|--music) + music=true + shift + ;; + -) + query="$query $1" + shift + ;; + -*|--*=) + error "Unsupported flag: $1" + ;; + *) + query="$query $1" + shift + ;; + esac +done + +# Check dependencies +if ! command -v jq &> /dev/null +then + error "jq was not found, please install it" +elif ! command -v mpv &> /dev/null +then + error -ne "mpv was not found, please install it" +elif ! command -v youtube-dl &> /dev/null +then + error "youtube-dl was not found, please install it" +fi + +print_logo +echo -ne "Searching for: $text_bold${query:1}$text_reset\n" + +# Set number of videos +if [ "$interactive" == true ] +then + n=10 +else + n="$alternative" +fi + +# Get results +results=$(youtube-dl --default-search "ytsearch" -j "ytsearch$n:${query:1}") +urls=$(echo $results | jq '.webpage_url' | tr -d '"') +titles=$(echo $results | jq '.fulltitle' | tr -d '"') +uploaders=$(echo $results | jq '.uploader' | tr -d '"') + +# Create arrays +OLDIFS=$IFS +IFS=$'\n' +urls=($urls) +titles=($titles) +uploaders=($uploaders) +IFS=$OLDIFS + +if [ "${#urls[@]}" == 0 ] +then + error "No results, try again" +fi + +# Select video +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 -e " ${text_bold}$i${text_reset}: ${titles[$i]} (${text_yellow}${text_bold}${uploaders[$i]}${text_reset})" + fi + done + echo -e " ${text_bold}q${text_reset}: Quit\n" + echo -ne "Selection: " + read -n1 selection + echo + while [[ ! "${selections[@]}" =~ "${selection}" ]] + do + echo -ne "Not valid, try again: " + read -n1 selection + echo + done + if [ "$selection" == "q" ] + then + echo + exit + fi + echo + url=${urls[$selection]} + title=${titles[$selection]} + uploader=${uploaders[$selection]} +else + url=${urls[$((alternative-1))]} + title=${titles[$((alternative-1))]} + uploader=${uploaders[$((alternative-1))]} +fi + +# Download or play video +if [ "$download" = 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 +if [ "$interactive" == true ] +then + while : + do + echo -ne "\nSelect another or enter [${text_bold}q${text_reset}] to quit: " + read -n1 selection + echo + while [[ ! "${selections[@]}" =~ "${selection}" ]] + do + echo -ne "Not valid, try again: " + read -n1 selection + echo + done + if [ "$selection" != "q" ] + then + echo + url=${urls[$selection]} + title=${titles[$selection]} + uploader=${uploaders[$selection]} + if [ "$download" == true ] + then + download "$url" "$title" "$uploader" + else + play "$url" "$title" "$uploader" + fi + else + echo + exit + fi + done fi