📦 deps(thirdparty): update snapshots
This commit is contained in:
+197
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def format_size(size_bytes):
|
||||
"""Formats file size in bytes to a human-readable string."""
|
||||
try:
|
||||
size_bytes = int(size_bytes)
|
||||
except (ValueError, TypeError):
|
||||
return "Unknown size"
|
||||
|
||||
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||
if size_bytes < 1024.0:
|
||||
return f"{size_bytes:.2f} {unit}"
|
||||
size_bytes /= 1024.0
|
||||
return f"{size_bytes:.2f} TB"
|
||||
|
||||
def parse_fps(fps_str):
|
||||
"""Parses fractional frame rates like '30/1' or '24000/1001' into floats."""
|
||||
if not fps_str:
|
||||
return "Unknown"
|
||||
if "/" in fps_str:
|
||||
try:
|
||||
num, den = map(float, fps_str.split("/"))
|
||||
if den != 0:
|
||||
val = num / den
|
||||
if val.is_integer():
|
||||
return f"{int(val)} fps"
|
||||
return f"{val:.2f} fps"
|
||||
except (ValueError, ZeroDivisionError):
|
||||
pass
|
||||
try:
|
||||
val = float(fps_str)
|
||||
if val.is_integer():
|
||||
return f"{int(val)} fps"
|
||||
return f"{val:.2f} fps"
|
||||
except ValueError:
|
||||
return fps_str
|
||||
|
||||
def inspect_video(file_path, raw=False):
|
||||
"""Runs ffprobe on the video file and returns parsed metadata dictionary."""
|
||||
if not os.path.exists(file_path):
|
||||
raise FileNotFoundError(f"File not found: {file_path}")
|
||||
|
||||
# Check if ffprobe is available
|
||||
try:
|
||||
subprocess.run(["ffprobe", "-version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
||||
except (subprocess.SubprocessError, FileNotFoundError):
|
||||
raise RuntimeError("ffprobe is not installed or not found in system PATH.")
|
||||
|
||||
cmd = [
|
||||
"ffprobe",
|
||||
"-v", "error",
|
||||
"-show_format",
|
||||
"-show_streams",
|
||||
"-of", "json",
|
||||
file_path
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
|
||||
data = json.loads(result.stdout)
|
||||
|
||||
if raw:
|
||||
return data
|
||||
|
||||
# Extract format level details
|
||||
fmt = data.get("format", {})
|
||||
duration = fmt.get("duration")
|
||||
size_bytes = fmt.get("size")
|
||||
bitrate = fmt.get("bit_rate")
|
||||
|
||||
# Format files size
|
||||
size_str = format_size(size_bytes) if size_bytes else "Unknown"
|
||||
|
||||
# Parse duration
|
||||
try:
|
||||
duration_val = float(duration) if duration else 0.0
|
||||
duration_str = f"{duration_val:.2f}s"
|
||||
except ValueError:
|
||||
duration_str = "Unknown"
|
||||
duration_val = None
|
||||
|
||||
# Parse bitrate
|
||||
try:
|
||||
bitrate_kbps = f"{int(float(bitrate) / 1000)} kbps" if bitrate else "Unknown"
|
||||
except ValueError:
|
||||
bitrate_kbps = "Unknown"
|
||||
|
||||
video_streams = [s for s in data.get("streams", []) if s.get("codec_type") == "video"]
|
||||
audio_streams = [s for s in data.get("streams", []) if s.get("codec_type") == "audio"]
|
||||
|
||||
has_video = len(video_streams) > 0
|
||||
has_audio = len(audio_streams) > 0
|
||||
|
||||
video_info = {}
|
||||
if has_video:
|
||||
v = video_streams[0]
|
||||
width = v.get("width")
|
||||
height = v.get("height")
|
||||
codec = v.get("codec_name", "Unknown").upper()
|
||||
r_fps = parse_fps(v.get("r_frame_rate"))
|
||||
avg_fps = parse_fps(v.get("avg_frame_rate"))
|
||||
|
||||
# Prefer r_frame_rate but fallback to avg
|
||||
fps = r_fps if r_fps != "0 fps" and r_fps != "Unknown" else avg_fps
|
||||
|
||||
video_info = {
|
||||
"resolution": f"{width}x{height}" if width and height else "Unknown",
|
||||
"width": width,
|
||||
"height": height,
|
||||
"fps": fps,
|
||||
"codec": codec,
|
||||
"duration": v.get("duration")
|
||||
}
|
||||
|
||||
audio_info = {}
|
||||
if has_audio:
|
||||
a = audio_streams[0]
|
||||
codec = a.get("codec_name", "Unknown").upper()
|
||||
channels = a.get("channels", "Unknown")
|
||||
sample_rate = a.get("sample_rate")
|
||||
sample_rate_khz = f"{float(sample_rate)/1000:.1f} kHz" if sample_rate else "Unknown"
|
||||
|
||||
audio_info = {
|
||||
"codec": codec,
|
||||
"channels": channels,
|
||||
"sample_rate": sample_rate_khz
|
||||
}
|
||||
|
||||
return {
|
||||
"file_name": os.path.basename(file_path),
|
||||
"file_size": size_str,
|
||||
"size_bytes": size_bytes,
|
||||
"duration": duration_str,
|
||||
"duration_seconds": duration_val,
|
||||
"bitrate": bitrate_kbps,
|
||||
"has_video": has_video,
|
||||
"video": video_info,
|
||||
"has_audio": has_audio,
|
||||
"audio": audio_info
|
||||
}
|
||||
|
||||
def print_terminal_report(info):
|
||||
"""Prints an aligned terminal report."""
|
||||
print(f"\nVideo Inspection Report: {info['file_name']}")
|
||||
print("=" * 50)
|
||||
print(f"File Size : {info['file_size']}")
|
||||
print(f"Duration : {info['duration']}")
|
||||
print(f"Bitrate : {info['bitrate']}")
|
||||
|
||||
print("\nVideo Stream Details:")
|
||||
if info["has_video"]:
|
||||
v = info["video"]
|
||||
print(f" * Resolution : {v['resolution']}")
|
||||
print(f" * Frame Rate : {v['fps']}")
|
||||
print(f" * Codec : {v['codec']}")
|
||||
else:
|
||||
print(" * No Video Stream Found.")
|
||||
|
||||
print("\nAudio Stream Details:")
|
||||
if info["has_audio"]:
|
||||
a = info["audio"]
|
||||
print(" * Status : Audio Present")
|
||||
print(f" * Codec : {a['codec']}")
|
||||
print(f" * Channels : {a['channels']}")
|
||||
print(f" * Sample Rate: {a['sample_rate']}")
|
||||
else:
|
||||
print(" * Status : No Audio Stream Present")
|
||||
print()
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Inspect video details (duration, frame rate, resolution, audio presence) using ffprobe.")
|
||||
parser.add_argument("file", help="Path to the video file to inspect")
|
||||
parser.add_argument("--json", action="store_true", help="Output parsed summary in JSON format")
|
||||
parser.add_argument("--raw", action="store_true", help="Output raw unmodified ffprobe JSON data")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.raw:
|
||||
info = inspect_video(args.file, raw=True)
|
||||
print(json.dumps(info, indent=2))
|
||||
else:
|
||||
info = inspect_video(args.file, raw=False)
|
||||
if args.json:
|
||||
print(json.dumps(info, indent=2))
|
||||
else:
|
||||
print_terminal_report(info)
|
||||
except Exception as e:
|
||||
print(f"Error inspecting video: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user