diff --git a/video_probe.py b/video_probe.py new file mode 100644 index 0000000..613ce96 --- /dev/null +++ b/video_probe.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +""" +Video Probe - Extract video metadata using ffprobe +Saves metadata to .probe.json file + +Usage: + python video_probe.py +""" + +import subprocess +import json +import sys +import os +from datetime import datetime + + +def probe_video(video_path): + """Extract video metadata using ffprobe""" + + if not os.path.exists(video_path): + print(f"Error: Video file not found: {video_path}") + return None + + # ffprobe command to get all streams and format info in JSON + cmd = [ + 'ffprobe', + '-v', 'quiet', + '-print_format', 'json', + '-show_format', + '-show_streams', + video_path + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + probe_data = json.loads(result.stdout) + except subprocess.CalledProcessError as e: + print(f"Error running ffprobe: {e}") + return None + except json.JSONDecodeError as e: + print(f"Error parsing ffprobe output: {e}") + return None + + # Extract relevant information + metadata = { + "video_path": os.path.abspath(video_path), + "probed_at": datetime.now().isoformat(), + "format": {}, + "video_stream": None, + "audio_streams": [], + "subtitle_streams": [], + "other_streams": [] + } + + # Format information + if 'format' in probe_data: + fmt = probe_data['format'] + metadata['format'] = { + "filename": fmt.get('filename'), + "format_name": fmt.get('format_name'), + "format_long_name": fmt.get('format_long_name'), + "duration": float(fmt.get('duration', 0)), + "size": int(fmt.get('size', 0)), + "bit_rate": int(fmt.get('bit_rate', 0)), + "probe_score": fmt.get('probe_score'), + "tags": fmt.get('tags', {}) + } + + # Stream information + if 'streams' in probe_data: + for stream in probe_data['streams']: + codec_type = stream.get('codec_type') + + if codec_type == 'video': + # Find the main video stream (usually first one) + if metadata['video_stream'] is None: + metadata['video_stream'] = { + "index": stream.get('index'), + "codec_name": stream.get('codec_name'), + "codec_long_name": stream.get('codec_long_name'), + "profile": stream.get('profile'), + "level": stream.get('level'), + "width": stream.get('width'), + "height": stream.get('height'), + "aspect_ratio": stream.get('display_aspect_ratio'), + "pix_fmt": stream.get('pix_fmt'), + "r_frame_rate": stream.get('r_frame_rate'), + "avg_frame_rate": stream.get('avg_frame_rate'), + "duration": float(stream.get('duration', 0)) if 'duration' in stream else None, + "bit_rate": int(stream.get('bit_rate', 0)) if 'bit_rate' in stream else None, + "tags": stream.get('tags', {}) + } + + elif codec_type == 'audio': + metadata['audio_streams'].append({ + "index": stream.get('index'), + "codec_name": stream.get('codec_name'), + "codec_long_name": stream.get('codec_long_name'), + "channels": stream.get('channels'), + "sample_rate": stream.get('sample_rate'), + "bit_rate": int(stream.get('bit_rate', 0)) if 'bit_rate' in stream else None, + "tags": stream.get('tags', {}) + }) + + elif codec_type == 'subtitle': + metadata['subtitle_streams'].append({ + "index": stream.get('index'), + "codec_name": stream.get('codec_name'), + "language": stream.get('tags', {}).get('language'), + "tags": stream.get('tags', {}) + }) + + else: + metadata['other_streams'].append({ + "index": stream.get('index'), + "codec_type": codec_type, + "codec_name": stream.get('codec_name'), + "tags": stream.get('tags', {}) + }) + + return metadata + + +def save_probe_metadata(video_path, metadata): + """Save probe metadata to JSON file""" + + video_dir = os.path.dirname(video_path) + video_name = os.path.splitext(os.path.basename(video_path))[0] + output_file = os.path.join(video_dir, f"{video_name}.probe.json") + + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(metadata, f, indent=2, ensure_ascii=False) + + return output_file + + +def main(): + if len(sys.argv) < 2: + print(f"Usage: python {sys.argv[0]} ") + print(f"Example: python {sys.argv[0]} video.mp4") + sys.exit(1) + + video_path = sys.argv[1] + + print(f"Probing video: {video_path}") + print("=" * 60) + + # Probe video + metadata = probe_video(video_path) + + if metadata is None: + print("Failed to probe video") + sys.exit(1) + + # Save to JSON + output_file = save_probe_metadata(video_path, metadata) + + # Print summary + print(f"\n✓ Video probed successfully!") + print(f"\nFile: {metadata['format'].get('filename')}") + print(f"Format: {metadata['format'].get('format_long_name')}") + print(f"Duration: {metadata['format'].get('duration', 0):.2f} seconds") + print(f"Size: {metadata['format'].get('size', 0) / 1024 / 1024:.2f} MB") + print(f"Bit rate: {metadata['format'].get('bit_rate', 0) / 1000:.0f} kbps") + + if metadata['video_stream']: + vs = metadata['video_stream'] + print(f"\nVideo Stream:") + print(f" Codec: {vs.get('codec_name')} ({vs.get('profile')})") + print(f" Resolution: {vs.get('width')}x{vs.get('height')}") + print(f" Frame rate: {vs.get('r_frame_rate')}") + print(f" Pixel format: {vs.get('pix_fmt')}") + + if metadata['audio_streams']: + print(f"\nAudio Streams: {len(metadata['audio_streams'])}") + for i, audio in enumerate(metadata['audio_streams'], 1): + print(f" [{i}] {audio.get('codec_name')} - {audio.get('channels')} channels @ {audio.get('sample_rate')} Hz") + + if metadata['subtitle_streams']: + print(f"\nSubtitle Streams: {len(metadata['subtitle_streams'])}") + for i, sub in enumerate(metadata['subtitle_streams'], 1): + print(f" [{i}] {sub.get('codec_name')} ({sub.get('language')})") + + print(f"\n✓ Metadata saved to: {output_file}") + print("=" * 60) + + +if __name__ == "__main__": + main() \ No newline at end of file