Pure Python library for EXIF metadata manipulation in JPEG and WebP image files.
npx @tessl/cli install tessl/pypi-piexif@1.1.00
# Piexif
1
2
Pure Python library for EXIF (Exchangeable Image File Format) metadata manipulation in JPEG and WebP image files. Piexif provides a simple and comprehensive API with five core functions for loading, dumping, inserting, removing, and transplanting EXIF data without external dependencies.
3
4
## Package Information
5
6
- **Package Name**: piexif
7
- **Package Type**: pypi
8
- **Language**: Python
9
- **Installation**: `pip install piexif`
10
- **Supported Python Versions**: 2.7, 3.5+, PyPy, IronPython
11
- **Supported Formats**: JPEG (all functions), TIFF (load only), WebP (load, dump, insert, remove)
12
13
## Core Imports
14
15
```python
16
import piexif
17
```
18
19
For specific functionality:
20
21
```python
22
from piexif import load, dump, insert, remove, transplant
23
from piexif import ImageIFD, ExifIFD, GPSIFD, InteropIFD, TYPES, TAGS
24
from piexif.helper import UserComment
25
```
26
27
## Basic Usage
28
29
```python
30
import piexif
31
32
# Load EXIF data from an image
33
exif_dict = piexif.load("image.jpg")
34
35
# Examine EXIF data structure
36
for ifd in ("0th", "Exif", "GPS", "1st"):
37
for tag in exif_dict[ifd]:
38
tag_name = piexif.TAGS[ifd][tag]["name"]
39
print(f"{tag_name}: {exif_dict[ifd][tag]}")
40
41
# Modify EXIF data
42
exif_dict["0th"][piexif.ImageIFD.Software] = b"My Software"
43
exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal] = b"2023:12:25 10:30:00"
44
45
# Convert back to bytes and insert into a new image
46
exif_bytes = piexif.dump(exif_dict)
47
piexif.insert(exif_bytes, "input.jpg", "output.jpg")
48
```
49
50
## Architecture
51
52
Piexif operates on the EXIF (Exchangeable Image File Format) data structure, which is embedded within image files as metadata. The EXIF format organizes metadata into multiple Image File Directories (IFDs), each containing related sets of tags:
53
54
- **0th IFD (Main Image)**: Primary image metadata using ImageIFD tags (dimensions, orientation, camera make/model, etc.)
55
- **Exif IFD**: Camera-specific settings using ExifIFD tags (exposure, ISO, focal length, date/time, etc.)
56
- **GPS IFD**: Location metadata using GPSIFD tags (coordinates, altitude, direction, etc.)
57
- **Interop IFD**: Interoperability data using InteropIFD tags (compatibility information)
58
- **1st IFD (Thumbnail)**: Thumbnail image metadata using ImageIFD tags
59
- **Thumbnail Data**: Embedded JPEG thumbnail image as raw bytes
60
61
This hierarchical structure allows piexif to preserve all metadata relationships while providing simple dictionary-based access to individual tags and values.
62
63
## Capabilities
64
65
### EXIF Data Loading
66
67
Load EXIF metadata from image files into a structured dictionary format.
68
69
```python { .api }
70
def load(input_data, key_is_name=False):
71
"""
72
Load EXIF data from JPEG, TIFF, or WebP files.
73
74
Parameters:
75
- input_data: str (file path) or bytes (image data)
76
- key_is_name: bool, optional - Use tag names as keys instead of tag numbers (default: False)
77
78
Returns:
79
dict: EXIF data with IFD structure
80
{
81
"0th": dict, # Main image metadata (ImageIFD tags)
82
"Exif": dict, # Camera-specific metadata (ExifIFD tags)
83
"GPS": dict, # GPS location data (GPSIFD tags)
84
"Interop": dict, # Interoperability data (InteropIFD tags)
85
"1st": dict, # Thumbnail image metadata (ImageIFD tags)
86
"thumbnail": bytes # Thumbnail JPEG data or None
87
}
88
89
Raises:
90
InvalidImageDataError: If image data is corrupted or invalid
91
"""
92
```
93
94
### EXIF Data Serialization
95
96
Convert EXIF dictionary data back to binary format for storage in image files.
97
98
```python { .api }
99
def dump(exif_dict):
100
"""
101
Convert EXIF dictionary to bytes format.
102
103
Parameters:
104
- exif_dict: dict - EXIF data dictionary with IFD structure
105
106
Returns:
107
bytes: EXIF data in binary format with proper headers
108
109
Raises:
110
ValueError: If EXIF dictionary structure is invalid
111
"""
112
```
113
114
### EXIF Data Insertion
115
116
Insert EXIF metadata into image files, supporting both file paths and binary data.
117
118
```python { .api }
119
def insert(exif, image, new_file=None):
120
"""
121
Insert EXIF data into JPEG or WebP image.
122
123
Parameters:
124
- exif: bytes - EXIF data in binary format (from dump())
125
- image: str (file path) or bytes (image data) - Target image
126
- new_file: str, optional - Output file path; if None, modifies original
127
128
Returns:
129
None: Modifies file in place or saves to new_file
130
131
Raises:
132
ValueError: If exif data is not valid EXIF data
133
InvalidImageDataError: If image format is unsupported
134
"""
135
```
136
137
### EXIF Data Removal
138
139
Remove all EXIF metadata from image files while preserving image quality.
140
141
```python { .api }
142
def remove(src, new_file=None):
143
"""
144
Remove EXIF data from JPEG or WebP image.
145
146
Parameters:
147
- src: str (file path) or bytes (image data) - Source image
148
- new_file: str, optional - Output file path; if None, modifies original
149
150
Returns:
151
None: Modifies file in place or saves to new_file
152
153
Raises:
154
InvalidImageDataError: If image format is unsupported
155
"""
156
```
157
158
### EXIF Data Transplantation
159
160
Copy EXIF metadata from one JPEG image to another, useful for preserving metadata during image processing.
161
162
```python { .api }
163
def transplant(exif_src, image, new_file=None):
164
"""
165
Copy EXIF data from one JPEG to another JPEG.
166
167
Parameters:
168
- exif_src: str (file path) or bytes (JPEG data) - Source JPEG with EXIF
169
- image: str (file path) or bytes (JPEG data) - Target JPEG image
170
- new_file: str, optional - Output file path; if None, modifies target
171
172
Returns:
173
None: Modifies file in place or saves to new_file
174
175
Raises:
176
ValueError: If source has no EXIF data or new_file not provided for bytes input
177
InvalidImageDataError: If image format is not JPEG
178
179
Note: Only supports JPEG format, not WebP or TIFF.
180
"""
181
```
182
183
## EXIF Constants and Tag References
184
185
### Data Types
186
187
```python { .api }
188
class TYPES:
189
"""EXIF data type constants."""
190
Byte = 1
191
Ascii = 2
192
Short = 3
193
Long = 4
194
Rational = 5
195
SByte = 6
196
Undefined = 7
197
SShort = 8
198
SLong = 9
199
SRational = 10
200
Float = 11
201
DFloat = 12
202
```
203
204
### Tag Dictionary
205
206
```python { .api }
207
TAGS: dict
208
"""
209
Tag information dictionary mapping IFD names to tag definitions.
210
211
Structure:
212
{
213
"0th": dict, # Main image tags (same as "Image")
214
"1st": dict, # Thumbnail image tags (same as "Image")
215
"Exif": dict, # Camera-specific tags
216
"GPS": dict, # GPS location tags
217
"Interop": dict, # Interoperability tags
218
"Image": dict # Standard TIFF/image tags
219
}
220
221
Each tag entry contains:
222
{
223
tag_number: {
224
"name": str, # Human-readable tag name
225
"type": int # EXIF data type (from TYPES class)
226
}
227
}
228
"""
229
```
230
231
### Image IFD Tags
232
233
```python { .api }
234
class ImageIFD:
235
"""Tag constants for main image metadata (0th and 1st IFD)."""
236
# Core image properties
237
ImageWidth = 256
238
ImageLength = 257
239
BitsPerSample = 258
240
Compression = 259
241
PhotometricInterpretation = 262
242
Orientation = 274
243
SamplesPerPixel = 277
244
XResolution = 282
245
YResolution = 283
246
ResolutionUnit = 296
247
248
# Image description
249
ImageDescription = 270
250
Make = 271
251
Model = 272
252
Software = 305
253
DateTime = 306
254
Artist = 315
255
Copyright = 33432
256
257
# Technical metadata
258
WhitePoint = 318
259
PrimaryChromaticities = 319
260
YCbCrCoefficients = 529
261
YCbCrSubSampling = 530
262
YCbCrPositioning = 531
263
ReferenceBlackWhite = 532
264
265
# Pointers to other IFDs
266
ExifTag = 34665 # Pointer to Exif IFD
267
GPSTag = 34853 # Pointer to GPS IFD
268
269
# Additional core tags
270
ProcessingSoftware = 11
271
NewSubfileType = 254
272
SubfileType = 255
273
Threshholding = 263
274
CellWidth = 264
275
CellLength = 265
276
FillOrder = 266
277
DocumentName = 269
278
StripOffsets = 273
279
RowsPerStrip = 278
280
StripByteCounts = 279
281
PlanarConfiguration = 284
282
GrayResponseUnit = 290
283
GrayResponseCurve = 291
284
T4Options = 292
285
T6Options = 293
286
TransferFunction = 301
287
HostComputer = 316
288
Predictor = 317
289
ColorMap = 320
290
HalftoneHints = 321
291
TileWidth = 322
292
TileLength = 323
293
TileOffsets = 324
294
TileByteCounts = 325
295
SubIFDs = 330
296
InkSet = 332
297
InkNames = 333
298
NumberOfInks = 334
299
DotRange = 336
300
TargetPrinter = 337
301
ExtraSamples = 338
302
SampleFormat = 339
303
SMinSampleValue = 340
304
SMaxSampleValue = 341
305
TransferRange = 342
306
ClipPath = 343
307
XClipPathUnits = 344
308
YClipPathUnits = 345
309
Indexed = 346
310
JPEGTables = 347
311
OPIProxy = 351
312
313
# JPEG-specific tags
314
JPEGProc = 512
315
JPEGInterchangeFormat = 513
316
JPEGInterchangeFormatLength = 514
317
JPEGRestartInterval = 515
318
JPEGLosslessPredictors = 517
319
JPEGPointTransforms = 518
320
JPEGQTables = 519
321
JPEGDCTables = 520
322
JPEGACTables = 521
323
324
# Metadata and extensions
325
XMLPacket = 700
326
Rating = 18246
327
RatingPercent = 18249
328
ImageID = 32781
329
CFARepeatPatternDim = 33421
330
CFAPattern = 33422
331
BatteryLevel = 33423
332
ImageResources = 34377
333
```
334
335
### Exif IFD Tags
336
337
```python { .api }
338
class ExifIFD:
339
"""Tag constants for camera-specific metadata (Exif IFD)."""
340
# Exposure settings
341
ExposureTime = 33434
342
FNumber = 33437
343
ExposureProgram = 34850
344
ISOSpeedRatings = 34855
345
ShutterSpeedValue = 37377
346
ApertureValue = 37378
347
BrightnessValue = 37379
348
ExposureBiasValue = 37380
349
MaxApertureValue = 37381
350
351
# Date and time
352
ExifVersion = 36864
353
DateTimeOriginal = 36867
354
DateTimeDigitized = 36868
355
OffsetTime = 36880
356
OffsetTimeOriginal = 36881
357
OffsetTimeDigitized = 36882
358
SubSecTime = 37520
359
SubSecTimeOriginal = 37521
360
SubSecTimeDigitized = 37522
361
362
# Camera settings
363
MeteringMode = 37383
364
LightSource = 37384
365
Flash = 37385
366
FocalLength = 37386
367
SubjectDistance = 37382
368
SubjectArea = 37396
369
370
# Image properties
371
ColorSpace = 40961
372
PixelXDimension = 40962
373
PixelYDimension = 40963
374
ComponentsConfiguration = 37121
375
CompressedBitsPerPixel = 37122
376
377
# Additional metadata
378
UserComment = 37510
379
MakerNote = 37500
380
FlashpixVersion = 40960
381
RelatedSoundFile = 40964
382
383
# Advanced technical settings
384
SpectralSensitivity = 34852
385
OECF = 34856
386
SensitivityType = 34864
387
StandardOutputSensitivity = 34865
388
RecommendedExposureIndex = 34866
389
ISOSpeed = 34867
390
ISOSpeedLatitudeyyy = 34868
391
ISOSpeedLatitudezzz = 34869
392
Temperature = 37888
393
Humidity = 37889
394
Pressure = 37890
395
WaterDepth = 37891
396
Acceleration = 37892
397
CameraElevationAngle = 37893
398
399
# Image capture details
400
FlashEnergy = 41483
401
SpatialFrequencyResponse = 41484
402
FocalPlaneXResolution = 41486
403
FocalPlaneYResolution = 41487
404
FocalPlaneResolutionUnit = 41488
405
SubjectLocation = 41492
406
ExposureIndex = 41493
407
SensingMethod = 41495
408
FileSource = 41728
409
SceneType = 41729
410
CFAPattern = 41730
411
412
# Processing and quality
413
CustomRendered = 41985
414
ExposureMode = 41986
415
WhiteBalance = 41987
416
DigitalZoomRatio = 41988
417
FocalLengthIn35mmFilm = 41989
418
SceneCaptureType = 41990
419
GainControl = 41991
420
Contrast = 41992
421
Saturation = 41993
422
Sharpness = 41994
423
DeviceSettingDescription = 41995
424
SubjectDistanceRange = 41996
425
ImageUniqueID = 42016
426
427
# Camera and lens identification
428
CameraOwnerName = 42032
429
BodySerialNumber = 42033
430
LensSpecification = 42034
431
LensMake = 42035
432
LensModel = 42036
433
LensSerialNumber = 42037
434
Gamma = 42240
435
436
# Interoperability pointer
437
InteroperabilityTag = 40965
438
```
439
440
### GPS IFD Tags
441
442
```python { .api }
443
class GPSIFD:
444
"""Tag constants for GPS location metadata (GPS IFD)."""
445
GPSVersionID = 0
446
GPSLatitudeRef = 1 # 'N' or 'S'
447
GPSLatitude = 2 # Degrees, minutes, seconds
448
GPSLongitudeRef = 3 # 'E' or 'W'
449
GPSLongitude = 4 # Degrees, minutes, seconds
450
GPSAltitudeRef = 5 # Above/below sea level
451
GPSAltitude = 6 # Altitude in meters
452
GPSTimeStamp = 7 # UTC time as hours, minutes, seconds
453
GPSSatellites = 8 # Satellites used for measurement
454
GPSStatus = 9 # Receiver status
455
GPSMeasureMode = 10 # Measurement mode
456
GPSDOP = 11 # Measurement precision
457
GPSSpeedRef = 12 # Speed unit
458
GPSSpeed = 13 # Speed of GPS receiver
459
GPSTrackRef = 14 # Reference for direction of movement
460
GPSTrack = 15 # Direction of movement
461
GPSImgDirectionRef = 16 # Reference for direction of image
462
GPSImgDirection = 17 # Direction of image when captured
463
GPSMapDatum = 18 # Geodetic survey data
464
GPSDestLatitudeRef = 19 # Reference for destination latitude
465
GPSDestLatitude = 20 # Destination latitude
466
GPSDestLongitudeRef = 21 # Reference for destination longitude
467
GPSDestLongitude = 22 # Destination longitude
468
GPSDestBearingRef = 23 # Reference for destination bearing
469
GPSDestBearing = 24 # Destination bearing
470
GPSDestDistanceRef = 25 # Reference for destination distance
471
GPSDestDistance = 26 # Destination distance
472
GPSProcessingMethod = 27 # GPS processing method
473
GPSAreaInformation = 28 # GPS area information
474
GPSDateStamp = 29 # GPS date
475
GPSDifferential = 30 # Differential correction
476
GPSHPositioningError = 31 # Horizontal positioning error
477
```
478
479
### Interoperability IFD Tags
480
481
```python { .api }
482
class InteropIFD:
483
"""Tag constants for interoperability metadata (Interop IFD)."""
484
InteroperabilityIndex = 1
485
```
486
487
## Helper Utilities
488
489
### UserComment Encoding
490
491
Utility class for handling the UserComment EXIF field, which requires special encoding.
492
493
```python { .api }
494
class UserComment:
495
"""Helper for UserComment EXIF field encoding/decoding."""
496
497
# Supported encodings
498
ASCII = 'ascii'
499
JIS = 'jis'
500
UNICODE = 'unicode'
501
ENCODINGS = (ASCII, JIS, UNICODE)
502
503
@classmethod
504
def load(cls, data):
505
"""
506
Convert UserComment EXIF field to string.
507
508
Parameters:
509
- data: bytes - UserComment field data from EXIF
510
511
Returns:
512
str: Decoded comment text
513
514
Raises:
515
ValueError: If data is invalid or encoding unsupported
516
"""
517
518
@classmethod
519
def dump(cls, data, encoding="ascii"):
520
"""
521
Convert string to UserComment EXIF field format.
522
523
Parameters:
524
- data: str - Comment text to encode
525
- encoding: str - Encoding to use ('ascii', 'jis', 'unicode')
526
527
Returns:
528
bytes: Encoded UserComment field data
529
530
Raises:
531
ValueError: If encoding is unsupported
532
"""
533
```
534
535
## Exception Types
536
537
```python { .api }
538
class InvalidImageDataError(ValueError):
539
"""Raised when image data is corrupted or in unsupported format."""
540
pass
541
```
542
543
## Usage Examples
544
545
### Basic EXIF Manipulation
546
547
```python
548
import piexif
549
550
# Load and examine EXIF data
551
exif_dict = piexif.load("photo.jpg")
552
print(f"Camera: {exif_dict['0th'].get(piexif.ImageIFD.Make, b'Unknown').decode()}")
553
print(f"Date: {exif_dict['Exif'].get(piexif.ExifIFD.DateTimeOriginal, b'Unknown').decode()}")
554
555
# Modify and save EXIF data
556
exif_dict["0th"][piexif.ImageIFD.Software] = b"Python Piexif"
557
exif_bytes = piexif.dump(exif_dict)
558
piexif.insert(exif_bytes, "photo.jpg", "photo_modified.jpg")
559
```
560
561
### Working with GPS Data
562
563
```python
564
import piexif
565
566
# Add GPS coordinates to an image
567
exif_dict = piexif.load("photo.jpg")
568
569
# GPS coordinates for New York City (40.7128° N, 74.0060° W)
570
exif_dict["GPS"] = {
571
piexif.GPSIFD.GPSVersionID: (2, 0, 0, 0),
572
piexif.GPSIFD.GPSLatitudeRef: b'N',
573
piexif.GPSIFD.GPSLatitude: ((40, 1), (42, 1), (46, 1)), # 40°42'46"
574
piexif.GPSIFD.GPSLongitudeRef: b'W',
575
piexif.GPSIFD.GPSLongitude: ((74, 1), (0, 1), (22, 1)), # 74°0'22"
576
}
577
578
exif_bytes = piexif.dump(exif_dict)
579
piexif.insert(exif_bytes, "photo.jpg", "photo_with_gps.jpg")
580
```
581
582
### UserComment Handling
583
584
```python
585
import piexif
586
from piexif.helper import UserComment
587
588
# Read UserComment from EXIF
589
exif_dict = piexif.load("photo.jpg")
590
user_comment_bytes = exif_dict["Exif"].get(piexif.ExifIFD.UserComment)
591
if user_comment_bytes:
592
comment = UserComment.load(user_comment_bytes)
593
print(f"User comment: {comment}")
594
595
# Set UserComment in EXIF
596
new_comment = "Processed with Python"
597
exif_dict["Exif"][piexif.ExifIFD.UserComment] = UserComment.dump(new_comment, "unicode")
598
exif_bytes = piexif.dump(exif_dict)
599
piexif.insert(exif_bytes, "photo.jpg", "photo_with_comment.jpg")
600
```
601
602
### Working with PIL/Pillow
603
604
```python
605
from PIL import Image
606
import piexif
607
608
# Load image with PIL and extract EXIF
609
im = Image.open("photo.jpg")
610
if "exif" in im.info:
611
exif_dict = piexif.load(im.info["exif"])
612
613
# Modify EXIF data
614
w, h = im.size
615
exif_dict["0th"][piexif.ImageIFD.XResolution] = (w, 1)
616
exif_dict["0th"][piexif.ImageIFD.YResolution] = (h, 1)
617
618
# Save with modified EXIF
619
exif_bytes = piexif.dump(exif_dict)
620
im.save("photo_resized.jpg", "jpeg", exif=exif_bytes)
621
```