| 1 |
import subprocess |
| 2 |
import json |
| 3 |
import os |
| 4 |
from collections import defaultdict |
| 5 |
from pathlib import Path |
| 6 |
|
| 7 |
# Common video file extensions |
| 8 |
VIDEO_EXTENSIONS = {'.mp4', '.avi', '.mkv', '.mov', '.wmv', '.flv', '.webm', '.m4v', '.mpg', '.mpeg'} |
| 9 |
|
| 10 |
def get_video_info(file_path): |
| 11 |
""" |
| 12 |
Extract video bitrate and resolution using ffprobe with modified bitrate calculation |
| 13 |
""" |
| 14 |
try: |
| 15 |
# First command to get stream information |
| 16 |
result = subprocess.run([ |
| 17 |
'ffprobe', |
| 18 |
'-v', 'quiet', |
| 19 |
'-print_format', 'json', |
| 20 |
'-show_streams', |
| 21 |
'-select_streams', 'v:0', |
| 22 |
file_path |
| 23 |
], capture_output=True, text=True) |
| 24 |
|
| 25 |
# Second command specifically for bitrate using -count_packets |
| 26 |
bitrate_result = subprocess.run([ |
| 27 |
'ffprobe', |
| 28 |
'-v', 'quiet', |
| 29 |
'-select_streams', 'v:0', |
| 30 |
'-show_entries', 'format=duration,size', |
| 31 |
'-print_format', 'json', |
| 32 |
file_path |
| 33 |
], capture_output=True, text=True) |
| 34 |
|
| 35 |
probe_data = json.loads(result.stdout) |
| 36 |
bitrate_data = json.loads(bitrate_result.stdout) |
| 37 |
|
| 38 |
video_stream = probe_data['streams'][0] |
| 39 |
width = int(video_stream.get('width', 0)) |
| 40 |
height = int(video_stream.get('height', 0)) |
| 41 |
|
| 42 |
# Calculate bitrate from file size and duration |
| 43 |
try: |
| 44 |
duration = float(bitrate_data['format']['duration']) |
| 45 |
size_bits = float(bitrate_data['format']['size']) * 8 |
| 46 |
bitrate = (size_bits / duration) / 1000 # Convert to kbps |
| 47 |
except (KeyError, ZeroDivisionError): |
| 48 |
# Fallback method using direct bit_rate if available |
| 49 |
bitrate = float(video_stream.get('bit_rate', 0)) / 1000 |
| 50 |
if bitrate == 0: |
| 51 |
# Second fallback: try format bitrate |
| 52 |
format_result = subprocess.run([ |
| 53 |
'ffprobe', |
| 54 |
'-v', 'quiet', |
| 55 |
'-print_format', 'json', |
| 56 |
'-show_format', |
| 57 |
file_path |
| 58 |
], capture_output=True, text=True) |
| 59 |
format_data = json.loads(format_result.stdout) |
| 60 |
bitrate = float(format_data['format'].get('bit_rate', 0)) / 1000 |
| 61 |
|
| 62 |
return { |
| 63 |
'bitrate': bitrate, |
| 64 |
'width': width, |
| 65 |
'height': height, |
| 66 |
'path': file_path, |
| 67 |
'filename': os.path.basename(file_path), |
| 68 |
'size_mb': os.path.getsize(file_path) / (1024 * 1024) # Convert to MB |
| 69 |
} |
| 70 |
|
| 71 |
except Exception as e: |
| 72 |
print(f"\nError analyzing video {file_path}: {e}") |
| 73 |
return None |
| 74 |
|
| 75 |
def categorize_bitrate(video_info): |
| 76 |
""" |
| 77 |
Categorize bitrate based on resolution and bitrate |
| 78 |
""" |
| 79 |
width = video_info['width'] |
| 80 |
height = video_info['height'] |
| 81 |
bitrate = video_info['bitrate'] |
| 82 |
pixels = width * height |
| 83 |
|
| 84 |
# Resolution categories and their bitrate thresholds |
| 85 |
categories = { |
| 86 |
'4K': { |
| 87 |
'pixels': (3840 * 2160, float('inf')), |
| 88 |
'thresholds': { |
| 89 |
'Low': (0, 10000), |
| 90 |
'Medium': (10000, 20000), |
| 91 |
'High': (20000, float('inf')) |
| 92 |
} |
| 93 |
}, |
| 94 |
'1080p': { |
| 95 |
'pixels': (1920 * 1080, 3840 * 2160), |
| 96 |
'thresholds': { |
| 97 |
'Low': (0, 5000), |
| 98 |
'Medium': (5000, 10000), |
| 99 |
'High': (10000, float('inf')) |
| 100 |
} |
| 101 |
}, |
| 102 |
'720p': { |
| 103 |
'pixels': (1280 * 720, 1920 * 1080), |
| 104 |
'thresholds': { |
| 105 |
'Low': (0, 2500), |
| 106 |
'Medium': (2500, 5000), |
| 107 |
'High': (5000, float('inf')) |
| 108 |
} |
| 109 |
}, |
| 110 |
'SD': { |
| 111 |
'pixels': (0, 1280 * 720), |
| 112 |
'thresholds': { |
| 113 |
'Low': (0, 1000), |
| 114 |
'Medium': (1000, 2500), |
| 115 |
'High': (2500, float('inf')) |
| 116 |
} |
| 117 |
} |
| 118 |
} |
| 119 |
|
| 120 |
# Find resolution category |
| 121 |
resolution_category = None |
| 122 |
for res_name, res_info in categories.items(): |
| 123 |
min_pixels, max_pixels = res_info['pixels'] |
| 124 |
if min_pixels < pixels <= max_pixels: |
| 125 |
resolution_category = res_name |
| 126 |
break |
| 127 |
|
| 128 |
if not resolution_category: |
| 129 |
return None |
| 130 |
|
| 131 |
# Find bitrate category |
| 132 |
for bitrate_category, (min_rate, max_rate) in categories[resolution_category]['thresholds'].items(): |
| 133 |
if min_rate <= bitrate < max_rate: |
| 134 |
return f"{resolution_category} - {bitrate_category}" |
| 135 |
|
| 136 |
return None |
| 137 |
|
| 138 |
def find_video_files(folder_path): |
| 139 |
""" |
| 140 |
Recursively find all video files in the given folder |
| 141 |
""" |
| 142 |
video_files = [] |
| 143 |
for path in Path(folder_path).rglob('*'): |
| 144 |
if path.suffix.lower() in VIDEO_EXTENSIONS: |
| 145 |
video_files.append(str(path)) |
| 146 |
return video_files |
| 147 |
|
| 148 |
def analyze_folder(folder_path): |
| 149 |
""" |
| 150 |
Analyze all videos in a folder and return categorized results |
| 151 |
""" |
| 152 |
print(f"Scanning folder: {folder_path}") |
| 153 |
|
| 154 |
# Find all video files |
| 155 |
video_files = find_video_files(folder_path) |
| 156 |
total_files = len(video_files) |
| 157 |
print(f"Found {total_files} video files") |
| 158 |
|
| 159 |
# Analyze each video |
| 160 |
categorized_videos = defaultdict(list) |
| 161 |
for i, file_path in enumerate(video_files, 1): |
| 162 |
print(f"\rAnalyzing video {i}/{total_files}: {os.path.basename(file_path)}", end='') |
| 163 |
|
| 164 |
video_info = get_video_info(file_path) |
| 165 |
if video_info: |
| 166 |
category = categorize_bitrate(video_info) |
| 167 |
if category: |
| 168 |
categorized_videos[category].append(video_info) |
| 169 |
|
| 170 |
print("\nAnalysis complete!") |
| 171 |
return categorized_videos |
| 172 |
|
| 173 |
def display_results(categorized_videos): |
| 174 |
""" |
| 175 |
Display categorized videos sorted by bitrate |
| 176 |
""" |
| 177 |
print("\nVideo Analysis Results:") |
| 178 |
print("=" * 100) |
| 179 |
|
| 180 |
# Sort categories in order: 4K, 1080p, 720p, SD, each with High, Medium, Low |
| 181 |
category_order = [] |
| 182 |
for res in ['4K', '1080p', '720p', 'SD']: |
| 183 |
for quality in ['High', 'Medium', 'Low']: |
| 184 |
category_order.append(f"{res} - {quality}") |
| 185 |
|
| 186 |
# Display results for each category |
| 187 |
for category in category_order: |
| 188 |
if category in categorized_videos: |
| 189 |
videos = categorized_videos[category] |
| 190 |
# Sort videos by bitrate (highest to lowest) |
| 191 |
videos.sort(key=lambda x: x['bitrate'], reverse=True) |
| 192 |
|
| 193 |
print(f"\n{category} Quality Videos:") |
| 194 |
print("-" * 100) |
| 195 |
print(f"{'Filename':<50} {'Resolution':<15} {'Bitrate':>10} {'Size':>10}") |
| 196 |
print("-" * 100) |
| 197 |
|
| 198 |
for video in videos: |
| 199 |
filename = video['filename'] |
| 200 |
if len(filename) > 47: |
| 201 |
filename = filename[:44] + "..." |
| 202 |
print(f"{filename:<50} {video['width']}x{video['height']:<15} {video['bitrate']:>8.0f}k {video['size_mb']:>8.1f}MB") |
| 203 |
|
| 204 |
def main(): |
| 205 |
# Prompt for folder path |
| 206 |
folder_path = input("Enter the folder path to analyze: ").strip('"') |
| 207 |
|
| 208 |
# Validate folder exists |
| 209 |
if not os.path.isdir(folder_path): |
| 210 |
print("Folder does not exist. Please check the path and try again.") |
| 211 |
return |
| 212 |
|
| 213 |
# Analyze videos |
| 214 |
categorized_videos = analyze_folder(folder_path) |
| 215 |
|
| 216 |
# Display results |
| 217 |
display_results(categorized_videos) |
| 218 |
|
| 219 |
if __name__ == "__main__": |
| 220 |
main() |