diff --git a/tracker/tests/test_compatibility.py b/tracker/tests/test_compatibility.py index c0594a0..1f62444 100644 --- a/tracker/tests/test_compatibility.py +++ b/tracker/tests/test_compatibility.py @@ -127,6 +127,17 @@ def test_load_session_import_payload_supports_cowlog_frame_rate_with_unit(self): self.assertEqual(report['frame_rate'], '29.97 fps') self.assertAlmostEqual(payload['events'][0]['time'], 10.5005, places=3) + def test_load_session_import_payload_supports_cowlog_frame_rate_ratio(self): + upload = SimpleUploadedFile( + 'cowlog.txt', + b'# fps\t30000/1001\n00:00:10:15\tEat\tNear\n', + content_type='text/plain', + ) + payload, report = load_session_import_payload(upload, self.session) + self.assertEqual(report['detected_format'], 'cowlog-results-v1') + self.assertEqual(report['frame_rate'], '30000/1001') + self.assertAlmostEqual(payload['events'][0]['time'], 10.5005, places=3) + def test_load_session_import_payload_supports_cowlog_colon_metadata(self): upload = SimpleUploadedFile( 'cowlog.txt', @@ -300,6 +311,17 @@ def test_load_session_import_payload_supports_tabular_custom_frame_rate(self): self.assertAlmostEqual(payload['events'][0]['time'], 5.2, places=3) self.assertAlmostEqual(payload['events'][1]['time'], 8.4, places=3) + def test_load_session_import_payload_supports_tabular_ratio_frame_rate(self): + upload = SimpleUploadedFile( + 'boris_rows.csv', + b'time,stop,behavior,frame_rate\n00:00:05:10,00:00:08:20,Stand,30000/1001\n', + content_type='text/csv', + ) + payload, report = load_session_import_payload(upload, self.session) + self.assertEqual(report['detected_format'], 'boris-tabular-csv-v1') + self.assertAlmostEqual(payload['events'][0]['time'], 5.3336, places=3) + self.assertAlmostEqual(payload['events'][1]['time'], 8.6673, places=3) + def test_load_session_import_payload_supports_state_duration_column(self): upload = SimpleUploadedFile( 'boris_rows.csv', diff --git a/tracker/views.py b/tracker/views.py index feaeaac..1ae10d0 100644 --- a/tracker/views.py +++ b/tracker/views.py @@ -692,7 +692,18 @@ def _normalize_frame_rate_token(value: str | float | Decimal | None) -> Decimal: return Decimal('25') if isinstance(value, Decimal): return value if value > 0 else Decimal('25') - match = re.search(r'[-+]?\d+(?:[.,]\d+)?', str(value)) + token = str(value).strip() + ratio_match = re.search( + r'(?P[-+]?\d+(?:[.,]\d+)?)\s*/\s*(?P[-+]?\d+(?:[.,]\d+)?)', token + ) + if ratio_match: + numerator = _decimal(ratio_match.group('num').replace(',', '.'), default='25') + denominator = _decimal(ratio_match.group('den').replace(',', '.'), default='1') + if denominator > 0: + ratio = numerator / denominator + if ratio > 0: + return ratio + match = re.search(r'[-+]?\d+(?:[.,]\d+)?', token) if not match: return Decimal('25') parsed = _decimal(match.group(0), default='25')