-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfolder_sorter.py
More file actions
159 lines (140 loc) · 4.68 KB
/
folder_sorter.py
File metadata and controls
159 lines (140 loc) · 4.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/usr/bin/env python3
"""
Folder Sorter: drop this script into any folder and run it.
- Shows counts by file type (Audio, Images, Videos, Archives, Executables, Other)
- Press Enter to proceed with sorting, or Esc to cancel.
- Creates subfolders and moves files. Name conflicts are resolved safely.
"""
from pathlib import Path
import shutil
import sys
import os
# ---------- Configuration ----------
CATEGORY_FOLDERS = {
"Audio": {"mp3", "wav", "flac", "aac", "ogg", "m4a", "wma", "aiff", "alac", "opus"},
"Images": {"jpg", "jpeg", "png", "gif", "bmp", "tiff", "tif", "webp", "heic", "svg"},
"Videos": {"mp4", "mkv", "avi", "mov", "wmv", "flv", "webm", "m4v", "mpeg", "mpg"},
"Executables": {"exe", "msi"},
}
ARCHIVE_SUFFIX_PATTERNS = [
".tar.gz", ".tar.bz2", ".tar.xz", ".tar.zst",
]
ARCHIVE_SINGLE_EXTS = {"zip", "rar", "7z", "gz", "bz2", "xz", "zst", "cab", "iso"}
DEST_NAMES = ["Audio", "Images", "Videos", "Archives", "Executables", "Other"]
# ---------- Keypress helpers ----------
def wait_for_enter_or_esc() -> str:
try:
import msvcrt
print("Press Enter to sort, or Esc to cancel...")
while True:
ch = msvcrt.getch()
if ch in (b"\r", b"\n"):
return "enter"
if ch == b"\x1b":
return "esc"
except ImportError:
import termios, tty
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
tty.setraw(fd)
print("Press Enter to sort, or Esc to cancel...")
while True:
ch = sys.stdin.read(1)
if ch == "\r" or ch == "\n":
return "enter"
if ch == "\x1b":
return "esc"
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
# ---------- Categorization ----------
def categorize(file: Path) -> str:
name_lower = file.name.lower()
for pat in ARCHIVE_SUFFIX_PATTERNS:
if name_lower.endswith(pat):
return "Archives"
ext = file.suffix.lower().lstrip(".")
if ext in ARCHIVE_SINGLE_EXTS:
return "Archives"
if ext in CATEGORY_FOLDERS["Executables"]:
return "Executables"
for cat, exts in CATEGORY_FOLDERS.items():
if cat == "Executables":
continue
if ext in exts:
return cat
return "Other"
# ---------- File moving with safe rename ----------
def safe_move(src: Path, dst_dir: Path) -> Path:
dst_dir.mkdir(parents=True, exist_ok=True)
target = dst_dir / src.name
if not target.exists():
return src.rename(target)
stem = target.stem
suffix = target.suffix
n = 1
while True:
candidate = dst_dir / f"{stem} ({n}){suffix}"
if not candidate.exists():
return src.rename(candidate)
n += 1
# ---------- Main ----------
def main():
# --- Determine working folder ---
if getattr(sys, 'frozen', False):
# Running as a bundled EXE (PyInstaller)
script_path = Path(sys.executable).resolve()
else:
# Running from .py
script_path = Path(__file__).resolve()
root = script_path.parent
dest_dirs = {name: root / name for name in DEST_NAMES}
files = [p for p in root.iterdir() if p.is_file() and p != script_path]
counts = {name: 0 for name in DEST_NAMES}
candidates = []
for f in files:
if f.parent.name in DEST_NAMES:
continue
cat = categorize(f)
counts[cat] += 1
candidates.append((f, cat))
total = sum(counts.values())
print("\nFound the following files in:", root)
for name in DEST_NAMES:
print(f" {name:12} : {counts[name]}")
print(f" {'Total':12} : {total}\n")
if total == 0:
print("Nothing to do. No files to sort.")
if os.name == "nt":
os.system("pause")
return
choice = wait_for_enter_or_esc()
if choice == "esc":
print("Canceled. No changes made.")
if os.name == "nt":
os.system("pause")
return
moved = 0
errors = 0
for f, cat in candidates:
try:
target_dir = dest_dirs[cat]
if f.parent == target_dir:
continue
safe_move(f, target_dir)
moved += 1
except Exception as e:
print(f" ! Error moving '{f.name}': {e}")
errors += 1
print(f"\nDone. Moved {moved} file(s).")
if errors:
print(f"Encountered {errors} error(s). See messages above.")
if os.name == "nt":
os.system("pause")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nCanceled by user.")
if os.name == "nt":
os.system("pause")