Skip to content

Instantly share code, notes, and snippets.

@tavianator
Created July 27, 2020 20:45
Show Gist options
  • Save tavianator/34ee000b7df1634ebeb90866e80a4962 to your computer and use it in GitHub Desktop.
Save tavianator/34ee000b7df1634ebeb90866e80a4962 to your computer and use it in GitHub Desktop.
partymix
partymix_ui.py: partymix.ui
pyuic4 partymix.ui >partymix_ui.py
#!/usr/bin/env python3
import sys
import re
import os
import os.path
import time
import subprocess
import urllib.request
from html.parser import HTMLParser
from PyQt4.Qt import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *
from partymix_ui import *
from multiprocessing import *
class YouTubeHTMLParser(HTMLParser):
title = ""
_title_seen = False
def handle_starttag(self, tag, attrs):
if tag == "title":
self._title_seen = True
def handle_endtag(self, tag):
if self._title_seen:
self._title_seen = False
self.title = re.sub(r"\\n", "", self.title)
self.title = re.sub(r" +", " ", self.title)
self.title = re.sub(r"- *YouTube", "", self.title)
self.title = self.title.strip()
def handle_data(self, data):
if self._title_seen:
self.title += data
class PartyMix(QMainWindow):
"""Main window for the party mix!."""
def __init__(self, items, to_download):
self.items = items
self.ui_items = []
self.to_download = to_download
QMainWindow.__init__(self)
self.ui = Ui_PartyMix()
self.ui.setupUi(self)
self.ui.back.setIcon(QIcon.fromTheme("go-previous"))
self.ui.youtube.setIcon(QIcon.fromTheme("go-home"))
self.ui.google.setIcon(QIcon.fromTheme("edit-find"))
self.connect(self.ui.youtube, SIGNAL("clicked()"), self.goto_youtube)
self.connect(self.ui.google, SIGNAL("clicked()"), self.goto_google)
self.ui.webView.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
self.connect(self.ui.webView, SIGNAL("linkClicked(QUrl)"), self.link_clicked)
self.timer = QTimer()
self.connect(self.timer, SIGNAL("timeout()"), self.timer_action)
self.timer.start(1000)
def goto_youtube(self):
self.ui.webView.load(QUrl("http://www.youtube.com/"))
def goto_google(self):
self.ui.webView.load(QUrl("http://www.google.com/"))
def link_clicked(self, url):
urlstr = url.toString()
if re.match(r"http://www.youtube.com/watch.*", urlstr):
if QMessageBox.question(self, "Add to playlist?",
"Do you want to queue this song onto the playlist?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) == QMessageBox.Yes:
self.queue(urlstr)
else:
self.ui.webView.load(url)
def queue(self, url):
f = urllib.request.urlopen(url)
html = f.read()
f.close()
parser = YouTubeHTMLParser()
try:
parser.feed(str(html))
parser.close()
except:
pass
v = re.search(r"v=([^&]*)", url).group(1)
layout = self.ui.playlistContents.layout()
self.make_playlist_item(parser.title, v, layout)
self.items.append({
"v" : v,
"state" : "queued"
})
self.to_download.put(len(self.items) - 1)
def make_playlist_item(self, title, v, parent):
i = len(self.ui_items)
widget = QWidget()
widget.setObjectName(v)
item = QGridLayout(widget)
item.setObjectName(v + "Layout")
itemTitle = QLabel()
itemTitle.setText(title)
itemTitle.setObjectName(v + "Title")
itemTitle.setWordWrap(True)
item.addWidget(itemTitle, 0, 0, 1, 1)
item.setColumnStretch(0, 1)
def hide_playlist_item():
widget.hide()
item = self.items[i]
item["state"] = "deleted"
self.items[i] = item
itemDelete = QToolButton()
itemDelete.setText("X")
itemDelete.setIcon(QIcon.fromTheme("edit-delete"))
itemDelete.setObjectName(v + "Delete")
self.connect(itemDelete, SIGNAL("clicked()"), hide_playlist_item)
item.addWidget(itemDelete, 0, 1, 1, 1)
itemProgress = QProgressBar()
itemProgress.setObjectName(v + "Progress")
item.addWidget(itemProgress, 1, 0, 1, 2)
parent.insertWidget(parent.count() - 1, widget)
self.ui.playlist.repaint()
self.ui.playlist.ensureWidgetVisible(widget)
self.ui_items.append({
"widget" : widget,
"progress" : itemProgress,
"value" : 0
})
def timer_action(self):
for i in range(len(self.ui_items)):
item = self.items[i]
ui_item = self.ui_items[i]
if item["state"] == "downloading":
ui_item["value"] = ui_item["value"] + (100 - ui_item["value"])/10
ui_item["progress"].setValue(ui_item["value"])
elif item["state"] == "downloaded" or item["state"] == "playing":
ui_item["progress"].hide()
if item["state"] == "playing":
ui_item["widget"].setEnabled(False)
# Downloader process
def download(items, to_download, to_play):
while True:
i = to_download.get()
if i == "done":
return
item = items[i]
if item["state"] == "deleted":
continue
v = item["v"]
path = "./downloads/" + v
if not os.path.exists(path):
item["state"] = "downloading"
items[i] = item
url = "http://www.youtube.com/watch?v=" + v
formats = subprocess.getoutput("clive --query-formats " + url)
match = re.search(r"^.*(fmt.*) : .*$", formats, re.MULTILINE)
os.system("clive -f {} -O {} {}".format(match.group(1), path, url))
item["state"] = "downloaded"
items[i] = item
to_play.put(i)
# Player process
def play(items, to_play):
while True:
i = to_play.get()
if i == "done":
return
item = items[i]
if item["state"] == "deleted":
continue
v = item["v"]
item["state"] = "playing"
items[i] = item
os.system("vlc --one-instance --no-loop --playlist-enqueue ./downloads/" + v + " &")
metadata = ""
while v not in metadata:
metadata = subprocess.getoutput("qdbus org.mpris.vlc /Player org.freedesktop.MediaPlayer.GetMetadata")
time.sleep(1)
# Entry point
def main():
manager = Manager()
items = manager.list()
to_download = Queue()
to_play = Queue()
downloader = Process(target = download, args = (items, to_download, to_play))
downloader.start()
player = Process(target = play, args = (items, to_play))
player.start()
try:
app = QApplication(sys.argv)
main = PartyMix(items, to_download)
main.show()
# main.setWindowState(main.windowState() | Qt.WindowMaximized)
app.exec()
finally:
to_download.put("done")
to_play.put("done")
player.join()
downloader.join()
if __name__ == "__main__":
main()
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PartyMix</class>
<widget class="QMainWindow" name="PartyMix">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Party Mixer</string>
</property>
<widget class="QWidget" name="centralwidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="playlistBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Playlist</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QScrollArea" name="playlist">
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="playlistContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>177</width>
<height>550</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="searchBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>3</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Search</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="navbar" stretch="0,0,0,0">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="back">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string>Back</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="youtube">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>YouTube</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="google">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Google</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QWebView" name="webView">
<property name="url">
<url>
<string>http://www.youtube.com/</string>
</url>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>QWebView</class>
<extends>QWidget</extends>
<header>QtWebKit/QWebView</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>webView</tabstop>
<tabstop>back</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>back</sender>
<signal>clicked()</signal>
<receiver>webView</receiver>
<slot>back()</slot>
<hints>
<hint type="sourcelabel">
<x>295</x>
<y>58</y>
</hint>
<hint type="destinationlabel">
<x>356</x>
<y>146</y>
</hint>
</hints>
</connection>
</connections>
</ui>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment