diff --git a/horsydist/resources/gui/Ui_DelSongs.py b/horsydist/resources/gui/Ui_DelSongs.py new file mode 100644 index 0000000..10e058f --- /dev/null +++ b/horsydist/resources/gui/Ui_DelSongs.py @@ -0,0 +1,32 @@ +from PyQt5 import QtCore, QtWidgets + + +class Ui_DelSongs(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(300, 510) + MainWindow.setMinimumSize(QtCore.QSize(300, 510)) + MainWindow.setMaximumSize(QtCore.QSize(300, 510)) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.listWidget = QtWidgets.QListWidget(self.centralwidget) + self.listWidget.setGeometry(QtCore.QRect(0, 0, 300, 461)) + self.listWidget.setObjectName("listWidget") + self.delButton = QtWidgets.QPushButton(self.centralwidget) + self.delButton.setGeometry(QtCore.QRect(0, 460, 300, 23)) + self.delButton.setObjectName("delButton") + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 300, 21)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Songs manager")) + self.delButton.setText(_translate("MainWindow", "Delete selected song")) diff --git a/horsydist/resources/gui/Ui_ExtendedMenu.py b/horsydist/resources/gui/Ui_ExtendedMenu.py new file mode 100644 index 0000000..67fd580 --- /dev/null +++ b/horsydist/resources/gui/Ui_ExtendedMenu.py @@ -0,0 +1,36 @@ +from PyQt5 import QtCore, QtWidgets + + +class Ui_ExtendedMenu(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(151, 74) + MainWindow.setMinimumSize(QtCore.QSize(151, 74)) + MainWindow.setMaximumSize(QtCore.QSize(151, 74)) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.prosearchButton = QtWidgets.QPushButton(self.centralwidget) + self.prosearchButton.setGeometry(QtCore.QRect(0, 0, 150, 50)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.prosearchButton.sizePolicy().hasHeightForWidth()) + self.prosearchButton.setSizePolicy(sizePolicy) + self.prosearchButton.setMaximumSize(QtCore.QSize(150, 50)) + self.prosearchButton.setObjectName("prosearchButton") + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 151, 21)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Extended functions")) + self.prosearchButton.setText(_translate("MainWindow", "Pro search")) \ No newline at end of file diff --git a/horsydist/resources/gui/Ui_MainBuild.py b/horsydist/resources/gui/Ui_MainBuild.py new file mode 100644 index 0000000..e7e5185 --- /dev/null +++ b/horsydist/resources/gui/Ui_MainBuild.py @@ -0,0 +1,42 @@ +from PyQt5 import QtCore, QtWidgets +import os + + +class Ui_MainBuild(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(161, 157) + MainWindow.setMinimumSize(QtCore.QSize(161, 157)) + MainWindow.setMaximumSize(QtCore.QSize(161, 157)) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.listWidget = QtWidgets.QListWidget(self.centralwidget) + self.listWidget.setGeometry(QtCore.QRect(0, 0, 161, 111)) + self.listWidget.setObjectName("listWidget") + self.pushButton = QtWidgets.QPushButton(self.centralwidget) + self.pushButton.setGeometry(QtCore.QRect(0, 110, 161, 23)) + self.pushButton.setObjectName("pushButton") + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 161, 21)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + for file in os.listdir(os.getcwd()): + if file == "MultiMate_Player.py": + self.listWidget.addItem(file) + elif file == "MultiMate_Player.pyw": + self.listWidget.addItem(file) + elif file == "MultiMate_Player.exe": + self.listWidget.addItem(file) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Choose main build")) + self.pushButton.setText(_translate("MainWindow", "Choose main build")) \ No newline at end of file diff --git a/horsydist/resources/gui/Ui_MainWindow.py b/horsydist/resources/gui/Ui_MainWindow.py new file mode 100644 index 0000000..2776eef --- /dev/null +++ b/horsydist/resources/gui/Ui_MainWindow.py @@ -0,0 +1,215 @@ +from PyQt5 import QtCore, QtGui, QtWidgets +import platform +import os + + +class Ui_MainWindow(QtWidgets.QMainWindow): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(801, 580) + MainWindow.setMinimumSize(QtCore.QSize(801, 580)) + MainWindow.setMaximumSize(QtCore.QSize(801, 580)) + MainWindow.setWindowIcon(QtGui.QIcon('resources/img/MultiMate.ico')) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.timeline = QtWidgets.QSlider(self.centralwidget) + self.timeline.setGeometry(QtCore.QRect(10, 490, 781, 22)) + self.timeline.setPageStep(1) + self.timeline.setOrientation(QtCore.Qt.Horizontal) + self.timeline.setObjectName("timeline") + self.timeline.setMaximum(1000) + + self.playpausebutton = QtWidgets.QPushButton(self.centralwidget) + self.playpausebutton.setEnabled(True) + self.playpausebutton.setGeometry(QtCore.QRect(390, 520, 40, 40)) + font = QtGui.QFont() + font.setKerning(True) + self.playpausebutton.setFont(font) + self.playpausebutton.setStyleSheet("background-color: rgba(10, 0, 0, 0);\n" + "") + self.playpausebutton.setText("") + self.playpausebutton.setObjectName("playpausebutton") + self.playpausePicture = QtWidgets.QLabel(self.centralwidget) + self.playpausePicture.setGeometry(QtCore.QRect(390, 520, 40, 40)) + self.playpausePicture.setText("") + self.playpausePicture.setPixmap(QtGui.QPixmap("resources/img/MultiMate40x40.png")) + self.playpausePicture.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) + self.playpausePicture.setObjectName("playpausePicture") + + self.prevbutton = QtWidgets.QPushButton(self.centralwidget) + self.prevbutton.setEnabled(True) + self.prevbutton.setGeometry(QtCore.QRect(340, 520, 40, 40)) + font = QtGui.QFont() + font.setKerning(True) + self.prevbutton.setFont(font) + self.prevbutton.setStyleSheet("background-color: rgba(10, 0, 0, 0);\n" + "") + self.prevbutton.setText("") + self.prevbutton.setObjectName("prevbutton") + self.prevPicture = QtWidgets.QLabel(self.centralwidget) + self.prevPicture.setGeometry(QtCore.QRect(340, 520, 40, 40)) + self.prevPicture.setText("") + self.prevPicture.setPixmap(QtGui.QPixmap("resources/img/prev.png")) + self.prevPicture.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) + self.prevPicture.setObjectName("prevPicture") + self.nextbutton = QtWidgets.QPushButton(self.centralwidget) + self.nextbutton.setEnabled(True) + self.nextbutton.setGeometry(QtCore.QRect(440, 520, 40, 40)) + font = QtGui.QFont() + font.setKerning(True) + self.nextbutton.setFont(font) + self.nextbutton.setStyleSheet("background-color: rgba(10, 0, 0, 0);\n" + "") + self.nextbutton.setText("") + self.nextbutton.setObjectName("nextbutton") + self.nextPicture = QtWidgets.QLabel(self.centralwidget) + self.nextPicture.setGeometry(QtCore.QRect(440, 520, 40, 40)) + self.nextPicture.setText("") + self.nextPicture.setPixmap(QtGui.QPixmap("resources/img/next.png")) + self.nextPicture.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) + self.nextPicture.setObjectName("nextPicture") + + self.speedBox = QtWidgets.QDoubleSpinBox(self.centralwidget) + self.speedBox.setGeometry(QtCore.QRect(10, 515, 62, 22)) + self.speedBox.setDecimals(1) + self.speedBox.setProperty("value", 1.0) + self.speedBox.setSingleStep(0.2) + self.speedBox.setObjectName("speedBox") + self.timenow = QtWidgets.QTextEdit(self.centralwidget) + self.timenow.setGeometry(QtCore.QRect(713, 515, 81, 23)) + self.timenow.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) + self.timenow.setObjectName("timenow") + self.speedTextLabel = QtWidgets.QLabel(self.centralwidget) + self.speedTextLabel.setGeometry(QtCore.QRect(25, 536, 47, 13)) + self.speedTextLabel.setObjectName("speedTextLabel") + self.timeTextLabel = QtWidgets.QLabel(self.centralwidget) + self.timeTextLabel.setGeometry(QtCore.QRect(740, 536, 47, 13)) + self.timeTextLabel.setObjectName("timeTextLabel") + self.volumeDial = QtWidgets.QDial(self.centralwidget) + self.volumeDial.setGeometry(QtCore.QRect(719, 0, 81, 81)) + self.volumeDial.setStyleSheet("") + self.volumeDial.setMinimum(1) + self.volumeDial.setMaximum(100) + self.volumeDial.setPageStep(1) + self.volumeDial.setProperty("value", 100) + self.volumeDial.setObjectName("volumeDial") + self.VolDialBG = QtWidgets.QLabel(self.centralwidget) + self.VolDialBG.setGeometry(QtCore.QRect(720, 0, 81, 81)) + self.VolDialBG.setText("") + self.VolDialBG.setPixmap(QtGui.QPixmap("resources/img/MultiMate80x80.png")) + self.VolDialBG.setObjectName("VolDialBG") + self.playlistsComboBox = QtWidgets.QComboBox(self.centralwidget) + self.playlistsComboBox.setGeometry(QtCore.QRect(0, 0, 231, 22)) + self.playlistsComboBox.setCurrentText("") + self.playlistsComboBox.setDuplicatesEnabled(True) + self.playlistsComboBox.setObjectName("playlistsComboBox") + for playlist in os.listdir(os.getcwd()): + self.playlistsComboBox.addItem(playlist) + self.nowPlaying = QtWidgets.QTextBrowser(self.centralwidget) + self.nowPlaying.setGeometry(QtCore.QRect(10, 460, 781, 23)) + self.nowPlaying.setObjectName("nowPlaying") + self.openPlaylistButton = QtWidgets.QPushButton(self.centralwidget) + self.openPlaylistButton.setGeometry(QtCore.QRect(230, 0, 75, 23)) + self.openPlaylistButton.setObjectName("openPlaylistButton") + + self.songList = QtWidgets.QListWidget(self.centralwidget) + self.songList.setGeometry(QtCore.QRect(0, 43, 301, 411)) + self.songList.setObjectName("songList") + + self.TextAllSongs = QtWidgets.QTextEdit(self.centralwidget) + self.TextAllSongs.setGeometry(QtCore.QRect(0, 20, 301, 23)) + self.TextAllSongs.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) + self.TextAllSongs.setObjectName("TextAllSongs") + self.toFindName = QtWidgets.QTextEdit(self.centralwidget) + self.toFindName.setGeometry(QtCore.QRect(310, 0, 331, 23)) + self.toFindName.setObjectName("toFindName") + self.findSongButton = QtWidgets.QPushButton(self.centralwidget) + self.findSongButton.setGeometry(QtCore.QRect(640, 0, 75, 23)) + self.findSongButton.setObjectName("findSongButton") + self.foundSongs = QtWidgets.QComboBox(self.centralwidget) + self.foundSongs.setGeometry(QtCore.QRect(310, 30, 331, 22)) + self.foundSongs.setObjectName("foundSongs") + self.addThisSongButton = QtWidgets.QPushButton(self.centralwidget) + self.addThisSongButton.setGeometry(QtCore.QRect(640, 30, 75, 23)) + self.addThisSongButton.setObjectName("addThisSongButton") + self.mixButton = QtWidgets.QPushButton(self.centralwidget) + self.mixButton.setGeometry(QtCore.QRect(720, 90, 75, 41)) + self.mixButton.setObjectName("mixButton") + + self.playlistSettingsButton = QtWidgets.QPushButton(self.centralwidget) + self.playlistSettingsButton.setGeometry(QtCore.QRect(720, 140, 75, 51)) + self.playlistSettingsButton.setObjectName("playlistSettingsButton") + + self.settingsButton = QtWidgets.QPushButton(self.centralwidget) + self.settingsButton.setGeometry(QtCore.QRect(720, 200, 75, 45)) + self.settingsButton.setObjectName("settingsButton") + + self.extendedFunctButton = QtWidgets.QPushButton(self.centralwidget) + self.extendedFunctButton.setGeometry(QtCore.QRect(720, 260, 75, 45)) + self.extendedFunctButton.setObjectName("extendedFunctButton") + + if platform.system() == "Darwin": # for MacOS + self.videoframe = QtWidgets.QMacCocoaViewContainer(self.centralwidget) + else: + self.videoframe = QtWidgets.QFrame(self.centralwidget) + self.videoframe.setGeometry(QtCore.QRect(310, 60, 394, 394)) + self.videoframe.setObjectName("videoframe") + self.timeline.raise_() + self.playpausePicture.raise_() + self.prevPicture.raise_() + self.nextPicture.raise_() + self.speedBox.raise_() + self.nextbutton.raise_() + self.prevbutton.raise_() + self.playpausebutton.raise_() + self.timenow.raise_() + self.speedTextLabel.raise_() + self.timeTextLabel.raise_() + self.VolDialBG.raise_() + self.volumeDial.raise_() + self.playlistsComboBox.raise_() + self.nowPlaying.raise_() + self.openPlaylistButton.raise_() + self.songList.raise_() + self.TextAllSongs.raise_() + self.toFindName.raise_() + self.findSongButton.raise_() + self.foundSongs.raise_() + self.addThisSongButton.raise_() + self.mixButton.raise_() + self.playlistSettingsButton.raise_() + self.videoframe.raise_() + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 801, 21)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + self.timer = QtCore.QTimer(self) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "MultiMate Player")) + self.speedBox.setPrefix(_translate("MainWindow", "x")) + self.speedTextLabel.setText(_translate("MainWindow", "Speed")) + self.timeTextLabel.setText(_translate("MainWindow", "Time")) + self.nowPlaying.setPlaceholderText(_translate("MainWindow", "Choose playlist and open it...")) + self.openPlaylistButton.setText(_translate("MainWindow", "Open playlist")) + self.TextAllSongs.setHtml(_translate("MainWindow", + "\n" + "
\n" + "Songs in playlist
")) + self.findSongButton.setText(_translate("MainWindow", "Find song")) + self.addThisSongButton.setText(_translate("MainWindow", "Add this song")) + self.mixButton.setText(_translate("MainWindow", "Mix")) + self.playlistSettingsButton.setText(_translate("MainWindow", "Playlist \nsettings")) + self.settingsButton.setText(_translate("MainWindow", "Settings")) + self.extendedFunctButton.setText(_translate("MainWindow", "Extended \n functions")) diff --git a/horsydist/resources/gui/Ui_PlaylistSettings.py b/horsydist/resources/gui/Ui_PlaylistSettings.py new file mode 100644 index 0000000..f3991e5 --- /dev/null +++ b/horsydist/resources/gui/Ui_PlaylistSettings.py @@ -0,0 +1,36 @@ +from PyQt5 import QtCore, QtWidgets + + +class Ui_PlaylistSettings(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(160, 97) + MainWindow.setMinimumSize(QtCore.QSize(160, 97)) + MainWindow.setMaximumSize(QtCore.QSize(160, 97)) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget) + self.verticalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 160, 80)) + self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.deletesongButton = QtWidgets.QPushButton(self.verticalLayoutWidget) + self.deletesongButton.setObjectName("deletesongButton") + self.verticalLayout.addWidget(self.deletesongButton) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 160, 21)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Playlist settings")) + self.deletesongButton.setText(_translate("MainWindow", "Manage songs in playlist")) diff --git a/horsydist/resources/gui/Ui_ProSearch.py b/horsydist/resources/gui/Ui_ProSearch.py new file mode 100644 index 0000000..2642469 --- /dev/null +++ b/horsydist/resources/gui/Ui_ProSearch.py @@ -0,0 +1,49 @@ +from PyQt5 import QtCore, QtWidgets + + +class Ui_ProSearch(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(750, 600) + MainWindow.setMinimumSize(QtCore.QSize(750, 600)) + MainWindow.setMaximumSize(QtCore.QSize(750, 600)) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.listWidget = QtWidgets.QListWidget(self.centralwidget) + self.listWidget.setGeometry(QtCore.QRect(0, 180, 750, 361)) + self.listWidget.setObjectName("listWidget") + self.lineEdit = QtWidgets.QLineEdit(self.centralwidget) + self.lineEdit.setGeometry(QtCore.QRect(160, 70, 440, 30)) + self.lineEdit.setStyleSheet("border-radius:\n" + " 1 px;") + self.lineEdit.setReadOnly(False) + self.lineEdit.setObjectName("lineEdit") + self.label = QtWidgets.QLabel(self.centralwidget) + self.label.setGeometry(QtCore.QRect(285, 20, 180, 31)) + self.label.setObjectName("label") + self.searchButton = QtWidgets.QPushButton(self.centralwidget) + self.searchButton.setGeometry(QtCore.QRect(300, 110, 150, 30)) + self.searchButton.setObjectName("searchButton") + self.pushButton = QtWidgets.QPushButton(self.centralwidget) + self.pushButton.setGeometry(QtCore.QRect(300, 550, 150, 31)) + self.pushButton.setObjectName("pushButton") + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 750, 21)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Pro search")) + self.lineEdit.setPlaceholderText(_translate("MainWindow", "Never gonna give you up")) + self.label.setText(_translate("MainWindow", + "Advanced search
")) + self.searchButton.setText(_translate("MainWindow", "Search")) + self.pushButton.setText(_translate("MainWindow", "Add selected")) diff --git a/horsydist/resources/gui/Ui_RPCsettings.py b/horsydist/resources/gui/Ui_RPCsettings.py new file mode 100644 index 0000000..ee3dd4e --- /dev/null +++ b/horsydist/resources/gui/Ui_RPCsettings.py @@ -0,0 +1,39 @@ +from PyQt5 import QtCore, QtWidgets +from resources.lib.config import config + + +class Ui_RPCsettings(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(160, 100) + MainWindow.setMinimumSize(QtCore.QSize(160, 100)) + MainWindow.setMaximumSize(QtCore.QSize(160, 100)) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget) + self.verticalLayoutWidget.setGeometry(QtCore.QRect(9, 0, 151, 80)) + self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.ShowRPCcheckBox = QtWidgets.QCheckBox(self.verticalLayoutWidget) + self.ShowRPCcheckBox.setObjectName("ShowRPCcheckBox") + self.verticalLayout.addWidget(self.ShowRPCcheckBox) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 160, 21)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.ShowRPCcheckBox.setChecked(config['showrpc']) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "RPC settings")) + self.ShowRPCcheckBox.setText(_translate("MainWindow", "Show RPC")) \ No newline at end of file diff --git a/horsydist/resources/gui/Ui_Settings.py b/horsydist/resources/gui/Ui_Settings.py new file mode 100644 index 0000000..d807676 --- /dev/null +++ b/horsydist/resources/gui/Ui_Settings.py @@ -0,0 +1,48 @@ +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Settings(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(160, 100) + MainWindow.setMinimumSize(QtCore.QSize(160, 100)) + MainWindow.setMaximumSize(QtCore.QSize(160, 100)) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget) + self.verticalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 160, 80)) + self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + + self.updateButton = QtWidgets.QPushButton(self.verticalLayoutWidget) + self.updateButton.setObjectName("updateButton") + self.verticalLayout.addWidget(self.updateButton) + + self.appBuildButton = QtWidgets.QPushButton(self.verticalLayoutWidget) + self.appBuildButton.setObjectName("appBuildButton") + self.verticalLayout.addWidget(self.appBuildButton) + + self.RPCButton = QtWidgets.QPushButton(self.verticalLayoutWidget) + self.RPCButton.setObjectName("RPCButton") + self.verticalLayout.addWidget(self.RPCButton) + + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 160, 21)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Settings")) + self.updateButton.setText(_translate("MainWindow", "Upgrade player")) + self.appBuildButton.setText(_translate("MainWindow", "Choose main build")) + self.RPCButton.setText(_translate("MainWindow", "Discord RPC settings")) diff --git a/horsydist/resources/gui/Ui_Updater.py b/horsydist/resources/gui/Ui_Updater.py new file mode 100644 index 0000000..5970d6b --- /dev/null +++ b/horsydist/resources/gui/Ui_Updater.py @@ -0,0 +1,37 @@ +from PyQt5 import QtCore, QtWidgets + + +class Ui_Updater(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(160, 97) + MainWindow.setMinimumSize(QtCore.QSize(160, 97)) + MainWindow.setMaximumSize(QtCore.QSize(160, 97)) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget) + self.verticalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 160, 80)) + self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + + self.updateButton = QtWidgets.QPushButton(self.verticalLayoutWidget) + self.updateButton.setObjectName("updateButton") + self.verticalLayout.addWidget(self.updateButton) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 160, 21)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Updater")) + self.updateButton.setText(_translate("MainWindow", "Download newest")) diff --git a/horsydist/resources/gui/gui.py b/horsydist/resources/gui/gui.py new file mode 100644 index 0000000..a17d5fe --- /dev/null +++ b/horsydist/resources/gui/gui.py @@ -0,0 +1,57 @@ +import sys +from PyQt5 import QtWidgets +from resources.gui.Ui_DelSongs import Ui_DelSongs +from resources.gui.Ui_ExtendedMenu import Ui_ExtendedMenu +from resources.gui.Ui_MainBuild import Ui_MainBuild +from resources.gui.Ui_MainWindow import Ui_MainWindow +from resources.gui.Ui_PlaylistSettings import Ui_PlaylistSettings +from resources.gui.Ui_ProSearch import Ui_ProSearch +from resources.gui.Ui_RPCsettings import Ui_RPCsettings +from resources.gui.Ui_Settings import Ui_Settings +from resources.gui.Ui_Updater import Ui_Updater + + +app = QtWidgets.QApplication(sys.argv) +MainWindow = QtWidgets.QMainWindow() +ui = Ui_MainWindow() +ui.setupUi(MainWindow) + +appPlSet = QtWidgets.QApplication(sys.argv) +MainWindowPlSet = QtWidgets.QMainWindow() +uiPlSet = Ui_PlaylistSettings() +uiPlSet.setupUi(MainWindowPlSet) + +appDelS = QtWidgets.QApplication(sys.argv) +MainWindowDelS = QtWidgets.QMainWindow() +uiDelS = Ui_DelSongs() +uiDelS.setupUi(MainWindowDelS) + +appSet = QtWidgets.QApplication(sys.argv) +MainWindowSet = QtWidgets.QMainWindow() +uiSet = Ui_Settings() +uiSet.setupUi(MainWindowSet) + +appUpd = QtWidgets.QApplication(sys.argv) +MainWindowUpd = QtWidgets.QMainWindow() +uiUpd = Ui_Updater() +uiUpd.setupUi(MainWindowUpd) + +appExt = QtWidgets.QApplication(sys.argv) +MainWindowExt = QtWidgets.QMainWindow() +uiExt = Ui_ExtendedMenu() +uiExt.setupUi(MainWindowExt) + +appPSearch = QtWidgets.QApplication(sys.argv) +MainWindowPSearch = QtWidgets.QMainWindow() +uiPSearch = Ui_ProSearch() +uiPSearch.setupUi(MainWindowPSearch) + +appRPCSet = QtWidgets.QApplication(sys.argv) +MainWindowRPCSet = QtWidgets.QMainWindow() +uiRPCSet = Ui_RPCsettings() +uiRPCSet.setupUi(MainWindowRPCSet) + +appMainBuild = QtWidgets.QApplication(sys.argv) +MainWindowMainBuild = QtWidgets.QMainWindow() +uiMainBuild = Ui_MainBuild() +uiMainBuild.setupUi(MainWindowMainBuild) diff --git a/horsydist/resources/MultiMate.ico b/horsydist/resources/img/MultiMate.ico similarity index 100% rename from horsydist/resources/MultiMate.ico rename to horsydist/resources/img/MultiMate.ico diff --git a/horsydist/resources/MultiMate.png b/horsydist/resources/img/MultiMate.png similarity index 100% rename from horsydist/resources/MultiMate.png rename to horsydist/resources/img/MultiMate.png diff --git a/horsydist/resources/MultiMate40x40.png b/horsydist/resources/img/MultiMate40x40.png similarity index 100% rename from horsydist/resources/MultiMate40x40.png rename to horsydist/resources/img/MultiMate40x40.png diff --git a/horsydist/resources/MultiMate80x80.png b/horsydist/resources/img/MultiMate80x80.png similarity index 100% rename from horsydist/resources/MultiMate80x80.png rename to horsydist/resources/img/MultiMate80x80.png diff --git a/horsydist/resources/hardplaybutton.png b/horsydist/resources/img/hardplaybutton.png similarity index 100% rename from horsydist/resources/hardplaybutton.png rename to horsydist/resources/img/hardplaybutton.png diff --git a/horsydist/resources/hardstopbutton.png b/horsydist/resources/img/hardstopbutton.png similarity index 100% rename from horsydist/resources/hardstopbutton.png rename to horsydist/resources/img/hardstopbutton.png diff --git a/horsydist/resources/next.png b/horsydist/resources/img/next.png similarity index 100% rename from horsydist/resources/next.png rename to horsydist/resources/img/next.png diff --git a/horsydist/resources/prev.png b/horsydist/resources/img/prev.png similarity index 100% rename from horsydist/resources/prev.png rename to horsydist/resources/img/prev.png diff --git a/horsydist/resources/lib/config.py b/horsydist/resources/lib/config.py new file mode 100644 index 0000000..f7dc555 --- /dev/null +++ b/horsydist/resources/lib/config.py @@ -0,0 +1,32 @@ +import os +import json + +configfile = "resources/cfg.cfg" + +if not os.path.isfile(configfile): + cfgwrite = open(configfile, 'w+') + empty = {} + json.dump(empty, cfgwrite, indent=3) + cfgwrite.close() + +with open(configfile) as cfgread: + config = json.load(cfgread) + +try: + config['mainbuild'] +except: + if "MultiMate_Player.py" in os.listdir(os.getcwd()): + config['mainbuild'] = 'MultiMate_Player.py' + elif "MultiMate_Player.pyw" in os.listdir(os.getcwd()): + config['mainbuild'] = 'MultiMate_Player.pyw' + elif "MultiMate_Player.exe" in os.listdir(os.getcwd()): + config['mainbuild'] = 'MultiMate_Player.exe' + with open(configfile, 'w+') as cfgwrite: + json.dump(config, cfgwrite, indent=3) + +try: + config['showrpc'] +except: + config['showrpc'] = True + with open(configfile, 'w+') as cfgwrite: + json.dump(config, cfgwrite, indent=3) diff --git a/horsydist/resources/lib/console.py b/horsydist/resources/lib/console.py new file mode 100644 index 0000000..d2807c7 --- /dev/null +++ b/horsydist/resources/lib/console.py @@ -0,0 +1,5 @@ +import os + + +def cls(): + os.system('cls' if os.name == 'nt' else 'clear') diff --git a/horsydist/resources/lib/rpc.py b/horsydist/resources/lib/rpc.py new file mode 100644 index 0000000..7249975 --- /dev/null +++ b/horsydist/resources/lib/rpc.py @@ -0,0 +1,13 @@ +from pypresence import Presence +from resources.lib.config import config +import time + + +rpc = Presence("896669007342633000") +try: + rpc.connect() + if config['showrpc']: + rpc.update(details="Just started app", state="Nothing is beeing listened...", large_image="multimate", + start=int(time.time())) +except: + pass diff --git a/horsydist/resources/lib/ytsearch.py b/horsydist/resources/lib/ytsearch.py new file mode 100644 index 0000000..ffd72c6 --- /dev/null +++ b/horsydist/resources/lib/ytsearch.py @@ -0,0 +1,65 @@ +import json +import requests +import urllib.parse + + +class YoutubeSearch: + def __init__(self, search_terms: str, max_results=None): + self.search_terms = search_terms + self.max_results = max_results + self.videos = self._search() + + def _search(self): + encoded_search = urllib.parse.quote_plus(self.search_terms) + BASE_URL = "https://youtube.com" + url = f"{BASE_URL}/search?q={encoded_search}" + response = requests.get(url).text + while "ytInitialData" not in response: + response = requests.get(url).text + results = self._parse_html(response) + if self.max_results is not None and len(results) > self.max_results: + return results[: self.max_results] + return results + + def _parse_html(self, response): + results = [] + start = ( + response.index("ytInitialData") + + len("ytInitialData") + + 3 + ) + end = response.index("};", start) + 1 + json_str = response[start:end] + data = json.loads(json_str) + + videos = data["contents"]["twoColumnSearchResultsRenderer"]["primaryContents"][ + "sectionListRenderer" + ]["contents"][0]["itemSectionRenderer"]["contents"] + + for video in videos: + res = {} + if "videoRenderer" in video.keys(): + video_data = video.get("videoRenderer", {}) + res["id"] = video_data.get("videoId", None) + res["thumbnails"] = [thumb.get("url", None) for thumb in video_data.get("thumbnail", {}).get("thumbnails", [{}]) ] + res["title"] = video_data.get("title", {}).get("runs", [[{}]])[0].get("text", None) + res["long_desc"] = video_data.get("descriptionSnippet", {}).get("runs", [{}])[0].get("text", None) + res["channel"] = video_data.get("longBylineText", {}).get("runs", [[{}]])[0].get("text", None) + res["duration"] = video_data.get("lengthText", {}).get("simpleText", 0) + res["views"] = video_data.get("viewCountText", {}).get("simpleText", 0) + res["publish_time"] = video_data.get("publishedTimeText", {}).get("simpleText", 0) + res["url_suffix"] = video_data.get("navigationEndpoint", {}).get("commandMetadata", {}).get("webCommandMetadata", {}).get("url", None) + results.append(res) + return results + + def to_dict(self, clear_cache=True): + result = self.videos + if clear_cache: + self.videos = "" + return result + + def to_json(self, clear_cache=True): + result = json.dumps({"videos": self.videos}) + if clear_cache: + self.videos = "" + return result diff --git a/horsydist/resources/pafy_fix/backend_shared.py b/horsydist/resources/pafy_fix/backend_shared.py new file mode 100644 index 0000000..ecfd4a6 --- /dev/null +++ b/horsydist/resources/pafy_fix/backend_shared.py @@ -0,0 +1,558 @@ +import os +import re +import sys +import time +import logging +import subprocess + +if sys.version_info[:2] >= (3, 0): + # pylint: disable=E0611,F0401,I0011 + from urllib.request import urlopen, build_opener + from urllib.error import HTTPError, URLError + from urllib.parse import parse_qs, urlparse + uni, pyver = str, 3 + +else: + from urllib2 import urlopen, build_opener, HTTPError, URLError + from urlparse import parse_qs, urlparse + uni, pyver = unicode, 2 + +early_py_version = sys.version_info[:2] < (2, 7) + +dbg = logging.debug + + +def extract_video_id(url): + """ Extract the video id from a url, return video id as str. """ + idregx = re.compile(r'[\w-]{11}$') + url = str(url).strip() + + if idregx.match(url): + return url # ID of video + + if '://' not in url: + url = '//' + url + parsedurl = urlparse(url) + if parsedurl.netloc in ('youtube.com', 'www.youtube.com', 'm.youtube.com', 'gaming.youtube.com'): + query = parse_qs(parsedurl.query) + if 'v' in query and idregx.match(query['v'][0]): + return query['v'][0] + elif parsedurl.netloc in ('youtu.be', 'www.youtu.be'): + vidid = parsedurl.path.split('/')[-1] if parsedurl.path else '' + if idregx.match(vidid): + return vidid + + err = "Need 11 character video id or the URL of the video. Got %s" + raise ValueError(err % url) + + +class BasePafy(object): + + """ Class to represent a YouTube video. """ + + def __init__(self, video_url, basic=True, gdata=False, + size=False, callback=None, ydl_opts=None): + """ Set initial values. """ + self.version = 1 + self.videoid = extract_video_id(video_url) + self.watchv_url = "http://www.youtube.com/watch?v=%s" % self.videoid + + self.callback = callback + self._have_basic = False + self._have_gdata = False + + self._description = None + self._likes = None + self._dislikes = None + self._category = None + self._published = None + self._username = None + + self._streams = [] + self._oggstreams = [] + self._m4astreams = [] + self._allstreams = [] + self._videostreams = [] + self._audiostreams = [] + + self._title = None + self._rating = None + self._length = None + self._author = None + self._duration = None + self._keywords = None + self._bigthumb = None + self._viewcount = None + self._bigthumbhd = None + self._bestthumb = None + self._mix_pl = None + self.expiry = None + + if basic: + self._fetch_basic() + + if gdata: + self._fetch_gdata() + + if size: + for s in self.allstreams: + # pylint: disable=W0104 + s.get_filesize() + + + def _fetch_basic(self): + """ Fetch basic data and streams. """ + raise NotImplementedError + + + def _fetch_gdata(self): + """ Extract gdata values, fetch gdata if necessary. """ + raise NotImplementedError + + + def _process_streams(self): + """ Create Stream object lists from internal stream maps. """ + raise NotImplementedError + + + def __repr__(self): + """ Print video metadata. Return utf8 string. """ + if self._have_basic: + info = [("Title", self.title), + ("Author", self.author), + ("ID", self.videoid), + ("Duration", self.duration), + ("Rating", self.rating), + ("Views", self.viewcount)] + + nfo = "\n".join(["%s: %s" % i for i in info]) + + else: + nfo = "Pafy object: %s [%s]" % (self.videoid, + self.title[:45] + "..") + + return nfo.encode("utf8", "replace") if pyver == 2 else nfo + + @property + def streams(self): + """ The streams for a video. Returns list.""" + if not self._streams: + self._process_streams() + + return self._streams + + @property + def allstreams(self): + """ All stream types for a video. Returns list. """ + if not self._allstreams: + self._process_streams() + + return self._allstreams + + @property + def audiostreams(self): + """ Return a list of audio Stream objects. """ + if not self._audiostreams: + self._process_streams() + + return self._audiostreams + + @property + def videostreams(self): + """ The video streams for a video. Returns list. """ + if not self._videostreams: + self._process_streams() + + return self._videostreams + + @property + def oggstreams(self): + """ Return a list of ogg encoded Stream objects. """ + if not self._oggstreams: + self._process_streams() + + return self._oggstreams + + @property + def m4astreams(self): + """ Return a list of m4a encoded Stream objects. """ + if not self._m4astreams: + self._process_streams() + + return self._m4astreams + + @property + def title(self): + """ Return YouTube video title as a string. """ + if not self._title: + self._fetch_basic() + + return self._title + + @property + def author(self): + """ The uploader of the video. Returns str. """ + if not self._author: + self._fetch_basic() + + return self._author + + @property + def rating(self): + """ Rating for a video. Returns float. """ + if not self._rating: + self._fetch_basic() + + return self._rating + + @property + def length(self): + """ Length of a video in seconds. Returns int. """ + if not self._length: + self._fetch_basic() + + return self._length + + @property + def viewcount(self): + """ Number of views for a video. Returns int. """ + if not self._viewcount: + self._fetch_basic() + + return self._viewcount + + @property + def bigthumb(self): + """ Large thumbnail image url. Returns str. """ + self._fetch_basic() + return self._bigthumb + + @property + def bigthumbhd(self): + """ Extra large thumbnail image url. Returns str. """ + self._fetch_basic() + return self._bigthumbhd + + @property + def duration(self): + """ Duration of a video (HH:MM:SS). Returns str. """ + if not self._length: + self._fetch_basic() + + self._duration = time.strftime('%H:%M:%S', time.gmtime(self._length)) + self._duration = uni(self._duration) + + return self._duration + + @property + def keywords(self): + """ Return keywords as list of str. """ + if not self._keywords: + self._fetch_gdata() + + return self._keywords + + @property + def category(self): + """ YouTube category of the video. Returns string. """ + if not self._category: + self._fetch_gdata() + + return self._category + + @property + def description(self): + """ Description of the video. Returns string. """ + if not self._description: + self._fetch_gdata() + + return self._description + + @property + def username(self): + """ Return the username of the uploader. """ + if not self._username: + self._fetch_basic() + + return self._username + + @property + def published(self): + """ The upload date and time of the video. Returns string. """ + if not self._published: + self._fetch_gdata() + + return self._published.replace(".000Z", "").replace("T", " ") + + @property + def likes(self): + """ The number of likes for the video. Returns int. """ + if not self._likes: + self._fetch_basic() + + return self._likes + + @property + def dislikes(self): + """ The number of dislikes for the video. Returns int. """ + if not self._dislikes: + self._fetch_basic() + + return self._dislikes + + def _getbest(self, preftype="any", ftypestrict=True, vidonly=False): + """ + Return the highest resolution video available. + + Select from video-only streams if vidonly is True + """ + streams = self.videostreams if vidonly else self.streams + + if not streams: + return None + + def _sortkey(x, key3d=0, keyres=0, keyftype=0): + """ sort function for max(). """ + key3d = "3D" not in x.resolution + keyres = int(x.resolution.split("x")[0]) + keyftype = preftype == x.extension + strict = (key3d, keyftype, keyres) + nonstrict = (key3d, keyres, keyftype) + return strict if ftypestrict else nonstrict + + r = max(streams, key=_sortkey) + + if ftypestrict and preftype != "any" and r.extension != preftype: + return None + + else: + return r + + def getbestvideo(self, preftype="any", ftypestrict=True): + """ + Return the best resolution video-only stream. + + set ftypestrict to False to return a non-preferred format if that + has a higher resolution + """ + return self._getbest(preftype, ftypestrict, vidonly=True) + + def getbest(self, preftype="any", ftypestrict=True): + """ + Return the highest resolution video+audio stream. + + set ftypestrict to False to return a non-preferred format if that + has a higher resolution + """ + return self._getbest(preftype, ftypestrict, vidonly=False) + + def getbestaudio(self, preftype="any", ftypestrict=True): + """ Return the highest bitrate audio Stream object.""" + if not self.audiostreams: + return None + + def _sortkey(x, keybitrate=0, keyftype=0): + """ Sort function for max(). """ + keybitrate = int(x.rawbitrate) + keyftype = preftype == x.extension + strict, nonstrict = (keyftype, keybitrate), (keybitrate, keyftype) + return strict if ftypestrict else nonstrict + + r = max(self.audiostreams, key=_sortkey) + + if ftypestrict and preftype != "any" and r.extension != preftype: + return None + + else: + return r + + @classmethod + def _content_available(cls, url): + try: + response = urlopen(url) + except HTTPError: + return False + else: + return response.getcode() < 300 + + def getbestthumb(self): + """ Return the best available thumbnail.""" + if not self._bestthumb: + part_url = "http://i.ytimg.com/vi/%s/" % self.videoid + # Thumbnail resolution sorted in descending order + thumbs = ("maxresdefault.jpg", + "sddefault.jpg", + "hqdefault.jpg", + "mqdefault.jpg", + "default.jpg") + for thumb in thumbs: + url = part_url + thumb + if self._content_available(url): + return url + + return self._bestthumb + + def populate_from_playlist(self, pl_data): + """ Populate Pafy object with items fetched from playlist data. """ + self._title = pl_data.get("title") + self._author = pl_data.get("author") + self._length = int(pl_data.get("length_seconds", 0)) + self._rating = pl_data.get("rating", 0.0) + self._viewcount = "".join(re.findall(r"\d", "{0}".format(pl_data.get("views", "0")))) + self._viewcount = int(self._viewcount) + self._description = pl_data.get("description") + + +class BaseStream(object): + + """ YouTube video stream class. """ + + def __init__(self, parent): + """ Set initial values. """ + self._itag = None + self._mediatype = None + self._threed = None + self._rawbitrate = None + self._resolution = None + self._quality = None + self._dimensions = None + self._bitrate = None + self._extension = None + self.encrypted = None + self._notes = None + self._url = None + self._rawurl = None + + self._parent = parent + self._filename = None + self._fsize = None + self._active = False + + + @property + def rawbitrate(self): + """ Return raw bitrate value. """ + return self._rawbitrate + + @property + def threed(self): + """ Return bool, True if stream is 3D. """ + return self._threed + + @property + def itag(self): + """ Return itag value of stream. """ + return self._itag + + @property + def resolution(self): + """ Return resolution of stream as str. 0x0 if audio. """ + return self._resolution + + @property + def dimensions(self): + """ Return dimensions of stream as tuple. (0, 0) if audio. """ + return self._dimensions + + @property + def quality(self): + """ Return quality of stream (bitrate or resolution). + + eg, 128k or 640x480 (str) + """ + return self._quality + + @property + def title(self): + """ Return YouTube video title as a string. """ + return self._parent.title + + @property + def extension(self): + """ Return appropriate file extension for stream (str). + + Possible values are: 3gp, m4a, m4v, mp4, webm, ogg + """ + return self._extension + + @property + def bitrate(self): + """ Return bitrate of an audio stream. """ + return self._bitrate + + @property + def mediatype(self): + """ Return mediatype string (normal, audio or video). + + (normal means a stream containing both video and audio.) + """ + return self._mediatype + + @property + def notes(self): + """ Return additional notes regarding the stream format. """ + return self._notes + + @property + def url(self): + """ Return the url, decrypt if required. """ + return self._url + + @property + def url_https(self): + """ Return https url. """ + return self.url.replace("http://", "https://") + + def __repr__(self): + """ Return string representation. """ + out = "%s:%s@%s" % (self.mediatype, self.extension, self.quality) + return out + + def cancel(self): + """ Cancel an active download. """ + if self._active: + self._active = False + return True + +def remux(infile, outfile, quiet=False, muxer="ffmpeg"): + """ Remux audio. """ + muxer = muxer if isinstance(muxer, str) else "ffmpeg" + + for tool in set([muxer, "ffmpeg", "avconv"]): + cmd = [tool, "-y", "-i", infile, "-acodec", "copy", "-vn", outfile] + + try: + with open(os.devnull, "w") as devnull: + subprocess.call(cmd, stdout=devnull, stderr=subprocess.STDOUT) + + except OSError: + dbg("Failed to remux audio using %s", tool) + + else: + os.unlink(infile) + dbg("remuxed audio file using %s" % tool) + + if not quiet: + sys.stdout.write("\nAudio remuxed.\n") + + break + + else: + logging.warning("audio remux failed") + os.rename(infile, outfile) + + +def get_size_done(bytesdone, progress): + _progress_dict = {'KB': 1024.0, 'MB': 1048576.0, 'GB': 1073741824.0} + return round(bytesdone/_progress_dict.get(progress, 1.0), 2) + + +def get_status_string(progress): + status_string = (' {:,} ' + progress + ' [{:.2%}] received. Rate: [{:4.0f} ' + 'KB/s]. ETA: [{:.0f} secs]') + + if early_py_version: + status_string = (' {0:} ' + progress + ' [{1:.2%}] received. Rate:' + ' [{2:4.0f} KB/s]. ETA: [{3:.0f} secs]') + + return status_string diff --git a/horsydist/resources/pafy_fix/backend_youtube_dl.py b/horsydist/resources/pafy_fix/backend_youtube_dl.py new file mode 100644 index 0000000..fa8f501 --- /dev/null +++ b/horsydist/resources/pafy_fix/backend_youtube_dl.py @@ -0,0 +1,183 @@ +import sys +import time +import logging +import os +import subprocess + +if sys.version_info[:2] >= (3, 0): + # pylint: disable=E0611,F0401,I0011 + uni = str +else: + uni = unicode + +import youtube_dl + +from .backend_shared import BasePafy, BaseStream, remux, get_status_string, get_size_done + +dbg = logging.debug + + +early_py_version = sys.version_info[:2] < (2, 7) + + +class YtdlPafy(BasePafy): + def __init__(self, *args, **kwargs): + self._ydl_info = None + self._ydl_opts = {'quiet': True, 'prefer_insecure': False, 'no_warnings': True} + ydl_opts = kwargs.get("ydl_opts") + if ydl_opts: + self._ydl_opts.update(ydl_opts) + super(YtdlPafy, self).__init__(*args, **kwargs) + + def _fetch_basic(self): + """ Fetch basic data and streams. """ + if self._have_basic: + return + + with youtube_dl.YoutubeDL(self._ydl_opts) as ydl: + try: + self._ydl_info = ydl.extract_info(self.videoid, download=False) + # Turn into an IOError since that is what pafy previously raised + except youtube_dl.utils.DownloadError as e: + raise IOError(str(e).replace('YouTube said', 'Youtube says')) + + if self.callback: + self.callback("Fetched video info") + + self._title = self._ydl_info['title'] + self._author = self._ydl_info['uploader'] + self._rating = self._ydl_info['average_rating'] + self._length = self._ydl_info['duration'] + self._viewcount = self._ydl_info['view_count'] + self._username = self._ydl_info['uploader_id'] + self._category = self._ydl_info['categories'][0] if self._ydl_info['categories'] else '' + self._bestthumb = self._ydl_info['thumbnails'][0]['url'] + self._bigthumb = "http://i.ytimg.com/vi/%s/mqdefault.jpg" % self.videoid + self._bigthumbhd = "http://i.ytimg.com/vi/%s/hqdefault.jpg" % self.videoid + self.expiry = time.time() + 60 * 60 * 5 + + self._have_basic = True + + def _fetch_gdata(self): + """ Extract gdata values, fetch gdata if necessary. """ + if self._have_gdata: + return + + item = self._get_video_gdata(self.videoid)['items'][0] + snippet = item['snippet'] + self._published = uni(snippet['publishedAt']) + self._description = uni(snippet["description"]) + # Note: using snippet.get since some videos have no tags object + self._keywords = [uni(i) for i in snippet.get('tags', ())] + self._have_gdata = True + + def _process_streams(self): + """ Create Stream object lists from internal stream maps. """ + + if not self._have_basic: + self._fetch_basic() + + allstreams = [YtdlStream(z, self) for z in self._ydl_info['formats']] + self._streams = [i for i in allstreams if i.mediatype == 'normal'] + self._audiostreams = [i for i in allstreams if i.mediatype == 'audio'] + self._videostreams = [i for i in allstreams if i.mediatype == 'video'] + self._m4astreams = [i for i in allstreams if i.extension == 'm4a'] + self._oggstreams = [i for i in allstreams if i.extension == 'ogg'] + self._allstreams = allstreams + + +class YtdlStream(BaseStream): + def __init__(self, info, parent): + super(YtdlStream, self).__init__(parent) + self._itag = info['format_id'] + + if (info.get('acodec') != 'none' and + info.get('vcodec') == 'none'): + self._mediatype = 'audio' + elif (info.get('acodec') == 'none' and + info.get('vcodec') != 'none'): + self._mediatype = 'video' + else: + self._mediatype = 'normal' + + self._threed = info.get('format_note') == '3D' + self._rawbitrate = info.get('abr', 0) * 1024 + + height = info.get('height') or 0 + width = info.get('width') or 0 + self._resolution = str(width) + 'x' + str(height) + self._dimensions = width, height + self._bitrate = str(info.get('abr', 0)) + 'k' + self._quality = self._bitrate if self._mediatype == 'audio' else self._resolution + + self._extension = info['ext'] + self._notes = info.get('format_note') or '' + self._url = info.get('url') + + self._info = info + + def get_filesize(self): + """ Return filesize of the stream in bytes. Set member variable. """ + + # Faster method + if 'filesize' in self._info and self._info['filesize'] is not None: + return self._info['filesize'] + + # Fallback + return super(YtdlStream, self).get_filesize() + + def download(self, filepath="", quiet=False, progress="Bytes", + callback=None, meta=False, remux_audio=False): + + downloader = youtube_dl.downloader.http.HttpFD(ydl(), + {'http_chunk_size': 10485760}) + + progress_available = ["KB", "MB", "GB"] + if progress not in progress_available: + progress = "Bytes" + + status_string = get_status_string(progress) + + def progress_hook(s): + if s['status'] == 'downloading': + bytesdone = s['downloaded_bytes'] + total = s['total_bytes'] + if s.get('speed') is not None: + rate = s['speed'] / 1024 + else: + rate = 0 + if s.get('eta') is None: + eta = 0 + else: + eta = s['eta'] + + progress_stats = (get_size_done(bytesdone, progress), + bytesdone*1.0/total, rate, eta) + if not quiet: + status = status_string.format(*progress_stats) + sys.stdout.write("\r" + status + ' ' * 4 + "\r") + sys.stdout.flush() + + if callback: + callback(total, *progress_stats) + + downloader._progress_hooks = [progress_hook] + + if filepath and os.path.isdir(filepath): + filename = self.generate_filename(max_length=256 - len('.temp')) + filepath = os.path.join(filepath, filename) + + elif filepath: + pass + + else: + filepath = self.generate_filename(meta=meta, max_length=256 - len('.temp')) + + infodict = {'url': self.url} + + downloader.download(filepath, infodict) + print() + + if remux_audio and self.mediatype == "audio": + subprocess.run(['mv', filepath, filepath + '.temp']) + remux(filepath + '.temp', filepath, quiet=quiet, muxer=remux_audio) diff --git a/horsydist/resources/pafy_fix/pafy.py b/horsydist/resources/pafy_fix/pafy.py new file mode 100644 index 0000000..5575760 --- /dev/null +++ b/horsydist/resources/pafy_fix/pafy.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +Pafy = None + +def new(url, basic=True, gdata=False, size=False, + callback=None, ydl_opts=None): + global Pafy + if Pafy is None: + from .backend_youtube_dl import YtdlPafy as Pafy + + return Pafy(url, basic, gdata, size, callback, ydl_opts=ydl_opts)