aboutsummaryrefslogtreecommitdiffstats
path: root/hypervideo_gui.py
diff options
context:
space:
mode:
Diffstat (limited to 'hypervideo_gui.py')
-rw-r--r--hypervideo_gui.py422
1 files changed, 0 insertions, 422 deletions
diff --git a/hypervideo_gui.py b/hypervideo_gui.py
deleted file mode 100644
index c668d27..0000000
--- a/hypervideo_gui.py
+++ /dev/null
@@ -1,422 +0,0 @@
-''' Hypervideo GUI '''
-
-import sys
-import os
-import re
-import threading
-import json
-import time
-
-import hypervideo
-
-from PyQt5.QtWidgets import (
- QAction,
- QApplication,
- QComboBox,
- QFileDialog,
- QGridLayout,
- QLabel,
- QLineEdit,
- QMainWindow,
- QMessageBox,
- QPushButton,
- QWidget,
-)
-
-from PyQt5.QtCore import (
- pyqtSlot,
-)
-
-__version__ = "0.1"
-__license__ = "GPL-3"
-
-__path__ = os.environ['HOME']
-__pathBASE__ = r'%s/.config/hypervideo_gui/' % __path__
-if not os.path.exists(__pathBASE__):
- os.makedirs(__pathBASE__)
-
-GUI_STATE_JSON_FILE = '%s/.config/hypervideo_gui/hypervideo_gui_state.json' % __path__
-
-
-class App(QMainWindow):
- """
- Simple applicaction for download using hypervideo
- """
- _VALID_URL = r"""^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$"""
-
- def __init__(self):
- super().__init__()
-
- # load GUI state from file, load default state if save file not found
- gui_state = self.load_gui_state()
- self.download_folder_list = gui_state['DownloadFolderList']
- self.default_formats_menu_items = ['Video/Audio - Best Quality',
- 'Audio Only - Best Quality',
- 'Detect All Available Formats']
-
- # initialize window dimensions
- self.left = 100
- self.top = 100
- self.width = 640
- self.height = 200
- self.setFixedSize(self.width, self.height)
-
- self._init_ui()
-
- def _init_ui(self):
- ''' Initial UI '''
- self.setWindowTitle('Simple Hypervideo Downloader GUI')
- self.setGeometry(self.left, self.top, self.width, self.height)
- # Initialize status bar
- self.statusBar().showMessage('Welcome to Simple Hypervideo Downloader GUI!')
-
- # 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)
-
- # Create URL entry buttons and entry textbox
- url_entry_label = QLabel('Enter URL:')
- self.url_entry_text = QLineEdit()
- self.url_entry_text.setPlaceholderText('https://invidio.us/watch?v=8SdPLG-_wtA')
- # set up callback to update video formats when URL is changed
- self.url_entry_text.textChanged.connect(self.reset_video_formats)
-
- # create output folder button and entry textbox
- output_folder_button = QPushButton('Select Output Folder')
- output_folder_button.setToolTip('Select output folder')
- output_folder_button.clicked.connect(self.update_output_folder)
- self.output_entry_combobox = QComboBox()
- self.output_entry_combobox.setEditable(True)
-
- for item in self.download_folder_list:
- # set default output folder to be downloads folder
- self.output_entry_combobox.addItem(item)
- # self.output_entry_combobox.editTextChanged[str].connect(self.download_text_changed)
-
- # add combobox for video download format and detect formats button
- detect_formats_label = QLabel('Download Format:')
-
- self.video_format_combobox = QComboBox()
- # set default values for format select combobox
- self.populate_video_format_combobox(self.default_formats_menu_items)
- self.video_format_combobox.activated[str].connect(self.video_format_change)
-
- # add download button
- download_button = QPushButton('Download')
- download_button.clicked.connect(self.download_video_callback)
-
- # create grid layout
- layout = QGridLayout()
-
- # add widgets to the layout
- layout.addWidget(url_entry_label, 1, 0)
- layout.addWidget(self.url_entry_text, 1, 1)
- layout.addWidget(output_folder_button, 2, 0)
- layout.addWidget(self.output_entry_combobox, 2, 1)
- layout.addWidget(detect_formats_label, 3, 0)
- layout.addWidget(self.video_format_combobox, 3, 1)
- layout.addWidget(download_button, 5, 0)
-
- # add grid layout as central widget for main window
- main_widget = QWidget()
- main_widget.setLayout(layout)
- self.setCentralWidget(main_widget)
-
- self.show()
-
- 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 url_catch(self):
- ''' Return URL EntryText'''
- url = self.url_entry_text.text()
- url = str(url.strip())
- return url
-
- def url_check_valid(self):
- ''' Check valid URL '''
- if re.match(self._VALID_URL, self.url_catch()) is None:
- check = False
- else:
- check = True
- return check
-
- def download_video_callback(self):
- ''' Callback for the "Download Video" button
- '''
-
- def download_video_thread_helper(self, ydl_opts):
- '''Download the video. Meant to be called in a background daemon thread
- '''
- with hypervideo.YoutubeDL(ydl_opts) as ydl:
- ydl.download([self.url_catch()])
-
- self.statusBar().showMessage('Downloading Video... Done!', msecs=0)
-
- # make sure a valid output directory was entered
- if not os.path.isdir(self.output_entry_combobox.currentText()):
- self.statusBar().showMessage('Invalid download directory!')
- return
-
- # make sure the Download Folder List combobox is populated with the latest entry
- # this covers the case where the user uses the edittext portion of the combobox
- self.add_item_to_downloads_combobox(self.output_entry_combobox.currentText())
-
- # set output path/format
- outtmpl = os.path.join(self.output_entry_combobox.currentText(), r'%(title)s.%(ext)s')
-
- # create the youtube downloader options based on video format combobox selection
- if self.video_format_combobox.currentText() == self.default_formats_menu_items[0]:
- # download best video quality
- ydl_opts = {
- 'format': 'bestvideo+bestaudio/best',
- 'outtmpl': outtmpl,
- }
- elif self.video_format_combobox.currentText() == self.default_formats_menu_items[1]:
- # for downloading best audio and converting to mp3
- ydl_opts = {
- 'format': 'bestaudio/best',
- 'outtmpl': outtmpl,
- 'postprocessors': [{
- 'key': 'FFmpegExtractAudio',
- 'preferredcodec': 'mp3',
- 'preferredquality': '192', # not actually best quality...
- }],
- }
- else:
- # grab video format from the dropdown string:
- # ie. "135 - some video metadata here" -> "135"
- video_format = self.video_format_combobox.currentText()[0:self.video_format_combobox.currentText().find('-')-1]
- # set output path/format
- outformat = os.path.join(self.output_entry_combobox.currentText(),
- r'%(title)s.f%(format_id)s.%(ext)s')
- ydl_opts = {
- 'format': video_format,
- 'outtmpl': outformat,
- }
-
- # download the video in daemon thread
- if self.url_check_valid() is False:
- self.statusBar().showMessage('Please add a URL...')
- else:
- self.statusBar().showMessage('Downloading Video...')
- thread = threading.Thread(target=download_video_thread_helper, args=(self, ydl_opts, ))
- thread.daemon = True
- thread.start()
-
- def update_video_formats(self):
- '''Grabs the list of available video formats in background thread and populates
- video format combobox with results when complete.
- '''
-
- def get_video_formats_thread_helper(self, url):
- ''' Grabs the available video formats. Intended to be run as background thread.
- '''
- self.options = {
- 'socket_timeout': 30,
- 'format': 'best',
- 'noplaylist': True, # only download single song, not playlist
- }
-
- if self.url_check_valid() is True:
- try:
- with hypervideo.YoutubeDL(self.options) as ydl:
- meta = ydl.extract_info(url, download=False)
- formats = meta.get('formats', [meta])
- except TypeError:
- self.statusBar().showMessage('Problem downloading %s' % url)
- return None
-
- # Search formats
- if formats is None:
- self.statusBar().showMessage('Formats not found')
- else:
- item_list = self.default_formats_menu_items[0:2]
- # ForLoop search formats
- for quality in formats:
- format_id = quality.get('format_id', '')
- ext = quality.get('ext', '')
- acodec = quality.get('acodec', '')
- vcodec = quality.get('vcodec', '')
- item_list.append('%s - %s (audio: %s) (video: %s)' %
- (format_id, ext, acodec, vcodec))
- # Replace value empty in audio or video
- format_list = [w.replace('(audio: )', '(audio: none)')
- .replace('(video: )', '(video: none)')
- for w in item_list]
- # Add formats in combobox
- self.populate_video_format_combobox(format_list)
- self.video_format_combobox.setCurrentIndex(0)
- time.sleep(.300)
- self.statusBar().showMessage('Finished Downloading Video Formats', msecs=0)
-
- # check if is valid url
- # should probably be reworked to be compatible with non-YouTube websites
- if self.url_check_valid() is False:
- self.populate_video_format_combobox(self.default_formats_menu_items)
- return
- else:
- # valid url - fetch the video formats in background daemon thread
- self.statusBar().showMessage('Downloading Video Formats')
- thread = threading.Thread(target=get_video_formats_thread_helper,
- args=(self, self.url_catch(), ))
- thread.daemon = True
- thread.start()
-
- def video_format_change(self, text):
- ''' Video Format Change '''
- if text == self.default_formats_menu_items[2]:
- # detect video formats was selected
- # update statusbar to let user know something is happening
- if self.url_check_valid() is False:
- self.statusBar().showMessage('Please add a URL...')
- else:
- # update video formats
- self.update_video_formats()
-
- 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.video_format_combobox.clear()
- for label in labels:
- self.video_format_combobox.addItem(label)
-
- def reset_video_formats(self):
- ''' Clean video formast '''
- idx = self.video_format_combobox.currentIndex()
-
- self.populate_video_format_combobox(self.default_formats_menu_items)
-
- # preserve combobox index if possible
- if idx > 1:
- self.video_format_combobox.setCurrentIndex(0)
- else:
- self.video_format_combobox.setCurrentIndex(idx)
-
- @pyqtSlot()
- def update_output_folder(self):
- ''' Callback for "Update Output Folder" button. Allows user to select
- output directory via standard UI.
- https://stackoverflow.com/questions/43121340/why-is-the-use-of-lensequence-in-condition-values-considered-incorrect-by-pyli
- '''
- file = str(QFileDialog.getExistingDirectory(self, "Select Directory"))
-
- if file:
- self.add_item_to_downloads_combobox(file)
- else:
- self.statusBar().showMessage('Select a folder!')
-
- def download_text_changed(self, text):
- ''' download text changed '''
- # function not used right now
- if text not in self.download_folder_list and os.path.isdir(text):
- self.add_item_to_downloads_combobox(text)
-
- def add_item_to_downloads_combobox(self, text):
- ''' Add item to download combobox '''
- if text not in self.download_folder_list:
- # if it's not in the list, add it to the list
- self.download_folder_list = [text]+self.download_folder_list
- else:
- # if it's in the list already, move it to the top of the list
- self.download_folder_list = [text]+[folder for folder in self.download_folder_list if not folder == text]
-
- # maximum download folder history of 5
- if len(self.download_folder_list) > 5:
- self.download_folder_list = self.download_folder_list[0:6]
-
- # update the combobox
- self.output_entry_combobox.clear()
- for item in self.download_folder_list:
- self.output_entry_combobox.addItem(item)
-
- self.output_entry_combobox.setCurrentIndex(0) # reset index - just in case
-
- def save_gui_state(self):
- ''' Save GUI State '''
- save_dict = {'DownloadFolderList': self.download_folder_list, }
-
- if not GUI_STATE_JSON_FILE:
- newf = open(GUI_STATE_JSON_FILE, "a+")
- newf.close()
-
- with open(GUI_STATE_JSON_FILE, 'w') as file:
- json.dump(save_dict, file)
-
- def load_gui_state(self):
- ''' Load GUI state '''
- if os.path.isfile(GUI_STATE_JSON_FILE):
- with open(GUI_STATE_JSON_FILE, 'r') as file:
- save_data = json.load(file)
-
- if isinstance(save_data['DownloadFolderList'], str):
- # convert to list
- save_data['DownloadFolderList'] = [save_data['DownloadFolderList']]
-
- else:
- save_data = {'DownloadFolderList': [get_default_download_path()], }
-
- return save_data
-
- def closeEvent(self, event):
- '''Protected Function for PyQt5
- gets called when the user closes the GUI.
- It saves the GUI state to the json file
- GUI_STATE_JSON_FILE.
- '''
- self.save_gui_state()
-
- 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 get_default_download_path():
- """Returns the path for the "Downloads" folder in GNU
- Used as default directory for videos to be saved to.
- #From: https://stackoverflow.com/questions/35851281/python-finding-the-users-downloads-folder
- """
- if os.name == 'posix':
- dpath = '/tmp/hypervideo-gui/'
- if not os.path.exists(dpath):
- os.makedirs(dpath)
- return os.path.join(dpath)
-
-if __name__ == '__main__':
- APP = QApplication(sys.argv)
- APP.setStyle('Fusion')
- ex = App()
- sys.exit(APP.exec_())