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.
Files changed (41) hide show
  1. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/CHANGELOG.md +23 -2
  2. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/LICENSE +1 -1
  3. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/Makefile +1 -1
  4. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/PKG-INFO +9 -8
  5. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/README.md +6 -6
  6. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/__init__.py +1 -1
  7. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/main.py +4 -1
  8. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/reader.py +77 -32
  9. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/sequence.py +45 -4
  10. {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
  11. {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
  12. geopic_tag_reader-1.3.1/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  13. {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
  14. geopic_tag_reader-1.3.1/geopic_tag_reader/translations/es/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  15. geopic_tag_reader-1.3.1/geopic_tag_reader/translations/es/LC_MESSAGES/geopic_tag_reader.po +182 -0
  16. {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
  17. 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
  18. geopic_tag_reader-1.3.1/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  19. {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
  20. geopic_tag_reader-1.3.1/geopic_tag_reader/translations/geopic_tag_reader.pot +159 -0
  21. geopic_tag_reader-1.3.1/geopic_tag_reader/translations/ko/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  22. {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
  23. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/pyproject.toml +2 -1
  24. geopic_tag_reader-1.2.0/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  25. geopic_tag_reader-1.2.0/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  26. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/.gitignore +0 -0
  27. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/.gitlab-ci.yml +0 -0
  28. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/.pre-commit-config.yaml +0 -0
  29. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/CODE_OF_CONDUCT.md +0 -0
  30. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/docs/develop.md +0 -0
  31. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/docs/index.md +0 -0
  32. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/docs/install.md +0 -0
  33. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/docs/tech/api_reference.md +0 -0
  34. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/docs/tech/cli.md +0 -0
  35. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/camera.py +0 -0
  36. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/i18n.py +0 -0
  37. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/model.py +0 -0
  38. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/py.typed +0 -0
  39. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/geopic_tag_reader/writer.py +0 -0
  40. {geopic_tag_reader-1.2.0 → geopic_tag_reader-1.3.1}/mkdocs.yml +0 -0
  41. {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.2.0...main
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
  MIT License
2
2
 
3
- Copyright (c) 2022 Geovisio team
3
+ Copyright (c) 2022-2024 Panoramax team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -11,7 +11,7 @@ type-check: ## Check all python types
11
11
  mypy geopic_tag_reader/
12
12
 
13
13
  fmt: ## Format code
14
- black --fast .
14
+ python -m black --fast .
15
15
 
16
16
  ci: type-check fmt test ## Run all check like the ci
17
17
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geopic-tag-reader
3
- Version: 1.2.0
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.8.3
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](https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Panoramax.svg/40px-Panoramax.svg.png) Panoramax
33
34
 
34
- __Panoramax__ is a digital resource for sharing and exploiting 📍📷 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
+ __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 GeoVisio/Panoramax components for your own projects and needs.
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/Install.md).
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/CLI_USAGE.md).
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/API_USAGE.md).
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/Develop.md).
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](https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Panoramax.svg/40px-Panoramax.svg.png) Panoramax
2
2
 
3
- __Panoramax__ is a digital resource for sharing and exploiting 📍📷 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/).
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 GeoVisio/Panoramax components for your own projects and needs.
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/Install.md).
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/CLI_USAGE.md).
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/API_USAGE.md).
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/Develop.md).
98
+ More information about developing is available in [documentation](./docs/develop.md).
99
99
 
100
100
 
101
101
  ## ⚖️ License
@@ -2,4 +2,4 @@
2
2
  GeoPicTagReader
3
3
  """
4
4
 
5
- __version__ = "1.2.0"
5
+ __version__ = "1.3.1"
@@ -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.isoformat())
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
- d, llw = decodeGPSDateTime(data, "Exif.GPSInfo", _, lat, lon)
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 d is None:
197
- d, llw = decodeGPSDateTime(data, "Xmp.exif", _, lat, lon)
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
- for exifField in [
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
- d = datetime.datetime(
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, data.get("Exif.Photo.PixelXDimension"), data.get("Exif.Photo.PixelYDimension")):
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, datetimeField: str, _: Callable[[str], str], lat: Optional[float] = None, lon: Optional[float] = None
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, datetimeField):
535
+ if d is None and isExifTagUsable(data, dtField):
489
536
  try:
490
- dateRaw = data[datetimeField][:10].replace(":", "-")
491
- timeRaw = data[datetimeField][11:].split(":")
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["Exif.Photo.SubSecTimeOriginal"] if isExifTagUsable(data, "Exif.Photo.SubSecTimeOriginal", float) else "0",
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"Exif.Photo.OffsetTime{'Original' if 'DateTimeOriginal' in datetimeField else ''}")
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
- data["Exif.Photo.SubSecTimeOriginal"] if isExifTagUsable(data, "Exif.Photo.SubSecTimeOriginal", float) else "0",
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
- pictures.sort(key=lambda p: p.metadata.ts.isoformat() if p.metadata is not None else "0000-00-00T00:00:00Z")
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
- timeOutOfDelta = (
248
- False if splitParams.maxTime is None else (abs(lastPic.metadata.ts - pic.metadata.ts)).total_seconds() > splitParams.maxTime
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)
@@ -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-13 20:42+0000\n"
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:"