def ffprobe(stream: IO[bytes], cmd='ffprobe', **kwargs): """Run ffprobe on an input stream and return a JSON representation of the output. Code adopted from ffmpeg-python by Karl Kroening (Apache License 2.0). Copyright 2017 Karl Kroening Raises: :class:`ffmpeg.Error`: if ffprobe returns a non-zero exit code, an :class:`Error` is returned with a generic error message. The stderr output can be retrieved by accessing the ``stderr`` property of the exception. """ args = [cmd, '-show_format', '-show_streams', '-of', 'json'] args += convert_kwargs_to_cmd_line_args(kwargs) args += ["-"] p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) assert p.stdin copyfileobj(p.stdin, stream) out, err = p.communicate() if p.returncode != 0: raise ffmpeg.Error('ffprobe', out, err) return json.loads(out.decode('utf-8'))
def convert_video_progress_bar(source: str, dest: str, manager=None): if manager is None: manager = enlighten.get_manager() name = source.rsplit(os.path.sep,1)[-1] if get_bitdepth(source).is_10bit: args = CONVERT_COMMAND_10Bits.format(source=source, dest=dest) else: args = CONVERT_COMMAND.format(source=source, dest=dest) proc = expect.spawn(args, encoding='utf-8') pbar = None try: proc.expect(pattern_duration) total = sum(map(lambda x: float(x[1])*60**x[0],enumerate(reversed(proc.match.groups()[0].strip().split(':'))))) cont = 0 pbar = manager.counter(total=100, desc=name, unit='%',bar_format=BAR_FMT, counter_format=COUNTER_FMT) while True: proc.expect(pattern_progress) progress = sum(map(lambda x: float(x[1])*60**x[0],enumerate(reversed(proc.match.groups()[0].strip().split(':'))))) percent = progress/total*100 pbar.update(percent-cont) cont = percent except expect.EOF: pass finally: if pbar is not None: pbar.close() proc.expect(expect.EOF) res = proc.before res += proc.read() exitstatus = proc.wait() if exitstatus: raise ffmpeg.Error('ffmpeg','',res)
def play(filename, cmd=None, **kwargs): proc = play_async(filename, cmd, **kwargs) out, err = proc.communicate(None) retcode = proc.poll() if retcode: raise ffmpeg.Error('ffplay', out, err) return out, err
def __execute_ffmpeg(self): p = subprocess.Popen(self.__ffmpeg_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() retcode = p.poll() if retcode: raise ffmpeg.Error('ffmpeg', out, err) return out, err
def convert_video_progress_bar(source: str, dest: str, manager=None): if manager is None: manager = enlighten.get_manager() stream = ffmpeg.input(source) stream = ffmpeg.output(stream, dest, vcodec='libx265', crf='28') args = ffmpeg.compile(stream, 'ffmpeg') args.insert(1, '-progress pipe:1') args = map(lambda x: '"' + x + '"' if '\\' in x or '/' in x else x, args) args = list(args) name = source.rsplit(os.path.sep, 1)[-1] proc = expect.spawn(' '.join(args), encoding='utf-8') pbar = None try: proc.expect(pattern_duration) total = sum( map(lambda x: float(x[1]) * 60**x[0], enumerate(reversed( proc.match.groups()[0].strip().split(':'))))) cont = 0 pbar = manager.counter(total=100, desc=name, unit='%', bar_format=BAR_FMT, counter_format=COUNTER_FMT) while True: proc.expect(pattern_progress) progress = sum( map( lambda x: float(x[1]) * 60**x[0], enumerate( reversed(proc.match.groups()[0].strip().split(':'))))) percent = progress / total * 100 pbar.update(percent - cont) cont = percent except expect.EOF: pass finally: if pbar is not None: pbar.close() proc.expect(expect.EOF) res = proc.before res += proc.read() exitstatus = proc.wait() if exitstatus: raise ffmpeg.Error('ffmpeg', '', res)
def ffmpeg_probe(path, **kwargs): """Run ffprobe on the specified file and return a JSON representation of the output. Based on the `ffmpeg.probe` provided by `ffmpeg-python`, but allows passing along additional options to `ffmpeg`. Parameters ---------- path : str This parameter can be a path or URL pointing directly to a video file or stream. Raises ------ ffmpeg.Error If `ffprobe` returns a non-zero exit code. The stderr output can be retrieved by accessing the `stderr` property of the exception. """ if not is_path_stream(path): path = os.path.expanduser(path) # Gather all `kwargs` into a list of tokens to send to `ffprobe`. additional_args = [] for key, value in kwargs.items(): if not key.startswith('-'): key = f'-{key}' additional_args.extend([key, str(value)]) args = [ 'ffprobe', *additional_args, '-show_format', '-show_streams', '-of', 'json', path ] proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() if proc.returncode != 0: raise ffmpeg.Error('ffprobe', out, err) return json.loads(out.decode('utf-8'))
def gif_conversion(file: IO[bytes], channel_id: str) -> IO[bytes]: """Convert Telegram GIF to real GIF, the NT way.""" gif_file = NamedTemporaryFile(suffix='.gif') file.seek(0) # Use custom ffprobe command to read from stream metadata = ffprobe(file) # Set input/output of ffmpeg to stream stream = ffmpeg.input("pipe:") if channel_id.startswith("blueset.wechat") and metadata.get( 'width', 0) > 600: # Workaround: Compress GIF for slave channel `blueset.wechat` # TODO: Move this logic to `blueset.wechat` in the future stream = stream.filter("scale", 600, -2) # Need to specify file format here as no extension hint presents. args = stream.output("pipe:", format="gif").compile() file.seek(0) # subprocess.Popen would still try to access the file handle instead of # using standard IO interface. Not sure if that would work on Windows. # Using the most classic buffer and copy via IO interface just to play # safe. p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) assert p.stdin copyfileobj(file, p.stdin) p.stdin.close() # Raise exception if error occurs, just like ffmpeg-python. if p.returncode != 0 and p.stderr: err = p.stderr.read().decode() print(err, file=sys.stderr) raise ffmpeg.Error('ffmpeg', "", err) assert p.stdout copyfileobj(p.stdout, gif_file) file.close() gif_file.seek(0) return gif_file