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
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
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()
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)
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()