#!/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
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.2'
__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('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(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(
"
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
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