Easy to use Python module to extract Exif metadata from digital image files
npx @tessl/cli install tessl/pypi-exifread@3.5.00
# ExifRead
1
2
Easy to use Python module to extract Exif metadata from digital image files. Supports multiple image formats including TIFF, JPEG, JPEG XL, PNG, Webp, HEIC, and RAW files with zero external dependencies.
3
4
## Package Information
5
6
- **Package Name**: ExifRead
7
- **Language**: Python
8
- **Installation**: `pip install exifread`
9
- **Supported Python**: 3.7 to 3.13
10
- **License**: BSD-3-Clause
11
- **Dependencies**: None (pure Python)
12
13
## Core Imports
14
15
```python
16
import exifread
17
```
18
19
For specific components:
20
21
```python
22
from exifread import process_file
23
from exifread.core.exceptions import ExifError, InvalidExif, ExifNotFound
24
from exifread.core.ifd_tag import IfdTag
25
from exifread.utils import get_gps_coords, Ratio
26
from exifread.serialize import convert_types
27
```
28
29
## Architecture
30
31
ExifRead uses a multi-layered architecture for EXIF metadata extraction:
32
33
1. **File Format Detection**: Automatically identifies image format (JPEG, TIFF, PNG, WebP, HEIC, etc.) and locates EXIF data blocks within the file structure
34
2. **IFD Processing**: Parses Image File Directory (IFD) structures that contain organized metadata entries with tags, types, and values
35
3. **Tag Extraction**: Converts raw binary EXIF data into structured IfdTag objects with human-readable information and typed values
36
4. **Type Conversion**: Optionally converts IfdTag objects to standard Python types (int, float, str, list) for easier serialization and programmatic use
37
38
The core `process_file()` function orchestrates this pipeline with configurable options for performance optimization (quick mode, early termination) and output format control (builtin types, thumbnail extraction). The architecture supports both lenient processing (warnings for errors) and strict processing (exceptions on errors) to accommodate various use cases from casual metadata viewing to production data processing.
39
40
## Basic Usage
41
42
```python
43
import exifread
44
45
# Basic EXIF extraction
46
with open("image.jpg", "rb") as f:
47
tags = exifread.process_file(f)
48
49
# Iterate through tags (skip long/boring ones)
50
for tag, value in tags.items():
51
if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename', 'EXIF MakerNote'):
52
print(f"Key: {tag}, value: {value}")
53
54
# Quick processing (skip MakerNotes and thumbnails)
55
with open("image.jpg", "rb") as f:
56
tags = exifread.process_file(f, details=False, extract_thumbnail=False)
57
58
# Convert to built-in Python types for JSON serialization
59
with open("image.jpg", "rb") as f:
60
tags = exifread.process_file(f, builtin_types=True)
61
import json
62
json_data = json.dumps(tags)
63
64
# Extract GPS coordinates
65
from exifread.utils import get_gps_coords
66
coords = get_gps_coords(tags) # Returns (latitude, longitude) or None
67
if coords:
68
lat, lng = coords
69
print(f"GPS: {lat}, {lng}")
70
```
71
72
## Capabilities
73
74
### EXIF Processing
75
76
Extract comprehensive EXIF metadata from digital image files with configurable processing options for performance optimization and data format conversion.
77
78
```python { .api }
79
def process_file(
80
fh,
81
stop_tag="UNDEF",
82
details=True,
83
strict=False,
84
debug=False,
85
truncate_tags=True,
86
auto_seek=True,
87
extract_thumbnail=True,
88
builtin_types=False
89
) -> dict:
90
"""
91
Process an image file to extract EXIF metadata.
92
93
Parameters:
94
- fh: Binary file handle opened in 'rb' mode
95
- stop_tag: Stop processing when this tag is retrieved (default: "UNDEF")
96
- details: If True, process MakerNotes for camera-specific metadata (default: True)
97
- strict: If True, raise exceptions on errors instead of logging warnings (default: False)
98
- debug: Output detailed debug information during processing (default: False)
99
- truncate_tags: If True, truncate printable tag output for readability (default: True)
100
- auto_seek: If True, automatically seek to start of file (default: True)
101
- extract_thumbnail: If True, extract JPEG thumbnail if present (default: True)
102
- builtin_types: If True, convert tags to standard Python types for serialization (default: False)
103
104
Returns:
105
Dictionary mapping tag names to values. Keys are formatted as "IFD_NAME TAG_NAME".
106
Values are IfdTag objects (builtin_types=False) or Python built-in types (builtin_types=True).
107
108
Raises:
109
- InvalidExif: When EXIF data is malformed (strict=True only)
110
- ExifNotFound: When no EXIF data is found (strict=True only)
111
"""
112
```
113
114
### GPS Coordinate Extraction
115
116
Extract latitude and longitude coordinates from EXIF GPS tags with automatic conversion to decimal degrees format.
117
118
```python { .api }
119
def get_gps_coords(tags: dict) -> tuple[float, float] | None:
120
"""
121
Extract GPS coordinates from EXIF tags.
122
123
Parameters:
124
- tags: Dictionary of EXIF tags from process_file()
125
126
Returns:
127
Tuple of (latitude, longitude) in decimal degrees, or None if GPS data not found.
128
Handles both IfdTag objects and serialized tags.
129
"""
130
```
131
132
### Data Serialization
133
134
Convert EXIF tag objects to standard Python types for easier programmatic use and JSON serialization.
135
136
```python { .api }
137
def convert_types(exif_tags: dict) -> dict:
138
"""
139
Convert Exif IfdTags to built-in Python types.
140
141
Parameters:
142
- exif_tags: Dictionary of IfdTag objects from process_file()
143
144
Returns:
145
Dictionary with same keys but values converted to int, float, str, bytes, list, or None.
146
Single-element lists are unpacked to individual values.
147
"""
148
```
149
150
### Command Line Interface
151
152
Process image files from the command line with various output and processing options.
153
154
```python { .api }
155
def main() -> None:
156
"""Main CLI entry point. Processes command line arguments and extracts EXIF data."""
157
158
def run_cli(args: argparse.Namespace) -> None:
159
"""
160
Execute CLI processing with parsed arguments.
161
162
Parameters:
163
- args: Parsed command line arguments from get_args()
164
"""
165
166
def get_args() -> argparse.Namespace:
167
"""
168
Parse command line arguments.
169
170
Returns:
171
argparse.Namespace with parsed CLI options
172
"""
173
```
174
175
**CLI Usage:**
176
```bash
177
# Basic usage
178
EXIF.py image.jpg
179
180
# Multiple files with quick processing and builtin types
181
EXIF.py -q -b image1.jpg image2.tiff
182
183
# Stop at specific tag with strict error handling
184
EXIF.py -t DateTimeOriginal -s image.jpg
185
186
# Debug mode with color output
187
EXIF.py -d -c image.jpg
188
189
# Module execution
190
python -m exifread image.jpg
191
```
192
193
**CLI Options:**
194
- `-v, --version`: Display version information
195
- `-q, --quick`: Skip MakerNotes and thumbnails for faster processing
196
- `-t TAG, --tag TAG`: Stop processing when specified tag is retrieved
197
- `-s, --strict`: Run in strict mode (raise exceptions on errors)
198
- `-b, --builtin`: Convert IfdTag values to built-in Python types
199
- `-d, --debug`: Run in debug mode with detailed information
200
- `-c, --color`: Enable colored output (POSIX systems only)
201
202
### Logging
203
204
Configure logging output with debug information and colored formatting for development and troubleshooting.
205
206
```python { .api }
207
def get_logger():
208
"""
209
Get the exifread logger instance.
210
211
Returns:
212
logging.Logger configured for exifread
213
"""
214
215
def setup_logger(debug: bool, color: bool) -> None:
216
"""
217
Configure logger with debug and color settings.
218
219
Parameters:
220
- debug: Enable debug level logging
221
- color: Enable colored console output
222
"""
223
```
224
225
## Data Types
226
227
### Tag Representation
228
229
```python { .api }
230
class IfdTag:
231
"""
232
Represents an IFD (Image File Directory) tag containing EXIF metadata.
233
234
Attributes:
235
- printable: Human-readable version of the tag data (str)
236
- tag: Numeric tag identifier (int)
237
- field_type: EXIF field type from FieldType enum
238
- values: Raw tag values (various types: str, bytes, list)
239
- field_offset: Byte offset of field start in IFD (int)
240
- field_length: Length of data field in bytes (int)
241
- prefer_printable: Whether to prefer printable over raw values for serialization (bool)
242
"""
243
244
def __init__(
245
self,
246
printable: str,
247
tag: int,
248
field_type: FieldType,
249
values,
250
field_offset: int,
251
field_length: int,
252
prefer_printable: bool = True
253
) -> None: ...
254
255
def __str__(self) -> str:
256
"""Return printable representation of tag."""
257
258
def __repr__(self) -> str:
259
"""Return detailed representation with field type and offset information."""
260
```
261
262
### Ratio Values
263
264
```python { .api }
265
class Ratio:
266
"""
267
Represents EXIF ratio values, extending fractions.Fraction with additional methods.
268
Used for GPS coordinates, exposure times, and other fractional EXIF data.
269
"""
270
271
def __new__(cls, numerator: int = 0, denominator: int | None = None): ...
272
273
@property
274
def num(self) -> int:
275
"""Get numerator value."""
276
277
@property
278
def den(self) -> int:
279
"""Get denominator value."""
280
281
def decimal(self) -> float:
282
"""Convert ratio to decimal floating point value."""
283
```
284
285
### Field Types
286
287
```python { .api }
288
class FieldType:
289
"""
290
EXIF field type enumeration defining data storage formats.
291
"""
292
PROPRIETARY = 0
293
BYTE = 1
294
ASCII = 2
295
SHORT = 3
296
LONG = 4
297
RATIO = 5
298
SIGNED_BYTE = 6
299
UNDEFINED = 7
300
SIGNED_SHORT = 8
301
SIGNED_LONG = 9
302
SIGNED_RATIO = 10
303
FLOAT_32 = 11
304
FLOAT_64 = 12
305
IFD = 13
306
```
307
308
### Exception Types
309
310
```python { .api }
311
class ExifError(Exception):
312
"""Base exception class for all ExifRead errors."""
313
314
class InvalidExif(ExifError):
315
"""Raised when EXIF data is malformed or corrupted."""
316
317
class ExifNotFound(ExifError):
318
"""Raised when no EXIF data is found in the image file."""
319
```
320
321
## Constants
322
323
```python { .api }
324
__version__: str = "3.5.1"
325
"""Package version string."""
326
327
DEFAULT_STOP_TAG: str = "UNDEF"
328
"""Default tag name to stop processing at."""
329
330
IGNORE_TAGS: list[int] = [0x02BC, 0x927C, 0x9286]
331
"""Tag IDs ignored during quick processing mode."""
332
333
FIELD_DEFINITIONS: dict
334
"""Maps FieldType values to (byte_length, description) tuples."""
335
```
336
337
## Usage Examples
338
339
### Processing Multiple Images
340
341
```python
342
import exifread
343
import os
344
from pathlib import Path
345
346
def process_directory(directory_path):
347
"""Process all JPEG images in a directory."""
348
image_extensions = {'.jpg', '.jpeg', '.tiff', '.tif'}
349
350
for file_path in Path(directory_path).iterdir():
351
if file_path.suffix.lower() in image_extensions:
352
try:
353
with open(file_path, 'rb') as f:
354
tags = exifread.process_file(f, builtin_types=True)
355
356
if tags:
357
print(f"\n{file_path.name}:")
358
# Print camera info
359
camera = tags.get('Image Make', 'Unknown')
360
model = tags.get('Image Model', 'Unknown')
361
print(f" Camera: {camera} {model}")
362
363
# Print capture date
364
date_taken = tags.get('EXIF DateTimeOriginal', 'Unknown')
365
print(f" Date: {date_taken}")
366
367
# Print GPS if available
368
from exifread.utils import get_gps_coords
369
coords = get_gps_coords(tags)
370
if coords:
371
lat, lng = coords
372
print(f" GPS: {lat:.6f}, {lng:.6f}")
373
374
except Exception as e:
375
print(f"Error processing {file_path.name}: {e}")
376
377
# Usage
378
process_directory("/path/to/photos")
379
```
380
381
### Custom Tag Processing
382
383
```python
384
import exifread
385
386
def extract_specific_tags(file_path, target_tags):
387
"""Extract only specific EXIF tags efficiently."""
388
389
with open(file_path, 'rb') as f:
390
# Use quick mode and stop early for efficiency
391
tags = exifread.process_file(
392
f,
393
details=False, # Skip MakerNotes
394
extract_thumbnail=False, # Skip thumbnails
395
builtin_types=True # Get Python types
396
)
397
398
result = {}
399
for tag_name in target_tags:
400
if tag_name in tags:
401
result[tag_name] = tags[tag_name]
402
403
return result
404
405
# Extract specific metadata
406
important_tags = [
407
'Image Make',
408
'Image Model',
409
'EXIF DateTimeOriginal',
410
'EXIF ExposureTime',
411
'EXIF FNumber',
412
'EXIF ISOSpeedRatings'
413
]
414
415
metadata = extract_specific_tags('photo.jpg', important_tags)
416
print(metadata)
417
```
418
419
### Image Orientation Correction
420
421
```python
422
import exifread
423
from PIL import Image
424
425
def correct_image_orientation(image_path):
426
"""Correct image orientation based on EXIF data."""
427
428
# Read EXIF orientation
429
with open(image_path, 'rb') as f:
430
tags = exifread.process_file(f, details=False)
431
432
# Open image
433
img = Image.open(image_path)
434
435
# Check for orientation tag
436
orientation = tags.get('Image Orientation')
437
if orientation:
438
orientation_value = orientation.values[0] if hasattr(orientation, 'values') else orientation
439
440
# Apply rotation based on orientation
441
if orientation_value == 3:
442
img = img.transpose(Image.ROTATE_180)
443
elif orientation_value == 6:
444
img = img.transpose(Image.ROTATE_270)
445
elif orientation_value == 8:
446
img = img.transpose(Image.ROTATE_90)
447
448
return img
449
450
# Usage
451
corrected_image = correct_image_orientation('photo.jpg')
452
corrected_image.save('corrected_photo.jpg')
453
```
454
455
## Error Handling
456
457
The library provides different error handling approaches based on the `strict` parameter:
458
459
**Lenient Mode (default):**
460
```python
461
# Warnings logged, empty dict returned on errors
462
with open('corrupted.jpg', 'rb') as f:
463
tags = exifread.process_file(f) # Returns {} if no EXIF found
464
```
465
466
**Strict Mode:**
467
```python
468
from exifread.core.exceptions import ExifNotFound, InvalidExif
469
470
try:
471
with open('image.jpg', 'rb') as f:
472
tags = exifread.process_file(f, strict=True)
473
except ExifNotFound:
474
print("No EXIF data found in image")
475
except InvalidExif:
476
print("EXIF data is corrupted or invalid")
477
except Exception as e:
478
print(f"Unexpected error: {e}")
479
```
480
481
## Supported Image Formats
482
483
- **TIFF**: Tagged Image File Format with full EXIF support
484
- **JPEG**: JPEG images with EXIF metadata blocks
485
- **JPEG XL**: Next-generation JPEG format
486
- **PNG**: Portable Network Graphics with EXIF chunks
487
- **WebP**: Web-optimized format with EXIF support
488
- **HEIC**: High Efficiency Image Container (Apple format)
489
- **RAW**: Various camera RAW formats with embedded EXIF
490
491
## Performance Considerations
492
493
**Quick Processing:** Use `details=False` and `extract_thumbnail=False` for faster processing:
494
```python
495
tags = exifread.process_file(f, details=False, extract_thumbnail=False)
496
```
497
498
**Early Termination:** Stop processing at specific tags:
499
```python
500
tags = exifread.process_file(f, stop_tag='DateTimeOriginal')
501
```
502
503
**Memory Efficiency:** Use built-in types to avoid keeping IfdTag objects:
504
```python
505
tags = exifread.process_file(f, builtin_types=True)
506
```