Created
April 11, 2025 18:16
-
-
Save shekohex/f3f0e2dea3f672aeb978e7c01f392cad to your computer and use it in GitHub Desktop.
Lionzhd Series fix paths
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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