Skip to content

Instantly share code, notes, and snippets.

@shekohex
Created April 11, 2025 18:16
Show Gist options
  • Save shekohex/f3f0e2dea3f672aeb978e7c01f392cad to your computer and use it in GitHub Desktop.
Save shekohex/f3f0e2dea3f672aeb978e7c01f392cad to your computer and use it in GitHub Desktop.
Lionzhd Series fix paths
#!/usr/bin/env python3
import json
import os
import sys
import argparse
import re
from typing import Dict, List, Optional, Any
def sanitize_filename(filename: str) -> str:
"""Removes or replaces characters that are invalid in filenames."""
# Remove characters that are strictly forbidden in most file systems
sanitized: str = re.sub(r'[\\/*?:"<>|]', '', filename)
# Replace slashes just in case (though removed above)
sanitized = sanitized.replace('/', '_').replace('\\', '_')
# Replace multiple spaces with a single underscore and strip leading/trailing underscores
sanitized = re.sub(r'\s+', '_', sanitized).strip('_')
# Optional: limit filename length if needed (e.g., 255 chars)
# max_len = 250 # Leave space for extension
# if len(sanitized) > max_len:
# sanitized = sanitized[:max_len]
return sanitized
def load_json_data(file_path: str) -> Dict[str, Any]:
"""Loads and parses the JSON data file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
data: Dict[str, Any] = json.load(f)
print(f"Successfully loaded JSON data from {file_path}")
return data
except FileNotFoundError:
print(f"Error: JSON file not found at {file_path}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError:
print(f"Error: Could not decode JSON from {file_path}. Check file format.", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"An unexpected error occurred while reading the JSON file: {e}", file=sys.stderr)
sys.exit(1)
def find_episode_title(data: Dict[str, Any], episode_id: str) -> Optional[str]:
"""Finds the episode title corresponding to the given episode ID."""
episodes_data: Optional[Dict[str, List[Dict[str, Any]]]] = data.get('episodes')
if not episodes_data:
print("Warning: 'episodes' key not found or is not a dictionary in JSON data.", file=sys.stderr)
return None
# Iterate through each season in the 'episodes' object
for season_number, episodes_list in episodes_data.items():
if not isinstance(episodes_list, list):
print(f"Warning: Episodes for season '{season_number}' is not a list. Skipping.", file=sys.stderr)
continue
# Iterate through each episode in the season's list
for episode in episodes_list:
if not isinstance(episode, dict):
print(f"Warning: Found non-dictionary item in episode list for season '{season_number}'. Skipping item.", file=sys.stderr)
continue
# Check if the episode 'id' matches the target ID
if episode.get('id') == episode_id:
title: Optional[str] = episode.get('title')
if isinstance(title, str):
return title # Return the title if found and is a string
else:
print(f"Warning: Title for episode ID '{episode_id}' is not a string. Skipping.", file=sys.stderr)
return None # Treat non-string title as not found
# Return None if the episode ID was not found in any season
return None
def process_directory(directory_path: str, data: Dict[str, Any]) -> None:
"""Processes the directory, renaming MKV files based on JSON data."""
print(f"\nProcessing directory: {directory_path}")
if not os.path.isdir(directory_path):
print(f"Error: Directory not found at {directory_path}", file=sys.stderr)
sys.exit(1)
# List all files in the specified directory
try:
files: List[str] = os.listdir(directory_path)
except OSError as e:
print(f"Error accessing directory {directory_path}: {e}", file=sys.stderr)
sys.exit(1)
rename_operations: List[Dict[str, str]] = []
# Iterate through each file in the directory
for filename in files:
# Check if the file is an MKV file
if filename.lower().endswith('.mkv'):
# Extract the episode ID (filename without extension)
episode_id: str
_: str
episode_id, _ = os.path.splitext(filename)
original_path: str = os.path.join(directory_path, filename)
# Find the corresponding episode title using the ID
title: Optional[str] = find_episode_title(data, episode_id)
if title:
# Sanitize the title to create a valid filename
sanitized_title: str = sanitize_filename(title)
if not sanitized_title:
print(f"Warning: Could not generate a valid filename from title '{title}' for ID {episode_id}. Skipping.", file=sys.stderr)
continue
# Construct the new filename
new_filename: str = f"{sanitized_title}.mkv"
new_path: str = os.path.join(directory_path, new_filename)
# Avoid renaming if the name is already correct
if original_path == new_path:
print(f"Skipping '{filename}': Already correctly named.")
continue
# Store the rename operation details
rename_operations.append({
'original_path': original_path,
'new_path': new_path,
'original_filename': filename,
'new_filename': new_filename
})
else:
# Print a warning if the episode ID was not found in the JSON data
print(f"Warning: No title found for episode ID '{episode_id}' (file: {filename}). Skipping.", file=sys.stderr)
# Preview and confirm rename operations
if not rename_operations:
print("\nNo files need renaming.")
return
print("\nPreview of proposed changes:")
for i, op in enumerate(rename_operations):
print(f"{i+1}. Rename '{op['original_filename']}' to '{op['new_filename']}'")
# Ask for confirmation
confirm: str = input("\nProceed with renaming? (y/n): ").strip().lower()
if confirm == 'y':
print("\nRenaming files...")
success_count: int = 0
fail_count: int = 0
for op in rename_operations:
try:
# Perform the rename operation
os.rename(op['original_path'], op['new_path'])
print(f"Renamed '{op['original_filename']}' to '{op['new_filename']}'")
success_count += 1
except OSError as e:
# Print an error message if renaming fails
print(f"Error renaming '{op['original_filename']}' to '{op['new_filename']}': {e}", file=sys.stderr)
fail_count += 1
print(f"\nRenaming complete. {success_count} files renamed, {fail_count} failed.")
else:
print("\nRenaming cancelled by user.")
def main() -> None:
"""Main function to parse arguments and orchestrate the process."""
# Set up argument parser
parser = argparse.ArgumentParser(description="Rename MKV files in a directory based on episode titles found in a JSON file.")
parser.add_argument("directory", help="Path to the directory containing the MKV season files.")
parser.add_argument("json_file", help="Path to the JSON data file containing episode information.")
# Parse command-line arguments
args = parser.parse_args()
# Load the JSON data using the provided path
series_data: Dict[str, Any] = load_json_data(args.json_file)
# Process the directory
process_directory(args.directory, series_data)
# Entry point of the script
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment