forked from python-openxml/python-docx
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path__init__.py
More file actions
226 lines (198 loc) · 6.43 KB
/
Copy path__init__.py
File metadata and controls
226 lines (198 loc) · 6.43 KB
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# encoding: utf-8
"""
Provides objects that can characterize image streams as to content type and
size, as a required step in including them in a document.
"""
from __future__ import (
absolute_import, division, print_function, unicode_literals
)
import hashlib
import os
try:
from PIL import Image as PIL_Image
except ImportError:
import Image as PIL_Image
from docx.compat import BytesIO, is_string
from docx.exceptions import UnrecognizedImageError
from docx.image.gif import Gif
from docx.image.jpeg import Exif, Jfif
from docx.image.png import Png
from docx.opc.constants import CONTENT_TYPE as CT
from docx.shared import lazyproperty
SIGNATURES = (
# class, offset, signature_bytes
(Png, 0, b'\x89PNG\x0D\x0A\x1A\x0A'),
(Jfif, 6, b'JFIF'),
(Exif, 6, b'Exif'),
(Gif, 0, b'GIF87a'),
(Gif, 0, b'GIF89a'),
)
def image_cls_that_can_parse(stream):
"""
Return the |Image| subclass that can parse the headers of the image file
contained in *stream*.
"""
def read_32(stream):
stream.seek(0)
return stream.read(32)
header = read_32(stream)
for cls, offset, signature_bytes in SIGNATURES:
end = offset + len(signature_bytes)
found_bytes = header[offset:end]
if found_bytes == signature_bytes:
return cls
raise UnrecognizedImageError
class Image_OLD(object):
"""
A helper object that knows how to analyze an image file.
"""
def __init__(
self, blob, filename, content_type, px_width, px_height,
horz_dpi, vert_dpi):
super(Image_OLD, self).__init__()
self._blob = blob
self._filename = filename
self._content_type = content_type
self._px_width = px_width
self._px_height = px_height
self._horz_dpi = horz_dpi
self._vert_dpi = vert_dpi
@property
def blob(self):
"""
The bytes of the image 'file'
"""
return self._blob
@property
def content_type(self):
"""
The MIME type of the image, e.g. 'image/png'.
"""
return self._content_type
@lazyproperty
def ext(self):
"""
The file extension for the image. If an actual one is available from
a load filename it is used. Otherwise a canonical extension is
assigned based on the content type.
"""
return os.path.splitext(self._filename)[1]
@property
def filename(self):
"""
Original image file name, if loaded from disk, or a generic filename
if loaded from an anonymous stream.
"""
return self._filename
@classmethod
def from_blob(cls, blob):
stream = BytesIO(blob)
return cls._from_stream(stream, blob)
@classmethod
def from_file(cls, image_descriptor):
"""
Return a new |Image| instance loaded from the image file identified
by *image_descriptor*, a path or file-like object.
"""
if is_string(image_descriptor):
path = image_descriptor
with open(path, 'rb') as f:
blob = f.read()
stream = BytesIO(blob)
filename = os.path.basename(path)
else:
stream = image_descriptor
stream.seek(0)
blob = stream.read()
filename = None
return cls._from_stream(stream, blob, filename)
@property
def horz_dpi(self):
"""
The horizontal dots per inch (dpi) of the image, defaults to 72 when
no dpi information is stored in the image, as is often the case.
"""
return self._horz_dpi
@property
def px_width(self):
"""
The horizontal pixel dimension of the image
"""
return self._px_width
@property
def px_height(self):
"""
The vertical pixel dimension of the image
"""
return self._px_height
@lazyproperty
def sha1(self):
"""
SHA1 hash digest of the image blob
"""
return hashlib.sha1(self._blob).hexdigest()
@property
def vert_dpi(self):
"""
The vertical dots per inch (dpi) of the image, defaults to 72 when no
dpi information is stored in the image.
"""
return self._vert_dpi
@classmethod
def _analyze_image(cls, stream):
pil_image = cls._open_pillow_image(stream)
content_type = cls._format_content_type(pil_image.format)
px_width, px_height = pil_image.size
try:
horz_dpi, vert_dpi = pil_image.info.get('dpi')
except:
horz_dpi, vert_dpi = (72, 72)
return content_type, px_width, px_height, horz_dpi, vert_dpi
@classmethod
def _def_mime_ext(cls, mime_type):
"""
Return the default file extension, e.g. ``'.png'``, corresponding to
*mime_type*. Raises |KeyError| for unsupported image types.
"""
content_type_extensions = {
CT.BMP: '.bmp', CT.GIF: '.gif', CT.JPEG: '.jpg', CT.PNG: '.png',
CT.TIFF: '.tiff', CT.X_WMF: '.wmf'
}
return content_type_extensions[mime_type]
@classmethod
def _format_content_type(cls, format):
"""
Return the content type string (MIME type for images) corresponding
to the Pillow image format string *format*.
"""
format_content_types = {
'BMP': CT.BMP, 'GIF': CT.GIF, 'JPEG': CT.JPEG, 'PNG': CT.PNG,
'TIFF': CT.TIFF, 'WMF': CT.X_WMF
}
return format_content_types[format]
@classmethod
def _from_stream(cls, stream, blob, filename=None):
content_type, px_width, px_height, horz_dpi, vert_dpi = (
cls._analyze_image(stream)
)
if filename is None:
filename = 'image%s' % cls._def_mime_ext(content_type)
return cls(
blob, filename, content_type, px_width, px_height, horz_dpi,
vert_dpi
)
@classmethod
def _open_pillow_image(cls, stream):
"""
Return a Pillow ``Image`` instance loaded from the image file-like
object *stream*. The image is validated to confirm it is a supported
image type.
"""
stream.seek(0)
pil_image = PIL_Image.open(stream)
try:
cls._format_content_type(pil_image.format)
except KeyError:
tmpl = "unsupported image format '%s'"
raise ValueError(tmpl % (pil_image.format))
return pil_image