#!/usr/bin/python ''' Hypervideo GUI ''' import sys from PyQt5.QtCore import ( QFile, QPoint, QRect, QSize, QStandardPaths, Qt, QProcess, QSettings ) from PyQt5.QtGui import QIcon, QFont, QClipboard from PyQt5.QtWidgets import ( QAction, QApplication, QComboBox, QFileDialog, QHBoxLayout, QLineEdit, QLabel, QMainWindow, QMessageBox, QProgressBar, QPushButton, QToolButton, QVBoxLayout, QWidget, ) # Debugging if len(sys.argv) == 2: if sys.argv[1] == '-v': DEBUG = 0 else: DEBUG = None else: DEBUG = None __version__ = '1.0.1' __license__ = 'GPL-3' __title__ = 'Simple Hypervideo Download GUI' class MainWindow(QMainWindow): def __init__(self): ''' Initial ''' super(MainWindow, self).__init__() self.hypervideo_bin = None self.url_catch = None self.out_folder_path = '/tmp/' self.settings = QSettings('YouTubeDL', 'YTDL') self.setAttribute(Qt.WA_DeleteOnClose) self.create_status_bar() pyfile = QStandardPaths.findExecutable("hypervideo") if not pyfile == "": debugging('Found executable: %s' % pyfile) self.hypervideo_bin = pyfile else: self.msgbox("hypervideo not found\nPlease install hypervideo") self.dflt_fms_menu_items = ['Video/Audio - Best Quality', 'Audio Only - Best Quality'] self.list = [] self.init_ui() def init_ui(self): ''' Initial UI ''' self.setWindowTitle(__title__) btnwidth = 155 self.cmd = None self.proc = QProcess(self) self.proc.started.connect(lambda: self.show_msg("Creating list")) self.proc.started.connect(lambda: self.fmts_btn.setEnabled(False)) self.proc.finished.connect(lambda: self.show_msg("List done!")) self.proc.finished.connect(self.process_finished) self.proc.finished.connect(lambda: self.fmts_btn.setEnabled(True)) self.proc.readyRead.connect(self.process_output) self.dl_proc = QProcess(self) self.dl_proc.setProcessChannelMode(QProcess.MergedChannels) self.dl_proc.started.connect(lambda: self.show_msg("Download started")) self.dl_proc.started.connect(lambda: self.dl_btn.setEnabled(False)) self.dl_proc.started.connect(lambda: self.fmts_btn.setEnabled(False)) self.dl_proc.started.connect(lambda: self.cxl_btn.setEnabled(True)) self.dl_proc.finished.connect(lambda: self.show_msg("Download done!")) self.dl_proc.finished.connect(lambda: self.dl_btn.setEnabled(True)) self.dl_proc.finished.connect(lambda: self.fmts_btn.setEnabled(True)) self.dl_proc.finished.connect(lambda: self.cxl_btn.setEnabled(False)) self.dl_proc.finished.connect(lambda: self.setWindowTitle(__title__)) self.dl_proc.readyRead.connect(self.dl_process_out) self.setGeometry(0, 0, 600, 250) self.setFixedSize(600, 250) self.setStyleSheet(ui_style_sheet(self)) self.setWindowIcon(QIcon.fromTheme("video-playlist")) # Menu main_menu = self.menuBar() file_menu = main_menu.addMenu('File') help_menu = main_menu.addMenu('Help') # Exit button exit_button = QAction('Exit', self) exit_button.setShortcut('Ctrl+Q') exit_button.setStatusTip('Exit application') exit_button.triggered.connect(self.close) # About button about_button = QAction('About', self) about_button.triggered.connect(self.on_button_clicked) # Adding buttons to Menu help_menu.addAction(about_button) file_menu.addAction(exit_button) # Path lbl_url = QLabel() lbl_url.setText("Insert URL/ID:") lbl_url.setAlignment(Qt.AlignRight) lbl_url.setFixedWidth(btnwidth) lbl_url.setAlignment(Qt.AlignCenter | Qt.AlignVCenter) self.lbl_url_path = QLineEdit() self.lbl_url_path.setPlaceholderText( 'https://invidio.us/watch?v=8SdPLG-_wtA') # Set up callback to update video formats when URL is changed self.lbl_url_path.textChanged.connect(self.reset_video_formats) hlayout = QHBoxLayout() hlayout.addWidget(lbl_url) hlayout.addWidget(self.lbl_url_path) # Output path btn_out_path = QToolButton() btn_out_path.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) btn_out_path.setText("Select Output Folder") btn_out_path.setFixedWidth(btnwidth) btn_out_path.clicked.connect(self.open_output_folder) self.lbl_out_path = QLineEdit() self.lbl_out_path.setPlaceholderText("Insert Output Folder Path") self.lbl_out_path.textChanged.connect(self.update_output_path) hlayout2 = QHBoxLayout() hlayout2.addWidget(btn_out_path) hlayout2.addWidget(self.lbl_out_path) # Hypervideo path btn_exec_path = QToolButton() btn_exec_path.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) btn_exec_path.setText("Select hypervideo") btn_exec_path.setFixedWidth(btnwidth) btn_exec_path.clicked.connect(self.select_hyper_dl) self.lbl_exec_path = QLineEdit(str(self.hypervideo_bin)) self.lbl_exec_path.textChanged.connect(self.update_hypervideo_path) self.lbl_exec_path.setPlaceholderText("Insert Path to Hypervideo") hlayout3 = QHBoxLayout() hlayout3.addWidget(btn_exec_path) hlayout3.addWidget(self.lbl_exec_path) # Download button self.dl_btn = QToolButton() self.dl_btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.dl_btn.setText("Download") self.dl_btn.clicked.connect(self.download_selected) self.dl_btn.setFixedWidth(btnwidth) self.dl_btn.setFixedHeight(32) # Get Formats button self.fmts_btn = QToolButton() self.fmts_btn.setText('Get Formats') self.fmts_btn.setFixedWidth(btnwidth) self.fmts_btn.setFixedHeight(32) self.fmts_btn.clicked.connect(self.fill_combo_formats) # Cancel button self.cxl_btn = QToolButton() self.cxl_btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.cxl_btn.setText("Cancel") self.cxl_btn.clicked.connect(self.cancel_download) self.cxl_btn.setEnabled(False) self.cxl_btn.setFixedWidth(btnwidth) self.cxl_btn.setFixedHeight(32) # Video FormatCombobox self.vfms_cmbox = QComboBox() self.populate_video_format_combobox(self.dflt_fms_menu_items) self.vfms_cmbox.setFixedHeight(26) # Progressbar self.pbar = QProgressBar() self.pbar.setFixedHeight(16) self.pbar.setMaximum(100) self.pbar.setMinimum(0) self.pbar.setValue(0) # Layout btn_layout = QHBoxLayout() btn_layout.addWidget(self.dl_btn) btn_layout.addWidget(self.fmts_btn) btn_layout.addWidget(self.cxl_btn) vlayout = QVBoxLayout() vlayout.addLayout(hlayout) vlayout.addLayout(hlayout2) vlayout.addLayout(hlayout3) vlayout.addWidget(self.vfms_cmbox) vlayout.addWidget(self.pbar) vlayout.addLayout(btn_layout) main_widget = QWidget() main_widget.setLayout(vlayout) self.setCentralWidget(main_widget) # Copy URL of clipboard self.clip = QApplication.clipboard() if self.clip.text().startswith("http"): self.lbl_url_path.setText(self.clip.text()) self.fill_combo_formats() self.read_settings() def on_button_clicked(self): """ Button about """ msg = QMessageBox() msg.setWindowTitle('About us') msg.setText( "

Written with Python3 and PyQt5
" "Version: %s
License: %s

" % (__version__, __license__)) msg.setIcon(QMessageBox.Information) self.show() msg.exec_() def closeEvent(self, event): '''Protected Function for PyQt5 gets called when the user closes the GUI. ''' self.write_settings() close = QMessageBox() close.setIcon(QMessageBox.Question) close.setWindowTitle('Exit') close.setText('You sure?') close.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel) close = close.exec() if close == QMessageBox.Yes: event.accept() else: event.ignore() def read_settings(self): ''' Read config ''' debugging('Reading settings') if self.settings.contains('geometry'): self.setGeometry(self.settings.value('geometry')) if self.settings.contains('outFolder'): self.lbl_out_path.setText(self.settings.value('outFolder')) def write_settings(self): ''' Save Settings ''' debugging('Writing settings') self.settings.setValue('outFolder', self.out_folder_path) self.settings.setValue('geometry', self.geometry()) def update_output_path(self): ''' Update Path Output ''' self.out_folder_path = self.lbl_out_path.text() self.show_msg( "Output path changed to: %s" % self.lbl_out_path.text()) def update_hypervideo_path(self): ''' Update Hypervideo Path Output ''' self.hypervideo_bin = self.lbl_exec_path.text() self.show_msg( "hypervideo path changed to: %s" % self.lbl_exec_path.text()) def show_msg(self, message): ''' Show Message in StatuBar ''' self.statusBar().showMessage(message, msecs=0) def select_hyper_dl(self): ''' Select hypervideo executable ''' file_name, _ = QFileDialog.getOpenFileName( self, "locate hypervideo", "/usr/bin/hypervideo", "exec Files (*)") debugging('Value of filename is %s' % file_name) if file_name is not None: self.lbl_exec_path.setText(file_name) self.hypervideo_bin = file_name def open_output_folder(self): ''' Open out folder path ''' dlg = QFileDialog() dlg.setFileMode(QFileDialog.Directory) d_path = dlg.getExistingDirectory() if d_path: self.lbl_out_path.setText(d_path) def populate_video_format_combobox(self, labels): '''Populate the video format combobox with video formats. Clear the previous labels. labels {list} -- list of strings representing the video format combobox options ''' self.vfms_cmbox.clear() for label in labels: self.vfms_cmbox.addItem(label) def reset_video_formats(self): ''' Clean video formast ''' idx = self.vfms_cmbox.currentIndex() self.populate_video_format_combobox(self.dflt_fms_menu_items) # Preserve combobox index if possible if idx > 1: self.vfms_cmbox.setCurrentIndex(0) else: self.vfms_cmbox.setCurrentIndex(idx) def fill_combo_formats(self): ''' Scan formats and Add item to combobox ''' self.vfms_cmbox.clear() if QFile.exists(self.hypervideo_bin): # Default options self.vfms_cmbox.addItems(self.dflt_fms_menu_items[0:2]) self.list = [] self.url_catch = self.lbl_url_path.text() if not self.lbl_url_path.text() == "": debugging('Scan Formats') self.proc.start(self.hypervideo_bin, ['-F', self.url_catch]) else: self.show_msg("URL empty") else: self.show_msg("hypervideo missing") def process_output(self): ''' Process out ''' try: output = str(self.proc.readAll(), encoding='utf8').rstrip() except TypeError: output = str(self.proc.readAll()).rstrip() self.list.append(output) def process_finished(self): ''' Process Finished ''' out = ','.join(self.list) out = out.partition("resolution note")[2] out = out.partition('\n')[2] mylist = out.rsplit('\n') debugging('Formats process finished with list: %s' % mylist) if mylist != ['']: self.vfms_cmbox.addItems(mylist) count = self.vfms_cmbox.count() self.vfms_cmbox.setCurrentIndex(count - 1) else: self.show_msg("Formats empty or URL without video") def download_selected(self): ''' Download selected video format ''' if QFile.exists(self.hypervideo_bin): self.pbar.setValue(0) self.url_catch = self.lbl_url_path.text() quality = None if self.vfms_cmbox.currentText() == self.dflt_fms_menu_items[0]: quality = 'bestvideo+bestaudio/best' options = [] options.append('-f') options.append(quality) elif self.vfms_cmbox.currentText() == self.dflt_fms_menu_items[1]: quality = '--audio-quality' options = [] options.append('-x') options.append('--audio-format') options.append('mp3') options.append(quality) options.append('192') else: quality = self.vfms_cmbox.currentText().partition(" ")[0] options = [] options.append('-f') options.append(quality) if self.url_catch != '': if quality is not None: options.append("-o") options.append("%(title)s.%(ext)s") options.append(self.url_catch) self.show_msg("Download started") debugging('Download Selected Quality: %s' % quality) debugging('Download URL: %s' % self.url_catch) self.dl_proc.setWorkingDirectory(self.out_folder_path) self.dl_proc.start(self.hypervideo_bin, options) else: self.show_msg("List of available files is empty") else: self.show_msg("URL empty") else: self.show_msg("hypervideo missing") def dl_process_out(self): ''' Download process out ''' try: out = str(self.dl_proc.readAll(), encoding='utf8').rstrip() except TypeError: out = str(self.dl_proc.readAll()).rstrip() out = out.rpartition("[download] ")[2] self.show_msg("Progress: %s" % out) self.setWindowTitle(out) out = out.rpartition("%")[0].rpartition(".")[0] if not out == "": try: pout = int(out) self.pbar.setValue(pout) except ValueError: pass def cancel_download(self): ''' Cancel download''' if self.dl_proc.state() == QProcess.Running: debugging('Process is running, will be cancelled') self.dl_proc.close() self.show_msg("Download cancelled") self.pbar.setValue(0) self.cxl_btn.setEnabled(False) else: self.show_msg("Process is not running") def create_status_bar(self): ''' Create StatusBar''' self.statusBar().showMessage("Ready") def msgbox(self, message): ''' MessageBox''' QMessageBox.warning(self, "Message", message) def debugging(var): ''' Debugging ''' if DEBUG == 0: message_debug = print('[debug] %s' % var) else: message_debug = None return message_debug def ui_style_sheet(self): ''' Style UI ''' return """ QStatusBar { font-family: Helvetica; font-size: 8pt; color: #666666; } QProgressBar:horizontal { border: 1px solid gray; text-align: top; padding: 1px; border-radius: 3px; background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #fff, stop: 0.4999 #eee, stop: 0.5 #ddd, stop: 1 #eee ); width: 15px; } QProgressBar::chunk:horizontal { background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #5baaf5, stop: 0.4999 #4ba6f5, stop: 0.5 #3ba6f5, stop: 1 #00aaff ); border-radius: 3px; border: 1px solid black; } """