class MainWindow(QMainWindow): #LABEL ADD_LABEL = _("Add") DOWNLOAD_LIST_LABEL = _("Download list") DELETE_LABEL = _("Delete") DELETEALL_LABEL = _("Clear") #PLAY_LABEL = _("Play") UP_LABEL = _("Up") DOWN_LABEL = _("Down") #RELOAD_LABEL = _("Reload") #PAUSE_LABEL = _("Pause") START_LABEL = _("Start") STOP_LABEL = _("Stop") INFO_LABEL = _("Info") ABOUT_LABEL = _("About") QUESTION_LABEL = _("Question") WARNING_LABEL = _("Warning") PROVIDE_URL_MSG = _("You need to provide at least one URL") QUIT_MSG = _("Are You sure to quit?") VIDEO_LABEL = _("Filename") EXTENSION_LABEL = _("Extension") SIZE_LABEL = _("Size") PERCENT_LABEL = _("Percent") ETA_LABEL = _("ETA") SPEED_LABEL = _("Speed") STATUS_LABEL = _("Status") URL_REPORT_MSG = _( "Total Progress: {0:.1f}% | Queued ({1}) Paused ({2}) Active ({3}) Completed ({4}) Error ({5})" ) CLOSING_MSG = _("Stopping downloads") DOWNLOAD_STARTED = _("Downloads started") UPDATING_MSG = _("Downloading latest youtube-dl. Please wait...") UPDATE_ERR_MSG = _("Youtube-dl download failed [{0}]") UPDATE_SUCC_MSG = _("Successfully downloaded youtube-dl") ################################# # STATUSLIST_COLUMNS # # Dictionary which contains the columns for the wxListCtrl widget. # Each key represents a column and holds informations about itself. # Structure informations: # column_key: (column_number, column_label, minimum_width, is_resizable) # STATUSLIST_COLUMNS = { 'filename': (0, VIDEO_LABEL, 150, True), 'extension': (1, EXTENSION_LABEL, 60, False), 'filesize': (2, SIZE_LABEL, 80, False), 'percent': (3, PERCENT_LABEL, 65, False), 'eta': (4, ETA_LABEL, 45, False), 'speed': (5, SPEED_LABEL, 90, False), 'status': (6, STATUS_LABEL, 160, False) } def __init__(self): super(MainWindow, self).__init__() if getattr(sys, 'frozen', False): # we are running in a |PyInstaller| bundle basedir = sys._MEIPASS else: basedir = os.path.dirname(__file__) loadUi(os.path.join(basedir, "qtyoutube-dl.ui"), self) self.setWindowIcon(QIcon(":qtyoutube-dl")) #load setting #self.cfg = QSettings("coolshou.idv", "qtyoutube-dl") #self._savepath = "" #self.loadSettings() ################################### cfgpath = os.path.join( QStandardPaths.writableLocation( QStandardPaths.ApplicationsLocation), "qtyoutube-dl") if not os.path.exists(cfgpath): os.mkdir(cfgpath) self.opt_manager = opt_manager(cfgpath) self.log_manager = LogManager(cfgpath, self.opt_manager.options['log_time']) self.download_manager = None self.update_thread = None self.app_icon = None #REFACTOR Get and set on __init__.py self._download_list = DownloadList() self._status_list = ListCtrl(self.STATUSLIST_COLUMNS) self._status_list.rowsInserted.connect(self._update_btns) #self._status_list.selectionChanged.connect(self._on_selectionChanged) self.tv_status.setModel(self._status_list) self.tv_status.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) self.tv_status.clicked.connect(self._on_clicked) #self._status_selection = self.tv_status.selectionModel(); #self._status_selection.selectionChanged.connect(self._on_selectionChanged) #self.tv_status.activated.connect(self._on_activated) # Set up youtube-dl options parser self._options_parser = OptionsParser() # Set the Timer self._app_timer = QTimer(self) self._app_timer.timeout.connect(self._on_timer) #upadte ui self.setSettings() #self._url_list = [] ############################################ #button #self.le_savepath.textChanged.connect(self.updateSavePath) self.path_combobox.currentTextChanged.connect(self.updateSavePath) ################################### # set bitmap bitmap_data = (("add", ":/add"), ("down", ":/down"), ("up", ":/up"), ("copy", ":/copy"), ("delete", ":/delete"), ("start", ":/get"), ("stop", ":/stop"), ("pause", ":/pause"), ("resume", ":/resume"), ("qtyoutube-dl", ":/qtyoutube-dl"), ("setting", ":/setting"), ("trash", ":/trash"), ("info", ":/info")) # ("pause", ":/pause"), # ("resume", ":/resume") self._bitmaps = {} for item in bitmap_data: target, name = item #self._bitmaps[target] = wx.Bitmap(os.path.join(self._pixmaps_path, name)) self._bitmaps[target] = QIcon(name) # Dictionary to store all the buttons # Set the data for all the wx.Button items # name, label, size, event_handler buttons_data = (("delete", self.DELETE_LABEL, (-1, -1), self._on_delete, self.pb_del), ("clear", self.DELETEALL_LABEL, (-1, -1), self._on_delAll, self.pb_delAll), ("up", self.UP_LABEL, (-1, -1), self._on_arrow_up, self.pb_up), ("down", self.DOWN_LABEL, (-1, -1), self._on_arrow_down, self.pb_down), ("start", self.START_LABEL, (-1, -1), self._on_start, self.pb_Start), ("savepath", "...", (35, -1), self._on_savepath, self.pb_savepath), ("add", self.ADD_LABEL, (-1, -1), self._on_add, self.pb_add)) #("play", self.PLAY_LABEL, (-1, -1), self._on_play, QPushButton), #("reload", self.RELOAD_LABEL, (-1, -1), self._on_reload, QPushButton), #("pause", self.PAUSE_LABEL, (-1, -1), self._on_pause, QPushButton), self._buttons = {} for item in buttons_data: name, label, size, evt_handler, parent = item #button = parent(self._panel, size=size) button = parent if parent == QPushButton: button.setText(label) # elif parent == wx.BitmapButton: # button.setToolTip(wx.ToolTip(label)) # if name in self._bitmaps: # #button.SetBitmap(self._bitmaps[name], wx.TOP) # button.SetBitmap(self._bitmaps[name], wx.TOP) if evt_handler is not None: #button.Bind(wx.EVT_BUTTON, evt_handler) button.clicked.connect(evt_handler) self._buttons[name] = button self._path_combobox = self.path_combobox #test data #self.te_urls.append("https://www.youtube.com/watch?v=a9V0nl_ezLw") self._initAction() self.show() @pyqtSlot(QModelIndex, int, int) def _update_btns(self, idx, first, last): #print("%s : %s , %s" %(idx, first, last)) iSum = self._status_list.rowCount() if iSum >= 1: #print("iSum:%s" % iSum) self.pb_Start.setEnabled(True) else: self.pb_Start.setEnabled(False) def _update_pause_button(self, event): selected_rows = self._status_list.get_all_selected() label = _("Pause") bitmap = self._bitmaps["pause"] for row in selected_rows: object_id = self._status_list.GetItemData(row) download_item = self._download_list.get_item(object_id) if download_item.stage == "Paused": # If we find one or more items in Paused # state set the button functionality to resume label = _("Resume") bitmap = self._bitmaps["resume"] break self._buttons["pause"].setText(label) self._buttons["pause"].setToolTip(label) self._buttons["pause"].setIcon(bitmap) def _initAction(self): #Action menu #view-log #option-setting self.actionupdate.triggered.connect(self._on_update) self.actionAbout.triggered.connect(self._on_about) pass def _get_urls(self): """Returns urls list. """ return [ line for line in self.te_urls.toPlainText().split('\n') if line ] def _create_popup(self, icon, text, title, style): msg = QMessageBox(icon, title, text, style) msg.setWindowIcon(QIcon(":qtyoutube-dl")) return msg.exec() def _on_add(self): urls = self._get_urls() if not urls: self._create_popup(QMessageBox.Warning, self.PROVIDE_URL_MSG, self.WARNING_LABEL, QMessageBox.Ok) else: #self._url_list.Clear() options = self._options_parser.parse(self.opt_manager.options) for url in urls: #make sure it is http or https if "http://" in url or "https://" in url: download_item = DownloadItem(url, options) download_item.path = self.opt_manager.options["save_path"] if not self._download_list.has_item( download_item.object_id): self._status_list.bind_item(download_item) self._download_list.insert(download_item) #clear line def _on_arrow_up(self, event): index = self._status_list.get_next_selected() if index != -1: while index >= 0: object_id = self._status_list.GetItemData(index) download_item = self._download_list.get_item(object_id) new_index = index - 1 if new_index < 0: new_index = 0 if not self._status_list.IsSelected(new_index): self._download_list.move_up(object_id) self._status_list.move_item_up(index) self._status_list._update_from_item( new_index, download_item) index = self._status_list.get_next_selected(index) def _on_arrow_down(self, event): index = self._status_list.get_next_selected(reverse=True) if index != -1: while index >= 0: object_id = self._status_list.GetItemData(index) download_item = self._download_list.get_item(object_id) new_index = index + 1 if new_index >= self._status_list.GetItemCount(): new_index = self._status_list.GetItemCount() - 1 if not self._status_list.IsSelected(new_index): self._download_list.move_down(object_id) self._status_list.move_item_down(index) self._status_list._update_from_item( new_index, download_item) index = self._status_list.get_next_selected(index, True) def _on_delAll(self): self._status_list.Clear() self._download_list.clear() def _on_delete(self): #get select idx index = self._status_list.get_next_selected() if index == -1: dlg = ButtonsChoiceDialog( self, [_("Remove all"), _("Remove completed")], _("No items selected. Please pick an action"), _("Delete")) ret_code = dlg.ShowModal() dlg.Destroy() #REFACTOR Maybe add this functionality directly to DownloadList? if ret_code == 1: for ditem in self._download_list.get_items(): if ditem.stage != "Active": self._status_list.remove_row( self._download_list.index(ditem.object_id)) self._download_list.remove(ditem.object_id) if ret_code == 2: for ditem in self._download_list.get_items(): if ditem.stage == "Completed": self._status_list.remove_row( self._download_list.index(ditem.object_id)) self._download_list.remove(ditem.object_id) else: if self.opt_manager.options["confirm_deletion"]: self._create_popup( QMessageBox.Question, _("Are you sure you want to remove selected items?"), _("Delete"), QMessageBox.Yes | QMessageBox.No) #dlg = wx.MessageDialog(self, _("Are you sure you want to remove selected items?"), _("Delete"), wx.YES_NO | wx.ICON_QUESTION) #result = dlg.ShowModal() == wx.ID_YES result = dlg.ShowModal() == QMessageBox.Yes dlg.Destroy() else: result = True if result: while index >= 0: object_id = self._status_list.GetItemData(index) selected_download_item = self._download_list.get_item( object_id) if selected_download_item.stage == "Active": #self._create_popup(_("Item is active, cannot remove"), self.WARNING_LABEL, wx.OK | wx.ICON_EXCLAMATION) self._create_popup(QMessageBox.Information, _("Item is active, cannot remove"), self.WARNING_LABEL, QMessageBox.OK) else: #if selected_download_item.stage == "Completed": #dlg = wx.MessageDialog(self, "Do you want to remove the files associated with this item?", "Remove files", wx.YES_NO | wx.ICON_QUESTION) #result = dlg.ShowModal() == wx.ID_YES #dlg.Destroy() #if result: #for cur_file in selected_download_item.get_files(): #remove_file(cur_file) self._status_list.remove_row(index) self._download_list.remove(object_id) index -= 1 index = self._status_list.get_next_selected(index) #self._update_pause_button(None) @pyqtSlot(QModelIndex) def _on_clicked(self, mIdx): print("select: %s, %s" % (mIdx, mIdx.row())) if mIdx.row() >= 0: self.pb_del.setEnabled(True) else: self.pb_del.setEnabled(False) self.pb_up.setEnabled(False) self.pb_down.setEnabled(False) @pyqtSlot(QModelIndex) def _on_activated(self, mIdx): print("select: %s" % mIdx) @pyqtSlot(QItemSelection, QItemSelection) def _on_selectionChanged(self, selected, deselected): print("selected: %s" % selected) def _on_savepath(self): folder = str( QFileDialog.getExistingDirectory(self, "Select save Directory")) if folder: #self._savepath = folder self.le_savepath.setText(folder) def _on_start(self, event): if self.download_manager is None: if self.update_thread is not None and self.update_thread.is_alive( ): self._create_popup( QMessageBox.Information, _("Update in progress. Please wait for the update to complete" ), self.WARNING_LABEL, QMessageBox.OK) else: self._start_download() else: self.download_manager.stop_downloads() def _start_download(self): if self._status_list.is_empty(): self._create_popup(_("No items to download"), self.WARNING_LABEL, QMessageBox.OK) else: self._app_timer.start(100) self.download_manager = DownloadManager(self, self._download_list, self.opt_manager, self.log_manager) self.download_manager.sig_callafter.connect( self._download_manager_handler) self.download_manager.sig_worker_callafter.connect( self._download_worker_handler) self._status_bar_write(self.DOWNLOAD_STARTED) self._buttons["start"].setText(self.STOP_LABEL) self._buttons["start"].setToolTip(self.STOP_LABEL) self._buttons["start"].setIcon(self._bitmaps["stop"]) def _on_timer(self): total_percentage = 0.0 queued = paused = active = completed = error = 0 for item in self._download_list.get_items(): if item.stage == "Queued": queued += 1 if item.stage == "Paused": paused += 1 if item.stage == "Active": active += 1 total_percentage += float( item.progress_stats["percent"].split('%')[0]) if item.stage == "Completed": completed += 1 if item.stage == "Error": error += 1 # REFACTOR Store percentage as float in the DownloadItem? # REFACTOR DownloadList keep track for each item stage? items_count = active + completed + error + queued total_percentage += completed * 100.0 + error * 100.0 if items_count: total_percentage /= items_count msg = self.URL_REPORT_MSG.format(total_percentage, queued, paused, active, completed, error) if self.update_thread is None: # Don't overwrite the update messages self._status_bar_write(msg) def _status_bar_write(self, msg): """Display msg in the status bar. """ self.statusbar.showMessage(msg) def _download_manager_worker_handler(self, signal, data): print("dl_work: %s: %s" % (signal, data)) print("_download_list: %s" % self._download_list.get_items()) def _download_worker_handler(self, data): """downloadmanager.Worker thread handler. Handles messages from the Worker thread. Args: See downloadmanager.Worker _talk_to_gui() method. """ #signal, data = msg.data #signal = sig download_item = self._download_list.get_item(data["index"]) download_item.update_stats(data) row = self._download_list.index(data["index"]) self._status_list._update_from_item(row, download_item) def _download_manager_handler(self, data): """downloadmanager.DownloadManager thread handler. Handles messages from the DownloadManager thread. Args: See downloadmanager.DownloadManager _talk_to_gui() method. """ #data = msg.data if data == 'finished': self._print_stats() self._reset_widgets() self.download_manager = None self._app_timer.stop() self._after_download() elif data == 'closed': self._status_bar_write(self.CLOSED_MSG) self._reset_widgets() self.download_manager = None self._app_timer.stop() elif data == 'closing': self._status_bar_write(self.CLOSING_MSG) elif data == 'report_active': pass #NOTE Remove from here and downloadmanager #since now we have the wx.Timer to check progress def _on_update(self, event): """Event handler of the self._update_btn widget. This method is used when the update button is pressed to start the update process. Note: Currently there is not way to stop the update process. """ if self.opt_manager.options["disable_update"]: self._create_popup( _("Updates are disabled for your system. Please use the system's package manager to update youtube-dl." ), self.INFO_LABEL, QMessageBox.OK) else: self._update_youtubedl() def _update_youtubedl(self): """Update youtube-dl binary to the latest version. """ if self.download_manager is not None and self.download_manager.is_alive( ): self._create_popup(self.DOWNLOAD_ACTIVE, self.WARNING_LABEL, QMessageBox.OK) elif self.update_thread is not None and self.update_thread.is_alive(): self._create_popup(self.UPDATE_ACTIVE, self.INFO_LABEL, QMessageBox.OK) else: self.update_thread = UpdateThread( self.opt_manager.options['youtubedl_path']) self.update_thread.sig_callafter.connect(self._update_handler) def _update_handler(self, sig, msg): """updatemanager.UpdateThread thread handler. Handles messages from the UpdateThread thread. Args: See updatemanager.UpdateThread _talk_to_gui() method. """ #data = msg.data if sig == 'download': self._status_bar_write("%s: %s" % (self.UPDATING_MSG, msg)) elif sig == 'error': self._status_bar_write(self.UPDATE_ERR_MSG.format(msg)) elif sig == 'correct': self._status_bar_write(self.UPDATE_SUCC_MSG) else: self._reset_widgets() self.update_thread = None def _reset_widgets(self): """Resets GUI widgets after update or download process. """ self._buttons["start"].setText(_("Start")) self._buttons["start"].setToolTip(_("Start")) self._buttons["start"].setIcon(self._bitmaps["start"]) def _on_about(self, event): #msg = "Name: %s\n" % __appname__ msg = "Version: %s\n" % __version__ msg += "Web: %s\n" % __projecturl__ msg += " %s\n" % __descriptionfull__ QMessageBox.about(self, "about - %s" % __appname__, msg) def updateSavePath(self, text): #TODO: make sure path of "text" exist self._savepath = text def loadSettings(self): '''load setting from file''' ''' self.cfg.beginGroup("main") #save path, self._savepath = self.cfg.value("savepath", QStandardPaths.writableLocation(QStandardPaths.DownloadLocation)) if not self._savepath: self._savepath = QStandardPaths.writableLocation(QStandardPaths.DownloadLocation) self._size = self.cfg.value("size", self.size()) self._pos = self.cfg.value("pos", self.pos()) #TODO: download list, resume? self.cfg.endGroup() ''' def setSettings(self): '''update setting to GUI''' self.resize( QSize(self.opt_manager.options["size"][0], self.opt_manager.options["size"][1])) self.move( QPoint(self.opt_manager.options["pos"][0], self.opt_manager.options["pos"][0])) #self.le_savepath.setText(self.opt_manager.options["save_path"]) self.path_combobox.addItem(self.opt_manager.options["save_path"]) def saveSettings(self): '''save setting to file''' self.opt_manager.options["size"] = "(%s,%s)" % (self.width(), self.height()) #self.opt_manager.options["pos"] #print("self.size(): %s" % self.opt_manager.options["size"]) self.opt_manager.save_to_file() ''' self.cfg.beginGroup("main") #save path self.cfg.setValue("savepath", self._savepath) self.cfg.setValue("size", self.size()) self.cfg.setValue("pos", self.pos()) #TODO: download list self.cfg.endGroup() ''' def closeEvent(self, event): # self.saveSettings() if 0: close = self._create_popup(QMessageBox.Question, self.QUIT_MSG, self.QUESTION_LABEL, QMessageBox.Yes | QMessageBox.Cancel) if close == QMessageBox.Yes: event.accept() else: event.ignore() else: event.accept()