From bcf4f3ad12886b8dd49d7a0d27a569d708016213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafal=20W=C3=B3jcik?= Date: Wed, 7 Oct 2026 15:12:29 +0200 Subject: [PATCH] Handle malformed video GPX sidecars --- mapillary_tools/exceptions.py | 4 +++ .../geotag/video_extractors/gpx.py | 10 +++++++- tests/integration/test_process.py | 25 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/mapillary_tools/exceptions.py b/mapillary_tools/exceptions.py index 5474fd1b..f24084de 100644 --- a/mapillary_tools/exceptions.py +++ b/mapillary_tools/exceptions.py @@ -56,6 +56,10 @@ class MapillaryVideoGPSNotFoundError(MapillaryDescriptionError): pass +class MapillaryInvalidGPXError(MapillaryDescriptionError): + pass + + class MapillaryInvalidVideoError(MapillaryDescriptionError): pass diff --git a/mapillary_tools/geotag/video_extractors/gpx.py b/mapillary_tools/geotag/video_extractors/gpx.py index 00722bd1..e4c83a57 100644 --- a/mapillary_tools/geotag/video_extractors/gpx.py +++ b/mapillary_tools/geotag/video_extractors/gpx.py @@ -12,6 +12,8 @@ import typing as T from pathlib import Path +import gpxpy + if sys.version_info >= (3, 12): from typing import override else: @@ -50,7 +52,13 @@ def __init__( @override def extract(self) -> types.VideoMetadata: - gpx_tracks = parse_gpx(self.gpx_path) + try: + gpx_tracks = parse_gpx(self.gpx_path) + except (OSError, UnicodeError, gpxpy.gpx.GPXException) as ex: + raise exceptions.MapillaryInvalidGPXError( + f"{self.gpx_path.name} is not a valid GPX file. " + "Replace it with a valid GPX file." + ) from ex if 1 < len(gpx_tracks): LOG.warning( diff --git a/tests/integration/test_process.py b/tests/integration/test_process.py index 765fad80..83574937 100644 --- a/tests/integration/test_process.py +++ b/tests/integration/test_process.py @@ -677,6 +677,31 @@ def test_process_geotag_with_gpx_pattern(setup_data: py.path.local): assert len(descs[0]["MAPGPSTrack"]) > 0 +def test_process_geotag_with_malformed_gpx_pattern(setup_data: py.path.local): + video_path = setup_data.join("gopro_data").join("max-360mode.mp4") + gpx_file = setup_data.join("gopro_data").join("max-360mode.gpx") + gpx_file.write("") + + descs = run_process_for_descs( + [ + *[ + "--geotag_source", + json.dumps({"source": "gpx", "filetypes": ["video"]}), + ], + *["--geotag_source", "native"], + str(video_path), + ] + ) + + video_desc = next(desc for desc in descs if desc["filename"] == str(video_path)) + assert video_desc["error"] == { + "type": "MapillaryInvalidGPXError", + "message": ( + "max-360mode.gpx is not a valid GPX file. Replace it with a valid GPX file." + ), + } + + def test_process_video_geotag_source_with_exiftool_runtime(setup_data: py.path.local): pytest_skip_if_not_exiftool_installed()