nt25 0.1.4__py3-none-any.whl → 0.1.5__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.
nt25/__init__.py CHANGED
@@ -1,9 +1,10 @@
1
1
  import importlib.metadata as meta
2
2
 
3
- from .lib import fio, calc, draw
3
+ from .lib import fio, calc, draw, et
4
4
  from .lib.draw import DType
5
5
 
6
6
  __version__ = meta.version(str(__package__))
7
7
  __data_path__ = __file__.replace('__init__.py', 'data')
8
8
 
9
- __all__ = ('__version__', '__data_path__', 'fio', 'calc', 'draw', 'DType')
9
+ __all__ = ('__version__', '__data_path__',
10
+ 'fio', 'calc', 'draw', 'DType', 'et')
@@ -1,5 +1,5 @@
1
1
  import os
2
- from nt25 import fio, calc, draw, DType, __version__, __data_path__
2
+ from nt25 import fio, calc, draw, DType, et, __version__, __data_path__
3
3
 
4
4
  # import timeit
5
5
  # timeit.timeit('', number=100, globals=globals())
@@ -70,7 +70,10 @@ def main():
70
70
  bar = calc.solveEq(foo['eq'], output=True)
71
71
 
72
72
  if len(bar) > 0:
73
- print(f'solveEq(750, 1.5) ~ {bar[0]['func'](y=[750], x1=1.5):.4f}')
73
+ print(f'solveEq(750, 1.5) ~ {bar[0]['func'](y=[750], x1=1.5):.4f}\n')
74
+
75
+ exif = et.parseExif(__data_path__ + '/exif.jpg')
76
+ print('exif:', exif)
74
77
 
75
78
 
76
79
  if __name__ == "__main__":
nt25/lib/et.py CHANGED
@@ -1,16 +1,14 @@
1
+ import io
1
2
  import time
2
3
  import json
4
+ import struct
3
5
  import argparse
4
6
 
5
7
  from datetime import UTC, datetime, timedelta, timezone
6
- from exif import Image
7
-
8
- VERSION = "0.1.1"
8
+ from exif import Image, DATETIME_STR_FORMAT
9
9
 
10
+ VERSION = "0.1.2"
10
11
  EPOCH = datetime.fromtimestamp(0, UTC)
11
- IGNORE = ['orientation', 'maker_note', '_interoperability_ifd_Pointer',
12
- 'components_configuration', 'scene_type', 'flashpix_version',
13
- 'gps_processing_method',]
14
12
 
15
13
 
16
14
  def dms2dec(dms: tuple):
@@ -19,11 +17,11 @@ def dms2dec(dms: tuple):
19
17
 
20
18
 
21
19
  def dtFormatter(str):
22
- return datetime.strptime(str, '%Y:%m:%d %H:%M:%S')
20
+ return datetime.strptime(str, DATETIME_STR_FORMAT)
23
21
 
24
22
 
25
23
  def dt2str(dt):
26
- return None if dt is None else dt.strftime('%Y-%m-%d %H:%M:%S')
24
+ return None if dt is None else dt.strftime(DATETIME_STR_FORMAT)
27
25
 
28
26
 
29
27
  def gpsDt2Dt(date, time, offset=8):
@@ -73,13 +71,6 @@ def parseExif(file):
73
71
  create = tryGet(img, 'datetime_original', None)
74
72
  modify = tryGet(img, 'datetime', None)
75
73
 
76
- # da = []
77
- # for d in (d1, d2, d3):
78
- # if d is not None:
79
- # print(d)
80
- # da.append(dtFormatter(d))
81
- # dt = None if len(da) == 0 else max(da)
82
-
83
74
  createDt = None if create is None else dtFormatter(create)
84
75
  modifyDt = None if modify is None else dtFormatter(modify)
85
76
 
@@ -117,11 +108,202 @@ def parseExif(file):
117
108
  }
118
109
 
119
110
 
111
+ class InvalidImageDataError(ValueError):
112
+ pass
113
+
114
+
115
+ def split_into_segments(data):
116
+ """Slices JPEG meta data into a list from JPEG binary data.
117
+ """
118
+ if data[0:2] != b"\xff\xd8":
119
+ raise InvalidImageDataError("Given data isn't JPEG.")
120
+
121
+ head = 2
122
+ segments = [b"\xff\xd8"]
123
+ while 1:
124
+ if data[head: head + 2] == b"\xff\xda":
125
+ segments.append(data[head:])
126
+ break
127
+ else:
128
+ length = struct.unpack(">H", data[head + 2: head + 4])[0]
129
+ endPoint = head + length + 2
130
+ seg = data[head: endPoint]
131
+ segments.append(seg)
132
+ head = endPoint
133
+
134
+ if (head >= len(data)):
135
+ raise InvalidImageDataError("Wrong JPEG data.")
136
+
137
+ return segments
138
+
139
+
140
+ def read_exif_from_file(filename):
141
+ """Slices JPEG meta data into a list from JPEG binary data.
142
+ """
143
+ f = open(filename, "rb")
144
+ data = f.read(6)
145
+
146
+ if data[0:2] != b"\xff\xd8":
147
+ raise InvalidImageDataError("Given data isn't JPEG.")
148
+
149
+ head = data[2:6]
150
+ HEAD_LENGTH = 4
151
+ exif = None
152
+ while len(head) == HEAD_LENGTH:
153
+ length = struct.unpack(">H", head[2: 4])[0]
154
+
155
+ if head[:2] == b"\xff\xe1":
156
+ segment_data = f.read(length - 2)
157
+ if segment_data[:4] != b'Exif':
158
+ head = f.read(HEAD_LENGTH)
159
+ continue
160
+ exif = head + segment_data
161
+ break
162
+ elif head[0:1] == b"\xff":
163
+ f.read(length - 2)
164
+ head = f.read(HEAD_LENGTH)
165
+ else:
166
+ break
167
+
168
+ f.close()
169
+ return exif
170
+
171
+
172
+ def get_exif_seg(segments):
173
+ """Returns Exif from JPEG meta data list
174
+ """
175
+ for seg in segments:
176
+ if seg[0:2] == b"\xff\xe1" and seg[4:10] == b"Exif\x00\x00":
177
+ return seg
178
+
179
+ return None
180
+
181
+
182
+ def merge_segments(segments, exif=b""):
183
+ """Merges Exif with APP0 and APP1 manipulations.
184
+ """
185
+ if segments[1][0:2] == b"\xff\xe0" and \
186
+ segments[2][0:2] == b"\xff\xe1" and \
187
+ segments[2][4:10] == b"Exif\x00\x00":
188
+ if exif:
189
+ segments[2] = exif
190
+ segments.pop(1)
191
+ elif exif is None:
192
+ segments.pop(2)
193
+ else:
194
+ segments.pop(1)
195
+
196
+ elif segments[1][0:2] == b"\xff\xe0":
197
+ if exif:
198
+ segments[1] = exif
199
+
200
+ elif (segments[1][0:2] == b"\xff\xe1" and
201
+ segments[1][4:10] == b"Exif\x00\x00"):
202
+
203
+ if exif:
204
+ segments[1] = exif
205
+ elif exif is None:
206
+ segments.pop(1)
207
+
208
+ else:
209
+ if exif:
210
+ segments.insert(1, exif)
211
+
212
+ return b"".join(segments)
213
+
214
+
215
+ def remove(src, new_file=None):
216
+ """
217
+ py:function:: piexif.remove(filename)
218
+
219
+ Remove exif from JPEG.
220
+
221
+ :param str filename: JPEG
222
+ """
223
+ output_is_file = False
224
+ if src[0:2] == b"\xff\xd8":
225
+ src_data = src
226
+ file_type = "jpeg"
227
+ else:
228
+ with open(src, 'rb') as f:
229
+ src_data = f.read()
230
+ output_is_file = True
231
+ if src_data[0:2] == b"\xff\xd8":
232
+ file_type = "jpeg"
233
+
234
+ if file_type == "jpeg":
235
+ segments = split_into_segments(src_data)
236
+ exif = get_exif_seg(segments)
237
+ if exif:
238
+ new_data = src_data.replace(exif, b"")
239
+ else:
240
+ new_data = src_data
241
+
242
+ if isinstance(new_file, io.BytesIO):
243
+ new_file.write(new_data)
244
+ new_file.seek(0)
245
+ elif new_file:
246
+ with open(new_file, "wb+") as f:
247
+ f.write(new_data)
248
+ elif output_is_file:
249
+ with open(src, "wb+") as f:
250
+ f.write(new_data)
251
+ else:
252
+ raise ValueError("Give a second argument to 'remove' to output file")
253
+
254
+
255
+ def transplant(exif_src, image, new_file=None):
256
+ """
257
+ py:function:: piexif.transplant(filename1, filename2)
258
+
259
+ Transplant exif from filename1 to filename2.
260
+
261
+ :param str filename1: JPEG
262
+ :param str filename2: JPEG
263
+ """
264
+ if exif_src[0:2] == b"\xff\xd8":
265
+ src_data = exif_src
266
+ else:
267
+ with open(exif_src, 'rb') as f:
268
+ src_data = f.read()
269
+
270
+ segments = split_into_segments(src_data)
271
+ exif = get_exif_seg(segments)
272
+
273
+ if exif is None:
274
+ raise ValueError("not found exif in input")
275
+
276
+ output_file = False
277
+ if image[0:2] == b"\xff\xd8":
278
+ image_data = image
279
+ else:
280
+ with open(image, 'rb') as f:
281
+ image_data = f.read()
282
+ output_file = True
283
+
284
+ segments = split_into_segments(image_data)
285
+ new_data = merge_segments(segments, exif)
286
+
287
+ if isinstance(new_file, io.BytesIO):
288
+ new_file.write(new_data)
289
+ new_file.seek(0)
290
+ elif new_file:
291
+ with open(new_file, "wb+") as f:
292
+ f.write(new_data)
293
+ elif output_file:
294
+ with open(image, "wb+") as f:
295
+ f.write(new_data)
296
+ else:
297
+ raise ValueError("Give a 3rd argument to 'transplant' to output file")
298
+
299
+
120
300
  def main():
121
301
  parser = argparse.ArgumentParser(description="EXIF tool")
122
302
  parser.add_argument('-v', '--version',
123
303
  help='echo version', action='store_true')
124
304
  parser.add_argument('-d', '--dump', help='dump meta', action='store_true')
305
+ parser.add_argument('-r', '--rm', help='remove meta', action='store_true')
306
+ parser.add_argument('-c', '--copy', type=str, help='copy meta')
125
307
  parser.add_argument('-f', '--file', type=str, help='image file')
126
308
 
127
309
  args = parser.parse_args()
@@ -131,11 +313,20 @@ def main():
131
313
  return
132
314
 
133
315
  if args.file is None:
134
- print("usage: et [-h] [-v] [-d] [-f FILE]")
316
+ print("usage: et [-h] [-v] [-r FILE] [-c FILE] [-d] [-f FILE]")
135
317
  return
136
318
 
137
- r = dumpExif(args.file) if args.dump else parseExif(args.file)
138
- print(json.dumps(r, indent=2, sort_keys=False))
319
+ if args.dump:
320
+ r = dumpExif(args.file)
321
+ elif args.rm:
322
+ r = remove(args.file)
323
+ elif args.copy:
324
+ r = transplant(args.copy, args.file)
325
+ else:
326
+ r = parseExif(args.file)
327
+
328
+ if r is not None:
329
+ print(json.dumps(r, indent=2, sort_keys=False))
139
330
 
140
331
 
141
332
  if __name__ == "__main__":
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nt25
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: Neo's Tools of Python
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: exif>=1.6.1
7
7
  Requires-Dist: matplotlib>=3.10.6
8
8
  Requires-Dist: openpyxl>=3.1.5
9
9
  Requires-Dist: pandas>=2.3.2
10
+ Requires-Dist: piexif>=1.1.3
10
11
  Requires-Dist: pyinstaller>=6.15.0
11
12
  Requires-Dist: scikit-learn>=1.7.1
12
13
  Requires-Dist: sympy>=1.14.0
@@ -32,7 +33,7 @@ Neo's Tools of Python in 2025
32
33
  ```sh
33
34
  uv init
34
35
  # fio, calc, draw basic demo
35
- uv run nt25
36
+ uv run demo
36
37
  # et
37
38
  uv run et
38
39
  ```
@@ -0,0 +1,11 @@
1
+ nt25/__init__.py,sha256=28wWlyuyScDrZx9ytGM_TxUipk5dg4pOyp_xyj6-NWk,295
2
+ nt25/demo.py,sha256=W34-7KTYVpBYXg0UrgmkVCRWAdO5RAc1K7zS9pz-BEQ,2179
3
+ nt25/data/test.xlsx,sha256=7C0JDS-TLm_KmjnKtfeajkpwGKSUhcLdr2W2UFUxAgM,10542
4
+ nt25/lib/calc.py,sha256=3X3k9jisSjRP7OokSdKvoVo4IIOzk2efexW8z1gMo-w,2265
5
+ nt25/lib/draw.py,sha256=OKTlkkNVUz_LGBA9Gk7fjcnbbbl7e_hT8nWKkcfeg2k,5642
6
+ nt25/lib/et.py,sha256=BWg4dl1EuDA6pKQbWqxSY_OE5ya7l8vvGsMb7SGDpaM,7933
7
+ nt25/lib/fio.py,sha256=WvHpG6QYR1NE19Ss3Sy2FdajTxibX5SVW3PyC5Y5Krk,2525
8
+ nt25-0.1.5.dist-info/METADATA,sha256=NbRanYnyhkstfCxQIS-nxf2f1E4VdI_QZJhd0TBmmeM,683
9
+ nt25-0.1.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ nt25-0.1.5.dist-info/entry_points.txt,sha256=mtt7YI92CecNLeMvyM3o5IyVzRpYSqqwmUgAzldhFH8,62
11
+ nt25-0.1.5.dist-info/RECORD,,
@@ -1,3 +1,3 @@
1
1
  [console_scripts]
2
+ demo = nt25.demo:main
2
3
  et = nt25.lib.et:main
3
- nt25 = nt25.main:main
@@ -1,11 +0,0 @@
1
- nt25/__init__.py,sha256=oIVhU2wegSHArUmypHr3nXQa8RRtH5TBZgRdRNN-OEw,274
2
- nt25/main.py,sha256=nVzuGx8PaxDU-QbLmVtAvDkfEFX5Z5g0xE0CWf9EwBA,2098
3
- nt25/data/test.xlsx,sha256=7C0JDS-TLm_KmjnKtfeajkpwGKSUhcLdr2W2UFUxAgM,10542
4
- nt25/lib/calc.py,sha256=3X3k9jisSjRP7OokSdKvoVo4IIOzk2efexW8z1gMo-w,2265
5
- nt25/lib/draw.py,sha256=OKTlkkNVUz_LGBA9Gk7fjcnbbbl7e_hT8nWKkcfeg2k,5642
6
- nt25/lib/et.py,sha256=X_w-f2yb2jPtauPZKfbonDH4FhNFVOiFwZBp9yga8HE,3643
7
- nt25/lib/fio.py,sha256=WvHpG6QYR1NE19Ss3Sy2FdajTxibX5SVW3PyC5Y5Krk,2525
8
- nt25-0.1.4.dist-info/METADATA,sha256=DbMNv3C3BKdJ0yD2iQg7ii3-pQw3qEDFbjUZqJE8MLo,654
9
- nt25-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
- nt25-0.1.4.dist-info/entry_points.txt,sha256=6O4CnhT__4-ORTKEY9vV-MjSzJwDM4FFbVODQsN4Gr8,62
11
- nt25-0.1.4.dist-info/RECORD,,
File without changes