sdata 1.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. sdata/__init__.py +140 -0
  2. sdata/base.py +684 -0
  3. sdata/contrib/__init__.py +0 -0
  4. sdata/contrib/piexif/__init__.py +12 -0
  5. sdata/contrib/piexif/_common.py +94 -0
  6. sdata/contrib/piexif/_dump.py +346 -0
  7. sdata/contrib/piexif/_exceptions.py +2 -0
  8. sdata/contrib/piexif/_exif.py +638 -0
  9. sdata/contrib/piexif/_insert.py +60 -0
  10. sdata/contrib/piexif/_load.py +270 -0
  11. sdata/contrib/piexif/_remove.py +56 -0
  12. sdata/contrib/piexif/_transplant.py +45 -0
  13. sdata/contrib/piexif/_webp.py +240 -0
  14. sdata/contrib/piexif/helper.py +66 -0
  15. sdata/contrib/ranger/__init__.py +8 -0
  16. sdata/contrib/ranger/collections/__init__.py +0 -0
  17. sdata/contrib/ranger/collections/rangebucketmap.py +346 -0
  18. sdata/contrib/ranger/collections/rangemap.py +396 -0
  19. sdata/contrib/ranger/collections/rangeset.py +483 -0
  20. sdata/contrib/ranger/range/__init__.py +0 -0
  21. sdata/contrib/ranger/range/cut.py +285 -0
  22. sdata/contrib/ranger/range/range.py +644 -0
  23. sdata/contrib/sobol_seq.py +394 -0
  24. sdata/contrib/sortedcontainers/LICENSE +13 -0
  25. sdata/contrib/sortedcontainers/__init__.py +74 -0
  26. sdata/contrib/sortedcontainers/sorteddict.py +789 -0
  27. sdata/contrib/sortedcontainers/sortedlist.py +2646 -0
  28. sdata/contrib/sortedcontainers/sortedset.py +733 -0
  29. sdata/contrib/sqlitedict.py +598 -0
  30. sdata/contrib/timeflake/__init__.py +40 -0
  31. sdata/contrib/timeflake/flake.py +69 -0
  32. sdata/contrib/timeflake/utils.py +47 -0
  33. sdata/deprecated/__init__.py +10 -0
  34. sdata/deprecated/data.py +1503 -0
  35. sdata/did/__init__.py +129 -0
  36. sdata/did/_http.py +67 -0
  37. sdata/did/base58btc.py +49 -0
  38. sdata/did/did_github.py +152 -0
  39. sdata/did/did_peer4.py +155 -0
  40. sdata/did/did_web.py +222 -0
  41. sdata/did/diddoc.py +75 -0
  42. sdata/did/ed25519.py +225 -0
  43. sdata/did/eddsa.py +107 -0
  44. sdata/did/errors.py +40 -0
  45. sdata/did/issue_vc.py +193 -0
  46. sdata/did/jose.py +126 -0
  47. sdata/did/keys.py +104 -0
  48. sdata/did/utils_didkey.py +161 -0
  49. sdata/did/verify_vp.py +158 -0
  50. sdata/doe.py +101 -0
  51. sdata/dtypes.py +510 -0
  52. sdata/experiments/__init__.py +331 -0
  53. sdata/experiments/blob0.py +131 -0
  54. sdata/experiments/file.py +42 -0
  55. sdata/experiments/filegroup.py +144 -0
  56. sdata/experiments/image_old.py +313 -0
  57. sdata/experiments/ks2.py +15 -0
  58. sdata/experiments/lapshear.py +8 -0
  59. sdata/experiments/material.py +7 -0
  60. sdata/experiments/parameters_deprecated.py +394 -0
  61. sdata/experiments/workbook.py +226 -0
  62. sdata/format.py +125 -0
  63. sdata/imagemeta.py +533 -0
  64. sdata/interactive.py +118 -0
  65. sdata/iolib/__init__.py +51 -0
  66. sdata/iolib/hashtree.py +264 -0
  67. sdata/iolib/json1sqlitestore.py +554 -0
  68. sdata/iolib/jsonsqlitestore.py +704 -0
  69. sdata/iolib/pud.py +242 -0
  70. sdata/iolib/reader.py +262 -0
  71. sdata/iolib/writer.py +419 -0
  72. sdata/metadata.py +882 -0
  73. sdata/parameter.py +414 -0
  74. sdata/schema.py +273 -0
  75. sdata/sclass/__init__.py +111 -0
  76. sdata/sclass/bfo_classes.py +362 -0
  77. sdata/sclass/blob.py +362 -0
  78. sdata/sclass/content.py +87 -0
  79. sdata/sclass/dataframe.py +1251 -0
  80. sdata/sclass/dataframegroup.py +187 -0
  81. sdata/sclass/dependency_graph.py +85 -0
  82. sdata/sclass/filereference.py +124 -0
  83. sdata/sclass/image.py +218 -0
  84. sdata/sclass/lazy_namespace.py +155 -0
  85. sdata/sclass/process.py +383 -0
  86. sdata/sclass/process_graph.py +221 -0
  87. sdata/sclass/zipfile.py +135 -0
  88. sdata/semantic.py +346 -0
  89. sdata/suuid.py +136 -0
  90. sdata/timestamp.py +336 -0
  91. sdata/tools.py +14 -0
  92. sdata/units.py +602 -0
  93. sdata/vocab.py +149 -0
  94. sdata-1.5.0.dist-info/METADATA +264 -0
  95. sdata-1.5.0.dist-info/RECORD +98 -0
  96. sdata-1.5.0.dist-info/WHEEL +5 -0
  97. sdata-1.5.0.dist-info/licenses/LICENSE-MIT +21 -0
  98. sdata-1.5.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,60 @@
1
+ import io
2
+ import struct
3
+ import sys
4
+
5
+ from ._common import *
6
+ from ._exceptions import InvalidImageDataError
7
+ from . import _webp
8
+
9
+ def insert(exif, image, new_file=None):
10
+ """
11
+ py:function:: piexif.insert(exif_bytes, filename)
12
+
13
+ Insert exif into JPEG.
14
+
15
+ :param bytes exif_bytes: Exif as bytes
16
+ :param str filename: JPEG
17
+ """
18
+ if exif[0:6] != b"\x45\x78\x69\x66\x00\x00":
19
+ raise ValueError("Given data is not exif data")
20
+
21
+ output_file = False
22
+ # Prevents "UnicodeWarning: Unicode equal comparison failed" warnings on Python 2
23
+ maybe_image = sys.version_info >= (3,0,0) or isinstance(image, str)
24
+
25
+ if maybe_image and image[0:2] == b"\xff\xd8":
26
+ image_data = image
27
+ file_type = "jpeg"
28
+ elif maybe_image and image[0:4] == b"RIFF" and image[8:12] == b"WEBP":
29
+ image_data = image
30
+ file_type = "webp"
31
+ else:
32
+ with open(image, 'rb') as f:
33
+ image_data = f.read()
34
+ if image_data[0:2] == b"\xff\xd8":
35
+ file_type = "jpeg"
36
+ elif image_data[0:4] == b"RIFF" and image_data[8:12] == b"WEBP":
37
+ file_type = "webp"
38
+ else:
39
+ raise InvalidImageDataError
40
+ output_file = True
41
+
42
+ if file_type == "jpeg":
43
+ exif = b"\xff\xe1" + struct.pack(">H", len(exif) + 2) + exif
44
+ segments = split_into_segments(image_data)
45
+ new_data = merge_segments(segments, exif)
46
+ elif file_type == "webp":
47
+ exif = exif[6:]
48
+ new_data = _webp.insert(image_data, exif)
49
+
50
+ if isinstance(new_file, io.BytesIO):
51
+ new_file.write(new_data)
52
+ new_file.seek(0)
53
+ elif new_file:
54
+ with open(new_file, "wb+") as f:
55
+ f.write(new_data)
56
+ elif output_file:
57
+ with open(image, "wb+") as f:
58
+ f.write(new_data)
59
+ else:
60
+ raise ValueError("Give a 3rd argument to 'insert' to output file")
@@ -0,0 +1,270 @@
1
+ import struct
2
+ import sys
3
+
4
+ from ._common import *
5
+ from ._exceptions import InvalidImageDataError
6
+ from ._exif import *
7
+ from . import _webp
8
+
9
+ LITTLE_ENDIAN = b"\x49\x49"
10
+
11
+
12
+ def load(input_data, key_is_name=False):
13
+ """
14
+ py:function:: piexif.load(filename)
15
+
16
+ Return exif data as dict. Keys(IFD name), be contained, are "0th", "Exif", "GPS", "Interop", "1st", and "thumbnail". Without "thumbnail", the value is dict(tag name/tag value). "thumbnail" value is JPEG as bytes.
17
+
18
+ :param str filename: JPEG or TIFF
19
+ :return: Exif data({"0th":dict, "Exif":dict, "GPS":dict, "Interop":dict, "1st":dict, "thumbnail":bytes})
20
+ :rtype: dict
21
+ """
22
+ exif_dict = {"0th":{},
23
+ "Exif":{},
24
+ "GPS":{},
25
+ "Interop":{},
26
+ "1st":{},
27
+ "thumbnail":None}
28
+ exifReader = _ExifReader(input_data)
29
+ if exifReader.tiftag is None:
30
+ return exif_dict
31
+
32
+ if exifReader.tiftag[0:2] == LITTLE_ENDIAN:
33
+ exifReader.endian_mark = "<"
34
+ else:
35
+ exifReader.endian_mark = ">"
36
+
37
+ pointer = struct.unpack(exifReader.endian_mark + "L",
38
+ exifReader.tiftag[4:8])[0]
39
+ exif_dict["0th"] = exifReader.get_ifd_dict(pointer, "0th")
40
+ first_ifd_pointer = exif_dict["0th"].pop("first_ifd_pointer")
41
+ if ImageIFD.ExifTag in exif_dict["0th"]:
42
+ pointer = exif_dict["0th"][ImageIFD.ExifTag]
43
+ exif_dict["Exif"] = exifReader.get_ifd_dict(pointer, "Exif")
44
+ if ImageIFD.GPSTag in exif_dict["0th"]:
45
+ pointer = exif_dict["0th"][ImageIFD.GPSTag]
46
+ exif_dict["GPS"] = exifReader.get_ifd_dict(pointer, "GPS")
47
+ if ExifIFD.InteroperabilityTag in exif_dict["Exif"]:
48
+ pointer = exif_dict["Exif"][ExifIFD.InteroperabilityTag]
49
+ exif_dict["Interop"] = exifReader.get_ifd_dict(pointer, "Interop")
50
+ if first_ifd_pointer != b"\x00\x00\x00\x00":
51
+ pointer = struct.unpack(exifReader.endian_mark + "L",
52
+ first_ifd_pointer)[0]
53
+ exif_dict["1st"] = exifReader.get_ifd_dict(pointer, "1st")
54
+ if (ImageIFD.JPEGInterchangeFormat in exif_dict["1st"] and
55
+ ImageIFD.JPEGInterchangeFormatLength in exif_dict["1st"]):
56
+ end = (exif_dict["1st"][ImageIFD.JPEGInterchangeFormat] +
57
+ exif_dict["1st"][ImageIFD.JPEGInterchangeFormatLength])
58
+ thumb = exifReader.tiftag[exif_dict["1st"][ImageIFD.JPEGInterchangeFormat]:end]
59
+ exif_dict["thumbnail"] = thumb
60
+
61
+ if key_is_name:
62
+ exif_dict = _get_key_name_dict(exif_dict)
63
+ return exif_dict
64
+
65
+
66
+ class _ExifReader(object):
67
+ def __init__(self, data):
68
+ # Prevents "UnicodeWarning: Unicode equal comparison failed" warnings on Python 2
69
+ maybe_image = sys.version_info >= (3,0,0) or isinstance(data, str)
70
+
71
+ if maybe_image and data[0:2] == b"\xff\xd8": # JPEG
72
+ segments = split_into_segments(data)
73
+ app1 = get_exif_seg(segments)
74
+ if app1:
75
+ self.tiftag = app1[10:]
76
+ else:
77
+ self.tiftag = None
78
+ elif maybe_image and data[0:2] in (b"\x49\x49", b"\x4d\x4d"): # TIFF
79
+ self.tiftag = data
80
+ elif maybe_image and data[0:4] == b"RIFF" and data[8:12] == b"WEBP":
81
+ self.tiftag = _webp.get_exif(data)
82
+ elif maybe_image and data[0:4] == b"Exif": # Exif
83
+ self.tiftag = data[6:]
84
+ else:
85
+ with open(data, 'rb') as f:
86
+ magic_number = f.read(2)
87
+ if magic_number == b"\xff\xd8": # JPEG
88
+ app1 = read_exif_from_file(data)
89
+ if app1:
90
+ self.tiftag = app1[10:]
91
+ else:
92
+ self.tiftag = None
93
+ elif magic_number in (b"\x49\x49", b"\x4d\x4d"): # TIFF
94
+ with open(data, 'rb') as f:
95
+ self.tiftag = f.read()
96
+ else:
97
+ with open(data, 'rb') as f:
98
+ header = f.read(12)
99
+ if header[0:4] == b"RIFF"and header[8:12] == b"WEBP":
100
+ with open(data, 'rb') as f:
101
+ file_data = f.read()
102
+ self.tiftag = _webp.get_exif(file_data)
103
+ else:
104
+ raise InvalidImageDataError("Given file is neither JPEG nor TIFF.")
105
+
106
+ def get_ifd_dict(self, pointer, ifd_name, read_unknown=False):
107
+ ifd_dict = {}
108
+ tag_count = struct.unpack(self.endian_mark + "H",
109
+ self.tiftag[pointer: pointer+2])[0]
110
+ offset = pointer + 2
111
+ if ifd_name in ["0th", "1st"]:
112
+ t = "Image"
113
+ else:
114
+ t = ifd_name
115
+ p_and_value = []
116
+ for x in range(tag_count):
117
+ pointer = offset + 12 * x
118
+ tag = struct.unpack(self.endian_mark + "H",
119
+ self.tiftag[pointer: pointer+2])[0]
120
+ value_type = struct.unpack(self.endian_mark + "H",
121
+ self.tiftag[pointer + 2: pointer + 4])[0]
122
+ value_num = struct.unpack(self.endian_mark + "L",
123
+ self.tiftag[pointer + 4: pointer + 8]
124
+ )[0]
125
+ value = self.tiftag[pointer+8: pointer+12]
126
+ p_and_value.append((pointer, value_type, value_num, value))
127
+ v_set = (value_type, value_num, value, tag)
128
+ if tag in TAGS[t]:
129
+ ifd_dict[tag] = self.convert_value(v_set)
130
+ elif read_unknown:
131
+ ifd_dict[tag] = (v_set[0], v_set[1], v_set[2], self.tiftag)
132
+ #else:
133
+ # pass
134
+
135
+ if ifd_name == "0th":
136
+ pointer = offset + 12 * tag_count
137
+ ifd_dict["first_ifd_pointer"] = self.tiftag[pointer:pointer + 4]
138
+ return ifd_dict
139
+
140
+ def convert_value(self, val):
141
+ data = None
142
+ t = val[0]
143
+ length = val[1]
144
+ value = val[2]
145
+
146
+ if t == TYPES.Byte: # BYTE
147
+ if length > 4:
148
+ pointer = struct.unpack(self.endian_mark + "L", value)[0]
149
+ data = struct.unpack("B" * length,
150
+ self.tiftag[pointer: pointer + length])
151
+ else:
152
+ data = struct.unpack("B" * length, value[0:length])
153
+ elif t == TYPES.Ascii: # ASCII
154
+ if length > 4:
155
+ pointer = struct.unpack(self.endian_mark + "L", value)[0]
156
+ data = self.tiftag[pointer: pointer+length - 1]
157
+ else:
158
+ data = value[0: length - 1]
159
+ elif t == TYPES.Short: # SHORT
160
+ if length > 2:
161
+ pointer = struct.unpack(self.endian_mark + "L", value)[0]
162
+ data = struct.unpack(self.endian_mark + "H" * length,
163
+ self.tiftag[pointer: pointer+length*2])
164
+ else:
165
+ data = struct.unpack(self.endian_mark + "H" * length,
166
+ value[0:length * 2])
167
+ elif t == TYPES.Long: # LONG
168
+ if length > 1:
169
+ pointer = struct.unpack(self.endian_mark + "L", value)[0]
170
+ data = struct.unpack(self.endian_mark + "L" * length,
171
+ self.tiftag[pointer: pointer+length*4])
172
+ else:
173
+ data = struct.unpack(self.endian_mark + "L" * length,
174
+ value)
175
+ elif t == TYPES.Rational: # RATIONAL
176
+ pointer = struct.unpack(self.endian_mark + "L", value)[0]
177
+ if length > 1:
178
+ data = tuple(
179
+ (struct.unpack(self.endian_mark + "L",
180
+ self.tiftag[pointer + x * 8:
181
+ pointer + 4 + x * 8])[0],
182
+ struct.unpack(self.endian_mark + "L",
183
+ self.tiftag[pointer + 4 + x * 8:
184
+ pointer + 8 + x * 8])[0])
185
+ for x in range(length)
186
+ )
187
+ else:
188
+ data = (struct.unpack(self.endian_mark + "L",
189
+ self.tiftag[pointer: pointer + 4])[0],
190
+ struct.unpack(self.endian_mark + "L",
191
+ self.tiftag[pointer + 4: pointer + 8]
192
+ )[0])
193
+ elif t == TYPES.SByte: # SIGNED BYTES
194
+ if length > 4:
195
+ pointer = struct.unpack(self.endian_mark + "L", value)[0]
196
+ data = struct.unpack("b" * length,
197
+ self.tiftag[pointer: pointer + length])
198
+ else:
199
+ data = struct.unpack("b" * length, value[0:length])
200
+ elif t == TYPES.Undefined: # UNDEFINED BYTES
201
+ if length > 4:
202
+ pointer = struct.unpack(self.endian_mark + "L", value)[0]
203
+ data = self.tiftag[pointer: pointer+length]
204
+ else:
205
+ data = value[0:length]
206
+ elif t == TYPES.SShort: # SIGNED SHORT
207
+ if length > 2:
208
+ pointer = struct.unpack(self.endian_mark + "L", value)[0]
209
+ data = struct.unpack(self.endian_mark + "h" * length,
210
+ self.tiftag[pointer: pointer+length*2])
211
+ else:
212
+ data = struct.unpack(self.endian_mark + "h" * length,
213
+ value[0:length * 2])
214
+ elif t == TYPES.SLong: # SLONG
215
+ if length > 1:
216
+ pointer = struct.unpack(self.endian_mark + "L", value)[0]
217
+ data = struct.unpack(self.endian_mark + "l" * length,
218
+ self.tiftag[pointer: pointer+length*4])
219
+ else:
220
+ data = struct.unpack(self.endian_mark + "l" * length,
221
+ value)
222
+ elif t == TYPES.SRational: # SRATIONAL
223
+ pointer = struct.unpack(self.endian_mark + "L", value)[0]
224
+ if length > 1:
225
+ data = tuple(
226
+ (struct.unpack(self.endian_mark + "l",
227
+ self.tiftag[pointer + x * 8: pointer + 4 + x * 8])[0],
228
+ struct.unpack(self.endian_mark + "l",
229
+ self.tiftag[pointer + 4 + x * 8: pointer + 8 + x * 8])[0])
230
+ for x in range(length)
231
+ )
232
+ else:
233
+ data = (struct.unpack(self.endian_mark + "l",
234
+ self.tiftag[pointer: pointer + 4])[0],
235
+ struct.unpack(self.endian_mark + "l",
236
+ self.tiftag[pointer + 4: pointer + 8]
237
+ )[0])
238
+ elif t == TYPES.Float: # FLOAT
239
+ if length > 1:
240
+ pointer = struct.unpack(self.endian_mark + "L", value)[0]
241
+ data = struct.unpack(self.endian_mark + "f" * length,
242
+ self.tiftag[pointer: pointer+length*4])
243
+ else:
244
+ data = struct.unpack(self.endian_mark + "f" * length,
245
+ value)
246
+ elif t == TYPES.DFloat: # DOUBLE
247
+ pointer = struct.unpack(self.endian_mark + "L", value)[0]
248
+ data = struct.unpack(self.endian_mark + "d" * length,
249
+ self.tiftag[pointer: pointer+length*8])
250
+ else:
251
+ raise ValueError("Exif might be wrong. Got incorrect value " +
252
+ "type to decode.\n" +
253
+ "tag: " + str(val[3]) + "\ntype: " + str(t))
254
+
255
+ if isinstance(data, tuple) and (len(data) == 1):
256
+ return data[0]
257
+ else:
258
+ return data
259
+
260
+
261
+ def _get_key_name_dict(exif_dict):
262
+ new_dict = {
263
+ "0th":{TAGS["Image"][n]["name"]:value for n, value in exif_dict["0th"].items()},
264
+ "Exif":{TAGS["Exif"][n]["name"]:value for n, value in exif_dict["Exif"].items()},
265
+ "1st":{TAGS["Image"][n]["name"]:value for n, value in exif_dict["1st"].items()},
266
+ "GPS":{TAGS["GPS"][n]["name"]:value for n, value in exif_dict["GPS"].items()},
267
+ "Interop":{TAGS["Interop"][n]["name"]:value for n, value in exif_dict["Interop"].items()},
268
+ "thumbnail":exif_dict["thumbnail"],
269
+ }
270
+ return new_dict
@@ -0,0 +1,56 @@
1
+ import io
2
+
3
+ from ._common import *
4
+ from . import _webp
5
+
6
+ def remove(src, new_file=None):
7
+ """
8
+ py:function:: piexif.remove(filename)
9
+
10
+ Remove exif from JPEG.
11
+
12
+ :param str filename: JPEG
13
+ """
14
+ output_is_file = False
15
+ if src[0:2] == b"\xff\xd8":
16
+ src_data = src
17
+ file_type = "jpeg"
18
+ elif src[0:4] == b"RIFF" and src[8:12] == b"WEBP":
19
+ src_data = src
20
+ file_type = "webp"
21
+ else:
22
+ with open(src, 'rb') as f:
23
+ src_data = f.read()
24
+ output_is_file = True
25
+ if src_data[0:2] == b"\xff\xd8":
26
+ file_type = "jpeg"
27
+ elif src_data[0:4] == b"RIFF" and src_data[8:12] == b"WEBP":
28
+ file_type = "webp"
29
+
30
+ if file_type == "jpeg":
31
+ segments = split_into_segments(src_data)
32
+ exif = get_exif_seg(segments)
33
+ if exif:
34
+ new_data = src_data.replace(exif, b"")
35
+ else:
36
+ new_data = src_data
37
+ elif file_type == "webp":
38
+ try:
39
+ new_data = _webp.remove(src_data)
40
+ except ValueError:
41
+ new_data = src_data
42
+ except e:
43
+ print(e.args)
44
+ raise ValueError("Error occurred.")
45
+
46
+ if isinstance(new_file, io.BytesIO):
47
+ new_file.write(new_data)
48
+ new_file.seek(0)
49
+ elif new_file:
50
+ with open(new_file, "wb+") as f:
51
+ f.write(new_data)
52
+ elif output_is_file:
53
+ with open(src, "wb+") as f:
54
+ f.write(new_data)
55
+ else:
56
+ raise ValueError("Give a second argument to 'remove' to output file")
@@ -0,0 +1,45 @@
1
+ import io
2
+
3
+ from ._common import *
4
+
5
+
6
+ def transplant(exif_src, image, new_file=None):
7
+ """
8
+ py:function:: piexif.transplant(filename1, filename2)
9
+
10
+ Transplant exif from filename1 to filename2.
11
+
12
+ :param str filename1: JPEG
13
+ :param str filename2: JPEG
14
+ """
15
+ if exif_src[0:2] == b"\xff\xd8":
16
+ src_data = exif_src
17
+ else:
18
+ with open(exif_src, 'rb') as f:
19
+ src_data = f.read()
20
+ segments = split_into_segments(src_data)
21
+ exif = get_exif_seg(segments)
22
+ if exif is None:
23
+ raise ValueError("not found exif in input")
24
+
25
+ output_file = False
26
+ if image[0:2] == b"\xff\xd8":
27
+ image_data = image
28
+ else:
29
+ with open(image, 'rb') as f:
30
+ image_data = f.read()
31
+ output_file = True
32
+ segments = split_into_segments(image_data)
33
+ new_data = merge_segments(segments, exif)
34
+
35
+ if isinstance(new_file, io.BytesIO):
36
+ new_file.write(new_data)
37
+ new_file.seek(0)
38
+ elif new_file:
39
+ with open(new_file, "wb+") as f:
40
+ f.write(new_data)
41
+ elif output_file:
42
+ with open(image, "wb+") as f:
43
+ f.write(new_data)
44
+ else:
45
+ raise ValueError("Give a 3rd argument to 'transplant' to output file")
@@ -0,0 +1,240 @@
1
+
2
+ import struct
3
+
4
+
5
+ def split(data):
6
+ if data[0:4] != b"RIFF" or data[8:12] != b"WEBP":
7
+ raise ValueError("Not WebP")
8
+
9
+ webp_length_bytes = data[4:8]
10
+ webp_length = struct.unpack("<L", webp_length_bytes)[0]
11
+ RIFF_HEADER_SIZE = 8
12
+ file_size = RIFF_HEADER_SIZE + webp_length
13
+
14
+ start = 12
15
+ pointer = start
16
+ CHUNK_FOURCC_LENGTH = 4
17
+ LENGTH_BYTES_LENGTH = 4
18
+
19
+ chunks = []
20
+ while pointer + CHUNK_FOURCC_LENGTH + LENGTH_BYTES_LENGTH < file_size:
21
+ fourcc = data[pointer:pointer + CHUNK_FOURCC_LENGTH]
22
+ pointer += CHUNK_FOURCC_LENGTH
23
+ chunk_length_bytes = data[pointer:pointer + LENGTH_BYTES_LENGTH]
24
+ chunk_length = struct.unpack("<L", chunk_length_bytes)[0]
25
+ pointer += LENGTH_BYTES_LENGTH
26
+
27
+ chunk_data = data[pointer:pointer + chunk_length]
28
+ chunks.append({"fourcc":fourcc, "length_bytes":chunk_length_bytes, "data":chunk_data})
29
+
30
+ padding = 1 if chunk_length % 2 else 0
31
+
32
+ pointer += chunk_length + padding
33
+ return chunks
34
+
35
+ def merge_chunks(chunks):
36
+ merged = b"".join([chunk["fourcc"]
37
+ + chunk["length_bytes"]
38
+ + chunk["data"]
39
+ + (len(chunk["data"]) % 2) * b"\x00"
40
+ for chunk in chunks])
41
+ return merged
42
+
43
+
44
+ def _get_size_from_vp8x(chunk):
45
+ width_minus_one_bytes = chunk["data"][-6:-3] + b"\x00"
46
+ width_minus_one = struct.unpack("<L", width_minus_one_bytes)[0]
47
+ width = width_minus_one + 1
48
+ height_minus_one_bytes = chunk["data"][-3:] + b"\x00"
49
+ height_minus_one = struct.unpack("<L", height_minus_one_bytes)[0]
50
+ height = height_minus_one + 1
51
+ return (width, height)
52
+
53
+ def _get_size_from_vp8(chunk):
54
+ BEGIN_CODE = b"\x9d\x01\x2a"
55
+ begin_index = chunk["data"].find(BEGIN_CODE)
56
+ if begin_index == -1:
57
+ ValueError("wrong VP8")
58
+ else:
59
+ BEGIN_CODE_LENGTH = len(BEGIN_CODE)
60
+ LENGTH_BYTES_LENGTH = 2
61
+ length_start = begin_index + BEGIN_CODE_LENGTH
62
+ width_bytes = chunk["data"][length_start:length_start + LENGTH_BYTES_LENGTH]
63
+ width = struct.unpack("<H", width_bytes)[0]
64
+ height_bytes = chunk["data"][length_start + LENGTH_BYTES_LENGTH:length_start + 2 * LENGTH_BYTES_LENGTH]
65
+ height = struct.unpack("<H", height_bytes)[0]
66
+ return (width, height)
67
+
68
+ def _vp8L_contains_alpha(chunk_data):
69
+ flag = ord(chunk_data[4:5]) >> 5-1 & ord(b"\x01")
70
+ contains = 1 * flag
71
+ return contains
72
+
73
+ def _get_size_from_vp8L(chunk):
74
+ b1 = chunk["data"][1:2]
75
+ b2 = chunk["data"][2:3]
76
+ b3 = chunk["data"][3:4]
77
+ b4 = chunk["data"][4:5]
78
+
79
+ width_minus_one = (ord(b2) & ord(b"\x3F")) << 8 | ord(b1)
80
+ width = width_minus_one + 1
81
+
82
+ height_minus_one = (ord(b4) & ord(b"\x0F")) << 10 | ord(b3) << 2 | (ord(b2) & ord(b"\xC0")) >> 6
83
+ height = height_minus_one + 1
84
+
85
+ return (width, height)
86
+
87
+ def _get_size_from_anmf(chunk):
88
+ width_minus_one_bytes = chunk["data"][6:9] + b"\x00"
89
+ width_minus_one = struct.unpack("<L", width_minus_one_bytes)[0]
90
+ width = width_minus_one + 1
91
+ height_minus_one_bytes = chunk["data"][9:12] + b"\x00"
92
+ height_minus_one = struct.unpack("<L", height_minus_one_bytes)[0]
93
+ height = height_minus_one + 1
94
+ return (width, height)
95
+
96
+ def set_vp8x(chunks):
97
+
98
+ width = None
99
+ height = None
100
+ flags = [b"0", b"0", b"0", b"0", b"0", b"0", b"0", b"0"] # [0, 0, ICC, Alpha, EXIF, XMP, Anim, 0]
101
+
102
+ for chunk in chunks:
103
+ if chunk["fourcc"] == b"VP8X":
104
+ width, height = _get_size_from_vp8x(chunk)
105
+ elif chunk["fourcc"] == b"VP8 ":
106
+ width, height = _get_size_from_vp8(chunk)
107
+ elif chunk["fourcc"] == b"VP8L":
108
+ is_rgba = _vp8L_contains_alpha(chunk["data"])
109
+ if is_rgba:
110
+ flags[3] = b"1"
111
+ width, height = _get_size_from_vp8L(chunk)
112
+ elif chunk["fourcc"] == b"ANMF":
113
+ width, height = _get_size_from_anmf(chunk)
114
+ elif chunk["fourcc"] == b"ICCP":
115
+ flags[2] = b"1"
116
+ elif chunk["fourcc"] == b"ALPH":
117
+ flags[3] = b"1"
118
+ elif chunk["fourcc"] == b"EXIF":
119
+ flags[4] = b"1"
120
+ elif chunk["fourcc"] == b"XMP ":
121
+ flags[5] = b"1"
122
+ elif chunk["fourcc"] == b"ANIM":
123
+ flags[6] = b"1"
124
+ width_minus_one = width - 1
125
+ height_minus_one = height - 1
126
+
127
+ if chunks[0]["fourcc"] == b"VP8X":
128
+ chunks.pop(0)
129
+
130
+ header_bytes = b"VP8X"
131
+ length_bytes = b"\x0a\x00\x00\x00"
132
+ flags_bytes = struct.pack("B", int(b"".join(flags), 2))
133
+ padding_bytes = b"\x00\x00\x00"
134
+ width_bytes = struct.pack("<L", width_minus_one)[:3]
135
+ height_bytes = struct.pack("<L", height_minus_one)[:3]
136
+
137
+ data_bytes = flags_bytes + padding_bytes + width_bytes + height_bytes
138
+
139
+ vp8x_chunk = {"fourcc":header_bytes, "length_bytes":length_bytes, "data":data_bytes}
140
+ chunks.insert(0, vp8x_chunk)
141
+
142
+ return chunks
143
+
144
+ def get_file_header(chunks):
145
+ WEBP_HEADER_LENGTH = 4
146
+ FOURCC_LENGTH = 4
147
+ LENGTH_BYTES_LENGTH = 4
148
+
149
+ length = WEBP_HEADER_LENGTH
150
+ for chunk in chunks:
151
+ data_length = struct.unpack("<L", chunk["length_bytes"])[0]
152
+ data_length += 1 if data_length % 2 else 0
153
+ length += FOURCC_LENGTH + LENGTH_BYTES_LENGTH + data_length
154
+ length_bytes = struct.pack("<L", length)
155
+ riff = b"RIFF"
156
+ webp_header = b"WEBP"
157
+ file_header = riff + length_bytes + webp_header
158
+ return file_header
159
+
160
+
161
+
162
+ def get_exif(data):
163
+ if data[0:4] != b"RIFF" or data[8:12] != b"WEBP":
164
+ raise ValueError("Not WebP")
165
+
166
+ if data[12:16] != b"VP8X":
167
+ raise ValueError("doesnot have exif")
168
+
169
+ webp_length_bytes = data[4:8]
170
+ webp_length = struct.unpack("<L", webp_length_bytes)[0]
171
+ RIFF_HEADER_SIZE = 8
172
+ file_size = RIFF_HEADER_SIZE + webp_length
173
+
174
+ start = 12
175
+ pointer = start
176
+ CHUNK_FOURCC_LENGTH = 4
177
+ LENGTH_BYTES_LENGTH = 4
178
+
179
+ chunks = []
180
+ exif = b""
181
+ while pointer < file_size:
182
+ fourcc = data[pointer:pointer + CHUNK_FOURCC_LENGTH]
183
+ pointer += CHUNK_FOURCC_LENGTH
184
+ chunk_length_bytes = data[pointer:pointer + LENGTH_BYTES_LENGTH]
185
+ chunk_length = struct.unpack("<L", chunk_length_bytes)[0]
186
+ if chunk_length % 2:
187
+ chunk_length += 1
188
+ pointer += LENGTH_BYTES_LENGTH
189
+ if fourcc == b"EXIF":
190
+ return data[pointer:pointer + chunk_length]
191
+ pointer += chunk_length
192
+ return None # if there isn't exif, return None.
193
+
194
+
195
+ def insert_exif_into_chunks(chunks, exif_bytes):
196
+ EXIF_HEADER = b"EXIF"
197
+ exif_length_bytes = struct.pack("<L", len(exif_bytes))
198
+ exif_chunk = {"fourcc":EXIF_HEADER, "length_bytes":exif_length_bytes, "data":exif_bytes}
199
+
200
+ xmp_index = None
201
+ animation_index = None
202
+
203
+ for index, chunk in enumerate(chunks):
204
+ if chunk["fourcc"] == b"EXIF":
205
+ chunks.pop(index)
206
+
207
+ for index, chunk in enumerate(chunks):
208
+ if chunk["fourcc"] == b"XMP ":
209
+ xmp_index = index
210
+ elif chunk["fourcc"] == b"ANIM":
211
+ animation_index = index
212
+ if xmp_index is not None:
213
+ chunks.insert(xmp_index, exif_chunk)
214
+ elif animation_index is not None:
215
+ chunks.insert(animation_index, exif_chunk)
216
+ else:
217
+ chunks.append(exif_chunk)
218
+ return chunks
219
+
220
+
221
+ def insert(webp_bytes, exif_bytes):
222
+ chunks = split(webp_bytes)
223
+ chunks = insert_exif_into_chunks(chunks, exif_bytes)
224
+ chunks = set_vp8x(chunks)
225
+ file_header = get_file_header(chunks)
226
+ merged = merge_chunks(chunks)
227
+ new_webp_bytes = file_header + merged
228
+ return new_webp_bytes
229
+
230
+
231
+ def remove(webp_bytes):
232
+ chunks = split(webp_bytes)
233
+ for index, chunk in enumerate(chunks):
234
+ if chunk["fourcc"] == b"EXIF":
235
+ chunks.pop(index)
236
+ chunks = set_vp8x(chunks)
237
+ file_header = get_file_header(chunks)
238
+ merged = merge_chunks(chunks)
239
+ new_webp_bytes = file_header + merged
240
+ return new_webp_bytes