nt25 0.1.3__py3-none-any.whl → 0.1.4__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/lib/et.py
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
import time
|
2
|
+
import json
|
3
|
+
import argparse
|
4
|
+
|
5
|
+
from datetime import UTC, datetime, timedelta, timezone
|
6
|
+
from exif import Image
|
7
|
+
|
8
|
+
VERSION = "0.1.1"
|
9
|
+
|
10
|
+
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
|
+
|
15
|
+
|
16
|
+
def dms2dec(dms: tuple):
|
17
|
+
d, m, s = dms
|
18
|
+
return d + m/60 + s/3600
|
19
|
+
|
20
|
+
|
21
|
+
def dtFormatter(str):
|
22
|
+
return datetime.strptime(str, '%Y:%m:%d %H:%M:%S')
|
23
|
+
|
24
|
+
|
25
|
+
def dt2str(dt):
|
26
|
+
return None if dt is None else dt.strftime('%Y-%m-%d %H:%M:%S')
|
27
|
+
|
28
|
+
|
29
|
+
def gpsDt2Dt(date, time, offset=8):
|
30
|
+
d = dtFormatter(f"{date} {int(time[0])}:{int(time[1])}:{int(time[2])}")
|
31
|
+
utc = d.replace(tzinfo=timezone.utc)
|
32
|
+
return utc.astimezone(timezone(timedelta(hours=offset)))
|
33
|
+
|
34
|
+
|
35
|
+
def tryGet(img, key, default):
|
36
|
+
value = default
|
37
|
+
|
38
|
+
try:
|
39
|
+
value = img[key]
|
40
|
+
except Exception:
|
41
|
+
pass
|
42
|
+
|
43
|
+
return value
|
44
|
+
|
45
|
+
|
46
|
+
def dumpExif(file):
|
47
|
+
result = {}
|
48
|
+
with open(file, 'rb') as f:
|
49
|
+
img = Image(f)
|
50
|
+
for key in img.get_all():
|
51
|
+
try:
|
52
|
+
result[key] = str(img[key])
|
53
|
+
except Exception:
|
54
|
+
pass
|
55
|
+
|
56
|
+
return result
|
57
|
+
|
58
|
+
|
59
|
+
def parseExif(file):
|
60
|
+
with open(file, 'rb') as f:
|
61
|
+
try:
|
62
|
+
img = Image(f)
|
63
|
+
except Exception:
|
64
|
+
return {}
|
65
|
+
|
66
|
+
width = tryGet(img, 'pixel_x_dimension', -1)
|
67
|
+
height = tryGet(img, 'pixel_y_dimension', -1)
|
68
|
+
|
69
|
+
if width < 0:
|
70
|
+
width = tryGet(img, 'image_width', -1)
|
71
|
+
height = tryGet(img, 'image_height', -1)
|
72
|
+
|
73
|
+
create = tryGet(img, 'datetime_original', None)
|
74
|
+
modify = tryGet(img, 'datetime', None)
|
75
|
+
|
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
|
+
createDt = None if create is None else dtFormatter(create)
|
84
|
+
modifyDt = None if modify is None else dtFormatter(modify)
|
85
|
+
|
86
|
+
latitude = tryGet(img, 'gps_latitude', None)
|
87
|
+
latitude = None if latitude is None else dms2dec(latitude)
|
88
|
+
|
89
|
+
longitude = tryGet(img, 'gps_longitude', None)
|
90
|
+
longitude = None if longitude is None else dms2dec(longitude)
|
91
|
+
|
92
|
+
gpsDatetime = None
|
93
|
+
gd = tryGet(img, 'gps_datestamp', None)
|
94
|
+
gt = tryGet(img, 'gps_timestamp', None)
|
95
|
+
|
96
|
+
if gd and gt:
|
97
|
+
offset = int(time.localtime().tm_gmtoff / 3600)
|
98
|
+
gpsDatetime = gpsDt2Dt(gd, gt, offset=offset)
|
99
|
+
|
100
|
+
ts = -1 if createDt is None else int(createDt.timestamp())
|
101
|
+
mTs = -1 if modifyDt is None else int(modifyDt.timestamp())
|
102
|
+
gpsTs = -1 if gpsDatetime is None else int(gpsDatetime.timestamp())
|
103
|
+
offset = max(mTs, gpsTs) - ts
|
104
|
+
offsetDelta = datetime.fromtimestamp(offset, UTC) - EPOCH
|
105
|
+
|
106
|
+
return {
|
107
|
+
"width": width,
|
108
|
+
"height": height,
|
109
|
+
"latitude": latitude,
|
110
|
+
"longitude": longitude,
|
111
|
+
"datetime.create": dt2str(createDt),
|
112
|
+
"datetime.modify": dt2str(modifyDt),
|
113
|
+
"datetime.gps": dt2str(gpsDatetime),
|
114
|
+
"ts": ts,
|
115
|
+
"offset": offset,
|
116
|
+
"offset.delta": str(offsetDelta),
|
117
|
+
}
|
118
|
+
|
119
|
+
|
120
|
+
def main():
|
121
|
+
parser = argparse.ArgumentParser(description="EXIF tool")
|
122
|
+
parser.add_argument('-v', '--version',
|
123
|
+
help='echo version', action='store_true')
|
124
|
+
parser.add_argument('-d', '--dump', help='dump meta', action='store_true')
|
125
|
+
parser.add_argument('-f', '--file', type=str, help='image file')
|
126
|
+
|
127
|
+
args = parser.parse_args()
|
128
|
+
|
129
|
+
if args.version:
|
130
|
+
print(f"et: {VERSION}")
|
131
|
+
return
|
132
|
+
|
133
|
+
if args.file is None:
|
134
|
+
print("usage: et [-h] [-v] [-d] [-f FILE]")
|
135
|
+
return
|
136
|
+
|
137
|
+
r = dumpExif(args.file) if args.dump else parseExif(args.file)
|
138
|
+
print(json.dumps(r, indent=2, sort_keys=False))
|
139
|
+
|
140
|
+
|
141
|
+
if __name__ == "__main__":
|
142
|
+
main()
|
@@ -1,11 +1,13 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: nt25
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.4
|
4
4
|
Summary: Neo's Tools of Python
|
5
5
|
Requires-Python: >=3.10
|
6
|
+
Requires-Dist: exif>=1.6.1
|
6
7
|
Requires-Dist: matplotlib>=3.10.6
|
7
8
|
Requires-Dist: openpyxl>=3.1.5
|
8
9
|
Requires-Dist: pandas>=2.3.2
|
10
|
+
Requires-Dist: pyinstaller>=6.15.0
|
9
11
|
Requires-Dist: scikit-learn>=1.7.1
|
10
12
|
Requires-Dist: sympy>=1.14.0
|
11
13
|
Description-Content-Type: text/markdown
|
@@ -14,11 +16,12 @@ Description-Content-Type: text/markdown
|
|
14
16
|
|
15
17
|
Neo's Tools of Python in 2025
|
16
18
|
|
17
|
-
##
|
19
|
+
## TODO
|
18
20
|
|
19
21
|
- [x] fio
|
20
22
|
- [x] calc
|
21
23
|
- [x] draw
|
24
|
+
- [x] et: EXIF tools
|
22
25
|
- [ ] mysql
|
23
26
|
- [ ] redis
|
24
27
|
- [ ] maptrans
|
@@ -28,7 +31,10 @@ Neo's Tools of Python in 2025
|
|
28
31
|
|
29
32
|
```sh
|
30
33
|
uv init
|
31
|
-
|
34
|
+
# fio, calc, draw basic demo
|
35
|
+
uv run nt25
|
36
|
+
# et
|
37
|
+
uv run et
|
32
38
|
```
|
33
39
|
|
34
40
|
## fun
|
@@ -3,8 +3,9 @@ nt25/main.py,sha256=nVzuGx8PaxDU-QbLmVtAvDkfEFX5Z5g0xE0CWf9EwBA,2098
|
|
3
3
|
nt25/data/test.xlsx,sha256=7C0JDS-TLm_KmjnKtfeajkpwGKSUhcLdr2W2UFUxAgM,10542
|
4
4
|
nt25/lib/calc.py,sha256=3X3k9jisSjRP7OokSdKvoVo4IIOzk2efexW8z1gMo-w,2265
|
5
5
|
nt25/lib/draw.py,sha256=OKTlkkNVUz_LGBA9Gk7fjcnbbbl7e_hT8nWKkcfeg2k,5642
|
6
|
+
nt25/lib/et.py,sha256=X_w-f2yb2jPtauPZKfbonDH4FhNFVOiFwZBp9yga8HE,3643
|
6
7
|
nt25/lib/fio.py,sha256=WvHpG6QYR1NE19Ss3Sy2FdajTxibX5SVW3PyC5Y5Krk,2525
|
7
|
-
nt25-0.1.
|
8
|
-
nt25-0.1.
|
9
|
-
nt25-0.1.
|
10
|
-
nt25-0.1.
|
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
|