show-calc/tv_show_analyzer.py
2026-04-26 09:10:36 -04:00

195 lines
6.0 KiB
Python

#!/usr/bin/env python3
"""
TV Show Directory Analyzer
Scans a TV show directory structure and generates a CSV report with:
- Show metadata
- Season folder validation
- Directory structure issues
- Size metrics and ratios
"""
import os
import sys
import csv
from pathlib import Path
from collections import defaultdict
def get_folder_size_gb(path):
"""Calculate total size of a folder in GB"""
total_size = 0
try:
for dirpath, dirnames, filenames in os.walk(path):
for filename in filenames:
filepath = os.path.join(dirpath, filename)
try:
total_size += os.path.getsize(filepath)
except (OSError, FileNotFoundError):
pass
except (PermissionError, OSError):
pass
return total_size / (1024 ** 3)
def count_episodes_in_folder(folder_path):
"""Count files in a folder (proxy for episode count)"""
try:
return len([f for f in os.listdir(folder_path)
if os.path.isfile(os.path.join(folder_path, f))])
except (PermissionError, OSError):
return 0
def is_season_folder(folder_name):
"""Check if folder name matches Season pattern"""
name_lower = folder_name.lower().strip()
# Matches: Season 1, season 1, Season1, S01, etc.
if name_lower.startswith('season'):
return True
if name_lower.startswith('s') and len(name_lower) <= 4:
try:
int(name_lower[1:])
return True
except ValueError:
pass
return False
def is_featurette_folder(folder_name):
"""Check if folder appears to be featurettes"""
name_lower = folder_name.lower()
return 'featurette' in name_lower or 'special' in name_lower or 'bonus' in name_lower
def analyze_show_directory(show_path):
"""
Analyze a single show directory
Returns dict with analysis results
"""
show_name = os.path.basename(show_path)
try:
subdirs = [d for d in os.listdir(show_path)
if os.path.isdir(os.path.join(show_path, d))]
except (PermissionError, OSError) as e:
return {
'show_name': show_name,
'total_size_gb': 0,
'season_folders': 0,
'non_season_folders': [],
'episode_count': 0,
'size_per_episode': 0,
'issues': f'Permission/Access Error: {str(e)}'
}
season_folders = []
non_season_folders = []
total_episodes = 0
for subdir in subdirs:
subdir_path = os.path.join(show_path, subdir)
if is_season_folder(subdir):
season_folders.append(subdir)
total_episodes += count_episodes_in_folder(subdir_path)
else:
non_season_folders.append(subdir)
# Calculate size
total_size_gb = get_folder_size_gb(show_path)
# Calculate size per episode
size_per_episode = total_size_gb / total_episodes if total_episodes > 0 else 0
# Identify issues
issues = []
if not season_folders:
issues.append("NO_SEASON_FOLDERS")
non_flagged_issues = [f for f in non_season_folders if not is_featurette_folder(f)]
if non_flagged_issues:
issues.append(f"UNEXPECTED_FOLDERS: {', '.join(non_flagged_issues)}")
featurette_folders = [f for f in non_season_folders if is_featurette_folder(f)]
return {
'show_name': show_name,
'total_size_gb': round(total_size_gb, 2),
'season_folders': len(season_folders),
'season_folder_names': ', '.join(sorted(season_folders)) if season_folders else 'NONE',
'non_season_folders': ', '.join(non_season_folders) if non_season_folders else 'None',
'featurette_folders': ', '.join(featurette_folders) if featurette_folders else 'None',
'episode_count': total_episodes,
'size_per_episode': round(size_per_episode, 3),
'issues': '; '.join(issues) if issues else 'OK'
}
def main():
if len(sys.argv) > 1:
directory = sys.argv[1]
else:
directory = input("Enter the path to your TV shows directory: ").strip()
directory = os.path.expanduser(directory)
if not os.path.isdir(directory):
print(f"Error: Directory not found: {directory}")
sys.exit(1)
print(f"Scanning directory: {directory}")
print("This may take a while for large collections...\n")
# Get all show directories (first level subdirectories)
try:
show_folders = [d for d in os.listdir(directory)
if os.path.isdir(os.path.join(directory, d))]
except (PermissionError, OSError) as e:
print(f"Error accessing directory: {e}")
sys.exit(1)
if not show_folders:
print("No directories found in the specified path.")
sys.exit(1)
results = []
for i, show_folder in enumerate(sorted(show_folders), 1):
show_path = os.path.join(directory, show_folder)
print(f"[{i}/{len(show_folders)}] Processing: {show_folder}")
analysis = analyze_show_directory(show_path)
results.append(analysis)
# Write to CSV
output_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'show_analysis.csv')
fieldnames = [
'show_name',
'total_size_gb',
'season_folders',
'season_folder_names',
'non_season_folders',
'featurette_folders',
'episode_count',
'size_per_episode',
'issues'
]
try:
with open(output_file, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(results)
print(f"\n✓ Analysis complete!")
print(f"✓ Results saved to: {output_file}")
print(f"✓ Analyzed {len(results)} shows")
except (PermissionError, IOError) as e:
print(f"Error writing CSV file: {e}")
sys.exit(1)
if __name__ == '__main__':
main()