geopic-tag-reader 1.2.0__tar.gz → 1.3.1__tar.gz
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.
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/CHANGELOG.md +23 -2
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/LICENSE +1 -1
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/Makefile +1 -1
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/PKG-INFO +9 -8
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/README.md +6 -6
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/__init__.py +1 -1
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/main.py +4 -1
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/reader.py +77 -32
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/sequence.py +45 -4
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.po +5 -1
- geopic_tag_reader-1.3.1/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.po +39 -27
- geopic_tag_reader-1.3.1/geopic_tag_reader/translations/es/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- geopic_tag_reader-1.3.1/geopic_tag_reader/translations/es/LC_MESSAGES/geopic_tag_reader.po +182 -0
- {geopic_tag_reader-1.2.0/geopic_tag_reader/translations/es → geopic_tag_reader-1.3.1/geopic_tag_reader/translations/fi}/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- geopic_tag_reader-1.2.0/geopic_tag_reader/translations/geopic_tag_reader.pot → geopic_tag_reader-1.3.1/geopic_tag_reader/translations/fi/LC_MESSAGES/geopic_tag_reader.po +4 -5
- geopic_tag_reader-1.3.1/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.po +14 -2
- geopic_tag_reader-1.3.1/geopic_tag_reader/translations/geopic_tag_reader.pot +159 -0
- geopic_tag_reader-1.3.1/geopic_tag_reader/translations/ko/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.2.0/geopic_tag_reader/translations/es → geopic_tag_reader-1.3.1/geopic_tag_reader/translations/ko}/LC_MESSAGES/geopic_tag_reader.po +42 -30
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/pyproject.toml +2 -1
- geopic_tag_reader-1.2.0/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- geopic_tag_reader-1.2.0/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/.gitignore +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/.gitlab-ci.yml +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/.pre-commit-config.yaml +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/CODE_OF_CONDUCT.md +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/docs/develop.md +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/docs/index.md +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/docs/install.md +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/docs/tech/api_reference.md +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/docs/tech/cli.md +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/camera.py +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/i18n.py +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/model.py +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/py.typed +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/writer.py +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/mkdocs.yml +0 -0
- {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/pytest.ini +0 -0
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.3.1] - 2024-10-09
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Checks for 360° pictures now use the width/height from image itself (instead of metadata, that could not be set)
|
|
15
|
+
|
|
16
|
+
## [1.3.0] - 2024-09-30
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Reader offers a new property `ts_by_source` to distinguish read datetime from GPS and camera.
|
|
21
|
+
- Sequence sorting uses same timestamp source through all pictures (GPS if available, camera else, fallback with other value if two timestamps are identical).
|
|
22
|
+
- Sequence splits uses whenever possible same timestamp source.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Documentation links were not up-to-date in README file.
|
|
27
|
+
- Sub-seconds values and time offset are applied only if part of the same EXIF group.
|
|
28
|
+
|
|
10
29
|
## [1.2.0] - 2024-07-30
|
|
11
30
|
|
|
12
31
|
### Added
|
|
@@ -210,9 +229,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
210
229
|
|
|
211
230
|
### Added
|
|
212
231
|
|
|
213
|
-
- EXIF tag reading methods extracted from [GeoVisio API](https://gitlab.com/panoramax/server/api)
|
|
232
|
+
- EXIF tag reading methods extracted from [Panoramax/GeoVisio API](https://gitlab.com/panoramax/server/api)
|
|
214
233
|
|
|
215
|
-
[Unreleased]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.
|
|
234
|
+
[Unreleased]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.3.1...main
|
|
235
|
+
[1.3.1]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.3.0...1.3.1
|
|
236
|
+
[1.3.0]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.2.0...1.3.0
|
|
216
237
|
[1.2.0]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.1.5...1.2.0
|
|
217
238
|
[1.1.5]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.1.4...1.1.5
|
|
218
239
|
[1.1.4]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.1.3...1.1.4
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: geopic-tag-reader
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: GeoPicTagReader
|
|
5
5
|
Author-email: Adrien PAVIE <panieravide@riseup.net>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -8,10 +8,11 @@ Description-Content-Type: text/markdown
|
|
|
8
8
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
9
|
Requires-Dist: typer ~= 0.12
|
|
10
10
|
Requires-Dist: xmltodict ~= 0.13
|
|
11
|
-
Requires-Dist: pyexiv2 == 2.
|
|
11
|
+
Requires-Dist: pyexiv2 == 2.15.0
|
|
12
12
|
Requires-Dist: timezonefinder == 6.2.0
|
|
13
13
|
Requires-Dist: pytz ~= 2023.3
|
|
14
14
|
Requires-Dist: types-pytz ~= 2023.3.0.1
|
|
15
|
+
Requires-Dist: types-python-dateutil ~= 2.9.0
|
|
15
16
|
Requires-Dist: flit ~= 3.8.0 ; extra == "build"
|
|
16
17
|
Requires-Dist: black ~= 24.3 ; extra == "dev"
|
|
17
18
|
Requires-Dist: mypy ~= 1.9 ; extra == "dev"
|
|
@@ -31,12 +32,12 @@ Provides-Extra: write-exif
|
|
|
31
32
|
|
|
32
33
|
#  Panoramax
|
|
33
34
|
|
|
34
|
-
__Panoramax__ is a digital resource for sharing and
|
|
35
|
+
__Panoramax__ is a digital resource for sharing and using 📍📷 field photos. Anyone can take photographs of places visible from the public streets and contribute them to the Panoramax database. This data is then freely accessible and reusable by all. More information available at [gitlab.com/panoramax](https://gitlab.com/panoramax) and [panoramax.fr](https://panoramax.fr/).
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
# 📷 GeoPic Tag Reader
|
|
38
39
|
|
|
39
|
-
This repository only contains the Python library to __read and write standardized metadata__ from geolocated pictures EXIF metadata. It can be used completely apart from all
|
|
40
|
+
This repository only contains the Python library to __read and write standardized metadata__ from geolocated pictures EXIF metadata. It can be used completely apart from all Panoramax components for your own projects and needs.
|
|
40
41
|
|
|
41
42
|
## Features
|
|
42
43
|
|
|
@@ -56,7 +57,7 @@ pip install geopic_tag_reader
|
|
|
56
57
|
geopic-tag-reader --help
|
|
57
58
|
```
|
|
58
59
|
|
|
59
|
-
To know more about install and other options, see [install documentation](./docs/
|
|
60
|
+
To know more about install and other options, see [install documentation](./docs/install.md).
|
|
60
61
|
|
|
61
62
|
If at some point you're lost or need help, you can contact us through [issues](https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/issues) or by [email](mailto:panieravide@riseup.net).
|
|
62
63
|
|
|
@@ -88,7 +89,7 @@ geopic-tag-reader write \
|
|
|
88
89
|
--output /path/to/edited_image.jpg
|
|
89
90
|
```
|
|
90
91
|
|
|
91
|
-
[Full documentation is also available here](./docs/
|
|
92
|
+
[Full documentation is also available here](./docs/index.md).
|
|
92
93
|
|
|
93
94
|
### As Python library
|
|
94
95
|
|
|
@@ -119,14 +120,14 @@ editedImg.write(editedImgBytes)
|
|
|
119
120
|
editedImg.close()
|
|
120
121
|
```
|
|
121
122
|
|
|
122
|
-
[Full documentation is also available here](./docs/
|
|
123
|
+
[Full documentation is also available here](./docs/tech/api_reference.md).
|
|
123
124
|
|
|
124
125
|
|
|
125
126
|
## Contributing
|
|
126
127
|
|
|
127
128
|
Pull requests are welcome. For major changes, please open an [issue](https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/issues) first to discuss what you would like to change.
|
|
128
129
|
|
|
129
|
-
More information about developing is available in [documentation](./docs/
|
|
130
|
+
More information about developing is available in [documentation](./docs/develop.md).
|
|
130
131
|
|
|
131
132
|
|
|
132
133
|
## ⚖️ License
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#  Panoramax
|
|
2
2
|
|
|
3
|
-
__Panoramax__ is a digital resource for sharing and
|
|
3
|
+
__Panoramax__ is a digital resource for sharing and using 📍📷 field photos. Anyone can take photographs of places visible from the public streets and contribute them to the Panoramax database. This data is then freely accessible and reusable by all. More information available at [gitlab.com/panoramax](https://gitlab.com/panoramax) and [panoramax.fr](https://panoramax.fr/).
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
# 📷 GeoPic Tag Reader
|
|
7
7
|
|
|
8
|
-
This repository only contains the Python library to __read and write standardized metadata__ from geolocated pictures EXIF metadata. It can be used completely apart from all
|
|
8
|
+
This repository only contains the Python library to __read and write standardized metadata__ from geolocated pictures EXIF metadata. It can be used completely apart from all Panoramax components for your own projects and needs.
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
@@ -25,7 +25,7 @@ pip install geopic_tag_reader
|
|
|
25
25
|
geopic-tag-reader --help
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
To know more about install and other options, see [install documentation](./docs/
|
|
28
|
+
To know more about install and other options, see [install documentation](./docs/install.md).
|
|
29
29
|
|
|
30
30
|
If at some point you're lost or need help, you can contact us through [issues](https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/issues) or by [email](mailto:panieravide@riseup.net).
|
|
31
31
|
|
|
@@ -57,7 +57,7 @@ geopic-tag-reader write \
|
|
|
57
57
|
--output /path/to/edited_image.jpg
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
[Full documentation is also available here](./docs/
|
|
60
|
+
[Full documentation is also available here](./docs/index.md).
|
|
61
61
|
|
|
62
62
|
### As Python library
|
|
63
63
|
|
|
@@ -88,14 +88,14 @@ editedImg.write(editedImgBytes)
|
|
|
88
88
|
editedImg.close()
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
[Full documentation is also available here](./docs/
|
|
91
|
+
[Full documentation is also available here](./docs/tech/api_reference.md).
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
## Contributing
|
|
95
95
|
|
|
96
96
|
Pull requests are welcome. For major changes, please open an [issue](https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/issues) first to discuss what you would like to change.
|
|
97
97
|
|
|
98
|
-
More information about developing is available in [documentation](./docs/
|
|
98
|
+
More information about developing is available in [documentation](./docs/develop.md).
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
## ⚖️ License
|
|
@@ -25,7 +25,10 @@ def read(
|
|
|
25
25
|
_ = i18n_init(lang)
|
|
26
26
|
print(_("Latitude:"), metadata.lat)
|
|
27
27
|
print(_("Longitude:"), metadata.lon)
|
|
28
|
-
print(_("Timestamp:"), metadata.ts
|
|
28
|
+
print(_("Timestamp:"), metadata.ts)
|
|
29
|
+
if metadata.ts_by_source is not None:
|
|
30
|
+
print(" -", (metadata.ts_by_source.gps or _("not set")), _("(GPS)"))
|
|
31
|
+
print(" -", (metadata.ts_by_source.camera or _("not set")), _("(Camera)"))
|
|
29
32
|
print(_("Heading:"), metadata.heading)
|
|
30
33
|
print(_("Type:"), metadata.type)
|
|
31
34
|
print(_("Make:"), metadata.make)
|
|
@@ -39,6 +39,32 @@ class CropValues:
|
|
|
39
39
|
top: int
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
@dataclass
|
|
43
|
+
class TimeBySource:
|
|
44
|
+
"""All datetimes read from available sources
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
gps (datetime): Time read from GPS clock
|
|
48
|
+
camera (datetime): Time read from camera clock (DateTimeOriginal)
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
gps: Optional[datetime.datetime] = None
|
|
52
|
+
camera: Optional[datetime.datetime] = None
|
|
53
|
+
|
|
54
|
+
def getBest(self) -> Optional[datetime.datetime]:
|
|
55
|
+
"""Get the best available datetime to use"""
|
|
56
|
+
if self.gps is not None and self.camera is None:
|
|
57
|
+
return self.gps
|
|
58
|
+
elif self.gps is None and self.camera is not None:
|
|
59
|
+
return self.camera
|
|
60
|
+
elif self.gps is None and self.camera is None:
|
|
61
|
+
return None
|
|
62
|
+
elif self.camera.microsecond > 0 and self.gps.microsecond == 0: # type: ignore
|
|
63
|
+
return self.camera
|
|
64
|
+
else:
|
|
65
|
+
return self.gps
|
|
66
|
+
|
|
67
|
+
|
|
42
68
|
@dataclass
|
|
43
69
|
class GeoPicTags:
|
|
44
70
|
"""Tags associated to a geolocated picture
|
|
@@ -59,6 +85,7 @@ class GeoPicTags:
|
|
|
59
85
|
pitch (float): Picture pitch angle, compared to horizon (in degrees, bottom = -90°, horizon = 0°, top = 90°)
|
|
60
86
|
roll (float): Picture roll angle, on a right/left axis (in degrees, left-arm down = -90°, flat = 0°, right-arm down = 90°)
|
|
61
87
|
yaw (float): Picture yaw angle, on a vertical axis (in degrees, front = 0°, right = 90°, rear = 180°, left = 270°). This offsets the center image from GPS direction for a correct 360° sphere correction
|
|
88
|
+
ts_by_source (TimeBySource): all read timestamps from image, for finer processing.
|
|
62
89
|
|
|
63
90
|
|
|
64
91
|
Implementation note: this needs to be sync with the PartialGeoPicTags structure
|
|
@@ -79,6 +106,7 @@ class GeoPicTags:
|
|
|
79
106
|
pitch: Optional[float] = None
|
|
80
107
|
roll: Optional[float] = None
|
|
81
108
|
yaw: Optional[float] = None
|
|
109
|
+
ts_by_source: Optional[TimeBySource] = None
|
|
82
110
|
|
|
83
111
|
|
|
84
112
|
class InvalidExifException(Exception):
|
|
@@ -114,6 +142,7 @@ class PartialGeoPicTags:
|
|
|
114
142
|
pitch: Optional[float] = None
|
|
115
143
|
roll: Optional[float] = None
|
|
116
144
|
yaw: Optional[float] = None
|
|
145
|
+
ts_by_source: Optional[TimeBySource] = None
|
|
117
146
|
|
|
118
147
|
|
|
119
148
|
class PartialExifException(Exception):
|
|
@@ -145,7 +174,10 @@ def readPictureMetadata(picture: bytes, lang_code: str = "en") -> GeoPicTags:
|
|
|
145
174
|
img = pyexiv2.ImageData(picture)
|
|
146
175
|
data = {}
|
|
147
176
|
data.update(img.read_exif())
|
|
177
|
+
data.update(img.read_iptc())
|
|
148
178
|
data.update(img.read_xmp())
|
|
179
|
+
width = img.get_pixel_width()
|
|
180
|
+
height = img.get_pixel_height()
|
|
149
181
|
|
|
150
182
|
imgComment = img.read_comment()
|
|
151
183
|
if imgComment is not None and len(imgComment.strip()) > 0:
|
|
@@ -187,35 +219,21 @@ def readPictureMetadata(picture: bytes, lang_code: str = "en") -> GeoPicTags:
|
|
|
187
219
|
if lon is not None and (lon < -180 or lon > 180):
|
|
188
220
|
raise InvalidExifException(_("Read longitude is out of WGS84 bounds (should be in [-180, 180])"))
|
|
189
221
|
|
|
190
|
-
# Parse date/time
|
|
191
|
-
|
|
222
|
+
# Parse GPS date/time
|
|
223
|
+
gpsTs, llw = decodeGPSDateTime(data, "Exif.GPSInfo", _, lat, lon)
|
|
192
224
|
|
|
193
225
|
if len(llw) > 0:
|
|
194
226
|
warnings.extend(llw)
|
|
195
227
|
|
|
196
|
-
if
|
|
197
|
-
|
|
228
|
+
if gpsTs is None:
|
|
229
|
+
gpsTs, llw = decodeGPSDateTime(data, "Xmp.exif", _, lat, lon)
|
|
198
230
|
if len(llw) > 0:
|
|
199
231
|
warnings.extend(llw)
|
|
200
232
|
|
|
201
|
-
|
|
202
|
-
"Exif.Image.DateTimeOriginal",
|
|
203
|
-
"Exif.Photo.DateTimeOriginal",
|
|
204
|
-
"Exif.Image.DateTime",
|
|
205
|
-
"Xmp.GPano.SourceImageCreateTime",
|
|
206
|
-
]:
|
|
207
|
-
if d is None:
|
|
208
|
-
d, llw = decodeDateTimeOriginal(data, exifField, _, lat, lon)
|
|
209
|
-
if len(llw) > 0:
|
|
210
|
-
warnings.extend(llw)
|
|
211
|
-
|
|
212
|
-
if d is not None:
|
|
213
|
-
break
|
|
214
|
-
|
|
215
|
-
if d is None and isExifTagUsable(data, "MAPGpsTime"):
|
|
233
|
+
if gpsTs is None and isExifTagUsable(data, "MAPGpsTime"):
|
|
216
234
|
try:
|
|
217
235
|
year, month, day, hour, minutes, seconds, milliseconds = [int(dp) for dp in data["MAPGpsTime"].split("_")]
|
|
218
|
-
|
|
236
|
+
gpsTs = datetime.datetime(
|
|
219
237
|
year,
|
|
220
238
|
month,
|
|
221
239
|
day,
|
|
@@ -229,6 +247,25 @@ def readPictureMetadata(picture: bytes, lang_code: str = "en") -> GeoPicTags:
|
|
|
229
247
|
except Exception as e:
|
|
230
248
|
warnings.append(_("Skipping Mapillary date/time as it was not recognized: {v}").format(v=data["MAPGpsTime"]))
|
|
231
249
|
|
|
250
|
+
# Parse camera date/time
|
|
251
|
+
cameraTs = None
|
|
252
|
+
for exifGroup, dtField, subsecField in [
|
|
253
|
+
("Exif.Photo", "DateTimeOriginal", "SubSecTimeOriginal"),
|
|
254
|
+
("Exif.Image", "DateTimeOriginal", "SubSecTimeOriginal"),
|
|
255
|
+
("Exif.Image", "DateTime", "SubSecTimeOriginal"),
|
|
256
|
+
("Xmp.GPano", "SourceImageCreateTime", "SubSecTimeOriginal"),
|
|
257
|
+
("Xmp.exif", "DateTimeOriginal", "SubsecTimeOriginal"), # Case matters
|
|
258
|
+
]:
|
|
259
|
+
if cameraTs is None:
|
|
260
|
+
cameraTs, llw = decodeDateTimeOriginal(data, exifGroup, dtField, subsecField, _, lat, lon)
|
|
261
|
+
if len(llw) > 0:
|
|
262
|
+
warnings.extend(llw)
|
|
263
|
+
|
|
264
|
+
if cameraTs is not None:
|
|
265
|
+
break
|
|
266
|
+
tsSources = TimeBySource(gps=gpsTs, camera=cameraTs) if gpsTs or cameraTs else None
|
|
267
|
+
d = tsSources.getBest() if tsSources is not None else None
|
|
268
|
+
|
|
232
269
|
# GPS Heading
|
|
233
270
|
heading = None
|
|
234
271
|
if isExifTagUsable(data, "Exif.GPSInfo.GPSImgDirection", Fraction):
|
|
@@ -318,7 +355,7 @@ def readPictureMetadata(picture: bytes, lang_code: str = "en") -> GeoPicTags:
|
|
|
318
355
|
if isExifTagUsable(data, "Xmp.GPano.ProjectionType"):
|
|
319
356
|
pic_type = data["Xmp.GPano.ProjectionType"]
|
|
320
357
|
# 360° based on known models
|
|
321
|
-
elif camera.is_360(make, model,
|
|
358
|
+
elif camera.is_360(make, model, width, height):
|
|
322
359
|
pic_type = "equirectangular"
|
|
323
360
|
# Flat by default
|
|
324
361
|
else:
|
|
@@ -370,6 +407,7 @@ def readPictureMetadata(picture: bytes, lang_code: str = "en") -> GeoPicTags:
|
|
|
370
407
|
pitch=pitch,
|
|
371
408
|
roll=roll,
|
|
372
409
|
yaw=yaw,
|
|
410
|
+
ts_by_source=tsSources,
|
|
373
411
|
),
|
|
374
412
|
)
|
|
375
413
|
|
|
@@ -390,6 +428,7 @@ def readPictureMetadata(picture: bytes, lang_code: str = "en") -> GeoPicTags:
|
|
|
390
428
|
pitch=pitch,
|
|
391
429
|
roll=roll,
|
|
392
430
|
yaw=yaw,
|
|
431
|
+
ts_by_source=tsSources,
|
|
393
432
|
)
|
|
394
433
|
|
|
395
434
|
|
|
@@ -480,20 +519,28 @@ def decodeLatLon(data: dict, group: str, _: Callable[[str], str]) -> Tuple[Optio
|
|
|
480
519
|
|
|
481
520
|
|
|
482
521
|
def decodeDateTimeOriginal(
|
|
483
|
-
data: dict,
|
|
522
|
+
data: dict,
|
|
523
|
+
exifGroup: str,
|
|
524
|
+
datetimeField: str,
|
|
525
|
+
subsecField: str,
|
|
526
|
+
_: Callable[[str], str],
|
|
527
|
+
lat: Optional[float] = None,
|
|
528
|
+
lon: Optional[float] = None,
|
|
484
529
|
) -> Tuple[Optional[datetime.datetime], List[str]]:
|
|
485
530
|
d = None
|
|
486
531
|
warnings = []
|
|
532
|
+
dtField = f"{exifGroup}.{datetimeField}"
|
|
533
|
+
ssField = f"{exifGroup}.{subsecField}"
|
|
487
534
|
|
|
488
|
-
if d is None and isExifTagUsable(data,
|
|
535
|
+
if d is None and isExifTagUsable(data, dtField):
|
|
489
536
|
try:
|
|
490
|
-
dateRaw = data[
|
|
491
|
-
timeRaw = data[
|
|
537
|
+
dateRaw = data[dtField][:10].replace(":", "-")
|
|
538
|
+
timeRaw = data[dtField][11:].split(":")
|
|
492
539
|
hourRaw = int(timeRaw[0])
|
|
493
540
|
minutesRaw = int(timeRaw[1])
|
|
494
541
|
secondsRaw, microsecondsRaw, msw = decodeSecondsAndMicroSeconds(
|
|
495
|
-
timeRaw[2],
|
|
496
|
-
data[
|
|
542
|
+
timeRaw[2] if len(timeRaw) >= 3 else "0",
|
|
543
|
+
data[ssField] if isExifTagUsable(data, ssField, float) else "0",
|
|
497
544
|
_,
|
|
498
545
|
)
|
|
499
546
|
warnings += msw
|
|
@@ -510,7 +557,7 @@ def decodeDateTimeOriginal(
|
|
|
510
557
|
|
|
511
558
|
# Timezone handling
|
|
512
559
|
# Try to read from EXIF
|
|
513
|
-
tz = decodeTimeOffset(data, f"
|
|
560
|
+
tz = decodeTimeOffset(data, f"{exifGroup}.OffsetTime{'Original' if 'DateTimeOriginal' in dtField else ''}")
|
|
514
561
|
if tz is not None:
|
|
515
562
|
d = d.replace(tzinfo=tz)
|
|
516
563
|
|
|
@@ -531,9 +578,7 @@ def decodeDateTimeOriginal(
|
|
|
531
578
|
|
|
532
579
|
except ValueError as e:
|
|
533
580
|
warnings.append(
|
|
534
|
-
_("Skipping original date/time (from {datefield}) as it was not recognized: {v}").format(
|
|
535
|
-
datefield=datetimeField, v=data[datetimeField]
|
|
536
|
-
)
|
|
581
|
+
_("Skipping original date/time (from {datefield}) as it was not recognized: {v}").format(datefield=dtField, v=data[dtField])
|
|
537
582
|
)
|
|
538
583
|
|
|
539
584
|
return (d, warnings)
|
|
@@ -571,7 +616,7 @@ def decodeGPSDateTime(
|
|
|
571
616
|
if timeRaw:
|
|
572
617
|
seconds, microseconds, msw = decodeSecondsAndMicroSeconds(
|
|
573
618
|
str(float(timeRaw[2])),
|
|
574
|
-
|
|
619
|
+
"0", # No SubSecTimeOriginal, it's only for DateTimeOriginal
|
|
575
620
|
_,
|
|
576
621
|
)
|
|
577
622
|
|
|
@@ -143,7 +143,35 @@ def sort_pictures(pictures: List[Picture], method: Optional[SortMethod] = SortMe
|
|
|
143
143
|
|
|
144
144
|
# Sort based on picture ts
|
|
145
145
|
elif strat == "time":
|
|
146
|
-
|
|
146
|
+
# Check if all pictures have GPS ts set
|
|
147
|
+
missingGpsTs = next(
|
|
148
|
+
(p for p in pictures if p.metadata is None or p.metadata.ts_by_source is None or p.metadata.ts_by_source.gps is None), None
|
|
149
|
+
)
|
|
150
|
+
if missingGpsTs:
|
|
151
|
+
# Check if all pictures have camera ts set
|
|
152
|
+
missingCamTs = next(
|
|
153
|
+
(p for p in pictures if p.metadata is None or p.metadata.ts_by_source is None or p.metadata.ts_by_source.camera is None),
|
|
154
|
+
None,
|
|
155
|
+
)
|
|
156
|
+
# Sort by best ts available
|
|
157
|
+
if missingCamTs:
|
|
158
|
+
pictures.sort(key=lambda p: p.metadata.ts.isoformat() if p.metadata is not None else "0000-00-00T00:00:00Z")
|
|
159
|
+
# Sort by camera ts
|
|
160
|
+
else:
|
|
161
|
+
pictures.sort(
|
|
162
|
+
key=lambda p: (
|
|
163
|
+
p.metadata.ts_by_source.camera.isoformat(), # type: ignore
|
|
164
|
+
p.metadata.ts_by_source.gps.isoformat() if p.metadata.ts_by_source.gps else "0000-00-00T00:00:00Z", # type: ignore
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
# Sort by GPS ts
|
|
168
|
+
else:
|
|
169
|
+
pictures.sort(
|
|
170
|
+
key=lambda p: (
|
|
171
|
+
p.metadata.ts_by_source.gps.isoformat(), # type: ignore
|
|
172
|
+
p.metadata.ts_by_source.camera.isoformat() if p.metadata.ts_by_source.camera else "0000-00-00T00:00:00Z", # type: ignore
|
|
173
|
+
)
|
|
174
|
+
)
|
|
147
175
|
|
|
148
176
|
if order == "desc":
|
|
149
177
|
pictures.reverse()
|
|
@@ -244,9 +272,22 @@ def split_in_sequences(pictures: List[Picture], splitParams: Optional[SplitParam
|
|
|
244
272
|
continue
|
|
245
273
|
|
|
246
274
|
# Time delta
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
275
|
+
timeDelta = lastPic.metadata.ts - pic.metadata.ts
|
|
276
|
+
if (
|
|
277
|
+
lastPic.metadata.ts_by_source
|
|
278
|
+
and lastPic.metadata.ts_by_source.gps
|
|
279
|
+
and pic.metadata.ts_by_source
|
|
280
|
+
and pic.metadata.ts_by_source.gps
|
|
281
|
+
):
|
|
282
|
+
timeDelta = lastPic.metadata.ts_by_source.gps - pic.metadata.ts_by_source.gps
|
|
283
|
+
elif (
|
|
284
|
+
lastPic.metadata.ts_by_source
|
|
285
|
+
and lastPic.metadata.ts_by_source.camera
|
|
286
|
+
and pic.metadata.ts_by_source
|
|
287
|
+
and pic.metadata.ts_by_source.camera
|
|
288
|
+
):
|
|
289
|
+
timeDelta = lastPic.metadata.ts_by_source.camera - pic.metadata.ts_by_source.camera
|
|
290
|
+
timeOutOfDelta = False if splitParams.maxTime is None else (abs(timeDelta)).total_seconds() > splitParams.maxTime
|
|
250
291
|
|
|
251
292
|
# Distance delta
|
|
252
293
|
distance = lastPic.distance_to(pic)
|
|
Binary file
|
|
@@ -8,7 +8,7 @@ msgstr ""
|
|
|
8
8
|
"Project-Id-Version: PACKAGE VERSION\n"
|
|
9
9
|
"Report-Msgid-Bugs-To: \n"
|
|
10
10
|
"POT-Creation-Date: 2024-07-10 13:05+0200\n"
|
|
11
|
-
"PO-Revision-Date: 2024-07
|
|
11
|
+
"PO-Revision-Date: 2024-08-07 13:42+0000\n"
|
|
12
12
|
"Last-Translator: Bastian Greshake Tzovaras <bastian@gedankenstuecke.de>\n"
|
|
13
13
|
"Language-Team: German <http://weblate.panoramax.xyz/projects/panoramax/"
|
|
14
14
|
"tag-reader/de/>\n"
|
|
@@ -163,3 +163,7 @@ msgstr ""
|
|
|
163
163
|
#, python-brace-format
|
|
164
164
|
msgid "Unsupported key in additional tags ({k})"
|
|
165
165
|
msgstr "Nicht unterstützter Schlüssel in den zusätzlichen Attributen ({k})"
|
|
166
|
+
|
|
167
|
+
#: geopic_tag_reader/main.py:37
|
|
168
|
+
msgid "Yaw:"
|
|
169
|
+
msgstr "Gierwinkel:"
|