Ejemplo n.º 1
0
    def delete_thumb(self, media_data_obj):

        """Called by self.tidy_directory().

        Checks all child videos of the specified media data object. If the
        associated thumbnail file exists, delete it.

        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 937 delete_thumb')

        for video_obj in media_data_obj.compile_all_videos( [] ):

            if video_obj.file_name is not None:

                # Thumbnails might be in one of two locations
                thumb_path = utils.find_thumbnail(self.app_obj, video_obj)

                # If the video's parent container has an alternative download
                #   destination set, we must check the corresponding media
                #   data object. If the latter also has a media.Video object
                #   matching this video, then this function returns None and
                #   nothing is deleted
                if thumb_path is not None:

                    thumb_path = self.check_video_in_actual_dir(
                        media_data_obj,
                        video_obj,
                        thumb_path,
                    )

                if thumb_path is not None \
                and os.path.isfile(thumb_path):

                    # Delete the thumbnail file
                    os.remove(thumb_path)
                    self.thumb_deleted_count += 1
Ejemplo n.º 2
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)
Ejemplo n.º 3
0
    def get_system_cmd(self, app_obj, video_obj=None, start_point=None,
    stop_point=None, clip_title=None, clip_dir=None, edit_dict=[]):

        """Can be called by anything.

        Given the FFmpeg options specified by self.options_dict, generates the
        FFmpeg system command, returning it as a list of options.

        N.B. The 'delete_original_flag' option is not applied until the end of
        the process operation.

        Args:

            app_obj (mainapp.TartubeApp): The main application

            video_obj (media.Video or None): If specified, uses the video's
                downloaded file as the source file. Not specified when called
                from config.FFmpegOptionsEditWin, in which case a specimen
                source file is used (so that a specimen system command can be
                displayed in the edit window)

            start_point, stop_point, clip_title, clip_dir (str):
                When splitting a video, the points at which to start/stop
                (timestamps or values in seconds), the clip title, and the
                destination directory for sections (if not the same as the
                original file). Ignored if 'output_mode' is not 'split' or
                'slice'. If 'output_mode' is 'split' or 'slice', then these
                arguments are not specified when called from
                config.FFmpegOptionsEditWin, in which case specimen timestamps/
                titles are used

            edit_dict (dict): When called from the edit window, any changes
                that have been made to the FFmpeg options, but which have not
                yet been saved to this object. We take those changes into
                account when compiling the system command.

        Return values:

            Returns a tuple of three items:

                - The full path to the source file
                - The full path to the output file
                - A (python) list of options comprising the complete system
                    commmand (including the FFmpeg binary and the source/output
                    files)

        """

        opt_list = []
        tuning_list = []
        return_list = []

        # When called from the edit window (config.FFmpegOptionsEditWin), any
        #   changes in the edit window may not have been applied to
        #   self.options_dict yet
        # To produce an up-to-date system command, use a temporary copy of
        #   self.options_dict, to which the unapplied changes have been added
        options_dict = self.options_dict.copy()
        for key in edit_dict:
            options_dict[key] = edit_dict[key]

        # (Shortcuts to values retrieved several times)
        bitrate = options_dict['bitrate']
        input_mode = options_dict['input_mode']
        limit_buffer = options_dict['limit_buffer']
        limit_mbps = options_dict['limit_mbps']
        output_mode = options_dict['output_mode']
        rate_factor = options_dict['rate_factor']

        # The 'extra_cmd_string' item must be processed, and split into
        #   a list of separate items, preserving everything inside quotes as a
        #   single item (just as we do for youtube-dl download options)
        extra_cmd_string = options_dict['extra_cmd_string']
        if extra_cmd_string != '':
            extra_cmd_list = utils.parse_options(extra_cmd_string)
        else:
            extra_cmd_list = []

        # FFmpeg binary
        binary = app_obj.ffmpeg_manager_obj.get_executable()

        # Set variables describing the full path to the source video/audio and/
        #   or source thumbnail files

        # If no media.Video object was specified, then use specimen paths that
        #   can be displayed in the edit window's textview
        if video_obj is None:

            source_video_path = 'source.ext'
            source_audio_path = 'source.ext'
            source_thumb_path = 'source.jpg'

        else:

            if video_obj.dummy_flag:

                # (Special case: 'dummy' video objects (those downloaded in the
                #   Classic Mode tab) use different IVs)

                # If a specified media.Video has an unknown path, then return
                #   an empty list; there is nothing for FFmpeg to convert
                if video_obj.dummy_path is None:
                    return None

                # Check the video/audio file actually exists. If not, there is
                #   nothing for FFmpeg to convert
                source_video_path = video_obj.dummy_path
                if not os.path.exists(source_video_path) \
                and input_mode == 'video':
                    return None, None, []

            else:

                # If a specified media.Video has an unknown filename, then
                #   return an empty list; there is nothing for FFmpeg to
                #   convert
                if video_obj.file_name is None:
                    return None, None, []

                # Check the video/audio file actually exists, and is marked as
                #   downloaded. If not, there is nothing for FFmpeg to convert
                source_video_path = video_obj.get_actual_path(app_obj)
                if (
                    not os.path.exists(source_video_path) \
                    or not video_obj.dl_flag
                ) and input_mode == 'video':
                    return None, None, []

            # Find the video's thumbnail
            source_thumb_path = utils.find_thumbnail(app_obj, video_obj, True)
            if source_thumb_path is None and input_mode == 'thumb':
                # Return an empty list; there is nothing for FFmpeg to convert
                return None, None, []

            # If 'output_mode' is 'merge', then look for an audio file with the
            #   same name as the video file (but otherwise don't bother)
            source_audio_path = None
            if output_mode == 'merge':

                name, video_ext = os.path.splitext(source_video_path)
                for audio_ext in formats.AUDIO_FORMAT_LIST:

                    audio_path = os.path.abspath(os.path.join(name, audio_ext))
                    if os.path.isfile(audio_path):
                        source_audio_path = audio_path
                        break

                if source_audio_path is None:
                    # Nothing merge
                    return None, None, []

        # Break down the full path into its components, so that we can set the
        #   output file, after applying optional modifications
        if input_mode == 'video':
            output_dir, output_file = os.path.split(source_video_path)
        else:
            output_dir, output_file = os.path.split(source_thumb_path)

        output_name, output_ext = os.path.splitext(output_file)

        add_end_filename = options_dict['add_end_filename']
        if add_end_filename != '':

            # Remove trailing whitepsace
            add_end_filename = re.sub(
                r'\s+$',
                '',
                options_dict['add_end_filename'],
            )

            # Update the filename
            output_name += add_end_filename

        regex_match_filename = options_dict['regex_match_filename']
        if regex_match_filename != '':

            output_name = re.sub(
                regex_match_filename,
                options_dict['regex_apply_subst'],
                output_name,
            )

        change_file_ext = options_dict['change_file_ext'].lower()
        if change_file_ext == '':
            output_file = output_name + output_ext
        else:
            output_file = output_name + '.' + change_file_ext

        if video_obj is None:
            output_path = output_file
        else:
            output_path = os.path.abspath(
                os.path.join(output_dir, output_file),
            )

        # Special case: when called from config.FFmpegOptionsEditWin, then show
        #   a specimen system command resembling the one that will eventually
        #   be generated by FFmpegManager.run_ffmpeg_multiple_files()
        if app_obj.ffmpeg_simple_options_flag \
        and video_obj is None \
        and output_mode != 'split' \
        and output_mode != 'slice':

            return_list.append(binary)
            return_list.append('-y')
            return_list.append('-loglevel')
            return_list.append('repeat+info')
            return_list.append('-i')

            if input_mode == 'video':
                return_list.append(source_video_path)
            else:
                return_list.append(source_thumb_path)

            if extra_cmd_list:
                return_list.extend(extra_cmd_list)

            return_list.extend(
                [
                    app_obj.ffmpeg_manager_obj._ffmpeg_filename_argument(
                        output_path,
                    ),
                ],
            )

            return source_video_path, output_path, return_list

        # When the full GUI layout is visible, apply all FFmpeg options
        if input_mode == 'video':

            opt_list.append('-i')
            opt_list.append(source_video_path)

        else:

            opt_list.append('-i')
            opt_list.append(source_thumb_path)

        # H.264
        if output_mode == 'h264':

            # In the original code, this was marked:
            #   Only necessary if the output filename does not end with .mp4
            opt_list.append('-c:v')
            opt_list.append(options_dict['gpu_encoding'])

            opt_list.append('-preset')
            opt_list.append(options_dict['patience_preset'])

            if options_dict['hw_accel'] != 'none':
                opt_list.append('-hwaccel')
                opt_list.append(options_dict['hw_accel'])

            if options_dict['tuning_film_flag']:
                tuning_list.append('film')
            if options_dict['tuning_animation_flag']:
                tuning_list.append('animation')
            if options_dict['tuning_grain_flag']:
                tuning_list.append('grain')
            if options_dict['tuning_still_image_flag']:
                tuning_list.append('stillimage')
            if options_dict['tuning_fast_decode_flag']:
                tuning_list.append('fastdecode')
            if options_dict['tuning_zero_latency_flag']:
                tuning_list.append('zerolatency')

            if tuning_list:
                opt_list.append('-tune')
                opt_list.append(','.join(tuning_list))

            if options_dict['fast_start_flag']:
                opt_list.append('-movflags')
                opt_list.append('faststart')

            if input_mode == 'video' and options_dict['audio_flag']:
                opt_list.append('-c:a')
                opt_list.append('aac')
                opt_list.append('-b:a')
                opt_list.append(
                    str(options_dict['audio_bitrate']) + 'k',
                )

            if options_dict['profile_flag'] and rate_factor != 0:
                opt_list.append('-profile:v')
                opt_list.append('baseline')
                opt_list.append('-level')
                opt_list.append('3.0')

            if options_dict['limit_flag']:
                opt_list.append('-maxrate')
                opt_list.append(str(limit_mbps) + 'M')
                opt_list.append('-bufsize')
                opt_list.append((str(limit_mbps) * str(limit_buffer)) + 'M')

            if options_dict['seek_flag']:

                # In the original code, this was marked:
                #   Inserts an I-frame every 15 frames
                opt_list.append('-x264-params')
                opt_list.append('keyint=15')

            # In the original code, this was marked:
            #   Preserves the frame timestamps of VFR videos
            opt_list.append('-vsync')
            opt_list.append('2')
            opt_list.append('-enc_time_base')
            opt_list.append('-1')

            if options_dict['quality_mode'] == 'crf':

                return_list.append(binary)
                return_list.extend(opt_list)
                return_list.append('-crf')
                return_list.append(str(rate_factor))

                if extra_cmd_list:
                    return_list.extend(extra_cmd_list)

                return_list.append(output_path)

            else:
                dummy_file = options_dict['dummy_file']
                if dummy_file == 'output':
                    dummy_file = output_path

                return_list.append(binary)
                return_list.append('-y')
                return_list.extend(opt_list)
                return_list.append('-b:v')
                return_list.append(str(bitrate))
                return_list.append('-pass')
                return_list.append('1')
                return_list.append('-f')
                return_list.append('mp4')
                return_list.append(dummy_file)

                return_list.append('&&')
                return_list.append(binary)
                return_list.extend(opt_list)
                return_list.append('-b:v')
                return_list.append(str(bitrate))
                return_list.append('-pass')
                return_list.append('2')

                if extra_cmd_list:
                    return_list.extend(extra_cmd_list)

                return_list.append(output_path)

        # GIF
        elif output_mode == 'gif':

            if options_dict['palette_mode'] == 'faster':

                return_list.append(binary)
                return_list.extend(opt_list)

                if extra_cmd_list:
                    return_list.extend(extra_cmd_list)

                return_list.append(output_name + '.gif')

            else:

                return_list.append(binary)
                return_list.extend(opt_list)
                return_list.append('-vf')
                return_list.append('palettegen')
                return_list.append('palette.png')

                return_list.append('&&')
                return_list.append(binary)
                return_list.extend(opt_list)
                return_list.append('-i')
                return_list.append('palette.png')
                return_list.append('-filter_complex')
                return_list.append('"[0:v][1:v] paletteuse"')

                if extra_cmd_list:
                    return_list.extend(extra_cmd_list)

                return_list.append(output_name + '.gif')

        # Merge video/audio
        elif output_mode == 'merge':

            return_list.append(binary)
            return_list.extend(opt_list)

            return_list.append('-i')
            return_list.append(source_audio_path)
            return_list.append('-c:v')
            return_list.append('copy')
            return_list.append('-c:a')
            return_list.append('copy')

            return_list.append(output_path)

        # Split video by timestamps, or times in seconds
        elif output_mode == 'split' or output_mode == 'slice':

            return_list.append(binary)
            return_list.extend(opt_list)

            if output_mode == 'split':

                return_list.append('-ss')
                if start_point is None:
                    # (A specimen timestamp)
                    return_list.append('0:00')
                else:
                    return_list.append(str(start_point))

                # (If no timestamp is specified, the end of the video is used)
                if stop_point is not None:
                    return_list.append('-to')
                    return_list.append(str(stop_point))

            else:

                return_list.append('-ss')
                if start_point is None:
                    # (A specimen time, in seconds)
                    return_list.append('0')
                else:
                    return_list.append(str(start_point))

                # (If no timestamp is specified, the end of the video is used)
                if stop_point is not None:
                    return_list.append('-to')
                    return_list.append(str(stop_point))

            if clip_title is None or clip_title == "":
                # (When called from config.FFmpegOptionsEditWin)
                clip_title = app_obj.split_video_generic_title

            if clip_dir is None:

                output_path = os.path.abspath(
                    os.path.join(output_dir, clip_title + output_ext),
                )

            else:

                output_path = os.path.abspath(
                    os.path.join(clip_dir, clip_title + output_ext),
                )

            return_list.append(output_path)

        # Video thumbnails
        else:

            return_list.append(binary)
            return_list.extend(opt_list)
            return_list.append(output_path)

        # All done
        if output_mode == 'thumb':
            return source_thumb_path, output_path, return_list
        else:
            return source_video_path, output_path, return_list
Ejemplo n.º 4
0
    def process_video(self, orig_video_obj, dest_dir=None, start_point=None, \
    stop_point=None, clip_title=None):
        """Called by self.run(), .slice_video() and .split_video().

        Sends a single video to FFmpeg for post-processing.

        Args:

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

            dest_dir (str): When splitting a video, the directory into which
                the video clips are saved (which may or may not be the same as
                the directory of the original file). Depending on settings, it
                may be the directory for a media.Folder object, or not. Not
                specified when not splitting a video

            start_point, stop_point (str): When splitting a video, the
                timestamps at which to start/stop (e.g. '15:29'). If
                'stop_point' is not specified, the clip ends at the end of
                the video. When removing video slices, the time (in seconds)
                at the beginning/end of each slice. If 'stop_point' is not
                specified, the slice ends at the end of the video

            clip_title (str): When splitting a video, the title of this video
                clip (if specified)

        Return values:

            True of success, False on failure

        """

        # 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 orig_video_obj.file_name is None \
        and (
            not orig_video_obj.dummy_flag
            or orig_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 False

        # Get the source/output files, ahd the full FFmpeg system command (as a
        #   list, and including the source/output files)
        source_path, output_path, cmd_list = self.options_obj.get_system_cmd(
            self.app_obj,
            orig_video_obj,
            start_point,
            stop_point,
            clip_title,
            dest_dir,
        )

        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 False

        # Update the main window's progress bar
        GObject.timeout_add(
            0,
            self.app_obj.main_win_obj.update_progress_bar,
            orig_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(
            orig_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,
            )

            return False

        else:

            self.success_count += 1

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

            # (If splitting files, there is nothing more to do)
            if start_point is not None:
                return True

            # Otherwise, 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:

                if not self.app_obj.remove_file(source_path):
                    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 = orig_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,
                    orig_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,
                        ), )

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

                    # (os.rename sometimes fails on external hard drives; this
                    #   is safer)
                    if not self.app_obj.move_file_or_directory(
                            thumb_path,
                            new_thumb_path,
                    ):
                        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 orig_video_obj.dummy_flag:
                    orig_video_obj.set_file_from_path(output_path)
                else:
                    orig_video_obj.set_dummy_path(output_path)

                # Also update its .name IV (but its .nickname)
                if rename_flag:
                    orig_video_obj.set_name(new_name)

            return True