Esempio n. 1
0
    def setUp(self):
        if not hasattr(self, 'assertRegex'):
            self.assertRegex = self.assertRegexpMatches
            self.assertNotRegex = self.assertNotRegexpMatches

        # make sure XDG_CONFIG_HOME doesn't interfere with our
        # change_home later
        if 'XDG_CONFIG_HOME' in os.environ:
            os.environ.pop('XDG_CONFIG_HOME')

        # create a mock srt subtitle file
        fd, self.srtfile = tempfile.mkstemp(prefix='storyboard-test-',
                                            suffix='.srt')
        os.close(fd)
        with open(self.srtfile, 'w') as fd:
            fd.write("1\n"
                     "00:00:01,000 --> 00:00:02,000\n"
                     "SubRip is the way to go\n")

        # create video file
        fd, self.videofile = tempfile.mkstemp(prefix='storyboard-test-',
                                              suffix='.mkv')
        os.close(fd)
        bins = fflocate.guess_bins()
        fflocate.check_bins(bins)  # error if bins do not exist
        self.ffmpeg_bin, self.ffprobe_bin = bins
        with open(os.devnull, 'wb') as devnull:
            command = [
                self.ffmpeg_bin,
                # video stream (320x180, pure pink)
                '-f',
                'lavfi',
                '-i',
                'color=c=pink:s=320x180:d=10',
                # audio stream (silent)
                '-f',
                'lavfi',
                '-i',
                'aevalsrc=0:d=10',
                # subtitle stream
                '-i',
                self.srtfile,
                # output option
                '-y',
                self.videofile
            ]
            subprocess.check_call(command, stdout=devnull, stderr=devnull)
Esempio n. 2
0
    def __init__(self, video, params=None):
        """Initialize the StoryBoard class.

        See the module docstring for parameters and exceptions.

        """

        if params is None:
            params = {}
        if 'bins' in params and params['bins'] is not None:
            bins = params['bins']
            assert isinstance(bins, tuple) and len(bins) == 2
        else:
            bins = fflocate.guess_bins()
        frame_codec = _read_param(params, 'frame_codec', 'png')
        video_duration = _read_param(params, 'video_duration', None)
        print_progress = _read_param(params, 'print_progress', False)

        fflocate.check_bins(bins)

        # seek frame by frame if video duration is specially given
        # (indicating that normal input seeking may not work)
        self._seek_frame_by_frame = video_duration is not None

        self._bins = bins
        if isinstance(video, metadata.Video):
            self.video = video
        elif isinstance(video, str):
            self.video = metadata.Video(video,
                                        params={
                                            'ffprobe_bin': bins[1],
                                            'video_duration': video_duration,
                                            'print_progress': print_progress,
                                        })
        else:
            raise ValueError("expected str or storyboard.metadata.Video "
                             "for the video argument, got %s" %
                             type(video).__name__)
        self.frames = []
        self._frame_codec = frame_codec
Esempio n. 3
0
def main():
    """CLI interface."""

    # pylint: disable=too-many-locals,too-many-statements,too-many-branches

    description = """Print video metadata.

    You may supply a list of videos, and the output for each video will
    be followed by a blank line to distinguish it from others. Below is
    the list of available options and their brief explanations. Some of
    the options can also be stored in a configuration file,
    $XDG_CONFIG_HOME/storyboard/storyboard.conf (or if $XDG_CONFIG_HOME
    is not defined, ~/.config/storyboard/storyboard.conf), under the
    "metadata-cli" section (the conf file format follows that described
    in https://docs.python.org/3/library/configparser.html).

    For more detailed explanations, see
    http://storyboard.rtfd.org/en/stable/metadata-cli.html (or replace
    "stable" with the version you are using).
    """
    parser = argparse.ArgumentParser(description=description)
    parser.add_argument('videos',
                        nargs='+',
                        metavar='VIDEO',
                        help="Path(s) to the video file(s).")
    parser.add_argument(
        '--ffprobe-bin',
        metavar='NAME',
        help="""The name/path of the ffprobe binary. The binay is
        guessed from OS type if this option is not specified.""")
    parser.add_argument('--include-sha1sum',
                        '-s',
                        action='store_const',
                        const=True,
                        help="Include SHA-1 digest of the video(s).")
    parser.add_argument('--exclude-sha1sum',
                        action='store_true',
                        help="""Exclude SHA-1 digest of the video(s). Overrides
        '--include-sha1sum'. This option is only useful if
        include_sha1sum is turned on by default in the config file.""")
    parser.add_argument(
        '--verbose',
        '-v',
        choices=['auto', 'on', 'off'],
        nargs='?',
        const='auto',
        help="""Whether to print progress information to stderr. Default
        is 'auto'.""")
    parser.add_argument('--version',
                        action='version',
                        version=version.__version__)
    cli_args = parser.parse_args()

    if 'XDG_CONFIG_HOME' in os.environ:
        config_file = os.path.join(os.environ['XDG_CONFIG_HOME'],
                                   'storyboard/storyboard.conf')
    else:
        config_file = os.path.expanduser(
            '~/.config/storyboard/storyboard.conf')

    defaults = {
        'ffprobe_bin': fflocate.guess_bins()[1],
        'include_sha1sum': False,
        'verbose': 'auto',
    }

    optreader = util.OptionReader(
        cli_args=cli_args,
        config_files=config_file,
        section='metadata-cli',
        defaults=defaults,
    )
    ffprobe_bin = optreader.opt('ffprobe_bin')
    include_sha1sum = optreader.opt('include_sha1sum', opttype=bool)
    if cli_args.exclude_sha1sum:
        # force override
        include_sha1sum = False
    verbose = optreader.opt('verbose')
    if verbose == 'on':
        print_progress = True
    elif verbose == 'off':
        print_progress = False
    else:
        if verbose != 'auto':
            msg = ("warning: '%s' is a not a valid argument to --verbose; "
                   "ignoring and using 'auto' instead\n" % verbose)
            sys.stderr.write(msg)
        if include_sha1sum and sys.stderr.isatty():
            print_progress = True
        else:
            print_progress = False

    # test ffprobe_bin
    try:
        fflocate.check_bins((None, ffprobe_bin))
    except OSError:
        msg = ("fatal error: '%s' does not exist on PATH or is corrupted "
               "(expected FFprobe)\n" % ffprobe_bin)
        sys.stderr.write(msg)
        exit(1)

    # real stuff happens from here
    returncode = 0
    for video in cli_args.videos:
        # pylint: disable=invalid-name
        try:
            v = Video(video,
                      params={
                          'ffprobe_bin': ffprobe_bin,
                          'print_progress': print_progress,
                      })
        except OSError as err:
            sys.stderr.write("error: %s\n\n" % str(err))
            returncode = 1
            continue

        metadata_string = v.format_metadata(params={
            'include_sha1sum': include_sha1sum,
            'print_progress': print_progress,
        })

        if print_progress:
            # print one empty line to separate progress info and output
            # content
            sys.stderr.write("\n")
        print(metadata_string)
        print('')
    return returncode
Esempio n. 4
0
def main():
    """CLI interface."""

    # pylint: disable=too-many-statements,too-many-branches

    description = """Generate video storyboards with metadata reports.

    You may supply a list of videos. For each video, the generated
    storyboard image will be saved to a secure temporary file, and its
    absolute path will be printed to stdout for further manipulations
    (permanent archiving, uploading to an image hosting website,
    etc). Note that stdout is guaranteed to only receive the image
    paths, one per line, so you may embed this program in a streamlined
    script; stderr, on the other hand, may receive progress information
    without guaranteed format (see the --print-progress option).

    Below is the list of available options and their brief
    explanations. The options can also be stored in a configuration
    file, $XDG_CONFIG_HOME/storyboard/storyboard.conf (or if
    $XDG_CONFIG_HOME is not defined,
    ~/.config/storyboard/storyboard.conf), under the "storyboard-cli"
    section (the conf file format follows that described in
    https://docs.python.org/3/library/configparser.html).

    Note that the storyboard is in fact much more customizable; see the
    API reference of
    storyboard.storyboard.StoryBoard.gen_storyboard. Those customization
    parameters are not exposed in the CLI, but you may easily write a
    wrapper script around the storyboard.storyboard if you'd like to.

    For more detailed explanations, see
    https://storyboard.readthedocs.io/en/stable/storyboard-cli.html (or
    replace "stable" with the version you are using).
    """
    parser = argparse.ArgumentParser(description=description)
    parser.add_argument('videos',
                        nargs='+',
                        metavar='VIDEO',
                        help="Path(s) to the video file(s).")
    parser.add_argument(
        '--ffmpeg-bin',
        metavar='NAME',
        help="""The name/path of the ffmpeg binary. The binay is
        guessed from OS type if this option is not specified.""")
    parser.add_argument(
        '--ffprobe-bin',
        metavar='NAME',
        help="""The name/path of the ffprobe binary. The binay is
        guessed from OS type if this option is not specified.""")
    parser.add_argument(
        '-f',
        '--output-format',
        choices=['jpeg', 'png'],
        help="Output format of the storyboard image. Default is JPEG.")
    parser.add_argument(
        '--quality',
        type=int,
        help="""Quality of the output image, should be an integer
        between 1 and 100. Only meaningful when the output format is
        JPEG. Default is 85.""")
    parser.add_argument(
        '--video-duration',
        type=float,
        metavar='SECONDS',
        help="""Video duration in seconds (float). By default the
        duration is extracted from container metadata, but in case it is
        not available or wrong, use this option to correct it and get a
        saner storyboard. Note however that this option activates output
        seeking (i.e., seeking the video frame by frame) in thumbnail
        generation, so it will be *infinitely* slower than without this
        option.""")
    parser.add_argument(
        '--exclude-sha1sum',
        '-s',
        action='store_const',
        const=True,
        help="Exclude SHA-1 digest of the video(s) from storyboard(s).")
    parser.add_argument('--include-sha1sum',
                        action='store_true',
                        help="""Include SHA-1 digest of the video(s). Overrides
        '--exclude-sha1sum'. This option is only useful if
        exclude_sha1sum is turned on by default in the config file.""")
    parser.add_argument(
        '--verbose',
        '-v',
        choices=['auto', 'on', 'off'],
        nargs='?',
        const='auto',
        help="""Whether to print progress information to stderr. Default
        is 'auto'.""")
    parser.add_argument('--version',
                        action='version',
                        version=version.__version__)
    cli_args = parser.parse_args()

    if 'XDG_CONFIG_HOME' in os.environ:
        config_file = os.path.join(os.environ['XDG_CONFIG_HOME'],
                                   'storyboard/storyboard.conf')
    else:
        config_file = os.path.expanduser(
            '~/.config/storyboard/storyboard.conf')

    ffmpeg_bin_guessed, ffprobe_bin_guessed = fflocate.guess_bins()
    defaults = {
        'ffmpeg_bin': ffmpeg_bin_guessed,
        'ffprobe_bin': ffprobe_bin_guessed,
        'output_format': 'jpeg',
        'quality': 85,
        'video_duration': None,
        'exclude-sha1sum': False,
        'verbose': 'auto',
    }

    optreader = util.OptionReader(
        cli_args=cli_args,
        config_files=config_file,
        section='storyboard-cli',
        defaults=defaults,
    )
    bins = (optreader.opt('ffmpeg_bin'), optreader.opt('ffprobe_bin'))
    output_format = optreader.opt('output_format')
    if output_format not in ['jpeg', 'png']:
        msg = ("fatal error: output format should be either 'jpeg' or 'png'; "
               "'%s' received instead\n" % output_format)
        sys.stderr.write(msg)
        exit(1)
    suffix = '.jpg' if output_format == 'jpeg' else '.png'
    quality = optreader.opt('quality', opttype=int)
    video_duration = optreader.opt('video_duration', opttype=float)
    include_sha1sum = not optreader.opt('exclude_sha1sum', opttype=bool)
    if cli_args.include_sha1sum:
        # force override
        include_sha1sum = True
    verbose = optreader.opt('verbose')
    if verbose == 'on':
        print_progress = True
    elif verbose == 'off':
        print_progress = False
    else:
        if verbose != 'auto':
            msg = ("warning: '%s' is a not a valid argument to --verbose; "
                   "ignoring and using 'auto' instead\n" % verbose)
            sys.stderr.write(msg)
        if sys.stderr.isatty():
            print_progress = True
        else:
            print_progress = False

    # test bins
    try:
        fflocate.check_bins(bins)
    except OSError:
        msg = ("fatal error: at least one of '%s' and '%s' does not exist on "
               "PATH or is corrupted (expected ffmpeg and ffprobe)\n" % bins)
        sys.stderr.write(msg)
        exit(1)

    # real stuff happens from here
    returncode = 0
    for video in cli_args.videos:
        try:
            storyboard_image = StoryBoard(
                video,
                params={
                    'bins': bins,
                    'video_duration': video_duration,
                    'print_progress': print_progress,
                }).gen_storyboard(
                    params={
                        'include_sha1sum': include_sha1sum,
                        'print_progress': print_progress,
                    })
        except OSError as err:
            sys.stderr.write("error: %s\n\n" % str(err))
            returncode = 1
            continue

        tempfd, storyboard_file = tempfile.mkstemp(prefix='storyboard-',
                                                   suffix=suffix)
        os.close(tempfd)
        if output_format == 'jpeg':
            storyboard_image.save(storyboard_file,
                                  'jpeg',
                                  quality=quality,
                                  optimize=True,
                                  progressive=True)
        else:  # 'png'
            storyboard_image.save(storyboard_file, 'png', optimize=True)

        if print_progress:
            sys.stderr.write("\n")
            sys.stderr.write("storyboard saved to: ")
            sys.stderr.flush()
            print(storyboard_file)
            sys.stderr.write("\n")
        else:
            print(storyboard_file)
    return returncode