geopic-tag-reader 1.1.5__tar.gz → 1.2.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.1.5 → geopic_tag_reader-1.2.0}/.gitlab-ci.yml +14 -14
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/CHANGELOG.md +15 -1
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/PKG-INFO +1 -1
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/docs/index.md +6 -6
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/docs/tech/api_reference.md +4 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/geopic_tag_reader/__init__.py +1 -1
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/geopic_tag_reader/main.py +1 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/geopic_tag_reader/reader.py +28 -40
- geopic_tag_reader-1.2.0/geopic_tag_reader/sequence.py +301 -0
- geopic_tag_reader-1.2.0/geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- geopic_tag_reader-1.2.0/geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.po +165 -0
- geopic_tag_reader-1.2.0/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.po +22 -22
- geopic_tag_reader-1.2.0/geopic_tag_reader/translations/es/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- geopic_tag_reader-1.1.5/geopic_tag_reader/translations/geopic_tag_reader.pot → geopic_tag_reader-1.2.0/geopic_tag_reader/translations/es/LC_MESSAGES/geopic_tag_reader.po +4 -5
- geopic_tag_reader-1.2.0/geopic_tag_reader/translations/geopic_tag_reader.pot +147 -0
- geopic_tag_reader-1.1.5/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/.gitignore +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/.pre-commit-config.yaml +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/CODE_OF_CONDUCT.md +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/LICENSE +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/Makefile +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/README.md +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/docs/develop.md +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/docs/install.md +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/docs/tech/cli.md +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/geopic_tag_reader/camera.py +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/geopic_tag_reader/i18n.py +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/geopic_tag_reader/model.py +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/geopic_tag_reader/py.typed +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.po +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/geopic_tag_reader/writer.py +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/mkdocs.yml +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/pyproject.toml +0 -0
- {geopic_tag_reader-1.1.5 → geopic_tag_reader-1.2.0}/pytest.ini +0 -0
|
@@ -38,20 +38,20 @@ tests-writer:
|
|
|
38
38
|
- make i18n-po2code
|
|
39
39
|
- pytest -s -vv
|
|
40
40
|
|
|
41
|
-
tests-deploy_pypi:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
41
|
+
# tests-deploy_pypi:
|
|
42
|
+
# stage: deploy
|
|
43
|
+
# image: python:3.8
|
|
44
|
+
# variables:
|
|
45
|
+
# FLIT_INDEX_URL: https://test.pypi.org/legacy/
|
|
46
|
+
# FLIT_USERNAME: $TEST_FLIT_USERNAME
|
|
47
|
+
# FLIT_PASSWORD: $TEST_FLIT_PASSWORD
|
|
48
|
+
# script:
|
|
49
|
+
# - apt update && apt install -y gcc git gettext
|
|
50
|
+
# - pip install .[build]
|
|
51
|
+
# - make i18n-po2code
|
|
52
|
+
# - flit publish
|
|
53
|
+
# only:
|
|
54
|
+
# - develop
|
|
55
55
|
|
|
56
56
|
deploy_pypi:
|
|
57
57
|
stage: deploy
|
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.0] - 2024-07-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- A new module `sequence` is available through Python to dispatch a set of pictures (based on their metadata) into several sequences, based on de-duplicate and split parameters. This is based on existing code previously stored in [command-line client](https://gitlab.com/panoramax/clients/cli), moved here to be shared between API and CLI.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Reader offers a `yaw` value (360° sphere correction), distinct from `heading` (GPS direction). EXIF tags are read a bit differently to reflect this : `yaw` comes from `Xmp.Camera.Yaw & Xmp.GPano.PoseHeadingDegrees`, `heading` from `Exif.GPSInfo.GPSImgDirection & MAPCompassHeading`.
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
- Previously used values `Exif.GPSInfo.GPS(Pitch|Roll)` and `Xmp.GPano.InitialView(Pitch|Roll)Degrees` are dropped, first one for not being standard, second one for not being correct to use for pitch/roll (prefer `Xmp.GPano.Pose(Pitch|Roll)Degrees`).
|
|
22
|
+
|
|
10
23
|
## [1.1.5] - 2024-07-10
|
|
11
24
|
|
|
12
25
|
### Fixed
|
|
@@ -199,7 +212,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
199
212
|
|
|
200
213
|
- EXIF tag reading methods extracted from [GeoVisio API](https://gitlab.com/panoramax/server/api)
|
|
201
214
|
|
|
202
|
-
[Unreleased]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.
|
|
215
|
+
[Unreleased]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.2.0...main
|
|
216
|
+
[1.2.0]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.1.5...1.2.0
|
|
203
217
|
[1.1.5]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.1.4...1.1.5
|
|
204
218
|
[1.1.4]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.1.3...1.1.4
|
|
205
219
|
[1.1.3]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.1.2...1.1.3
|
|
@@ -50,7 +50,6 @@ The following EXIF tags are recognized and used if defined, but are **optional**
|
|
|
50
50
|
Image orientation is read from
|
|
51
51
|
|
|
52
52
|
- `GPSImgDirection`
|
|
53
|
-
- `GPano:PoseHeadingDegrees`
|
|
54
53
|
- or in [Mapillary](https://www.mapillary.com/) tags: `MAPCompassHeading`
|
|
55
54
|
|
|
56
55
|
#### :material-timer: Milliseconds in date
|
|
@@ -80,21 +79,22 @@ Camera focal length (to get precise field of view) is read from:
|
|
|
80
79
|
- `Exif.Image.FocalLength`
|
|
81
80
|
- `Exif.Photo.FocalLength`
|
|
82
81
|
|
|
83
|
-
#### :octicons-horizontal-rule-16: Pitch and
|
|
82
|
+
#### :octicons-horizontal-rule-16: Yaw, Pitch and Roll
|
|
83
|
+
|
|
84
|
+
Yaw value is read from:
|
|
85
|
+
|
|
86
|
+
- `Xmp.Camera.Yaw`
|
|
87
|
+
- `Xmp.GPano.PoseHeadingDegrees`
|
|
84
88
|
|
|
85
89
|
Pitch value is read from:
|
|
86
90
|
|
|
87
91
|
- `Xmp.Camera.Pitch`
|
|
88
|
-
- `Exif.GPSInfo.GPSPitch`
|
|
89
92
|
- `Xmp.GPano.PosePitchDegrees`
|
|
90
|
-
- `Xmp.GPano.InitialViewPitchDegrees`
|
|
91
93
|
|
|
92
94
|
Roll value is read from:
|
|
93
95
|
|
|
94
96
|
- `Xmp.Camera.Roll`
|
|
95
|
-
- `Exif.GPSInfo.GPSRoll`
|
|
96
97
|
- `Xmp.GPano.PoseRollDegrees`
|
|
97
|
-
- `Xmp.GPano.InitialViewRollDegrees`
|
|
98
98
|
|
|
99
99
|
#### ⛰️ Altitude
|
|
100
100
|
|
|
@@ -47,7 +47,7 @@ class GeoPicTags:
|
|
|
47
47
|
lat (float): GPS Latitude (in WGS84)
|
|
48
48
|
lon (float): GPS Longitude (in WGS84)
|
|
49
49
|
ts (datetime): The capture date (date & time with timezone)
|
|
50
|
-
heading (int): Picture heading
|
|
50
|
+
heading (int): Picture GPS heading (in degrees, North = 0°, East = 90°, South = 180°, West = 270°). Value is computed based on image center (if yaw=0°)
|
|
51
51
|
type (str): The kind of picture (flat, equirectangular)
|
|
52
52
|
make (str): The camera manufacturer name
|
|
53
53
|
model (str): The camera model name
|
|
@@ -58,6 +58,7 @@ class GeoPicTags:
|
|
|
58
58
|
altitude (float): altitude (in m) (optional)
|
|
59
59
|
pitch (float): Picture pitch angle, compared to horizon (in degrees, bottom = -90°, horizon = 0°, top = 90°)
|
|
60
60
|
roll (float): Picture roll angle, on a right/left axis (in degrees, left-arm down = -90°, flat = 0°, right-arm down = 90°)
|
|
61
|
+
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
|
|
61
62
|
|
|
62
63
|
|
|
63
64
|
Implementation note: this needs to be sync with the PartialGeoPicTags structure
|
|
@@ -77,6 +78,7 @@ class GeoPicTags:
|
|
|
77
78
|
altitude: Optional[float] = None
|
|
78
79
|
pitch: Optional[float] = None
|
|
79
80
|
roll: Optional[float] = None
|
|
81
|
+
yaw: Optional[float] = None
|
|
80
82
|
|
|
81
83
|
|
|
82
84
|
class InvalidExifException(Exception):
|
|
@@ -111,6 +113,7 @@ class PartialGeoPicTags:
|
|
|
111
113
|
altitude: Optional[float] = None
|
|
112
114
|
pitch: Optional[float] = None
|
|
113
115
|
roll: Optional[float] = None
|
|
116
|
+
yaw: Optional[float] = None
|
|
114
117
|
|
|
115
118
|
|
|
116
119
|
class PartialExifException(Exception):
|
|
@@ -226,57 +229,40 @@ def readPictureMetadata(picture: bytes, lang_code: str = "en") -> GeoPicTags:
|
|
|
226
229
|
except Exception as e:
|
|
227
230
|
warnings.append(_("Skipping Mapillary date/time as it was not recognized: {v}").format(v=data["MAPGpsTime"]))
|
|
228
231
|
|
|
229
|
-
# Heading
|
|
232
|
+
# GPS Heading
|
|
230
233
|
heading = None
|
|
231
|
-
if isExifTagUsable(data, "
|
|
232
|
-
gpsDir = int(round(float(Fraction(data["Exif.GPSInfo.GPSImgDirection"]))))
|
|
233
|
-
gpanoHeading = int(round(float(data["Xmp.GPano.PoseHeadingDegrees"])))
|
|
234
|
-
if gpsDir > 0 and gpanoHeading == 0:
|
|
235
|
-
heading = gpsDir
|
|
236
|
-
elif gpsDir == 0 and gpanoHeading > 0:
|
|
237
|
-
heading = gpanoHeading
|
|
238
|
-
else:
|
|
239
|
-
if gpsDir != gpanoHeading:
|
|
240
|
-
warnings.append(_("Contradicting heading values found, GPSImgDirection value is used"))
|
|
241
|
-
heading = gpsDir
|
|
242
|
-
|
|
243
|
-
elif isExifTagUsable(data, "Xmp.GPano.PoseHeadingDegrees", float):
|
|
244
|
-
heading = int(round(float(data["Xmp.GPano.PoseHeadingDegrees"])))
|
|
245
|
-
|
|
246
|
-
elif isExifTagUsable(data, "Exif.GPSInfo.GPSImgDirection", Fraction):
|
|
234
|
+
if isExifTagUsable(data, "Exif.GPSInfo.GPSImgDirection", Fraction):
|
|
247
235
|
heading = int(round(float(Fraction(data["Exif.GPSInfo.GPSImgDirection"]))))
|
|
248
236
|
|
|
249
237
|
elif "MAPCompassHeading" in data and isExifTagUsable(data["MAPCompassHeading"], "TrueHeading", float):
|
|
250
238
|
heading = int(round(float(data["MAPCompassHeading"]["TrueHeading"])))
|
|
251
239
|
|
|
252
|
-
# Pitch
|
|
240
|
+
# Yaw / Pitch / roll
|
|
241
|
+
yaw = None
|
|
253
242
|
pitch = None
|
|
254
243
|
roll = None
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
244
|
+
exifYPRFields = {
|
|
245
|
+
"yaw": ["Xmp.Camera.Yaw", "Xmp.GPano.PoseHeadingDegrees"],
|
|
246
|
+
"pitch": ["Xmp.Camera.Pitch", "Xmp.GPano.PosePitchDegrees"],
|
|
247
|
+
"roll": ["Xmp.Camera.Roll", "Xmp.GPano.PoseRollDegrees"],
|
|
248
|
+
}
|
|
249
|
+
for ypr in exifYPRFields:
|
|
250
|
+
for exifTag in exifYPRFields[ypr]:
|
|
261
251
|
foundValue = None
|
|
262
252
|
# Look for float or fraction
|
|
263
|
-
if isExifTagUsable(data,
|
|
264
|
-
foundValue = float(data[
|
|
265
|
-
elif isExifTagUsable(data,
|
|
266
|
-
foundValue = float(Fraction(data[
|
|
253
|
+
if isExifTagUsable(data, exifTag, float):
|
|
254
|
+
foundValue = float(data[exifTag])
|
|
255
|
+
elif isExifTagUsable(data, exifTag, Fraction):
|
|
256
|
+
foundValue = float(Fraction(data[exifTag]))
|
|
267
257
|
|
|
268
|
-
# Save
|
|
258
|
+
# Save found value
|
|
269
259
|
if foundValue is not None:
|
|
270
|
-
if
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if roll is None:
|
|
277
|
-
roll = foundValue
|
|
278
|
-
else:
|
|
279
|
-
continue
|
|
260
|
+
if ypr == "yaw" and yaw is None:
|
|
261
|
+
yaw = foundValue
|
|
262
|
+
elif ypr == "pitch" and pitch is None:
|
|
263
|
+
pitch = foundValue
|
|
264
|
+
elif ypr == "roll" and roll is None:
|
|
265
|
+
roll = foundValue
|
|
280
266
|
|
|
281
267
|
# Make and model
|
|
282
268
|
make = data.get("Exif.Image.Make") or data.get("MAPDeviceMake")
|
|
@@ -383,6 +369,7 @@ def readPictureMetadata(picture: bytes, lang_code: str = "en") -> GeoPicTags:
|
|
|
383
369
|
altitude=altitude,
|
|
384
370
|
pitch=pitch,
|
|
385
371
|
roll=roll,
|
|
372
|
+
yaw=yaw,
|
|
386
373
|
),
|
|
387
374
|
)
|
|
388
375
|
|
|
@@ -402,6 +389,7 @@ def readPictureMetadata(picture: bytes, lang_code: str = "en") -> GeoPicTags:
|
|
|
402
389
|
altitude=altitude,
|
|
403
390
|
pitch=pitch,
|
|
404
391
|
roll=roll,
|
|
392
|
+
yaw=yaw,
|
|
405
393
|
)
|
|
406
394
|
|
|
407
395
|
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Optional, List, Tuple
|
|
4
|
+
from pathlib import PurePath
|
|
5
|
+
from geopic_tag_reader.reader import GeoPicTags
|
|
6
|
+
import datetime
|
|
7
|
+
import math
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SortMethod(str, Enum):
|
|
11
|
+
filename_asc = "filename-asc"
|
|
12
|
+
filename_desc = "filename-desc"
|
|
13
|
+
time_asc = "time-asc"
|
|
14
|
+
time_desc = "time-desc"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class MergeParams:
|
|
19
|
+
maxDistance: Optional[float] = None
|
|
20
|
+
maxRotationAngle: Optional[int] = None
|
|
21
|
+
|
|
22
|
+
def is_merge_needed(self):
|
|
23
|
+
# Only check max distance, as max rotation angle is only useful when dist is defined
|
|
24
|
+
return self.maxDistance is not None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class SplitParams:
|
|
29
|
+
maxDistance: Optional[int] = None
|
|
30
|
+
maxTime: Optional[int] = None
|
|
31
|
+
|
|
32
|
+
def is_split_needed(self):
|
|
33
|
+
return self.maxDistance is not None or self.maxTime is not None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Picture:
|
|
38
|
+
filename: str
|
|
39
|
+
metadata: GeoPicTags
|
|
40
|
+
|
|
41
|
+
def distance_to(self, other) -> float:
|
|
42
|
+
"""Computes distance in meters based on Haversine formula"""
|
|
43
|
+
R = 6371000
|
|
44
|
+
phi1 = math.radians(self.metadata.lat)
|
|
45
|
+
phi2 = math.radians(other.metadata.lat)
|
|
46
|
+
delta_phi = math.radians(other.metadata.lat - self.metadata.lat)
|
|
47
|
+
delta_lambda = math.radians(other.metadata.lon - self.metadata.lon)
|
|
48
|
+
a = math.sin(delta_phi / 2.0) ** 2 + math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2.0) ** 2
|
|
49
|
+
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
|
50
|
+
distance = R * c
|
|
51
|
+
return distance
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SplitReason(str, Enum):
|
|
55
|
+
time = "time"
|
|
56
|
+
distance = "distance"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class Split:
|
|
61
|
+
prevPic: Picture
|
|
62
|
+
nextPic: Picture
|
|
63
|
+
reason: SplitReason
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class Sequence:
|
|
68
|
+
pictures: List[Picture]
|
|
69
|
+
|
|
70
|
+
def from_ts(self) -> Optional[datetime.datetime]:
|
|
71
|
+
"""Start date/time of this sequence"""
|
|
72
|
+
|
|
73
|
+
if len(self.pictures) == 0:
|
|
74
|
+
return None
|
|
75
|
+
return self.pictures[0].metadata.ts
|
|
76
|
+
|
|
77
|
+
def to_ts(self) -> Optional[datetime.datetime]:
|
|
78
|
+
"""End date/time of this sequence"""
|
|
79
|
+
|
|
80
|
+
if len(self.pictures) == 0:
|
|
81
|
+
return None
|
|
82
|
+
return self.pictures[-1].metadata.ts
|
|
83
|
+
|
|
84
|
+
def delta_with(self, otherSeq) -> Optional[Tuple[datetime.timedelta, float]]:
|
|
85
|
+
"""
|
|
86
|
+
Delta between the end of this sequence and the start of the other one.
|
|
87
|
+
Returns a tuple (timedelta, distance in meters)
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
if len(self.pictures) == 0 or len(otherSeq.pictures) == 0:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
return (otherSeq.from_ts() - self.to_ts(), otherSeq.pictures[0].distance_to(self.pictures[-1])) # type: ignore
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class DispatchReport:
|
|
98
|
+
sequences: List[Sequence]
|
|
99
|
+
duplicate_pictures: Optional[List[Picture]] = None
|
|
100
|
+
sequences_splits: Optional[List[Split]] = None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def sort_pictures(pictures: List[Picture], method: Optional[SortMethod] = SortMethod.time_asc) -> List[Picture]:
|
|
104
|
+
"""Sorts pictures according to given strategy
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
pictures : Picture[]
|
|
109
|
+
List of pictures to sort
|
|
110
|
+
method : SortMethod
|
|
111
|
+
Sort logic to adopt (time-asc, time-desc, filename-asc, filename-desc)
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
Picture[]
|
|
116
|
+
List of pictures, sorted
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
if method is None:
|
|
120
|
+
method = SortMethod.time_asc
|
|
121
|
+
|
|
122
|
+
if method not in [item.value for item in SortMethod]:
|
|
123
|
+
raise Exception("Invalid sort strategy: " + str(method))
|
|
124
|
+
|
|
125
|
+
# Get the sort logic
|
|
126
|
+
strat, order = method.split("-")
|
|
127
|
+
|
|
128
|
+
# Sort based on filename
|
|
129
|
+
if strat == "filename":
|
|
130
|
+
# Check if pictures can be sorted by numeric notation
|
|
131
|
+
hasNonNumber = False
|
|
132
|
+
for p in pictures:
|
|
133
|
+
try:
|
|
134
|
+
int(PurePath(p.filename or "").stem)
|
|
135
|
+
except:
|
|
136
|
+
hasNonNumber = True
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
def sort_fct(p):
|
|
140
|
+
return PurePath(p.filename or "").stem if hasNonNumber else int(PurePath(p.filename or "").stem)
|
|
141
|
+
|
|
142
|
+
pictures.sort(key=sort_fct)
|
|
143
|
+
|
|
144
|
+
# Sort based on picture ts
|
|
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")
|
|
147
|
+
|
|
148
|
+
if order == "desc":
|
|
149
|
+
pictures.reverse()
|
|
150
|
+
|
|
151
|
+
return pictures
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def find_duplicates(pictures: List[Picture], params: Optional[MergeParams] = None) -> Tuple[List[Picture], List[Picture]]:
|
|
155
|
+
"""
|
|
156
|
+
Finds too similar pictures.
|
|
157
|
+
Note that input list should be properly sorted.
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
pictures : list of sorted pictures to check
|
|
162
|
+
params : parameters used to consider two pictures as a duplicate
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
(Non-duplicates pictures, Duplicates pictures)
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
if params is None or not params.is_merge_needed():
|
|
170
|
+
return (pictures, [])
|
|
171
|
+
|
|
172
|
+
nonDups: List[Picture] = []
|
|
173
|
+
dups: List[Picture] = []
|
|
174
|
+
lastNonDuplicatedPicId = 0
|
|
175
|
+
|
|
176
|
+
for i, currentPic in enumerate(pictures):
|
|
177
|
+
if i == 0:
|
|
178
|
+
nonDups.append(currentPic)
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
prevPic = pictures[lastNonDuplicatedPicId]
|
|
182
|
+
|
|
183
|
+
if prevPic.metadata is None or currentPic.metadata is None:
|
|
184
|
+
nonDups.append(currentPic)
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
# Compare distance
|
|
188
|
+
dist = prevPic.distance_to(currentPic)
|
|
189
|
+
|
|
190
|
+
if dist <= params.maxDistance: # type: ignore
|
|
191
|
+
# Compare angle (if available on both images)
|
|
192
|
+
if params.maxRotationAngle is not None and prevPic.metadata.heading is not None and currentPic.metadata.heading is not None:
|
|
193
|
+
deltaAngle = abs(currentPic.metadata.heading - prevPic.metadata.heading)
|
|
194
|
+
|
|
195
|
+
if deltaAngle <= params.maxRotationAngle:
|
|
196
|
+
dups.append(currentPic)
|
|
197
|
+
else:
|
|
198
|
+
lastNonDuplicatedPicId = i
|
|
199
|
+
nonDups.append(currentPic)
|
|
200
|
+
else:
|
|
201
|
+
dups.append(currentPic)
|
|
202
|
+
else:
|
|
203
|
+
lastNonDuplicatedPicId = i
|
|
204
|
+
nonDups.append(currentPic)
|
|
205
|
+
|
|
206
|
+
return (nonDups, dups)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def split_in_sequences(pictures: List[Picture], splitParams: Optional[SplitParams] = SplitParams()) -> Tuple[List[Sequence], List[Split]]:
|
|
210
|
+
"""
|
|
211
|
+
Split a list of pictures into many sequences.
|
|
212
|
+
Note that this function expect pictures to be sorted and have their metadata set.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
pictures : Picture[]
|
|
217
|
+
List of pictures to check, sorted and with metadata defined
|
|
218
|
+
splitParams : SplitParams
|
|
219
|
+
The parameters to define deltas between two pictures
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
List[Sequence]
|
|
224
|
+
List of pictures splitted into smaller sequences
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
# No split parameters given -> just return given pictures
|
|
228
|
+
if splitParams is None or not splitParams.is_split_needed():
|
|
229
|
+
return ([Sequence(pictures)], [])
|
|
230
|
+
|
|
231
|
+
sequences: List[Sequence] = []
|
|
232
|
+
splits: List[Split] = []
|
|
233
|
+
currentPicList: List[Picture] = []
|
|
234
|
+
|
|
235
|
+
for pic in pictures:
|
|
236
|
+
if len(currentPicList) == 0: # No checks for 1st pic
|
|
237
|
+
currentPicList.append(pic)
|
|
238
|
+
else:
|
|
239
|
+
lastPic = currentPicList[-1]
|
|
240
|
+
|
|
241
|
+
# Missing metadata -> skip
|
|
242
|
+
if lastPic.metadata is None or pic.metadata is None:
|
|
243
|
+
currentPicList.append(pic)
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
# Time delta
|
|
247
|
+
timeOutOfDelta = (
|
|
248
|
+
False if splitParams.maxTime is None else (abs(lastPic.metadata.ts - pic.metadata.ts)).total_seconds() > splitParams.maxTime
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Distance delta
|
|
252
|
+
distance = lastPic.distance_to(pic)
|
|
253
|
+
distanceOutOfDelta = False if splitParams.maxDistance is None else distance > splitParams.maxDistance
|
|
254
|
+
|
|
255
|
+
# One of deltas maxed -> create new sequence
|
|
256
|
+
if timeOutOfDelta or distanceOutOfDelta:
|
|
257
|
+
sequences.append(Sequence(currentPicList))
|
|
258
|
+
currentPicList = [pic]
|
|
259
|
+
splits.append(Split(lastPic, pic, SplitReason.time if timeOutOfDelta else SplitReason.distance))
|
|
260
|
+
|
|
261
|
+
# Otherwise, still in same sequence
|
|
262
|
+
else:
|
|
263
|
+
currentPicList.append(pic)
|
|
264
|
+
|
|
265
|
+
sequences.append(Sequence(currentPicList))
|
|
266
|
+
|
|
267
|
+
return (sequences, splits)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def dispatch_pictures(
|
|
271
|
+
pictures: List[Picture],
|
|
272
|
+
sortMethod: Optional[SortMethod] = None,
|
|
273
|
+
mergeParams: Optional[MergeParams] = None,
|
|
274
|
+
splitParams: Optional[SplitParams] = None,
|
|
275
|
+
) -> DispatchReport:
|
|
276
|
+
"""
|
|
277
|
+
Dispatches a set of pictures into many sequences.
|
|
278
|
+
This function both sorts, de-duplicates and splits in sequences all your pictures.
|
|
279
|
+
|
|
280
|
+
Parameters
|
|
281
|
+
----------
|
|
282
|
+
pictures : set of pictures to dispatch
|
|
283
|
+
sortMethod : strategy for sorting pictures
|
|
284
|
+
mergeParams : conditions for considering two pictures as duplicates
|
|
285
|
+
splitParams : conditions for considering two sequences as distinct
|
|
286
|
+
|
|
287
|
+
Returns
|
|
288
|
+
-------
|
|
289
|
+
DispatchReport : clean sequences, duplicates pictures and split reasons
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
# Sort
|
|
293
|
+
myPics = sort_pictures(pictures, sortMethod)
|
|
294
|
+
|
|
295
|
+
# De-duplicate
|
|
296
|
+
(myPics, dupsPics) = find_duplicates(myPics, mergeParams)
|
|
297
|
+
|
|
298
|
+
# Split in sequences
|
|
299
|
+
(mySeqs, splits) = split_in_sequences(myPics, splitParams)
|
|
300
|
+
|
|
301
|
+
return DispatchReport(mySeqs, dupsPics if len(dupsPics) > 0 else None, splits if len(splits) > 0 else None)
|
|
Binary file
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
|
+
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the PACKAGE package.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
5
|
+
#
|
|
6
|
+
msgid ""
|
|
7
|
+
msgstr ""
|
|
8
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
9
|
+
"Report-Msgid-Bugs-To: \n"
|
|
10
|
+
"POT-Creation-Date: 2024-07-10 13:05+0200\n"
|
|
11
|
+
"PO-Revision-Date: 2024-07-13 20:42+0000\n"
|
|
12
|
+
"Last-Translator: Bastian Greshake Tzovaras <bastian@gedankenstuecke.de>\n"
|
|
13
|
+
"Language-Team: German <http://weblate.panoramax.xyz/projects/panoramax/"
|
|
14
|
+
"tag-reader/de/>\n"
|
|
15
|
+
"Language: de\n"
|
|
16
|
+
"MIME-Version: 1.0\n"
|
|
17
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
18
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
19
|
+
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
|
20
|
+
"X-Generator: Weblate 5.4.3\n"
|
|
21
|
+
|
|
22
|
+
#: geopic_tag_reader/main.py:26
|
|
23
|
+
msgid "Latitude:"
|
|
24
|
+
msgstr "Breitengrad:"
|
|
25
|
+
|
|
26
|
+
#: geopic_tag_reader/main.py:27
|
|
27
|
+
msgid "Longitude:"
|
|
28
|
+
msgstr "Längengrad:"
|
|
29
|
+
|
|
30
|
+
#: geopic_tag_reader/main.py:28
|
|
31
|
+
msgid "Timestamp:"
|
|
32
|
+
msgstr "Zeitpunkt:"
|
|
33
|
+
|
|
34
|
+
#: geopic_tag_reader/main.py:29
|
|
35
|
+
msgid "Heading:"
|
|
36
|
+
msgstr "Richtung:"
|
|
37
|
+
|
|
38
|
+
#: geopic_tag_reader/main.py:30
|
|
39
|
+
msgid "Type:"
|
|
40
|
+
msgstr "Typ:"
|
|
41
|
+
|
|
42
|
+
#: geopic_tag_reader/main.py:31
|
|
43
|
+
msgid "Make:"
|
|
44
|
+
msgstr "Hersteller:"
|
|
45
|
+
|
|
46
|
+
#: geopic_tag_reader/main.py:32
|
|
47
|
+
msgid "Model:"
|
|
48
|
+
msgstr "Modell:"
|
|
49
|
+
|
|
50
|
+
#: geopic_tag_reader/main.py:33
|
|
51
|
+
msgid "Focal length:"
|
|
52
|
+
msgstr "Brennweite:"
|
|
53
|
+
|
|
54
|
+
#: geopic_tag_reader/main.py:34
|
|
55
|
+
msgid "Crop parameters:"
|
|
56
|
+
msgstr "Zuschnittseinstellungen:"
|
|
57
|
+
|
|
58
|
+
#: geopic_tag_reader/main.py:35
|
|
59
|
+
msgid "Pitch:"
|
|
60
|
+
msgstr "Neigung:"
|
|
61
|
+
|
|
62
|
+
#: geopic_tag_reader/main.py:36
|
|
63
|
+
msgid "Roll:"
|
|
64
|
+
msgstr "Rollwinkel:"
|
|
65
|
+
|
|
66
|
+
#: geopic_tag_reader/main.py:39
|
|
67
|
+
msgid "Warnings raised by reader:"
|
|
68
|
+
msgstr "Warnungen vom Leser erhoben:"
|
|
69
|
+
|
|
70
|
+
#: geopic_tag_reader/reader.py:183
|
|
71
|
+
msgid "Read latitude is out of WGS84 bounds (should be in [-90, 90])"
|
|
72
|
+
msgstr ""
|
|
73
|
+
"Ausgelesener Breitengrad liegt außerhalb der WGS84-Grenzen (sollte in "
|
|
74
|
+
"[-90,90] liegen)"
|
|
75
|
+
|
|
76
|
+
#: geopic_tag_reader/reader.py:185
|
|
77
|
+
msgid "Read longitude is out of WGS84 bounds (should be in [-180, 180])"
|
|
78
|
+
msgstr ""
|
|
79
|
+
"Ausgelesener Längengrad liegt außerhalb der WGS84-Grenzen (sollte in "
|
|
80
|
+
"[-180,180] liegen)"
|
|
81
|
+
|
|
82
|
+
#: geopic_tag_reader/reader.py:227
|
|
83
|
+
#, python-brace-format
|
|
84
|
+
msgid "Skipping Mapillary date/time as it was not recognized: {v}"
|
|
85
|
+
msgstr ""
|
|
86
|
+
"Mapillary Datum/Uhrzeit wurde übersprungen weil sie nicht erkannt wurden: {v}"
|
|
87
|
+
|
|
88
|
+
#: geopic_tag_reader/reader.py:240
|
|
89
|
+
msgid "Contradicting heading values found, GPSImgDirection value is used"
|
|
90
|
+
msgstr ""
|
|
91
|
+
"Widersprüchliche Richtungswerte gefunden, GPSImgDirection-Wert wird verwendet"
|
|
92
|
+
|
|
93
|
+
#: geopic_tag_reader/reader.py:351
|
|
94
|
+
msgid "No GPS coordinates or broken coordinates in picture EXIF tags"
|
|
95
|
+
msgstr "Keine oder invalide GPS-Koordinaten in den EXIF-Daten des Fotos"
|
|
96
|
+
|
|
97
|
+
#: geopic_tag_reader/reader.py:357
|
|
98
|
+
msgid "No valid date in picture EXIF tags"
|
|
99
|
+
msgstr "Kein valides Datum in den EXIF-Daten des Fotos"
|
|
100
|
+
|
|
101
|
+
#: geopic_tag_reader/reader.py:362
|
|
102
|
+
msgid "The picture is missing mandatory metadata:"
|
|
103
|
+
msgstr "Dem Foto fehlen obligatorische Metadaten:"
|
|
104
|
+
|
|
105
|
+
#: geopic_tag_reader/reader.py:449 geopic_tag_reader/reader.py:478
|
|
106
|
+
msgid "GPSLatitudeRef not found, assuming GPSLatitudeRef is North"
|
|
107
|
+
msgstr "GPSLatitudeRef nicht gefunden, nutze Norden als GPSLatitudeRef"
|
|
108
|
+
|
|
109
|
+
#: geopic_tag_reader/reader.py:457
|
|
110
|
+
msgid "Broken GPS coordinates in picture EXIF tags"
|
|
111
|
+
msgstr "Invalide GPS-Koordinaten in den EXIF-Daten des Fotos"
|
|
112
|
+
|
|
113
|
+
#: geopic_tag_reader/reader.py:460 geopic_tag_reader/reader.py:484
|
|
114
|
+
msgid "GPSLongitudeRef not found, assuming GPSLongitudeRef is East"
|
|
115
|
+
msgstr "GPSLongitudeRef nicht gefunden, nutze Osten als GPSLongitudeRef"
|
|
116
|
+
|
|
117
|
+
#: geopic_tag_reader/reader.py:537
|
|
118
|
+
msgid "Precise timezone information not found, fallback to UTC"
|
|
119
|
+
msgstr "Genaue Zeitzonen-Information nicht gefunden, falle auf UTC zurück"
|
|
120
|
+
|
|
121
|
+
#: geopic_tag_reader/reader.py:542
|
|
122
|
+
msgid ""
|
|
123
|
+
"Precise timezone information not found (and no GPS coordinates to help), "
|
|
124
|
+
"fallback to UTC"
|
|
125
|
+
msgstr ""
|
|
126
|
+
"Genaue Zeitzonen-Information nicht gefunden (und keine GPS-Koordinaten um zu "
|
|
127
|
+
"helfen), falle auf UTC zurück"
|
|
128
|
+
|
|
129
|
+
#: geopic_tag_reader/reader.py:546
|
|
130
|
+
#, python-brace-format
|
|
131
|
+
msgid ""
|
|
132
|
+
"Skipping original date/time (from {datefield}) as it was not recognized: {v}"
|
|
133
|
+
msgstr ""
|
|
134
|
+
"Überspringe originales Datum/Zeit (aus {datefield}) weil es nicht erkannt "
|
|
135
|
+
"wurde: {v}"
|
|
136
|
+
|
|
137
|
+
#: geopic_tag_reader/reader.py:580
|
|
138
|
+
#, python-brace-format
|
|
139
|
+
msgid ""
|
|
140
|
+
"GPSTimeStamp and GPSDateTime don't contain supported time format (in {group} "
|
|
141
|
+
"group)"
|
|
142
|
+
msgstr ""
|
|
143
|
+
"GPSTimeStamp und GPSDateTime enthalten kein unterstütztes Zeitformat (in "
|
|
144
|
+
"{group} Gruppe)"
|
|
145
|
+
|
|
146
|
+
#: geopic_tag_reader/reader.py:611
|
|
147
|
+
#, python-brace-format
|
|
148
|
+
msgid "Skipping GPS date/time ({group} group) as it was not recognized: {v}"
|
|
149
|
+
msgstr ""
|
|
150
|
+
"Überspringe GPS Datum/Zeit ({group} Gruppe) da es nicht erkannt wurde: {v}"
|
|
151
|
+
|
|
152
|
+
#: geopic_tag_reader/reader.py:637
|
|
153
|
+
#, python-brace-format
|
|
154
|
+
msgid ""
|
|
155
|
+
"Microseconds read from decimal seconds value ({microsecondsFromSeconds}) is "
|
|
156
|
+
"not matching value from EXIF field ({microseconds}). Max value will be kept."
|
|
157
|
+
msgstr ""
|
|
158
|
+
"Die Mikrosekunden die aus den Dezimalwerten der Sekunden gelesen wurden "
|
|
159
|
+
"({microsecondsFromSeconds}) stimmen nicht mit den Werten aus den EXIF-Daten "
|
|
160
|
+
"({microseconds}) überein. Maximaler Wert wird beibehalten."
|
|
161
|
+
|
|
162
|
+
#: geopic_tag_reader/writer.py:132
|
|
163
|
+
#, python-brace-format
|
|
164
|
+
msgid "Unsupported key in additional tags ({k})"
|
|
165
|
+
msgstr "Nicht unterstützter Schlüssel in den zusätzlichen Attributen ({k})"
|
|
Binary file
|
|
@@ -7,8 +7,8 @@ msgid ""
|
|
|
7
7
|
msgstr ""
|
|
8
8
|
"Project-Id-Version: PACKAGE VERSION\n"
|
|
9
9
|
"Report-Msgid-Bugs-To: \n"
|
|
10
|
-
"POT-Creation-Date: 2024-07-
|
|
11
|
-
"PO-Revision-Date: 2024-07-
|
|
10
|
+
"POT-Creation-Date: 2024-07-30 16:49+0200\n"
|
|
11
|
+
"PO-Revision-Date: 2024-07-30 16:49+0200\n"
|
|
12
12
|
"Last-Translator: Automatically generated\n"
|
|
13
13
|
"Language-Team: none\n"
|
|
14
14
|
"Language: en\n"
|
|
@@ -61,56 +61,56 @@ msgstr "Pitch:"
|
|
|
61
61
|
msgid "Roll:"
|
|
62
62
|
msgstr "Roll:"
|
|
63
63
|
|
|
64
|
-
#: geopic_tag_reader/main.py:
|
|
64
|
+
#: geopic_tag_reader/main.py:37
|
|
65
|
+
msgid "Yaw:"
|
|
66
|
+
msgstr "Yaw:"
|
|
67
|
+
|
|
68
|
+
#: geopic_tag_reader/main.py:40
|
|
65
69
|
msgid "Warnings raised by reader:"
|
|
66
70
|
msgstr "Warnings raised by reader:"
|
|
67
71
|
|
|
68
|
-
#: geopic_tag_reader/reader.py:
|
|
72
|
+
#: geopic_tag_reader/reader.py:186
|
|
69
73
|
msgid "Read latitude is out of WGS84 bounds (should be in [-90, 90])"
|
|
70
74
|
msgstr "Read latitude is out of WGS84 bounds (should be in [-90, 90])"
|
|
71
75
|
|
|
72
|
-
#: geopic_tag_reader/reader.py:
|
|
76
|
+
#: geopic_tag_reader/reader.py:188
|
|
73
77
|
msgid "Read longitude is out of WGS84 bounds (should be in [-180, 180])"
|
|
74
78
|
msgstr "Read longitude is out of WGS84 bounds (should be in [-180, 180])"
|
|
75
79
|
|
|
76
|
-
#: geopic_tag_reader/reader.py:
|
|
80
|
+
#: geopic_tag_reader/reader.py:230
|
|
77
81
|
#, python-brace-format
|
|
78
82
|
msgid "Skipping Mapillary date/time as it was not recognized: {v}"
|
|
79
83
|
msgstr "Skipping Mapillary date/time as it was not recognized: {v}"
|
|
80
84
|
|
|
81
|
-
#: geopic_tag_reader/reader.py:
|
|
82
|
-
msgid "Contradicting heading values found, GPSImgDirection value is used"
|
|
83
|
-
msgstr "Contradicting heading values found, GPSImgDirection value is used"
|
|
84
|
-
|
|
85
|
-
#: geopic_tag_reader/reader.py:351
|
|
85
|
+
#: geopic_tag_reader/reader.py:337
|
|
86
86
|
msgid "No GPS coordinates or broken coordinates in picture EXIF tags"
|
|
87
87
|
msgstr "No GPS coordinates or broken coordinates in picture EXIF tags"
|
|
88
88
|
|
|
89
|
-
#: geopic_tag_reader/reader.py:
|
|
89
|
+
#: geopic_tag_reader/reader.py:343
|
|
90
90
|
msgid "No valid date in picture EXIF tags"
|
|
91
91
|
msgstr "No valid date in picture EXIF tags"
|
|
92
92
|
|
|
93
|
-
#: geopic_tag_reader/reader.py:
|
|
93
|
+
#: geopic_tag_reader/reader.py:348
|
|
94
94
|
msgid "The picture is missing mandatory metadata:"
|
|
95
95
|
msgstr "The picture is missing mandatory metadata:"
|
|
96
96
|
|
|
97
|
-
#: geopic_tag_reader/reader.py:
|
|
97
|
+
#: geopic_tag_reader/reader.py:437 geopic_tag_reader/reader.py:466
|
|
98
98
|
msgid "GPSLatitudeRef not found, assuming GPSLatitudeRef is North"
|
|
99
99
|
msgstr "GPSLatitudeRef not found, assuming GPSLatitudeRef is North"
|
|
100
100
|
|
|
101
|
-
#: geopic_tag_reader/reader.py:
|
|
101
|
+
#: geopic_tag_reader/reader.py:445
|
|
102
102
|
msgid "Broken GPS coordinates in picture EXIF tags"
|
|
103
103
|
msgstr "Broken GPS coordinates in picture EXIF tags"
|
|
104
104
|
|
|
105
|
-
#: geopic_tag_reader/reader.py:
|
|
105
|
+
#: geopic_tag_reader/reader.py:448 geopic_tag_reader/reader.py:472
|
|
106
106
|
msgid "GPSLongitudeRef not found, assuming GPSLongitudeRef is East"
|
|
107
107
|
msgstr "GPSLongitudeRef not found, assuming GPSLongitudeRef is East"
|
|
108
108
|
|
|
109
|
-
#: geopic_tag_reader/reader.py:
|
|
109
|
+
#: geopic_tag_reader/reader.py:525
|
|
110
110
|
msgid "Precise timezone information not found, fallback to UTC"
|
|
111
111
|
msgstr "Precise timezone information not found, fallback to UTC"
|
|
112
112
|
|
|
113
|
-
#: geopic_tag_reader/reader.py:
|
|
113
|
+
#: geopic_tag_reader/reader.py:530
|
|
114
114
|
msgid ""
|
|
115
115
|
"Precise timezone information not found (and no GPS coordinates to help), "
|
|
116
116
|
"fallback to UTC"
|
|
@@ -118,14 +118,14 @@ msgstr ""
|
|
|
118
118
|
"Precise timezone information not found (and no GPS coordinates to help), "
|
|
119
119
|
"fallback to UTC"
|
|
120
120
|
|
|
121
|
-
#: geopic_tag_reader/reader.py:
|
|
121
|
+
#: geopic_tag_reader/reader.py:534
|
|
122
122
|
#, python-brace-format
|
|
123
123
|
msgid ""
|
|
124
124
|
"Skipping original date/time (from {datefield}) as it was not recognized: {v}"
|
|
125
125
|
msgstr ""
|
|
126
126
|
"Skipping original date/time (from {datefield}) as it was not recognized: {v}"
|
|
127
127
|
|
|
128
|
-
#: geopic_tag_reader/reader.py:
|
|
128
|
+
#: geopic_tag_reader/reader.py:568
|
|
129
129
|
#, python-brace-format
|
|
130
130
|
msgid ""
|
|
131
131
|
"GPSTimeStamp and GPSDateTime don't contain supported time format (in {group} "
|
|
@@ -134,12 +134,12 @@ msgstr ""
|
|
|
134
134
|
"GPSTimeStamp and GPSDateTime don't contain supported time format (in {group} "
|
|
135
135
|
"group)"
|
|
136
136
|
|
|
137
|
-
#: geopic_tag_reader/reader.py:
|
|
137
|
+
#: geopic_tag_reader/reader.py:599
|
|
138
138
|
#, python-brace-format
|
|
139
139
|
msgid "Skipping GPS date/time ({group} group) as it was not recognized: {v}"
|
|
140
140
|
msgstr "Skipping GPS date/time ({group} group) as it was not recognized: {v}"
|
|
141
141
|
|
|
142
|
-
#: geopic_tag_reader/reader.py:
|
|
142
|
+
#: geopic_tag_reader/reader.py:625
|
|
143
143
|
#, python-brace-format
|
|
144
144
|
msgid ""
|
|
145
145
|
"Microseconds read from decimal seconds value ({microsecondsFromSeconds}) is "
|
|
Binary file
|
|
@@ -3,18 +3,17 @@
|
|
|
3
3
|
# This file is distributed under the same license as the PACKAGE package.
|
|
4
4
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
5
5
|
#
|
|
6
|
-
#, fuzzy
|
|
7
6
|
msgid ""
|
|
8
7
|
msgstr ""
|
|
9
8
|
"Project-Id-Version: PACKAGE VERSION\n"
|
|
10
9
|
"Report-Msgid-Bugs-To: \n"
|
|
11
10
|
"POT-Creation-Date: 2024-07-10 13:05+0200\n"
|
|
12
11
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
|
-
"Last-Translator:
|
|
14
|
-
"Language-Team:
|
|
15
|
-
"Language: \n"
|
|
12
|
+
"Last-Translator: Automatically generated\n"
|
|
13
|
+
"Language-Team: none\n"
|
|
14
|
+
"Language: es\n"
|
|
16
15
|
"MIME-Version: 1.0\n"
|
|
17
|
-
"Content-Type: text/plain; charset=
|
|
16
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
18
17
|
"Content-Transfer-Encoding: 8bit\n"
|
|
19
18
|
|
|
20
19
|
#: geopic_tag_reader/main.py:26
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
|
+
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the PACKAGE package.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
5
|
+
#
|
|
6
|
+
#, fuzzy
|
|
7
|
+
msgid ""
|
|
8
|
+
msgstr ""
|
|
9
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
10
|
+
"Report-Msgid-Bugs-To: \n"
|
|
11
|
+
"POT-Creation-Date: 2024-07-30 16:49+0200\n"
|
|
12
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
15
|
+
"Language: \n"
|
|
16
|
+
"MIME-Version: 1.0\n"
|
|
17
|
+
"Content-Type: text/plain; charset=CHARSET\n"
|
|
18
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
19
|
+
|
|
20
|
+
#: geopic_tag_reader/main.py:26
|
|
21
|
+
msgid "Latitude:"
|
|
22
|
+
msgstr ""
|
|
23
|
+
|
|
24
|
+
#: geopic_tag_reader/main.py:27
|
|
25
|
+
msgid "Longitude:"
|
|
26
|
+
msgstr ""
|
|
27
|
+
|
|
28
|
+
#: geopic_tag_reader/main.py:28
|
|
29
|
+
msgid "Timestamp:"
|
|
30
|
+
msgstr ""
|
|
31
|
+
|
|
32
|
+
#: geopic_tag_reader/main.py:29
|
|
33
|
+
msgid "Heading:"
|
|
34
|
+
msgstr ""
|
|
35
|
+
|
|
36
|
+
#: geopic_tag_reader/main.py:30
|
|
37
|
+
msgid "Type:"
|
|
38
|
+
msgstr ""
|
|
39
|
+
|
|
40
|
+
#: geopic_tag_reader/main.py:31
|
|
41
|
+
msgid "Make:"
|
|
42
|
+
msgstr ""
|
|
43
|
+
|
|
44
|
+
#: geopic_tag_reader/main.py:32
|
|
45
|
+
msgid "Model:"
|
|
46
|
+
msgstr ""
|
|
47
|
+
|
|
48
|
+
#: geopic_tag_reader/main.py:33
|
|
49
|
+
msgid "Focal length:"
|
|
50
|
+
msgstr ""
|
|
51
|
+
|
|
52
|
+
#: geopic_tag_reader/main.py:34
|
|
53
|
+
msgid "Crop parameters:"
|
|
54
|
+
msgstr ""
|
|
55
|
+
|
|
56
|
+
#: geopic_tag_reader/main.py:35
|
|
57
|
+
msgid "Pitch:"
|
|
58
|
+
msgstr ""
|
|
59
|
+
|
|
60
|
+
#: geopic_tag_reader/main.py:36
|
|
61
|
+
msgid "Roll:"
|
|
62
|
+
msgstr ""
|
|
63
|
+
|
|
64
|
+
#: geopic_tag_reader/main.py:37
|
|
65
|
+
msgid "Yaw:"
|
|
66
|
+
msgstr ""
|
|
67
|
+
|
|
68
|
+
#: geopic_tag_reader/main.py:40
|
|
69
|
+
msgid "Warnings raised by reader:"
|
|
70
|
+
msgstr ""
|
|
71
|
+
|
|
72
|
+
#: geopic_tag_reader/reader.py:186
|
|
73
|
+
msgid "Read latitude is out of WGS84 bounds (should be in [-90, 90])"
|
|
74
|
+
msgstr ""
|
|
75
|
+
|
|
76
|
+
#: geopic_tag_reader/reader.py:188
|
|
77
|
+
msgid "Read longitude is out of WGS84 bounds (should be in [-180, 180])"
|
|
78
|
+
msgstr ""
|
|
79
|
+
|
|
80
|
+
#: geopic_tag_reader/reader.py:230
|
|
81
|
+
#, python-brace-format
|
|
82
|
+
msgid "Skipping Mapillary date/time as it was not recognized: {v}"
|
|
83
|
+
msgstr ""
|
|
84
|
+
|
|
85
|
+
#: geopic_tag_reader/reader.py:337
|
|
86
|
+
msgid "No GPS coordinates or broken coordinates in picture EXIF tags"
|
|
87
|
+
msgstr ""
|
|
88
|
+
|
|
89
|
+
#: geopic_tag_reader/reader.py:343
|
|
90
|
+
msgid "No valid date in picture EXIF tags"
|
|
91
|
+
msgstr ""
|
|
92
|
+
|
|
93
|
+
#: geopic_tag_reader/reader.py:348
|
|
94
|
+
msgid "The picture is missing mandatory metadata:"
|
|
95
|
+
msgstr ""
|
|
96
|
+
|
|
97
|
+
#: geopic_tag_reader/reader.py:437 geopic_tag_reader/reader.py:466
|
|
98
|
+
msgid "GPSLatitudeRef not found, assuming GPSLatitudeRef is North"
|
|
99
|
+
msgstr ""
|
|
100
|
+
|
|
101
|
+
#: geopic_tag_reader/reader.py:445
|
|
102
|
+
msgid "Broken GPS coordinates in picture EXIF tags"
|
|
103
|
+
msgstr ""
|
|
104
|
+
|
|
105
|
+
#: geopic_tag_reader/reader.py:448 geopic_tag_reader/reader.py:472
|
|
106
|
+
msgid "GPSLongitudeRef not found, assuming GPSLongitudeRef is East"
|
|
107
|
+
msgstr ""
|
|
108
|
+
|
|
109
|
+
#: geopic_tag_reader/reader.py:525
|
|
110
|
+
msgid "Precise timezone information not found, fallback to UTC"
|
|
111
|
+
msgstr ""
|
|
112
|
+
|
|
113
|
+
#: geopic_tag_reader/reader.py:530
|
|
114
|
+
msgid ""
|
|
115
|
+
"Precise timezone information not found (and no GPS coordinates to help), "
|
|
116
|
+
"fallback to UTC"
|
|
117
|
+
msgstr ""
|
|
118
|
+
|
|
119
|
+
#: geopic_tag_reader/reader.py:534
|
|
120
|
+
#, python-brace-format
|
|
121
|
+
msgid ""
|
|
122
|
+
"Skipping original date/time (from {datefield}) as it was not recognized: {v}"
|
|
123
|
+
msgstr ""
|
|
124
|
+
|
|
125
|
+
#: geopic_tag_reader/reader.py:568
|
|
126
|
+
#, python-brace-format
|
|
127
|
+
msgid ""
|
|
128
|
+
"GPSTimeStamp and GPSDateTime don't contain supported time format (in {group} "
|
|
129
|
+
"group)"
|
|
130
|
+
msgstr ""
|
|
131
|
+
|
|
132
|
+
#: geopic_tag_reader/reader.py:599
|
|
133
|
+
#, python-brace-format
|
|
134
|
+
msgid "Skipping GPS date/time ({group} group) as it was not recognized: {v}"
|
|
135
|
+
msgstr ""
|
|
136
|
+
|
|
137
|
+
#: geopic_tag_reader/reader.py:625
|
|
138
|
+
#, python-brace-format
|
|
139
|
+
msgid ""
|
|
140
|
+
"Microseconds read from decimal seconds value ({microsecondsFromSeconds}) is "
|
|
141
|
+
"not matching value from EXIF field ({microseconds}). Max value will be kept."
|
|
142
|
+
msgstr ""
|
|
143
|
+
|
|
144
|
+
#: geopic_tag_reader/writer.py:132
|
|
145
|
+
#, python-brace-format
|
|
146
|
+
msgid "Unsupported key in additional tags ({k})"
|
|
147
|
+
msgstr ""
|
|
Binary file
|
|
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
|