Skip to content

Instantly share code, notes, and snippets.

@fffonion
Created December 25, 2025 22:54
Show Gist options
  • Select an option

  • Save fffonion/723ac0cfbde4cd69c992c33dce08e035 to your computer and use it in GitHub Desktop.

Select an option

Save fffonion/723ac0cfbde4cd69c992c33dce08e035 to your computer and use it in GitHub Desktop.
Jellyfin Season Verification
import requests
import sys
from datetime import datetime
# Configuration
JELLYFIN_URL = "http://jellyfin:8096" # Change to your Jellyfin server URL
API_KEY = "123456789" # Create one from Dashboard -> API keys
LOG_FILE = "jellyfin_invalid_seasons.log"
def log_message(message, log_file=LOG_FILE):
"""Write message to both console and log file."""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{timestamp}] {message}"
print(log_entry)
with open(log_file, "a", encoding="utf-8") as f:
f.write(log_entry + "\n")
def get_all_series(base_url, api_key):
"""Fetch all TV series from Jellyfin."""
endpoint = f"{base_url}/Items"
params = {
"api_key": api_key,
"IncludeItemTypes": "Series",
"Recursive": "true",
"Fields": "Path"
}
try:
response = requests.get(endpoint, params=params)
response.raise_for_status()
return response.json().get("Items", [])
except requests.exceptions.RequestException as e:
log_message(f"ERROR: Failed to fetch series: {e}")
sys.exit(1)
def get_seasons_for_series(base_url, api_key, series_id):
"""Get all seasons for a specific series."""
endpoint = f"{base_url}/Shows/{series_id}/Seasons"
params = {"api_key": api_key}
try:
response = requests.get(endpoint, params=params)
response.raise_for_status()
return response.json().get("Items", [])
except requests.exceptions.RequestException as e:
log_message(f"ERROR: Failed to fetch seasons for series {series_id}: {e}")
return []
def check_season_exists(base_url, api_key, series_id, season_id):
"""Check if a season ID actually exists in Jellyfin."""
endpoint = f"{base_url}/Shows/{series_id}/Episodes"
params = {"api_key": api_key, "seasonId": season_id, "limit": 1}
try:
response = requests.get(endpoint, params=params)
if response.status_code == 200:
return True, response.json()
elif response.status_code == 404:
return False, None
elif response.status_code == 500:
return False, None
else:
response.raise_for_status()
return True, response.json()
except requests.exceptions.RequestException as e:
log_message(f"ERROR: Failed to verify season {season_id}: {e}")
return None, None
def main():
# Initialize log file
with open(LOG_FILE, "w", encoding="utf-8") as f:
f.write(f"Jellyfin Season Validation Log - Started at {datetime.now()}\n")
f.write("=" * 80 + "\n\n")
log_message("Starting Jellyfin season validation...")
log_message(f"Server: {JELLYFIN_URL}\n")
# Fetch all series
all_series = get_all_series(JELLYFIN_URL, API_KEY)
if not all_series:
log_message("No TV series found on the server.")
return
log_message(f"Found {len(all_series)} TV series. Validating seasons...\n")
total_seasons = 0
invalid_seasons = 0
problematic_shows = []
# Process each series
for idx, series in enumerate(all_series, 1):
series_id = series.get("Id")
series_name = series.get("Name")
series_path = series.get("Path", "N/A")
print(f"[{idx}/{len(all_series)}] Checking: {series_name}")
seasons = get_seasons_for_series(JELLYFIN_URL, API_KEY, series_id)
if not seasons:
continue
show_has_issues = False
invalid_season_list = []
for season in seasons:
season_id = season.get("Id")
season_name = season.get("Name", "Unknown Season")
total_seasons += 1
exists, season_data = check_season_exists(JELLYFIN_URL, API_KEY, series_id, season_id)
if exists is False:
invalid_seasons += 1
show_has_issues = True
invalid_season_list.append({
"season_id": season_id,
"season_name": season_name
})
log_message(f" ✗ INVALID: Season ID {season_id} ({season_name}) does not exist!")
elif exists is None:
show_has_issues = True
invalid_season_list.append({
"season_id": season_id,
"season_name": season_name
})
log_message(f" ? ERROR: Could not verify season ID {season_id} ({season_name})")
if show_has_issues:
problematic_shows.append({
"show_id": series_id,
"show_name": series_name,
"show_path": series_path,
"invalid_seasons": invalid_season_list
})
# Summary
log_message("\n" + "=" * 80)
log_message("VALIDATION SUMMARY")
log_message("=" * 80)
log_message(f"Total TV Shows: {len(all_series)}")
log_message(f"Total Seasons Checked: {total_seasons}")
log_message(f"Invalid/Missing Seasons: {invalid_seasons}")
log_message(f"Shows with Issues: {len(problematic_shows)}")
if problematic_shows:
log_message("\n" + "=" * 80)
log_message("PROBLEMATIC SHOWS DETAILS")
log_message("=" * 80)
for show in problematic_shows:
log_message(f"\nShow: {show['show_name']}")
log_message(f" Show ID: {show['show_id']}")
log_message(f" Path: {show['show_path']}")
log_message(f" Invalid Seasons:")
for season in show['invalid_seasons']:
log_message(f" - {season['season_name']} (ID: {season['season_id']})")
else:
log_message("\n✓ All seasons validated successfully!")
log_message(f"\nLog saved to: {LOG_FILE}")
if __name__ == "__main__":
# Check if requests is installed
try:
import requests
except ImportError:
print("Error: 'requests' library is not installed.")
print("Install it using: pip install requests")
sys.exit(1)
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment