geopic-tag-reader 1.0.6__tar.gz → 1.1.0__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 (66) hide show
  1. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/CHANGELOG.md +10 -1
  2. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/PKG-INFO +4 -4
  3. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/docs/API_USAGE.md +1 -1
  4. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/docs/model.md +3 -1
  5. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/docs/reader.md +29 -22
  6. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/geopic_tag_reader/__init__.py +1 -1
  7. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/geopic_tag_reader/main.py +1 -1
  8. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/geopic_tag_reader/reader.py +51 -12
  9. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/geopic_tag_reader/writer.py +2 -2
  10. geopic_tag_reader-1.1.0/tests/fixtures/charset.jpg +0 -0
  11. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/test_reader.py +37 -26
  12. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/test_writer.py +3 -3
  13. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/.gitignore +0 -0
  14. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/.gitlab-ci.yml +0 -0
  15. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/.pre-commit-config.yaml +0 -0
  16. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/CODE_OF_CONDUCT.md +0 -0
  17. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/LICENSE +0 -0
  18. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/Makefile +0 -0
  19. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/README.md +0 -0
  20. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/docs/.pages +0 -0
  21. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/docs/CLI_USAGE.md +0 -0
  22. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/docs/Develop.md +0 -0
  23. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/docs/Install.md +0 -0
  24. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/docs/camera.md +0 -0
  25. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/docs/writer.md +0 -0
  26. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/geopic_tag_reader/camera.py +0 -0
  27. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/geopic_tag_reader/model.py +0 -0
  28. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/geopic_tag_reader/py.typed +0 -0
  29. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/pyproject.toml +3 -3
  30. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/pytest.ini +0 -0
  31. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/__init__.py +0 -0
  32. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/conftest.py +0 -0
  33. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/1.jpg +0 -0
  34. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/IMG_20210720_144918.jpg +0 -0
  35. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/IMG_20210720_161352.jpg +0 -0
  36. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/a1.jpg +0 -0
  37. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/b1.jpg +0 -0
  38. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/broken_makernotes.jpg +0 -0
  39. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/c1.jpg +0 -0
  40. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/cropped.jpg +0 -0
  41. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/d1.jpg +0 -0
  42. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/datetime_ms_float.jpg +0 -0
  43. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/datetime_offset.jpg +0 -0
  44. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/e1.jpg +0 -0
  45. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/flat.jpg +0 -0
  46. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/gopromax_flat.jpg +0 -0
  47. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/gps_date_slash.jpg +0 -0
  48. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/gps_date_time_stamp.jpg +0 -0
  49. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/img_Ricoh_Theta.jpg +0 -0
  50. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/img_V4MPack.jpg +0 -0
  51. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/img_categorisee.jpg +0 -0
  52. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/img_datetimeoriginal.jpg +0 -0
  53. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/img_gps_date_string.jpg +0 -0
  54. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/img_gps_datestamp.jpg +0 -0
  55. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/img_gps_sotm.jpg +0 -0
  56. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/img_invalid_gps_date.jpg +0 -0
  57. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/img_without_coord.jpg +0 -0
  58. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/img_without_dt.jpg +0 -0
  59. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/img_without_exif_tags.jpg +0 -0
  60. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/insta360_date.jpg +0 -0
  61. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/int_long_tag.jpg +0 -0
  62. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/out_of_bounds_lat.jpg +0 -0
  63. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/out_of_bounds_lon.jpg +0 -0
  64. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/pic_with_float_lat.jpg +0 -0
  65. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/fixtures/ricoh_theta_no_projection.jpg +0 -0
  66. {geopic_tag_reader-1.0.6 → geopic_tag_reader-1.1.0}/tests/test_main.py +0 -0
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.0] - 2024-04-17
11
+
12
+ ### Changed
13
+
14
+ - Encoding information (`charset=...`) is now stripped out of text EXIF tags.
15
+ - `GeoPicTags` objects returns `ts` as a Python `datetime` object (instead of decimal epoch).
16
+ - Improved timezone handling in reader, GPS coordinates are used to find appropriate timezone when no timezone is defined in EXIF tags. If a UTC fallback is done, a warning is thrown.
17
+
10
18
  ## [1.0.6] - 2024-04-02
11
19
 
12
20
  ### Changed
@@ -152,7 +160,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
152
160
 
153
161
  - EXIF tag reading methods extracted from [GeoVisio API](https://gitlab.com/geovisio/api)
154
162
 
155
- [Unreleased]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.0.6...main
163
+ [Unreleased]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.1.0...main
164
+ [1.1.0]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.0.6...1.1.0
156
165
  [1.0.6]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.0.5...1.0.6
157
166
  [1.0.5]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.0.4...1.0.5
158
167
  [1.0.4]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.0.3...1.0.4
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geopic-tag-reader
3
- Version: 1.0.6
3
+ Version: 1.1.0
4
4
  Summary: GeoPicTagReader
5
5
  Author-email: Adrien PAVIE <panieravide@riseup.net>
6
6
  Requires-Python: >=3.8
@@ -9,6 +9,9 @@ Classifier: License :: OSI Approved :: MIT License
9
9
  Requires-Dist: typer ~= 0.12
10
10
  Requires-Dist: xmltodict ~= 0.13
11
11
  Requires-Dist: pyexiv2 == 2.8.3
12
+ Requires-Dist: timezonefinder == 6.2.0
13
+ Requires-Dist: pytz ~= 2023.3
14
+ Requires-Dist: types-pytz ~= 2023.3.0.1
12
15
  Requires-Dist: flit ~= 3.8.0 ; extra == "build"
13
16
  Requires-Dist: black ~= 24.3 ; extra == "dev"
14
17
  Requires-Dist: mypy ~= 1.9 ; extra == "dev"
@@ -17,9 +20,6 @@ Requires-Dist: pytest-datafiles ~= 3.0 ; extra == "dev"
17
20
  Requires-Dist: lazydocs ~= 0.4.8 ; extra == "dev"
18
21
  Requires-Dist: types-xmltodict ~= 0.13 ; extra == "dev"
19
22
  Requires-Dist: pre-commit ~= 3.3.3 ; extra == "dev"
20
- Requires-Dist: timezonefinder == 6.2.0 ; extra == "write-exif"
21
- Requires-Dist: pytz ~= 2023.3 ; extra == "write-exif"
22
- Requires-Dist: types-pytz ~= 2023.3.0.1 ; extra == "write-exif"
23
23
  Requires-Dist: python-dateutil ~= 2.8.2 ; extra == "write-exif"
24
24
  Project-URL: Home, https://gitlab.com/geovisio/geo-picture-tag-reader
25
25
  Provides-Extra: build
@@ -11,7 +11,7 @@
11
11
 
12
12
  ## Classes
13
13
 
14
- - [`model.PictureType`](./model.md#class-picturetype): An enumeration.
14
+ - [`model.PictureType`](./model.md#class-picturetype)
15
15
  - [`reader.CropValues`](./reader.md#class-cropvalues): Cropped equirectangular pictures metadata
16
16
  - [`reader.GeoPicTags`](./reader.md#class-geopictags): Tags associated to a geolocated picture
17
17
  - [`reader.InvalidExifException`](./reader.md#class-invalidexifexception): Exception for invalid EXIF information from image
@@ -14,7 +14,9 @@
14
14
  <a href="../geopic_tag_reader/model.py#L4"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
15
15
 
16
16
  ## <kbd>class</kbd> `PictureType`
17
- An enumeration.
17
+
18
+
19
+
18
20
 
19
21
 
20
22
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  ---
12
12
 
13
- <a href="../geopic_tag_reader/reader.py#L114"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
13
+ <a href="../geopic_tag_reader/reader.py#L118"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
14
14
 
15
15
  ## <kbd>function</kbd> `readPictureMetadata`
16
16
 
@@ -35,7 +35,7 @@ Extracts metadata from picture file
35
35
 
36
36
  ---
37
37
 
38
- <a href="../geopic_tag_reader/reader.py#L346"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
38
+ <a href="../geopic_tag_reader/reader.py#L355"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
39
39
 
40
40
  ## <kbd>function</kbd> `decodeMakeModel`
41
41
 
@@ -48,7 +48,7 @@ Python 2/3 compatible decoding of make/model field.
48
48
 
49
49
  ---
50
50
 
51
- <a href="../geopic_tag_reader/reader.py#L357"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
51
+ <a href="../geopic_tag_reader/reader.py#L366"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
52
52
 
53
53
  ## <kbd>function</kbd> `isValidManyFractions`
54
54
 
@@ -63,7 +63,7 @@ isValidManyFractions(value: str) → bool
63
63
 
64
64
  ---
65
65
 
66
- <a href="../geopic_tag_reader/reader.py#L364"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
66
+ <a href="../geopic_tag_reader/reader.py#L373"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
67
67
 
68
68
  ## <kbd>function</kbd> `decodeManyFractions`
69
69
 
@@ -76,7 +76,7 @@ Try to decode a list of fractions, separated by spaces
76
76
 
77
77
  ---
78
78
 
79
- <a href="../geopic_tag_reader/reader.py#L377"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
79
+ <a href="../geopic_tag_reader/reader.py#L386"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
80
80
 
81
81
  ## <kbd>function</kbd> `decodeLatLon`
82
82
 
@@ -92,14 +92,16 @@ Reads GPS info from given group to get latitude/longitude as float coordinates
92
92
 
93
93
  ---
94
94
 
95
- <a href="../geopic_tag_reader/reader.py#L432"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
95
+ <a href="../geopic_tag_reader/reader.py#L441"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
96
96
 
97
97
  ## <kbd>function</kbd> `decodeDateTimeOriginal`
98
98
 
99
99
  ```python
100
100
  decodeDateTimeOriginal(
101
101
  data: dict,
102
- datetimeField: str
102
+ datetimeField: str,
103
+ lat: Optional[float] = None,
104
+ lon: Optional[float] = None
103
105
  ) → Tuple[Optional[datetime], List[str]]
104
106
  ```
105
107
 
@@ -110,7 +112,7 @@ decodeDateTimeOriginal(
110
112
 
111
113
  ---
112
114
 
113
- <a href="../geopic_tag_reader/reader.py#L464"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
115
+ <a href="../geopic_tag_reader/reader.py#L495"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
114
116
 
115
117
  ## <kbd>function</kbd> `decodeTimeOffset`
116
118
 
@@ -125,12 +127,17 @@ decodeTimeOffset(data: dict, offsetTimeField: str) → Optional[tzinfo]
125
127
 
126
128
  ---
127
129
 
128
- <a href="../geopic_tag_reader/reader.py#L470"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
130
+ <a href="../geopic_tag_reader/reader.py#L501"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
129
131
 
130
132
  ## <kbd>function</kbd> `decodeGPSDateTime`
131
133
 
132
134
  ```python
133
- decodeGPSDateTime(data: dict, group: str) → Tuple[Optional[datetime], List[str]]
135
+ decodeGPSDateTime(
136
+ data: dict,
137
+ group: str,
138
+ lat: Optional[float] = None,
139
+ lon: Optional[float] = None
140
+ ) → Tuple[Optional[datetime], List[str]]
134
141
  ```
135
142
 
136
143
 
@@ -140,7 +147,7 @@ decodeGPSDateTime(data: dict, group: str) → Tuple[Optional[datetime], List[str
140
147
 
141
148
  ---
142
149
 
143
- <a href="../geopic_tag_reader/reader.py#L513"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
150
+ <a href="../geopic_tag_reader/reader.py#L552"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
144
151
 
145
152
  ## <kbd>function</kbd> `decodeSecondsAndMicroSeconds`
146
153
 
@@ -158,7 +165,7 @@ decodeSecondsAndMicroSeconds(
158
165
 
159
166
  ---
160
167
 
161
- <a href="../geopic_tag_reader/reader.py#L539"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
168
+ <a href="../geopic_tag_reader/reader.py#L578"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
162
169
 
163
170
  ## <kbd>function</kbd> `isExifTagUsable`
164
171
 
@@ -185,7 +192,7 @@ Is a given EXIF tag usable (not null and not an empty string)
185
192
 
186
193
  ---
187
194
 
188
- <a href="../geopic_tag_reader/reader.py#L16"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
195
+ <a href="../geopic_tag_reader/reader.py#L20"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
189
196
 
190
197
  ## <kbd>class</kbd> `CropValues`
191
198
  Cropped equirectangular pictures metadata
@@ -226,7 +233,7 @@ __init__(
226
233
 
227
234
  ---
228
235
 
229
- <a href="../geopic_tag_reader/reader.py#L37"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
236
+ <a href="../geopic_tag_reader/reader.py#L41"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
230
237
 
231
238
  ## <kbd>class</kbd> `GeoPicTags`
232
239
  Tags associated to a geolocated picture
@@ -237,7 +244,7 @@ Tags associated to a geolocated picture
237
244
 
238
245
  - <b>`lat`</b> (float): GPS Latitude (in WGS84)
239
246
  - <b>`lon`</b> (float): GPS Longitude (in WGS84)
240
- - <b>`ts`</b> (float): The capture date (as POSIX timestamp)
247
+ - <b>`ts`</b> (datetime): The capture date (date & time with timezone)
241
248
  - <b>`heading`</b> (int): Picture heading (in degrees, North = 0°, East = 90°, South = 180°, West = 270°)
242
249
  - <b>`type`</b> (str): The kind of picture (flat, equirectangular)
243
250
  - <b>`make`</b> (str): The camera manufacturer name
@@ -260,7 +267,7 @@ Implementation note: this needs to be sync with the PartialGeoPicTags structure
260
267
  __init__(
261
268
  lat: float,
262
269
  lon: float,
263
- ts: float,
270
+ ts: datetime,
264
271
  heading: Optional[int],
265
272
  type: str,
266
273
  make: Optional[str],
@@ -283,12 +290,12 @@ __init__(
283
290
 
284
291
  ---
285
292
 
286
- <a href="../geopic_tag_reader/reader.py#L73"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
293
+ <a href="../geopic_tag_reader/reader.py#L77"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
287
294
 
288
295
  ## <kbd>class</kbd> `InvalidExifException`
289
296
  Exception for invalid EXIF information from image
290
297
 
291
- <a href="../geopic_tag_reader/reader.py#L76"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
298
+ <a href="../geopic_tag_reader/reader.py#L80"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
292
299
 
293
300
  ### <kbd>method</kbd> `__init__`
294
301
 
@@ -306,7 +313,7 @@ __init__(msg)
306
313
 
307
314
  ---
308
315
 
309
- <a href="../geopic_tag_reader/reader.py#L80"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
316
+ <a href="../geopic_tag_reader/reader.py#L84"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
310
317
 
311
318
  ## <kbd>class</kbd> `PartialGeoPicTags`
312
319
  Tags associated to a geolocated picture when not all tags have been found
@@ -321,7 +328,7 @@ Implementation note: this needs to be sync with the GeoPicTags structure
321
328
  __init__(
322
329
  lat: Optional[float] = None,
323
330
  lon: Optional[float] = None,
324
- ts: Optional[float] = None,
331
+ ts: Optional[datetime] = None,
325
332
  heading: Optional[int] = None,
326
333
  type: Optional[str] = None,
327
334
  make: Optional[str] = None,
@@ -344,14 +351,14 @@ __init__(
344
351
 
345
352
  ---
346
353
 
347
- <a href="../geopic_tag_reader/reader.py#L101"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
354
+ <a href="../geopic_tag_reader/reader.py#L105"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
348
355
 
349
356
  ## <kbd>class</kbd> `PartialExifException`
350
357
  Exception for partial / missing EXIF information from image
351
358
 
352
359
  Contains a PartialGeoPicTags with all tags that have been read and the list of missing tags
353
360
 
354
- <a href="../geopic_tag_reader/reader.py#L108"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
361
+ <a href="../geopic_tag_reader/reader.py#L112"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
355
362
 
356
363
  ### <kbd>method</kbd> `__init__`
357
364
 
@@ -2,4 +2,4 @@
2
2
  GeoPicTagReader
3
3
  """
4
4
 
5
- __version__ = "1.0.6"
5
+ __version__ = "1.1.0"
@@ -22,7 +22,7 @@ def read(
22
22
 
23
23
  print("Latitude:", metadata.lat)
24
24
  print("Longitude:", metadata.lon)
25
- print("Timestamp:", metadata.ts)
25
+ print("Timestamp:", metadata.ts.isoformat())
26
26
  print("Heading:", metadata.heading)
27
27
  print("Type:", metadata.type)
28
28
  print("Make:", metadata.make)
@@ -7,11 +7,15 @@ import re
7
7
  import json
8
8
  from fractions import Fraction
9
9
  from geopic_tag_reader import camera
10
+ import timezonefinder # type: ignore
11
+ import pytz
10
12
 
11
13
  # This is a fix for invalid MakerNotes leading to picture not read at all
12
14
  # https://github.com/LeoHsiao1/pyexiv2/issues/58
13
15
  pyexiv2.set_log_level(4)
14
16
 
17
+ tz_finder = timezonefinder.TimezoneFinder()
18
+
15
19
 
16
20
  @dataclass
17
21
  class CropValues:
@@ -41,7 +45,7 @@ class GeoPicTags:
41
45
  Attributes:
42
46
  lat (float): GPS Latitude (in WGS84)
43
47
  lon (float): GPS Longitude (in WGS84)
44
- ts (float): The capture date (as POSIX timestamp)
48
+ ts (datetime): The capture date (date & time with timezone)
45
49
  heading (int): Picture heading (in degrees, North = 0°, East = 90°, South = 180°, West = 270°)
46
50
  type (str): The kind of picture (flat, equirectangular)
47
51
  make (str): The camera manufacturer name
@@ -58,7 +62,7 @@ class GeoPicTags:
58
62
 
59
63
  lat: float
60
64
  lon: float
61
- ts: float
65
+ ts: datetime.datetime
62
66
  heading: Optional[int]
63
67
  type: str
64
68
  make: Optional[str]
@@ -86,7 +90,7 @@ class PartialGeoPicTags:
86
90
 
87
91
  lat: Optional[float] = None
88
92
  lon: Optional[float] = None
89
- ts: Optional[float] = None
93
+ ts: Optional[datetime.datetime] = None
90
94
  heading: Optional[int] = None
91
95
  type: Optional[str] = None
92
96
  make: Optional[str] = None
@@ -142,6 +146,11 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
142
146
  except:
143
147
  pass
144
148
 
149
+ # Sanitize charset information
150
+ for k, v in data.items():
151
+ if isinstance(v, str):
152
+ data[k] = re.sub(r"charset=[^\s]+", "", v).strip()
153
+
145
154
  # Parse latitude/longitude
146
155
  lat, lon, llw = decodeLatLon(data, "Exif.GPSInfo")
147
156
  if len(llw) > 0:
@@ -163,13 +172,13 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
163
172
  raise InvalidExifException("Read longitude is out of WGS84 bounds (should be in [-180, 180])")
164
173
 
165
174
  # Parse date/time
166
- d, llw = decodeGPSDateTime(data, "Exif.GPSInfo")
175
+ d, llw = decodeGPSDateTime(data, "Exif.GPSInfo", lat, lon)
167
176
 
168
177
  if len(llw) > 0:
169
178
  warnings.extend(llw)
170
179
 
171
180
  if d is None:
172
- d, llw = decodeGPSDateTime(data, "Xmp.exif")
181
+ d, llw = decodeGPSDateTime(data, "Xmp.exif", lat, lon)
173
182
  if len(llw) > 0:
174
183
  warnings.extend(llw)
175
184
 
@@ -180,7 +189,7 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
180
189
  "Xmp.GPano.SourceImageCreateTime",
181
190
  ]:
182
191
  if d is None:
183
- d, llw = decodeDateTimeOriginal(data, exifField)
192
+ d, llw = decodeDateTimeOriginal(data, exifField, lat, lon)
184
193
  if len(llw) > 0:
185
194
  warnings.extend(llw)
186
195
 
@@ -313,7 +322,7 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
313
322
  PartialGeoPicTags(
314
323
  lat,
315
324
  lon,
316
- d.timestamp() if d else None,
325
+ d,
317
326
  heading,
318
327
  pic_type,
319
328
  make,
@@ -330,7 +339,7 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
330
339
  return GeoPicTags(
331
340
  lat,
332
341
  lon,
333
- d.timestamp(),
342
+ d,
334
343
  heading,
335
344
  pic_type,
336
345
  make,
@@ -429,7 +438,9 @@ def decodeLatLon(data: dict, group: str) -> Tuple[Optional[float], Optional[floa
429
438
  return (lat, lon, warnings)
430
439
 
431
440
 
432
- def decodeDateTimeOriginal(data: dict, datetimeField: str) -> Tuple[Optional[datetime.datetime], List[str]]:
441
+ def decodeDateTimeOriginal(
442
+ data: dict, datetimeField: str, lat: Optional[float] = None, lon: Optional[float] = None
443
+ ) -> Tuple[Optional[datetime.datetime], List[str]]:
433
444
  d = None
434
445
  warnings = []
435
446
 
@@ -442,7 +453,6 @@ def decodeDateTimeOriginal(data: dict, datetimeField: str) -> Tuple[Optional[dat
442
453
  secondsRaw, microsecondsRaw, msw = decodeSecondsAndMicroSeconds(
443
454
  timeRaw[2], data["Exif.Photo.SubSecTimeOriginal"] if isExifTagUsable(data, "Exif.Photo.SubSecTimeOriginal", float) else "0"
444
455
  )
445
- tz = decodeTimeOffset(data, f"Exif.Photo.OffsetTime{'Original' if 'DateTimeOriginal' in datetimeField else ''}")
446
456
  warnings += msw
447
457
 
448
458
  d = datetime.datetime.combine(
@@ -452,9 +462,30 @@ def decodeDateTimeOriginal(data: dict, datetimeField: str) -> Tuple[Optional[dat
452
462
  minutesRaw,
453
463
  secondsRaw,
454
464
  microsecondsRaw,
455
- tzinfo=tz or datetime.timezone.utc,
456
465
  ),
457
466
  )
467
+
468
+ # Timezone handling
469
+ # Try to read from EXIF
470
+ tz = decodeTimeOffset(data, f"Exif.Photo.OffsetTime{'Original' if 'DateTimeOriginal' in datetimeField else ''}")
471
+ if tz is not None:
472
+ d = d.replace(tzinfo=tz)
473
+
474
+ # Otherwise, try to deduct from coordinates
475
+ elif lon is not None and lat is not None:
476
+ tz_name = tz_finder.timezone_at(lng=lon, lat=lat)
477
+ if tz_name is not None:
478
+ d = pytz.timezone(tz_name).localize(d)
479
+ # Otherwise, default to UTC + warning
480
+ else:
481
+ d = d.replace(tzinfo=datetime.timezone.utc)
482
+ warnings.append("Precise timezone information not found, fallback to UTC")
483
+
484
+ # Otherwise, default to UTC + warning
485
+ else:
486
+ d = d.replace(tzinfo=datetime.timezone.utc)
487
+ warnings.append("Precise timezone information not found (and no GPS coordinates to help), fallback to UTC")
488
+
458
489
  except ValueError as e:
459
490
  warnings.append("Skipping original date/time (from " + datetimeField + ") as it was not recognized:\n\t" + str(e))
460
491
 
@@ -467,7 +498,9 @@ def decodeTimeOffset(data: dict, offsetTimeField: str) -> Optional[datetime.tzin
467
498
  return None
468
499
 
469
500
 
470
- def decodeGPSDateTime(data: dict, group: str) -> Tuple[Optional[datetime.datetime], List[str]]:
501
+ def decodeGPSDateTime(
502
+ data: dict, group: str, lat: Optional[float] = None, lon: Optional[float] = None
503
+ ) -> Tuple[Optional[datetime.datetime], List[str]]:
471
504
  d = None
472
505
  warnings = []
473
506
 
@@ -504,6 +537,12 @@ def decodeGPSDateTime(data: dict, group: str) -> Tuple[Optional[datetime.datetim
504
537
  ),
505
538
  )
506
539
 
540
+ # Set timezone from coordinates
541
+ if lon is not None and lat is not None:
542
+ tz_name = tz_finder.timezone_at(lng=lon, lat=lat)
543
+ if tz_name is not None:
544
+ d = d.astimezone(pytz.timezone(tz_name))
545
+
507
546
  except ValueError as e:
508
547
  warnings.append(f"Skipping GPS date/time ({group} group) as it was not recognized:\n\t{str(e)}")
509
548
 
@@ -3,11 +3,11 @@ from datetime import datetime, timedelta
3
3
  from dataclasses import dataclass
4
4
  from geopic_tag_reader.model import PictureType
5
5
  from enum import Enum
6
+ import timezonefinder # type: ignore
7
+ import pytz
6
8
 
7
9
  try:
8
10
  import pyexiv2 # type: ignore
9
- import timezonefinder # type: ignore
10
- import pytz
11
11
  except ImportError:
12
12
  raise Exception(
13
13
  """Impossible to write the exif tags without the '[write-exif]' dependency (that will need to install libexiv2).
@@ -7,7 +7,6 @@ from .conftest import FIXTURE_DIR, openImg
7
7
  def assertGeoPicTagsEquals(gpt, expectedDict):
8
8
  assert gpt.lat == expectedDict.get("lat")
9
9
  assert gpt.lon == expectedDict.get("lon")
10
- assert gpt.ts == expectedDict.get("ts")
11
10
  assert gpt.heading == expectedDict.get("heading")
12
11
  assert gpt.type == expectedDict.get("type")
13
12
  assert gpt.make == expectedDict.get("make")
@@ -17,6 +16,12 @@ def assertGeoPicTagsEquals(gpt, expectedDict):
17
16
  assert gpt.tagreader_warnings == expectedDict.get("tagreader_warnings", [])
18
17
  assert len(gpt.exif) > 0
19
18
 
19
+ if expectedDict.get("ts") is not None:
20
+ assert gpt.ts is not None
21
+ assert gpt.ts.isoformat() == expectedDict["ts"]
22
+ else:
23
+ assert gpt.ts is None
24
+
20
25
  if gpt.crop:
21
26
  assert expectedDict.get("crop") is not None
22
27
  assert gpt.crop.fullWidth == expectedDict["crop"].get("fullWidth")
@@ -37,7 +42,7 @@ def test_readPictureMetadata(datafiles):
37
42
  {
38
43
  "lat": 49.00688961988304,
39
44
  "lon": 1.9191854417991367,
40
- "ts": 1627550214.0,
45
+ "ts": "2021-07-29T11:16:54+02:00",
41
46
  "heading": 349,
42
47
  "type": "equirectangular",
43
48
  "make": "GoPro",
@@ -56,7 +61,7 @@ def test_readPictureMetadata_negCoords(datafiles):
56
61
  {
57
62
  "lat": 48.33756428166505,
58
63
  "lon": -1.9331088333333333,
59
- "ts": 1652453580.0,
64
+ "ts": "2022-05-13T16:53:00+02:00",
60
65
  "heading": 32,
61
66
  "type": "equirectangular",
62
67
  "make": "GoPro",
@@ -75,7 +80,7 @@ def test_readPictureMetadata_flat(datafiles):
75
80
  {
76
81
  "lat": 48.139852239480945,
77
82
  "lon": -1.9499731060073981,
78
- "ts": 1429976268.0,
83
+ "ts": "2015-04-25T15:37:48+02:00",
79
84
  "heading": 155,
80
85
  "type": "flat",
81
86
  "make": "OLYMPUS IMAGING CORP.",
@@ -93,7 +98,7 @@ def test_readPictureMetadata_flat2(datafiles):
93
98
  {
94
99
  "lat": 48.85779642035038,
95
100
  "lon": 2.3392783047650747,
96
- "ts": 1430744932.0,
101
+ "ts": "2015-05-04T13:08:52+02:00",
97
102
  "heading": 302,
98
103
  "type": "flat",
99
104
  "make": "Canon",
@@ -111,7 +116,7 @@ def test_readPictureMetadata_xmpHeading(datafiles):
111
116
  {
112
117
  "lat": 50.87070833333333,
113
118
  "lon": -1.5260916666666666,
114
- "ts": 1600008019.767,
119
+ "ts": "2020-09-13T15:40:19.767000+01:00",
115
120
  "heading": 67,
116
121
  "type": "equirectangular",
117
122
  "make": "Google",
@@ -130,7 +135,7 @@ def test_readPictureMetadata_noHeading(datafiles):
130
135
  {
131
136
  "lat": 48.15506638888889,
132
137
  "lon": -1.6844680555555556,
133
- "ts": 1666166194.0,
138
+ "ts": "2022-10-19T09:56:34+02:00",
134
139
  "heading": None,
135
140
  "type": "flat",
136
141
  "make": "SONY",
@@ -154,7 +159,7 @@ def test_readPictureMetadata_ricoh_theta(datafiles):
154
159
  "lon": 2.3205357914890987,
155
160
  "make": "RICOH",
156
161
  "model": "THETA m15",
157
- "ts": 1458911533.0,
162
+ "ts": "2016-03-25T14:12:13+01:00",
158
163
  "type": "equirectangular",
159
164
  },
160
165
  )
@@ -173,7 +178,7 @@ def test_readPictureMetadata_v4mpack(datafiles):
173
178
  "lon": -1.2761512389983616,
174
179
  "make": "STFMANI",
175
180
  "model": "V4MPOD 1",
176
- "ts": 1555417213.0,
181
+ "ts": "2019-04-16T14:20:13+02:00",
177
182
  "type": "equirectangular",
178
183
  "altitude": 34,
179
184
  },
@@ -192,7 +197,7 @@ def test_readPictureMetadata_a5000(datafiles):
192
197
  "lon": 2.51197323068765,
193
198
  "make": "OnePlus",
194
199
  "model": "ONEPLUS A5000",
195
- "ts": 1626797632.199995,
200
+ "ts": "2021-07-20T16:13:52.199995+02:00",
196
201
  "type": "flat",
197
202
  "altitude": 0,
198
203
  },
@@ -227,7 +232,7 @@ def test_readPictureMetadata_int_long_tag(datafiles):
227
232
  "lon": 4.700622,
228
233
  "make": None,
229
234
  "model": None,
230
- "ts": 1673515020.0,
235
+ "ts": "2023-01-12T09:17:00+01:00",
231
236
  "type": "flat",
232
237
  "tagreader_warnings": ["GPSLongitudeRef not found, assuming GPSLongitudeRef is East"],
233
238
  },
@@ -249,7 +254,7 @@ def test_readPictureMetadata_invalidGpsDate(datafiles):
249
254
  "lon": 2.358506155368721,
250
255
  "make": "samsung",
251
256
  "model": None,
252
- "ts": 1631101437.0754,
257
+ "ts": "2021-09-08T11:43:57.075400+02:00",
253
258
  "type": "flat",
254
259
  "altitude": 73,
255
260
  "tagreader_warnings": [
@@ -271,7 +276,7 @@ def test_readPictureMetadata_gpsDateStamp(datafiles):
271
276
  "lon": 2.4833194444444446,
272
277
  "make": "Apple",
273
278
  "model": "iPhone 12 Pro",
274
- "ts": 1682785851.565,
279
+ "ts": "2023-04-29T18:30:51.565000+02:00",
275
280
  "type": "flat",
276
281
  "altitude": 36,
277
282
  "tagreader_warnings": [
@@ -293,7 +298,7 @@ def test_readPictureMetadata_gpsDateString(datafiles):
293
298
  "lon": -1.0015555555555555,
294
299
  "make": "Apple",
295
300
  "model": "iPhone 8 Plus",
296
- "ts": 1564313142.529,
301
+ "ts": "2019-07-28T13:25:42.529000+02:00",
297
302
  "type": "flat",
298
303
  "altitude": 19,
299
304
  "tagreader_warnings": [
@@ -322,7 +327,7 @@ def test_readPictureMetadata_cropped(datafiles):
322
327
  "lon": 1.919185441804927,
323
328
  "make": "GoPro",
324
329
  "model": "Max",
325
- "ts": 1627550214.0,
330
+ "ts": "2021-07-29T11:16:54+02:00",
326
331
  "type": "equirectangular",
327
332
  "altitude": 93,
328
333
  "crop": {"fullWidth": 4032, "fullHeight": 2016, "width": 2150, "height": 1412, "top": 134, "left": 538},
@@ -341,7 +346,7 @@ def test_readPictureMetadata_datetimeoriginal(datafiles):
341
346
  "lon": -1.382302762883527,
342
347
  "make": "Motorola",
343
348
  "model": "XT1052",
344
- "ts": 1598866588.0,
349
+ "ts": "2020-08-31T09:36:28+02:00",
345
350
  "type": "flat",
346
351
  "altitude": 72,
347
352
  "tagreader_warnings": [
@@ -362,7 +367,7 @@ def test_readPictureMetadata_datetimeoriginal_decimal_milliseconds(datafiles):
362
367
  "lon": 121.15072633333334,
363
368
  "make": "BlackVue",
364
369
  "model": "DR900S-1CH",
365
- "ts": 1699748719.023,
370
+ "ts": "2023-11-12T00:25:19.023000+08:00",
366
371
  "type": "flat",
367
372
  "altitude": 29,
368
373
  "tagreader_warnings": [],
@@ -401,7 +406,7 @@ def test_readPictureWithoutCoord(datafiles):
401
406
  "lon": None,
402
407
  "make": "Apple",
403
408
  "model": "iPhone 12 Pro",
404
- "ts": 1682785851.565,
409
+ "ts": "2023-04-29T16:30:51.565000+00:00",
405
410
  "type": "flat",
406
411
  "altitude": 36,
407
412
  "tagreader_warnings": [
@@ -472,7 +477,7 @@ def test_readPictureWithLatitudeAsFloat(datafiles):
472
477
  "lon": 7.683081,
473
478
  "make": None,
474
479
  "model": "PULSAR",
475
- "ts": 1637075896.89,
480
+ "ts": "2021-11-16T16:18:16.890000+01:00",
476
481
  "type": "equirectangular",
477
482
  "altitude": None,
478
483
  "tagreader_warnings": [],
@@ -492,7 +497,7 @@ def test_readPictureMetadata_gps_date_time_stamp(datafiles):
492
497
  "lon": 7.683081,
493
498
  "make": None,
494
499
  "model": "PULSAR",
495
- "ts": 1637075896.89,
500
+ "ts": "2021-11-16T16:18:16.890000+01:00",
496
501
  "type": "equirectangular",
497
502
  "altitude": None,
498
503
  "tagreader_warnings": [],
@@ -512,7 +517,7 @@ def test_readPictureMetadata_insta360(datafiles):
512
517
  "lon": 3.482755555555556,
513
518
  "make": "Insta360",
514
519
  "model": "One X2.PHOTO_NORMAL",
515
- "ts": 1700662669.0,
520
+ "ts": "2023-11-22T14:17:49+01:00",
516
521
  "type": "equirectangular",
517
522
  "altitude": 84,
518
523
  "tagreader_warnings": [],
@@ -532,7 +537,7 @@ def test_readPictureMetadata_ricoh_noproj(datafiles):
532
537
  "lon": -1.5464416503906249,
533
538
  "make": "Ricoh",
534
539
  "model": "Theta S",
535
- "ts": 1672575342.0,
540
+ "ts": "2023-01-01T13:15:42+01:00",
536
541
  "type": "equirectangular",
537
542
  "altitude": None,
538
543
  "tagreader_warnings": [],
@@ -549,7 +554,7 @@ def test_readPictureMetadata_gopromax_flat(datafiles):
549
554
  {
550
555
  "lat": 47.22555109997222,
551
556
  "lon": -1.5631604999722222,
552
- "ts": 1708173992.0,
557
+ "ts": "2024-02-17T13:46:32+01:00",
553
558
  "heading": None,
554
559
  "type": "flat",
555
560
  "make": "GoPro",
@@ -572,7 +577,7 @@ def test_readPictureMetadata_broken_makernotes(datafiles):
572
577
  "lon": -1.7841980555555554,
573
578
  "make": "SONY",
574
579
  "model": "FDR-X1000V",
575
- "ts": 1599115820.0,
580
+ "ts": "2020-09-03T08:50:20+02:00",
576
581
  "type": "flat",
577
582
  "altitude": 99,
578
583
  "focal_length": 2.8,
@@ -590,7 +595,7 @@ def test_readPictureMetadata_datetime_offset(datafiles):
590
595
  {
591
596
  "lat": 48.33756428166505,
592
597
  "lon": -1.9331088333333333,
593
- "ts": 1652453651.0,
598
+ "ts": "2022-05-13T16:54:11+02:00",
594
599
  "heading": 32,
595
600
  "type": "equirectangular",
596
601
  "make": "GoPro",
@@ -610,7 +615,7 @@ def test_readPictureMetadata_gps_date_slash(datafiles):
610
615
  {
611
616
  "lat": 43.42541569992266,
612
617
  "lon": 1.3766216000638112,
613
- "ts": 1686640500.0,
618
+ "ts": "2023-06-13T09:15:00+02:00",
614
619
  "heading": None,
615
620
  "type": "flat",
616
621
  "make": None,
@@ -623,3 +628,9 @@ def test_readPictureMetadata_gps_date_slash(datafiles):
623
628
  ],
624
629
  },
625
630
  )
631
+
632
+
633
+ @pytest.mark.datafiles(os.path.join(FIXTURE_DIR, "charset.jpg"))
634
+ def test_readPictureMetadata_charset(datafiles):
635
+ result = reader.readPictureMetadata(openImg(str(datafiles) + "/charset.jpg"))
636
+ assert result.exif["Exif.Photo.UserComment"] == "CD31 31_D0003_51_600"
@@ -18,7 +18,7 @@ def test_writePictureMetadata_capture_time(datafiles):
18
18
  image_file_upd = writer.writePictureMetadata(img_orig, writer.PictureMetadata(capture_time=capture_time))
19
19
  tags = reader.readPictureMetadata(image_file_upd)
20
20
 
21
- assert datetime.fromtimestamp(tags.ts, tz=pytz.UTC) == capture_time
21
+ assert tags.ts == capture_time
22
22
 
23
23
  # we also check specific tags:
24
24
  assert tags.exif["Exif.Photo.DateTimeOriginal"] == "2023-06-01 12:48:01"
@@ -36,7 +36,7 @@ def test_writePictureMetadata_capture_time_no_timezone(datafiles):
36
36
  tags = reader.readPictureMetadata(image_file_upd)
37
37
 
38
38
  paris = pytz.timezone("Europe/Paris")
39
- assert datetime.fromtimestamp(tags.ts, tz=pytz.UTC) == paris.localize(capture_time).astimezone(pytz.UTC)
39
+ assert tags.ts == paris.localize(capture_time).astimezone(pytz.UTC)
40
40
 
41
41
  # DateTimeOriginal should be a local time, so 12:48:01 localized in Europe/Paris timezome (since it's where the picture has been taken)
42
42
  assert tags.exif["Exif.Photo.DateTimeOriginal"] == "2023-06-01 12:48:01"
@@ -78,7 +78,7 @@ def test_writePictureMetadata_capture_time_no_position_in_file_but_overriden(dat
78
78
  tags = reader.readPictureMetadata(image_file_upd)
79
79
 
80
80
  paris = pytz.timezone("Europe/Paris")
81
- assert datetime.fromtimestamp(tags.ts, tz=pytz.UTC) == paris.localize(capture_time).astimezone(pytz.UTC)
81
+ assert tags.ts == paris.localize(capture_time).astimezone(pytz.UTC)
82
82
 
83
83
  # DateTimeOriginal should be a local time, so 12:48:01 localized in Europe/Paris timezome (since it's where the picture has been taken)
84
84
  assert tags.exif["Exif.Photo.DateTimeOriginal"] == "2023-06-01 12:48:01"
@@ -11,6 +11,9 @@ dependencies = [
11
11
  "typer ~= 0.12",
12
12
  "xmltodict ~= 0.13",
13
13
  "pyexiv2 == 2.8.3",
14
+ "timezonefinder == 6.2.0",
15
+ "pytz ~= 2023.3",
16
+ "types-pytz ~= 2023.3.0.1",
14
17
  ]
15
18
  requires-python = ">=3.8"
16
19
 
@@ -33,9 +36,6 @@ dev = [
33
36
  build = ["flit ~= 3.8.0"]
34
37
  # optional dependencies to be able to write exif tags
35
38
  write-exif = [
36
- "timezonefinder == 6.2.0",
37
- "pytz ~= 2023.3",
38
- "types-pytz ~= 2023.3.0.1",
39
39
  "python-dateutil ~= 2.8.2",
40
40
  ]
41
41