Merge duplicate TV show folders into existing show on rename

This commit is contained in:
justin committed 2026-08-20 20:00:59 -07:00
1 parent ea7a92472b
commit aa08736e22
2 files changed
+169

No files matched your search

+167
View File
@@ -1343,6 +1343,149 @@ def rename_season_folder_files(folder, final_name, log):
return renamed, skipped
def infer_season_from_files(folder):
for item in sorted(folder.rglob("*")):
if not item.is_file() or item.suffix.lower().lstrip(".") not in VIDEO_EXTENSIONS:
continue
se = parse_season_episode(item.name)
if se:
return se[0]
season = parse_season_only(item.name)
if season is not None:
return season
return None
def merge_duplicate_show_folder(source_folder, target_folder, raw_name, final_name, log):
moved = 0
skipped = 0
for sub in list_subfolders(source_folder):
season = parse_season_folder_name(sub.name)
if season is None:
continue
organize_subtitle_folder(sub, log)
for item in list_video_files(sub):
ext = item.suffix.lower().lstrip(".")
file_season, episode, marker = resolve_season_folder_file(item, season)
item_season = file_season if file_season is not None else season
item_season_folder = season_folder_name(item_season)
item_target_dir = target_folder / item_season_folder
subtitles = gather_video_subtitles(item, True)
new_name = item.name if episode is None else episode_file_name(
final_name, item_season, episode, ext, detect_resolution(item.name)
)
dest = item_target_dir / new_name
if dest.exists() and not same_existing_path(dest, item):
print(f"Skipping (target already exists): {item.name}")
skipped += 1
continue
item_target_dir.mkdir(exist_ok=True)
item.rename(dest)
log.record(item, dest)
print(f"Moved: {item.name} -> {target_folder.name}/{item_season_folder}/{new_name}")
moved += 1
moved += rename_consolidated_subtitles(subtitles, dest, log)
try:
if sub.exists() and not any(sub.iterdir()):
sub.rmdir()
except OSError:
pass
folder_season = parse_season_folder_name(raw_name)
if folder_season is None:
folder_season = infer_season_from_files(source_folder)
organize_subtitle_folder(source_folder, log)
for item in list_video_files(source_folder):
ext = item.suffix.lower().lstrip(".")
file_season, episode, marker = resolve_season_folder_file(item, folder_season)
item_season = file_season if file_season is not None else folder_season
subtitles = gather_video_subtitles(item, True)
if item_season is None or episode is None:
print(f"No season/episode found, skipping: {item.name}")
skipped += 1
continue
item_season_folder = season_folder_name(item_season)
item_target_dir = target_folder / item_season_folder
new_name = episode_file_name(final_name, item_season, episode, ext, detect_resolution(item.name))
dest = item_target_dir / new_name
if dest.exists() and not same_existing_path(dest, item):
print(f"Skipping (target already exists): {item.name}")
skipped += 1
continue
item_target_dir.mkdir(exist_ok=True)
item.rename(dest)
log.record(item, dest)
print(f"Moved: {item.name} -> {target_folder.name}/{item_season_folder}/{new_name}")
moved += 1
moved += rename_consolidated_subtitles(subtitles, dest, log)
try:
if source_folder.exists() and not any(source_folder.iterdir()):
source_folder.rmdir()
print(f"Removed empty folder: {source_folder}")
except OSError:
pass
return moved, skipped
def preview_merge_duplicate_show_folder(source_folder, target_folder, raw_name, final_name):
for sub in list_subfolders(source_folder):
season = parse_season_folder_name(sub.name)
if season is None:
continue
preview_subtitle_folder(sub)
for item in list_video_files(sub):
ext = item.suffix.lower().lstrip(".")
file_season, episode, marker = resolve_season_folder_file(item, season)
item_season = file_season if file_season is not None else season
item_season_folder = season_folder_name(item_season)
subtitles = gather_video_subtitles(item, True)
new_name = item.name if episode is None else episode_file_name(
final_name, item_season, episode, ext, detect_resolution(item.name)
)
dest = target_folder / item_season_folder / new_name
print(f"Move: {item.name} -> {target_folder.name}/{item_season_folder}/{new_name}")
preview_consolidated_subtitles(subtitles, dest)
folder_season = parse_season_folder_name(raw_name)
if folder_season is None:
folder_season = infer_season_from_files(source_folder)
preview_subtitle_folder(source_folder)
for item in list_video_files(source_folder):
ext = item.suffix.lower().lstrip(".")
file_season, episode, marker = resolve_season_folder_file(item, folder_season)
item_season = file_season if file_season is not None else folder_season
subtitles = gather_video_subtitles(item, True)
if item_season is None or episode is None:
print(f"No season/episode found, skipping: {item.name}")
continue
item_season_folder = season_folder_name(item_season)
new_name = episode_file_name(final_name, item_season, episode, ext, detect_resolution(item.name))
dest = target_folder / item_season_folder / new_name
print(f"Move: {item.name} -> {target_folder.name}/{item_season_folder}/{new_name}")
preview_consolidated_subtitles(subtitles, dest)
def preview_video_files(folder, media_type, final_name, api_key):
files = list_video_files(folder)
@@ -2066,6 +2209,7 @@ def run_scan(args, log):
folders_skipped = 0
files_renamed = 0
files_skipped = 0
simulated_tv_folder_names = set()
total_folders = len(subfolders)
@@ -2093,11 +2237,22 @@ def run_scan(args, log):
if test_mode:
new_folder = folder.parent / folder_name
needs_rename = new_folder != folder
target_already_taken = (
new_folder.is_dir() or folder_name in simulated_tv_folder_names
) and not same_existing_path(new_folder, folder)
if media_type == "tv" and needs_rename and target_already_taken:
print(f"Would merge into existing show folder: {raw_name} -> {folder_name}")
preview_merge_duplicate_show_folder(folder, new_folder, raw_name, final_name)
examples_shown += 1
continue
if needs_rename:
print(f"Renamed folder: {raw_name} -> {folder_name}")
else:
print(f"Skipping renaming (already correctly named): {raw_name}")
if media_type == "tv":
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)
@@ -2107,6 +2262,18 @@ def run_scan(args, log):
new_folder = folder.parent / folder_name
needs_rename = new_folder != folder
if needs_rename and media_type == "tv" and new_folder.is_dir() and not same_existing_path(new_folder, folder):
if not args.yes and not confirm(f"Merge '{raw_name}' into existing show folder '{folder_name}'?"):
print(f"Skipped: {raw_name}")
folders_skipped += 1
continue
merged, merge_skipped = merge_duplicate_show_folder(folder, new_folder, raw_name, final_name, log)
files_renamed += merged
files_skipped += merge_skipped
folders_renamed += 1
continue
if needs_rename:
if not args.yes and not confirm(f"Rename '{raw_name}' -> '{folder_name}'?"):
print(f"Skipped: {raw_name}")