0

Is there a utility that can search YouTube from the command line, and then either view or download the search results according to user input?

$ youtube-search madonna

1 Madonna - Hung Up (Official Music Video)
madonna ♩ 180M views 9 years ago
https://www.youtube.com/watch?v=EDwb9jOVRtU
"Hung Up" by Madonna from Confessions On A Dance Floor, available now.

2 Madonna - Like A Prayer (Official Music Video)
madonna ♩ 69M views 9 years ago
https://www.youtube.com/watch?v=79fzeNUqQbQ
2006 WMG Like A Prayer.

etc.

And then you can enter:

  • "v1" to view video 1 (with VLC, etc.)
  • "d1-3" to download videos 1-3 (with youtube-dl, etc.)
  • "n" to view next page of search results

What I tried so far:

googler works partially with YouTube, but for some reason only shows two search results when searching for "Madonna". Also there is not the choice between view and download.

youtube-dl has a search function, but doesn't seem to print the search results nor accept user input. youtube-dl -j ytsearch:madonna lists metadata about search results, but doesn't seem to contain the video link, title and description that would be desired.

  • Take a look at github.com/mps-youtube/mps-youtube. You can do all three things you mentioned with it. – ritiek Jan 23 at 15:22
  • @ritiek: I heard about it, but refrained from installing it due to the insane number of dependencies: libpng, frei0r, glib, pixman, cairo, graphite2, icu4c, harfbuzz, libvpx, opencore-amr, opus, sdl2, snappy, theora, x264, x265, ffmpeg, libtiff, little-cms2, lua@5.1, mujs, openjpeg, webp, leptonica, tesseract, zimg, vapoursynth and mpv. Maybe it's a great utility, but seems like way overkill for my needs. – forthrin Jan 23 at 20:15
  • So... the answer to your question is, "yes", but your response is, "I don't want to install that." – roaima Jan 23 at 21:16
2

Firstly, you need ytsearchN: to ask for N results. Secondly, make sure that you have the latest youtube-dl (I had some issues with an old version).

The following basic script will get 5 results, display their titles and urls, and ask which to download. Making it respond to the commands "vN" and "dN" would be simple ("dN" is effectively already implemented); I'm not sure how you could get the next page of results, though.

#!/bin/bash

tempfile=$(mktemp)
youtube_dl_log=$(mktemp)

youtube-dl -j "ytsearch5:$*" > $tempfile

# workaround for lack of mapfile in bash < 4
# https://stackoverflow.com/a/41475317/6598435
while IFS= read -r line
do
    youtube_urls+=("$line")
done < <(cat $tempfile | jq '.webpage_url' | tr -d '"' )
# # for bash >= 4
# mapfile -t youtube_urls < <(cat $tempfile | jq '.webpage_url' | tr -d '"' )

cat $tempfile | jq '.fulltitle, .webpage_url'

while :
do
    echo "Enter video number to download."
    read i
    # don't download anything if you just press enter
    if [ ! x"$i" == x"" ]
    then
        # to make numbering of videos more intuitive (start from 1 not 0)
        youtube-dl --no-progress ${youtube_urls[$i - 1]} &
    fi
done

You might, perhaps, want to redirect the output from youtube-dl to a file (or /dev/null), though it could also be considered useful.

  • A very lightweight and nice suggestion. However, macOS Unix doesn't have mapfile and jq. jq seems available in Homebrew, but how do I get mapfile? Or can your script be rewritten for compatibility? stackoverflow.com/questions/41475261/… – forthrin Jan 23 at 20:21
  • mapfile is a built-in (like help, while, alias etc.), in bash ≥ 4; by default macOS ships with lower versions of bash, as they're allergic to GPLv3 (the version they ship is the last that was under GPLv2). You can install the latest bash with homebrew. I'll modify the script to avoid use of mapfile, but bash 4 has many great features (among other things "bracketed paste" and nicer customisation of keybindings) so it might be worth installing anyway. – aplaice Jan 23 at 20:53
  • I tried installing Homebrew bash (5.0.0), but there's no mapfile there. Thanks for the modification! I'll try it out. Also, I think many people will benefit from this script. – forthrin Jan 23 at 21:37
1

A selenium based solution:

#!/usr/bin/env python

from pyvirtualdisplay import Display
from selenium import webdriver
import time
import argparse
import re
import subprocess


class bcolors:
    BLUE = '\033[94m'
    GREEN = '\033[92m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'

pattern1=re.compile("v:[0-9]");
pattern2=re.compile("d:[0-9](-[0-9])?$")

parser = argparse.ArgumentParser()
parser.add_argument("query", help="enter the youtube search query here")
args = parser.parse_args()
query=args.query

display = Display(visible=0, size=(800, 600))
display.start()

driver = webdriver.Chrome()  # Optional argument, if not specified will search path.
driver.get('https://youtube.com');
search_box = driver.find_element_by_name('search_query')
search_box.send_keys(query)
search_box.submit()

i=1
page = driver.find_elements_by_css_selector("ytd-video-renderer.ytd-item-section-renderer")
link_list=[];
for video in page:
    a1=video.find_element_by_id('thumbnail').get_attribute('href')
    a2=video.find_element_by_id('video-title').text
    a3=video.find_elements_by_css_selector('yt-formatted-string[has-link-only_]:not([force-default-style]) a.yt-simple-endpoint.yt-formatted-string')[0].text
    a4=video.find_elements_by_css_selector('#metadata-line.ytd-video-meta-block span.ytd-video-meta-block')[0].text
    a5=video.find_elements_by_css_selector('#metadata-line.ytd-video-meta-block span.ytd-video-meta-block')[1].text
    a6=video.find_elements_by_css_selector('#description-text.ytd-video-renderer')[0].text
    #ink=video.get_attribute('innerHTML')
    print i,a2
    print a3+"    "+a4+"    "+a5
    print a1
    print a6
    print
    i+=1
    link_list.append(a1)


driver.quit()
display.stop()

print bcolors.BOLD+bcolors.GREEN+"Available options- v: open video in VLC media player, d: download video by number, q: quit"+bcolors.ENDC
print bcolors.BOLD+bcolors.BLUE+"Usage - option:number    or    option:beginning-end"+bcolors.ENDC
print
entry="y"
while entry!="q":
    entry=raw_input(">")
    if pattern1.match(entry):
        link=entry.split(":")[1]
        bashCommand="vlc "+link_list[int(link)]
        pass
    elif pattern2.match(entry):
        nums=entry.split(":")[1]
        b=int(nums.split("-")[0])
        e=int(nums.split("-")[1])
        bashCommand="youtube-dl "+" ".join(link_list[b:e])
    print bashCommand
    process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
    output, error = process.communicate()
    pass

I haven't tried running the actual commands (Popen) but I think this should do it.

  • Nice try, however I think it would be better to use a lightweight HTML scraper such as BeautifulSoup. Not everyone has Chrome installed. – forthrin Jan 23 at 20:17
  • The same thing can be done for firefox also right? And I have never used BeautifulSoup, so probably I will look into that, thanks – Yuki.kuroshita Jan 23 at 23:08
1

Not quite pure CLI but why not use yad or zenity to ease the selection process?

Register as a developer and get an API key for youtube then this works 'as is'.

If you don't want to use the API then uncomment and comment the lines as noted and it will use 'youtube-dl' instead, though the search is slower.

#!/bin/bash

APIKEY="GET YOUR OWN API KEY!"
APIURL="https://www.googleapis.com/youtube/v3/search"
NORESULTS=25
DOWNURL="https://www.youtube.com/watch?v="

function down { youtube-dl  "$DOWNURL$1"; }
function watch { xdg-open "$DOWNURL$1"; }

SEARCH=$( yad --center --title="Search for clip" --text="Enter search term" --entry 2>/dev/null)
SEARCH=$(echo $SEARCH | sed 's/ /%20/g')

#uncomment the 2 lines below to use youtube-dl 
#youtube-dl -j "ytsearch$NORESULTS:$SEARCH" | jq '.id, .title' > yt.search
#SELECTION=$(cat yt.search | sed 's/^\"\"$/\"No entry\"/' | sed 's/&/+/g' | sed '/^[^ ]*$/ i \"FALSE\"' | yad --center --checklist --list --width=1000 --height=800 --column=Check --column=ID --column=Title --button='gtk-cancel':0 --button='Download':2 --button='watch':4)

#comment out the following 3 lines if using youtube-dl
QUERY="part=snippet&maxResults=$NORESULTS&q=$SEARCH&type=video&key=$APIKEY"
curl -i -G -d "$QUERY" "$APIURL" |  sed '1,15d' | jq '.items[] | .id.videoId, .snippet.title, .snippet.description' > yt.search
SELECTION=$(cat yt.search | sed 's/^\"\"$/\"No entry\"/' | sed 's/&/+/g' | sed '/^[^ ]*$/ i \"FALSE\"' | yad --center --checklist --list --width=1000 --height=800 --column=Check --column=ID --column=Title --column=Description --button='gtk-cancel':0 --button='Download':2 --button='watch':4)

ACTION=$?
case $ACTION in
    0)
        echo "cancelled"
        exit
        ;;
    *)
        for i in $(echo $SELECTION | sed 's/|/\n/g' | grep -P "^\"[^ ]*\"$" | sed 's/\"//g'); do 
            if [ $ACTION -eq "2" ]; then #the download button was pressed 
                down "$i"
            else #the watch button was pressed
                watch "$i"
            fi
        done
    ;;
esac
  • It's good you let the user choose not to use the API, so downloads can be done anonymously. What's yad? I'm on macOS Unix. Can it be replaced with something more generic? – forthrin Jan 23 at 20:23
  • yad is a tool for generating gui for scripts, another is zenity. According to apple.stackexchange there is a utility called cocoadialogfor macOS. – bu5hman Jan 24 at 2:57
  • How many seconds does ytsearch10 take on your machine? Here it takes 20 seconds, which makes it unusable. – forthrin Jan 27 at 17:08
  • ytsearch takes forever for me too. That's why I had a look at the API. It's like lightning compared to ytsearch. – bu5hman Jan 27 at 17:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.