I made exactly this, including options like moving through the video with a scale, play/pause function, making playlist, shuffle function.
One thing to know is that youtube_dl has trouble with some videos, there is a fork called yt_dlp which works better. For my app, I changed pafy so that it uses yt_dlp instead of youtube_dl. I can share the whole code, it's a lot, but it's pretty good I think.
#! /usr/bin/python3
from tkinter import *
from bs4 import BeautifulSoup
from youtube_search import YoutubeSearch
import re,random, vlc, pafy, datetime, time,yt_dlp
#global vars
ran = 0
x = []
win = Tk()
count = 0
count2=-1
playurl = ""
scalevar = ""
scalevar2= 100
url2 = []
dur = 0
#window, player
win.geometry('610x100')
win.title("Youtube Player")
menubar = Menu(win)
instance = vlc.Instance()
player = instance.media_player_new()
#toggling shuffle
def funcRAN():
global ran
ran = not(ran)
#this function keeps track of time and moves the timescale
def every_second():
global count2
global ran
length = player.get_length()
place = player.get_time()
if player.is_playing() == 0 and len(url2) > (count2 + 1) and len(url2) > 0 and abs(place-length) < 10000:
if ran ==0:
count2 += 1
else:
counttemp = count2
while counttemp == count2:
count2 = random.randrange(len(url2))
scale.set(1)
player.set_time(1)
xvar = funcGO3(url2[count2], playurl)
player.set_time(xvar)
place = 1
if player.is_playing() == 1:
timee =str(datetime.timedelta(seconds = round(place/1000))) + " / " + str(datetime.timedelta(seconds = round(length/1000)))
my_label2.config(text=timee)
scale.set(place)
win.after(1000,lambda:every_second())
def settime(scalevar):
place = player.get_time()
scalevar = int(scalevar)
if abs(scalevar - place) > 4000:
player.set_time(scalevar)
def songskip(amount):
global count2
global ran
win.focus()
if ran == 0:
skip = count2 + amount
else:
counttemp = count2
while counttemp == count2:
count2 = random.randrange(len(url2))
skip = count2
if amount == -1 and player.get_time() > 10000:
player.set_time(0)
elif skip >= 0 and skip < len(url2):
count2 = skip
funcGO3(url2[skip], playurl)
#this function is for downloading the song
def funcDL(song_url, song_title):
outtmpl = song_title + '.%(ext)s'
ydl_opts = {
'format': 'bestaudio/best',
'outtmpl': outtmpl,
'postprocessors': [
{'key': 'FFmpegExtractAudio','preferredcodec': 'mp3',
'preferredquality': '192',
},
{'key': 'FFmpegMetadata'},
],
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info_dict = ydl.extract_info("youtube.com" + song_url, download=True)
#this function is for moving back and forth in time of the song
def funcSKIP(amount):
win.focus()
timee = player.get_time() + amount
if timee < 0:
timee = 0
timeall = player.get_length()
if timee>timeall:
timee = timeall
scale.set(timee)
#to pause
def funcPPTRANSFER():
if str(win.focus_get()) != str(".!entry"):
funcPP()
#to pause
def funcPP():
win.focus()
pause = player.is_playing()
player.set_pause(pause)
if pause == 1:
btnPP.config(text="|>")
else:
btnPP.config(text="||")
#moving through the songlist
def funcMOVE(title, move, resultcount):
win.focus()
global count
count += move
if count < 0:
count =resultcount
if count > resultcount:
count = 0
my_label.config(text = title[count])
#starting the song
def funcGO(title, URL, playurl):
global count2
global count
win.focus()
instance.vlm_stop_media(playurl)
URL = URL[count]
url = "https://www.youtube.com" + str(URL)
count2 += 1
url2.insert(count2,url)
video = pafy.new(url)
#best = video.getbestaudio()
best = video.audiostreams[0]
print(best.quality, int(best.get_filesize()/10000)/100, "mb")
printer = video.title
playurl = best.url
media=instance.media_new(playurl)
media.get_mrl()
player.set_media(media)
btnPP.place(x=340, y=2)
btnGO2.place(x=170, y=62)
btnBACK.place(x=295, y=2)
btnBACK2.place(x=240, y=2)
btnFWD.place(x=395, y=2)
btnFWD2.place(x=445, y=2)
scale.place(x=370, y=68)
player.play()
btnPP.config(text="||")
scale.set(0)
while player.is_playing() == 0:
time.sleep(1)
scale.config(to = player.get_length())
win.title(title[count])
#this is to select the next song in the list
def funcGO2(URL):
global url2
global count
win.focus()
URL2 = URL[count]
url2.append("https://www.youtube.com" + str(URL2))
#this is to play the next song in the list
def funcGO3(url, playurl):
win.focus()
instance.vlm_stop_media(playurl)
video = pafy.new(url)
#best = video.getbestaudio()
best = video.audiostreams[0]
print(best.quality, int(best.get_filesize()/10000)/100, "mb")
printer = video.title
playurl = best.url
media=instance.media_new(playurl)
media.get_mrl()
btnPP.place(x=340, y=2)
btnGO2.place(x=170, y=62)
btnBACK.place(x=295, y=2)
btnBACK2.place(x=240, y=2)
btnFWD.place(x=395, y=2)
btnFWD2.place(x=445, y=2)
scale.place(x=370, y=68)
player.set_media(media)
player.set_time(1)
player.play()
player.set_time(1)
btnPP.config(text="||")
scale.set(0)
scalevar = scale.get()
while player.is_playing() == 0:
time.sleep(1)
player.set_time(1)
length = player.get_length()
scale.config(to = length)
timee =str(datetime.timedelta(seconds = 0)) + " / " + str(datetime.timedelta(seconds = round(length/1000)))
my_label2.config(text=timee)
win.title(printer)
#win.after(2000, lambda:every_second(player, scale, count2))
return(scalevar)
#controlling the volume
def funcSCALE(scalevar2):
player.audio_set_volume(int(scalevar2))
#extracting info from the html of the search
def titlee(URL, question):
results = YoutubeSearch(URL, max_results=40).to_dict()
title=[]
URL=[]
resultcount = -1
for v in results:
length = v['duration']
if length.count(':') > 1 and dur == 1:
continue
if length.count(':') == 1:
m, s = length.split(':')
length = int(m) * 60 + int(s)
if length > 300 and dur == 1:
continue
URL.append(v['url_suffix'])
resultcount+= 1
print(resultcount)
title.append(re.sub(r"[^a-zA-Z0-9 .,:;+-=!?/()öäßü]", "", v['title']))
if question == 1:
url2.append("https://www.youtube.com" + str(v['url_suffix']))
if resultcount == 20:
break
btnDL.place(x=505, y=2)
return(URL, title, 0, resultcount)
#making the search
def func(event):
global count
global x
btnGO.focus()
btnDL.place(x=500, y=2)
x = str(var.get())
URL, title, count, resultcount = titlee(x, 0)
btnL.config(command = (lambda: funcMOVE(title, -1, resultcount)))
btnR.config(command = (lambda:funcMOVE(title, 1, resultcount)))
btnGO.config(command = (lambda: funcGO(title,URL, playurl)))
btnGO2.config(command = (lambda: funcGO2(URL)))
btnDL.config(command =(lambda: funcDL(URL[count],title[count])))
my_label.config(text = title[count])
#import all songs from querie
def funcImportall():
global x
titlee(x, 1)
#toggle limit duration of song
def funcDur():
global dur
dur = not(dur)
#clear playlist
def funcClear():
global url2
url2 = []
btnPP = Button(win, text = "||", command =(lambda: funcPP()))
btnBACK = Button(win, text = "<", command =(lambda: funcSKIP(-10000)))
btnBACK2 = Button(win, text = "<<", command =(lambda: songskip(-1)))
btnFWD = Button(win, text = ">", command =(lambda: funcSKIP(10000)))
btnFWD2 = Button(win, text = ">>", command =(lambda: songskip(1)))
btnDL = Button(win, text = "↓")
btnL = Button(win, text = "<-")
btnR = Button(win, text = "->")
btnGO = Button(win, text = "OK")
btnGO2 = Button(win, text = "+")
scale = Scale(win, from_=0, to=1000, orient=HORIZONTAL,length=200, variable = scalevar, showvalue=0, command = settime)
scale2 = Scale(win, from_=200, to=0, orient=VERTICAL,length=80, variable = scalevar2, showvalue=0, command = funcSCALE)
scale2.place(x=580, y=2)
scale2.set(100)
my_label = Label(win, text = "")
my_label.place(x=5, y=36)
my_label2 = Label(win, text = "")
my_label2.place(x=220, y=66)
var = Entry(win, width=20)
var.place(x=5, y=5)
var.focus()
var.bind('<Return>', func)
btnL.place(x=5, y=62)
btnR.place(x=60, y=62)
btnGO.place(x=115, y=62)
win.bind_all("<Button-1>", lambda event: event.widget.focus_set())
filemenu = Menu(win, tearoff=0)
filemenu.add_command(label="toggle shuffle", command=funcRAN)
filemenu.add_command(label="toggle limit duration", command=funcDur)
editmenu = Menu(menubar, tearoff=0)
editmenu.add_command(label="all results to playlist", command=funcImportall)
editmenu.add_command(label="clear playlist", command=funcClear)
menubar.add_cascade(label="Options", menu=filemenu)
menubar.add_cascade(label="Playlists", menu=editmenu)
win.config(menu=menubar)
win.bind('<space>',lambda event:funcPPTRANSFER())
win.after(2000, lambda:every_second())
var.focus()
win.mainloop()