mapillary-tools 0.13.3a1__py3-none-any.whl → 0.14.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +235 -14
  3. mapillary_tools/authenticate.py +325 -64
  4. mapillary_tools/{geotag/blackvue_parser.py → blackvue_parser.py} +74 -54
  5. mapillary_tools/camm/camm_builder.py +55 -97
  6. mapillary_tools/camm/camm_parser.py +425 -177
  7. mapillary_tools/commands/__main__.py +11 -4
  8. mapillary_tools/commands/authenticate.py +8 -1
  9. mapillary_tools/commands/process.py +27 -51
  10. mapillary_tools/commands/process_and_upload.py +19 -5
  11. mapillary_tools/commands/sample_video.py +2 -3
  12. mapillary_tools/commands/upload.py +18 -9
  13. mapillary_tools/commands/video_process_and_upload.py +19 -5
  14. mapillary_tools/config.py +28 -12
  15. mapillary_tools/constants.py +46 -4
  16. mapillary_tools/exceptions.py +34 -35
  17. mapillary_tools/exif_read.py +158 -53
  18. mapillary_tools/exiftool_read.py +19 -5
  19. mapillary_tools/exiftool_read_video.py +12 -1
  20. mapillary_tools/exiftool_runner.py +77 -0
  21. mapillary_tools/geo.py +148 -107
  22. mapillary_tools/geotag/factory.py +298 -0
  23. mapillary_tools/geotag/geotag_from_generic.py +152 -11
  24. mapillary_tools/geotag/geotag_images_from_exif.py +43 -124
  25. mapillary_tools/geotag/geotag_images_from_exiftool.py +66 -70
  26. mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +32 -48
  27. mapillary_tools/geotag/geotag_images_from_gpx.py +41 -116
  28. mapillary_tools/geotag/geotag_images_from_gpx_file.py +15 -96
  29. mapillary_tools/geotag/geotag_images_from_nmea_file.py +4 -2
  30. mapillary_tools/geotag/geotag_images_from_video.py +46 -46
  31. mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +98 -92
  32. mapillary_tools/geotag/geotag_videos_from_gpx.py +140 -0
  33. mapillary_tools/geotag/geotag_videos_from_video.py +149 -181
  34. mapillary_tools/geotag/options.py +159 -0
  35. mapillary_tools/{geotag → gpmf}/gpmf_parser.py +194 -171
  36. mapillary_tools/history.py +3 -11
  37. mapillary_tools/mp4/io_utils.py +0 -1
  38. mapillary_tools/mp4/mp4_sample_parser.py +11 -3
  39. mapillary_tools/mp4/simple_mp4_parser.py +0 -10
  40. mapillary_tools/process_geotag_properties.py +151 -386
  41. mapillary_tools/process_sequence_properties.py +554 -202
  42. mapillary_tools/sample_video.py +8 -15
  43. mapillary_tools/telemetry.py +24 -12
  44. mapillary_tools/types.py +80 -22
  45. mapillary_tools/upload.py +316 -298
  46. mapillary_tools/upload_api_v4.py +55 -122
  47. mapillary_tools/uploader.py +396 -254
  48. mapillary_tools/utils.py +26 -0
  49. mapillary_tools/video_data_extraction/extract_video_data.py +17 -36
  50. mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +34 -19
  51. mapillary_tools/video_data_extraction/extractors/camm_parser.py +41 -17
  52. mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +4 -1
  53. mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +1 -2
  54. mapillary_tools/video_data_extraction/extractors/gopro_parser.py +37 -22
  55. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a1.dist-info}/METADATA +3 -2
  56. mapillary_tools-0.14.0a1.dist-info/RECORD +78 -0
  57. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a1.dist-info}/WHEEL +1 -1
  58. mapillary_tools/geotag/utils.py +0 -26
  59. mapillary_tools-0.13.3a1.dist-info/RECORD +0 -75
  60. /mapillary_tools/{geotag → gpmf}/gpmf_gps_filter.py +0 -0
  61. /mapillary_tools/{geotag → gpmf}/gps_filter.py +0 -0
  62. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a1.dist-info}/entry_points.txt +0 -0
  63. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a1.dist-info/licenses}/LICENSE +0 -0
  64. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a1.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,13 @@
1
+ from __future__ import annotations
2
+
1
3
  import dataclasses
2
4
  import logging
3
5
  import typing as T
4
- from multiprocessing import Pool
5
6
  from pathlib import Path
6
7
 
7
8
  from .. import exceptions, geo, types
8
9
  from .geotag_from_generic import GeotagImagesFromGeneric
9
- from .geotag_images_from_exif import GeotagImagesFromEXIF
10
+ from .geotag_images_from_exif import ImageEXIFExtractor
10
11
 
11
12
 
12
13
  LOG = logging.getLogger(__name__)
@@ -20,41 +21,19 @@ class GeotagImagesFromGPX(GeotagImagesFromGeneric):
20
21
  use_gpx_start_time: bool = False,
21
22
  use_image_start_time: bool = False,
22
23
  offset_time: float = 0.0,
23
- num_processes: T.Optional[int] = None,
24
+ num_processes: int | None = None,
24
25
  ):
25
- super().__init__()
26
- self.image_paths = image_paths
26
+ super().__init__(image_paths, num_processes=num_processes)
27
27
  self.points = points
28
28
  self.use_gpx_start_time = use_gpx_start_time
29
29
  self.use_image_start_time = use_image_start_time
30
30
  self.offset_time = offset_time
31
- self.num_processes = num_processes
32
-
33
- @staticmethod
34
- def geotag_image(image_path: Path) -> types.ImageMetadataOrError:
35
- return GeotagImagesFromEXIF.geotag_image(image_path, skip_lonlat_error=True)
36
-
37
- def geotag_multiple_images(
38
- self, image_paths: T.Sequence[Path]
39
- ) -> T.List[types.ImageMetadataOrError]:
40
- if self.num_processes is None:
41
- num_processes = self.num_processes
42
- disable_multiprocessing = False
43
- else:
44
- num_processes = max(self.num_processes, 1)
45
- disable_multiprocessing = self.num_processes <= 0
46
-
47
- if disable_multiprocessing:
48
- return list(map(GeotagImagesFromGPX.geotag_image, image_paths))
49
- else:
50
- with Pool(processes=num_processes) as pool:
51
- return pool.map(GeotagImagesFromGPX.geotag_image, image_paths)
52
31
 
53
32
  def _interpolate_image_metadata_along(
54
33
  self,
55
34
  image_metadata: types.ImageMetadata,
56
35
  sorted_points: T.Sequence[geo.Point],
57
- ) -> types.ImageMetadataOrError:
36
+ ) -> types.ImageMetadata:
58
37
  assert sorted_points, "must have at least one point"
59
38
 
60
39
  if image_metadata.time < sorted_points[0].time:
@@ -63,15 +42,12 @@ class GeotagImagesFromGPX(GeotagImagesFromGeneric):
63
42
  gpx_end_time = types.datetime_to_map_capture_time(sorted_points[-1].time)
64
43
  # with the tolerance of 1ms
65
44
  if 0.001 < delta:
66
- exc = exceptions.MapillaryOutsideGPXTrackError(
45
+ raise exceptions.MapillaryOutsideGPXTrackError(
67
46
  f"The image date time is {round(delta, 3)} seconds behind the GPX start point",
68
47
  image_time=types.datetime_to_map_capture_time(image_metadata.time),
69
48
  gpx_start_time=gpx_start_time,
70
49
  gpx_end_time=gpx_end_time,
71
50
  )
72
- return types.describe_error_metadata(
73
- exc, image_metadata.filename, filetype=types.FileType.IMAGE
74
- )
75
51
 
76
52
  if sorted_points[-1].time < image_metadata.time:
77
53
  delta = image_metadata.time - sorted_points[-1].time
@@ -79,15 +55,12 @@ class GeotagImagesFromGPX(GeotagImagesFromGeneric):
79
55
  gpx_end_time = types.datetime_to_map_capture_time(sorted_points[-1].time)
80
56
  # with the tolerance of 1ms
81
57
  if 0.001 < delta:
82
- exc = exceptions.MapillaryOutsideGPXTrackError(
58
+ raise exceptions.MapillaryOutsideGPXTrackError(
83
59
  f"The image time is {round(delta, 3)} seconds beyond the GPX end point",
84
60
  image_time=types.datetime_to_map_capture_time(image_metadata.time),
85
61
  gpx_start_time=gpx_start_time,
86
62
  gpx_end_time=gpx_end_time,
87
63
  )
88
- return types.describe_error_metadata(
89
- exc, image_metadata.filename, filetype=types.FileType.IMAGE
90
- )
91
64
 
92
65
  interpolated = geo.interpolate(sorted_points, image_metadata.time)
93
66
 
@@ -100,34 +73,25 @@ class GeotagImagesFromGPX(GeotagImagesFromGeneric):
100
73
  time=interpolated.time,
101
74
  )
102
75
 
103
- def to_description(self) -> T.List[types.ImageMetadataOrError]:
104
- metadatas: T.List[types.ImageMetadataOrError] = []
105
-
106
- if not self.points:
107
- exc = exceptions.MapillaryGPXEmptyError(
108
- "Empty GPS extracted from the geotag source"
109
- )
110
- for image_path in self.image_paths:
111
- metadatas.append(
112
- types.describe_error_metadata(
113
- exc, image_path, filetype=types.FileType.IMAGE
114
- ),
115
- )
116
- assert len(self.image_paths) == len(metadatas)
117
- return metadatas
76
+ def _generate_image_extractors(self) -> T.Sequence[ImageEXIFExtractor]:
77
+ return [
78
+ ImageEXIFExtractor(path, skip_lonlat_error=True)
79
+ for path in self.image_paths
80
+ ]
81
+
82
+ def to_description(self) -> list[types.ImageMetadataOrError]:
83
+ final_metadatas: list[types.ImageMetadataOrError] = []
118
84
 
119
- image_metadata_or_errors = self.geotag_multiple_images(self.image_paths)
85
+ image_metadata_or_errors = super().to_description()
120
86
 
121
- image_metadatas = []
122
- for metadata_or_error in image_metadata_or_errors:
123
- if isinstance(metadata_or_error, types.ErrorMetadata):
124
- metadatas.append(metadata_or_error)
125
- else:
126
- image_metadatas.append(metadata_or_error)
87
+ image_metadatas, error_metadatas = types.separate_errors(
88
+ image_metadata_or_errors
89
+ )
90
+ final_metadatas.extend(error_metadatas)
127
91
 
128
92
  if not image_metadatas:
129
- assert len(self.image_paths) == len(metadatas)
130
- return metadatas
93
+ assert len(self.image_paths) == len(final_metadatas)
94
+ return final_metadatas
131
95
 
132
96
  # Do not use point itself for comparison because point.angle or point.alt could be None
133
97
  # when you compare nonnull value with None, it will throw
@@ -162,64 +126,25 @@ class GeotagImagesFromGPX(GeotagImagesFromGeneric):
162
126
  LOG.debug("GPX start time delta: %s", time_delta)
163
127
  image_time_offset += time_delta
164
128
 
165
- LOG.debug("Final time offset for interpolation: %s", image_time_offset)
129
+ if image_time_offset:
130
+ LOG.debug("Final time offset for interpolation: %s", image_time_offset)
131
+ for image_metadata in sorted_image_metadatas:
132
+ # TODO: this time modification seems to affect final capture times
133
+ image_metadata.time += image_time_offset
166
134
 
167
135
  for image_metadata in sorted_image_metadatas:
168
- image_metadata.time += image_time_offset
169
- metadatas.append(
170
- self._interpolate_image_metadata_along(image_metadata, sorted_points)
171
- )
172
-
173
- assert len(self.image_paths) == len(metadatas)
174
- return metadatas
175
-
176
-
177
- class GeotagImagesFromGPXWithProgress(GeotagImagesFromGPX):
178
- def __init__(
179
- self,
180
- image_paths: T.Sequence[Path],
181
- points: T.Sequence[geo.Point],
182
- use_gpx_start_time: bool = False,
183
- use_image_start_time: bool = False,
184
- offset_time: float = 0.0,
185
- num_processes: T.Optional[int] = None,
186
- progress_bar=None,
187
- ) -> None:
188
- super().__init__(
189
- image_paths,
190
- points,
191
- use_gpx_start_time=use_gpx_start_time,
192
- use_image_start_time=use_image_start_time,
193
- offset_time=offset_time,
194
- num_processes=num_processes,
195
- )
196
- self._progress_bar = progress_bar
197
-
198
- def geotag_multiple_images(
199
- self, image_paths: T.Sequence[Path]
200
- ) -> T.List[types.ImageMetadataOrError]:
201
- if self._progress_bar is None:
202
- return super().geotag_multiple_images(image_paths)
203
-
204
- if self.num_processes is None:
205
- num_processes = self.num_processes
206
- disable_multiprocessing = False
207
- else:
208
- num_processes = max(self.num_processes, 1)
209
- disable_multiprocessing = self.num_processes <= 0
210
-
211
- output = []
212
- with Pool(processes=num_processes) as pool:
213
- image_metadatas_iter: T.Iterator[types.ImageMetadataOrError]
214
- if disable_multiprocessing:
215
- image_metadatas_iter = map(
216
- GeotagImagesFromGPX.geotag_image, image_paths
136
+ try:
137
+ final_metadatas.append(
138
+ self._interpolate_image_metadata_along(
139
+ image_metadata, sorted_points
140
+ )
217
141
  )
218
- else:
219
- image_metadatas_iter = pool.imap(
220
- GeotagImagesFromGPX.geotag_image, image_paths
142
+ except exceptions.MapillaryOutsideGPXTrackError as ex:
143
+ error_metadata = types.describe_error_metadata(
144
+ ex, image_metadata.filename, filetype=types.FileType.IMAGE
221
145
  )
222
- for image_metadata_or_error in image_metadatas_iter:
223
- self._progress_bar.update(1)
224
- output.append(image_metadata_or_error)
225
- return output
146
+ final_metadatas.append(error_metadata)
147
+
148
+ assert len(self.image_paths) == len(final_metadatas)
149
+
150
+ return final_metadatas
@@ -1,30 +1,27 @@
1
- import dataclasses
1
+ from __future__ import annotations
2
+
2
3
  import logging
3
4
  import typing as T
4
- from multiprocessing import Pool
5
5
  from pathlib import Path
6
6
 
7
7
  import gpxpy
8
- from tqdm import tqdm
9
8
 
10
- from .. import exif_read, geo, types
11
- from .geotag_from_generic import GeotagImagesFromGeneric
12
- from .geotag_images_from_gpx import GeotagImagesFromGPXWithProgress
9
+ from .. import geo
10
+ from .geotag_images_from_gpx import GeotagImagesFromGPX
13
11
 
14
12
 
15
13
  LOG = logging.getLogger(__name__)
16
14
 
17
15
 
18
- class GeotagImagesFromGPXFile(GeotagImagesFromGeneric):
16
+ class GeotagImagesFromGPXFile(GeotagImagesFromGPX):
19
17
  def __init__(
20
18
  self,
21
19
  image_paths: T.Sequence[Path],
22
20
  source_path: Path,
23
21
  use_gpx_start_time: bool = False,
24
22
  offset_time: float = 0.0,
25
- num_processes: T.Optional[int] = None,
23
+ num_processes: int | None = None,
26
24
  ):
27
- super().__init__()
28
25
  try:
29
26
  tracks = parse_gpx(source_path)
30
27
  except Exception as ex:
@@ -38,102 +35,24 @@ class GeotagImagesFromGPXFile(GeotagImagesFromGeneric):
38
35
  len(tracks),
39
36
  source_path,
40
37
  )
41
- self.points: T.List[geo.Point] = sum(tracks, [])
42
- self.image_paths = image_paths
43
- self.source_path = source_path
44
- self.use_gpx_start_time = use_gpx_start_time
45
- self.offset_time = offset_time
46
- self.num_processes = num_processes
47
-
48
- @staticmethod
49
- def _extract_image_metadata(
50
- image_metadata: types.ImageMetadata,
51
- ) -> types.ImageMetadataOrError:
52
- try:
53
- exif = exif_read.ExifRead(image_metadata.filename)
54
- orientation = exif.extract_orientation()
55
- make = exif.extract_make()
56
- model = exif.extract_model()
57
- except Exception as ex:
58
- return types.describe_error_metadata(
59
- ex, image_metadata.filename, filetype=types.FileType.IMAGE
60
- )
61
-
62
- return dataclasses.replace(
63
- image_metadata,
64
- MAPOrientation=orientation,
65
- MAPDeviceMake=make,
66
- MAPDeviceModel=model,
67
- )
68
-
69
- def to_description(self) -> T.List[types.ImageMetadataOrError]:
70
- with tqdm(
71
- total=len(self.image_paths),
72
- desc="Interpolating",
73
- unit="images",
74
- disable=LOG.getEffectiveLevel() <= logging.DEBUG,
75
- ) as pbar:
76
- geotag = GeotagImagesFromGPXWithProgress(
77
- self.image_paths,
78
- self.points,
79
- use_gpx_start_time=self.use_gpx_start_time,
80
- offset_time=self.offset_time,
81
- progress_bar=pbar,
82
- )
83
- image_metadata_or_errors = geotag.to_description()
84
-
85
- image_metadatas: T.List[types.ImageMetadata] = []
86
- error_metadatas: T.List[types.ErrorMetadata] = []
87
- for metadata in image_metadata_or_errors:
88
- if isinstance(metadata, types.ErrorMetadata):
89
- error_metadatas.append(metadata)
90
- else:
91
- image_metadatas.append(metadata)
92
-
93
- if self.num_processes is None:
94
- num_processes = self.num_processes
95
- disable_multiprocessing = False
96
- else:
97
- num_processes = max(self.num_processes, 1)
98
- disable_multiprocessing = self.num_processes <= 0
99
-
100
- with Pool(processes=num_processes) as pool:
101
- image_metadatas_iter: T.Iterator[types.ImageMetadataOrError]
102
- if disable_multiprocessing:
103
- image_metadatas_iter = map(
104
- GeotagImagesFromGPXFile._extract_image_metadata, image_metadatas
105
- )
106
- else:
107
- # Do not pass error metadatas where the error object can not be pickled for multiprocessing to work
108
- # Otherwise we get:
109
- # TypeError: __init__() missing 3 required positional arguments: 'image_time', 'gpx_start_time', and 'gpx_end_time'
110
- # See https://stackoverflow.com/a/61432070
111
- image_metadatas_iter = pool.imap(
112
- GeotagImagesFromGPXFile._extract_image_metadata, image_metadatas
113
- )
114
- image_metadata_or_errors = list(
115
- tqdm(
116
- image_metadatas_iter,
117
- desc="Processing",
118
- unit="images",
119
- disable=LOG.getEffectiveLevel() <= logging.DEBUG,
120
- )
121
- )
122
-
123
- return (
124
- T.cast(T.List[types.ImageMetadataOrError], error_metadatas)
125
- + image_metadata_or_errors
38
+ points = sum(tracks, [])
39
+ super().__init__(
40
+ image_paths,
41
+ points,
42
+ use_gpx_start_time=use_gpx_start_time,
43
+ offset_time=offset_time,
44
+ num_processes=num_processes,
126
45
  )
127
46
 
128
47
 
129
48
  Track = T.List[geo.Point]
130
49
 
131
50
 
132
- def parse_gpx(gpx_file: Path) -> T.List[Track]:
51
+ def parse_gpx(gpx_file: Path) -> list[Track]:
133
52
  with gpx_file.open("r") as f:
134
53
  gpx = gpxpy.parse(f)
135
54
 
136
- tracks: T.List[Track] = []
55
+ tracks: list[Track] = []
137
56
 
138
57
  for track in gpx.tracks:
139
58
  for segment in track.segments:
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import datetime
2
4
  import typing as T
3
5
  from pathlib import Path
@@ -15,7 +17,7 @@ class GeotagImagesFromNMEAFile(GeotagImagesFromGPX):
15
17
  source_path: Path,
16
18
  use_gpx_start_time: bool = False,
17
19
  offset_time: float = 0.0,
18
- num_processes: T.Optional[int] = None,
20
+ num_processes: int | None = None,
19
21
  ):
20
22
  points = get_lat_lon_time_from_nmea(source_path)
21
23
  super().__init__(
@@ -27,7 +29,7 @@ class GeotagImagesFromNMEAFile(GeotagImagesFromGPX):
27
29
  )
28
30
 
29
31
 
30
- def get_lat_lon_time_from_nmea(nmea_file: Path) -> T.List[geo.Point]:
32
+ def get_lat_lon_time_from_nmea(nmea_file: Path) -> list[geo.Point]:
31
33
  with nmea_file.open("r") as f:
32
34
  lines = f.readlines()
33
35
  lines = [line.rstrip("\n\r") for line in lines]
@@ -1,13 +1,13 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
4
  import typing as T
3
5
  from pathlib import Path
4
6
 
5
- from tqdm import tqdm
6
-
7
7
  from .. import types, utils
8
8
 
9
9
  from .geotag_from_generic import GeotagImagesFromGeneric
10
- from .geotag_images_from_gpx import GeotagImagesFromGPXWithProgress
10
+ from .geotag_images_from_gpx import GeotagImagesFromGPX
11
11
 
12
12
 
13
13
  LOG = logging.getLogger(__name__)
@@ -19,69 +19,69 @@ class GeotagImagesFromVideo(GeotagImagesFromGeneric):
19
19
  image_paths: T.Sequence[Path],
20
20
  video_metadatas: T.Sequence[types.VideoMetadataOrError],
21
21
  offset_time: float = 0.0,
22
- num_processes: T.Optional[int] = None,
22
+ num_processes: int | None = None,
23
23
  ):
24
- self.image_paths = image_paths
24
+ super().__init__(image_paths, num_processes=num_processes)
25
25
  self.video_metadatas = video_metadatas
26
26
  self.offset_time = offset_time
27
- self.num_processes = num_processes
28
- super().__init__()
29
27
 
30
- def to_description(self) -> T.List[types.ImageMetadataOrError]:
31
- # will return this list
32
- final_image_metadatas: T.List[types.ImageMetadataOrError] = []
28
+ def to_description(self) -> list[types.ImageMetadataOrError]:
29
+ # Will return this list
30
+ final_image_metadatas: list[types.ImageMetadataOrError] = []
33
31
 
34
- for video_metadata in self.video_metadatas:
32
+ video_metadatas, video_error_metadatas = types.separate_errors(
33
+ self.video_metadatas
34
+ )
35
+
36
+ for video_error_metadata in video_error_metadatas:
37
+ video_path = video_error_metadata.filename
38
+ sample_paths = list(
39
+ utils.filter_video_samples(self.image_paths, video_path)
40
+ )
41
+ LOG.debug(
42
+ "Found %d sample images from video %s with error: %s",
43
+ len(sample_paths),
44
+ video_path,
45
+ video_error_metadata.error,
46
+ )
47
+ for sample_path in sample_paths:
48
+ image_error_metadata = types.describe_error_metadata(
49
+ video_error_metadata.error,
50
+ sample_path,
51
+ filetype=types.FileType.IMAGE,
52
+ )
53
+ final_image_metadatas.append(image_error_metadata)
54
+
55
+ for video_metadata in video_metadatas:
35
56
  video_path = video_metadata.filename
36
- LOG.debug("Processing video: %s", video_path)
37
57
 
38
- sample_image_paths = list(
58
+ sample_paths = list(
39
59
  utils.filter_video_samples(self.image_paths, video_path)
40
60
  )
41
61
  LOG.debug(
42
62
  "Found %d sample images from video %s",
43
- len(sample_image_paths),
63
+ len(sample_paths),
44
64
  video_path,
45
65
  )
46
66
 
47
- if isinstance(video_metadata, types.ErrorMetadata):
48
- for sample_image_path in sample_image_paths:
49
- error_metadata = types.describe_error_metadata(
50
- video_metadata.error,
51
- sample_image_path,
52
- filetype=types.FileType.IMAGE,
53
- )
54
- final_image_metadatas.append(error_metadata)
55
- continue
56
-
57
- with tqdm(
58
- total=len(sample_image_paths),
59
- desc=f"Interpolating {video_path.name}",
60
- unit="images",
61
- disable=LOG.getEffectiveLevel() <= logging.DEBUG,
62
- ) as pbar:
63
- image_metadatas = GeotagImagesFromGPXWithProgress(
64
- sample_image_paths,
65
- video_metadata.points,
66
- use_gpx_start_time=False,
67
- use_image_start_time=True,
68
- offset_time=self.offset_time,
69
- num_processes=self.num_processes,
70
- progress_bar=pbar,
71
- ).to_description()
72
- final_image_metadatas.extend(image_metadatas)
73
-
74
- # update make and model
75
- LOG.debug(
76
- 'Found camera make "%s" and model "%s"',
77
- video_metadata.make,
78
- video_metadata.model,
67
+ geotag = GeotagImagesFromGPX(
68
+ sample_paths,
69
+ video_metadata.points,
70
+ use_gpx_start_time=False,
71
+ use_image_start_time=True,
72
+ offset_time=self.offset_time,
73
+ num_processes=self.num_processes,
79
74
  )
75
+
76
+ image_metadatas = geotag.to_description()
77
+
80
78
  for metadata in image_metadatas:
81
79
  if isinstance(metadata, types.ImageMetadata):
82
80
  metadata.MAPDeviceMake = video_metadata.make
83
81
  metadata.MAPDeviceModel = video_metadata.model
84
82
 
83
+ final_image_metadatas.extend(image_metadatas)
84
+
85
85
  # NOTE: this method only geotags images that have a corresponding video,
86
86
  # so the number of image metadata objects returned might be less than
87
87
  # the number of the input image_paths