Created
July 27, 2020 20:45
-
-
Save tavianator/34ee000b7df1634ebeb90866e80a4962 to your computer and use it in GitHub Desktop.
partymix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
partymix_ui.py: partymix.ui | |
pyuic4 partymix.ui >partymix_ui.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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