diff --git a/plex-library-tool.py b/plex-library-tool.py index 564e54c..97abfb2 100755 --- a/plex-library-tool.py +++ b/plex-library-tool.py @@ -155,6 +155,24 @@ def parse_episode_only(filename): return None +TRAILING_TAG_PATTERN = re.compile(r'[\[\(][^\[\]\(\)]*[\]\)]\s*$') +TRAILING_NUMBER_PATTERN = re.compile(r'(? {target_season_folder}/{new_name}") + preview_consolidated_subtitles(subtitles, folder / target_season_folder / new_name) continue target_season_folder = season_folder_name(season) @@ -1574,7 +1700,7 @@ def preview_video_files(folder, media_type, final_name, api_key): preview_consolidated_subtitles(subtitles, folder / target_season_folder / item.name) -def rename_video_files(folder, media_type, final_name, log, api_key): +def rename_video_files(folder, media_type, final_name, log, api_key, tmdb_id=None): renamed = 0 skipped = 0 @@ -1621,10 +1747,15 @@ def rename_video_files(folder, media_type, final_name, log, api_key): else: season = parse_season_only(item.name) if season is None: - print(f"No season/episode found, skipping: {item.name}") - skipped += 1 - continue - new_name = item.name + resolved = resolve_absolute_episode(api_key, tmdb_id, item.name) + if resolved is None: + print(f"No season/episode found, skipping: {item.name}") + skipped += 1 + continue + season, episode = resolved + new_name = episode_file_name(final_name, season, episode, ext, detect_resolution(item.name)) + else: + new_name = item.name target_season_folder = season_folder_name(season) season_dir = folder / target_season_folder @@ -2265,7 +2396,7 @@ def run_scan(args, log): if extract_year(raw_name) is None: hint_year = infer_year_from_files(folder) vprint(f" No year in folder name, inferred from files: {hint_year}") - final_name, match_year, _, error = lookup_folder(api_key, media_type, raw_name, hint_year) + final_name, match_year, match_id, error = lookup_folder(api_key, media_type, raw_name, hint_year) if error: print(error) @@ -2296,7 +2427,7 @@ def run_scan(args, log): simulated_tv_folder_names.add(folder_name) preview_season_folders(folder) preview_season_folder_files(folder, final_name) - preview_video_files(folder, media_type, final_name, api_key) + preview_video_files(folder, media_type, final_name, api_key, match_id) examples_shown += 1 continue @@ -2340,7 +2471,7 @@ def run_scan(args, log): files_renamed += sub_renamed files_skipped += sub_skipped - renamed, skipped = rename_video_files(folder, media_type, final_name, log, api_key) + renamed, skipped = rename_video_files(folder, media_type, final_name, log, api_key, match_id) files_renamed += renamed files_skipped += skipped @@ -2438,6 +2569,20 @@ def check_show_episodes(api_key, show_folder): return missing, None +SPECIALS_FOLDER_PATTERN = re.compile(r'^specials?$', re.IGNORECASE) + + +def looks_like_single_show_folder(folder): + subfolders = list_subfolders(folder) + if not subfolders: + return False + season_like = sum( + 1 for s in subfolders + if parse_season_folder_name(s.name) is not None or SPECIALS_FOLDER_PATTERN.match(s.name) + ) + return season_like > len(subfolders) / 2 + + def run_episode_check(args): path_arg = args.episodes if isinstance(args.episodes, str) else None share = resolve_share(path_arg) @@ -2453,9 +2598,15 @@ def run_episode_check(args): api_key = get_api_key() get_tmdb_language() - shows = sorted( - p for p in Path(share).iterdir() if p.is_dir() and not p.name.startswith(".") - ) + share_path = Path(share) + + if looks_like_single_show_folder(share_path): + vprint(f" {share_path.name} looks like a single show's folder (season subfolders found), checking it directly") + shows = [share_path] + else: + shows = sorted( + p for p in share_path.iterdir() if p.is_dir() and not p.name.startswith(".") + ) checked = 0 incomplete = 0