6
6
import mimetypes
7
7
import subprocess
8
8
from pytz import timezone
9
+ import sys
10
+ from pillow_heif import register_heif_opener
11
+ import pyexiv2
9
12
10
13
# Define Eastern Time (ET)
11
14
eastern = timezone ('US/Eastern' )
25
28
# Log file name
26
29
LOG_FILE_NAME = "file_rename_log.txt"
27
30
31
+ # Register HEIC support
32
+ register_heif_opener ()
33
+
28
34
# Function to write logs to both the GUI and the log file
29
35
def write_log (message ):
30
36
log_widget .insert (END , message + "\n " )
@@ -58,49 +64,88 @@ def generate_log_filename(base_name, directory):
58
64
return os .path .join (directory , filename )
59
65
60
66
# Function to get the date taken
67
+ from pillow_heif import register_heif_opener
68
+ from PIL import Image
69
+
70
+ # Register HEIC support
71
+ register_heif_opener ()
72
+
73
+ # Updated get_date_taken function
61
74
def get_date_taken (file_path ):
62
75
fallback_creation_time = datetime .datetime .fromtimestamp (os .path .getctime (file_path ))
63
76
fallback_modification_time = datetime .datetime .fromtimestamp (os .path .getmtime (file_path ))
77
+ date_taken = None # Default to None
78
+ is_fallback = True # Assume fallback unless proven otherwise
64
79
65
80
try :
66
81
mime_type , _ = mimetypes .guess_type (file_path )
67
82
if mime_type and mime_type .startswith ('image' ):
68
83
image = Image .open (file_path )
69
- exif_data = image ._getexif ()
70
- if exif_data :
71
- for tag , value in exif_data .items ():
72
- decoded = TAGS .get (tag , tag )
73
- if decoded == "DateTimeOriginal" :
74
- naive_time = datetime .datetime .strptime (value , '%Y:%m:%d %H:%M:%S' )
75
- if naive_time .tzinfo is None :
76
- date_taken = eastern .localize (naive_time )
77
- else :
78
- date_taken = naive_time .astimezone (eastern )
79
- return date_taken , False
84
+
85
+ # Handle HEIC/HEIF formats
86
+ if mime_type in ("image/heic" , "image/heif" ):
87
+ try :
88
+ from pillow_heif import register_heif_opener
89
+ register_heif_opener ()
90
+
91
+ # Check for EXIF metadata
92
+ metadata = image .info .get ("Exif" )
93
+ if metadata :
94
+ from exif import Image as ExifImage
95
+ exif_image = ExifImage (metadata )
96
+ if exif_image .has_exif and exif_image .datetime_original :
97
+ naive_time = datetime .datetime .strptime (exif_image .datetime_original , '%Y:%m:%d %H:%M:%S' )
98
+ date_taken = eastern .localize (naive_time ) if naive_time .tzinfo is None else naive_time .astimezone (eastern )
99
+ is_fallback = False
100
+
101
+ # Check for XMP metadata if EXIF fails
102
+ if date_taken is None :
103
+ xmp_data = image .info .get ("xmp" )
104
+ if xmp_data :
105
+ import xml .etree .ElementTree as ET
106
+ root = ET .fromstring (xmp_data )
107
+ create_date = root .find (".//{http://ns.adobe.com/xap/1.0/}CreateDate" )
108
+ if create_date is not None :
109
+ naive_time = datetime .datetime .strptime (create_date .text , '%Y-%m-%dT%H:%M:%S' )
110
+ date_taken = eastern .localize (naive_time ) if naive_time .tzinfo is None else naive_time .astimezone (eastern )
111
+ is_fallback = False
112
+ except Exception as e :
113
+ write_log (f"Error processing HEIC/HEIF metadata for { file_path } : { e } " )
114
+ else :
115
+ # Handle standard image formats using Pillow
116
+ exif_data = image ._getexif ()
117
+ if exif_data :
118
+ for tag , value in exif_data .items ():
119
+ decoded = TAGS .get (tag , tag )
120
+ if decoded == "DateTimeOriginal" :
121
+ naive_time = datetime .datetime .strptime (value , '%Y:%m:%d %H:%M:%S' )
122
+ date_taken = eastern .localize (naive_time ) if naive_time .tzinfo is None else naive_time .astimezone (eastern )
123
+ is_fallback = False
124
+ break
80
125
elif mime_type and mime_type .startswith ('video' ):
81
- # Use ffprobe to extract creation time from videos
126
+ # Handle video formats using ffprobe
82
127
result = subprocess .run (
83
- ["ffprobe" , "-v" , "error" , "-select_streams" , "v:0" , "-show_entries" ,
128
+ ["ffprobe" , "-v" , "error" , "-select_streams" , "v:0" , "-show_entries" ,
84
129
"format_tags=creation_time" , "-of" , "default=noprint_wrappers=1:nokey=1" , file_path ],
85
130
stdout = subprocess .PIPE ,
86
131
stderr = subprocess .STDOUT ,
87
- creationflags = subprocess .CREATE_NO_WINDOW if os .name == "nt" else 0 # Suppress window on Windows
132
+ creationflags = subprocess .CREATE_NO_WINDOW if os .name == "nt" else 0
88
133
)
89
134
creation_time = result .stdout .decode ().strip ()
90
135
if creation_time :
91
136
naive_time = datetime .datetime .fromisoformat (creation_time .replace ('Z' , '' ))
92
- if naive_time .tzinfo is None :
93
- date_taken = eastern .localize (naive_time )
94
- else :
95
- date_taken = naive_time .astimezone (eastern )
96
- return date_taken , False
137
+ date_taken = eastern .localize (naive_time ) if naive_time .tzinfo is None else naive_time .astimezone (eastern )
138
+ is_fallback = False
97
139
except Exception as e :
98
- return None , True # Return None with fallback indicator
140
+ write_log ( f"Error processing file { file_path } : { e } " )
99
141
100
- # Fallback to file last modified date
101
- return eastern .localize (fallback_modification_time ), True
142
+ # If no date_taken was found, fallback to modification time
143
+ if date_taken is None :
144
+ date_taken = eastern .localize (fallback_modification_time )
102
145
103
- # Function to rename files
146
+ return date_taken , is_fallback
147
+
148
+ # Updated rename_files_by_date function with refined logging
104
149
def rename_files_by_date (folder_path ):
105
150
global main_log_file , secondary_log_file
106
151
@@ -136,10 +181,7 @@ def rename_files_by_date(folder_path):
136
181
137
182
for file in files :
138
183
date_info = get_date_taken (file )
139
- if isinstance (date_info , tuple ):
140
- date_taken , is_fallback = date_info
141
- else :
142
- date_taken , is_fallback = date_info , False
184
+ date_taken , is_fallback = date_info if isinstance (date_info , tuple ) else (None , True )
143
185
144
186
# Collect dates for logging
145
187
fallback_creation_time = datetime .datetime .fromtimestamp (os .path .getctime (file ))
@@ -152,18 +194,20 @@ def rename_files_by_date(folder_path):
152
194
# Store file data
153
195
file_dates .append ((file , date_taken , is_fallback , fallback_creation_time , fallback_modification_time ))
154
196
155
- # Log details in real-time
197
+ # Logging logic
156
198
if is_fallback :
157
- write_log (f"No Date Taken Found. Fallback Date Modified for { file } : { date_taken } ({ date_taken .strftime ('%Y_%m_%d_%H' )} )" )
199
+ write_log (f"No Date Taken Found. Fallback Date Modified for { file } : { date_taken } ({ date_taken .strftime ('%Y_%m_%d_%H' )} )" if date_taken else f"No Date Taken Found. Fallback failed for { file } . Defaulting to unknown date." )
200
+ write_log (f" Date Taken: N/A" )
158
201
else :
159
202
write_log (f"Date Taken for { file } : { date_taken } ({ date_taken .strftime ('%Y_%m_%d_%H' )} )" )
160
- write_log (f" Date Taken: { date_taken if not is_fallback else 'N/A' } " )
203
+ write_log (f" Date Taken: { date_taken } " )
204
+
161
205
write_log (f" Creation: { fallback_creation_time } " )
162
206
write_log (f" Last Modified: { fallback_modification_time } " )
163
207
164
208
write_log ("Step 4: Sorting files by date..." )
165
209
update_progress_bar (4 )
166
- file_dates .sort (key = lambda x : x [1 ]) # Sort by date_taken
210
+ file_dates .sort (key = lambda x : x [1 ] or fallback_modification_time ) # Sort by date_taken or fallback
167
211
write_log ("Files sorted by date." )
168
212
169
213
write_log ("Step 5: Renaming files..." )
0 commit comments