Skip to content

Commit f4cf592

Browse files
Create pypixpro–code
1 parent b5498fe commit f4cf592

File tree

1 file changed

+168
-0
lines changed

1 file changed

+168
-0
lines changed

pypixpro–code

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import sys
5+
import shutil
6+
import subprocess
7+
from pathlib import Path
8+
import hashlib
9+
from PIL import Image, ExifTags, UnidentifiedImageError
10+
11+
try:
12+
import pyheif # For HEIC files
13+
except ImportError as e:
14+
subprocess.run(['osascript', '-e', f'display alert "Error: pyheif not found. Install it using pip."'])
15+
sys.exit(1)
16+
17+
# Constants
18+
STANDARD_IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".tif", ".webp", ".psd",
19+
".svg", ".ico", ".jfif", ".pjpeg", ".pjp", ".avif", ".apng"}
20+
PNG_EXTENSION = ".png"
21+
HEIC_EXTENSIONS = {".heic", ".heif"}
22+
DNG_RAW_EXTENSIONS = {".dng", ".raw", ".nef", ".cr2", ".cr3", ".arw", ".orf", ".rw2", ".raf", ".srw", ".kdc"}
23+
MISC_EXTENSIONS = {".doc", ".docx", ".txt", ".py", ".zsh", ".sh", ".mov", ".mp4", ".pdf", ".xlsx", ".pptx"}
24+
25+
# Folder Names
26+
PORTRAIT_FOLDER_NAME = "Portrait"
27+
LANDSCAPE_FOLDER_NAME = "Landscape"
28+
SCREENSHOTS_FOLDER_NAME = "Screenshots"
29+
MISC_FOLDER_NAME = "Misc"
30+
31+
def get_input_folder():
32+
"""Get the folder path from command-line arguments or show a prompt."""
33+
if len(sys.argv) > 1:
34+
input_folder_path = sys.argv[1]
35+
return Path(input_folder_path)
36+
else:
37+
prompt_message = (
38+
"⚠️ Please back up your files before processing them.\n\n"
39+
"Drag and drop a folder onto the droplet to begin."
40+
)
41+
subprocess.run(['osascript', '-e', f'display alert "{prompt_message}"'])
42+
sys.exit(1)
43+
44+
def get_prefix(folder_name):
45+
"""Prompt for a prefix for Portrait and Landscape folders."""
46+
try:
47+
result = subprocess.run(
48+
['osascript', '-e', f'display dialog "Enter prefix for {folder_name} photos:" default answer ""'],
49+
capture_output=True, text=True, check=True
50+
)
51+
output = result.stdout.strip().split("text returned:")[-1].strip()
52+
return output or f"{folder_name} Photo"
53+
except subprocess.CalledProcessError:
54+
return f"{folder_name} Photo"
55+
56+
def generate_checksums(root_folder):
57+
"""Generate file checksums using hashlib."""
58+
checksums = {}
59+
for path in root_folder.rglob('*'):
60+
if path.is_file():
61+
try:
62+
hasher = hashlib.sha256()
63+
with path.open('rb') as f:
64+
while chunk := f.read(8192):
65+
hasher.update(chunk)
66+
checksum = hasher.hexdigest()
67+
checksums.setdefault(checksum, []).append(path)
68+
except Exception:
69+
pass # Continue on error
70+
return checksums
71+
72+
def correct_orientation(image):
73+
"""Correct image orientation using EXIF metadata."""
74+
try:
75+
exif = image._getexif()
76+
if exif is not None:
77+
orientation = exif.get(274) # EXIF tag 274 is Orientation
78+
if orientation == 3:
79+
image = image.rotate(180, expand=True)
80+
elif orientation == 6:
81+
image = image.rotate(270, expand=True)
82+
elif orientation == 8:
83+
image = image.rotate(90, expand=True)
84+
return image
85+
except Exception as e:
86+
print(f"Error correcting orientation: {e}")
87+
return image # Return original image on error
88+
89+
def rename_files(target_folder, prefix):
90+
"""Rename files with the given prefix."""
91+
files = sorted(target_folder.glob('*'))
92+
for idx, file in enumerate(files, 1):
93+
new_name = f"{prefix} {str(idx).zfill(3)}{file.suffix}"
94+
try:
95+
file.rename(target_folder / new_name)
96+
except Exception:
97+
pass # Continue on error
98+
99+
def process_heic_image(path):
100+
"""Process HEIC image to determine its dimensions."""
101+
try:
102+
heif_file = pyheif.read(path)
103+
width, height = heif_file.size
104+
print(f"Processed HEIC: {path.name} - {width}x{height}") # Debugging dimensions
105+
return width, height
106+
except Exception as e:
107+
print(f"Error processing HEIC {path}: {e}")
108+
return None, None
109+
110+
def sort_images(root_folder, portrait_prefix, landscape_prefix):
111+
"""Sort images into folders and apply prefixes."""
112+
portrait_folder = root_folder / PORTRAIT_FOLDER_NAME
113+
landscape_folder = root_folder / LANDSCAPE_FOLDER_NAME
114+
115+
portrait_folder.mkdir(exist_ok=True)
116+
landscape_folder.mkdir(exist_ok=True)
117+
118+
screenshots_folder = None
119+
misc_folder = None
120+
121+
for path in root_folder.rglob('*'):
122+
if path.is_file() and path.parent not in {portrait_folder, landscape_folder}:
123+
suffix = path.suffix.lower()
124+
try:
125+
if suffix in HEIC_EXTENSIONS:
126+
width, height = process_heic_image(path)
127+
if width and height:
128+
target = portrait_folder if height > width else landscape_folder
129+
shutil.move(str(path), target / path.name)
130+
elif suffix in STANDARD_IMAGE_EXTENSIONS:
131+
with Image.open(path) as img:
132+
img = correct_orientation(img) # Correct orientation for JPEGs
133+
width, height = img.size
134+
print(f"Processed JPEG: {path.name} - {width}x{height}") # Debugging dimensions
135+
target = portrait_folder if height > width else landscape_folder
136+
shutil.move(str(path), target / path.name)
137+
elif suffix == PNG_EXTENSION:
138+
if not screenshots_folder:
139+
screenshots_folder = root_folder / SCREENSHOTS_FOLDER_NAME
140+
screenshots_folder.mkdir(exist_ok=True)
141+
shutil.move(str(path), screenshots_folder / path.name)
142+
elif suffix in DNG_RAW_EXTENSIONS:
143+
shutil.move(str(path), landscape_folder / path.name)
144+
elif suffix in MISC_EXTENSIONS:
145+
if not misc_folder:
146+
misc_folder = root_folder / MISC_FOLDER_NAME
147+
misc_folder.mkdir(exist_ok=True)
148+
shutil.move(str(path), misc_folder / path.name)
149+
except Exception as e:
150+
print(f"Error processing {path}: {e}")
151+
152+
rename_files(portrait_folder, portrait_prefix)
153+
rename_files(landscape_folder, landscape_prefix)
154+
155+
def main():
156+
"""Main entry point."""
157+
input_folder = get_input_folder()
158+
159+
checksums = generate_checksums(input_folder)
160+
portrait_prefix = get_prefix("Portrait")
161+
landscape_prefix = get_prefix("Landscape")
162+
163+
sort_images(input_folder, portrait_prefix, landscape_prefix)
164+
165+
subprocess.run(['osascript', '-e', 'display alert "Process completed successfully!"'])
166+
167+
if __name__ == "__main__":
168+
main()

0 commit comments

Comments
 (0)