Skip to content

Commit bbd548a

Browse files
committed
Updating to fix bug extracting EXIF metadata from some HEIC files
1 parent ec74b79 commit bbd548a

File tree

3 files changed

+80
-36
lines changed

3 files changed

+80
-36
lines changed

.github/workflows/workflow.yml

+3-11
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,24 @@ jobs:
1313
runs-on: windows-latest
1414

1515
steps:
16-
# Step 1: Checkout the repository
1716
- name: Checkout repository
1817
uses: actions/checkout@v3
1918

20-
# Step 2: Set up Python environment
2119
- name: Set up Python
2220
uses: actions/setup-python@v4
2321
with:
2422
python-version: 3.12
2523

26-
# Step 3: Install dependencies
2724
- name: Install dependencies
2825
run: |
2926
pip install -r requirements.txt
3027
pip install pyinstaller
3128
32-
# Step 4: Build the Windows executable
3329
- name: Build executable
3430
run: |
35-
pyinstaller --noconfirm --noconsole --icon=file_renamer_icon.ico --add-data "file_renamer_icon.ico;." --hidden-import=pillow_heif --hidden-import=pytz.zoneinfo --exclude-module numpy --exclude-module mkl --exclude-module tcl --exclude-module tbb --exclude-module pywin32 --exclude-module psutil rename_files.py
36-
37-
# Step 5: Upload as artifact
31+
piexif_path=$(python -c "import piexif; import os; print(os.path.dirname(piexif.__file__))")
32+
pyinstaller --clean --noconfirm --noconsole --icon=file_renamer_icon.ico --add-data "file_renamer_icon.ico;." --add-data "$piexif_path;piexif" --hidden-import=pillow_heif --hidden-import=pytz.zoneinfo --hidden-import=piexif --collect-all pillow_heif --collect-all piexif --exclude-module numpy --exclude-module mkl --exclude-module tcl --exclude-module tbb --exclude-module pywin32 --exclude-module psutil --log-level=DEBUG rename_files.py
33+
3834
- name: Upload artifact
3935
uses: actions/upload-artifact@v4
4036
with:
@@ -45,18 +41,15 @@ jobs:
4541
runs-on: ubuntu-latest
4642
needs: build-executable
4743
steps:
48-
# Step 1: Checkout the repository
4944
- name: Checkout repository
5045
uses: actions/checkout@v3
5146

52-
# Step 2: Fetch latest release URL
5347
- name: Fetch latest release URL
5448
id: fetch-latest-release
5549
run: |
5650
LATEST_RELEASE_URL=$(curl -s https://api.github.com/repos/dcwelch/file_renamer/releases/latest | jq -r '.assets[] | select(.name | endswith(".zip")).browser_download_url')
5751
echo "LATEST_RELEASE_URL=${LATEST_RELEASE_URL}" >> $GITHUB_ENV
5852
59-
# Step 3: Update webpage content
6053
- name: Update webpage
6154
run: |
6255
mkdir gh-pages
@@ -118,7 +111,6 @@ jobs:
118111
</body>
119112
</html>' > gh-pages/index.html
120113
121-
# Step 4: Deploy to GitHub Pages
122114
- name: Deploy to GitHub Pages
123115
uses: peaceiris/actions-gh-pages@v3
124116
with:

rename_files.py

+75-24
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,6 @@ def generate_log_filename(base_name, directory):
6262
counter += 1
6363
return os.path.join(directory, filename)
6464

65-
# Function to get the date taken
66-
from pillow_heif import register_heif_opener
67-
from PIL import Image
68-
69-
# Register HEIC support
70-
register_heif_opener()
71-
7265
# Updated get_date_taken function
7366
def get_date_taken(file_path):
7467
fallback_creation_time = datetime.datetime.fromtimestamp(os.path.getctime(file_path))
@@ -82,34 +75,86 @@ def get_date_taken(file_path):
8275
image = Image.open(file_path)
8376

8477
# Handle HEIC/HEIF formats using pillow-heif
78+
# --------------------------------------------------------------------------------------------------
79+
# --------------------------------------------------------------------------------------------------
80+
# --------------------------------------------------------------------------------------------------
81+
8582
if mime_type in ("image/heic", "image/heif"):
8683
try:
87-
register_heif_opener()
88-
metadata = image.info.get("Exif")
89-
90-
# Extract EXIF metadata if present
84+
try:
85+
import piexif
86+
write_log("[DEBUG] Successfully imported piexif in the bundled executable.")
87+
except ImportError as e:
88+
write_log(f"[ERROR] Failed to import piexif: {e}")
89+
90+
metadata = image.info.get("exif") # Note: Changed to lowercase 'exif' key
91+
xmp_data = image.info.get("xmp")
92+
93+
# Debugging fetched metadata (show first 500 characters for clarity)
94+
metadata_preview = str(image.info)[:500]
95+
write_log(f"[DEBUG] Metadata preview for {file_path}: {metadata_preview}")
96+
97+
# Attempt to parse EXIF metadata using piexif
9198
if metadata:
92-
from exif import Image as ExifImage
93-
exif_image = ExifImage(metadata)
94-
if exif_image.has_exif and exif_image.datetime_original:
95-
naive_time = datetime.datetime.strptime(exif_image.datetime_original, '%Y:%m:%d %H:%M:%S')
96-
date_taken = eastern.localize(naive_time) if naive_time.tzinfo is None else naive_time.astimezone(eastern)
97-
is_fallback = False
98-
99-
# Extract XMP metadata as a fallback
100-
if date_taken is None:
101-
xmp_data = image.info.get("xmp")
102-
if xmp_data:
99+
write_log(f"[DEBUG] Found EXIF metadata for {file_path}. Attempting to parse with piexif...")
100+
try:
101+
exif_dict = piexif.load(metadata)
102+
write_log(f"[DEBUG] Parsed EXIF tags: {exif_dict.keys()}")
103+
104+
# Extract DateTimeOriginal
105+
datetime_original = exif_dict.get("Exif", {}).get(piexif.ExifIFD.DateTimeOriginal)
106+
if datetime_original:
107+
datetime_original = datetime_original.decode("utf-8") # Convert bytes to string
108+
write_log(f"[DEBUG] Found DateTimeOriginal: {datetime_original}")
109+
naive_time = datetime.datetime.strptime(datetime_original, '%Y:%m:%d %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+
else:
113+
write_log(f"[WARNING] No DateTimeOriginal found in EXIF metadata.")
114+
115+
# Fallback to DateTimeDigitized
116+
if date_taken is None:
117+
datetime_digitized = exif_dict.get("Exif", {}).get(piexif.ExifIFD.DateTimeDigitized)
118+
if datetime_digitized:
119+
datetime_digitized = datetime_digitized.decode("utf-8")
120+
write_log(f"[DEBUG] Found DateTimeDigitized: {datetime_digitized}")
121+
naive_time = datetime.datetime.strptime(datetime_digitized, '%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+
else:
125+
write_log(f"[WARNING] No DateTimeDigitized found in EXIF metadata.")
126+
except Exception as exif_error:
127+
write_log(f"[ERROR] Failed to parse EXIF metadata using piexif for {file_path}: {exif_error}")
128+
129+
# Extract XMP metadata as a further fallback
130+
if date_taken is None and xmp_data:
131+
write_log(f"[DEBUG] Found XMP metadata for {file_path}. Attempting to parse...")
132+
try:
103133
import xml.etree.ElementTree as ET
104134
root = ET.fromstring(xmp_data)
105135
create_date = root.find(".//{http://ns.adobe.com/xap/1.0/}CreateDate")
106136
if create_date is not None:
137+
write_log(f"[DEBUG] Found CreateDate in XMP metadata: {create_date.text}")
107138
naive_time = datetime.datetime.strptime(create_date.text, '%Y-%m-%dT%H:%M:%S')
108139
date_taken = eastern.localize(naive_time) if naive_time.tzinfo is None else naive_time.astimezone(eastern)
109140
is_fallback = False
141+
else:
142+
write_log(f"[WARNING] No CreateDate found in XMP metadata for {file_path}.")
143+
except Exception as xmp_error:
144+
write_log(f"[ERROR] Failed to parse XMP metadata for {file_path}: {xmp_error}")
145+
146+
# Final debugging message
147+
if date_taken:
148+
write_log(f"[INFO] Successfully determined date_taken for {file_path}: {date_taken}")
149+
else:
150+
write_log(f"[ERROR] Unable to determine date_taken for {file_path}. All methods failed.")
110151

111152
except Exception as e:
112-
write_log(f"Error processing HEIC/HEIF metadata for {file_path}: {e}")
153+
write_log(f"[ERROR] Processing HEIC/HEIF metadata failed for {file_path}: {e}")
154+
155+
# --------------------------------------------------------------------------------------------------
156+
# --------------------------------------------------------------------------------------------------
157+
# --------------------------------------------------------------------------------------------------
113158
else:
114159
# Standard image formats using Pillow
115160
exif_data = image._getexif()
@@ -121,6 +166,11 @@ def get_date_taken(file_path):
121166
date_taken = eastern.localize(naive_time) if naive_time.tzinfo is None else naive_time.astimezone(eastern)
122167
is_fallback = False
123168
break
169+
elif decoded == "DateTimeDigitized":
170+
naive_time = datetime.datetime.strptime(value, '%Y:%m:%d %H:%M:%S')
171+
date_taken = eastern.localize(naive_time) if naive_time.tzinfo is None else naive_time.astimezone(eastern)
172+
is_fallback = False
173+
break
124174

125175
elif mime_type and mime_type.startswith('video'):
126176
# Extract metadata from video formats using ffprobe
@@ -244,7 +294,8 @@ def rename_files_by_date(folder_path):
244294

245295
# GUI Implementation
246296
def create_gui():
247-
global log_widget, progress_bar, progress_bar_labels # Make log_widget accessible to other functions
297+
298+
global log_widget, progress_bar, progress_bar_labels
248299

249300
def pick_folder():
250301
folder = filedialog.askdirectory()

requirements.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pillow
22
pillow-heif
3-
pytz
3+
pytz
4+
piexif

0 commit comments

Comments
 (0)