181 lines
4.9 KiB
Python
181 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
import argparse
|
|
import re
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from serverLibary import safe_rename,safe_delete
|
|
|
|
VIDEO_EXTENSIONS = {".mpg", ".avi", ".mkv", ".mp4", ".m4v", ".srt"}
|
|
|
|
UNWANTED_PATTERNS_POST = [
|
|
r"TV\ Series", r"[a-z]264", r"10bit", r"\[.*?\]",
|
|
r"320kbps", r"AC3", r"NF\.WEB", r"bdrip", r"RERIP",
|
|
r"DUAL-", r"LostFilm", r"readnfo", r"BluRay",
|
|
r"720p", r"1080p", r"WEB", r"AMZN", r"HDTV",
|
|
r"DVD", r"BDRip", r"HDRip", r"REPACK", r"XviD", r"ETHEL", f"DL ", f"HEVC"
|
|
]
|
|
|
|
UNWANTED_PATTERNS_PRE = [
|
|
r"www.UIndex.org", r"\[TorrentCounter\.to\]"
|
|
]
|
|
|
|
GARBAGE_PATTERNS = [
|
|
r"www\.YTS.*", r".*\.nfo", r"RARBG.*", r".*YIFY.*\.jpg", f"Torrent Downloaded From.*txt", "Screens"
|
|
]
|
|
|
|
DAMAGE_PATTERNS = [
|
|
(r"\.\.", "."),
|
|
(r"__", "_"),
|
|
(r"\s+", " "),
|
|
]
|
|
|
|
|
|
def iter_media_files(base: Path):
|
|
if base.is_file():
|
|
yield base
|
|
for path in base.rglob("*"):
|
|
if path.is_file() and path.suffix.lower() in VIDEO_EXTENSIONS:
|
|
yield path
|
|
|
|
def clean_name(name: Path, search=None, replace=None):
|
|
if name.is_file():
|
|
base, ext = os.path.splitext(name.name)
|
|
else:
|
|
base = name.name
|
|
ext = ""
|
|
#base = base.replace(" ", ".")
|
|
base = re.sub(r"s(\d{2})e", r"S\1E", base, flags=re.IGNORECASE)
|
|
|
|
for pattern in UNWANTED_PATTERNS_PRE:
|
|
base = re.sub(rf"{pattern}[^a-zA-Z0-9]*", "", base, flags=re.IGNORECASE)
|
|
|
|
|
|
base_new = base
|
|
for pattern in UNWANTED_PATTERNS_POST:
|
|
base_new = re.sub(rf"{pattern}.*$", "", base_new, flags=re.IGNORECASE)
|
|
|
|
if len(base_new) > 5:
|
|
base = base_new
|
|
|
|
if search and replace:
|
|
base = re.sub(search, replace, base)
|
|
|
|
# Remove all non-word characters at the end of the string
|
|
base = re.sub(r'[^A-Za-z0-9)]+$', '', base)
|
|
base = re.sub(r"\.+", ".", base)
|
|
base = re.sub(r" +", " ", base)
|
|
base = base.strip(".")
|
|
return base + ext
|
|
|
|
|
|
def capitalize_name(name: str):
|
|
return re.sub(
|
|
r"(^|[ ._\-])([a-z])",
|
|
lambda m: m.group(1) + m.group(2).upper(),
|
|
name.lower()
|
|
)
|
|
|
|
|
|
# -----------------------------
|
|
# Operations
|
|
# -----------------------------
|
|
|
|
def rename_files(base: Path, args):
|
|
for file in iter_media_files(base):
|
|
new_name = clean_name(file, args.search, args.replace)
|
|
|
|
if args.capitalize:
|
|
new_name = capitalize_name(new_name)
|
|
|
|
new_path = file.with_name(new_name)
|
|
safe_rename(file, new_path, args.dryrun, args.confirm)
|
|
|
|
|
|
def rename_directories(base: Path, args):
|
|
# reverse order → avoid renaming parent first
|
|
for path in sorted(base.rglob("*"), reverse=True):
|
|
if not path.is_dir():
|
|
continue
|
|
|
|
new_name = clean_name(path, args.search, args.replace)
|
|
|
|
if args.capitalize:
|
|
new_name = capitalize_name(new_name)
|
|
|
|
new_path = path.with_name(new_name)
|
|
safe_rename(path, new_path, args.dryrun, args.confirm)
|
|
|
|
|
|
def repair_names(base: Path, args):
|
|
for file in iter_media_files(base):
|
|
new_name = file.name
|
|
|
|
for pattern, repl in DAMAGE_PATTERNS:
|
|
new_name = re.sub(pattern, repl, new_name)
|
|
|
|
new_path = file.with_name(new_name)
|
|
safe_rename(file, new_path, args.dryrun, args.confirm)
|
|
|
|
|
|
def delete_garbage(base: Path, args):
|
|
for path in base.rglob("*"):
|
|
for pattern in GARBAGE_PATTERNS:
|
|
if re.match(pattern, path.name, re.IGNORECASE):
|
|
print(f"Deleting: {path}")
|
|
if not args.dryrun:
|
|
safe_delete(path)
|
|
|
|
|
|
# -----------------------------
|
|
# CLI
|
|
# -----------------------------
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(description="Media renamer tool")
|
|
|
|
parser.add_argument("-p","--path", default=".", help="Target directory")
|
|
parser.add_argument("-rep","--repair", action="store_true", help="Fix filenames")
|
|
parser.add_argument("-d","--delete", dest="delete", action="store_true", help="Delete junk files")
|
|
parser.add_argument("-cap","--capitalize", dest="capitalize", action="store_true", help="Capitalize names")
|
|
|
|
parser.add_argument("-r","--replace", nargs=2, metavar=("SEARCH", "REPLACE"))
|
|
|
|
parser.add_argument("-dr","--dryrun", action="store_true", help="Dry run")
|
|
parser.add_argument("-c","--confirm", action="store_true", help="Confirm each rename")
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
|
|
if args.replace:
|
|
args.search = args.replace
|
|
else:
|
|
args.search = args.replace = None
|
|
|
|
base = Path(args.path).resolve()
|
|
|
|
if not base.exists():
|
|
print("Invalid path")
|
|
return
|
|
|
|
# 1. fix broken names first (optional but recommended)
|
|
if args.repair:
|
|
repair_names(base, args)
|
|
|
|
# 2. rename directories FIRST (important for path stability)
|
|
rename_directories(base, args)
|
|
|
|
# 3. rename files AFTER
|
|
rename_files(base, args)
|
|
|
|
# 4. cleanup
|
|
if args.delete:
|
|
delete_garbage(base, args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|