Skip to content

Commit c8e16c3

Browse files
committed
fix: improve geolocation and type validation
- Add coordinate range validation for lat/lon - Return None for invalid coordinates to skip EXIF fields - Require both lat and lon to be valid before adding GPS data - Add isinstance check for date_captured consistency - Support https:// URIs in digital_source_type - Improve digital_source_type documentation with examples Addresses review comments for better input validation and docs
1 parent 86cded6 commit c8e16c3

File tree

1 file changed

+21
-8
lines changed

1 file changed

+21
-8
lines changed

src/numbers_c2pa/core.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ def format_geolocation(value: Optional[str], is_latitude: bool) -> Optional[str]
4545
if not value:
4646
return None
4747
d = Decimal(value)
48+
# Validate coordinate ranges - return None for invalid values to skip EXIF field
49+
if is_latitude and not (-90 <= d <= 90):
50+
return None
51+
if not is_latitude and not (-180 <= d <= 180):
52+
return None
4853
degrees = int(abs(d))
4954
minutes = (abs(d) - degrees) * 60
5055
if is_latitude:
@@ -128,11 +133,13 @@ def create_assertion_metadata(
128133
}
129134
if isinstance(date_created, datetime):
130135
metadata['dc:date'] = date_created.strftime('%Y-%m-%dT%H:%M:%SZ')
131-
if latitude:
132-
metadata['exif:GPSLatitude'] = format_geolocation(latitude, True)
133-
if longitude:
134-
metadata['exif:GPSLongitude'] = format_geolocation(longitude, False)
135-
if date_captured:
136+
if latitude and longitude:
137+
formatted_lat = format_geolocation(latitude, True)
138+
formatted_lon = format_geolocation(longitude, False)
139+
if formatted_lat and formatted_lon:
140+
metadata['exif:GPSLatitude'] = formatted_lat
141+
metadata['exif:GPSLongitude'] = formatted_lon
142+
if isinstance(date_captured, datetime):
136143
metadata['exif:GPSTimeStamp'] = date_captured.strftime('%H:%M:%S')
137144
metadata['exif:DateTimeOriginal'] = date_captured.strftime('%Y:%m:%d %H:%M:%S')
138145

@@ -218,8 +225,14 @@ def create_action_c2pa_opened(
218225
Args:
219226
asset_hex_hash: Hex-encoded SHA256 hash of the asset
220227
digital_source_type: Digital source type. Can be either:
221-
- Short form: 'trainedAlgorithmicMedia' (IPTC namespace auto-prepended)
222-
- Full URI: 'http://c2pa.org/digitalsourcetype/empty' (used as-is)
228+
- Short form (e.g., 'trainedAlgorithmicMedia', 'digitalCapture', 'negativeFilm'):
229+
The IPTC namespace 'http://cv.iptc.org/newscodes/digitalsourcetype/' will be prepended.
230+
Example: 'trainedAlgorithmicMedia' → 'http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia'
231+
- Full URI (starting with 'http://' or 'https://'):
232+
Used as-is. Can be from any namespace (IPTC or C2PA).
233+
Examples: 'http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture'
234+
'http://c2pa.org/digitalsourcetype/empty'
235+
See IPTC NewsCodes: https://cv.iptc.org/newscodes/digitalsourcetype/
223236
software_agent: Name of the software that created/modified the asset
224237
225238
Returns:
@@ -243,7 +256,7 @@ def create_action_c2pa_opened(
243256
}
244257
if digital_source_type:
245258
# If full URI provided, use as-is; otherwise prepend IPTC namespace
246-
if digital_source_type.startswith('http://'):
259+
if digital_source_type.startswith(('http://', 'https://')):
247260
action['digitalSourceType'] = digital_source_type
248261
else:
249262
action['digitalSourceType'] = (

0 commit comments

Comments
 (0)