mapillary-tools 0.13.3__py3-none-any.whl → 0.14.0__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 (87) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +198 -55
  3. mapillary_tools/authenticate.py +326 -64
  4. mapillary_tools/blackvue_parser.py +195 -0
  5. mapillary_tools/camm/camm_builder.py +55 -97
  6. mapillary_tools/camm/camm_parser.py +429 -181
  7. mapillary_tools/commands/__main__.py +10 -6
  8. mapillary_tools/commands/authenticate.py +8 -1
  9. mapillary_tools/commands/process.py +27 -51
  10. mapillary_tools/commands/process_and_upload.py +18 -5
  11. mapillary_tools/commands/sample_video.py +2 -3
  12. mapillary_tools/commands/upload.py +44 -13
  13. mapillary_tools/commands/video_process_and_upload.py +19 -5
  14. mapillary_tools/config.py +65 -26
  15. mapillary_tools/constants.py +141 -18
  16. mapillary_tools/exceptions.py +37 -34
  17. mapillary_tools/exif_read.py +221 -116
  18. mapillary_tools/exif_write.py +10 -8
  19. mapillary_tools/exiftool_read.py +33 -42
  20. mapillary_tools/exiftool_read_video.py +97 -47
  21. mapillary_tools/exiftool_runner.py +57 -0
  22. mapillary_tools/ffmpeg.py +417 -242
  23. mapillary_tools/geo.py +158 -118
  24. mapillary_tools/geotag/__init__.py +0 -1
  25. mapillary_tools/geotag/base.py +147 -0
  26. mapillary_tools/geotag/factory.py +307 -0
  27. mapillary_tools/geotag/geotag_images_from_exif.py +14 -131
  28. mapillary_tools/geotag/geotag_images_from_exiftool.py +136 -85
  29. mapillary_tools/geotag/geotag_images_from_gpx.py +60 -124
  30. mapillary_tools/geotag/geotag_images_from_gpx_file.py +13 -126
  31. mapillary_tools/geotag/geotag_images_from_nmea_file.py +4 -5
  32. mapillary_tools/geotag/geotag_images_from_video.py +88 -51
  33. mapillary_tools/geotag/geotag_videos_from_exiftool.py +123 -0
  34. mapillary_tools/geotag/geotag_videos_from_gpx.py +52 -0
  35. mapillary_tools/geotag/geotag_videos_from_video.py +20 -185
  36. mapillary_tools/geotag/image_extractors/base.py +18 -0
  37. mapillary_tools/geotag/image_extractors/exif.py +60 -0
  38. mapillary_tools/geotag/image_extractors/exiftool.py +18 -0
  39. mapillary_tools/geotag/options.py +182 -0
  40. mapillary_tools/geotag/utils.py +52 -16
  41. mapillary_tools/geotag/video_extractors/base.py +18 -0
  42. mapillary_tools/geotag/video_extractors/exiftool.py +70 -0
  43. mapillary_tools/geotag/video_extractors/gpx.py +116 -0
  44. mapillary_tools/geotag/video_extractors/native.py +160 -0
  45. mapillary_tools/{geotag → gpmf}/gpmf_parser.py +205 -182
  46. mapillary_tools/{geotag → gpmf}/gps_filter.py +5 -3
  47. mapillary_tools/history.py +134 -20
  48. mapillary_tools/mp4/construct_mp4_parser.py +17 -10
  49. mapillary_tools/mp4/io_utils.py +0 -1
  50. mapillary_tools/mp4/mp4_sample_parser.py +36 -28
  51. mapillary_tools/mp4/simple_mp4_builder.py +10 -9
  52. mapillary_tools/mp4/simple_mp4_parser.py +13 -22
  53. mapillary_tools/process_geotag_properties.py +184 -414
  54. mapillary_tools/process_sequence_properties.py +594 -225
  55. mapillary_tools/sample_video.py +20 -26
  56. mapillary_tools/serializer/description.py +587 -0
  57. mapillary_tools/serializer/gpx.py +132 -0
  58. mapillary_tools/telemetry.py +26 -13
  59. mapillary_tools/types.py +98 -611
  60. mapillary_tools/upload.py +411 -387
  61. mapillary_tools/upload_api_v4.py +167 -142
  62. mapillary_tools/uploader.py +804 -284
  63. mapillary_tools/utils.py +49 -18
  64. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info}/METADATA +93 -35
  65. mapillary_tools-0.14.0.dist-info/RECORD +75 -0
  66. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info}/WHEEL +1 -1
  67. mapillary_tools/geotag/blackvue_parser.py +0 -118
  68. mapillary_tools/geotag/geotag_from_generic.py +0 -22
  69. mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -93
  70. mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -145
  71. mapillary_tools/video_data_extraction/cli_options.py +0 -22
  72. mapillary_tools/video_data_extraction/extract_video_data.py +0 -176
  73. mapillary_tools/video_data_extraction/extractors/base_parser.py +0 -75
  74. mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +0 -34
  75. mapillary_tools/video_data_extraction/extractors/camm_parser.py +0 -38
  76. mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +0 -71
  77. mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +0 -53
  78. mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +0 -52
  79. mapillary_tools/video_data_extraction/extractors/gopro_parser.py +0 -43
  80. mapillary_tools/video_data_extraction/extractors/gpx_parser.py +0 -108
  81. mapillary_tools/video_data_extraction/extractors/nmea_parser.py +0 -24
  82. mapillary_tools/video_data_extraction/video_data_parser_factory.py +0 -39
  83. mapillary_tools-0.13.3.dist-info/RECORD +0 -75
  84. /mapillary_tools/{geotag → gpmf}/gpmf_gps_filter.py +0 -0
  85. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info}/entry_points.txt +0 -0
  86. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info/licenses}/LICENSE +0 -0
  87. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info}/top_level.txt +0 -0
@@ -1,145 +0,0 @@
1
- import logging
2
- import typing as T
3
- import xml.etree.ElementTree as ET
4
- from multiprocessing import Pool
5
- from pathlib import Path
6
-
7
- from tqdm import tqdm
8
-
9
- from .. import exceptions, exiftool_read, geo, types, utils
10
- from ..exiftool_read_video import ExifToolReadVideo
11
- from ..telemetry import GPSPoint
12
- from . import gpmf_gps_filter, utils as video_utils
13
- from .geotag_from_generic import GeotagVideosFromGeneric
14
-
15
- LOG = logging.getLogger(__name__)
16
- _DESCRIPTION_TAG = "rdf:Description"
17
-
18
-
19
- class GeotagVideosFromExifToolVideo(GeotagVideosFromGeneric):
20
- def __init__(
21
- self,
22
- video_paths: T.Sequence[Path],
23
- xml_path: Path,
24
- num_processes: T.Optional[int] = None,
25
- ):
26
- self.video_paths = video_paths
27
- self.xml_path = xml_path
28
- self.num_processes = num_processes
29
- super().__init__()
30
-
31
- @staticmethod
32
- def geotag_video(element: ET.Element) -> types.VideoMetadataOrError:
33
- video_path = exiftool_read.find_rdf_description_path(element)
34
- assert video_path is not None, "must find the path from the element"
35
-
36
- try:
37
- exif = ExifToolReadVideo(ET.ElementTree(element))
38
-
39
- points = exif.extract_gps_track()
40
-
41
- if not points:
42
- raise exceptions.MapillaryVideoGPSNotFoundError(
43
- "No GPS data found from the video"
44
- )
45
-
46
- points = geo.extend_deduplicate_points(points)
47
- assert points, "must have at least one point"
48
-
49
- if all(isinstance(p, GPSPoint) for p in points):
50
- points = T.cast(
51
- T.List[geo.Point],
52
- gpmf_gps_filter.remove_noisy_points(
53
- T.cast(T.List[GPSPoint], points)
54
- ),
55
- )
56
- if not points:
57
- raise exceptions.MapillaryGPSNoiseError("GPS is too noisy")
58
-
59
- stationary = video_utils.is_video_stationary(
60
- geo.get_max_distance_from_start([(p.lat, p.lon) for p in points])
61
- )
62
-
63
- if stationary:
64
- raise exceptions.MapillaryStationaryVideoError("Stationary video")
65
-
66
- video_metadata = types.VideoMetadata(
67
- video_path,
68
- md5sum=None,
69
- filesize=utils.get_file_size(video_path),
70
- filetype=types.FileType.VIDEO,
71
- points=points,
72
- make=exif.extract_make(),
73
- model=exif.extract_model(),
74
- )
75
-
76
- LOG.debug("Calculating MD5 checksum for %s", str(video_metadata.filename))
77
-
78
- video_metadata.update_md5sum()
79
-
80
- except Exception as ex:
81
- if not isinstance(ex, exceptions.MapillaryDescriptionError):
82
- LOG.warning(
83
- "Failed to geotag video %s: %s",
84
- video_path,
85
- str(ex),
86
- exc_info=LOG.getEffectiveLevel() <= logging.DEBUG,
87
- )
88
- return types.describe_error_metadata(
89
- ex, video_path, filetype=types.FileType.VIDEO
90
- )
91
-
92
- return video_metadata
93
-
94
- def to_description(self) -> T.List[types.VideoMetadataOrError]:
95
- rdf_description_by_path = exiftool_read.index_rdf_description_by_path(
96
- [self.xml_path]
97
- )
98
-
99
- error_metadatas: T.List[types.ErrorMetadata] = []
100
- rdf_descriptions: T.List[ET.Element] = []
101
- for path in self.video_paths:
102
- rdf_description = rdf_description_by_path.get(
103
- exiftool_read.canonical_path(path)
104
- )
105
- if rdf_description is None:
106
- exc = exceptions.MapillaryEXIFNotFoundError(
107
- f"The {_DESCRIPTION_TAG} XML element for the video not found"
108
- )
109
- error_metadatas.append(
110
- types.describe_error_metadata(
111
- exc, path, filetype=types.FileType.VIDEO
112
- )
113
- )
114
- else:
115
- rdf_descriptions.append(rdf_description)
116
-
117
- if self.num_processes is None:
118
- num_processes = self.num_processes
119
- disable_multiprocessing = False
120
- else:
121
- num_processes = max(self.num_processes, 1)
122
- disable_multiprocessing = self.num_processes <= 0
123
-
124
- with Pool(processes=num_processes) as pool:
125
- video_metadatas_iter: T.Iterator[types.VideoMetadataOrError]
126
- if disable_multiprocessing:
127
- video_metadatas_iter = map(
128
- GeotagVideosFromExifToolVideo.geotag_video, rdf_descriptions
129
- )
130
- else:
131
- video_metadatas_iter = pool.imap(
132
- GeotagVideosFromExifToolVideo.geotag_video,
133
- rdf_descriptions,
134
- )
135
- video_metadata_or_errors = list(
136
- tqdm(
137
- video_metadatas_iter,
138
- desc="Extracting GPS tracks from ExifTool XML",
139
- unit="videos",
140
- disable=LOG.getEffectiveLevel() <= logging.DEBUG,
141
- total=len(self.video_paths),
142
- )
143
- )
144
-
145
- return error_metadatas + video_metadata_or_errors
@@ -1,22 +0,0 @@
1
- import typing as T
2
- from pathlib import Path
3
-
4
-
5
- known_parser_options = ["source", "pattern", "exiftool_path"]
6
-
7
-
8
- class CliParserOptions(T.TypedDict, total=False):
9
- source: str
10
- pattern: T.Optional[str]
11
- exiftool_path: T.Optional[Path]
12
-
13
-
14
- class CliOptions(T.TypedDict, total=False):
15
- paths: T.Sequence[Path]
16
- recursive: bool
17
- geotag_sources_options: T.Sequence[CliParserOptions]
18
- geotag_source_path: Path
19
- exiftool_path: Path
20
- num_processes: int
21
- device_make: T.Optional[str]
22
- device_model: T.Optional[str]
@@ -1,176 +0,0 @@
1
- import logging
2
- import typing as T
3
- from multiprocessing import Pool
4
- from pathlib import Path
5
-
6
- import tqdm
7
-
8
- from .. import exceptions, geo, utils
9
- from ..geotag import gpmf_gps_filter, utils as video_utils
10
- from ..telemetry import GPSPoint
11
- from ..types import (
12
- ErrorMetadata,
13
- FileType,
14
- MetadataOrError,
15
- VideoMetadata,
16
- VideoMetadataOrError,
17
- )
18
- from . import video_data_parser_factory
19
- from .cli_options import CliOptions
20
- from .extractors.base_parser import BaseParser
21
-
22
-
23
- LOG = logging.getLogger(__name__)
24
-
25
-
26
- class VideoDataExtractor:
27
- options: CliOptions
28
-
29
- def __init__(self, options: CliOptions) -> None:
30
- self.options = options
31
-
32
- def process(self) -> T.List[MetadataOrError]:
33
- paths = self.options["paths"]
34
- self._check_paths(paths)
35
- video_files = utils.find_videos(paths)
36
- self._check_sources_cardinality(video_files)
37
-
38
- num_processes = self.options["num_processes"] or None
39
- with Pool(processes=num_processes) as pool:
40
- if num_processes == 1:
41
- iter: T.Iterator[VideoMetadataOrError] = map(
42
- self.process_file, video_files
43
- )
44
- else:
45
- iter = pool.imap(self.process_file, video_files)
46
-
47
- video_metadata_or_errors = list(
48
- tqdm.tqdm(
49
- iter,
50
- desc="Extracting GPS tracks",
51
- unit="videos",
52
- disable=LOG.getEffectiveLevel() <= logging.DEBUG,
53
- total=len(video_files),
54
- )
55
- )
56
-
57
- return video_metadata_or_errors
58
-
59
- def process_file(self, file: Path) -> VideoMetadataOrError:
60
- parsers = video_data_parser_factory.make_parsers(file, self.options)
61
- points: T.Sequence[geo.Point] = []
62
- make = self.options["device_make"]
63
- model = self.options["device_model"]
64
-
65
- ex: T.Optional[Exception]
66
- for parser in parsers:
67
- log_vars = {
68
- "filename": file,
69
- "parser": parser.parser_label,
70
- "source": parser.geotag_source_path,
71
- }
72
- try:
73
- if not points:
74
- points = self._extract_points(parser, log_vars)
75
- if not model:
76
- model = parser.extract_model()
77
- if not make:
78
- make = parser.extract_make()
79
- except Exception as e:
80
- ex = e
81
- LOG.warning(
82
- '%(filename)s: Exception for parser %(parser)s while processing source %(source)s: "%(e)s"',
83
- {**log_vars, "e": e},
84
- )
85
-
86
- # After trying all parsers, return the points if we found any, otherwise
87
- # the last exception thrown or a default one.
88
- # Note that if we have points, we return them, regardless of exceptions
89
- # with make or model.
90
- if points:
91
- video_metadata = VideoMetadata(
92
- filename=file,
93
- filetype=FileType.VIDEO,
94
- md5sum=None,
95
- filesize=utils.get_file_size(file),
96
- points=points,
97
- make=make,
98
- model=model,
99
- )
100
- video_metadata.update_md5sum()
101
- return video_metadata
102
- else:
103
- return ErrorMetadata(
104
- filename=file,
105
- error=(
106
- ex
107
- if ex
108
- else exceptions.MapillaryVideoGPSNotFoundError(
109
- "No GPS data found from the video"
110
- )
111
- ),
112
- filetype=FileType.VIDEO,
113
- )
114
-
115
- def _extract_points(
116
- self, parser: BaseParser, log_vars: T.Dict
117
- ) -> T.Sequence[geo.Point]:
118
- points = parser.extract_points()
119
- if points:
120
- LOG.debug(
121
- "%(filename)s: %(points)d points extracted by parser %(parser)s from file %(source)s}",
122
- {**log_vars, "points": len(points)},
123
- )
124
-
125
- return self._sanitize_points(points)
126
-
127
- @staticmethod
128
- def _check_paths(import_paths: T.Sequence[Path]):
129
- for path in import_paths:
130
- if not path.is_file() and not path.is_dir():
131
- raise exceptions.MapillaryFileNotFoundError(
132
- f"Import file or directory not found: {path}"
133
- )
134
-
135
- def _check_sources_cardinality(self, files: T.Sequence[Path]):
136
- if len(files) > 1:
137
- for parser_opts in self.options["geotag_sources_options"]:
138
- pattern = parser_opts.get("pattern")
139
- if pattern and "%" not in pattern:
140
- raise exceptions.MapillaryUserError(
141
- "Multiple video files found: Geotag source pattern for source %s must include filename placeholders",
142
- parser_opts["source"],
143
- )
144
-
145
- @staticmethod
146
- def _sanitize_points(points: T.Sequence[geo.Point]) -> T.Sequence[geo.Point]:
147
- """
148
- Deduplicates points, when possible removes noisy ones, and checks
149
- against stationary videos
150
- """
151
-
152
- if not points:
153
- raise exceptions.MapillaryVideoGPSNotFoundError(
154
- "No GPS data found in the given sources"
155
- )
156
-
157
- points = geo.extend_deduplicate_points(points)
158
-
159
- if all(isinstance(p, GPSPoint) for p in points):
160
- points = T.cast(
161
- T.Sequence[geo.Point],
162
- gpmf_gps_filter.remove_noisy_points(
163
- T.cast(T.Sequence[GPSPoint], points)
164
- ),
165
- )
166
- if not points:
167
- raise exceptions.MapillaryGPSNoiseError("GPS is too noisy")
168
-
169
- stationary = video_utils.is_video_stationary(
170
- geo.get_max_distance_from_start([(p.lat, p.lon) for p in points])
171
- )
172
-
173
- if stationary:
174
- raise exceptions.MapillaryStationaryVideoError("Stationary video")
175
-
176
- return points
@@ -1,75 +0,0 @@
1
- import abc
2
- import functools
3
- import logging
4
- import os
5
- import typing as T
6
- from pathlib import Path
7
-
8
- from ... import geo
9
- from ..cli_options import CliOptions, CliParserOptions
10
-
11
- LOG = logging.getLogger(__name__)
12
-
13
-
14
- class BaseParser(metaclass=abc.ABCMeta):
15
- videoPath: Path
16
- options: CliOptions
17
- parserOptions: CliParserOptions
18
-
19
- def __init__(
20
- self, video_path: Path, options: CliOptions, parser_options: CliParserOptions
21
- ) -> None:
22
- self.videoPath = video_path
23
- self.options = options
24
- self.parserOptions = parser_options
25
-
26
- @property
27
- @abc.abstractmethod
28
- def default_source_pattern(self) -> str:
29
- raise NotImplementedError
30
-
31
- @property
32
- @abc.abstractmethod
33
- def parser_label(self) -> str:
34
- raise NotImplementedError
35
-
36
- @abc.abstractmethod
37
- def extract_points(self) -> T.Sequence[geo.Point]:
38
- raise NotImplementedError
39
-
40
- @abc.abstractmethod
41
- def extract_make(self) -> T.Optional[str]:
42
- raise NotImplementedError
43
-
44
- @abc.abstractmethod
45
- def extract_model(self) -> T.Optional[str]:
46
- raise NotImplementedError
47
-
48
- @functools.cached_property
49
- def geotag_source_path(self) -> T.Optional[Path]:
50
- video_dir = self.videoPath.parent.resolve()
51
- video_filename = self.videoPath.name
52
- video_basename, video_ext = os.path.splitext(video_filename)
53
- pattern = self.parserOptions.get("pattern") or self.default_source_pattern
54
-
55
- replaced = Path(
56
- pattern.replace("%f", video_filename)
57
- .replace("%g", video_basename)
58
- .replace("%e", video_ext)
59
- )
60
- abs_path = (
61
- replaced if replaced.is_absolute() else Path.joinpath(video_dir, replaced)
62
- ).resolve()
63
-
64
- return abs_path if abs_path.is_file() else None
65
-
66
- @staticmethod
67
- def _rebase_times(points: T.Sequence[geo.Point], offset: float = 0.0):
68
- """
69
- Make point times start from 0
70
- """
71
- if points:
72
- first_timestamp = points[0].time
73
- for p in points:
74
- p.time = (p.time - first_timestamp) + offset
75
- return points
@@ -1,34 +0,0 @@
1
- import typing as T
2
-
3
- from ... import geo
4
- from ...geotag import blackvue_parser
5
- from ...mp4 import simple_mp4_parser as sparser
6
- from .base_parser import BaseParser
7
-
8
-
9
- class BlackVueParser(BaseParser):
10
- default_source_pattern = "%f"
11
- must_rebase_times_to_zero = False
12
- parser_label = "blackvue"
13
-
14
- pointsFound: bool = False
15
-
16
- def extract_points(self) -> T.Sequence[geo.Point]:
17
- source_path = self.geotag_source_path
18
- if not source_path:
19
- return []
20
- with source_path.open("rb") as fp:
21
- try:
22
- points = blackvue_parser.extract_points(fp) or []
23
- self.pointsFound = len(points) > 0
24
- return points
25
- except sparser.ParsingError:
26
- return []
27
-
28
- def extract_make(self) -> T.Optional[str]:
29
- # If no points were found, assume this is not a BlackVue
30
- return "Blackvue" if self.pointsFound else None
31
-
32
- def extract_model(self) -> T.Optional[str]:
33
- with self.videoPath.open("rb") as fp:
34
- return blackvue_parser.extract_camera_model(fp) or None
@@ -1,38 +0,0 @@
1
- import functools
2
- import typing as T
3
-
4
- from ... import geo
5
- from ...camm import camm_parser
6
- from ...mp4 import simple_mp4_parser as sparser
7
- from .base_parser import BaseParser
8
-
9
-
10
- class CammParser(BaseParser):
11
- default_source_pattern = "%f"
12
- must_rebase_times_to_zero = False
13
- parser_label = "camm"
14
-
15
- @functools.cached_property
16
- def _camera_info(self) -> T.Tuple[str, str]:
17
- source_path = self.geotag_source_path
18
- if not source_path:
19
- return "", ""
20
-
21
- with source_path.open("rb") as fp:
22
- return camm_parser.extract_camera_make_and_model(fp)
23
-
24
- def extract_points(self) -> T.Sequence[geo.Point]:
25
- source_path = self.geotag_source_path
26
- if not source_path:
27
- return []
28
- with source_path.open("rb") as fp:
29
- try:
30
- return camm_parser.extract_points(fp) or []
31
- except sparser.ParsingError:
32
- return []
33
-
34
- def extract_make(self) -> T.Optional[str]:
35
- return self._camera_info[0] or None
36
-
37
- def extract_model(self) -> T.Optional[str]:
38
- return self._camera_info[1] or None
@@ -1,71 +0,0 @@
1
- import shutil
2
- import subprocess
3
- import typing as T
4
- from pathlib import Path
5
-
6
- from ... import constants, exceptions, geo
7
- from ..cli_options import CliOptions, CliParserOptions
8
- from .base_parser import BaseParser
9
- from .exiftool_xml_parser import ExiftoolXmlParser
10
-
11
-
12
- class ExiftoolRuntimeParser(BaseParser):
13
- """
14
- Wrapper around ExiftoolRdfParser that executes exiftool
15
- """
16
-
17
- exiftoolXmlParser: ExiftoolXmlParser
18
-
19
- default_source_pattern = "%f"
20
- must_rebase_times_to_zero = True
21
- parser_label = "exiftool_runtime"
22
-
23
- def __init__(
24
- self, video_path: Path, options: CliOptions, parser_options: CliParserOptions
25
- ):
26
- super().__init__(video_path, options, parser_options)
27
- exiftool_path = shutil.which(constants.EXIFTOOL_PATH)
28
-
29
- if not exiftool_path:
30
- raise exceptions.MapillaryExiftoolNotFoundError(
31
- "Cannot execute exiftool. Please install it from https://exiftool.org/ or you package manager, or set the environment variable MAPILLARY_TOOLS_EXIFTOOL_PATH"
32
- )
33
- if not self.geotag_source_path:
34
- return
35
-
36
- # To handle non-latin1 filenames under Windows, we pass the path
37
- # via stdin. See https://exiftool.org/faq.html#Q18
38
- stdin = str(self.geotag_source_path)
39
- args = [
40
- exiftool_path,
41
- "-q",
42
- "-r",
43
- "-n",
44
- "-ee",
45
- "-api",
46
- "LargeFileSupport=1",
47
- "-X",
48
- "-charset",
49
- "filename=utf8",
50
- "-@",
51
- "-",
52
- ]
53
-
54
- process = subprocess.run(
55
- args, capture_output=True, text=True, input=stdin, encoding="utf-8"
56
- )
57
-
58
- self.exiftoolXmlParser = ExiftoolXmlParser(
59
- video_path, options, parser_options, process.stdout
60
- )
61
-
62
- def extract_points(self) -> T.Sequence[geo.Point]:
63
- return self.exiftoolXmlParser.extract_points() if self.exiftoolXmlParser else []
64
-
65
- def extract_make(self) -> T.Optional[str]:
66
- return self.exiftoolXmlParser.extract_make() if self.exiftoolXmlParser else None
67
-
68
- def extract_model(self) -> T.Optional[str]:
69
- return (
70
- self.exiftoolXmlParser.extract_model() if self.exiftoolXmlParser else None
71
- )
@@ -1,53 +0,0 @@
1
- import typing as T
2
- import xml.etree.ElementTree as ET
3
-
4
- from pathlib import Path
5
-
6
- from ... import geo
7
- from ...exiftool_read import EXIFTOOL_NAMESPACES
8
- from ...exiftool_read_video import ExifToolReadVideo
9
- from ...geotag.geotag_videos_from_exiftool_video import _DESCRIPTION_TAG
10
- from ..cli_options import CliOptions, CliParserOptions
11
- from .base_parser import BaseParser
12
-
13
-
14
- class ExiftoolXmlParser(BaseParser):
15
- default_source_pattern = "%g.xml"
16
- parser_label = "exiftool_xml"
17
-
18
- exifToolReadVideo: T.Optional[ExifToolReadVideo] = None
19
-
20
- def __init__(
21
- self,
22
- video_path: Path,
23
- options: CliOptions,
24
- parser_options: CliParserOptions,
25
- xml_content: T.Optional[str] = None,
26
- ) -> None:
27
- super().__init__(video_path, options, parser_options)
28
-
29
- if xml_content:
30
- etree = ET.fromstring(xml_content)
31
- else:
32
- xml_path = self.geotag_source_path
33
- if not xml_path:
34
- return
35
- etree = ET.parse(xml_path).getroot()
36
-
37
- element = next(etree.iterfind(_DESCRIPTION_TAG, namespaces=EXIFTOOL_NAMESPACES))
38
- self.exifToolReadVideo = ExifToolReadVideo(ET.ElementTree(element))
39
-
40
- def extract_points(self) -> T.Sequence[geo.Point]:
41
- gps_points = (
42
- self.exifToolReadVideo.extract_gps_track() if self.exifToolReadVideo else []
43
- )
44
- self._rebase_times(gps_points)
45
- return gps_points
46
-
47
- def extract_make(self) -> T.Optional[str]:
48
- return self.exifToolReadVideo.extract_make() if self.exifToolReadVideo else None
49
-
50
- def extract_model(self) -> T.Optional[str]:
51
- return (
52
- self.exifToolReadVideo.extract_model() if self.exifToolReadVideo else None
53
- )
@@ -1,52 +0,0 @@
1
- import typing as T
2
- from pathlib import Path
3
-
4
- from ... import geo
5
- from ..cli_options import CliOptions, CliParserOptions
6
- from .base_parser import BaseParser
7
- from .blackvue_parser import BlackVueParser
8
- from .camm_parser import CammParser
9
- from .gopro_parser import GoProParser
10
-
11
-
12
- class GenericVideoParser(BaseParser):
13
- """
14
- Wrapper around the three native video parsers. It will try to execute them
15
- in the order camm-gopro-blackvue, like the previous implementation
16
- """
17
-
18
- parsers: T.Sequence[BaseParser] = []
19
-
20
- default_source_pattern = "%f"
21
- must_rebase_times_to_zero = False
22
- parser_label = "video"
23
-
24
- def __init__(
25
- self, video_path: Path, options: CliOptions, parser_options: CliParserOptions
26
- ) -> None:
27
- super().__init__(video_path, options, parser_options)
28
- camm_parser = CammParser(video_path, options, parser_options)
29
- gopro_parser = GoProParser(video_path, options, parser_options)
30
- blackvue_parser = BlackVueParser(video_path, options, parser_options)
31
- self.parsers = [camm_parser, gopro_parser, blackvue_parser]
32
-
33
- def extract_points(self) -> T.Sequence[geo.Point]:
34
- for parser in self.parsers:
35
- points = parser.extract_points()
36
- if points:
37
- return points
38
- return []
39
-
40
- def extract_make(self) -> T.Optional[str]:
41
- for parser in self.parsers:
42
- make = parser.extract_make()
43
- if make:
44
- return make
45
- return None
46
-
47
- def extract_model(self) -> T.Optional[str]:
48
- for parser in self.parsers:
49
- model = parser.extract_model()
50
- if model:
51
- return model
52
- return None