YouTube downloading

sudo pip install --upgrade youtube_dl

yt2.py

#!/usr/bin/env python3.7

import os, platform
import difflib
import ytfunctions as yt
import filetree

def similar(a, b):
    return difflib.SequenceMatcher(None, a, b).ratio()

def play_local_audio( filename, quiet=False ):
    if not quiet:
        print("Playing",filename)
    # Note: This function will only exit once the song has finished
    if platform.system() == "Windows":
        pass # TODO
    elif platform.system() == "Linux": # Raspberry Pi
        os.system("cvlc \""+filename+"\"")
    elif platform.system() == "Darwin": # Mac OS
        os.system("mplayer \""+filename+"\"")

def request_play_song( local_cache_folder, song_request ):
    if not os.path.exists(local_cache_folder):
        os.makedirs(local_cache_folder)
    # Scan for local files
    collection = filetree.get_file_tree(local_cache_folder, recursive=True)
    # Check if we already have the song request
    similar_score = 0
    similar_item = None
    for item in collection:
        if song_request.lower() in item["name"].lower():
            similar_score = 0.8
            similar_item = item
        else:
            similarity = similar(item["name"].lower(), song_request.lower())
            if (similarity > 0.4) and (similarity > similar_score):
                similar_score = similar(item["name"], song_request)
                similar_item = item
    # We have a likely song, let's play it
    if similar_score > 0:
        print("Playing {}".format(similar_item["name"]))
        play_local_audio( similar_item["absolute"] )
    # Go searching Youtube for it instead
    else:
        print("Checking youtube for {}".format(song_request))
        vids = yt.search( song_request )
        if len(vids) > 0:
            print("Downloading {}".format(vids[0]["title"]))
            print("       From {}".format(vids[0]["url"]))
            yt.download(vids[0]["url"], folder=local_cache_folder, extension="m4a", callback=play_local_audio)

# Test it works
if __name__ == "__main__":
    print("Hi! I'm your friendly neighbourhood DJ!")
    request = input("What song would you like? ")
    request_play_song("./media", request)

ytfunctions.py

#!/usr/bin/env python3.7

import requests
import webbrowser
import os 
import sys
import platform
import re
import pafy         # https://github.com/mps-youtube/pafy
from bs4 import BeautifulSoup

"""
Requirements.txt: 
pafy
youtube-dl
bs4
requests
webbrowser

On OSX, install SSL certs by executing following in terminal:
/Applications/Python\ 3.7/Install\ Certificates.command
"""
def _clean_file_name( original ):
    # Strip non-filename-friendly characters from the filename
    regex = re.compile('[^a-zA-Z0-9 \-.]')
    return regex.sub("", original )

def search(query):
    params = {"search_query": query}
    url = "http://www.youtube.com/results"
    response = requests.get(url, params=params)
    html = response.content.decode("utf-8")
    soup = BeautifulSoup(html, "html.parser")
    vids = soup.find_all(attrs={'class': 'yt-uix-tile-link'})
    results = []
    for vid in vids:
        href = vid["href"]
        if href[0:9] == "/watch?v=":
            href = vid["href"]
            if "&" in href:
                href = href[ : href.index("&") ]
            url = "https://www.youtube.com" + href
            content = vid.contents[0]
            results.append( { "url": url, "title": content, "href": vid["href"] } )
    return results

def open_browser(url):
    webbrowser.open(url, new=2) # 2 = open in new tab

def download(url, mode="audio", saveAs=None, folder=None, extension="mp4", quiet=True, callback=None):
    video = pafy.new(url, ydl_opts={'nocheckcertificate': True, "--no-check-certificate": True})
    if not quiet:
        print(video)
    if mode == "audio":
        best = video.getbestaudio(preftype=extension)
    else:
        best = video.getbestvideo(preftype=extension)
    if saveAs == None:
        saveAs = video.title+"."+best.extension
    elif not saveAs[len(best.extension)].lower() == best.extension:
        saveAs = saveAs+"."+best.extension
    saveAs = _clean_file_name(saveAs)
    if not folder==None:
        saveAs = os.path.join(folder, saveAs)
    if not quiet:
        print("Saving ",saveAs," (size: ",best.get_filesize(),")")
    best.download(quiet=quiet, filepath=saveAs)
    if not callback == None:
        callback(saveAs)
    return saveAs

def get_formats(url):
    video = pafy.new(url, ydl_opts={'nocheckcertificate': True, "--no-check-certificate": True})
    results = []
    for s in video.streams:
        results.append( {"type":"video", "resolution": s.resolution, "extension": s.extension, "size": s.get_filesize(), "url": s.url } )
    audiostreams = video.audiostreams
    for a in audiostreams:
        results.append( {"type":"audio", "bitrate": a.bitrate, "extension": a.extension, "size": a.get_filesize(), "url": a.url } )
    return results

def print_formats(url):
    res = get_formats(url)
    for item in res:
        print(item)

filetree.py

#!/usr/bin/env python3.7

import os
from datetime import datetime

def get_file_hash(filename):
    import hashlib
    BUFFER_SIZE = 65536

    hasher = hashlib.sha256()
    with open(filename, 'rb') as f:
        while True:
            data = f.read(BUFFER_SIZE)
            if not data:
                break
            hasher.update(data)
    return hasher.hexdigest()

def _get_file_properties( relative_file_name, hash=False ):
    mtime = os.path.getmtime( relative_file_name )
    size = os.path.getsize( relative_file_name )
    absolute = os.path.abspath( relative_file_name )
    last_seperator = absolute.rindex( os.sep )
    parent_folder = absolute[ : last_seperator ]
    local_file_name = absolute[ last_seperator+1 : ]
    extension = ""
    if "." in local_file_name:
        extension_location = local_file_name.rindex(".")
        extension = local_file_name[ extension_location+1 : ]
    result = { 
        "type":         "file", 
        "parent":       parent_folder, 
        "name":         local_file_name, 
        "extension":    extension,
        "absolute":     absolute,
        "mtime":        mtime, 
        "size":         size
    }
    if hash:
        result["hash"] = get_file_hash( relative_file_name )
    return result


def get_file_tree( parent_folder, recursive=True, hash=True ):
    files = []
    if recursive:
        for root, dirlist, filelist in os.walk(parent_folder):
            for name in filelist:
                full_name = os.path.join(root, name)
                prop = _get_file_properties( full_name, hash=hash )
                files.append( prop )
            for name in dirlist:
                full_name = os.path.join(root, name)
                relative_folder = root[ len(parent_folder): ]
                rec = { 
                    "type":     "folder", 
                    "parent":   relative_folder, 
                    "name":     name,
                    "absolute": full_name
                }
                files.append(rec)
    else: # not recursive
        # https://stackoverflow.com/a/24137699
        items = [ f for f in os.listdir(parent_folder) if os.path.isfile(os.path.join(parent_folder,f)) ]
        for name in items:
            result = _get_file_properties( os.path.join(parent_folder, name), hash=hash )
            files.append( result )
    return(files)