Пример #1
0
    def __init__(self, parent=None, name: str = '', description: str = '', allow_empty=False):
        super(Dialog, self).__init__(parent)
        self.option = QLineEdit()
        self.allow_empty = allow_empty
        self.label = QLabel(color_text('Insert option:', 'limegreen'))
        self.name_label = QLabel(color_text(name + ':', 'limegreen'))
        self.tooltip = QLabel(description)
        self.ok_button = QPushButton('Ok', self)
        self.ok_button.setFixedSize(self.ok_button.sizeHint())
        self.ok_button.setDisabled(not allow_empty)
        self.ok_button.clicked.connect(self.accept)

        self.cancel_button = QPushButton('Cancel', self)
        self.cancel_button.setFixedSize(self.cancel_button.sizeHint())
        self.cancel_button.clicked.connect(self.reject)

        layout = QGridLayout(self)
        layout.addWidget(self.name_label, 0, 0, 1, 3)
        layout.addWidget(self.tooltip, 1, 0, 1, 3)

        layout.addWidget(self.label, 2, 0, 1, 3)
        layout.addWidget(self.option, 3, 0, 1, 3)

        layout.setColumnStretch(0, 1)
        layout.setColumnStretch(1, 0)
        layout.setColumnStretch(2, 0)
        layout.addWidget(self.ok_button, 4, 1)
        layout.addWidget(self.cancel_button, 4, 2)

        self.option.textChanged.connect(self.input_check)
        self.setFixedHeight(self.sizeHint().height())
        self.setFixedWidth(self.sizeHint().width())
        self.option.setFocus()
Пример #2
0
    def stat_update(self):
        def show_infolabel():
            nonlocal self
            if not self.info_label_in_layout:
                self.info_label.show()
                self.info_label_in_layout = True

        if self._open_window is not None:
            self._open_window.setText('\n'.join(self.process.debug_log))

        self.status_box.setText(color_text(self.process.status, color='lawngreen'))
        self.progress.setText(color_text(self.process.progress, color='lawngreen'))
        self.eta.setText(self.process.eta)
        self.speed.setText(self.process.speed)
        self.filesize.setText(self.process.filesize)
        self.playlist.setText(self.process.playlist)

        self.progress.setStyleSheet(f'background: {"#484848" if self.process.progress else "#303030"}')
        self.eta.setStyleSheet(f'background: {"#484848" if self.process.eta else "#303030"}')
        self.speed.setStyleSheet(f'background: {"#484848" if self.process.speed else "#303030"}')
        self.filesize.setStyleSheet(f'background: {"#484848" if self.process.filesize else "#303030"}')
        self.playlist.setStyleSheet(f'background: {"#484848" if self.process.playlist else "#303030"}')

        if self.process.status == 'ERROR':
            show_infolabel()

            self.status_box.setText(color_text(self.process.status))
            self.info_label.setText(f'{self.process.name if self.process.name else "Process"}'
                                    f' failed with message:\n{self.process.info.replace("ERROR:", "").lstrip()}')
        elif self.process.status == 'Aborted':
            self.status_box.setText(color_text(self.process.status))
            show_infolabel()
            name = ' | ' + self.process.name if self.process.name else ''
            self.info_label.setText(self.process.info + name)

        elif self.process.info or self._debug or self.process.name:
            # Shows the info label if there is debug info, or if any other field has info
            if self._debug and not any((self.process.info, self.process.name)):
                if self.process.program_log:
                    show_infolabel()
            else:
                show_infolabel()

            content = []

            if self.process.name:
                content.append(self.process.name.strip())

            if self.process.info:
                content.append(self.process.info.replace("[download] ", ""))

            if self._debug:
                content += ['<br>Debug info:<br>'] + list(self.process.program_log)

            self.info_label.setText('<br>'.join(content))

        self.adjust()
Пример #3
0
    def print_process_output(self, text):
        scrollbar = self.tab1.textbrowser.verticalScrollBar()
        place = scrollbar.sliderPosition()

        if place == scrollbar.maximum():
            keep_position = False
        else:
            keep_position = True

        # get the last line of QTextEdit
        self.tab1.textbrowser.moveCursor(QTextCursor.End,
                                         QTextCursor.MoveAnchor)
        self.tab1.textbrowser.moveCursor(QTextCursor.StartOfLine,
                                         QTextCursor.MoveAnchor)
        self.tab1.textbrowser.moveCursor(QTextCursor.End,
                                         QTextCursor.KeepAnchor)
        last_line = self.tab1.textbrowser.textCursor().selectedText()

        # Check if a percentage has already been placed.
        if "%" in last_line and 'ETA' in last_line and "%" in text:
            self.tab1.textbrowser.textCursor().removeSelectedText()
            self.tab1.textbrowser.textCursor().deletePreviousChar()
            # Last line of text
            self.tab1.textbrowser.append(
                color_text(text.split("[download]")[-1][1:],
                           color='lawngreen',
                           weight='bold',
                           sections=(0, 5)))
            if '100%' in text:
                self.tab1.textbrowser.append('')

        else:
            if ("%" in text and 'ETA' in text) or '100% of ' in text:
                # Last line of text
                self.tab1.textbrowser.append(
                    color_text(text.split("[download]")[-1][1:],
                               color='lawngreen',
                               weight='bold',
                               sections=(0, 5)))
            elif '[download]' in text:
                self.tab1.textbrowser.append(''.join(
                    [text.replace('[download] ', ''), '\n']))

            else:
                self.tab1.textbrowser.append(''.join([text, '\n']))

        # Prevents some leftover highlighted text on errors and such.
        self.tab1.textbrowser.moveCursor(QTextCursor.End,
                                         QTextCursor.MoveAnchor)

        # Ensures slider position is kept when not at bottom, and stays at bottom with new text when there.
        if keep_position:
            scrollbar.setSliderPosition(place)
        else:
            scrollbar.setSliderPosition(scrollbar.maximum())
Пример #4
0
    def dir_info(self):
        # TODO: Print this info to GUI.
        file_dir = os.path.dirname(os.path.abspath(__file__)).replace('\\', '/')
        debug = [color_text('Youtube-dl.exe path: ') + self.youtube_dl_path,
                 color_text('ffmpeg.exe path: ') + self.ffmpeg_path,
                 color_text('Filedir: ') + file_dir,
                 color_text('Workdir: ') + self.file_handler.work_dir,
                 color_text('Youtube-dl working directory: ') + self.program_workdir]

        debug += [color_text('\nIcon paths:'), *self.icon_list]

        debug += [color_text('\nChecking if icons are in place:', 'darkorange', 'bold')]

        for i in self.icon_list:
            if i is not None:
                if self.file_handler.is_file(str(i)):
                    try:
                        debug.append(f'Found: {os.path.split(i)[1]}')
                    except IndexError:
                        debug.append(f'Found: {i}')

        if self.icon_list.count(None):
            debug.append(color_text(f'Missing {self.icon_list.count(None)} icon file(s)!'))

        # RichText does not support both the use of \n and <br> at the same time. Use <br>
        debug_info = '<br>'.join([text.replace('\n', '<br>') for text in debug if text is not None])
        mock_download = MockDownload(info=debug_info)
        self.add_download_to_gui(mock_download)

        self.tab_widget.setCurrentIndex(0)
Пример #5
0
    def dir_info(self):

        file_dir = os.path.dirname(os.path.abspath(__file__)).replace(
            '\\', '/')

        debug = [
            color_text('\nYoutube-dl.exe path:'), self.youtube_dl_path,
            color_text('\nffmpeg.exe path:'), self.ffmpeg_path,
            color_text('Filedir:'), file_dir,
            color_text('Workdir:'), self.file_handler.work_dir,
            color_text('Youtube-dl working directory:'), self.program_workdir,
            color_text('\nIcon paths:'), *self.icon_list
        ]

        for i in debug:
            self.tab1.textbrowser.append(str(i))

        self.tab1.textbrowser.append(
            color_text('\nChecking if icons are in place:', 'darkorange',
                       'bold'))

        for i in self.icon_list:
            if i is not None:

                if self.file_handler.is_file(str(i)):
                    try:
                        self.tab1.textbrowser.append(''.join(
                            ['Found: ', os.path.split(i)[1]]))
                    except IndexError:
                        self.tab1.textbrowser.append(''.join(['Found: ', i]))
                else:
                    self.tab1.textbrowser.append(''.join(['Missing in:', i]))

        self.tab_widget.setCurrentIndex(0)
Пример #6
0
 def restart_current_download(self):
     # TODO: Trigger this make trigger for restarting download!
     if self.active_download is not None and self.active_download.state(
     ) == QProcess.Running:
         self.active_download.kill()
         self.output.emit(
             color_text('Restarting download!', weight='normal'))
         self.active_download.start()
     else:
         self.output.emit('No active download to restart!')
Пример #7
0
    def program_state_changed(self, new_state):
        if new_state == QProcess.NotRunning:
            self.active_download.disconnect()
            self.output.emit('\nDone\n')
            self.queue_handler(process_finished=True)
        elif new_state == QProcess.Running:
            self.output.emit(
                color_text('Starting...\n',
                           'lawngreen',
                           'normal',
                           sections=(0, 8)))

        return
Пример #8
0
    def _single_queue_handler(self, process_finished=False):
        # TODO: Add parallel downloads!
        if not self.RUNNING:
            self.clearOutput.emit()

        if not self.RUNNING or process_finished:
            # TODO: Detect crash when redistributable C++ is not present, if possible

            # if process_finished:
            #     error_code = self.active_download.exitCode()
            #     if error_code:
            #         self.output.emit(color_text(f'Youtube-dl closed with error code {error_code}! '
            #                                     'Is the required C++ distributable installed?'))
            #         self.error_count += 1

            if self._queue:
                download = self._queue.popleft()
                self.updateQueue.emit(f'Items in queue: {len(self._queue):3}')
                self.active_download = download
                try:
                    download.start_dl()
                    self.RUNNING = True
                    self.stateChanged.emit()
                except TypeError as e:
                    self.error_count += 1
                    self.output.emit(color_text(f'FAILED with error {e}'))
                    return self.queue_handler(process_finished=True)

            else:
                self.active_download = None
                self.RUNNING = False
                self.stateChanged.emit()

                error_report = 0 if not self.error_count else color_text(
                    str(self.error_count), "darkorange", "bold")
                self.output.emit(f'Error count: {error_report}.')
                self.error_count = 0
        self.updateQueue.emit(f'Items in queue: {len(self._queue):3}')
Пример #9
0
    def __init__(self, process: Download, slot, debug=False, parent=None, url=None):
        super(ProcessListItem, self).__init__(parent=parent)
        self.process = process
        self.slot = slot
        self.url = url
        self.process.getOutput.connect(self.stat_update)
        self.line = QHBoxLayout()
        self.setFocusPolicy(Qt.NoFocus)
        # self.setStyleSheet()
        self._open_window = None

        self.status_box = QLabel(color_text(self.process.status, color='lawngreen'))
        self.status_box.setContextMenuPolicy(Qt.CustomContextMenu)
        self.status_box.customContextMenuRequested.connect(self.open_info_menu)

        self.progress = QLabel(parent=self)
        self.progress.setAlignment(Qt.AlignCenter)
        self.eta = QLabel('', parent=self)
        self.eta.setAlignment(Qt.AlignCenter)
        self.speed = QLabel(parent=self)
        self.speed.setAlignment(Qt.AlignCenter)
        self.filesize = QLabel(parent=self)
        self.filesize.setAlignment(Qt.AlignCenter)
        self.playlist = QLabel(parent=self)
        self.playlist.setAlignment(Qt.AlignCenter)
        font_size_pixels = FONT_CONSOLAS.pixelSize()

        self.status_box.setBaseSize(20 * font_size_pixels, self.sizeHint().height())

        self.progress.setFixedWidth(5 * font_size_pixels)
        self.eta.setFixedWidth(4 * font_size_pixels)
        self.speed.setFixedWidth(6 * font_size_pixels)
        self.filesize.setFixedWidth(6 * font_size_pixels)
        self.playlist.setFixedWidth(6 * font_size_pixels)

        self.line.addWidget(self.progress, 0)
        self.line.addWidget(self.eta, 0)
        self.line.addWidget(self.speed, 0)
        self.line.addWidget(self.filesize, 0)
        self.line.addWidget(self.playlist, 0)

        self.line2 = QHBoxLayout()
        self.line2.addWidget(self.status_box, 0)
        self.line2.addStretch(1)
        self.line2.addLayout(self.line, 1)

        self.info_label_in_layout = False
        self.info_label = QLabel('', parent=self)
        if url is not None:
            self.info_label.setToolTip(f'URL: {url} (Right-click to copy)')
        self.info_label.setWordWrap(True)
        self.info_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.info_label.hide()
        self.info_label.setContextMenuPolicy(Qt.CustomContextMenu)
        self.info_label.customContextMenuRequested.connect(self.open_file_menu)

        self.vline = QVBoxLayout()
        self.vline.addLayout(self.line2, 0)
        self.vline.addWidget(self.info_label, 1)
        self.setLayout(self.vline)

        self.progress.setStyleSheet(f'background: {"#484848" if self.process.progress else "#303030"}')
        self.eta.setStyleSheet(f'background: {"#484848" if self.process.eta else "#303030"}')
        self.speed.setStyleSheet(f'background: {"#484848" if self.process.speed else "#303030"}')
        self.filesize.setStyleSheet(f'background: {"#484848" if self.process.filesize else "#303030"}')
        self.playlist.setStyleSheet(f'background: {"#484848" if self.process.playlist else "#303030"}')

        self._debug = debug
Пример #10
0
    def build_gui(self):
        """Generates the GUI elements, and hooks everything up."""

        # Indicates if license is shown. (For license tab)
        # TODO: Move to license tab?
        self.license_shown = False

        # Sorts the parameters, so that favorite ones are added to the favorite widget.
        favorites = {
            i: self.settings[i]
            for i in self.settings.get_favorites()
        }
        options = {
            k: v
            for k, v in self.settings.parameters.items() if k not in favorites
        }

        # Main widget. This will be the ones that holds everything.
        # Create top level tab widget system for the UI.
        self.tab_widget = QTabWidget(self)

        self.onclose.connect(self.confirm)
        self.sendClose.connect(self.closeE)

        # Connecting stuff for tab 1.
        # Start buttons starts download

        self.tab1 = MainTab(self.settings, self)

        self.tab1.start_btn.clicked.connect(self.queue_dl)
        # Stop button kills the process, aka youtube-dl.
        self.tab1.stop_btn.clicked.connect(self.stop_download)
        # Close button closes the window/process.
        self.tab1.close_btn.clicked.connect(self.close)
        # When the check button is checked or unchecked, calls function checked.
        self.tab1.checkbox.stateChanged.connect(self.allow_start)
        # Connects actions to text changes and adds action to when you press Enter.
        self.tab1.lineedit.textChanged.connect(self.allow_start)

        # Queue downloading
        self.tab1.lineedit.returnPressed.connect(self.tab1.start_btn.click)

        # Change profile
        self.tab1.profile_dropdown.currentTextChanged.connect(
            self.load_profile)
        # Delete profile
        self.tab1.profile_dropdown.deleteItem.connect(self.delete_profile)

        # Tab 2
        self.tab2 = ParameterTab(options, favorites, self.settings, self)
        # Adds the tab2 layout to the widget.

        # Connection stuff tab 2.
        self.tab2.open_folder_action.triggered.connect(self.open_folder)
        self.tab2.copy_action.triggered.connect(self.copy_to_cliboard)

        self.tab2.options.itemChanged.connect(self.parameter_updater)
        self.tab2.options.move_request.connect(self.move_item)
        self.tab2.options.itemRemoved.connect(self.item_removed)
        self.tab2.options.addOption.connect(self.add_option)

        self.tab2.favorites.itemChanged.connect(self.parameter_updater)
        self.tab2.favorites.move_request.connect(self.move_item)
        self.tab2.favorites.itemRemoved.connect(self.item_removed)
        self.tab2.favorites.addOption.connect(self.add_option)

        self.tab2.browse_btn.clicked.connect(self.savefile_dialog)
        self.tab2.save_profile_btn.clicked.connect(self.save_profile)

        # Tab 3.
        # Tab creation.
        self.tab3 = TextTab(parent=self)

        # Connecting stuff tab 3.

        # When loadbutton is clicked, launch load textfile.
        self.tab3.loadButton.clicked.connect(self.load_text_from_file)
        # When savebutton clicked, save text to document.
        self.tab3.saveButton.clicked.connect(self.save_text_to_file)

        # Tab 4
        # Button to browse for .txt file to download files.
        self.tab4 = AboutTab(self.settings, parent=self)

        ## Connecting stuff tab 4.

        # Starts self.update_youtube_dl, locate_program_path checks for updates.
        self.tab4.update_btn.clicked.connect(self.update_youtube_dl)
        self.tab4.dirinfo_btn.clicked.connect(self.dir_info)
        self.tab4.reset_btn.clicked.connect(self.reset_settings)
        self.tab4.license_btn.clicked.connect(self.read_license)
        self.tab4.location_btn.clicked.connect(self.textfile_dialog)

        # Future tab creation here! Currently 4 tabs
        # TODO: Move stylesheet applying to method, make color picking dialog to customize in realtime
        if self.settings.user_options['use_win_accent']:
            try:
                color = get_win_accent_color()
                bg_color = f"""
                QMainWindow {{
                    background-color: {color};
                }}
                QTabBar {{
                    background-color: {color};
                }}"""
            except (OSError, PermissionError):
                bg_color = ''
        else:
            bg_color = ''

        self.style_with_options = bg_color + f"""
                                QCheckBox::indicator:unchecked {{
                                    image: url({self.unchecked_icon});
                                }}

                                QCheckBox::indicator:checked {{
                                    image: url({self.checked_icon});
                                }}
                                QComboBox::down-arrow {{
                                    border-image: url({self.down_arrow_icon});
                                    height: {self.tab1.profile_dropdown.iconSize().height()}px;
                                    width: {self.tab1.profile_dropdown.iconSize().width()}px;
                                }}

                                QComboBox::down-arrow::on {{
                                    image: url({self.down_arrow_icon_clicked});
                                    height: {self.tab1.profile_dropdown.iconSize().height()}px;
                                    width: {self.tab1.profile_dropdown.iconSize().width()}px;
                                    
                                }}
                                
                                QTreeWidget::indicator:checked {{
                                    image: url({self.checked_icon});
                                }}
                                
                                QTreeWidget::indicator:unchecked {{
                                    image: url({self.unchecked_icon});
                                }}
                                
                                QTreeWidget::branch {{
                                    image: none;
                                    border-image: none;    
                                }}
                                
                                QTreeWidget::branch:has-siblings:!adjoins-item {{
                                    image: none;
                                    border-image: none;
                                }}
                                
                                QTreeWidget::branch:has-siblings:adjoins-item {{
                                    border-image: none;
                                    image: none;
                                }}
                                
                                QTreeWidget::branch:!has-children:!has-siblings:adjoins-item {{
                                    border-image: none;
                                    image: none;
                                }}
                                
                                """

        # Configuration main widget.
        # Adds tabs to the tab widget, and names the tabs.
        self.tab_widget.addTab(self.tab1, 'Main')
        self.tab_widget.addTab(self.tab2, 'Param')
        self.tab_widget.addTab(self.tab3, 'List')
        self.tab_widget.addTab(self.tab4, 'About')
        # Sets the styling for the GUI, everything from buttons to anything. ##
        self.setStyleSheet(get_stylesheet() + self.style_with_options)

        # Set window title.
        self.setWindowTitle('Grabber')
        self.setWindowIcon(self.windowIcon)
        # Set base size.
        self.setMinimumWidth(340)
        self.setMinimumHeight(200)

        if self.settings.user_options['select_on_focus']:
            self.gotfocus.connect(self.window_focus_event)
        else:
            self.tab1.lineedit.setFocus()

        # Other functionality.
        self.shortcut = QShortcut(QKeySequence("Ctrl+S"), self.tab3.textedit)
        self.shortcut.activated.connect(self.tab3.saveButton.click)

        # TODO: Hook up to button, or change output to somewhere the user can get it.
        # self.trigger_queue_print = QShortcut(QKeySequence('Ctrl+P'), self)
        # self.trigger_queue_print.activated.connect(self.print_queue)

        # Check for youtube
        if self.youtube_dl_path is None:
            self.tab4.update_btn.setDisabled(True)
            self.tab1.textbrowser.append(
                color_text(
                    '\nNo youtube-dl.exe found! Add to path, '
                    'or make sure it\'s in the same folder as this program. '
                    'Then close and reopen this program.', 'darkorange',
                    'bold'))
        # Sets the download items tooltips to the full file path.
        self.download_name_handler()

        # Ensures widets are in correct state at startup and when tab1.lineedit is changed.
        self.allow_start()
        # Shows the main window.
        self.setCentralWidget(self.tab_widget)

        self.show()

        # self.main_tab.show() # Old method.

        # Connect after show!!
        self.resizedByUser.connect(self.resize_contents)
        # To make sure the window is updated on first enter
        # if resized before tab2 is shown, i'll be blank.

        self.downloader.output.connect(self.print_process_output)
        self.downloader.stateChanged.connect(self.allow_start)
        self.downloader.clearOutput.connect(self.tab1.textbrowser.clear)
        self.downloader.updateQueue.connect(
            lambda text: self.tab1.queue_label.setText(text))
        self.tab_widget.currentChanged.connect(self.resize_contents)
Пример #11
0
    def queue_dl(self):
        command = []

        if self.tab1.checkbox.isChecked():
            if self.tab4.txt_lineedit.text() == '':
                self.alert_message('Error!', 'No textfile selected!', '')
                self.tab1.textbrowser.append(
                    'No textfile selected...\n\nNo download queued!')
                return

            txt = self.settings.user_options['multidl_txt']
            command += ['-a', f'{txt}']
        else:
            txt = self.tab1.lineedit.text()
            command.append(f'{txt}')

        # for i in range(len(command)):
        #    command[i] = command[i].format(txt=txt)'
        file_name_format = '%(title)s.%(ext)s'

        for parameter, options in self.settings.parameters.items():
            if parameter == 'Download location':
                if options['state']:
                    add = format_in_list(
                        options['command'],
                        self.settings.get_active_setting(parameter) +
                        file_name_format)
                    command += add
                else:
                    command += ['-o', self.local_dl_path + file_name_format]

            elif parameter == 'Keep archive':
                if options['state']:
                    add = format_in_list(
                        options['command'],
                        os.path.join(
                            os.getcwd(),
                            self.settings.get_active_setting(parameter)))
                    command += add
            elif parameter == 'Username':
                if options['state']:
                    option = self.settings.get_active_setting(parameter)
                    if option in self._temp:
                        _password = self._temp[option]
                    else:
                        dialog = Dialog(
                            self,
                            'Password',
                            f'Input you password for the account "{option}".',
                            allow_empty=True,
                            password=True)

                        if dialog.exec_() == QDialog.Accepted:
                            self._temp[
                                option] = _password = dialog.option.text()
                        else:
                            self.tab1.textbrowser.append(
                                color_text('ERROR: No password was entered.',
                                           sections=(0, 6)))
                            return

                    add = format_in_list(options['command'], option)
                    add += ['--password', _password]

                    command += add

            else:
                if options['state']:
                    if self.settings.get_active_setting(parameter):
                        option = self.settings.get_active_setting(parameter)
                    else:
                        option = ''
                    add = format_in_list(options['command'], option)
                    command += add

        # Sets encoding to utf-8, allowing better character support in output stream.
        command += ['--encoding', 'utf-8']

        if self.ffmpeg_path is not None:
            command += ['--ffmpeg-location', self.ffmpeg_path]

        download = Download(self.program_workdir, self.youtube_dl_path,
                            command, self)
        self.tab1.start_btn.setDisabled(True)
        self.downloader.queue_dl(download)