def write_frames_of_scenes(video_path, output_dir, vid_name, l_scene_end):
    vid_read = get_reader(video_path)
    curr_scene = 1
    frame_num = 0

    for i, frame in tqdm(enumerate(vid_read)):
        if i in l_scene_end:
            curr_scene += 1
            frame_num = 0
        frame_num += 1
        file_name = '{}-scene{}-frame{}.png'.format(vid_name, curr_scene,
                                                    frame_num)
        cv2.imwrite(
            get_and_create_path(file_name,
                                output_dir + '/scene' + str(curr_scene)),
            cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))

    print('Completed writing files')
Exemple #2
0
def save_images(scene_list, video_manager, num_images=3, frame_margin=1,
                image_extension='jpg', encoder_param=95,
                image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER',
                output_dir=None, downscale_factor=1, show_progress=False,
                scale=None, height=None, width=None):
    # type: (List[Tuple[FrameTimecode, FrameTimecode]], VideoManager,
    #        Optional[int], Optional[int], Optional[str], Optional[int],
    #        Optional[str], Optional[str], Optional[int], Optional[bool],
    #        Optional[float], Optional[int], Optional[int])
    #       -> Dict[List[str]]
    """ Saves a set number of images from each scene, given a list of scenes
    and the associated video/frame source.

    Arguments:
        scene_list: A list of scenes (pairs of FrameTimecode objects) returned
            from calling a SceneManager's detect_scenes() method.
        video_manager: A VideoManager object corresponding to the scene list.
            Note that the video will be closed/re-opened and seeked through.
        num_images: Number of images to generate for each scene.  Minimum is 1.
        frame_margin: Number of frames to pad each scene around the beginning
            and end (e.g. moves the first/last image into the scene by N frames).
            Can set to 0, but will result in some video files failing to extract
            the very last frame.
        image_extension: Type of image to save (must be one of 'jpg', 'png', or 'webp').
        encoder_param: Quality/compression efficiency, based on type of image:
            'jpg' / 'webp':  Quality 0-100, higher is better quality.  100 is lossless for webp.
            'png': Compression from 1-9, where 9 achieves best filesize but is slower to encode.
        image_name_template: Template to use when creating the images on disk. Can
            use the macros $VIDEO_NAME, $SCENE_NUMBER, and $IMAGE_NUMBER. The image
            extension is applied automatically as per the argument image_extension.
        output_dir: Directory to output the images into.  If not set, the output
            is created in the working directory.
        downscale_factor: Integer factor to downscale images by.  No filtering
            is currently done, only downsampling (thus requiring an integer).
        show_progress: If True, shows a progress bar if tqdm is installed.
        scale: Optional factor by which to rescale saved images.A scaling factor of 1 would
            not result in rescaling. A value <1 results in a smaller saved image, while a
            value >1 results in an image larger than the original. This value is ignored if
            either the height or width values are specified.
        height: Optional value for the height of the saved images. Specifying both the height
            and width will resize images to an exact size, regardless of aspect ratio.
            Specifying only height will rescale the image to that number of pixels in height
            while preserving the aspect ratio.
        width: Optional value for the width of the saved images. Specifying both the width
            and height will resize images to an exact size, regardless of aspect ratio.
            Specifying only width will rescale the image to that number of pixels wide
            while preserving the aspect ratio.


    Returns:
        Dict[List[str]]: Dictionary of the format { scene_num : [image_paths] },
        where scene_num is the number of the scene in scene_list (starting from 1),
        and image_paths is a list of the paths to the newly saved/created images.

    Raises:
        ValueError: Raised if any arguments are invalid or out of range (e.g.
        if num_images is negative).
    """

    if not scene_list:
        return {}
    if num_images <= 0 or frame_margin < 0:
        raise ValueError()

    # TODO: Validate that encoder_param is within the proper range.
    # Should be between 0 and 100 (inclusive) for jpg/webp, and 1-9 for png.
    imwrite_param = [get_cv2_imwrite_params()[image_extension],
                     encoder_param] if encoder_param is not None else []

    video_name = video_manager.get_video_name()

    # Reset video manager and downscale factor.
    video_manager.release()
    video_manager.reset()
    video_manager.set_downscale_factor(downscale_factor)
    video_manager.start()

    # Setup flags and init progress bar if available.
    completed = True
    logging.info('Generating output images (%d per scene)...', num_images)
    progress_bar = None
    if show_progress and tqdm:
        progress_bar = tqdm(
            total=len(scene_list) * num_images,
            unit='images',
            dynamic_ncols=True)

    filename_template = Template(image_name_template)

    scene_num_format = '%0'
    scene_num_format += str(max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd'
    image_num_format = '%0'
    image_num_format += str(math.floor(math.log(num_images, 10)) + 2) + 'd'

    timecode_list = dict()

    fps = scene_list[0][0].framerate

    timecode_list = [
        [
            FrameTimecode(int(f), fps=fps) for f in [
                # middle frames
                a[len(a)//2] if (0 < j < num_images-1) or num_images == 1

                # first frame
                else min(a[0] + frame_margin, a[-1]) if j == 0

                # last frame
                else max(a[-1] - frame_margin, a[0])

                # for each evenly-split array of frames in the scene list
                for j, a in enumerate(np.array_split(r, num_images))
            ]
        ]
        for i, r in enumerate([
            # pad ranges to number of images
            r
            if 1+r[-1]-r[0] >= num_images
            else list(r) + [r[-1]] * (num_images - len(r))
            # create range of frames in scene
            for r in (
                range(start.get_frames(), end.get_frames())
                # for each scene in scene list
                for start, end in scene_list
                )
        ])
    ]

    image_filenames = {i: [] for i in range(len(timecode_list))}
    aspect_ratio = get_aspect_ratio(video_manager)
    if abs(aspect_ratio - 1.0) < 0.01:
        aspect_ratio = None

    for i, scene_timecodes in enumerate(timecode_list):
        for j, image_timecode in enumerate(scene_timecodes):
            video_manager.seek(image_timecode)
            ret_val, frame_im = video_manager.read()
            if ret_val:
                file_path = '%s.%s' % (
                    filename_template.safe_substitute(
                        VIDEO_NAME=video_name,
                        SCENE_NUMBER=scene_num_format % (i + 1),
                        IMAGE_NUMBER=image_num_format % (j + 1),
                        FRAME_NUMBER=image_timecode.get_frames()),
                    image_extension)
                image_filenames[i].append(file_path)
                if aspect_ratio is not None:
                    frame_im = cv2.resize(
                        frame_im, (0, 0), fx=aspect_ratio, fy=1.0,
                        interpolation=cv2.INTER_CUBIC)
                
                # Get frame dimensions prior to resizing or scaling
                frame_height = frame_im.shape[0]
                frame_width = frame_im.shape[1]

                # Figure out what kind of resizing needs to be done
                if height and width:
                    frame_im = cv2.resize(
                        frame_im, (width, height), interpolation=cv2.INTER_CUBIC)
                elif height and not width:
                    factor = height / float(frame_height)
                    width = int(factor * frame_width)
                    frame_im = cv2.resize(
                        frame_im, (width, height), interpolation=cv2.INTER_CUBIC)
                elif width and not height:
                    factor = width / float(frame_width)
                    height = int(factor * frame_height)
                    frame_im = cv2.resize(
                        frame_im, (width, height), interpolation=cv2.INTER_CUBIC)
                elif scale:
                    frame_im = cv2.resize(
                        frame_im, (0, 0), fx=scale, fy=scale,
                        interpolation=cv2.INTER_CUBIC)

                cv2.imwrite(
                    get_and_create_path(file_path, output_dir),
                    frame_im, imwrite_param)
            else:
                completed = False
                break
            if progress_bar:
                progress_bar.update(1)

    if not completed:
        logging.error('Could not generate all output images.')

    return image_filenames
def split_video_ffmpeg(input_video_paths, scene_list, output_file_template, video_name,
                       arg_override='-c:v libx264 -preset fast -crf 21 -c:a copy',
                       hide_progress=False, suppress_output=False):
    # type: (List[str], List[Tuple[FrameTimecode, FrameTimecode]], Optional[str],
    #        Optional[str], Optional[bool]) -> None
    """ Calls the ffmpeg command on the input video(s), generating a new video for
    each scene based on the start/end timecodes. """

    if not input_video_paths or not scene_list:
        return

    logging.info(
        'Splitting input video%s using ffmpeg, output path template:\n  %s',
        's' if len(input_video_paths) > 1 else '', output_file_template)

    if len(input_video_paths) > 1:
        # TODO: Add support for splitting multiple/appended input videos.
        # https://trac.ffmpeg.org/wiki/Concatenate#samecodec
        # Requires generating a temporary file list for ffmpeg.
        logging.error(
            'Sorry, splitting multiple appended/concatenated input videos with'
            ' ffmpeg is not supported yet. This feature will be added to a future'
            ' version of PySceneDetect. In the meantime, you can try using the'
            ' -c / --copy option with the split-video to use mkvmerge, which'
            ' generates less accurate output, but supports multiple input videos.')
        raise NotImplementedError()

    arg_override = arg_override.replace('\\"', '"')

    ret_val = None
    arg_override = arg_override.split(' ')
    filename_template = Template(output_file_template)
    scene_num_format = '%0'
    scene_num_format += str(max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd'

    try:
        progress_bar = None
        total_frames = scene_list[-1][1].get_frames() - scene_list[0][0].get_frames()
        if tqdm and not hide_progress:
            progress_bar = tqdm(total=total_frames, unit='frame', miniters=1)
        processing_start_time = time.time()
        for i, (start_time, end_time) in enumerate(scene_list):
            duration = (end_time - start_time)
            call_list = ['ffmpeg']
            if suppress_output:
                call_list += ['-v', 'quiet']
            elif i > 0:
                # Only show ffmpeg output for the first call, which will display any
                # errors if it fails, and then break the loop. We only show error messages
                # for the remaining calls.
                call_list += ['-v', 'error']
            call_list += [
                '-y',
                '-ss',
                start_time.get_timecode(),
                '-i',
                input_video_paths[0]]
            call_list += arg_override
            call_list += [
                '-strict',
                '-2',
                '-t',
                duration.get_timecode(),
                '-sn',
                filename_template.safe_substitute(
                    VIDEO_NAME=video_name,
                    SCENE_NUMBER=scene_num_format % (i + 1))
                ]
            ret_val = subprocess.call(call_list)
            if not suppress_output and i == 0 and len(scene_list) > 1:
                logging.info(
                    'Output from ffmpeg for Scene 1 shown above, splitting remaining scenes...')
            if ret_val != 0:
                break
            if progress_bar:
                progress_bar.update(duration.get_frames())
        if progress_bar:
            print('')
            logging.info('Average processing speed %.2f frames/sec.',
                         float(total_frames) / (time.time() - processing_start_time))
    except OSError:
        logging.error('ffmpeg could not be found on the system.'
                      ' Please install ffmpeg to enable video output support.')
    if ret_val is not None and ret_val != 0:
        logging.error('Error splitting video (ffmpeg returned %d).', ret_val)
    def detect_scenes(self, frame_source, end_time=None, frame_skip=0,
                      show_progress=True):
        # type: (VideoManager, Union[int, FrameTimecode],
        #        Optional[Union[int, FrameTimecode]], Optional[bool]) -> int
        """ Perform scene detection on the given frame_source using the added SceneDetectors.

        Blocks until all frames in the frame_source have been processed. Results can
        be obtained by calling either the get_scene_list() or get_cut_list() methods.

        Arguments:
            frame_source (scenedetect.video_manager.VideoManager or cv2.VideoCapture):
                A source of frames to process (using frame_source.read() as in VideoCapture).
                VideoManager is preferred as it allows concatenation of multiple videos
                as well as seeking, by defining start time and end time/duration.
            end_time (int or FrameTimecode): Maximum number of frames to detect
                (set to None to detect all available frames). Only needed for OpenCV
                VideoCapture objects; for VideoManager objects, use set_duration() instead.
            frame_skip (int): Not recommended except for extremely high framerate videos.
                Number of frames to skip (i.e. process every 1 in N+1 frames,
                where N is frame_skip, processing only 1/N+1 percent of the video,
                speeding up the detection time at the expense of accuracy).
                `frame_skip` **must** be 0 (the default) when using a StatsManager.
            show_progress (bool): If True, and the ``tqdm`` module is available, displays
                a progress bar with the progress, framerate, and expected time to
                complete processing the video frame source.
        Returns:
            int: Number of frames read and processed from the frame source.
        Raises:
            ValueError: `frame_skip` **must** be 0 (the default) if the SceneManager
                was constructed with a StatsManager object.
        """

        if frame_skip > 0 and self._stats_manager is not None:
            raise ValueError('frame_skip must be 0 when using a StatsManager.')

        start_frame = 0
        curr_frame = 0
        end_frame = None

        total_frames = math.trunc(frame_source.get(cv2.CAP_PROP_FRAME_COUNT))

        start_time = frame_source.get(cv2.CAP_PROP_POS_FRAMES)
        if isinstance(start_time, FrameTimecode):
            start_frame = start_time.get_frames()
        elif start_time is not None:
            start_frame = int(start_time)
        self._start_frame = start_frame

        curr_frame = start_frame

        if isinstance(end_time, FrameTimecode):
            end_frame = end_time.get_frames()
        elif end_time is not None:
            end_frame = int(end_time)

        if end_frame is not None:
            total_frames = end_frame

        if start_frame is not None and not isinstance(start_time, FrameTimecode):
            total_frames -= start_frame

        if total_frames < 0:
            total_frames = 0

        progress_bar = None
        if tqdm and show_progress:
            progress_bar = tqdm(
                total=total_frames, unit='frames')
        try:

            while True:
                if end_frame is not None and curr_frame >= end_frame:
                    break
                # We don't compensate for frame_skip here as the frame_skip option
                # is not allowed when using a StatsManager - thus, processing is
                # *always* required for *all* frames when frame_skip > 0.
                if (self._is_processing_required(self._num_frames + start_frame)
                        or self._is_processing_required(self._num_frames + start_frame + 1)):
                    ret_val, frame_im = frame_source.read()
                else:
                    ret_val = frame_source.grab()
                    frame_im = None

                if not ret_val:
                    break
                self._process_frame(self._num_frames + start_frame, frame_im)

                curr_frame += 1
                self._num_frames += 1
                if progress_bar:
                    progress_bar.update(1)

                if frame_skip > 0:
                    for _ in range(frame_skip):
                        if not frame_source.grab():
                            break
                        curr_frame += 1
                        self._num_frames += 1
                        if progress_bar:
                            progress_bar.update(1)

            self._post_process(curr_frame)

            num_frames = curr_frame - start_frame

        finally:

            if progress_bar:
                progress_bar.close()

        return num_frames
Exemple #5
0
    def _generate_images(self, scene_list, video_name,
                         image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER',
                         output_dir=None):
        # type: (List[Tuple[FrameTimecode, FrameTimecode]) -> None

        if self.num_images != 2:
            raise NotImplementedError()

        if not scene_list:
            return
        if not self.options_processed:
            return
        self.check_input_open()

        imwrite_param = []
        if self.image_param is not None:
            imwrite_param = [self.imwrite_params[self.image_extension], self.image_param]
        click.echo(imwrite_param)

        # Reset video manager and downscale factor.
        self.video_manager.release()
        self.video_manager.reset()
        self.video_manager.set_downscale_factor(1)
        self.video_manager.start()

        filename_template = Template(image_name_template)

        # Setup flags and init progress bar if available.
        completed = True
        logging.info('Generating output images (%d per scene)...', self.num_images)
        progress_bar = None
        if tqdm and not self.quiet_mode:
            progress_bar = tqdm(
                total=len(scene_list) * 2, unit='images')

        scene_num_format = '%0'
        scene_num_format += str(max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd'
        image_num_format = '%0'
        image_num_format += str(math.floor(math.log(self.num_images, 10)) + 2) + 'd'

        for i, (start_time, end_time) in enumerate(scene_list):
            # TODO: Interpolate timecodes if num_frames_per_scene != 2.
            self.video_manager.seek(start_time)
            self.video_manager.grab()
            ret_val, frame_im = self.video_manager.retrieve()
            if ret_val:
                cv2.imwrite(
                    self.get_output_file_path(
                        '%s.%s' % (filename_template.safe_substitute(
                            VIDEO_NAME=video_name,
                            SCENE_NUMBER=scene_num_format % (i+1),
                            IMAGE_NUMBER=image_num_format % (1)
                        ), self.image_extension),
                        output_dir=output_dir), frame_im, imwrite_param)
            else:
                completed = False
                break
            if progress_bar:
                progress_bar.update(1)
            self.video_manager.seek(end_time - 1)
            self.video_manager.grab()
            ret_val, frame_im = self.video_manager.retrieve()
            if ret_val:
                cv2.imwrite(
                    self.get_output_file_path(
                        '%s.%s' % (filename_template.safe_substitute(
                            VIDEO_NAME=video_name,
                            SCENE_NUMBER='%03d' % (i+1),
                            IMAGE_NUMBER='%02d' % (2)
                        ), self.image_extension),
                        output_dir=output_dir), frame_im, imwrite_param)
            else:
                completed = False
                break
            if progress_bar:
                progress_bar.update(1)

        if not completed:
            logging.error('Could not generate all output images.')
def generate_images(
        scene_list,
        video_manager,
        video_name,
        num_images=2,
        image_extension='jpg',
        quality_or_compression=95,
        image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER',
        output_dir=None,
        downscale_factor=1,
        show_progress=False):
    # type: (...) -> bool
    """

    TODO: Documentation.

    Arguments:
        quality_or_compression: For image_extension=jpg or webp, represents encoding quality,
        from 0-100 (higher indicates better quality). For WebP, 100 indicates lossless.
        Default value in the CLI is 95 for JPEG, and 100 for WebP.

        If image_extension=png, represents the compression rate, from 0-9. Higher values
        produce smaller files but result in longer compression time. This setting does not
        affect image quality (lossless PNG), only file size. Default value in the CLI is 3.

        [default: 95]

    Returns:
        True if all requested images were generated & saved successfully, False otherwise.

    """

    if not scene_list:
        return True
    if num_images <= 0:
        raise ValueError()

    imwrite_param = []
    available_extensions = get_cv2_imwrite_params()
    if quality_or_compression is not None:
        if image_extension in available_extensions:
            imwrite_param = [
                available_extensions[image_extension], quality_or_compression
            ]
        else:
            valid_extensions = str(list(available_extensions.keys()))
            raise RuntimeError(
                'Invalid image extension, must be one of (case-sensitive): %s'
                % valid_extensions)

    # Reset video manager and downscale factor.
    video_manager.release()
    video_manager.reset()
    video_manager.set_downscale_factor(downscale_factor)
    video_manager.start()

    # Setup flags and init progress bar if available.
    completed = True
    progress_bar = None
    if tqdm and show_progress:
        progress_bar = tqdm(total=len(scene_list) * num_images, unit='images')

    filename_template = Template(image_name_template)

    scene_num_format = '%0'
    scene_num_format += str(
        max(3,
            math.floor(math.log(len(scene_list), 10)) + 1)) + 'd'
    image_num_format = '%0'
    image_num_format += str(math.floor(math.log(num_images, 10)) + 2) + 'd'

    timecode_list = dict()

    for i in range(len(scene_list)):
        timecode_list[i] = []

    if num_images == 1:
        for i, (start_time, end_time) in enumerate(scene_list):
            duration = end_time - start_time
            timecode_list[i].append(start_time +
                                    int(duration.get_frames() / 2))
    else:
        middle_images = num_images - 2
        for i, (start_time, end_time) in enumerate(scene_list):
            timecode_list[i].append(start_time)

            if middle_images > 0:
                duration = (end_time.get_frames() -
                            1) - start_time.get_frames()
                duration_increment = None
                duration_increment = int(duration / (middle_images + 1))
                for j in range(middle_images):
                    timecode_list[i].append(start_time +
                                            ((j + 1) * duration_increment))
            # End FrameTimecode is always the same frame as the next scene's start_time
            # (one frame past the end), so we need to subtract 1 here.
            timecode_list[i].append(end_time - 1)

    for i in timecode_list:
        for j, image_timecode in enumerate(timecode_list[i]):
            video_manager.seek(image_timecode)
            video_manager.grab()
            ret_val, frame_im = video_manager.retrieve()
            if ret_val:
                file_path = '%s.%s' % (filename_template.safe_substitute(
                    VIDEO_NAME=video_name,
                    SCENE_NUMBER=scene_num_format % (i + 1),
                    IMAGE_NUMBER=image_num_format % (j + 1)), image_extension)
                cv2.imwrite(get_and_create_path(file_path, output_dir),
                            frame_im, imwrite_param)
            else:
                completed = False
                break
            if progress_bar:
                progress_bar.update(1)

    return completed
Exemple #7
0
def process_video(args,
                  scene_changes,
                  crop_output='file',
                  out_file=None,
                  aspect_rat=True,
                  pan_thresh=None,
                  rm_low_res=128):
    device = 'cpu' if args.cpu else 'cuda'
    # fa = face_alignment.FaceAlignment(face_alignment.LandmarksType._2D, flip_input=False,
    #                                   device=device, face_detector='blazeface')
    fa = None

    model, device, half = load_model(weights)

    video = imageio.get_reader(args.inp)

    trajectories = []
    previous_frame = None
    fps = video.get_meta_data()['fps']
    commands = []
    num = 0
    cur_traj = 0
    try:
        # loop for each frame and i is frame num
        for i, frame in tqdm(enumerate(video)):
            # frame is matrix of pixels
            frame_shape = frame.shape
            # extract_bbox returns bboxes
            # bbox in this format: [bbox_num, [crop_x, crop_y, crop_x2, crop_y2, score]]
            bboxes = extract_bbox(frame, fa, model, device, half)
            # For each trajectory check the criterion
            not_valid_trajectories = []
            valid_trajectories = []

            # trajectory in this format: (bbox, tube_bbox, start frame, end frame)
            for trajectory in trajectories:
                # new tube_bbox is old bbox
                tube_bbox = trajectory[0]
                if trajectory[3] in scene_changes:
                    # ending a trajectory if the frame is the frame of a scene change
                    not_valid_trajectories.append(trajectory)
                    continue

                intersection = 0
                # bboxes in this format: [bbox_num, [crop_x, crop_y, crop_x2, crop_y2, score]]
                for bbox in bboxes:
                    intersection = max(
                        intersection,
                        bb_intersection_over_union(tube_bbox, bbox))
                if intersection > args.iou_with_initial:
                    valid_trajectories.append(trajectory)
                else:
                    not_valid_trajectories.append(trajectory)

            if crop_output == "file":
                cur_traj += save_bbox_traj_to_file(not_valid_trajectories,
                                                   fps,
                                                   frame_shape,
                                                   video,
                                                   cur_traj,
                                                   args,
                                                   aspect_rat,
                                                   pan_thresh=pan_thresh,
                                                   fa=fa,
                                                   rm_low_res=rm_low_res,
                                                   model=model,
                                                   device=device,
                                                   half=half)
            elif crop_output == 'ffmpeg cmd':
                # get ffmpeg commands to clip and crop vid for not valid trajectories,
                # bbox is valid up until this point, so get command for it
                new_cmd = compute_bbox_trajectories(not_valid_trajectories,
                                                    fps, frame_shape, args)
                for cmd in new_cmd:
                    commands.append(cmd)
                    out_file.write("{} {}_{}.mp4\n".format(
                        cmd, args.out_crops, num))
                    out_file.flush()
                    num += 1

            # continue on with valid trajectories
            trajectories = valid_trajectories

            ## Assign bbox to trajectories, create new trajectories
            for bbox in bboxes:
                intersection = 0
                current_trajectory = None
                # trajectory in this format: (bbox, tube_bbox, start frame, end frame)
                for trajectory in trajectories:
                    # new tube_bbox is old bbox
                    tube_bbox = trajectory[0]
                    # current_intersection is iou
                    current_intersection = bb_intersection_over_union(
                        tube_bbox, bbox)
                    if intersection < current_intersection and current_intersection > args.iou_with_initial:
                        intersection = bb_intersection_over_union(
                            tube_bbox, bbox)
                        current_trajectory = trajectory

                ## Create new trajectory
                if current_trajectory is None:
                    # initial trajectory
                    trajectories.append([bbox, bbox, i, i])
                else:
                    # change end frame to current frame
                    current_trajectory[3] = i
                    # join function combines tube_bbox and bbox to output new tube_bbox
                    current_trajectory[1] = join(current_trajectory[1], bbox)

    except IndexError as e:
        raise (e)

    if crop_output == 'ffmpeg cmd':
        new_cmd = compute_bbox_trajectories(trajectories, fps, frame_shape,
                                            args)
        for cmd in new_cmd:
            commands.append(cmd)
            out_file.write("{} {}_{}.mp4\n".format(cmd, args.out_crops, num))
            out_file.flush()
            num += 1
        return commands
    def detect_scenes(self,
                      frame_source,
                      start_time=0,
                      end_time=None,
                      frame_skip=0,
                      show_progress=True):
        # type: (VideoManager, Union[int, FrameTimecode],
        #        Optional[Union[int, FrameTimecode]], Optional[bool]) -> int
        """ Perform scene detection on the given frame_source using the added SceneDetectors.

        Blocks until all frames in the frame_source have been processed. Results
        can be obtained by calling the get_scene_list() method afterwards.

        Arguments:
            frame_source (scenedetect.VideoManager or cv2.VideoCapture):  A source of
                frames to process (using frame_source.read() as in VideoCapture).
                VideoManager is preferred as it allows concatenation of multiple videos
                as well as seeking, by defining start time and end time/duration.
            start_time (int or FrameTimecode): Time/frame the passed frame_source object
                is currently at in time (i.e. the frame # read() will return next).
                Must be passed if the frame_source has been seeked past frame 0
                (i.e. calling set_duration on a VideoManager or seeking a VideoCapture).
            end_time (int or FrameTimecode): Maximum number of frames to detect
                (set to None to detect all available frames). Only needed for OpenCV
                VideoCapture objects, as VideoManager allows set_duration.
            frame_skip (int): Number of frames to skip (i.e. process every 1 in N+1
                frames, where N is frame_skip, processing only 1/N+1 percent of the
                video, speeding up the detection time at the expense of accuracy).
            show_progress (bool): If True, and the tqdm module is available, displays
                a progress bar with the progress, framerate, and expected time to
                complete processing the video frame source.
        Returns:
            Number of frames read and processed from the frame source.
        Raises:
            ValueError
        """

        if frame_skip > 0 and self._stats_manager is not None:
            raise ValueError('frame_skip must be 0 when using a StatsManager.')

        start_frame = 0
        curr_frame = 0
        end_frame = None

        total_frames = math.trunc(frame_source.get(cv2.CAP_PROP_FRAME_COUNT))

        if isinstance(start_time, FrameTimecode):
            start_frame = start_time.get_frames()
        elif start_time is not None:
            start_frame = int(start_time)
        self._start_frame = start_frame

        curr_frame = start_frame

        if isinstance(end_time, FrameTimecode):
            end_frame = end_time.get_frames()
        elif end_time is not None:
            end_frame = int(end_time)

        if end_frame is not None:
            total_frames = end_frame
        if start_frame is not None:
            total_frames -= start_frame

        progress_bar = None
        if tqdm and show_progress:
            progress_bar = tqdm(total=total_frames, unit='frames')
        try:

            while True:
                if end_frame is not None and curr_frame >= end_frame:
                    break
                # We don't compensate for frame_skip here as the frame_skip option
                # is not allowed when using a StatsManager, and thus processing is
                # always required for all frames when using frame_skip.
                if (self._is_processing_required(self._num_frames +
                                                 start_frame)
                        or self._is_processing_required(self._num_frames +
                                                        start_frame + 1)):
                    ret_val, frame_im = frame_source.read()
                else:
                    ret_val = frame_source.grab()
                    frame_im = None

                if not ret_val:
                    break
                self._process_frame(self._num_frames + start_frame, frame_im)

                curr_frame += 1
                self._num_frames += 1
                if progress_bar:
                    progress_bar.update(1)

                if frame_skip > 0:
                    for _ in range(frame_skip):
                        if not frame_source.grab():
                            break
                        curr_frame += 1
                        self._num_frames += 1
                        if progress_bar:
                            progress_bar.update(1)

            self._post_process(curr_frame)

            num_frames = curr_frame - start_frame

        finally:
            if progress_bar:
                progress_bar.close()

        return num_frames
Exemple #9
0
    def _generate_images(
            self,
            scene_list,
            video_name,
            image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER',
            output_dir=None):
        # type: (List[Tuple[FrameTimecode, FrameTimecode]) -> None

        if not scene_list:
            return
        if not self.options_processed:
            return
        if self.num_images <= 0:
            raise ValueError()
        self.check_input_open()

        imwrite_param = []
        if self.image_param is not None:
            imwrite_param = [
                self.imwrite_params[self.image_extension], self.image_param
            ]

        # Reset video manager and downscale factor.
        self.video_manager.release()
        self.video_manager.reset()
        self.video_manager.set_downscale_factor(1)
        self.video_manager.start()

        # Setup flags and init progress bar if available.
        completed = True
        logging.info('Generating output images (%d per scene)...',
                     self.num_images)
        progress_bar = None
        if tqdm and not self.quiet_mode:
            progress_bar = tqdm(total=len(scene_list) * self.num_images,
                                unit='images')

        filename_template = Template(image_name_template)

        scene_num_format = '%0'
        scene_num_format += str(
            max(3,
                math.floor(math.log(len(scene_list), 10)) + 1)) + 'd'
        image_num_format = '%0'
        image_num_format += str(math.floor(math.log(self.num_images, 10)) +
                                2) + 'd'

        timecode_list = dict()
        self.image_filenames = dict()

        for i in range(len(scene_list)):
            timecode_list[i] = []
            self.image_filenames[i] = []

        if self.num_images == 1:
            for i, (start_time, end_time) in enumerate(scene_list):
                duration = end_time - start_time
                timecode_list[i].append(start_time +
                                        int(duration.get_frames() / 2))

        else:
            middle_images = self.num_images - 2
            for i, (start_time, end_time) in enumerate(scene_list):
                timecode_list[i].append(start_time)

                if middle_images > 0:
                    duration = (end_time.get_frames() -
                                1) - start_time.get_frames()
                    duration_increment = None
                    duration_increment = int(duration / (middle_images + 1))
                    for j in range(middle_images):
                        timecode_list[i].append(start_time +
                                                ((j + 1) * duration_increment))

                # End FrameTimecode is always the same frame as the next scene's start_time
                # (one frame past the end), so we need to subtract 1 here.
                timecode_list[i].append(end_time - 1)

        for i in timecode_list:
            for j, image_timecode in enumerate(timecode_list[i]):
                self.video_manager.seek(image_timecode)
                self.video_manager.grab()
                ret_val, frame_im = self.video_manager.retrieve()
                if ret_val:
                    file_path = '%s.%s' % (filename_template.safe_substitute(
                        VIDEO_NAME=video_name,
                        SCENE_NUMBER=scene_num_format % (i + 1),
                        IMAGE_NUMBER=image_num_format %
                        (j + 1)), self.image_extension)
                    self.image_filenames[i].append(file_path)
                    cv2.imwrite(
                        self.get_output_file_path(file_path,
                                                  output_dir=output_dir),
                        frame_im, imwrite_param)
                else:
                    completed = False
                    break
                if progress_bar:
                    progress_bar.update(1)

        if not completed:
            logging.error('Could not generate all output images.')
Exemple #10
0
def split_video_ffmpeg(
        input_video_paths,
        scene_list,
        output_file_template,
        video_name,
        arg_override='-c:v libx264 -preset fast -crf 21 -c:a aac',
        hide_progress=False,
        suppress_output=False):
    # type: (List[str], List[Tuple[FrameTimecode, FrameTimecode]], Optional[str],
    #        Optional[str], Optional[bool], Optional[bool]) -> None
    """ Calls the ffmpeg command on the input video(s), generating a new video for
    each scene based on the start/end timecodes.
    Arguments:
        input_video_paths (List[str]): List of strings to the input video path(s).
            Is a list to allow for concatenation of multiple input videos together.
        scene_list (List[Tuple[FrameTimecode, FrameTimecode]]): List of scenes
            (pairs of FrameTimecodes) denoting the start/end frames of each scene.
        output_file_template (str): Template to use for generating the output filenames.
            Can use $VIDEO_NAME and $SCENE_NUMBER in this format, for example:
            `$VIDEO_NAME - Scene $SCENE_NUMBER`
        video_name (str): Name of the video to be substituted in output_file_template.
        arg_override (str): Allows overriding the arguments passed to ffmpeg for encoding.
        hide_progress (bool): If True, will hide progress bar provided by tqdm (if installed).
        suppress_output (bool): If True, will set verbosity to quiet for the first scene.
    """

    if not input_video_paths or not scene_list:
        return

    logging.info(
        'Splitting input video%s using ffmpeg, output path template:\n  %s',
        's' if len(input_video_paths) > 1 else '', output_file_template)

    if len(input_video_paths) > 1:
        # TODO: Add support for splitting multiple/appended input videos.
        # https://trac.ffmpeg.org/wiki/Concatenate#samecodec
        # Requires generating a temporary file list for ffmpeg.
        logging.error(
            'Sorry, splitting multiple appended/concatenated input videos with'
            ' ffmpeg is not supported yet. This feature will be added to a future'
            ' version of PySceneDetect. In the meantime, you can try using the'
            ' -c / --copy option with the split-video to use mkvmerge, which'
            ' generates less accurate output, but supports multiple input videos.'
        )
        raise NotImplementedError()

    arg_override = arg_override.replace('\\"', '"')

    ret_val = None
    arg_override = arg_override.split(' ')
    filename_template = Template(output_file_template)
    scene_num_format = '%0'
    scene_num_format += str(
        max(3,
            math.floor(math.log(len(scene_list), 10)) + 1)) + 'd'

    try:
        progress_bar = None
        total_frames = scene_list[-1][1].get_frames(
        ) - scene_list[0][0].get_frames()
        if tqdm and not hide_progress:
            progress_bar = tqdm(total=total_frames, unit='frame', miniters=1)
        processing_start_time = time.time()
        final_file = filename_template.safe_substitute(
            VIDEO_NAME=video_name, SCENE_NUMBER=scene_num_format % (i + 1))
        final_file = final_file.split('.')[0]
        if not os.path.isdir(f'{final_file}'):
            os.mkdir(f'{final_file}')
        for i, (start_time, end_time) in enumerate(scene_list):
            duration = (end_time - start_time)
            call_list = ['ffmpeg']
            if suppress_output:
                call_list += ['-v', 'quiet']
            elif i > 0:
                # Only show ffmpeg output for the first call, which will display any
                # errors if it fails, and then break the loop. We only show error messages
                # for the remaining calls.
                call_list += ['-v', 'error']
            call_list += [
                '-y', '-ss',
                str(start_time.get_seconds()), '-i', input_video_paths[0],
                '-t',
                str(duration.get_seconds())
            ]
            call_list += arg_override
            call_list += ['-sn', f'{final_file}/%d.png']
            ret_val = invoke_command(call_list)
            if not suppress_output and i == 0 and len(scene_list) > 1:
                logging.info(
                    'Output from ffmpeg for Scene 1 shown above, splitting remaining scenes...'
                )
            if ret_val != 0:
                break
            if progress_bar:
                progress_bar.update(duration.get_frames())
        if progress_bar:
            print('')
            logging.info(
                'Average processing speed %.2f frames/sec.',
                float(total_frames) / (time.time() - processing_start_time))

    except CommandTooLong:
        logging.error(COMMAND_TOO_LONG_STRING)
    except OSError:
        logging.error('ffmpeg could not be found on the system.'
                      ' Please install ffmpeg to enable video output support.')
    if ret_val is not None and ret_val != 0:
        logging.error('Error splitting video (ffmpeg returned %d).', ret_val)
Exemple #11
0
def generate_images(
        video_manager,
        scene_list,
        video_name,
        image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER',
        output_dir=None):
    import logging
    import cv2
    from string import Template
    from scenedetect.platform import tqdm
    logging.getLogger().setLevel(logging.INFO)

    num_images = 2
    image_extension = "jpg"

    # imwrite_param = []
    # if image_param is not None:
    #     imwrite_param = [imwrite_params[image_extension], image_param]

    # Reset video manager and downscale factor.
    video_manager = video_manager
    video_manager.release()
    video_manager.reset()
    video_manager.set_downscale_factor(1)
    video_manager.start()

    print("Detect Scenes:", len(scene_list))

    # Setup flags and init progress bar if available.
    completed = True
    logging.info('Generating output images (%d per scene)...', num_images)
    progress_bar = None
    if tqdm:
        progress_bar = tqdm(total=len(scene_list) * num_images, unit='images')

    filename_template = Template(image_name_template)

    if scene_list:
        scene_num_format = '%0'
        scene_num_format += str(
            max(3,
                math.floor(math.log(len(scene_list), 10)) + 1)) + 'd'
        image_num_format = '%0'
        image_num_format += str(math.floor(math.log(num_images, 10)) + 2) + 'd'

    timecode_list = dict()

    for i in range(len(scene_list)):
        timecode_list[i] = []

    if num_images == 1:
        for i, (start_time, end_time) in enumerate(scene_list):
            duration = end_time - start_time
            timecode_list[i].append(start_time +
                                    int(duration.get_frames() / 2))

    else:
        middle_images = num_images - 2
        for i, (start_time, end_time) in enumerate(scene_list):
            timecode_list[i].append(start_time)

            if middle_images > 0:
                duration = (end_time.get_frames() -
                            1) - start_time.get_frames()
                duration_increment = None
                duration_increment = int(duration / (middle_images + 1))
                for j in range(middle_images):
                    timecode_list[i].append(start_time +
                                            ((j + 1) * duration_increment))

            # End FrameTimecode is always the same frame as the next scene's start_time
            # (one frame past the end), so we need to subtract 1 here.
            timecode_list[i].append(end_time - 1)

    img_folder = create_folder(video_name)

    for i in timecode_list:
        for j, image_timecode in enumerate(timecode_list[i]):
            video_manager.seek(image_timecode)
            video_manager.grab()
            ret_val, frame_im = video_manager.retrieve()
            if ret_val:
                cv2.imwrite(
                    get_output_file_path(
                        '%s.%s' % (filename_template.safe_substitute(
                            VIDEO_NAME=video_name,
                            SCENE_NUMBER=scene_num_format % (i + 1),
                            IMAGE_NUMBER=image_num_format %
                            (j + 1)), image_extension),
                        output_dir=os.path.join(
                            os.path.dirname(os.path.realpath(__file__)),
                            img_folder)), frame_im)
                # return output_dir
            else:
                completed = False
                break
            if progress_bar:
                progress_bar.update(1)

    # type: (List[Tuple[FrameTimecode, FrameTimecode]) -> None

    if not scene_list:

        img_folder = create_folder(video_name)
        video_manager.release()
        video_manager.reset()
        video_manager.set_downscale_factor(1)
        video_manager.start()
        video_manager.seek("00:00:02.000")
        video_manager.grab()
        ret_val, frame_im = video_manager.retrieve()
        if ret_val:
            cv2.imwrite(
                os.path.join(os.path.dirname(os.path.realpath(__file__)),
                             img_folder,
                             video_name[0:-4] + "." + image_extension),
                frame_im)
    #         return os.path.join(os.path.dirname(os.path.realpath(__file__)), img_folder, video_name[0:-4])

    if not completed:
        logging.error('Could not generate all output images.')
Exemple #12
0
    def _generate_images(self, scene_list, video_name,
                         image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER',
                         output_dir=None):
        # type: (List[Tuple[FrameTimecode, FrameTimecode]) -> None

        if not scene_list:
            return
        if not self.options_processed:
            return
        if self.num_images <= 0:
            raise ValueError()
        self.check_input_open()

        imwrite_param = []
        if self.image_param is not None:
            imwrite_param = [self.imwrite_params[self.image_extension], self.image_param]

        # Reset video manager and downscale factor.
        self.video_manager.release()
        self.video_manager.reset()
        self.video_manager.set_downscale_factor(1)
        self.video_manager.start()

        # Setup flags and init progress bar if available.
        completed = True
        logging.info('Generating output images (%d per scene)...', self.num_images)
        progress_bar = None
        if tqdm and not self.quiet_mode:
            progress_bar = tqdm(
                total=len(scene_list) * self.num_images, unit='images')

        filename_template = Template(image_name_template)


        scene_num_format = '%0'
        scene_num_format += str(max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd'
        image_num_format = '%0'
        image_num_format += str(math.floor(math.log(self.num_images, 10)) + 2) + 'd'

        timecode_list = dict()
        self.image_filenames = dict()

        for i in range(len(scene_list)):
            timecode_list[i] = []
            self.image_filenames[i] = []

        if self.num_images == 1:
            for i, (start_time, end_time) in enumerate(scene_list):
                duration = end_time - start_time
                timecode_list[i].append(start_time + int(duration.get_frames() / 2))

        else:
            middle_images = self.num_images - 2
            for i, (start_time, end_time) in enumerate(scene_list):
                timecode_list[i].append(start_time)

                if middle_images > 0:
                    duration = (end_time.get_frames() - 1) - start_time.get_frames()
                    duration_increment = None
                    duration_increment = int(duration / (middle_images + 1))
                    for j in range(middle_images):
                        timecode_list[i].append(start_time + ((j+1) * duration_increment))

                # End FrameTimecode is always the same frame as the next scene's start_time
                # (one frame past the end), so we need to subtract 1 here.
                timecode_list[i].append(end_time - 1)

        for i in timecode_list:
            for j, image_timecode in enumerate(timecode_list[i]):
                self.video_manager.seek(image_timecode)
                self.video_manager.grab()
                ret_val, frame_im = self.video_manager.retrieve()
                if ret_val:
                    file_path = '%s.%s' % (filename_template.safe_substitute(
                        VIDEO_NAME=video_name,
                        SCENE_NUMBER=scene_num_format % (i + 1),
                        IMAGE_NUMBER=image_num_format % (j + 1)),
                                           self.image_extension)
                    self.image_filenames[i].append(file_path)
                    cv2.imwrite(
                        self.get_output_file_path(file_path,
                                                  output_dir=output_dir),
                        frame_im, imwrite_param)
                else:
                    completed = False
                    break
                if progress_bar:
                    progress_bar.update(1)

        if not completed:
            logging.error('Could not generate all output images.')
Exemple #13
0
def split_video_ffmpeg(input_video_paths, scene_list, output_file_prefix,
                       arg_override='-c:v copy -c:a copy', output_extension='mp4',
                       hide_progress=False, suppress_output=False):
    # type: (List[str], List[Tuple[FrameTimecode, FrameTimecode]], Optional[str],
    #        Optional[str], Optional[bool]) -> None
    """ Calls the ffmpeg command on the input video(s), generating a new video for
    each scene based on the start/end timecodes. """

    if not input_video_paths:
        return

    if len(input_video_paths) > 1:
        # TODO: Add support for splitting multiple/appended input videos.
        # https://trac.ffmpeg.org/wiki/Concatenate#samecodec
        # Requires generating a temporary file list for ffmpeg.
        logging.error('Splitting multiple input videos with ffmpeg is not supported yet.')
        raise NotImplementedError()

    arg_override = arg_override.replace('\\"', '"')

    ret_val = None
    arg_override = arg_override.split(' ')

    try:
        progress_bar = None
        if tqdm and not hide_progress:
            progress_bar = tqdm(total=len(scene_list), unit='scenes')
        for i, (start_time, end_time) in enumerate(scene_list):
            call_list = ['ffmpeg']
            if suppress_output:
                call_list += ['-v', 'quiet']
            elif i > 0:
                # Only show ffmpeg output for the first call, which will display any
                # errors if it fails. We suppress the output for the remaining calls.
                call_list += ['-v', 'error']
            call_list += [
                '-y',
                '-i',
                input_video_paths[0]]
            call_list += arg_override
            call_list += [
                '-strict',
                '-2',
                '-ss',
                start_time.get_timecode(),
                '-t',
                (end_time - start_time).get_timecode(),
                '-sn',
                '%s-Scene-%03d.%s' % (output_file_prefix, i + 1, output_extension)
                ]
            ret_val = subprocess.call(call_list)
            if not suppress_output and i == 0 and len(scene_list) > 1:
                logging.info(
                    'Output from ffmpeg for Scene 1 shown above, splitting remaining scenes...')
            if ret_val != 0:
                break
            if progress_bar:
                progress_bar.update(1)
    except OSError:
        logging.error('ffmpeg could not be found on the system.'
                      ' Please install ffmpeg to enable video output support.')
    if ret_val is not None and ret_val != 0:
        logging.error('Error splitting video (ffmpeg returned %d).', ret_val)
Exemple #14
0
    def detect_scenes(self, frame_source, end_time=None, frame_skip=0,
                      show_progress=True, callback=None):
        # type: (VideoManager, Union[int, FrameTimecode],
        #        Optional[Union[int, FrameTimecode]], Optional[bool], optional[callable[numpy.ndarray]) -> int
        """ Perform scene detection on the given frame_source using the added SceneDetectors.

        Blocks until all frames in the frame_source have been processed. Results can
        be obtained by calling either the get_scene_list() or get_cut_list() methods.

        Arguments:
            frame_source (scenedetect.video_manager.VideoManager or cv2.VideoCapture):
                A source of frames to process (using frame_source.read() as in VideoCapture).
                VideoManager is preferred as it allows concatenation of multiple videos
                as well as seeking, by defining start time and end time/duration.
            end_time (int or FrameTimecode): Maximum number of frames to detect
                (set to None to detect all available frames). Only needed for OpenCV
                VideoCapture objects; for VideoManager objects, use set_duration() instead.
            frame_skip (int): Not recommended except for extremely high framerate videos.
                Number of frames to skip (i.e. process every 1 in N+1 frames,
                where N is frame_skip, processing only 1/N+1 percent of the video,
                speeding up the detection time at the expense of accuracy).
                `frame_skip` **must** be 0 (the default) when using a StatsManager.
            show_progress (bool): If True, and the ``tqdm`` module is available, displays
                a progress bar with the progress, framerate, and expected time to
                complete processing the video frame source.
            callback ((image_ndarray, frame_num: int) -> None): If not None, called after
                each scene/event detected.
        Returns:
            int: Number of frames read and processed from the frame source.
        Raises:
            ValueError: `frame_skip` **must** be 0 (the default) if the SceneManager
                was constructed with a StatsManager object.
        """

        if frame_skip > 0 and self._stats_manager is not None:
            raise ValueError('frame_skip must be 0 when using a StatsManager.')

        start_frame = 0
        curr_frame = 0
        end_frame = None
        self._base_timecode = FrameTimecode(
            timecode=0, fps=frame_source.get(cv2.CAP_PROP_FPS))

        total_frames = math.trunc(frame_source.get(cv2.CAP_PROP_FRAME_COUNT))

        start_time = frame_source.get(cv2.CAP_PROP_POS_FRAMES)
        if isinstance(start_time, FrameTimecode):
            start_frame = start_time.get_frames()
        elif start_time is not None:
            start_frame = int(start_time)
        self._start_frame = start_frame

        curr_frame = start_frame

        if isinstance(end_time, FrameTimecode):
            end_frame = end_time.get_frames()
        elif end_time is not None:
            end_frame = int(end_time)

        if end_frame is not None:
            total_frames = end_frame

        if start_frame is not None and not isinstance(start_time, FrameTimecode):
            total_frames -= start_frame

        if total_frames < 0:
            total_frames = 0

        progress_bar = None
        if tqdm and show_progress:
            progress_bar = tqdm(
                total=total_frames,
                unit='frames',
                dynamic_ncols=True)
        try:

            while True:
                if end_frame is not None and curr_frame >= end_frame:
                    break
                # We don't compensate for frame_skip here as the frame_skip option
                # is not allowed when using a StatsManager - thus, processing is
                # *always* required for *all* frames when frame_skip > 0.
                if (self._is_processing_required(self._num_frames + start_frame)
                        or self._is_processing_required(self._num_frames + start_frame + 1)):
                    ret_val, frame_im = frame_source.read()
                else:
                    ret_val = frame_source.grab()
                    frame_im = None

                if not ret_val:
                    break
                self._process_frame(self._num_frames + start_frame, frame_im, callback)

                curr_frame += 1
                self._num_frames += 1
                if progress_bar:
                    progress_bar.update(1)

                if frame_skip > 0:
                    for _ in range(frame_skip):
                        if not frame_source.grab():
                            break
                        curr_frame += 1
                        self._num_frames += 1
                        if progress_bar:
                            progress_bar.update(1)

            self._post_process(curr_frame)

            num_frames = curr_frame - start_frame

        finally:

            if progress_bar:
                progress_bar.close()

        return num_frames
Exemple #15
0
    def _generate_images(self, scene_list, image_prefix, output_dir=None):
        # type: (List[Tuple[FrameTimecode, FrameTimecode]) -> None

        if self.num_images != 2:
            raise NotImplementedError()

        if not scene_list:
            return
        if not self.options_processed:
            return
        self.check_input_open()

        imwrite_param = []
        if self.image_param is not None:
            imwrite_param = [
                self.imwrite_params[self.image_extension], self.image_param
            ]
        click.echo(imwrite_param)

        # Reset video manager and downscale factor.
        self.video_manager.release()
        self.video_manager.reset()
        self.video_manager.set_downscale_factor(1)
        self.video_manager.start()

        # Setup flags and init progress bar if available.
        completed = True
        logging.info('Generating output images (%d per scene)...',
                     self.num_images)
        progress_bar = None
        if tqdm and not self.quiet_mode:
            progress_bar = tqdm(total=len(scene_list) * 2, unit='images')

        for i, (start_time, end_time) in enumerate(scene_list):
            # TODO: Interpolate timecodes if num_frames_per_scene != 2.
            self.video_manager.seek(start_time)
            self.video_manager.grab()
            ret_val, frame_im = self.video_manager.retrieve()
            if ret_val:
                cv2.imwrite(
                    self.get_output_file_path(
                        '%s-Scene-%03d-00.%s' %
                        (image_prefix, i + 1, self.image_extension),
                        output_dir=output_dir), frame_im, imwrite_param)
            else:
                completed = False
                break
            if progress_bar:
                progress_bar.update(1)
            self.video_manager.seek(end_time)
            self.video_manager.grab()
            ret_val, frame_im = self.video_manager.retrieve()
            if ret_val:
                cv2.imwrite(
                    self.get_output_file_path(
                        '%s-Scene-%03d-01.%s' %
                        (image_prefix, i + 1, self.image_extension),
                        output_dir=output_dir), frame_im, imwrite_param)
            else:
                completed = False
                break
            if progress_bar:
                progress_bar.update(1)

        if not completed:
            logging.error('Could not generate all output images.')
Exemple #16
0
    def _generate_images(
            self,
            scene_list,
            video_name,
            image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER',
            output_dir=None,
            downscale_factor=1):
        # type: (List[Tuple[FrameTimecode, FrameTimecode]) -> None

        if not scene_list:
            return
        if not self.options_processed:
            return
        if self.num_images <= 0:
            raise ValueError()
        self.check_input_open()

        imwrite_param = []
        if self.image_param is not None:
            imwrite_param = [
                self.imwrite_params[self.image_extension], self.image_param
            ]

        # Reset video manager and downscale factor.
        self.video_manager.release()
        self.video_manager.reset()
        self.video_manager.set_downscale_factor(1)
        self.video_manager.start()

        # Setup flags and init progress bar if available.
        completed = True
        logging.info('Generating output images (%d per scene)...',
                     self.num_images)
        progress_bar = None
        if tqdm and not self.quiet_mode:
            progress_bar = tqdm(total=len(scene_list) * self.num_images,
                                unit='images')

        filename_template = Template(image_name_template)

        scene_num_format = '%0'
        scene_num_format += str(
            max(3,
                math.floor(math.log(len(scene_list), 10)) + 1)) + 'd'
        image_num_format = '%0'
        image_num_format += str(math.floor(math.log(self.num_images, 10)) +
                                2) + 'd'

        timecode_list = dict()

        fps = scene_list[0][0].framerate

        timecode_list = [
            [
                FrameTimecode(int(f), fps=fps) for f in [
                    # middle frames
                    a[len(a) // 2] if (
                        0 < j < self.num_images - 1) or self.num_images == 1

                    # first frame
                    else min(a[0] + self.image_frame_margin, a[-1]) if j == 0

                    # last frame
                    else max(a[-1] - self.image_frame_margin, a[0])

                    # for each evenly-split array of frames in the scene list
                    for j, a in enumerate(np.array_split(r, self.num_images))
                ]
            ] for i, r in enumerate([
                # pad ranges to number of images
                r if r.stop - r.start >= self.num_images else list(r) +
                [r.stop - 1] * (self.num_images - len(r))
                # create range of frames in scene
                for r in (
                    range(start.get_frames(), end.get_frames())
                    # for each scene in scene list
                    for start, end in scene_list)
            ])
        ]

        self.image_filenames = {i: [] for i in range(len(timecode_list))}

        for i, tl in enumerate(timecode_list):
            for j, image_timecode in enumerate(tl):
                self.video_manager.seek(image_timecode)
                self.video_manager.grab()
                ret_val, frame_im = self.video_manager.retrieve()

                if downscale_factor != 1:
                    logging.info("resizing thumb")
                    scale_percent = 1 / downscale_factor
                    width = int(frame_im.shape[1] * scale_percent)
                    height = int(frame_im.shape[0] * scale_percent)
                    resized = cv2.resize(frame_im, (width, height),
                                         interpolation=cv2.INTER_AREA)
                    frame_im = resized

                if ret_val:
                    file_path = '%s.%s' % (filename_template.safe_substitute(
                        VIDEO_NAME=video_name,
                        SCENE_NUMBER=scene_num_format % (i + 1),
                        IMAGE_NUMBER=image_num_format % (j + 1),
                        FRAME_NUMBER=image_timecode.get_frames()),
                                           self.image_extension)
                    self.image_filenames[i].append(file_path)
                    cv2.imwrite(
                        get_and_create_path(
                            file_path, output_dir if output_dir is not None
                            else self.output_directory), frame_im,
                        imwrite_param)
                else:
                    completed = False
                    break
                if progress_bar:
                    progress_bar.update(1)

        if not completed:
            logging.error('Could not generate all output images.')
def split_video_ffmpeg(input_video_paths, scene_list, output_file_template, video_name,
                       arg_override='-c:v libx264 -preset fast -crf 21 -c:a copy',
                       hide_progress=False, suppress_output=False):
    # type: (List[str], List[Tuple[FrameTimecode, FrameTimecode]], Optional[str],
    #        Optional[str], Optional[bool]) -> None
    """ Calls the ffmpeg command on the input video(s), generating a new video for
    each scene based on the start/end timecodes. """

    if not input_video_paths or not scene_list:
        return

    logging.info(
        'Splitting input video%s using ffmpeg, output path template:\n  %s',
        's' if len(input_video_paths) > 1 else '', output_file_template)
        
    if len(input_video_paths) > 1:
        # TODO: Add support for splitting multiple/appended input videos.
        # https://trac.ffmpeg.org/wiki/Concatenate#samecodec
        # Requires generating a temporary file list for ffmpeg.
        logging.error(
            'Sorry, splitting multiple appended/concatenated input videos with'
            ' ffmpeg is not supported yet. This feature will be added to a future'
            ' version of PySceneDetect. In the meantime, you can try using the'
            ' -c / --copy option with the split-video to use mkvmerge, which'
            ' generates less accurate output, but supports multiple input videos.')
        raise NotImplementedError()

    arg_override = arg_override.replace('\\"', '"')

    ret_val = None
    arg_override = arg_override.split(' ')
    filename_template = Template(output_file_template)
    scene_num_format = '%0'
    scene_num_format += str(max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd'

    try:
        progress_bar = None
        total_frames = scene_list[-1][1].get_frames() - scene_list[0][0].get_frames()
        if tqdm and not hide_progress:
            progress_bar = tqdm(total=total_frames, unit='frame', miniters=1)
        processing_start_time = time.time()
        for i, (start_time, end_time) in enumerate(scene_list):
            duration = (end_time - start_time)
            # Fix FFmpeg start timecode frame shift.
            start_time -= 1
            call_list = ['ffmpeg']
            if suppress_output:
                call_list += ['-v', 'quiet']
            elif i > 0:
                # Only show ffmpeg output for the first call, which will display any
                # errors if it fails, and then break the loop. We only show error messages
                # for the remaining calls.
                call_list += ['-v', 'error']
            call_list += [
                '-y',
                '-ss',
                start_time.get_timecode(),
                '-i',
                input_video_paths[0]]
            call_list += arg_override
            call_list += [
                '-strict',
                '-2',
                '-t',
                duration.get_timecode(),
                '-sn',
                filename_template.safe_substitute(
                    VIDEO_NAME=video_name,
                    SCENE_NUMBER=scene_num_format % (i + 1))
                ]
            ret_val = subprocess.call(call_list)
            if not suppress_output and i == 0 and len(scene_list) > 1:
                logging.info(
                    'Output from ffmpeg for Scene 1 shown above, splitting remaining scenes...')
            if ret_val != 0:
                break
            if progress_bar:
                progress_bar.update(duration.get_frames())
        if progress_bar:
            print('')
            logging.info('Average processing speed %.2f frames/sec.',
                         float(total_frames) / (time.time() - processing_start_time))
    except OSError:
        logging.error('ffmpeg could not be found on the system.'
                      ' Please install ffmpeg to enable video output support.')
    if ret_val is not None and ret_val != 0:
        logging.error('Error splitting video (ffmpeg returned %d).', ret_val)