Bulk File Renamer

import os

import re

import shutil

from pathlib import Path

from datetime import datetime

 

# ============================================================

# UTILITY: List Files in Folder

# ============================================================

 

def list_files(folder, extension_filter=None):

    files = []

    for f in sorted(Path(folder).iterdir()):

        if f.is_file():

            if extension_filter:

                if f.suffix.lower() == extension_filter.lower():

                    files.append(f)

            else:

                files.append(f)

    return files

 

 

# ============================================================

# MODE 1: Add Prefix / Suffix

# ============================================================

 

def rename_prefix_suffix(files, prefix="", suffix=""):

    renamed = []

    for f in files:

        stem = f.stem        # filename without extension

        ext  = f.suffix      # .jpg, .txt etc.

        new_name = f"{prefix}{stem}{suffix}{ext}"

        new_path = f.parent / new_name

        renamed.append((f, new_path))

    return renamed

 

 

# ============================================================

# MODE 2: Sequential Numbering

# ============================================================

 

def rename_sequential(files, base_name="file", start=1, padding=3):

    renamed = []

    for i, f in enumerate(files, start=start):

        ext = f.suffix

        number = str(i).zfill(padding)

        new_name = f"{base_name}_{number}{ext}"

        new_path = f.parent / new_name

        renamed.append((f, new_path))

    return renamed

 

 

# ============================================================

# MODE 3: Find & Replace in Filename

# ============================================================

 

def rename_find_replace(files, find_text, replace_text):

    renamed = []

    for f in files:

        new_name = f.name.replace(find_text, replace_text)

        new_path = f.parent / new_name

        renamed.append((f, new_path))

    return renamed

 

 

# ============================================================

# MODE 4: Regex-Based Rename

# ============================================================

 

def rename_regex(files, pattern, replacement):

    renamed = []

    for f in files:

        try:

            new_name = re.sub(pattern, replacement, f.name)

            new_path = f.parent / new_name

            renamed.append((f, new_path))

        except re.error as e:

            print(f"  ⚠ Regex error for {f.name}: {e}")

    return renamed

 

 

# ============================================================

# MODE 5: Add Date Stamp

# ============================================================

 

def rename_add_date(files, position="prefix"):

    today = datetime.today().strftime("%Y%m%d")

    renamed = []

    for f in files:

        stem = f.stem

        ext  = f.suffix

        if position == "prefix":

            new_name = f"{today}_{stem}{ext}"

        else:

            new_name = f"{stem}_{today}{ext}"

        new_path = f.parent / new_name

        renamed.append((f, new_path))

    return renamed

 

 

# ============================================================

# PREVIEW & CONFIRM

# ============================================================

 

def preview_changes(renamed):

    print("\n" + "-"*60)

    print(f"{'ORIGINAL':<30} {'NEW NAME':<30}")

    print("-"*60)

    for old, new in renamed:

        print(f"{old.name:<30} {new.name:<30}")

    print("-"*60)

 

 

def apply_rename(renamed, dry_run=False):

    success = 0

    skipped = 0

    for old, new in renamed:

        if old == new:

            skipped += 1

            continue

        if new.exists():

            print(f"  ⚠ Skipped (already exists): {new.name}")

            skipped += 1

            continue

        if not dry_run:

            old.rename(new)

        success += 1

 

    if dry_run:

        print(f"\n Dry Run Complete: {success} files would be renamed, {skipped} skipped.")

    else:

        print(f"\n Done! {success} files renamed, {skipped} skipped.")

 

 

# ============================================================

# BACKUP (Optional)

# ============================================================

 

def backup_folder(folder):

    backup_path = str(folder) + "_backup_" + datetime.today().strftime("%Y%m%d_%H%M%S")

    shutil.copytree(folder, backup_path)

    print(f" Backup created: {backup_path}")

 

 

# ============================================================

# MAIN MENU

# ============================================================

 

def main():

    print("\n" + "="*55)

    print("          BULK FILE RENAMER")

    print("="*55)

 

    # --- Folder Path ---

    folder = input("\nEnter folder path: ").strip()

    if not os.path.isdir(folder):

        print(" Invalid folder path!")

        return

 

    # --- Optional Extension Filter ---

    ext_filter = input("Filter by extension? (e.g. .jpg) or press Enter for all: ").strip()

    if not ext_filter:

        ext_filter = None

 

    files = list_files(folder, ext_filter)

    if not files:

        print(" No files found!")

        return

 

    print(f"\n Found {len(files)} file(s) in '{folder}'")

 

    # --- Optional Backup ---

    backup = input("\nCreate backup before renaming? (y/n): ").strip().lower()

    if backup == "y":

        backup_folder(folder)

 

    # --- Choose Mode ---

    print("\nChoose Rename Mode:")

    print("  1. Add Prefix / Suffix")

    print("  2. Sequential Numbering")

    print("  3. Find & Replace")

    print("  4. Regex Pattern")

    print("  5. Add Date Stamp")

 

    choice = input("\nEnter choice (1-5): ").strip()

 

    renamed = []

 

    if choice == "1":

        prefix = input("Enter prefix (or leave blank): ").strip()

        suffix = input("Enter suffix (or leave blank): ").strip()

        renamed = rename_prefix_suffix(files, prefix, suffix)

 

    elif choice == "2":

        base = input("Enter base name (e.g. photo): ").strip()

        start = int(input("Start number (default 1): ").strip() or 1)

        padding = int(input("Number padding digits (default 3): ").strip() or 3)

        renamed = rename_sequential(files, base, start, padding)

 

    elif choice == "3":

        find = input("Find text: ").strip()

        replace = input("Replace with: ").strip()

        renamed = rename_find_replace(files, find, replace)

 

    elif choice == "4":

        print("  Example pattern: (\\d+)  → matches numbers")

        pattern = input("Regex pattern: ").strip()

        replacement = input("Replacement: ").strip()

        renamed = rename_regex(files, pattern, replacement)

 

    elif choice == "5":

        pos = input("Add date as prefix or suffix? (prefix/suffix): ").strip().lower()

        if pos not in ["prefix", "suffix"]:

            pos = "prefix"

        renamed = rename_add_date(files, pos)

 

    else:

        print(" Invalid choice.")

        return

 

    if not renamed:

        print(" No files to rename.")

        return

 

    # --- Preview ---

    preview_changes(renamed)

 

    # --- Dry Run or Apply ---

    mode = input("\nChoose action:\n  1. Apply rename\n  2. Dry run (preview only)\nChoice: ").strip()

 

    if mode == "1":

        apply_rename(renamed, dry_run=False)

    else:

        apply_rename(renamed, dry_run=True)

 

 

# ============================================================

# RUN

# ============================================================

 

if __name__ == "__main__":

    main()

No comments: