diff options
Diffstat (limited to 'hypervideo_gui.py')
-rw-r--r-- | hypervideo_gui.py | 422 |
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_()) |