I was working on a small downloader for Youtube videos using trollius (for async), htmlpy and pafy (and youtube-dl). Now i have a problem where once i click the download button, the UI freezes up while the backend is downloading the video.
I have tried to make the downloader class its own thread and run that alongside the UI but that doesn't seem to work. I also tried to uses coroutine to have the video download asynchronously while the UI continues. Neither seemed to work.
Here is some code
class Downloader(htmlPy.Object,threading.Thread):
dd = os.path.join(os.getenv('USERPROFILE'), 'Downloads') # dd = download director
dd = dd + "/vindownload/"
def __init__(self, app):
threading.Thread.__init__(self)
super(Downloader, self).__init__()
# Initialize the class here, if required.
self.app = app
return
#htmlPy.Slot(str)
def download_single(self, json_data):
form_data = json.loads(json_data)
print json_data
url = form_data["name"]
dt = form_data["dt"] # Download type is audio or video
if url.__contains__("https://www.youtube.com/watch?v="):
if dt == 'audio':
print "hello1"
loop = trollius.get_event_loop()
loop.run_until_complete(self._downloadVid(url, vid=False))
loop.stop()
else:
print "hello1"
loop = trollius.get_event_loop()
loop.run_until_complete(self._downloadVid(url, vid=True))
loop.stop()
self.app.evaluate_javascript("document.getElementById('form').reset()")
else:
print "Incorrect url"
print form_data
#trollius.coroutine
def _downloadVid(self, url, vid=False, order_reverse=False, vinName=None):
print "hello123"
video = pafy.new(url)
print video
name = u''.join(video.title).encode('utf8')
name = re.sub("[<>:\"/\\|?*]", "", name)
if not vid:
file = video.getbestaudio()
else:
file = video.getbest()
if (order_reverse):
file.download(self.dd + vinName + name + ".mp4", quiet=False,callback=self.mycb)
else:
file.download(self.dd + name + ".mp4", quiet=False,callback=self.mycb)
def mycb(self,total, recvd, ratio, rate, eta):
print(recvd, ratio, eta)
and my initialize.py
BASE_DIR = os.path.abspath(os.path.dirname("initilize.py"))
app = htmlPy.AppGUI(title=u"Vin download", width=700, height=400, resizable=False)
app.static_path = os.path.join(BASE_DIR, "static/")
app.template_path = os.path.join(BASE_DIR, "templates/")
app.web_app.setMaximumWidth(830)
app.web_app.setMaximumHeight(600)
download = Downloader(app)
download.start()
# Register back-end functionalities
app.bind(download)
app.template = ("./index.html", {"template_variable_name": "value"})
# Instructions for running application
if __name__ == "__main__":
# The driver file will have to be imported everywhere in back-end.
# So, always keep app.start() in if __name__ == "__main__" conditional
app.start()
Now my question is. Is there a way where i can free up my UI while downloading so it doesn't look like the application crashed.
I am using: Python 2.7, Trollius, Pafy, Youtube-dl, HTMLPY.
Thank you for your time.
Alright i found the answer to my question, here is what i did. i changed my download_single method to the following:
#htmlPy.Slot(str)
def download_single(self, json_data):
form_data = json.loads(json_data)
print json_data
url = form_data["name"]
dt = form_data["dt"] # Download type is audio or video
videoDown = videoDownload(url, dt, self.app, self.dd)
videoDown.start()
print form_data
And the code i had in it earlier is now transferred to a new class called videoDownload taking all those attributes stated above.
videoDownload.py
class videoDownload(threading.Thread):
def __init__(self, url, dt, app, dd):
threading.Thread.__init__(self)
self.url = url
self.dt = dt
self.app = app
self.dd = dd
def run(self):
threads.append(self)
if self.url.__contains__("https://www.youtube.com/watch?v="):
if self.dt == 'audio':
print "hello1"
self._downloadVid(self.url, vid=False)
else:
print "hello1"
self._downloadVid(self.url, vid=True)
else:
print "Incorrect url"
def _downloadVid(self, url, vid=False, order_reverse=False, vinName=None):
print "hello123"
video = pafy.new(url)
print video
name = u''.join(video.title).encode('utf8')
name = re.sub("[<>:\"/\\|?*]", "", name)
if not vid:
file = video.getbestaudio()
else:
file = video.getbest()
if (order_reverse):
file.download(self.dd + vinName + name + ".mp4", quiet=False, callback=self.mycb)
else:
file.download(self.dd + name + ".mp4", quiet=False, callback=self.mycb)
threads.remove(self)
def mycb(self, total, recvd, ratio, rate, eta):
pass
This solved the issues i had with ui being blocked. The thread will end itself once leaving the run method and it will be removed from a threads array that is defined above the class.
Have a great day all
~Ellisan
Related
I'm using PyQT5 and currently have a QListWidgetItem that has a custom icon of an image thumbnail.
class ThumbWidget(QListWidgetItem):
def __init__(self, filename: str, database):
QListWidgetItem.__init__(self)
self.filename = filename
self.database = database
self.setText(basename(self.filename))
standard_file_icon = QWidget().style().standardIcon(QStyle.SP_FileIcon)
self.setIcon(standard_file_icon)
self.setSizeHint(QSize(THUMB_WIDTH, THUMB_HEIGHT + FILENAME_MARGIN))
def __str__(self):
return f'Thumbnail for {self.filename}'
def load_thumb(self):
metadata = self.database.file_metadata(self.filename)
img_thumb = metadata['thumb']
if img_thumb:
img = QPixmap()
img.loadFromData(img_thumb, 'JPEG')
self.setIcon(QIcon(img))
As you can see, it starts off using a SP_FileIcon by default and loads in a Thumbnail. Currently this happens in the QListWidget using a thread to load all the thumbnails at once.
class MediaBrowser(QListWidget):
def __init__(self, database: Database, viewtab, dir_path):
QListWidget.__init__(self)
self.log = logging.getLogger('mediahug')
self.database = database
self.viewtab = viewtab
self.current_directory = dir_path
# self.current_file_widgets = []
self.thumb_loader_thread = None
...
...
...
def thumb_load_progress(self, msg):
self.log.debug(msg)
if msg == 'thumb-load':
self.viewtab.scan_label_action.setVisible(True)
self.viewtab.scan_thumb_progress_action.setVisible(True)
elif msg == 'thumb-finish':
self.log.debug('Thumb Scan Finished. Hiding Progressbar')
self.viewtab.scan_label_action.setVisible(False)
self.viewtab.scan_thumb_progress_action.setVisible(False)
elif type(msg) == float:
self.viewtab.scan_thumb_progress.setValue(int(msg * 100))
else:
self.log.warning(f'Unknown thumb loader progress message {msg}')
def load_files(self, dir_path):
if self.thumb_loader_thread and self.thumb_loader_thread.isRunning():
self.log.info('Killing Previous Thumbnail Loading Thread')
self.thumb_loader_thread.requestInterruption()
self.thumb_loader_thread.wait(sys.maxsize)
self.log.info('Previous Thumbnail Thread Done')
self.clear()
# Load New File widgets
onlyfiles = [f for f in listdir(dir_path) if isfile(join(dir_path, f))]
for f in onlyfiles:
vid = join(dir_path, f)
self.log.debug(f"Creating File/Thumb Widget {vid}")
self.addItem(ThumbWidget(vid, self.database))
self.thumb_loader_thread = ThumbLoaderThread(self.all_files(), dir_path)
self.thumb_loader_thread.signal.connect(self.thumb_load_progress)
self.thumb_loader_thread.start()
This can take a long time and load a lot of thumbnails for large collections. I would like to ditch that thread entirely and instead, load the thumbnails lazily/more efficiently. The QListWidget gets wrapped inside of a QScrollArea. Is there any means, either via the QListWidget widget or within the QListWidgetItem, to receive events when a particular list item comes into view and opportunistically load the thumbnail at that point? I've looked through the API and can't seem to find anything.
This is a repost of my previous post which I deleted. In the first post I asked if someone knows a way of creating bookmarks with PyQT5, however, originally, I did not post what issues I am having now, with my way. I have 3 toolbars, in my web browser.
For additional buttons as - exit, minimize, maximize and etc.
For navigation in the web.
Bookmarks all are created with the instance of QToolBar().
Bookmarks toolbar code
self.bookmark_bar = QToolBar('Bookmark')
self.bookmark_bar.setIconSize(QSize(12, 12))
self.bookmark_bar.setMovable(False)
self.addToolBar(self.bookmark_bar)
After the tool bars are created the buttons are added, as there is a lot of code there I will show just the final result as a screenshot and the last few lines of code in init function.
The last few lines of code:
self.add_new_tab(QUrl('http://www.google.com'), 'Home Page')
self.bookmarks_load()
self.show()
self.setWindowTitle('Tempo')
Everything is fine with the Web Browser itself, as everything is working except the bookmarks. The line of code 'bookmarks_load()' load the bookmarks from a .txt file. As for now in the .txt document the bookmark is youtube.com (does not matter which link is used). The bookmarks functioncs:
Add a website to a bookmark.txt
def Bookmark(self):
try:
qurl = QUrl(self.urlbar.text())
print('Here we are using the QUrl toString method: %s ---> Type: %s' % (qurl.toString(), type(qurl)))
url = qurl.toString()
print('Here we are storing the new string to a new variable: %s ---> Type: %s' % (url, type(url)))
b = open(os.path.join('bookmarks', 'bookmarks.txt'), "wb")
self.bookmarks_write = pickle.dump(url, b)
b.close()
except:
print("Crash - Bookmarks not stored")
self.bookmark_btn.setText("★")
Load bookmarks from the document file.
def bookmarks_load(self):
try:
bookmarks_open = open(os.path.join('bookmarks', 'bookmarks.txt'), 'rb')
self.bookmarks_write = pickle.load(bookmarks_open)
bookmarks_open.close()
self.create_bookmarks()
except:
bookmarks_open = open(os.path.join('bookmarks', 'bookmarks.txt'), 'wb')
bookmarks = 'http://www.stackoverflow.com'
self.bookmarks_write = pickle.dump(bookmarks, bookmarks_open)
bookmarks_open.close()
self.create_bookmarks()
print('Crash - Did not load bookmarks')
Create the bookmarks 'button', when pressed open a new tab with that website.
def create_bookmarks(self):
bookmark_list = []
try:
for book in self.bookmarks_write.split():
print(book)
bookmark_list.append(book)
print(bookmark_list)
except:
print("Something went wrong with the list")
try:
button = QAction(QIcon(os.path.join('images', 'tab_icon.PNG')), 'Open bookmark', self)
button.triggered.connect(self.add_new_tab(QUrl(bookmark_list[0]), 'New'))
self.bookmark_bar.addAction(button)
except:
print('Button is causing the error')
Now this is the part which I start having issues. If I remove the - triggered.connect line and I do not add any functionality to that 'button' everything launches and works, without any errors. It stores and can load the bookmarks. However, when that line is added, it crashes and the button is not created(The app does not exit as there is an except statement which catches the error - which pyqt does not show what error it is.). This is the add_new_tab() function:
def add_new_tab(self, qurl=None, label='Blank'):
if qurl is None:
qurl = QUrl('')
web_browser = QWebEngineView()
web_browser.setUrl(qurl)
web_browser.adjustSize()
index = self.tabs.addTab(web_browser, label)
self.tabs.setCurrentIndex(index)
web_browser.urlChanged.connect(lambda qurl, web_browser=web_browser:
self.update_urlbar(qurl, web_browser))
web_browser.loadFinished.connect(lambda _, i=index, web_browser=web_browser:
self.tabs.setTabText(i, web_browser.page().title()))
Originally I open tabs by 'double clicking' on the tab bar with this function:
def tab_open_doubleclick(self, index):
if index == -1:
self.add_new_tab()
As you can see on the trigger - I do pass the link as QUrl and I do add a test label. The problem I have it somehow does not want to work and I can not find why, because Python PyQT5 does not show an error, it just closes with the return code.
Screenshots as explanation:
Link not added to a bookmarks.txt
Link added to bookmarks.txt
Pickle does dump the link in .txt
The "except" statement is run, while the triggered.connect line is not commented out.
The app continues to run, but the loaded bookmarks buttons are not there.
In the following example I show how to add the functionality of the BookMark, for it use QSettings to store the data but for this you must use setOrganizationName(), setOrganizationDomain() and setApplicationName(). Also I added the functionality of obtaining the title of the web page. The BookMarkToolBar class has the bookmarkClicked method that returns the url associated with the BookMark and the title.
from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
class BookMarkToolBar(QtWidgets.QToolBar):
bookmarkClicked = QtCore.pyqtSignal(QtCore.QUrl, str)
def __init__(self, parent=None):
super(BookMarkToolBar, self).__init__(parent)
self.actionTriggered.connect(self.onActionTriggered)
self.bookmark_list = []
def setBoorkMarks(self, bookmarks):
for bookmark in bookmarks:
self.addBookMarkAction(bookmark["title"], bookmark["url"])
def addBookMarkAction(self, title, url):
bookmark = {"title": title, "url": url}
fm = QtGui.QFontMetrics(self.font())
if bookmark not in self.bookmark_list:
text = fm.elidedText(title, QtCore.Qt.ElideRight, 150)
action = self.addAction(text)
action.setData(bookmark)
self.bookmark_list.append(bookmark)
#QtCore.pyqtSlot(QtWidgets.QAction)
def onActionTriggered(self, action):
bookmark = action.data()
self.bookmarkClicked.emit(bookmark["url"], bookmark["title"])
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.defaultUrl = QtCore.QUrl()
self.tabs = QtWidgets.QTabWidget()
self.tabs.setTabsClosable(True)
self.setCentralWidget(self.tabs)
self.urlLe = QtWidgets.QLineEdit()
self.urlLe.returnPressed.connect(self.onReturnPressed)
self.favoriteButton = QtWidgets.QToolButton()
self.favoriteButton.setIcon(QtGui.QIcon("images/star.png"))
self.favoriteButton.clicked.connect(self.addFavoriteClicked)
toolbar = self.addToolBar("")
toolbar.addWidget(self.urlLe)
toolbar.addWidget(self.favoriteButton)
self.addToolBarBreak()
self.bookmarkToolbar = BookMarkToolBar()
self.bookmarkToolbar.bookmarkClicked.connect(self.add_new_tab)
self.addToolBar(self.bookmarkToolbar)
self.readSettings()
def onReturnPressed(self):
self.tabs.currentWidget().setUrl(QtCore.QUrl.fromUserInput(self.urlLe.text()))
def addFavoriteClicked(self):
loop = QtCore.QEventLoop()
def callback(resp):
setattr(self, "title", resp)
loop.quit()
web_browser = self.tabs.currentWidget()
web_browser.page().runJavaScript("(function() { return document.title;})();", callback)
url = web_browser.url()
loop.exec_()
self.bookmarkToolbar.addBookMarkAction(getattr(self, "title"), url)
def add_new_tab(self, qurl=QtCore.QUrl(), label='Blank'):
web_browser = QtWebEngineWidgets.QWebEngineView()
web_browser.setUrl(qurl)
web_browser.adjustSize()
web_browser.urlChanged.connect(self.updateUlrLe)
index = self.tabs.addTab(web_browser, label)
self.tabs.setCurrentIndex(index)
self.urlLe.setText(qurl.toString())
def updateUlrLe(self, url):
self.urlLe.setText(url.toDisplayString())
def readSettings(self):
setting = QtCore.QSettings()
self.defaultUrl = setting.value("defaultUrl", QtCore.QUrl('http://www.google.com'))
self.add_new_tab(self.defaultUrl, 'Home Page')
self.bookmarkToolbar.setBoorkMarks(setting.value("bookmarks", []))
def saveSettins(self):
settings = QtCore.QSettings()
settings.setValue("defaultUrl", self.defaultUrl)
settings.setValue("bookmarks", self.bookmarkToolbar.bookmark_list)
def closeEvent(self, event):
self.saveSettins()
super(MainWindow, self).closeEvent(event)
if __name__ == '__main__':
import sys
QtCore.QCoreApplication.setOrganizationName("eyllanesc.org")
QtCore.QCoreApplication.setOrganizationDomain("www.eyllanesc.com")
QtCore.QCoreApplication.setApplicationName("MyApp")
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
I have been trying to create a way to stream a youtube url (preferably audio only, although this doesn't matter too much) right from python code. I have tried numerous things but none really seem to work. So far, I am able to search for videos or playlists using youtube data api, grab the first video or playlist and pass it into pafy to get different streaming urls. Does anyone know of a way to play youtube audio/video through python without downloading the video first? I would think it is possible with a cmd line tool such as mplayer or vlc using the sub process to pop open a cmd for the cmd line and pass in the url, but I am stuck. Any help is needed. Please! Here is my following code:
import argparse
import pafy
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
DEVELOPER_KEY = 'DEVELOPER KEY'
YOUTUBE_API_SERVICE_NAME = 'youtube'
YOUTUBE_API_VERSION = 'v3'
def pafy_video(video_id):
url = 'https://www.youtube.com/watch?v={0}'.format(video_id)
vid = pafy.new(url)
def pafy_playlist(playlist_id)
url = "https://www.youtube.com/playlist?list={0}".format(playlist_id)
playlist = pafy.get_playlist(url)
def youtube_search(options):
youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=DEVELOPER_KEY)
search_response = youtube.search().list(
q='Hello world',
part='id,snippet',
maxResults=options.max_results
).execute()
videos = []
playlists = []
channels = []
for search_result in search_response.get('items', []):
if search_result['id']['kind'] == 'youtube#video':
videos.append('%s' % (search_result['id']['videoId']))
elif search_result['id']['kind'] == 'youtube#channel':
channels.append('%s' % (search_result['id']['channelId']))
elif search_result['id']['kind'] == 'youtube#playlist':
playlists.append('%s' % (search_result['id']['playlistId']))
if videos:
print('Videos:{0}'.format(videos))
pafy_video(videos[0])
elif playlists:
print('Playlists:{0}'.format(playlists))
pafy_video(playlists[0])
#https://www.youtube.com/watch?v=rOU4YiuaxAM
#url = 'https://www.youtube.com/watch?v={0}'.format(videos[0])
#print(url)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--q', help='Search term', default='Google')
parser.add_argument('--max-results', help='Max results', default=3)
args = parser.parse_args()
youtube_search(args)
Tldr; I would like to stream a youtube video (using the url or id) straight from python code without downloading the video first
Thank you!
pafy according to its documentation do not list playing media directly (at least I didn't find any).
However we can use it to get correct url, and then use player such as vlc to play directly without downloading.
You can download vlc from here
First we get correct / best URL from youtube using pafy
import pafy
import vlc
url = "https://www.youtube.com/watch?v=bMt47wvK6u0"
video = pafy.new(url)
best = video.getbest()
playurl = best.url
Over here playurl is best URL to play.
Then we use VLC to play it.
Instance = vlc.Instance()
player = Instance.media_player_new()
Media = Instance.media_new(playurl)
Media.get_mrl()
player.set_media(Media)
player.play()
This will open a window with no controls (play/pause/stop etc).
You can run these command on the repr window or at python prompt (depending upon how you are using it)
You will need to build one accordingly using vlc commands such as
>>> player.pause() #-- to pause video
>>> player.play() #-- resume paused video. On older versions,
# this function was called resume
>>> player.stop() #-- to stop/end video
The best way to accomplish this is to use mpv with youtube-dl (a light weight media player that comes with a command line console which allows you to stream any type of video/audio codecs via copy and pasting url to mpv console (works for cmd and terminal). Usage: path\to\mpv https://your/video/audioURL.com/
First import these modules - install via pip install bs4 requests
import re, requests, subprocess, urllib.parse, urllib.request
from bs4 import BeautifulSoup
Then create a variable to store music title of your choice
music_name = "Linkin Park Numb"
query_string = urllib.parse.urlencode({"search_query": music_name})
The goal here to use this search query to extract the output title and link of the first video result. We can then endcode the youtube url "https://www.youtube.com/results?search_query=" with the identifier using urllib (eg., "https://www.youtube.com/results?search_query=linkin+park+numb")
formatUrl = urllib.request.urlopen("https://www.youtube.com/results?" + query_string)
This right here re.findall(r"watch\?v=(\S{11})" views the 11 character identifier of all the video results from our query
search_results = re.findall(r"watch\?v=(\S{11})", formatUrl.read().decode())
After decoding the content, we can extract the full url by concatenating the main youtube url with the 11 character identifier
clip = requests.get("https://www.youtube.com/watch?v=" + "{}".format(search_results[0]))
clip2 = "https://www.youtube.com/watch?v=" + "{}".format(search_results[0])
print(clip2)
Output ==> https://www.youtube.com/watch?v=kXYiU_JCYtU
To further inspect the content, we can use beautifulsoup to scrape the extact title of the video
inspect = BeautifulSoup(clip.content, "html.parser")
yt_title = inspect.find_all("meta", property="og:title")
for concatMusic1 in yt_title:
pass
print(concatMusic1['content'])
Output ==> Numb (Official Video) - Linkin Park
Finally, to play our music we input this to the command line
subprocess.Popen(
"start /b " + "path\\to\\mpv.exe" + clip2 + " --no-video --loop=inf --input-ipc-server=\\\\.\\pipe\\mpv-pipe > output.txt",
shell=True)
Full script
===================================================================
import re, requests, subprocess, urllib.parse, urllib.request
from bs4 import BeautifulSoup
music_name = "Linkin Park Numb"
query_string = urllib.parse.urlencode({"search_query": music_name})
formatUrl = urllib.request.urlopen("https://www.youtube.com/results?" + query_string)
search_results = re.findall(r"watch\?v=(\S{11})", formatUrl.read().decode())
clip = requests.get("https://www.youtube.com/watch?v=" + "{}".format(search_results[0]))
clip2 = "https://www.youtube.com/watch?v=" + "{}".format(search_results[0])
inspect = BeautifulSoup(clip.content, "html.parser")
yt_title = inspect.find_all("meta", property="og:title")
for concatMusic1 in yt_title:
pass
print(concatMusic1['content'])
subprocess.Popen(
"start /b " + "path\\to\\mpv.exe " + clip2 + " --no-video --loop=inf --input-ipc-server=\\\\.\\pipe\\mpv-pipe > output.txt",
shell=True)
# Alternatively, you can do this for simplicity sake:
# subprocess.Popen("start /b " + "path\\to\\mpv.exe " + clip2 + "--no-video", shell=True)
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()
I'm building a gui front-end for a project I've been working on, and I'm having trouble figuring out this specific aspect of PyQT. Long ago there was a post that I had that I think would've solved the problem but I can't quite find it.
Regardless, I have the following files, main.py and playlistcreator.py respectively:
(main.py)
import getpass
import playlistcreator
import os
import sys
import sip
sip.setapi('QString', 2)
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
# Window class
class Window(QWidget):
def __init__(self): # constructor for Window (passes "self" -aka Window- to __init__ to initialize it)
super(Window, self).__init__() # inherits from QMainWindow
self.setGeometry(50, 50, 300, 150) # Set window dimensions
self.setWindowTitle("Google Music Playlist Transfer") # Set window title
self.setWindowIcon(QIcon('gmusic.png')) # Set window icon
self.login = QWidget() # login widget ("front page")
self.processing = QWidget() # on successful login - being processing data
self.file_inputs = QWidget() # after processing, ask user where they would like to store data
# call functions
self.loginUI()
self.processingUI()
self.file_inputsUI()
# create stacked widget and add layouts to stack
self.Stack = QStackedWidget(self)
self.Stack.addWidget(self.login)
self.Stack.addWidget(self.processing)
self.Stack.addWidget(self.file_inputs)
main_box = QHBoxLayout(self)
main_box.addWidget(self.Stack)
self.setLayout(main_box)
self.show()
def loginUI(self):
# Set email field
self.email = QLineEdit()
self.email.setMaxLength(110)
self.email.setAlignment(Qt.AlignLeft)
# Set password field
self.pwd = QLineEdit()
self.pwd.setAlignment(Qt.AlignLeft)
self.pwd.setEchoMode(QLineEdit.Password)
# Form layout
form_layout = QFormLayout()
form_layout.addRow("Email: ", self.email)
form_layout.addRow("Password: ", self.pwd)
# Login button
self.login_btn = QPushButton("Login", self) # login button
self.login_btn.clicked.connect(self.process_login) # tell button what to do
self.login_btn.resize(self.login_btn.sizeHint())
# Quit button
self.quit_btn = QPushButton("Exit", self) # exit button
self.quit_btn.clicked.connect(self.close_application) # tell button what to do
# Error label layout
self.error_layout = QHBoxLayout()
# Button box layout
button_box = QHBoxLayout()
button_box.addStretch(1)
button_box.addWidget(self.login_btn)
button_box.addWidget(self.quit_btn)
# input layout (main layout for "home")
input_box = QVBoxLayout()
input_box.addLayout(self.error_layout)
input_box.addLayout(form_layout)
input_box.addLayout(button_box)
self.login.setLayout(input_box)
def processingUI(self):
# setup layout
layout = QHBoxLayout()
# alert user that we're grabbing track data
self.progress_label = QLabel("Grabbing tracks from Google Music. . .")
self.progress_label.setStyleSheet("color: rgb(0, 100, 0);")
layout.addWidget(self.progress_label)
# Get users list of "thumbs up" tracks from Google Music
# set layout
favorite_tracks = self.get_tracks
self.processing.setLayout(layout)
def file_inputsUI(self):
layout = QFormLayout()
# Set text field for directory
self.dir_textbox = QTextEdit(self)
# Add textbox to layout
layout.addRow("Music Directory: ", self.dir_textbox)
self.file_inputs.setLayout(layout)
def close_application(self):
confirm = QMessageBox.question(self, 'Exit Confirmation',
"Are you sure you want to exit?",
QMessageBox.Yes | QMessageBox.No)
if confirm == QMessageBox.Yes:
sys.exit()
else:
pass
def process_login(self):
email_input = str(self.email.text())
pwd_input = str(self.pwd.text())
login_successful = playlistcreator.login(email_input, pwd_input)
if login_successful:
self.Stack.setCurrentIndex(1)
else:
self.error_label = QLabel("Please check your email/password!")
self.error_label.setStyleSheet("color: rgb(255, 0, 0);")
self.error_layout.addWidget(self.error_label)
def get_tracks(self):
tracks = playlistcreator.get_favorite_tracks()
print("You have ", len(tracks), " favorite tracks!")
return tracks
# def open_dir_dialog(self):
# directory = QFileDialog.getExistingDirectory(self, 'Select USB Drive Location')
# self.myTextBox.setText(fileName)
def main():
# Create an PyQT5 application object.
app = QApplication(sys.argv)
GUI = Window()
sys.exit(app.exec_())
# # Input Music Directory
# music_dir = input("Please put the path to your music folder: ")
# list_of_paths = playlistcreator.findmp3(music_dir, favorite_tracks)
# # Input playlist file save location
# playlist_path = input("Where would you like to save the playlist?: ")
# playlist_name = input("What would you like to name the playlist? ")
# playlist_name += ".m3u8"
# # Testing appending file extension to string
# playlistcreator.addtoplaylist(list_of_paths, playlist_path, playlist_name)
# playlistcreator.logout()
main()
(playlistcreator.py)
import os
from gmusicapi import Mobileclient
from mutagen.easyid3 import EasyID3
api = Mobileclient()
songCount = 0
favorite_files_path = []
logged_in = False
def login(email, password):
library = []
if "#" not in email:
email += "#gmail.com"
logged_in = api.login(email, password, Mobileclient.FROM_MAC_ADDRESS)
return logged_in
def get_favorite_tracks():
print("Getting songs from Google Music. . .")
library = api.get_all_songs()
print("There are", len(library), "items in your music library")
good_songs = [song for song in library if song['rating'] == '5']
print("You have", len(good_songs), "favorite tracks!")
return good_songs
def logout():
api.logout()
def findmp3(rootFolder, favoriteTracks):
print("Searching for favorite tracks. . .")
for directory, subdirectory, files in os.walk(rootFolder, topdown=False): # for all files in directory
global songCount
global favorite_files_path
for mp3 in files: # for files in
if mp3.endswith(".mp3"): # if file ends with .mp3
file_path = os.path.join(directory, mp3) # concatenate to create full path
try:
file_tags = EasyID3(file_path) # grab file tags from mp3 file
except:
file_tags = EasyID3() # create tags if none are found
file_tags.save(file_path) # save file to have new tags
if "title" in file_tags:
fileTitle = file_tags["title"][0] # TIT2 = corresponding tag in mutagen for song title
else:
fileTitle = "None"
if "artist" in file_tags:
fileArtist = file_tags["artist"][0] # TPE = corresponding tag in mutagen for artist
else:
fileArtist = "None"
if "album" in file_tags:
fileAlbum = file_tags["album"][0] # TALB = corresponding tag in mutagen for album
else:
fileAlbum = "None"
for track in favoriteTracks:
gMusicTitle = track["title"]
gMusicArtist = track["artist"]
gMusicAlbum = track["album"]
if fileTitle in gMusicTitle and fileArtist in gMusicArtist and fileAlbum in gMusicAlbum: # if file tags (artist, album, title) match gMusic tags
songCount += 1
if songCount == 1:
print("Found 1 song")
else:
print("Found", songCount, "songs") # print updated count of found tracks
favorite_files_path.append(file_path) # add all found files to a list (via their paths)
break # break out of "for track in favorite tracks"
return favorite_files_path
def addtoplaylist(paths, playlist_path, playlist_name):
# Open file (or create if it does not exist)
# change to given directory
try:
os.chdir(playlist_path)
except Exception as err:
print(err)
# open file - if it does not exist, create it
with open(playlist_name, 'a+', encoding="UTF-8") as playlistFile:
print("Adding tracks...", end="")
for track in paths:
print(".", end="")
playlistFile.write(track + '\n')
playlistFile.close()
The problem I've been running into is when the user logs in they hit the processingUI window (which states that the program is grabbing all their tracks from Google) but it doesn't actually seem to be running the get_tracks() method (which is what actually gets the user's tracks from Google), as the program hangs there and doesn't do anything.
I've tested the program without a GUI using straight command line, and it works flawlessly, but I'm not sure what the issue is with the GUI - do I need to thread the execution to start when the proper "window" is brought to the front?
I'm trying to read webpages using pyqt. I need to call a method multiple times with different URLs. I am currently using code similar to: http://blog.sitescraper.net/2010/06/scraping-javascript-webpages-in-python.html#comment-form
However when I try I get seg faults. Any suggestions welcome.
import sys
from time import clock
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4.QtWebKit import *
from PyQt4.QtNetwork import *
class Render(QWebPage):
def __init__(self):
self.app = QApplication(sys.argv)
QWebPage.__init__(self)
self.networkAccessManager().finished.connect(self.handleEnd)
self.loadFinished.connect(self._loadFinished)
self.mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
self.mainFrame().setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff)
def loadURL(self, url):
self.mainFrame().load(QUrl(url))
self.app.exec_()
def savePageImage (self, width, height, Imagefile):
pageSize = self.mainFrame().contentsSize();
if width == 0:
pageWidth = pageSize.width()
else:
pageWidth = width
if height == 0:
pageHeight = pageSize.height()
else:
pageHeight = height
self.setViewportSize(QSize(pageWidth, pageHeight))
Img = QImage(self.viewportSize(), QImage.Format_ARGB32)
painter = QPainter(Img)
self.mainFrame().render(painter)
painter.end()
Img.save(Imagefile)
def _loadFinished(self, result):
print "load finish"
self.frame = self.mainFrame()
self.returnVal = result
self.app.quit()
def handleEnd (self, reply):
# get first http code and disconnect
# could add filter to listen relevant responses
self.httpcode = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
self.networkAccessManager().finished.disconnect(self.handleEnd)
jsrurl = 'http://www.w3resource.com/javascript/document-alert-confirm/four.html'
badurl='something.or.other'
badhttp = 'http://eclecticself.com/test2.html'
testurl = 'http://www.nydailynews.com/entertainment/index.html'
testurl2 = 'http://www.palmbeachpost.com/'
testurl3 = 'http://www.nydailynews.com/news/politics/2011/08/03/2011-08-03_pat_buchanan_downplays_controversy_after_calling_president_obama_your_boy_to_rev.html'
url = testurl
start = clock()
r = Render()
r.loadURL(url)
html = r.frame.toHtml()
elapsed = clock() - start
print elapsed
if (r.returnVal == True):
if (r.httpcode.toInt()[0] != 404):
#print html.toUtf8()
start = clock()
r.savePageImage(1024, 0, "pageSnapshot.png")
elapsed = clock() - start
print elapsed
else:
print 'page not found'
else:
print 'badurl'
s = Render()
s.loadURL(jsrurl)
html = s.frame.toHtml()
elapsed = clock() - start
print elapsed
if (s.returnVal == True):
if (s.httpcode.toInt()[0] != 404):
print html.toUtf8()
start = clock()
s.savePageImage(1024, 0, "pageSnapshot.png")
elapsed = clock() - start
print elapsed
else:
print 'page not found'
else:
print 'badurl'
PyQt is often forgetting to keep references to objects. Workarounds:
Try to use PySide instead of PyQt, it is easy, since the API is almost completely the same as PyQt. I would try PySide first, it might solve your problem immediately or at least make it predictable and reproducible.
Try to keep references to all the Qt objects you are using and remove those references when you're done with the objects. You can also try to explicitly close them or navigate to "about:blank" before going to the next Web page.
It usually helps. If not, then you need to narrow it down as utdemir suggested it above. Debugging usually not help, since such issues are often timing related as well. Logging without an output buffer usually helps you get closer to the source of the problem.
I'm with you in soul, such issues are hard to track down!