Beispiel #1
0
    def check_videos_exist(self, media_data_obj):

        """Called by self.tidy_directory().

        Checks all child videos of the specified media data object. If the
        video should exist, but doesn't (or vice-versa), modify the media.Video
        object's IVs accordingly.

        Args:

            media_data_obj (media.Channel, media.Playlist or media.Folder):
                The media data object whose directory must be tidied up

        """

        if DEBUG_FUNC_FLAG:
            utils.debug_time('top 642 check_videos_exist')

        for video_obj in media_data_obj.compile_all_videos( [] ):

            if video_obj.file_name is not None:

                video_path = video_obj.get_actual_path(self.app_obj)

                if not video_obj.dl_flag \
                and os.path.isfile(video_path):

                    # File exists, but is marked as not downloaded
                    self.app_obj.mark_video_downloaded(
                        video_obj,
                        True,       # Video is downloaded
                        True,       # ...but don't mark it as new
                    )

                    self.video_exist_count += 1

                    self.app_obj.main_win_obj.output_tab_write_stdout(
                        1,
                        '   ' + _(
                            'Video file exists:',
                        ) + ' \'' + video_obj.name + '\'',
                    )

                elif video_obj.dl_flag \
                and not os.path.isfile(video_path):

                    # File doesn't exist, but is marked as downloaded
                    self.app_obj.mark_video_downloaded(
                        video_obj,
                        False,      # Video is not downloaded
                    )

                    self.video_no_exist_count += 1

                    self.app_obj.main_win_obj.output_tab_write_stdout(
                        1,
                        '   ' + _(
                            'Video file doesn\'t exist:',
                        ) + ' \'' + video_obj.name + '\'',
                    )
Beispiel #2
0
    def setup_finish_page_default(self):
        """Called by self.setup_page().

        Sets up the widget layout for a page, for all operating systems except
        MS Windows.
        """

        self.add_image(
            self.app_obj.main_win_obj.icon_dict['ready_icon'],
            0,
            0,
            1,
            1,
        )

        self.add_label(
            '<span font_size="large" font_weight="bold">' \
            + _('All done!') + '</span>',
            0, 1, 1, 1,
        )

        # (Empty label for spacing)
        self.add_empty_label(0, 2, 1, 1)

        self.add_label(
            '<span font_size="large" style="italic">' \
            + 'It is stronly recommended that you install FFmpeg.</span>',
            0, 3, 1, 1,
        )

        self.add_label(
            '<span font_size="large" style="italic">' \
            + utils.tidy_up_long_string(
                _(
                    'Without FFmpeg, Tartube cannot download high-resolution' \
                    + ' videos, and cannot display video thumbnails from' \
                    + ' YouTube.',
                ),
                60,
            ) + '</span>',
            0, 4, 1, 1,
        )

        # (Empty label for spacing)
        self.add_empty_label(0, 5, 1, 1)

        self.add_label(
            '<span font_size="large"  style="italic">' \
            + _('Click the <b>OK</b> button to start Tartube!') \
            + '</span>',
            0, 6, 1, 1,
        )
Beispiel #3
0
    def call_moviepy(self, video_obj, video_path):
        """Called by thread inside self.check_video_corrupt().

        When we call moviepy.editor.VideoFileClip() on a corrupted video file,
        moviepy freezes indefinitely.

        This function is called inside a thread, so a timeout of (by default)
        ten seconds can be applied.

        Args:

            video_obj (media.Video): The video object being updated

            video_path (str): The path to the video file itself

        """

        try:
            clip = moviepy.editor.VideoFileClip(video_path)

        except:
            self.video_corrupt_count += 1

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Video file might be corrupt:') + ' \'' \
                + video_obj.name + '\'',
            )
Beispiel #4
0
    def tidy_directory(self, media_data_obj):

        """Called by self.run().

        Tidy up the directory of a single channel, playlist or folder.

        Args:

            media_data_obj (media.Channel, media.Playlist or media.Folder):
                The media data object whose directory must be tidied up

        """

        if DEBUG_FUNC_FLAG:
            utils.debug_time('top 502 tidy_directory')

        # Update the main window's progress bar
        self.job_count += 1
        GObject.timeout_add(
            0,
            self.app_obj.main_win_obj.update_progress_bar,
            media_data_obj.name,
            self.job_count,
            self.job_total,
        )

        media_type = media_data_obj.get_type()

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            _('Checking:') + ' \'' + media_data_obj.name + '\'',
        )

        if self.corrupt_flag:
            self.check_video_corrupt(media_data_obj)

        if self.exist_flag:
            self.check_videos_exist(media_data_obj)

        if self.del_video_flag:
            self.delete_video(media_data_obj)

        if self.del_descrip_flag:
            self.delete_descrip(media_data_obj)

        if self.del_json_flag:
            self.delete_json(media_data_obj)

        if self.del_xml_flag:
            self.delete_xml(media_data_obj)

        if self.del_thumb_flag:
            self.delete_thumb(media_data_obj)

        if self.del_webp_flag:
            self.delete_webp(media_data_obj)

        if self.del_archive_flag:
            self.delete_archive(media_data_obj)
Beispiel #5
0
    def setup_page(self):
        """Called initially by self.setup(), then by .on_button_next_clicked()
        or .on_button_prev_clicked().

        Sets up the page specified by self.current_page.
        """

        index = self.current_page
        page_func = self.page_list[self.current_page]
        if page_func is None:

            # Emergency fallback
            index = 0
            page_func = self.page_list[0]

        if len(self.page_list) <= 1:
            self.next_button.set_sensitive(False)
            self.prev_button.set_sensitive(False)
        elif index == 0:
            self.next_button.set_sensitive(True)
            self.prev_button.set_sensitive(False)
        else:
            self.next_button.set_sensitive(True)
            self.prev_button.set_sensitive(True)

        if index >= len(self.page_list) - 1:
            self.next_button.set_label(_('OK'))
        else:
            self.next_button.set_label(_('Next'))

        self.next_button.get_child().set_width_chars(10)

        # Replace the inner grid...
        self.vbox.remove(self.inner_grid)

        self.inner_grid = Gtk.Grid()
        self.vbox.pack_start(self.inner_grid, True, False, 0)
        self.inner_grid.set_row_spacing(self.spacing_size)
        self.inner_grid.set_column_spacing(self.spacing_size)

        # ...and then refill it, with the widget layout for the new page
        method = getattr(self, page_func)
        method()

        self.show_all()
Beispiel #6
0
    def setup_finish_page_mswin(self):
        """Called by self.setup_page().

        Sets up the widget layout for a page, shown only on MS Windows.
        """

        self.add_image(
            self.app_obj.main_win_obj.icon_dict['ready_icon'],
            0,
            0,
            1,
            1,
        )

        self.add_label(
            '<span font_size="large" font_weight="bold">' \
            + _('All done!') + '</span>',
            0, 1, 1, 1,
        )

        # (Empty label for spacing)
        self.add_empty_label(0, 2, 1, 1)

        self.add_label(
            '<span font_size="large" style="italic">' \
            + utils.tidy_up_long_string(
                _(
                    'If you need to re-install or update the downloader or' \
                    + ' FFmpeg, you can do it from the main window\'s menu.',
                ),
                60,
            ) + '</span>',
            0, 3, 1, 1,
        )

        # (Empty label for spacing)
        self.add_empty_label(0, 4, 1, 1)

        self.add_label(
            '<span font_size="large"  style="italic">' \
            + _('Click the <b>OK</b> button to start Tartube!') \
            + '</span>',
            0, 5, 1, 1,
        )
Beispiel #7
0
    def run(self):

        """Called as a result of self.__init__().

        Calls FFmpegManager.run_ffmpeg for every media.Video object in the
        list.

        Then informs the main application that the process operation is
        complete.
        """

        # Show information about the process operation in the Output Tab
        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            _('Starting process operation'),
        )

        # Process each video in turn
        while self.running_flag and self.video_list:

            self.process_video(self.video_list.pop(0))

            # Pause a moment, before the next iteration of the loop (don't want
            #   to hog resources)
            time.sleep(self.sleep_time)

        # Operation complete. Set the stop time
        self.stop_time = int(time.time())

        # Show a confirmation in the Output Tab
        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            _('Process operation finished'),
        )

        # Let the timer run for a few more seconds to prevent Gtk errors (for
        #   systems with Gtk < 3.24)
        GObject.timeout_add(
            0,
            self.app_obj.process_manager_halt_timer,
        )
Beispiel #8
0
    def convert_next_button(self):
        """Can be called by anything.

        Converts the 'Next' to an 'OK' button, and sensitises it.

        Should usually be called from the last page, when the code is ready to
        let the window finish the wizard.
        """

        self.next_button.set_label(_('Finish'))
        self.next_button.get_child().set_width_chars(10)
        self.next_button.set_sensitive(True)
Beispiel #9
0
    def on_button_choose_folder_clicked(self, button, label):
        """Called from a callback in self.setup_db_page().

        Opens a file chooser dialogue, so the user can set the location of
        Tartube's data directory.

        Args:

            button (Gtk.Button): The widget clicked

            label (Gtk.Label): Once set, the path to the directory is displayed
                in this label

        """

        if not self.mswin_flag:
            title = _('Select Tartube\'s data directory')
        else:
            title = _('Select Tartube\'s data folder')

        dialogue_win = self.app_obj.dialogue_manager_obj.show_file_chooser(
            title,
            self,
            Gtk.FileChooserAction.SELECT_FOLDER,
        )

        # Get the user's response
        response = dialogue_win.run()
        if response == Gtk.ResponseType.OK:

            self.data_dir = dialogue_win.get_filename()
            label.set_markup(
                '<span font_size="large" font_weight="bold">' + self.data_dir \
                + '</span>',
            )

            # Data directory set, so re-enable the Next button
            self.next_button.set_sensitive(True)

        dialogue_win.destroy()
Beispiel #10
0
    def setup_button_strip(self):
        """Called by self.setup().

        Creates a strip of buttons at the bottom of the window: a 'cancel'
        button on the left, and 'next'/'previous' buttons on the right.

        The window is closed by using the 'cancel' button, or by clicking the
        'next' button on the last page.
        """

        hbox = Gtk.HBox()
        self.grid.attach(hbox, 0, 1, 1, 1)

        # 'Cancel' button
        self.cancel_button = Gtk.Button(_('Cancel'))
        hbox.pack_start(self.cancel_button, False, False, 0)
        self.cancel_button.get_child().set_width_chars(10)
        self.cancel_button.set_tooltip_text(
            _('Close this window without completing it'), )
        self.cancel_button.connect('clicked', self.on_button_cancel_clicked)

        # 'Next' button
        self.next_button = Gtk.Button(_('Next'))
        hbox.pack_end(self.next_button, False, False, 0)
        self.next_button.get_child().set_width_chars(10)
        self.next_button.set_tooltip_text(_('Go to the next page'))
        self.next_button.connect('clicked', self.on_button_next_clicked)

        # 'Previous' button
        self.prev_button = Gtk.Button(_('Previous'))
        hbox.pack_end(self.prev_button, False, False, self.spacing_size)
        self.prev_button.get_child().set_width_chars(10)
        self.prev_button.set_tooltip_text(_('Go to the previous page'))
        self.prev_button.connect('clicked', self.on_button_prev_clicked)
Beispiel #11
0
    def create_child_process(self, cmd_list):

        """Called by self.install_ffmpeg() or .install_ytdl().

        Based on code from downloads.VideoDownloader.create_child_process().

        Executes the system command, creating a new child process which
        executes youtube-dl.

        Args:

            cmd_list (list): Python list that contains the command to execute.

        """

        if DEBUG_FUNC_FLAG:
            utils.debug_time('uop 167 create_child_process')

        info = preexec = None

        if os.name == 'nt':
            # Hide the child process window that MS Windows helpfully creates
            #   for us
            info = subprocess.STARTUPINFO()
            info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        else:
            # Make this child process the process group leader, so that we can
            #   later kill the whole process group with os.killpg
            preexec = os.setsid

        try:
            self.child_process = subprocess.Popen(
                cmd_list,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                preexec_fn=preexec,
                startupinfo=info,
            )

        except (ValueError, OSError) as error:
            # (The code in self.run() will spot that the child process did not
            #   start)
            self.stderr_list.append(_('Child process did not start'))
Beispiel #12
0
    def run(self):
        """Called as a result of self.__init__().

        Compiles a list of media data objects (channels, playlists and folders)
        to tidy up. If self.init_obj is not set, only that channel/playlist/
        folder (and its child channels/playlists/folders) are tidied up;
        otherwise the whole data directory is tidied up.

        Then calls self.tidy_directory() for each item in the list.

        Finally informs the main application that the tidy operation is
        complete.
        """

        # Show information about the tidy operation in the Output Tab
        if not self.init_obj:
            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                _('Starting tidy operation, tidying up whole data directory'),
            )

        else:

            media_type = self.init_obj.get_type()

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                _('Starting tidy operation, tidying up \'{0}\'').format(
                    self.init_obj.name, ))

        if self.corrupt_flag:
            text = _('YES')
        else:
            text = _('NO')

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Check videos are not corrupted:') + ' ' + text,
        )

        if self.corrupt_flag:

            if self.del_corrupt_flag:
                text = _('YES')
            else:
                text = _('NO')

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Delete corrupted videos:') + ' ' + text,
            )

        if self.exist_flag:
            text = _('YES')
        else:
            text = _('NO')

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Check videos do/don\'t exist:') + ' ' + text,
        )

        if self.del_video_flag:
            text = _('YES')
        else:
            text = _('NO')

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Delete all video files:') + ' ' + text,
        )

        if self.del_video_flag:

            if self.del_others_flag:
                text = _('YES')
            else:
                text = _('NO')

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Delete other video/audio files:') + ' ' + text,
            )

        if self.del_archive_flag:
            text = _('YES')
        else:
            text = _('NO')

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Delete downloader archive files:') + ' ' + text,
        )

        if self.move_thumb_flag:
            text = _('YES')
        else:
            text = _('NO')

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Move thumbnails into own folder:') + ' ' + text,
        )

        if self.del_thumb_flag:
            text = _('YES')
        else:
            text = _('NO')

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Delete all thumbnail files:') + ' ' + text,
        )

        if self.convert_webp_flag:
            text = _('YES')
        else:
            text = _('NO')

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Convert .webp thumbnails to .jpg:') + ' ' + text,
        )

        if self.move_data_flag:
            text = _('YES')
        else:
            text = _('NO')

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Move other metadata files into own folder:') \
            + ' ' + text,
        )

        if self.del_descrip_flag:
            text = _('YES')
        else:
            text = _('NO')

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Delete all description files:') + ' ' + text,
        )

        if self.del_json_flag:
            text = _('YES')
        else:
            text = _('NO')

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Delete all metadata (JSON) files:') + ' ' + text,
        )

        if self.del_xml_flag:
            text = _('YES')
        else:
            text = _('NO')

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Delete all annotation files:') + ' ' + text,
        )

        # Compile a list of channels, playlists and folders to tidy up (each
        #   one has their own sub-directory inside Tartube's data directory)
        obj_list = []
        if self.init_obj:
            # Add this channel/playlist/folder, and any child channels/
            #   playlists/folders (but not videos, obviously)
            obj_list = self.init_obj.compile_all_containers(obj_list)
        else:
            # Add all channels/playlists/folders in the database
            for dbid in list(self.app_obj.media_name_dict.values()):

                obj = self.app_obj.media_reg_dict[dbid]
                # Don't add private folders
                if not isinstance(obj, media.Folder) or not obj.priv_flag:
                    obj_list.append(obj)

        self.job_total = len(obj_list)

        # Check each sub-directory in turn, updating the media data registry
        #   as we go
        while self.running_flag and obj_list:
            self.tidy_directory(obj_list.pop(0))

            # Pause a moment, before the next iteration of the loop (don't want
            #   to hog resources)
            time.sleep(self.sleep_time)

        # Operation complete. Set the stop time
        self.stop_time = int(time.time())

        # Show a confirmation in the Output Tab
        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            _('Tidy operation finished'),
        )

        if self.corrupt_flag:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Corrupted videos found:') + ' ' \
                + str(self.video_corrupt_count),
            )

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Corrupted videos deleted:') + ' ' \
                + str(self.video_corrupt_deleted_count),
            )

        if self.exist_flag:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('New video files detected:') + ' ' \
                + str(self.video_exist_count),
            )

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Missing video files detected:') + ' ' \
                + str(self.video_no_exist_count),
            )

        if self.del_video_flag:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Non-corrupted video files deleted:') + ' ' \
                + str(self.video_deleted_count),
            )

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Other video/audio files deleted:') + ' ' \
                + str(self.other_deleted_count),
            )

        if self.del_archive_flag:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Downloader archive files deleted:') + ' ' \
                + str(self.archive_deleted_count),
            )

        if self.move_thumb_flag:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Thumbnail files moved:') + ' ' \
                + str(self.thumb_moved_count),
            )

        if self.del_thumb_flag:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Thumbnail files deleted:') + ' ' \
                + str(self.thumb_deleted_count),
            )

        if self.convert_webp_flag:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('.webp thumbnails converted to .jpg:') + ' ' \
                + str(self.webp_converted_count),
            )

        if self.move_data_flag:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Other metadata files moved:') + ' ' \
                + str(self.data_moved_count),
            )

        if self.del_descrip_flag:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Description files deleted:') + ' ' \
                + str(self.descrip_deleted_count),
            )

        if self.del_json_flag:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Metadata (JSON) files deleted:') + ' ' \
                + str(self.json_deleted_count),
            )

        if self.del_xml_flag:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '   ' + _('Annotation files deleted:') + ' ' \
                + str(self.xml_deleted_count),
            )

        # Let the timer run for a few more seconds to prevent Gtk errors (for
        #   systems with Gtk < 3.24)
        GObject.timeout_add(
            0,
            self.app_obj.tidy_manager_halt_timer,
        )
Beispiel #13
0
    def refresh_from_actual_destination(self, media_data_obj):

        """Called by self.run().

        A modified version of self.refresh_from_default_destination().
        Refreshes a single channel, playlist or folder, for which an
        alternative download destination has been set.

        If a file is missing in the alternative download destination, mark the
        video object as not downloaded.

        Don't check for unexpected video files in the alternative download
        destination - we expect that they exist.

        Args:

            media_data_obj (media.Channel, media.Playlist or media.Folder):
                The media data object to refresh

        """

        if DEBUG_FUNC_FLAG:
            utils.debug_time('rop 519 refresh_from_actual_destination')

        # Update the main window's progress bar
        self.job_count += 1
        GObject.timeout_add(
            0,
            self.app_obj.main_win_obj.update_progress_bar,
            media_data_obj.name,
            self.job_count,
            self.job_total,
        )

        # Keep a running total of matched videos for this channel, playlist or
        #   folder
        local_total_count = 0
        local_match_count = 0
        # (No new media.Video objects are created)
        local_missing_count = 0

        # Update our progress in the Output Tab
        if isinstance(media_data_obj, media.Channel):
            string = _('Channel:') + '  '
        elif isinstance(media_data_obj, media.Playlist):
            string = _('Playlist:') + ' '
        else:
            string = _('Folder:') + '   '

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            string + media_data_obj.name,
        )

        # Get the alternative download destination
        dir_path = media_data_obj.get_actual_dir(self.app_obj)

        # Get a list of video files in that sub-directory
        try:
            init_list = os.listdir(dir_path)
        except:
            # Can't read the directory
            return

        # Now check each media.Video object, to see if the video file still
        #   exists (or not)
        for child_obj in media_data_obj.child_list:

            if isinstance(child_obj, media.Video) and child_obj.file_name:

                this_file = child_obj.file_name + child_obj.file_ext
                if child_obj.dl_flag and not this_file in init_list:

                    local_missing_count += 1

                    # Video doesn't exist, so mark it as not downloaded
                    self.app_obj.mark_video_downloaded(child_obj, False)

                    # Update our progress in the Output Tab (if required)
                    self.app_obj.main_win_obj.output_tab_write_stdout(
                        1,
                        '      ' + _('Missing:') + ' ' + child_obj.name,
                    )

                elif not child_obj.dl_flag and this_file in init_list:

                    self.video_total_count += 1
                    local_total_count += 1
                    self.video_match_count += 1
                    local_match_count += 1

                    # Video exists, so mark it as downloaded (but don't mark it
                    #   as new)
                    self.app_obj.mark_video_downloaded(child_obj, True, True)

                    # Update our progress in the Output Tab (if required)
                    if self.app_obj.refresh_output_videos_flag:
                        self.app_obj.main_win_obj.output_tab_write_stdout(
                            1,
                            '      ' + _('Match:') + ' ' + child_obj.name,
                        )

        # Check complete, display totals
        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Total videos:') + ' ' + str(local_total_count) \
            + ', ' + _('matched:') + ' ' + str(local_match_count) \
            + ', ' + _('missing:') + ' ' + str(local_missing_count),
        )
Beispiel #14
0
    def setup_db_page(self):
        """Called by self.setup_page().

        Sets up the widget layout for a page.
        """

        grid_width = 3

        self.add_label(
            '<span font_size="large" style="italic">' \
            + _('Tartube stores all of its downloads in one place.') \
            + '</span>',
            0, 0, grid_width, 1,
        )

        # (Empty label for spacing)
        self.add_empty_label(0, 1, grid_width, 1)

        if not self.mswin_flag:

            msg = utils.tidy_up_long_string(
                _(
                    'If you don\'t want to use the default location, then' \
                    + ' click <b>Choose</b> to select a different one.',
                ),
                60,
            )

            msg2 = utils.tidy_up_long_string(
                _(
                    'If you have used Tartube before, you can select an' \
                    + ' existing directory, instead of creating a new one.',
                ),
                60,
            )

        else:

            msg = _('Click <b>Choose</b> to create a new folder.')
            msg2 = utils.tidy_up_long_string(
                _(
                    'If you have used Tartube before, you can select an' \
                    + ' existing folder, instead of creating a new one.',
                ),
                60,
            )

        self.add_label(
            '<span font_size="large" style="italic">' + msg + '</span>',
            0,
            2,
            grid_width,
            1,
        )

        # (Empty label for spacing)
        self.add_empty_label(0, 3, grid_width, 1)

        self.add_label(
            '<span font_size="large" style="italic">' + msg2 + '</span>',
            0,
            4,
            grid_width,
            1,
        )

        # (Empty label for spacing)
        self.add_empty_label(0, 5, grid_width, 1)

        button = Gtk.Button(_('Choose'))
        self.inner_grid.attach(button, 1, 6, 1, 1)
        # (Signal connect appears below)

        if not self.mswin_flag:
            button2 = Gtk.Button(_('Use default location'))
            self.inner_grid.attach(button2, 1, 7, 1, 1)
            # (Signal connect appears below)

        # (Empty label for spacing)
        self.add_empty_label(0, 8, grid_width, 1)

        # The specified path appears here, after it has been selected
        if self.data_dir is None:

            label = self.add_label(
                '',
                0,
                9,
                grid_width,
                1,
            )

        else:

            label = self.add_label(
                '<span font_size="large" font_weight="bold">' \
                + self.data_dir + '</span>',
                0, 9, grid_width, 1,
            )

        # (Signal connects from above)
        button.connect(
            'clicked',
            self.on_button_choose_folder_clicked,
            label,
        )

        if not self.mswin_flag:

            button2.connect(
                'clicked',
                self.on_button_default_folder_clicked,
                label,
            )

        # Disable the Next button until a folder has been created/selected
        if self.data_dir is None:
            self.next_button.set_sensitive(False)
Beispiel #15
0
    def install_ffmpeg(self):
        """Called by self.run().

        A modified version of self.install_ytdl, that installs FFmpeg on an
        MS Windows system.

        Creates a child process to run the installation process.

        Reads from the child process STDOUT and STDERR, and calls the main
        application with the result of the update (success or failure).
        """

        # Show information about the update operation in the Output Tab
        self.install_ffmpeg_write_output(
            _('Starting update operation, installing FFmpeg'), )

        # Create a new child process to install either the 64-bit or 32-bit
        #   version of FFmpeg, as appropriate
        if sys.maxsize <= 2147483647:
            binary = 'mingw-w64-i686-ffmpeg'
        else:
            binary = 'mingw-w64-x86_64-ffmpeg'

        self.create_child_process(['pacman', '-S', binary, '--noconfirm'], )

        # Show the system command in the Output Tab
        self.install_ffmpeg_write_output(
            ' '.join(['pacman', '-S', binary, '--noconfirm']),
            True,  # A system command, not a message
        )

        # So that we can read from the child process STDOUT and STDERR, attach
        #   a file descriptor to the PipeReader objects
        if self.child_process is not None:

            self.stdout_reader.attach_file_descriptor(
                self.child_process.stdout, )

            self.stderr_reader.attach_file_descriptor(
                self.child_process.stderr, )

        while self.is_child_process_alive():

            # Read from the child process STDOUT, and convert into unicode for
            #   Python's convenience
            while not self.stdout_queue.empty():

                stdout = self.stdout_queue.get_nowait().rstrip()
                stdout = stdout.decode('cp1252')

                if stdout:

                    # Show command line output in the Output Tab (or wizard
                    #   window textview)
                    self.install_ffmpeg_write_output(stdout)

        # The child process has finished
        while not self.stderr_queue.empty():

            # Read from the child process STDERR queue (we don't need to read
            #   it in real time), and convert into unicode for python's
            #   convenience
            stderr = self.stderr_queue.get_nowait().rstrip()
            stderr = stderr.decode('cp1252')

            # Ignore pacman warning messages, e.g. 'warning: dependency cycle
            #   detected:'
            if stderr and not re.match('warning\:', stderr):

                self.stderr_list.append(stderr)

                # Show command line output in the Output Tab (or wizard window
                #   textview)
                self.install_ffmpeg_write_output(stderr)

        # (Generate our own error messages for debugging purposes, in certain
        #   situations)
        if self.child_process is None:
            self.stderr_list.append(_('FFmpeg installation did not start'))

        elif self.child_process.returncode > 0:
            self.stderr_list.append(
                _('Child process exited with non-zero code: {}').format(
                    self.child_process.returncode, ))

        # Operation complete. self.success_flag is checked by
        #   mainapp.TartubeApp.update_manager_finished
        if not self.stderr_list:
            self.success_flag = True

        # Show a confirmation in the the Output Tab (or wizard window textview)
        self.install_ffmpeg_write_output(_('Update operation finished'))

        # Let the timer run for a few more seconds to prevent Gtk errors (for
        #   systems with Gtk < 3.24)
        GObject.timeout_add(
            0,
            self.app_obj.update_manager_halt_timer,
        )
Beispiel #16
0
    def run(self):

        """Called as a result of self.__init__().

        Creates a child process to run the youtube-dl system command.

        Reads from the child process STDOUT and STDERR, and calls the main
        application with the result of the process (success or failure).
        """

        # Checking for a new release of Tartube doesn't involve any system
        #   commands or child processes, so it is handled by a separate
        #   function
        if self.info_type == 'version':

            return self.run_check_version()

        # Show information about the info operation in the Output Tab
        if self.info_type == 'test_ytdl':

            msg = _(
                'Starting info operation, testing downloader with specified' \
                + ' options',
            )

        else:

            if self.info_type == 'formats':

                msg = _(
                    'Starting info operation, fetching list of video/audio'\
                    + ' formats for \'{0}\'',
                ).format(self.video_obj.name)

            else:

                msg = _(
                    'Starting info operation, fetching list of subtitles'\
                    + ' for \'{0}\'',
                ).format(self.video_obj.name)

        self.app_obj.main_win_obj.output_tab_write_stdout(1, msg)

        # Convert a path beginning with ~ (not on MS Windows)
        ytdl_path = self.app_obj.check_downloader(self.app_obj.ytdl_path)
        if os.name != 'nt':
            ytdl_path = re.sub('^\~', os.path.expanduser('~'), ytdl_path)

        # Prepare the system command
        if self.info_type == 'formats':

            cmd_list = [
                ytdl_path,
                '--list-formats',
                self.video_obj.source,
            ]

        elif self.info_type == 'subs':

            cmd_list = [
                ytdl_path,
                '--list-subs',
                self.video_obj.source,
            ]

        else:

            if app_obj.ytdl_path_custom_flag:
                cmd_list = ['python3'] + [ytdl_path]
            else:
                cmd_list = [ytdl_path]

            if self.options_string is not None \
            and self.options_string != '':

                # Parse the string into a list. It was obtained from a
                #   Gtk.TextView, so it can contain newline and/or multiple
                #   whitepsace characters. Whitespace characters within
                #   double quotes "..." must be preserved
                option_list = utils.parse_options(self.options_string)
                for item in option_list:
                    cmd_list.append(item)

            if self.url_string is not None \
            and self.url_string != '':

                cmd_list.append('-o')
                cmd_list.append(
                    os.path.join(
                        self.app_obj.temp_test_dir,
                        '%(title)s.%(ext)s',
                    ),
                )

                cmd_list.append(self.url_string)

        # Create the new child process
        self.create_child_process(cmd_list)

        # Show the system command in the Output Tab
        space = ' '
        self.app_obj.main_win_obj.output_tab_write_system_cmd(
            1,
            space.join(cmd_list),
        )

        # So that we can read from the child process STDOUT and STDERR, attach
        #   a file descriptor to the PipeReader objects
        if self.child_process is not None:

            self.stdout_reader.attach_file_descriptor(
                self.child_process.stdout,
            )

            self.stderr_reader.attach_file_descriptor(
                self.child_process.stderr,
            )

        while self.is_child_process_alive():

            # Read from the child process STDOUT, and convert into unicode for
            #   Python's convenience
            while not self.stdout_queue.empty():

                stdout = self.stdout_queue.get_nowait().rstrip()
                if stdout:

                    if os.name == 'nt':
                        stdout = stdout.decode('cp1252')
                    else:
                        stdout = stdout.decode('utf-8')

                    self.output_list.append(stdout)
                    self.stdout_list.append(stdout)

                    # Show command line output in the Output Tab
                    self.app_obj.main_win_obj.output_tab_write_stdout(
                        1,
                        stdout,
                    )

        # The child process has finished
        while not self.stderr_queue.empty():

            # Read from the child process STDERR queue (we don't need to read
            #   it in real time), and convert into unicode for python's
            #   convenience
            stderr = self.stderr_queue.get_nowait().rstrip()
            if os.name == 'nt':
                stderr = stderr.decode('cp1252')
            else:
                stderr = stderr.decode('utf-8')

            if stderr:

                # While testing youtube-dl, don't treat anything as an error
                if self.info_type == 'test_ytdl':
                    self.stdout_list.append(stderr)

                # When fetching subtitles from a video that has none, don't
                #   treat youtube-dl WARNING: messages as something that
                #   makes the info operation fail
                elif self.info_type == 'subs':

                    if not re.match('WARNING\:', stderr):
                        self.stderr_list.append(stderr)

                # When fetching formats, recognise all warnings as errors
                else:
                    self.stderr_list.append(stderr)

                # Show command line output in the Output Tab
                self.app_obj.main_win_obj.output_tab_write_stderr(
                    1,
                    stderr,
                )

        # (Generate our own error messages for debugging purposes, in certain
        #   situations)
        if self.child_process is None:

            msg = _('System process did not start')
            self.stderr_list.append(msg)
            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                msg,
            )

        elif self.child_process.returncode > 0:

            msg = _('Child process exited with non-zero code: {}').format(
                self.child_process.returncode,
            )
            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                msg,
            )

        # Operation complete. self.success_flag is checked by
        #   mainapp.TartubeApp.info_manager_finished()
        if not self.stderr_list:
            self.success_flag = True

        # Show a confirmation in the the Output Tab
        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            _('Info operation finished'),
        )

        # Let the timer run for a few more seconds to prevent Gtk errors (for
        #   systems with Gtk < 3.24)
        GObject.timeout_add(
            0,
            self.app_obj.info_manager_halt_timer,
        )
Beispiel #17
0
def do_translate(config_flag=False):
    """Function called for the first time below, setting various values.

    If mainapp.TartubeApp.load_config() changes the locale to something else,
    called for a second time to update those values.

    Args:

        config_flag (bool): False for the initial call, True for the second
            call from mainapp.TartubeApp.load_config()

    """

    global FOLDER_ALL_VIDEOS, FOLDER_BOOKMARKS, FOLDER_FAVOURITE_VIDEOS, \
    FOLDER_LIVESTREAMS, FOLDER_MISSING_VIDEOS, FOLDER_NEW_VIDEOS, \
    FOLDER_WAITING_VIDEOS, FOLDER_TEMPORARY_VIDEOS, FOLDER_UNSORTED_VIDEOS

    global YTDL_UPDATE_DICT

    global MAIN_STAGE_QUEUED, MAIN_STAGE_ACTIVE, MAIN_STAGE_PAUSED, \
    MAIN_STAGE_COMPLETED, MAIN_STAGE_ERROR, ACTIVE_STAGE_PRE_PROCESS, \
    ACTIVE_STAGE_DOWNLOAD, ACTIVE_STAGE_POST_PROCESS, ACTIVE_STAGE_CHECKING, \
    COMPLETED_STAGE_FINISHED, COMPLETED_STAGE_WARNING, \
    COMPLETED_STAGE_ALREADY, ERROR_STAGE_ERROR, ERROR_STAGE_STOPPED, \
    ERROR_STAGE_ABORT

    global TIME_METRIC_TRANS_DICT

    global FILE_OUTPUT_NAME_DICT, FILE_OUTPUT_CONVERT_DICT

    global VIDEO_OPTION_LIST, VIDEO_OPTION_DICT

    # System folder names
    FOLDER_ALL_VIDEOS = _('All Videos')
    FOLDER_BOOKMARKS = _('Bookmarks')
    FOLDER_FAVOURITE_VIDEOS = _('Favourite Videos')
    FOLDER_LIVESTREAMS = _('Livestreams')
    FOLDER_MISSING_VIDEOS = _('Missing Videos')
    FOLDER_NEW_VIDEOS = _('New Videos')
    FOLDER_WAITING_VIDEOS = _('Waiting Videos')
    FOLDER_TEMPORARY_VIDEOS = _('Temporary Videos')
    FOLDER_UNSORTED_VIDEOS = _('Unsorted Videos')

    # youtube-dl update shell commands
    YTDL_UPDATE_DICT = {
        'ytdl_update_default_path': _('Update using default youtube-dl path'),
        'ytdl_update_local_path': _('Update using local youtube-dl path'),
        'ytdl_update_pip': _('Update using pip'),
        'ytdl_update_pip_omit_user':
        _('Update using pip (omit --user option)'),
        'ytdl_update_pip3': _('Update using pip3'),
        'ytdl_update_pip3_omit_user':
        _('Update using pip3 (omit --user option)'),
        'ytdl_update_pip3_recommend': _('Update using pip3 (recommended)'),
        'ytdl_update_pypi_path': _('Update using PyPI youtube-dl path'),
        'ytdl_update_win_32': _('Windows 32-bit update (recommended)'),
        'ytdl_update_win_64': _('Windows 64-bit update (recommended)'),
        'ytdl_update_disabled': _('youtube-dl updates are disabled'),
    }

    #  Download operation stages
    MAIN_STAGE_QUEUED = _('Queued')
    MAIN_STAGE_ACTIVE = _('Active')
    MAIN_STAGE_PAUSED = _('Paused')  # (not actually used)
    MAIN_STAGE_COMPLETED = _('Completed')  # (not actually used)
    MAIN_STAGE_ERROR = _('Error')
    # Sub-stages of the 'Active' stage
    ACTIVE_STAGE_PRE_PROCESS = _('Pre-processing')
    ACTIVE_STAGE_DOWNLOAD = _('Downloading')
    ACTIVE_STAGE_POST_PROCESS = _('Post-processing')
    ACTIVE_STAGE_CHECKING = _('Checking')
    # Sub-stages of the 'Completed' stage
    COMPLETED_STAGE_FINISHED = _('Finished')
    COMPLETED_STAGE_WARNING = _('Warning')
    COMPLETED_STAGE_ALREADY = _('Already downloaded')
    # Sub-stages of the 'Error' stage
    ERROR_STAGE_ERROR = _('Error')  # (not actually used)
    ERROR_STAGE_STOPPED = _('Stopped')
    ERROR_STAGE_ABORT = _('Filesize abort')

    if config_flag:

        for key in TIME_METRIC_TRANS_DICT:
            TIME_METRIC_TRANS_DICT[key] = _(key)

        # File output templates use a combination of English words, each of
        #   which must be translated
        translate_note = _(
            'TRANSLATOR\'S NOTE: ID refers to a video\'s unique ID on the' \
            + ' website, e.g. on YouTube "CS9OO0S5w2k"',
        )

        new_name_dict = {}
        for key in FILE_OUTPUT_NAME_DICT.keys():

            mod_value \
            = re.sub('Custom', _('Custom'), FILE_OUTPUT_NAME_DICT[key])
            mod_value = re.sub('ID', _('ID'), mod_value)
            mod_value = re.sub('Title', _('Title'), mod_value)
            mod_value = re.sub('Quality', _('Quality'), mod_value)
            mod_value = re.sub('Autonumber', _('Autonumber'), mod_value)

            new_name_dict[key] = mod_value

        FILE_OUTPUT_NAME_DICT = new_name_dict

        # Video/audio formats. A number of them contain 'Any format', which
        #   must be translated
        new_list = []
        new_dict = {}
        for item in VIDEO_OPTION_LIST:

            mod_item = re.sub('Any format', _('Any format'), item)
            new_list.append(mod_item)
            new_dict[mod_item] = VIDEO_OPTION_DICT[item]

        VIDEO_OPTION_LIST = new_list
        VIDEO_OPTION_DICT = new_dict

    # End of this function
    return
Beispiel #18
0
    LOCALE_LIST.append(key)
    LOCALE_DICT[key] = value

# Some icons are different at Christmas
today = datetime.date.today()
day = today.strftime("%d")
month = today.strftime("%m")
if (int(month) == 12 and int(day) >= 24) \
or (int(month) == 1 and int(day) <= 5):
    xmas_flag = True
else:
    xmas_flag = False

# Standard list and dictionaries
time_metric_setup_list = [
    'seconds', _('seconds'), 1,
    'minutes', _('minutes'), 60,
    'hours', _('hours'), int(60 * 60),
    'days', _('days'), int(60 * 60 * 24),
    'weeks', _('weeks'), int(60 * 60 * 24 * 7),
    'years', _('years'), int(60 * 60 * 24 * 365),
]

TIME_METRIC_LIST = []
TIME_METRIC_DICT = {}
TIME_METRIC_TRANS_DICT = {}

while time_metric_setup_list:
    key = time_metric_setup_list.pop(0)
    trans_key = time_metric_setup_list.pop(0)
    value = time_metric_setup_list.pop(0)
Beispiel #19
0
    LOCALE_DICT[key] = value

# Some icons are different at Christmas
today = datetime.date.today()
day = today.strftime("%d")
month = today.strftime("%m")
if (int(month) == 12 and int(day) >= 24) \
or (int(month) == 1 and int(day) <= 5):
    xmas_flag = True
else:
    xmas_flag = False

# Standard list and dictionaries
time_metric_setup_list = [
    'seconds',
    _('seconds'),
    1,
    'minutes',
    _('minutes'),
    60,
    'hours',
    _('hours'),
    int(60 * 60),
    'days',
    _('days'),
    int(60 * 60 * 24),
    'weeks',
    _('weeks'),
    int(60 * 60 * 24 * 7),
    'years',
    _('years'),
Beispiel #20
0
    def install_ytdl(self):
        """Called by self.run().

        Based on code from downloads.VideoDownloader.do_download().

        Creates a child process to run the youtube-dl update.

        Reads from the child process STDOUT and STDERR, and calls the main
        application with the result of the update (success or failure).
        """

        # Show information about the update operation in the Output tab (or in
        #   the setup wizard window, if called from there)
        downloader = self.app_obj.get_downloader(self.wiz_win_obj)
        self.install_ytdl_write_output(
            _('Starting update operation, installing/updating ' +
              downloader), )

        # Prepare the system command

        # The user can change the system command for updating youtube-dl,
        #   depending on how it was installed
        # (For example, if youtube-dl was installed via pip, then it must be
        #   updated via pip)
        if self.wiz_win_obj \
        and self.wiz_win_obj.ytdl_update_current is not None:
            ytdl_update_current = self.wiz_win_obj.ytdl_update_current
        else:
            ytdl_update_current = self.app_obj.ytdl_update_current

        # Special case: install yt-dlp with no dependencies, if required
        if (
            (
                not self.wiz_win_obj \
                and self.app_obj.ytdl_fork == 'yt-dlp' \
                and self.app_obj.ytdl_fork_no_dependency_flag
            ) or (
                self.wiz_win_obj \
                and self.wiz_win_obj.ytdl_fork == 'yt-dlp' \
                and self.wiz_win_obj.ytdl_fork_no_dependency_flag
            )
        ):
            if ytdl_update_current == 'ytdl_update_pip':
                ytdl_update_current = 'ytdl_update_pip_no_dependencies'

            elif ytdl_update_current == 'ytdl_update_pip3' \
            or ytdl_update_current == 'ytdl_update_pip3_recommend':
                ytdl_update_current = 'ytdl_update_pip3_no_dependencies'

            elif ytdl_update_current == 'ytdl_update_win_64':
                ytdl_update_current = 'ytdl_update_win_64_no_dependencies'

            elif ytdl_update_current == 'ytdl_update_win_32':
                ytdl_update_current = 'ytdl_update_win_32_no_dependencies'

        # Prepare a system command...
        if os.name == 'nt' \
        and ytdl_update_current == 'ytdl_update_custom_path' \
        and re.search('\.exe$', self.app_obj.ytdl_path):
            # Special case: on MS Windows, a custom path may point at an .exe,
            #   therefore 'python3' must be removed from the system command
            #   (we can't run 'python3.exe youtube-dl.exe' or anything like
            #   that)
            cmd_list = [self.app_obj.ytdl_path, '-U']

        else:
            cmd_list = self.app_obj.ytdl_update_dict[ytdl_update_current]

        mod_list = []
        for arg in cmd_list:

            # Substitute in the fork, if one is specified
            arg = self.app_obj.check_downloader(arg, self.wiz_win_obj)
            # Convert a path beginning with ~ (not on MS Windows)
            if os.name != 'nt':
                arg = re.sub('^\~', os.path.expanduser('~'), arg)

            mod_list.append(arg)

        # ...and display it in the Output tab (if required)
        self.install_ytdl_write_output(
            ' '.join(mod_list),
            True,  # A system command, not a message
        )

        # Create a new child process using that command...
        self.create_child_process(mod_list)
        # ...and set up the PipeReader objects to read from the child process
        #   STDOUT and STDERR
        if self.child_process is not None:
            self.stdout_reader.attach_fh(self.child_process.stdout)
            self.stderr_reader.attach_fh(self.child_process.stderr)

        while self.is_child_process_alive():

            # Pause a moment between each iteration of the loop (we don't want
            #   to hog system resources)
            time.sleep(self.sleep_time)

            # Read from the child process STDOUT and STDERR, in the correct
            #   order, until there is nothing left to read
            while self.read_ytdl_child_process(downloader):
                pass

        # (Generate our own error messages for debugging purposes, in certain
        #   situations)
        if self.child_process is None:

            msg = _('Update did not start')

            self.stderr_list.append(msg)
            self.install_ytdl_write_output(msg)

        elif self.child_process.returncode > 0:

            msg = _('Child process exited with non-zero code: {}').format(
                self.child_process.returncode, )

            self.stderr_list.append(msg)
            self.install_ytdl_write_output(msg)

        # Operation complete. self.success_flag is checked by
        #   mainapp.TartubeApp.update_manager_finished
        if not self.stderr_list:
            self.success_flag = True

        # Show a confirmation in the the Output tab (or wizard window textview)
        self.install_ytdl_write_output(_('Update operation finished'))

        # Let the timer run for a few more seconds to prevent Gtk errors (for
        #   systems with Gtk < 3.24)
        GObject.timeout_add(
            0,
            self.app_obj.update_manager_halt_timer,
        )
Beispiel #21
0
    def install_streamlink(self):
        """Called by self.run().

        A modified version of self.install_ytdl, that installs streamlink on an
        MS Windows system.

        Creates a child process to run the installation process.

        Reads from the child process STDOUT and STDERR, and calls the main
        application with the result of the update (success or failure).
        """

        # Show information about the update operation in the Output tab
        self.install_streamlink_write_output(
            _('Starting update operation, installing streamlink'), )

        # Create a new child process to install either the 64-bit or 32-bit
        #   version of streamlink, as appropriate
        if sys.maxsize <= 2147483647:
            binary = 'mingw-w64-i686-streamlink'
        else:
            binary = 'mingw-w64-x86_64-streamlink'

        # Prepare a system command...
        cmd_list = ['pacman', '-S', binary, '--noconfirm']
        # ...and display it in the Output tab (if required)
        self.install_streamlink_write_output(
            ' '.join(cmd_list),
            True,  # A system command, not a message
        )

        # Create a new child process using that command...
        self.create_child_process(cmd_list)
        # ...and set up the PipeReader objects to read from the child process
        #   STDOUT and STDERR
        if self.child_process is not None:
            self.stdout_reader.attach_fh(self.child_process.stdout)
            self.stderr_reader.attach_fh(self.child_process.stderr)

        while self.is_child_process_alive():

            # Pause a moment between each iteration of the loop (we don't want
            #   to hog system resources)
            time.sleep(self.sleep_time)

            # Read from the child process STDOUT and STDERR, in the correct
            #   order, until there is nothing left to read
            while self.read_streamlink_child_process():
                pass

        # (Generate our own error messages for debugging purposes, in certain
        #   situations)
        if self.child_process is None:
            self.stderr_list.append(_('streamlink installation did not start'))

        elif self.child_process.returncode > 0:
            self.stderr_list.append(
                _('Child process exited with non-zero code: {}').format(
                    self.child_process.returncode, ))

        # Operation complete. self.success_flag is checked by
        #   mainapp.TartubeApp.update_manager_finished()
        if not self.stderr_list:
            self.success_flag = True

        # Show a confirmation in the the Output tab (or wizard window textview)
        self.install_streamlink_write_output(_('Update operation finished'))

        # Let the timer run for a few more seconds to prevent Gtk errors
        GObject.timeout_add(
            0,
            self.app_obj.update_manager_halt_timer,
        )
Beispiel #22
0
    def check_video_corrupt(self, media_data_obj):
        """Called by self.tidy_directory().

        Checks all child videos of the specified media data object. If the
        video are corrupted, don't delete them (let the user do that manually).

        Args:

            media_data_obj (media.Channel, media.Playlist or media.Folder):
                The media data object whose directory must be tidied up

        """

        for video_obj in media_data_obj.compile_all_videos([]):

            if video_obj.file_name is not None \
            and video_obj.dl_flag:

                video_path = video_obj.get_actual_path(self.app_obj)

                if os.path.isfile(video_path):

                    # Code copied from
                    #   mainapp.TartubeApp.update_video_from_filesystem()
                    # When the video file is corrupted, moviepy freezes
                    #   indefinitely
                    # Instead, let's try placing the procedure inside a thread
                    #   (unlike the original function, this one is never called
                    #   if .refresh_moviepy_timeout is 0)
                    this_thread = threading.Thread(
                        target=self.call_moviepy,
                        args=(
                            video_obj,
                            video_path,
                        ),
                    )

                    this_thread.daemon = True
                    this_thread.start()
                    this_thread.join(self.app_obj.refresh_moviepy_timeout)
                    if this_thread.is_alive():

                        # moviepy timed out, so assume the video is corrupted
                        self.video_corrupt_count += 1

                        if self.del_corrupt_flag \
                        and os.path.isfile(video_path):

                            # Delete the corrupted file
                            os.remove(video_path)

                            self.video_corrupt_deleted_count += 1

                            self.app_obj.main_win_obj.output_tab_write_stdout(
                                1,
                                '   ' +
                                _('Deleted (possibly) corrupted video file:', )
                                + ' \'' + video_obj.name + '\'',
                            )

                            self.app_obj.mark_video_downloaded(
                                video_obj,
                                False,
                            )

                        else:

                            # Don't delete it
                            self.app_obj.main_win_obj.output_tab_write_stdout(
                                1,
                                '   ' + _('Video file might be corrupt:', ) +
                                ' \'' + video_obj.name + '\'',
                            )
Beispiel #23
0
    LOCALE_DICT[key] = value

# Some icons are different at Christmas
today = datetime.date.today()
day = today.strftime("%d")
month = today.strftime("%m")
if (int(month) == 12 and int(day) >= 24) \
or (int(month) == 1 and int(day) <= 5):
    xmas_flag = True
else:
    xmas_flag = False

# Standard list and dictionaries
time_metric_setup_list = [
    'seconds',
    _('seconds'),
    1,
    'minutes',
    _('minutes'),
    60,
    'hours',
    _('hours'),
    int(60 * 60),
    'days',
    _('days'),
    int(60 * 60 * 24),
    'weeks',
    _('weeks'),
    int(60 * 60 * 24 * 7),
    'years',
    _('years'),
Beispiel #24
0
    def process_video(self, video_obj):

        """Called by self.run().

        Sends a single video to FFmpeg for post-processing.

        Args:

            video_obj (media.Video): The video to be sent to FFmpeg

        """

        # Get the path to the video file, which might be in the directory of
        #   its parent channel/playlist/folder, or in a different directory
        #   altogether
        input_path = video_obj.get_actual_path(self.app_obj)
        # Set the output path; the same as the input path, unless the user has
        #   requested changes
        output_file, output_ext = os.path.splitext(input_path)

        if self.add_string != '':
            output_file += self.add_string

        if self.substitute_string != '':
            output_file = re.sub(
                self.regex_string,
                self.substitute_string,
                output_file,
            )

        if self.ext_string != '':
            output_ext = self.ext_string

        output_path = output_file + output_ext

        # Update the main window's progress bar
        self.job_count += 1
        GObject.timeout_add(
            0,
            self.app_obj.main_win_obj.update_progress_bar,
            video_obj.name,
            self.job_count,
            self.job_total,
        )

        # Update our progress in the Output Tab
        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Video') + ' ' + str(self.job_count) + '/' \
            + str(self.job_total) + ': ' + video_obj.name,
        )

        # Show the system command we're about to execute...
        test_list = self.app_obj.ffmpeg_manager_obj.run_ffmpeg(
            input_path,
            output_path,
            self.option_list,
            True,
        )

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '      ' + _('Input:') + ' ' + ' '.join(test_list[1]),
        )

        # ...and then send the command to FFmpeg for processing, which returns
        #   a list in the form (success_flag, optional_message)
        result_list = self.app_obj.ffmpeg_manager_obj.run_ffmpeg(
            input_path,
            output_path,
            self.option_list,
        )

        if not result_list or not result_list[0]:
            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '      ' + _('Output: FAILED:') + ' ' + result_list[1],
            )

        else:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                '      ' + _('Output:') + ' ' +  output_path,
            )

            # Delete the original video file, and update media.Video IVs, if
            #   required
            if self.delete_flag \
            and os.path.isfile(input_path) \
            and os.path.isfile(output_path) \
            and input_path != output_path:
                os.remove(input_path)
                video_obj.set_file(output_file, output_ext)
Beispiel #25
0
    def process_video(self, video_obj):
        """Called by self.run().

        Sends a single video to FFmpeg for post-processing.

        Args:

            video_obj (media.Video): The video to be sent to FFmpeg

        """

        self.job_count += 1

        # Update our progress in the Output Tab
        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            _('Video') + ' ' + str(self.job_count) + '/' \
            + str(self.job_total) + ': ' + video_obj.name,
        )

        # mainwin.MainWin.on_video_catalogue_process_ffmpeg_multi() should have
        #   filtered any media.Video objects whose .file_name is unknown, but
        #   just in case, check again
        # (Special case: 'dummy' video objects (those downloaded in the Classic
        #   Mode tab) use different IVs)
        if video_obj.file_name is None \
        and (not video_obj.dummy_flag or video_obj.dummy_path is None):
            self.app_obj.main_win_obj.output_tab_write_stderr(
                1,
                _('FAILED: File name is not known'),
            )

            self.fail_count += 1

            return

        # Get the source/output files, ahd the FFmpeg system command (as a
        #   list)
        source_path, output_path, cmd_list = self.options_obj.get_system_cmd(
            self.app_obj,
            video_obj,
        )

        if source_path is None:

            self.app_obj.main_win_obj.output_tab_write_stderr(
                1,
                _('FAILED: File not found'),
            )

            self.fail_count += 1

            return

        # Update the main window's progress bar
        GObject.timeout_add(
            0,
            self.app_obj.main_win_obj.update_progress_bar,
            video_obj.name,
            self.job_count,
            self.job_total,
        )

        # Update the Output Tab again
        self.app_obj.main_win_obj.output_tab_write_system_cmd(
            1,
            ' '.join(cmd_list),
        )

        # Process the video
        success_flag, msg \
        = self.app_obj.ffmpeg_manager_obj.run_ffmpeg_with_options(
            video_obj,
            source_path,
            cmd_list,
        )

        if not success_flag:

            self.fail_count += 1

            self.app_obj.main_win_obj.output_tab_write_stderr(
                1,
                _('FAILED:') + ' ' + msg,
            )

        else:

            self.success_count += 1

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                _('Output file:') + ' ' + output_path,
            )

            # Delete the original video file, if required
            if self.options_obj.options_dict['delete_original_flag'] \
            and os.path.isfile(source_path) \
            and os.path.isfile(output_path) \
            and source_path != output_path:

                try:

                    os.remove(source_path)

                except:

                    self.fail_count += 1

                    self.app_obj.main_win_obj.output_tab_write_stderr(
                        1,
                        _('Could not delete the original file:') + ' ' \
                        + source_path,
                    )

            # Ignoring changes to the extension, has the video/audio filename
            #   changed?
            new_dir, new_file = os.path.split(output_path)
            new_name, new_ext = os.path.splitext(new_file)
            old_name = video_obj.name

            rename_flag = False
            if (
                self.options_obj.options_dict['add_end_filename'] != '' \
                or self.options_obj.options_dict['regex_match_filename'] \
                != '' \
            ) and old_name != new_name:
                rename_flag = True

            # If the flag is set, rename a thumbnail file to match the
            #   video file
            if rename_flag \
            and self.options_obj.options_dict['rename_both_flag']:

                thumb_path = utils.find_thumbnail(
                    self.app_obj,
                    video_obj,
                    True,  # Rename a temporary thumbnail too
                )

                if thumb_path:

                    thumb_name, thumb_ext = os.path.splitext(thumb_path)
                    new_thumb_path = os.path.abspath(
                        os.path.join(
                            new_dir,
                            new_name + thumb_ext,
                        ), )

                    # (Don't call utils.rename_file(), as we need our own
                    #   try/except)
                    try:

                        # (On MSWin, can't do os.rename if the destination file
                        #   already exists)
                        if os.path.isfile(new_thumb_path):
                            os.remove(new_thumb_path)

                        # (os.rename sometimes fails on external hard drives;
                        #   this is safer)
                        shutil.move(thumb_path, new_thumb_path)

                    except:

                        self.fail_count += 1

                        self.app_obj.main_win_obj.output_tab_write_stderr(
                            1,
                            _('Could not rename the thumbnail:') + ' ' \
                            + thumb_path,
                        )

            # If a video/audio file was processed, update its filename
            if self.options_obj.options_dict['input_mode'] != 'thumb':

                if not video_obj.dummy_flag:
                    video_obj.set_file_from_path(output_path)
                else:
                    video_obj.set_dummy_path(output_path)

                # Also update its .name IV (but its .nickname)
                if rename_flag:
                    video_obj.set_name(new_name)
Beispiel #26
0
    def run(self):

        """Called as a result of self.__init__().

        Compiles a list of media data objects (channels, playlists and folders)
        to refresh. If self.init_obj is not set, only that channel/playlist/
        folder (and its child channels/playlists/folders) are refreshed;
        otherwise the whole media registry is refreshed.

        Then calls self.refresh_from_default_destination() for each item in the
        list.

        Finally informs the main application that the refresh operation is
        complete.
        """

        if DEBUG_FUNC_FLAG:
            utils.debug_time('rop 143 run')

        # Show information about the refresh operation in the Output Tab
        if not self.init_obj:
            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                _('Starting refresh operation, analysing whole database'),
            )

        else:

            media_type = self.init_obj.get_type()

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                _('Starting refresh operation, analysing \'{}\'').format(
                    self.init_obj.name,
                ),
            )

        # Compile a list of channels, playlists and folders to refresh (each
        #   one has their own sub-directory inside Tartube's data directory)
        obj_list = []
        if self.init_obj:
            # Add this channel/playlist/folder, and any child channels/
            #   playlists/folders (but not videos, obviously)
            obj_list = self.init_obj.compile_all_containers(obj_list)
        else:
            # Add all channels/playlists/folders in the database
            for dbid in list(self.app_obj.media_name_dict.values()):

                obj = self.app_obj.media_reg_dict[dbid]
                # Don't add private folders
                if not isinstance(obj, media.Folder) or not obj.priv_flag:
                    obj_list.append(obj)

        self.job_total = len(obj_list)

        # Check each sub-directory in turn, updating the media data registry
        #   as we go
        while self.running_flag and obj_list:

            obj = obj_list.pop(0)

            if obj.dbid == obj.master_dbid:
                self.refresh_from_default_destination(obj)
            else:
                self.refresh_from_actual_destination(obj)

            # Pause a moment, before the next iteration of the loop (don't want
            #   to hog resources)
            time.sleep(self.sleep_time)

        # Operation complete. Set the stop time
        self.stop_time = int(time.time())

        # Show a confirmation in the Output Tab
        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            _('Refresh operation finished'),
        )

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Number of video files analysed:') + '             ' \
            + str(self.video_total_count),
        )

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Video files already in the database:') + '        ' \
            + str(self.video_match_count),
        )

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('New videos found and added to the database:') + ' ' \
            +  str(self.video_new_count),
        )

        # Let the timer run for a few more seconds to prevent Gtk errors (for
        #   systems with Gtk < 3.24)
        GObject.timeout_add(
            0,
            self.app_obj.refresh_manager_halt_timer,
        )
Beispiel #27
0
    def run_check_version(self):

        """Called by self.run().

        Checking for a new release of Tartube doesn't involve any system
        commands or child processes, so it is handled separately by this
        function.

        There is a stable release at Sourceforge, and a development release at
        Github. Fetch the VERSION file from each, and store the stable/
        development versions, so that mainapp.TartubeApp.info_manager_finished
        can display them.
        """

        # Show information about the info operation in the Output Tab
        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            _('Starting info operation, checking for new releases of Tartube'),
        )

        # Check the stable version, http://tartube.sourceforge.io/VERSION
        stable_path = __main__.__website__ + '/VERSION'

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            _('Checking stable release...'),
        )

        self.app_obj.main_win_obj.output_tab_write_system_cmd(1, stable_path)

        try:
            request_obj = requests.get(
                stable_path,
                timeout = self.app_obj.request_get_timeout,
            )

            response = utils.strip_whitespace(request_obj.text)
            if not re.search('^\d+\.\d+\.\d+\s*$', response):

                self.app_obj.main_win_obj.output_tab_write_stdout(
                    1,
                    _('Ignoring invalid version'),
                )

            else:

                self.stable_version = response

                self.app_obj.main_win_obj.output_tab_write_stdout(
                    1,
                    _('Retrieved version:') + ' ' + str(response),
                )

        except:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                _('Connection failed'),
            )

        # Check the development version,
        #   http://raw.githubusercontent.com/axcore/tartube/master/VERSION
        dev_path = __main__.__website_dev__ + '/VERSION'

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            _('Checking development release...'),
        )

        self.app_obj.main_win_obj.output_tab_write_system_cmd(1, dev_path)

        try:
            request_obj = requests.get(
                dev_path,
                timeout = self.app_obj.request_get_timeout,
            )

            response = utils.strip_whitespace(request_obj.text)
            if not re.search('^\d+\.\d+\.\d+\s*$', response):

                self.app_obj.main_win_obj.output_tab_write_stdout(
                    1,
                    _('Ignoring invalid version'),
                )

            else:

                self.dev_version = response

                self.app_obj.main_win_obj.output_tab_write_stdout(
                    1,
                    _('Retrieved version:') + ' ' + str(response),
                )

        except:

            self.app_obj.main_win_obj.output_tab_write_stdout(
                1,
                _('Connection failed'),
            )

        # Operation complete. self.success_flag is checked by
        #   mainapp.TartubeApp.info_manager_finished()
        self.success_flag = True

        # Show a confirmation in the the Output Tab
        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            _('Info operation finished'),
        )

        # Let the timer run for a few more seconds to prevent Gtk errors (for
        #   systems with Gtk < 3.24)
        GObject.timeout_add(
            0,
            self.app_obj.info_manager_halt_timer,
        )
Beispiel #28
0
    def refresh_from_default_destination(self, media_data_obj):

        """Called by self.run().

        Refreshes a single channel, playlist or folder, for which an
        alternative download destination has not been set.

        If a file is missing in the channel/playlist/folder's sub-directory,
        mark the video object as not downloaded.

        If unexpected video files exist in the sub-directory, create a new
        media.Video object for them.

        Args:

            media_data_obj (media.Channel, media.Playlist or media.Folder):
                The media data object to refresh

        """

        if DEBUG_FUNC_FLAG:
            utils.debug_time('rop 252 refresh_from_default_destination')

        # Update the main window's progress bar
        self.job_count += 1
        GObject.timeout_add(
            0,
            self.app_obj.main_win_obj.update_progress_bar,
            media_data_obj.name,
            self.job_count,
            self.job_total,
        )

        # Keep a running total of matched/new videos for this channel, playlist
        #   or folder
        local_total_count = 0
        local_match_count = 0
        local_new_count = 0

        # Update our progress in the Output Tab
        if isinstance(media_data_obj, media.Channel):
            string = _('Channel:') + '  '
        elif isinstance(media_data_obj, media.Playlist):
            string = _('Playlist:') + ' '
        else:
            string = _('Folder:') + '   '

        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            string + media_data_obj.name,
        )

        # Get the sub-directory for this media data object
        dir_path = media_data_obj.get_default_dir(self.app_obj)

        # Get a list of video files in the sub-directory
        try:
            init_list = os.listdir(dir_path)
        except:
            # Can't read the directory
            return

        # From this list, filter out files without a recognised file extension
        #   (.mp4, .webm, etc)
        mod_list = []
        for relative_path in init_list:

            # (If self.stop_refresh_operation() has been called, give up
            #   immediately)
            if not self.running_flag:
                return

            filename, ext = os.path.splitext(relative_path)
            # (Remove the initial .)
            ext = ext[1:]
            if ext in formats.VIDEO_FORMAT_DICT:

                mod_list.append(relative_path)

        # From the new list, filter out duplicate filenames (e.g. if the list
        #   contains both 'my_video.mp4' and 'my_video.webm', filter out the
        #   second one, adding to a list of alternative files)
        filter_list = []
        filter_dict = {}
        alt_list = []
        for relative_path in mod_list:

            # (If self.stop_refresh_operation() has been called, give up
            #   immediately)
            if not self.running_flag:
                return

            filename, ext = os.path.splitext(relative_path)

            if not filename in filter_dict:
                filter_list.append(relative_path)
                filter_dict[filename] = relative_path

            else:
                alt_list.append(relative_path)

        # Now compile a dictionary of media.Video objects in this channel/
        #   playlist/folder, so we can eliminate them one by one
        check_dict = {}
        for child_obj in media_data_obj.child_list:

            # (If self.stop_refresh_operation() has been called, give up
            #   immediately)
            if not self.running_flag:
                return

            if isinstance(child_obj, media.Video) and child_obj.file_name:

                # Does the video file still exist?
                this_file = child_obj.file_name + child_obj.file_ext
                if child_obj.dl_flag and not this_file in init_list:
                    self.app_obj.mark_video_downloaded(child_obj, False)
                else:
                    check_dict[child_obj.file_name] = child_obj

        # If this channel/playlist/folder is the alternative download
        #   destination for other channels/playlists/folders, compile a
        #   dicationary of their media.Video objects
        # (If we find a video we weren't expecting, before creating a new
        #   media.Video object, we must first check it isn't one of them)
        slave_dict = {}
        for slave_dbid in media_data_obj.slave_dbid_list:

            # (If self.stop_refresh_operation() has been called, give up
            #   immediately)
            if not self.running_flag:
                return

            slave_obj = self.app_obj.media_reg_dict[slave_dbid]
            for child_obj in slave_obj.child_list:

                if isinstance(child_obj, media.Video) and child_obj.file_name:
                    slave_dict[child_obj.file_name] = child_obj

        # Now try to match each video file (in filter_list) with an existing
        #   media.Video object (in check_dict)
        # If there is no match, and if the video file doesn't match a video
        #   in another channel/playlist/folder (for which this is the
        #   alternative download destination), then we can create a new
        #   media.Video object
        for relative_path in filter_list:

            # (If self.stop_refresh_operation() has been called, give up
            #   immediately)
            if not self.running_flag:
                return

            filename, ext = os.path.splitext(relative_path)

            if self.app_obj.refresh_output_videos_flag:

                self.app_obj.main_win_obj.output_tab_write_stdout(
                    1,
                    '   ' + _('Checking:') + ' '  + filename,
                )

            if filename in check_dict:

                # File matched
                self.video_total_count += 1
                local_total_count += 1
                self.video_match_count += 1
                local_match_count += 1

                # If it is not marked as downloaded, we can mark it so now
                child_obj = check_dict[filename]
                if not child_obj.dl_flag:
                    self.app_obj.mark_video_downloaded(child_obj, True)

                # Make sure the stored extension is correct (e.g. if we've
                #   matched an existing .webm video file, with an expected
                #   .mp4 video file)
                if child_obj.file_ext != ext:
                    child_relative_path \
                    = child_obj.file_name + child_obj.file_ext

                    if not child_relative_path in alt_list:
                        child_obj.set_file(filename, ext)

                # Eliminate this media.Video object; no other video file should
                #   match it
                del check_dict[filename]

                # Update our progress in the Output Tab (if required)
                if self.app_obj.refresh_output_videos_flag:
                    self.app_obj.main_win_obj.output_tab_write_stdout(
                        1,
                    '   ' + _('Match:') + ' '  + filename,
                    )

            elif filename not in slave_dict:

                # File didn't match a media.Video object
                self.video_total_count += 1
                local_total_count += 1
                self.video_new_count += 1
                local_new_count += 1

                # Display the list of non-matching videos, if required
                if self.app_obj.refresh_output_videos_flag \
                and self.app_obj.refresh_output_verbose_flag:

                    for failed_path in check_dict.keys():
                        self.app_obj.main_win_obj.output_tab_write_stdout(
                            1,
                            '   ' + _('Non-match:') + ' '  + filename,
                        )

                # Create a new media.Video object
                video_obj = self.app_obj.add_video(media_data_obj, None)
                video_path = os.path.abspath(
                    os.path.join(
                        dir_path,
                        filter_dict[filename],
                    )
                )

                # Set the new video object's IVs
                filename, ext = os.path.splitext(filter_dict[filename])
                video_obj.set_name(filename)
                video_obj.set_nickname(filename)
                video_obj.set_file(filename, ext)

                if ext == '.mkv':
                    video_obj.set_mkv()

                video_obj.set_file_size(
                    os.path.getsize(
                        os.path.abspath(
                            os.path.join(dir_path, filter_dict[filename]),
                        ),
                    ),
                )

                # If the video's JSON file exists downloaded, we can extract
                #   video statistics from it
                self.app_obj.update_video_from_json(video_obj)

                # For any of those statistics that haven't been set (because
                #   the JSON file was missing or didn't contain the right
                #   statistics), set them directly
                self.app_obj.update_video_from_filesystem(
                    video_obj,
                    video_path,
                )

                # This call marks the video as downloaded, and also updates the
                #   Video Index and Video Catalogue (if required)
                self.app_obj.mark_video_downloaded(video_obj, True)

                if self.app_obj.refresh_output_videos_flag:
                    self.app_obj.main_win_obj.output_tab_write_stdout(
                        1,
                        '   ' + _('New video:') + ' '  + filename,
                    )

        # Check complete, display totals
        self.app_obj.main_win_obj.output_tab_write_stdout(
            1,
            '   ' + _('Total videos:') + ' ' + str(local_total_count) \
            + ', ' + _('matched:') + ' ' + str(local_match_count) \
            + ', ' + _('new:') + ' ' + str(local_new_count),
        )
Beispiel #29
0
    def install_ytdl(self):
        """Called by self.run().

        Based on code from downloads.VideoDownloader.do_download().

        Creates a child process to run the youtube-dl update.

        Reads from the child process STDOUT and STDERR, and calls the main
        application with the result of the update (success or failure).
        """

        # Show information about the update operation in the Output Tab (or in
        #   the setup wizard window, if called from there)
        downloader = self.app_obj.get_downloader(self.wiz_win_obj)
        self.install_ytdl_write_output(
            _('Starting update operation, installing/updating ' +
              downloader), )

        # Prepare the system command

        # The user can change the system command for updating youtube-dl,
        #   depending on how it was installed
        # (For example, if youtube-dl was installed via pip, then it must be
        #   updated via pip)
        if self.wiz_win_obj \
        and self.wiz_win_obj.ytdl_update_current is not None:
            ytdl_update_current = self.wiz_win_obj.ytdl_update_current
        else:
            ytdl_update_current = self.app_obj.ytdl_update_current

        cmd_list = self.app_obj.ytdl_update_dict[ytdl_update_current]

        mod_list = []
        for arg in cmd_list:

            # Substitute in the fork, if one is specified
            arg = self.app_obj.check_downloader(arg, self.wiz_win_obj)
            # Convert a path beginning with ~ (not on MS Windows)
            if os.name != 'nt':
                arg = re.sub('^\~', os.path.expanduser('~'), arg)

            mod_list.append(arg)

        # Create a new child process using that command
        self.create_child_process(mod_list)

        # Show the system command in the Output Tab
        self.install_ytdl_write_output(
            ' '.join(mod_list),
            True,  # A system command, not a message
        )

        # So that we can read from the child process STDOUT and STDERR, attach
        #   a file descriptor to the PipeReader objects
        if self.child_process is not None:

            self.stdout_reader.attach_file_descriptor(
                self.child_process.stdout, )

            self.stderr_reader.attach_file_descriptor(
                self.child_process.stderr, )

        while self.is_child_process_alive():

            # Read from the child process STDOUT, and convert into unicode for
            #   Python's convenience
            while not self.stdout_queue.empty():

                stdout = self.stdout_queue.get_nowait().rstrip()
                if stdout:

                    if os.name == 'nt':
                        stdout = stdout.decode('cp1252')
                    else:
                        stdout = stdout.decode('utf-8')

                    # "It looks like you installed youtube-dl with a package
                    #   manager, pip, setup.py or a tarball. Please use that to
                    #   update."
                    # "The script youtube-dl is installed in '...' which is not
                    #   on PATH. Consider adding this directory to PATH..."
                    if re.search('It looks like you installed', stdout) \
                    or re.search(
                        'The script ' + downloader + ' is installed',
                        stdout,
                    ):
                        self.stderr_list.append(stdout)

                    else:

                        # Try to intercept the new version number for
                        #   youtube-dl
                        self.intercept_version_from_stdout(stdout)
                        self.stdout_list.append(stdout)

                    # Show command line output in the Output Tab (or wizard
                    #   window textview)
                    self.install_ytdl_write_output(stdout)

        # The child process has finished
        while not self.stderr_queue.empty():

            # Read from the child process STDERR queue (we don't need to read
            #   it in real time), and convert into unicode for python's
            #   convenience
            stderr = self.stderr_queue.get_nowait().rstrip()
            if os.name == 'nt':
                stderr = stderr.decode('cp1252')
            else:
                stderr = stderr.decode('utf-8')

            if stderr:

                # If the user has pip installed, rather than pip3, they will by
                #   now (mid-2019) be seeing a Python 2.7 deprecation warning.
                #   Ignore that message, if received
                # If a newer version of pip is available, the user will see a
                #   'You should consider upgrading' warning. Ignore that too,
                #   if received
                if not re.search('DEPRECATION', stderr) \
                and not re.search('You are using pip version', stderr) \
                and not re.search('You should consider upgrading', stderr):
                    self.stderr_list.append(stderr)

                # Show command line output in the Output Tab (or wizard window
                #   textview)
                self.install_ytdl_write_output(stderr)

        # (Generate our own error messages for debugging purposes, in certain
        #   situations)
        if self.child_process is None:

            msg = _('Update did not start')

            self.stderr_list.append(msg)
            self.install_ytdl_write_output(msg)

        elif self.child_process.returncode > 0:

            msg = _('Child process exited with non-zero code: {}').format(
                self.child_process.returncode, )

            self.stderr_list.append(msg)
            self.install_ytdl_write_output(msg)

        # Operation complete. self.success_flag is checked by
        #   mainapp.TartubeApp.update_manager_finished
        if not self.stderr_list:
            self.success_flag = True

        # Show a confirmation in the the Output Tab (or wizard window textview)
        self.install_ytdl_write_output(_('Update operation finished'))

        # Let the timer run for a few more seconds to prevent Gtk errors (for
        #   systems with Gtk < 3.24)
        GObject.timeout_add(
            0,
            self.app_obj.update_manager_halt_timer,
        )
Beispiel #30
0
    def setup_set_downloader_page(self):
        """Called by self.setup_page().

        Sets up the widget layout for a page.
        """

        grid_width = 3

        self.add_label(
            '<span font_size="large" style="italic">' \
            + _('Choose which downloader to use.') \
            + '</span>',
            0, 0, grid_width, 1,
        )

        # youtube-dlc
        radiobutton = self.setup_set_downloader_page_add_button(
            1,                  # Row number
            '<b>youtube-dlc</b>: <i>' \
            + self.app_obj.ytdl_fork_descrip_dict['youtube-dlc'] \
            + '</i>',
            _('Use youtube-dlc'),
        )

        # youtube-dl
        radiobutton2 = self.setup_set_downloader_page_add_button(
            2,                  # Row number
            '<b>youtube-dl</b>: <i>' \
            + self.app_obj.ytdl_fork_descrip_dict['youtube-dl'] \
            + '</i>',
            _('Use youtube-dl'),
            radiobutton,
        )

        # Any other fork
        radiobutton3, entry = self.setup_set_downloader_page_add_button(
            3,  # Row number
            self.app_obj.ytdl_fork_descrip_dict['custom'],
            _('Use a different fork of youtube-dl'),
            radiobutton2,
            True,  # Show an entry
        )

        # Set widgets to their initial state
        if self.ytdl_fork is None or self.ytdl_fork == 'youtube-dl':
            radiobutton2.set_active(True)
            entry.set_sensitive(False)
        elif self.ytdl_fork == 'youtube-dlc':
            radiobutton.set_active(True)
            entry.set_sensitive(False)
        else:
            radiobutton3.set_active(True)
            if self.ytdl_fork is not None:
                entry.set_text(self.ytdl_fork)
            else:
                entry.set_text('')
            entry.set_sensitive(True)

        # (Signal connects from the call to
        #   self.setup_set_downloader_page_add_button() )
        radiobutton.connect(
            'toggled',
            self.on_button_ytdl_fork_toggled,
            entry,
            'youtube-dlc',
        )
        radiobutton2.connect(
            'toggled',
            self.on_button_ytdl_fork_toggled,
            entry,
            'youtube-dl',
        )
        radiobutton3.connect(
            'toggled',
            self.on_button_ytdl_fork_toggled,
            entry,
        )
        entry.connect(
            'changed',
            self.on_entry_ytdl_fork_changed,
            radiobutton3,
        )