138 lines · 3.3 KB
Raw Download
1
#!/usr/bin/env python3
2
"""
3
Recursively finds files with double extensions in a folder and removes the first
4
(false) extension, keeping only the true/last extension.
5
6
Example:
7
  "The Office S08E24.mov.mp4" -> "The Office S08E24.mp4"
8
9
# Preview changes without renaming anything (recommended first step)
10
python fix_double_extensions.py /path/to/folder --dry-run
11
12
# Actually rename the files
13
python fix_double_extensions.py /path/to/folder
14
"""
15
16
import os
17
import argparse
18
19
20
# Common media/file extensions to recognize as "real" extensions
21
KNOWN_EXTENSIONS = {
22
    ".mp4",
23
    ".mkv",
24
    ".avi",
25
    ".mov",
26
    ".wmv",
27
    ".flv",
28
    ".webm",
29
    ".m4v",
30
    ".mp3",
31
    ".aac",
32
    ".flac",
33
    ".wav",
34
    ".ogg",
35
    ".m4a",
36
    ".wma",
37
    ".jpg",
38
    ".jpeg",
39
    ".png",
40
    ".gif",
41
    ".bmp",
42
    ".tiff",
43
    ".webp",
44
    ".heic",
45
    ".pdf",
46
    ".doc",
47
    ".docx",
48
    ".xls",
49
    ".xlsx",
50
    ".ppt",
51
    ".pptx",
52
    ".zip",
53
    ".tar",
54
    ".gz",
55
    ".rar",
56
    ".7z",
57
    ".py",
58
    ".js",
59
    ".ts",
60
    ".html",
61
    ".css",
62
    ".json",
63
    ".xml",
64
    ".csv",
65
    ".txt",
66
    ".srt",
67
    ".sub",
68
    ".ass",
69
    ".vtt",
70
}
71
72
73
def find_double_extension_files(root_dir):
74
    """Yield (filepath, new_name) tuples for files with double extensions."""
75
    for dirpath, _, filenames in os.walk(root_dir):
76
        for filename in filenames:
77
            name, ext1 = os.path.splitext(filename)
78
            if not ext1:
79
                continue
80
            _, ext2 = os.path.splitext(name)
81
            if ext2 and ext2.lower() in KNOWN_EXTENSIONS:
82
                # Double extension detected: ext2 is the false one, ext1 is the real one
83
                new_name = name[: -len(ext2)] + ext1
84
                filepath = os.path.join(dirpath, filename)
85
                yield filepath, new_name
86
87
88
def main():
89
    parser = argparse.ArgumentParser(
90
        description="Remove false double extensions from filenames recursively."
91
    )
92
    parser.add_argument("folder", help="Root folder to search")
93
    parser.add_argument(
94
        "--dry-run",
95
        action="store_true",
96
        help="Show what would be renamed without actually renaming",
97
    )
98
    args = parser.parse_args()
99
100
    if not os.path.isdir(args.folder):
101
        print(f"Error: '{args.folder}' is not a valid directory.")
102
        return
103
104
    matches = list(find_double_extension_files(args.folder))
105
106
    if not matches:
107
        print("No files with double extensions found.")
108
        return
109
110
    print(
111
        f"{'DRY RUN — ' if args.dry_run else ''}Found {len(matches)} file(s) to rename:\n"
112
    )
113
114
    for filepath, new_name in matches:
115
        dirpath = os.path.dirname(filepath)
116
        new_path = os.path.join(dirpath, new_name)
117
        old_name = os.path.basename(filepath)
118
119
        print(f"  {old_name}")
120
        print(f"  -> {new_name}\n")
121
122
        if not args.dry_run:
123
            if os.path.exists(new_path):
124
                print(f"  [SKIPPED] Target already exists: {new_path}\n")
125
            else:
126
                os.rename(filepath, new_path)
127
128
    if args.dry_run:
129
        print(
130
            "Dry run complete. No files were renamed. Remove --dry-run to apply changes."
131
        )
132
    else:
133
        print("Done.")
134
135
136
if __name__ == "__main__":
137
    main()
138