geopic-tag-reader 1.0.5__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.
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/CHANGELOG.md +41 -15
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/Makefile +1 -1
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/PKG-INFO +10 -11
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/docs/reader.md +29 -22
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/geopic_tag_reader/__init__.py +2 -1
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/geopic_tag_reader/main.py +1 -1
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/geopic_tag_reader/reader.py +51 -12
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/geopic_tag_reader/writer.py +2 -2
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/pyproject.toml +9 -11
- geopic_tag_reader-1.1.0/tests/fixtures/charset.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/test_reader.py +39 -28
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/test_writer.py +3 -3
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/.gitignore +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/.gitlab-ci.yml +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/.pre-commit-config.yaml +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/CODE_OF_CONDUCT.md +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/LICENSE +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/README.md +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/docs/.pages +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/docs/API_USAGE.md +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/docs/CLI_USAGE.md +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/docs/Develop.md +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/docs/Install.md +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/docs/camera.md +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/docs/model.md +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/docs/writer.md +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/geopic_tag_reader/camera.py +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/geopic_tag_reader/model.py +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/geopic_tag_reader/py.typed +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/pytest.ini +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/__init__.py +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/conftest.py +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/1.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/IMG_20210720_144918.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/IMG_20210720_161352.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/a1.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/b1.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/broken_makernotes.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/c1.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/cropped.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/d1.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/datetime_ms_float.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/datetime_offset.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/e1.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/flat.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/gopromax_flat.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/gps_date_slash.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/gps_date_time_stamp.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/img_Ricoh_Theta.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/img_V4MPack.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/img_categorisee.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/img_datetimeoriginal.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/img_gps_date_string.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/img_gps_datestamp.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/img_gps_sotm.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/img_invalid_gps_date.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/img_without_coord.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/img_without_dt.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/img_without_exif_tags.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/insta360_date.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/int_long_tag.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/out_of_bounds_lat.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/out_of_bounds_lon.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/pic_with_float_lat.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/ricoh_theta_no_projection.jpg +0 -0
- {geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/test_main.py +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
|
|
2
3
|
All notable changes to this project will be documented in this file.
|
|
3
4
|
|
|
4
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
@@ -6,137 +7,162 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
7
|
|
|
7
8
|
## [Unreleased]
|
|
8
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
|
+
|
|
18
|
+
## [1.0.6] - 2024-04-02
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Bump some dependencies, the most important one being [typer](https://typer.tiangolo.com/) since it removes the need for typer-cli.
|
|
9
23
|
|
|
10
24
|
## [1.0.5] - 2024-03-06
|
|
11
25
|
|
|
12
26
|
### Fixed
|
|
27
|
+
|
|
13
28
|
- Automatic detection of 360° pictures based on make and model also checks for image dimensions (to avoid false positive, when 360° camera can also take flat pictures).
|
|
14
29
|
- Slash character in GPS date stamp was not correctly interpreted.
|
|
15
30
|
|
|
16
|
-
|
|
17
31
|
## [1.0.4] - 2024-02-12
|
|
18
32
|
|
|
19
33
|
### Changed
|
|
20
|
-
- When using date/time not coming from GPS, offset time EXIF field are also read (`Exif.Photo.OffsetTimeOriginal` or `Exif.Photo.OffsetTime` depending on used date/time EXIF tag).
|
|
21
34
|
|
|
35
|
+
- When using date/time not coming from GPS, offset time EXIF field are also read (`Exif.Photo.OffsetTimeOriginal` or `Exif.Photo.OffsetTime` depending on used date/time EXIF tag).
|
|
22
36
|
|
|
23
37
|
## [1.0.3] - 2023-12-18
|
|
24
38
|
|
|
25
39
|
### Added
|
|
40
|
+
|
|
26
41
|
- Support reading date from EXIF fields `Exif.Image.DateTime` and `Xmp.GPano.SourceImageCreateTime`.
|
|
27
42
|
- Auto-detect if a picture is 360° based on make and model.
|
|
28
43
|
|
|
29
44
|
### Fixed
|
|
30
|
-
- Avoid failures for pictures with invalid offset for maker notes (due to PyExiv2 log level).
|
|
31
45
|
|
|
46
|
+
- Avoid failures for pictures with invalid offset for maker notes (due to PyExiv2 log level).
|
|
32
47
|
|
|
33
48
|
## [1.0.2] - 2023-11-20
|
|
34
49
|
|
|
35
50
|
### Added
|
|
51
|
+
|
|
36
52
|
- A warning is thrown when microseconds values between decimal seconds and sub-second time fields are not matching.
|
|
37
53
|
|
|
38
54
|
### Changed
|
|
39
|
-
- Fraction values for date, time and GPS coordinates are supported.
|
|
40
55
|
|
|
56
|
+
- Fraction values for date, time and GPS coordinates are supported.
|
|
41
57
|
|
|
42
58
|
## [1.0.1] - 2023-11-17
|
|
43
59
|
|
|
44
60
|
### Fixed
|
|
45
|
-
- `DateTimeOriginal` field wasn't correctly read when seconds value was decimal.
|
|
46
61
|
|
|
62
|
+
- `DateTimeOriginal` field wasn't correctly read when seconds value was decimal.
|
|
47
63
|
|
|
48
64
|
## [1.0.0] - 2023-10-18
|
|
49
65
|
|
|
50
66
|
### Added
|
|
67
|
+
|
|
51
68
|
- Add subseconds to written metadata
|
|
52
69
|
- Read altitude in metadata
|
|
53
70
|
- Write direction and altitude metadata
|
|
54
71
|
- Add `additional_exif` tags in `writePictureMetadata`
|
|
55
72
|
|
|
56
73
|
### Changed
|
|
57
|
-
- EXIF list of tags now uses the [Exiv2](https://exiv2.org/metadata.html) notation (example `Exif.Image.Artist`) in returned data. To do this, pyexiv2 dependency is always necessary, and Pillow dependency has been removed. As a consequence, `readPictureMetadata` function __now takes in input `bytes`__ read from picture file instead of Pillow image. This is a breaking change.
|
|
58
|
-
- Use overriden metadata is available to localize overriden capture_time
|
|
59
74
|
|
|
75
|
+
- EXIF list of tags now uses the [Exiv2](https://exiv2.org/metadata.html) notation (example `Exif.Image.Artist`) in returned data. To do this, pyexiv2 dependency is always necessary, and Pillow dependency has been removed. As a consequence, `readPictureMetadata` function **now takes in input `bytes`** read from picture file instead of Pillow image. This is a breaking change.
|
|
76
|
+
- Use overriden metadata is available to localize overriden capture_time
|
|
60
77
|
|
|
61
78
|
## [0.4.1] - 2023-09-08
|
|
62
79
|
|
|
63
80
|
### Added
|
|
64
|
-
- Latitude and longitude values are checked to verify they fit into WGS84 projection bounds (-180, -90, 180, 90).
|
|
65
81
|
|
|
82
|
+
- Latitude and longitude values are checked to verify they fit into WGS84 projection bounds (-180, -90, 180, 90).
|
|
66
83
|
|
|
67
84
|
## [0.4.0] - 2023-09-01
|
|
68
85
|
|
|
69
86
|
### Added
|
|
87
|
+
|
|
70
88
|
- When a picture does not contain a mandatory exif tag (coordinates or datetime), a `PartialExifException` is thrown containing some information about what has been parsed and what is missing.
|
|
71
89
|
|
|
72
90
|
## [0.3.1] - 2023-07-31
|
|
73
91
|
|
|
74
92
|
### Added
|
|
93
|
+
|
|
75
94
|
- A way to write exif lon/lat and type tags.
|
|
76
95
|
|
|
77
96
|
## [0.3.0] - 2023-07-31
|
|
78
97
|
|
|
79
98
|
### Added
|
|
99
|
+
|
|
80
100
|
- Support of any date/time separator for EXIF tag `DateTimeOriginal`
|
|
81
101
|
- A way to write exif tags. To use this, you need to install this library with the extra `[write-exif]`.
|
|
82
102
|
|
|
83
|
-
|
|
84
103
|
## [0.2.0] - 2023-07-13
|
|
85
104
|
|
|
86
105
|
### Added
|
|
106
|
+
|
|
87
107
|
- Support of cropped equirectangular panoramas
|
|
88
108
|
|
|
89
109
|
### Changed
|
|
110
|
+
|
|
90
111
|
- Support python 3.8
|
|
91
112
|
|
|
92
113
|
## [0.1.3]
|
|
93
114
|
|
|
94
115
|
### Changed
|
|
116
|
+
|
|
95
117
|
- Bump [Typer](typer.tiangolo.com/) version, and use fork of [Typer-cli](https://gitlab.com/geovisio/infra/typer-cli)
|
|
96
118
|
|
|
97
119
|
## [0.1.2]
|
|
98
120
|
|
|
99
121
|
### Added
|
|
100
|
-
- Full typing support ([PEP 484](https://peps.python.org/pep-0484/) and [PEP 561](https://peps.python.org/pep-0561/))
|
|
101
122
|
|
|
123
|
+
- Full typing support ([PEP 484](https://peps.python.org/pep-0484/) and [PEP 561](https://peps.python.org/pep-0561/))
|
|
102
124
|
|
|
103
125
|
## [0.1.1]
|
|
104
126
|
|
|
105
127
|
### Added
|
|
106
|
-
- Support of Mapillary tags stored in EXIF tag `ImageDescription`
|
|
107
128
|
|
|
129
|
+
- Support of Mapillary tags stored in EXIF tag `ImageDescription`
|
|
108
130
|
|
|
109
131
|
## [0.1.0]
|
|
110
132
|
|
|
111
133
|
### Added
|
|
134
|
+
|
|
112
135
|
- If GPS Date or time can't be read, fallbacks to Original Date EXIF tag associated with a reader warning
|
|
113
136
|
- New EXIF tags are supported: `GPSDateTime`
|
|
114
137
|
|
|
115
138
|
### Changed
|
|
139
|
+
|
|
116
140
|
- `tag_reader:warning` property has been moved from EXIF, and is now available as a direct property named `tagreader_warnings` of `GeoPicTags` class
|
|
117
141
|
- Reader now supports `GPSLatitude` and `GPSLongitude` stored as decimal values instead of tuple
|
|
118
142
|
- Reader now supports reading `FocalLength` written in `NUMBER/NUMBER` format
|
|
119
143
|
- If EXIF tags for heading `PoseHeadingDegrees` and `GPSImgDirection` have contradicting values, we use by default `GPSImgDirection` value and issue a warning, instead of raising an error
|
|
120
144
|
|
|
121
145
|
### Fixed
|
|
122
|
-
- EXIF tag `SubsecTimeOriginal` was not correctly read due to a typo
|
|
123
146
|
|
|
147
|
+
- EXIF tag `SubsecTimeOriginal` was not correctly read due to a typo
|
|
124
148
|
|
|
125
149
|
## [0.0.2] - 2023-05-10
|
|
126
150
|
|
|
127
151
|
### Added
|
|
152
|
+
|
|
128
153
|
- EXIF tag `UserComment` is now read and available in raw `exif` tags
|
|
129
154
|
- If not set, `GPSLatitudeRef` defaults to North and `GPSLongitudeRef` defaults to East
|
|
130
155
|
- A new `tag_reader:warning` property lists non-blocking warnings raised while reading EXIF tags
|
|
131
156
|
|
|
132
|
-
|
|
133
157
|
## [0.0.1] - 2023-03-31
|
|
134
158
|
|
|
135
159
|
### Added
|
|
136
|
-
- EXIF tag reading methods extracted from [GeoVisio API](https://gitlab.com/geovisio/api)
|
|
137
160
|
|
|
161
|
+
- EXIF tag reading methods extracted from [GeoVisio API](https://gitlab.com/geovisio/api)
|
|
138
162
|
|
|
139
|
-
[Unreleased]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.0
|
|
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
|
|
165
|
+
[1.0.6]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.0.5...1.0.6
|
|
140
166
|
[1.0.5]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.0.4...1.0.5
|
|
141
167
|
[1.0.4]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.0.3...1.0.4
|
|
142
168
|
[1.0.3]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.0.2...1.0.3
|
|
@@ -14,7 +14,7 @@ fmt: ## Format code
|
|
|
14
14
|
ci: type-check fmt test ## Run all check like the ci
|
|
15
15
|
|
|
16
16
|
docs: ## Generates documentation from Typer embedded docs
|
|
17
|
-
python -m
|
|
17
|
+
python -m typer ./geopic_tag_reader/main.py utils docs --name geopic-tag-reader --output docs/CLI_USAGE.md
|
|
18
18
|
lazydocs --overview-file API_USAGE.md --ignored-modules main --output-path docs/ geopic_tag_reader/
|
|
19
19
|
|
|
20
20
|
help: ## Print this help message
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: geopic-tag-reader
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: GeoPicTagReader
|
|
5
5
|
Author-email: Adrien PAVIE <panieravide@riseup.net>
|
|
6
6
|
Requires-Python: >=3.8
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
-
Requires-Dist: typer ~= 0.
|
|
10
|
-
Requires-Dist: xmltodict ~= 0.13
|
|
9
|
+
Requires-Dist: typer ~= 0.12
|
|
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
|
-
Requires-Dist: black ~=
|
|
14
|
-
Requires-Dist: mypy ~= 1.
|
|
16
|
+
Requires-Dist: black ~= 24.3 ; extra == "dev"
|
|
17
|
+
Requires-Dist: mypy ~= 1.9 ; extra == "dev"
|
|
15
18
|
Requires-Dist: pytest ~= 7.2.0 ; extra == "dev"
|
|
16
|
-
Requires-Dist: pytest-datafiles ~=
|
|
17
|
-
Requires-Dist: typer-cli-forked ~= 0.0.14 ; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-datafiles ~= 3.0 ; extra == "dev"
|
|
18
20
|
Requires-Dist: lazydocs ~= 0.4.8 ; extra == "dev"
|
|
19
|
-
Requires-Dist: types-xmltodict ~= 0.13
|
|
21
|
+
Requires-Dist: types-xmltodict ~= 0.13 ; extra == "dev"
|
|
20
22
|
Requires-Dist: pre-commit ~= 3.3.3 ; extra == "dev"
|
|
21
|
-
Requires-Dist: timezonefinder == 6.2.0 ; extra == "write-exif"
|
|
22
|
-
Requires-Dist: pytz ~= 2023.3 ; extra == "write-exif"
|
|
23
|
-
Requires-Dist: types-pytz ~= 2023.3.0.1 ; extra == "write-exif"
|
|
24
23
|
Requires-Dist: python-dateutil ~= 2.8.2 ; extra == "write-exif"
|
|
25
24
|
Project-URL: Home, https://gitlab.com/geovisio/geo-picture-tag-reader
|
|
26
25
|
Provides-Extra: build
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
<a href="../geopic_tag_reader/reader.py#
|
|
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#
|
|
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#
|
|
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#
|
|
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#
|
|
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#
|
|
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#
|
|
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#
|
|
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(
|
|
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#
|
|
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#
|
|
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#
|
|
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#
|
|
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> (
|
|
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:
|
|
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#
|
|
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#
|
|
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#
|
|
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[
|
|
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#
|
|
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#
|
|
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
|
|
|
@@ -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 (
|
|
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:
|
|
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[
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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).
|
|
@@ -8,9 +8,12 @@ license = {file = "LICENSE"}
|
|
|
8
8
|
classifiers = ["License :: OSI Approved :: MIT License"]
|
|
9
9
|
dynamic = ["version", "description"]
|
|
10
10
|
dependencies = [
|
|
11
|
-
"typer ~= 0.
|
|
12
|
-
"xmltodict ~= 0.13
|
|
11
|
+
"typer ~= 0.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
|
|
|
@@ -22,22 +25,17 @@ geopic-tag-reader="geopic_tag_reader.main:app"
|
|
|
22
25
|
|
|
23
26
|
[project.optional-dependencies]
|
|
24
27
|
dev = [
|
|
25
|
-
"black ~=
|
|
26
|
-
"mypy ~= 1.
|
|
28
|
+
"black ~= 24.3",
|
|
29
|
+
"mypy ~= 1.9",
|
|
27
30
|
"pytest ~= 7.2.0",
|
|
28
|
-
"pytest-datafiles ~=
|
|
29
|
-
"typer-cli-forked ~= 0.0.14", # fork dependency needed until https://github.com/tiangolo/typer-cli/pull/120 is merged.
|
|
30
|
-
#"typer-cli ~= 0.0.13",
|
|
31
|
+
"pytest-datafiles ~= 3.0",
|
|
31
32
|
"lazydocs ~= 0.4.8",
|
|
32
|
-
"types-xmltodict ~= 0.13
|
|
33
|
+
"types-xmltodict ~= 0.13",
|
|
33
34
|
"pre-commit ~= 3.3.3",
|
|
34
35
|
]
|
|
35
36
|
build = ["flit ~= 3.8.0"]
|
|
36
37
|
# optional dependencies to be able to write exif tags
|
|
37
38
|
write-exif = [
|
|
38
|
-
"timezonefinder == 6.2.0",
|
|
39
|
-
"pytz ~= 2023.3",
|
|
40
|
-
"types-pytz ~= 2023.3.0.1",
|
|
41
39
|
"python-dateutil ~= 2.8.2",
|
|
42
40
|
]
|
|
43
41
|
|
|
Binary file
|
|
@@ -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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
138
|
+
"ts": "2022-10-19T09:56:34+02:00",
|
|
134
139
|
"heading": None,
|
|
135
140
|
"type": "flat",
|
|
136
141
|
"make": "SONY",
|
|
@@ -143,7 +148,7 @@ def test_readPictureMetadata_noHeading(datafiles):
|
|
|
143
148
|
|
|
144
149
|
@pytest.mark.datafiles(os.path.join(FIXTURE_DIR, "img_Ricoh_Theta.jpg"))
|
|
145
150
|
def test_readPictureMetadata_ricoh_theta(datafiles):
|
|
146
|
-
for f in datafiles.
|
|
151
|
+
for f in datafiles.iterdir():
|
|
147
152
|
result = reader.readPictureMetadata(openImg(str(f)))
|
|
148
153
|
assertGeoPicTagsEquals(
|
|
149
154
|
result,
|
|
@@ -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":
|
|
162
|
+
"ts": "2016-03-25T14:12:13+01:00",
|
|
158
163
|
"type": "equirectangular",
|
|
159
164
|
},
|
|
160
165
|
)
|
|
@@ -162,7 +167,7 @@ def test_readPictureMetadata_ricoh_theta(datafiles):
|
|
|
162
167
|
|
|
163
168
|
@pytest.mark.datafiles(os.path.join(FIXTURE_DIR, "img_V4MPack.jpg"))
|
|
164
169
|
def test_readPictureMetadata_v4mpack(datafiles):
|
|
165
|
-
for f in datafiles.
|
|
170
|
+
for f in datafiles.iterdir():
|
|
166
171
|
result = reader.readPictureMetadata(openImg(str(f)))
|
|
167
172
|
assertGeoPicTagsEquals(
|
|
168
173
|
result,
|
|
@@ -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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/img_without_exif_tags.jpg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{geopic_tag_reader-1.0.5 → geopic_tag_reader-1.1.0}/tests/fixtures/ricoh_theta_no_projection.jpg
RENAMED
|
File without changes
|
|
File without changes
|