Example #1
0
 def make_src_file(self, extension):
     mime = self.get_mimetype(self.buffer)
     is_webp = mime == 'image/webp'
     if not is_webp:
         with named_tmp_file(data=self.buffer,
                             suffix=extension) as src_file:
             yield src_file
     else:
         with make_tmp_dir() as tmp_dir:
             im = self.image
             num_frames = im.n_frames
             num_digits = len(str(num_frames))
             format_str = "%(dir)s/%(idx)0{}d.tif".format(num_digits)
             concat_buf = BytesIO()
             concat_buf.write(b"ffconcat version 1.0\n")
             concat_buf.write(b"# %dx%d\n" % im.size)
             for i, frame in enumerate(ImageSequence.Iterator(im)):
                 frame.load()
                 duration_ms = im.info['duration']
                 out_file = format_str % {'dir': tmp_dir, 'idx': i}
                 frame.save(out_file, lossless=True)
                 concat_buf.write(b"file '%s'\n" % out_file.encode('utf-8'))
                 duration_str = "%s" % (Decimal(duration_ms) /
                                        Decimal(1000))
                 concat_buf.write(b"duration %s\n" %
                                  duration_str.encode('utf-8'))
             self.buffer = concat_buf.getvalue()
             with named_tmp_file(data=self.buffer,
                                 suffix='.txt') as src_file:
                 yield src_file
Example #2
0
    def load(self, buffer, extension):
        self.engine = self.get_engine(buffer, extension)
        if self.context.request.format and not self.context.request.filters:
            # RequestParameters.filters is an empty list when none are in the url,
            # and ImagingHandler._write_results_to_client assumes that if
            # context.request.format is set then it came from the format filter.
            # Since we set the format in the engine this causes a TypeError,
            # so we need to ensure that it is a string here.
            self.context.request.filters = ""
        logger.debug("Set engine to %s (extension %s)" %
                     (type(self.engine).__module__, extension))
        still_frame_pos = getattr(self.context.request, 'still_position', None)
        # Are we requesting a still frame?
        if self.engine is self.ffmpeg_engine and still_frame_pos:
            with named_tmp_file(data=buffer, suffix=extension) as src_file:
                buffer = self.ffmpeg_engine.run_ffmpeg(
                    src_file, 'png',
                    ['-ss', still_frame_pos, '-frames:v', '1'])
                self.engine = self.image_engine
                extension = '.png'
                if not self.context.request.format:
                    self.context.request.format = 'jpg'

        # Change the default extension if we're transcoding video
        if self.engine is self.ffmpeg_engine and extension == ".jpg":
            extension = ".mp4"

        self.extension = extension
        self.engine.load(buffer, extension)
def ffprobe(buf, extension=None, flat=True):
    """
    Returns a dict based on the json output of ffprobe. If ``flat`` is ``True``,
    the 'format' key-values are made top-level, as well as the first video stream
    in the file (the rest are discarded). Any 'stream' keys that have the same
    name as a key in 'format' are prefixed with ``stream_``.
    """
    global FFPROBE_PATH

    if FFPROBE_PATH is None:
        FFPROBE_PATH = which('ffprobe')

    if FFPROBE_PATH is None:
        raise FFmpegError("Could not find ffprobe executable")

    with named_tmp_file(data=buf, extension=extension) as input_file:
        command = [
            FFPROBE_PATH, '-hide_banner', '-loglevel', 'fatal', '-show_error',
            '-show_format', '-show_streams', '-print_format', 'json',
            '-i', input_file,
        ]

        proc = Popen(command, stdout=PIPE, stdin=PIPE, stderr=PIPE)

        stdout, stderr = proc.communicate()

        try:
            probe_data = json.loads(stdout)
        except ValueError:
            probe_data = None

        if not isinstance(probe_data, dict):
            raise FFmpegError("ffprobe returned invalid data")

        if 'error' in probe_data:
            raise FFmpegError("%(string)s (%(code)s)" % probe_data['error'])

        if 'format' not in probe_data or 'streams' not in probe_data:
            raise FFmpegError("ffprobe returned invalid data")

        if not flat:
            return probe_data

        try:
            video_stream = next(s for s in probe_data['streams'] if s['codec_type'] == 'video')
        except StopIteration:
            raise FFmpegError("File is missing a video stream")

        data = probe_data['format']
        for k, v in six.iteritems(video_stream):
            if k in data:
                k = 'stream_%s' % k
            data[k] = v
        return data
Example #4
0
    def transcode_to_gif(self, src_file):
        with named_tmp_file(suffix='.png') as palette_file:
            if not self.use_gif_engine and self.ffmpeg_vfilters:
                libav_filter = ','.join(self.ffmpeg_vfilters)
            else:
                libav_filter = 'scale=%d:%d:flags=lanczos' % self.original_size

            input_flags = ['-f', 'concat', '-safe', '0'
                           ] if src_file.endswith('.txt') else []

            self.run_cmd([
                self.ffmpeg_path,
                '-hide_banner',
            ] + input_flags + [
                '-i',
                src_file,
                '-lavfi',
                "%s,palettegen" % libav_filter,
                '-y',
                palette_file,
            ])

            gif_buffer = self.run_cmd([
                self.ffmpeg_path,
                '-hide_banner',
            ] + input_flags + [
                '-i',
                src_file,
                '-i',
                palette_file,
                '-lavfi',
                "%s[x];[x][1:v]paletteuse" % libav_filter,
                '-f',
                'gif',
                '-',
            ])

            if not self.use_gif_engine:
                return gif_buffer
            else:
                gif_engine = self.context.modules.gif_engine
                gif_engine.load(gif_buffer, '.gif')
                gif_engine.operations.append('-O3')

                for op_fn, op_args in self.operations:
                    gif_engine_method = getattr(gif_engine, op_fn)
                    gif_engine_method(*op_args)

            return gif_engine.read()
Example #5
0
    def load(self, buffer, extension):
        mime = BaseEngine.get_mimetype(buffer)

        is_gif = extension == '.gif'
        is_webp = extension == '.webp'

        if is_webp and self.ffmpeg_handle_animated_webp and is_animated(
                buffer):
            logger.debug("Setting engine to %s (extension %s)" %
                         (self.context.config.FFMPEG_ENGINE, extension))
            self.engine = self.ffmpeg_engine
        elif is_gif and self.ffmpeg_handle_animated_gif and is_animated(
                buffer):
            logger.debug("Setting engine to %s (extension %s)" %
                         (self.context.config.FFMPEG_ENGINE, extension))
            self.engine = self.ffmpeg_engine
        elif is_gif and self.use_gif_engine:
            logger.debug("Setting engine to %s (extension %s)" %
                         (self.context.config.GIF_ENGINE, extension))
            self.engine = self.context.modules.gif_engine
        elif mime.startswith('video/'):
            logger.debug("Setting engine to %s (extension %s)" %
                         (self.context.config.FFMPEG_ENGINE, extension))
            self.engine = self.ffmpeg_engine
        else:
            logger.debug("Setting engine to %s (extension %s)" %
                         (self.context.config.IMAGE_ENGINE, extension))
            self.engine = self.image_engine

        still_frame_pos = getattr(self.context.request, 'still_position', None)
        # Are we requesting a still frame?
        if self.engine is self.ffmpeg_engine and still_frame_pos:
            with named_tmp_file(data=buffer, suffix=extension) as src_file:
                buffer = self.ffmpeg_engine.run_ffmpeg(
                    src_file, 'png',
                    ['-ss', still_frame_pos, '-frames:v', '1'])
                self.engine = self.image_engine
                extension = '.png'
                if not self.context.request.format:
                    self.context.request.format = 'jpg'

        self.extension = extension
        self.engine.load(buffer, extension)
Example #6
0
    def run_ffmpeg(self, input_file, out_format, flags=None, two_pass=False):
        flags = flags or []

        input_flags = []
        # text files in concat-format require additional input flags
        if input_file.endswith('.txt'):
            input_flags += ['-f', 'concat', '-safe', '0']
            txt_src = self.buffer.decode('utf-8')
            # If all frames have the same duration, set the -r flag to ensure
            # that no frames get dropped
            durations = set(re.findall(r'duration ([\d\.]+)', txt_src))
            if len(durations) == 1:
                duration = list(durations)[0]
                input_flags += ['-r', '1/%s' % duration]
                flags += ['-r', '1/%s' % duration]

        with named_tmp_file(suffix='.%s' % out_format) as out_file:
            if not two_pass:
                self.run_cmd([
                    self.ffmpeg_path,
                    '-hide_banner',
                ] + input_flags + [
                    '-i',
                    input_file,
                ] + flags + ['-y', out_file])
                with open(out_file, mode='rb') as f:
                    return f.read()

            with named_tmp_file(suffix='.log') as passlogfile:
                if '-x265-params' in flags:
                    params_idx = flags.index('-x265-params') + 1
                    if flags[params_idx]:
                        x265_params = flags[params_idx].split(":")
                    else:
                        x265_params = []
                    pass_one_flags = copy.copy(flags)
                    pass_two_flags = copy.copy(flags)
                    pass_one_flags[params_idx] = ":".join(
                        x265_params +
                        ['pass=1', 'stats=%s' % passlogfile])
                    pass_two_flags[params_idx] = ":".join(
                        x265_params +
                        ['pass=2', 'stats=%s' % passlogfile])
                else:
                    pass_one_flags = flags + [
                        '-pass', '1', '-passlogfile', passlogfile
                    ]
                    pass_two_flags = flags + [
                        '-pass', '2', '-passlogfile', passlogfile
                    ]

                self.run_cmd([
                    self.ffmpeg_path,
                    '-hide_banner',
                ] + input_flags + [
                    '-i',
                    input_file,
                ] + pass_one_flags + ['-y', '/dev/null'])

                self.run_cmd([
                    self.ffmpeg_path,
                    '-hide_banner',
                ] + input_flags + [
                    '-i',
                    input_file,
                ] + pass_two_flags + ['-y', out_file])

                with open(out_file, mode='rb') as f:
                    return f.read()