diff --git a/bin/ffmpeg-parser b/bin/ffmpeg-parser index 196a901..7292465 120000 --- a/bin/ffmpeg-parser +++ b/bin/ffmpeg-parser @@ -1 +1 @@ -../av/ffmpeg-parser \ No newline at end of file +../py-packages/ffmpeg-parser/ffmpegparser/ffmpegparser.py \ No newline at end of file diff --git a/bin/ffprobe-parser b/bin/ffprobe-parser new file mode 120000 index 0000000..faefc1d --- /dev/null +++ b/bin/ffprobe-parser @@ -0,0 +1 @@ +../py-packages/ffmpeg-parser/ffmpegparser/ffprobeparser.py \ No newline at end of file diff --git a/py-packages/Makefile b/py-packages/Makefile index bfb1c65..8a4e511 100644 --- a/py-packages/Makefile +++ b/py-packages/Makefile @@ -25,7 +25,7 @@ format: ## Reformat packages with black black SimpleWebPage/ black TSVFilter/ black markslider/ - + black ffmpeg-parser/ tar-SimpleWebPage: clean ## Create package for SimpleWebPage tar czf SimpleWebPage.tgz SimpleWebPage/ diff --git a/py-packages/ffmpeg-parser/ffmpegparser/__init__.py b/py-packages/ffmpeg-parser/ffmpegparser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/av/ffmpeg-parser b/py-packages/ffmpeg-parser/ffmpegparser/ffmpegparser.py similarity index 97% rename from av/ffmpeg-parser rename to py-packages/ffmpeg-parser/ffmpegparser/ffmpegparser.py index 9f935a9..406aef9 100755 --- a/av/ffmpeg-parser +++ b/py-packages/ffmpeg-parser/ffmpegparser/ffmpegparser.py @@ -8,6 +8,7 @@ import parse from ansi import cursor from datetime import datetime +__version__=1.0 class Chopper: def __init__(self, buf): @@ -247,11 +248,13 @@ def ask_for_overwrite(commands, path): return commands -if __name__ == "__main__": +def main(): commands = sys.argv[1:] if len(commands) == 1: if commands[0] == "-h": - print("This command passes all arguments to FFMPEG, and parses output to readable format with progress") + print( + "This command passes all arguments to FFMPEG, and parses output to readable format with progress" + ) sys.exit(0) try: has_overwrite = parse_overwrite(commands) @@ -290,3 +293,7 @@ if __name__ == "__main__": while process.poll() != None: time.sleep(1) sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/py-packages/ffmpeg-parser/ffmpegparser/ffprobeparser.py b/py-packages/ffmpeg-parser/ffmpegparser/ffprobeparser.py new file mode 100755 index 0000000..847bddc --- /dev/null +++ b/py-packages/ffmpeg-parser/ffmpegparser/ffprobeparser.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +import subprocess +import sys +import os +import time +import parse +import argparse +import json +from ansi.colour import fg, fx +from datetime import datetime + + +class Probe: + def __init__(self): + parser = argparse.ArgumentParser(description="Readable version of ffprobe. Give filename as argument.") + parser.add_argument("-c", dest="colorize", action="store_true", default=False, help="Colorize") + parser.add_argument("filename", help="File to probe") + parsed = parser.parse_args() + + self.filename = parsed.filename + self.colorize = parsed.colorize + self.run_probe() + self.print() + + def run_probe(self): + process = subprocess.Popen( + [ + "", + "-v", + "error", + "-hide_banner", + "-of", + "default=noprint_wrappers=0", + "-print_format", "json", + "-show_format", + "-show_streams", + self.filename, + ], + executable="ffprobe", + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + self.raw_data, errors = process.communicate() + if len(errors) > 0: + print(errors) + self.json_data = json.loads(self.raw_data) + + + def print(self): + # ~ print(self.json_data['format']) + self.json_data['format']['time'] = self._timestr(self.json_data['format'].get('duration',"0")) + self.json_data['format']['hrate'] = self._speedfmt(self.json_data['format'].get('bit_rate',"0")) + self.json_data['format']['hsize'] = self._sizefmt(float(self.json_data['format'].get('size',0))) + self.json_data['format'].update(self._get_colors()) + + msg = """{Y}==== Format ===={z} + File: {filename} + Format: {format_long_name} ({format_name}) + Length: {time} + Size: {hsize} +Bitrate: {hrate} +Streams: {nb_streams}""".format(**self.json_data['format'] + ) + print(msg) + for stream in self.json_data['streams']: + original = stream.copy() + stream.update(self._get_colors()) + + if stream['codec_type'] == "video": + msg = self.format_video(stream) + elif stream['codec_type'] == "audio": + msg = self.format_audio(stream) + elif stream['codec_type'] == "subtitle": + msg = self.format_subtitle(stream) + else: + msg = "Unrecognized stream\n{}".format(json.dumps(original, indent=2)) + + print(msg) + + + def format_subtitle(self, stream): + + stream['tags-lang'] = stream.get('tags',{}).get('language',"NA") + msg = """{Y}==== Stream:{index} ===={z} + Type: {G}{codec_type}{z} + Codec: {codec_long_name} ({codec_name}) +Language: {tags-lang}""".format(**stream) + return msg + + def format_video(self, stream): + stream['hrate'] = self._speedfmt(stream.get("bit_rate",0)) + stream['fps'] = "{:.2f}".format(float(stream['r_frame_rate'].split('/')[0]) / float(stream['r_frame_rate'].split('/')[1])) + msg = """{Y}==== Stream:{index} ===={z} + Type: {G}{codec_type}{z} + Codec: {codec_long_name} ({codec_name}) + Bitrate: {hrate} + Resolution: {width}x{height} + Aspect: {display_aspect_ratio} + Profile: {profile} + FPS: {r_frame_rate} ({fps})""".format(**stream) + + return msg + + def format_audio(self, stream): + stream['hrate'] = self._speedfmt(stream.get("bit_rate",0)) + msg = """{Y}==== Stream:{index} ===={z} + Type: {G}{codec_type}{z} + Codec: {codec_long_name} ({codec_name}) + Bitrate: {hrate} +Sample Rate: {sample_rate} Hz + Channels: {channels} ({channel_layout})""".format(**stream) + + return msg + + + def _timestr(self, sec): + msec = int((float(sec) - int(float(sec)))*1000) + sec = int(float(sec)) + try: + hours = int(sec) // 3600 % 24 + minutes = int(sec) // 60 % 60 + seconds = int(sec) % 60 + + return "{:02d}:{:02d}:{:02d}.{:03d}".format(hours, minutes, seconds, msec) + except Exception: + return sec + + def _mbstr(self, b): + + try: + return "{:.1f}".format((float(b) / (1024 ** 2))) + except Exception: + return b + + def _speedfmt(self, b): + + return self._sizefmt(float(b), suffix="bit/s") + try: + return "{:.1f}".format((float(b) / (1024 ** 2))) + except Exception: + return b + + def _sizefmt(self, num, suffix='B'): + + num = float(num) + for unit in ['','K','M','G','T','P','E','Z']: + if abs(num) < 1024.0: + return "%3.1f %s%s" % (num, unit, suffix) + num /= 1024.0 + return "%.1f %s%s" % (num, 'Y', suffix) + + def _get_colors(self): + + if self.colorize: + return { + 'Y': fg.boldyellow, + 'G': fg.boldgreen, + 'z': fx.reset, + } + else: + return { + 'Y': "", + 'G': "", + 'z': "", + } + +def main(): + Probe() + +if __name__ == "__main__": + main() diff --git a/py-packages/ffmpeg-parser/setup.py b/py-packages/ffmpeg-parser/setup.py new file mode 100644 index 0000000..9064103 --- /dev/null +++ b/py-packages/ffmpeg-parser/setup.py @@ -0,0 +1,26 @@ +from distutils.core import setup + + +def version_reader(path): + for line in open(path, "rt").read(1024).split("\n"): + if line.startswith("__version__"): + return line.split("=")[1].strip().replace('"', "") + + +version = version_reader(os.path.join("ffmpegparser", "ffmpegparser.py")) +setup( + name="ffmpegparser", + packages=["ffmpegparser"], + version=version, + description="View parse ffmpeg and ffprobe output nicer", + author="Ville Rantanen", + author_email="ville.q.rantanen@gmail.com", + keywords=["ffmpeg"], + entry_points={ + "console_scripts": [ + "ffmpeg-parser = ffmpegparser.ffmpegparser:main", + "ffprobe-parser = ffmpegparser.ffprobeparser:main" + ] + }, + install_requires=["parse", "ansi"], +)