123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- #!/usr/bin/python
- ''' Hypervideo GUI '''
- import sys
- from PyQt5.QtCore import (
- QFile,
- QStandardPaths,
- Qt,
- QProcess,
- QSettings
- )
- from PyQt5.QtWidgets import (
- QAction,
- QComboBox,
- QFileDialog,
- QHBoxLayout,
- QLineEdit,
- QLabel,
- QMainWindow,
- QMessageBox,
- QProgressBar,
- 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.3'
- __license__ = 'GPL-3'
- __title__ = 'Simple Hypervideo Download GUI'
- class MainWindow(QMainWindow):
- ''' MainWindow '''
- def __init__(self):
- ''' Initial '''
- super(MainWindow, self).__init__()
- self.hypervideo_bin = None
- self.url_catch = None
- self.out_folder_path = '/tmp/'
- self.settings = QSettings('hypervideo-gui', 'main')
- 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(self.msg_dl_finished)
- 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))
- # 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)
- self.read_settings()
- def on_button_clicked(self):
- """ Button about """
- msg = QMessageBox()
- msg.setWindowTitle('About us')
- msg.setText(
- "<p align='center'>Written with Python3 and PyQt5<br>"
- "Version: %s <br> License: %s </p>" %
- (__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
- options = []
- if self.vfms_cmbox.currentText() == self.dflt_fms_menu_items[0]:
- quality = 'bestvideo+bestaudio/best'
- options.append('-f')
- options.append(quality)
- elif self.vfms_cmbox.currentText() == self.dflt_fms_menu_items[1]:
- quality = '--audio-quality'
- 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.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 msg_dl_finished(self):
- ''' Message finished download '''
- # Check if it's completed download
- if self.pbar.value() == 100:
- msg = QMessageBox()
- msg.setWindowTitle('%s' % __title__)
- msg.setIcon(QMessageBox.Information)
- msg.setText('Download done!')
- self.show()
- msg.exec_()
- 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 '''
- self.mystyle = """
- QStatusBar
- {
- font-family: DejaVu Sans;
- 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;
- }
- """
- style = self.mystyle
- return style
|