Gangmax Blog

Remove EXIF Info From JPEG File

Here is a Python script I created to remove the EXIF info from a given JPEG file.

exif_remover.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#! env python3
# References:
# https://stackoverflow.com/a/27638728/3115617
# https://www.media.mit.edu/pia/Research/deepview/exif.html
# http://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
# Licensed under GPLv3.

import sys
import struct
from typing import Union, Tuple

SOI_MARKER = b'\xff\xd8'
EOI_MARKER = b'\xff\xd9'
STRIPPED_APP_MARKERS = [
b'\xff\xe0', # JFIF
b'\xff\xe1', # EXIF
b'\xff\xed' # TIFF/IPTC
]

def _unpack(format: str, data: bytes, data_length: int=None) -> \
Union[int,bytes]:
if format == '{}s' and data_length is not None:
format = '{}s'.format(data_length)
return struct.unpack(format, data)[0]

def _to_int(content: bytes) -> int:
return _unpack('>H', content)

def _is_app_marker(content: bytes) -> bool:
value = _to_int(content)
return value >= 0xff00 and content not in [SOI_MARKER, EOI_MARKER]

def _read(content: bytes, index: int, size: int) -> Tuple[bytes, int]:
return content[index:index + size], index + size

def read_file(filename: str) -> bytes:
with open(filename, 'rb') as f:
return f.read()

def write_file(filename: str, content: bytes) -> None:
with open(filename, 'wb') as f:
f.write(content)

def drop_exif(source: bytes) -> bytes:
target = bytearray()
if len(source) <= 2 or source[0:2] != SOI_MARKER:
# This is not a valid JPEG file.
return source
target += SOI_MARKER
index = 2
app_index = 0
while index < len(source):
app_marker, index = _read(source, index, 2)
app_index += 1
print('APP Marker[{}, {}]: {}'.format(app_index, index - 2, app_marker))
if _is_app_marker(app_marker):
size_bytes, index = _read(source, index, 2)
size = _to_int(size_bytes)
if app_marker not in STRIPPED_APP_MARKERS:
target += app_marker
target += source[index - 2:index - 2 + size]
print(' NON-EXIF segment is found, copy it...')
else:
print(' EXIF segment is found, skip it...')
index = index - 2 + size
else:
# Image stream is found, copy until the end.
target += app_marker
target += source[index:]
index = len(source)
print(' Image stream is found, copy until the end.')
return bytes(target)

if __name__ == '__main__':
if len(sys.argv) < 3:
print("Usage: strip_exif.py <source.jpg> <target.jpg>")
exit(0)
source_file = sys.argv[1]
target_file = sys.argv[2]
source = read_file(source_file)
target = drop_exif(source)
write_file(target_file, target)
print('Done.')

Comments