matrice-analytics 0.1.43__py3-none-any.whl → 0.1.44__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.

Potentially problematic release.


This version of matrice-analytics might be problematic. Click here for more details.

@@ -1743,56 +1743,57 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
1743
1743
  return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
1744
1744
 
1745
1745
  def _format_timestamp(self, timestamp: Any) -> str:
1746
- """Format a timestamp so that exactly two digits follow the decimal point (milliseconds).
1746
+ """Format a timestamp to match the current timestamp format: YYYY:MM:DD HH:MM:SS.
1747
1747
 
1748
1748
  The input can be either:
1749
- 1. A numeric Unix timestamp (``float`` / ``int``) – it will first be converted to a
1750
- string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
1751
- 2. A string already following the same layout.
1749
+ 1. A numeric Unix timestamp (``float`` / ``int``) – it will be converted to datetime.
1750
+ 2. A string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
1752
1751
 
1753
- The returned value preserves the overall format of the input but truncates or pads
1754
- the fractional seconds portion to **exactly two digits**.
1752
+ The returned value will be in the format: YYYY:MM:DD HH:MM:SS (no milliseconds, no UTC suffix).
1755
1753
 
1756
1754
  Example
1757
1755
  -------
1758
- >>> self._format_timestamp("2025-08-19-04:22:47.187574 UTC")
1759
- '2025-08-19-04:22:47.18 UTC'
1756
+ >>> self._format_timestamp("2025-10-27-19:31:20.187574 UTC")
1757
+ '2025:10:27 19:31:20'
1760
1758
  """
1761
1759
 
1762
- # Convert numeric timestamps to the expected string representation first
1760
+ # Convert numeric timestamps to datetime first
1763
1761
  if isinstance(timestamp, (int, float)):
1764
- timestamp = datetime.fromtimestamp(timestamp, timezone.utc).strftime(
1765
- '%Y-%m-%d-%H:%M:%S.%f UTC'
1766
- )
1762
+ dt = datetime.fromtimestamp(timestamp, timezone.utc)
1763
+ return dt.strftime('%Y:%m:%d %H:%M:%S')
1767
1764
 
1768
1765
  # Ensure we are working with a string from here on
1769
1766
  if not isinstance(timestamp, str):
1770
1767
  return str(timestamp)
1771
1768
 
1772
- # If there is no fractional component, simply return the original string
1773
- if '.' not in timestamp:
1774
- return timestamp
1769
+ # Remove ' UTC' suffix if present
1770
+ timestamp_clean = timestamp.replace(' UTC', '').strip()
1775
1771
 
1776
- # Split out the main portion (up to the decimal point)
1777
- main_part, fractional_and_suffix = timestamp.split('.', 1)
1778
-
1779
- # Separate fractional digits from the suffix (typically ' UTC')
1780
- if ' ' in fractional_and_suffix:
1781
- fractional_part, suffix = fractional_and_suffix.split(' ', 1)
1782
- suffix = ' ' + suffix # Re-attach the space removed by split
1783
- else:
1784
- fractional_part, suffix = fractional_and_suffix, ''
1772
+ # Remove milliseconds if present (everything after the last dot)
1773
+ if '.' in timestamp_clean:
1774
+ timestamp_clean = timestamp_clean.split('.')[0]
1785
1775
 
1786
- # Guarantee exactly two digits for the fractional part
1787
- fractional_part = (fractional_part + '00')[:2]
1776
+ # Parse the timestamp string and convert to desired format
1777
+ try:
1778
+ # Handle format: YYYY-MM-DD-HH:MM:SS
1779
+ if timestamp_clean.count('-') >= 2:
1780
+ # Replace first two dashes with colons for date part, third with space
1781
+ parts = timestamp_clean.split('-')
1782
+ if len(parts) >= 4:
1783
+ # parts = ['2025', '10', '27', '19:31:20']
1784
+ formatted = f"{parts[0]}:{parts[1]}:{parts[2]} {'-'.join(parts[3:])}"
1785
+ return formatted
1786
+ except Exception:
1787
+ pass
1788
1788
 
1789
- return f"{main_part}.{fractional_part}{suffix}"
1789
+ # If parsing fails, return the cleaned string as-is
1790
+ return timestamp_clean
1790
1791
 
1791
1792
  def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
1792
1793
  """Get formatted current timestamp based on stream type."""
1794
+
1793
1795
  if not stream_info:
1794
1796
  return "00:00:00.00"
1795
-
1796
1797
  if precision:
1797
1798
  if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
1798
1799
  if frame_id:
@@ -1801,7 +1802,6 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
1801
1802
  start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
1802
1803
  stream_time_str = self._format_timestamp_for_video(start_time)
1803
1804
 
1804
-
1805
1805
  return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
1806
1806
  else:
1807
1807
  return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
@@ -1813,7 +1813,8 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
1813
1813
  start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
1814
1814
 
1815
1815
  stream_time_str = self._format_timestamp_for_video(start_time)
1816
-
1816
+
1817
+
1817
1818
  return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
1818
1819
  else:
1819
1820
  stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
@@ -1835,23 +1836,57 @@ class FaceRecognitionEmbeddingUseCase(BaseProcessor):
1835
1836
 
1836
1837
  if precision:
1837
1838
  if self.start_timer is None:
1838
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1839
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1840
+ if not candidate or candidate == "NA":
1841
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1842
+ self.start_timer = candidate
1839
1843
  return self._format_timestamp(self.start_timer)
1840
1844
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
1841
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1845
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1846
+ if not candidate or candidate == "NA":
1847
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1848
+ self.start_timer = candidate
1842
1849
  return self._format_timestamp(self.start_timer)
1843
1850
  else:
1844
1851
  return self._format_timestamp(self.start_timer)
1845
1852
 
1846
1853
  if self.start_timer is None:
1847
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1854
+ # Prefer direct input_settings.stream_time if available and not NA
1855
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1856
+ if not candidate or candidate == "NA":
1857
+ # Fallback to nested stream_info.stream_time used by current timestamp path
1858
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
1859
+ if stream_time_str:
1860
+ try:
1861
+ timestamp_str = stream_time_str.replace(" UTC", "")
1862
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
1863
+ self._tracking_start_time = dt.replace(tzinfo=timezone.utc).timestamp()
1864
+ candidate = datetime.fromtimestamp(self._tracking_start_time, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1865
+ except:
1866
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1867
+ else:
1868
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1869
+ self.start_timer = candidate
1848
1870
  return self._format_timestamp(self.start_timer)
1849
1871
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
1850
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1872
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1873
+ if not candidate or candidate == "NA":
1874
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
1875
+ if stream_time_str:
1876
+ try:
1877
+ timestamp_str = stream_time_str.replace(" UTC", "")
1878
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
1879
+ ts = dt.replace(tzinfo=timezone.utc).timestamp()
1880
+ candidate = datetime.fromtimestamp(ts, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1881
+ except:
1882
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1883
+ else:
1884
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1885
+ self.start_timer = candidate
1851
1886
  return self._format_timestamp(self.start_timer)
1852
1887
 
1853
1888
  else:
1854
- if self.start_timer is not None:
1889
+ if self.start_timer is not None and self.start_timer != "NA":
1855
1890
  return self._format_timestamp(self.start_timer)
1856
1891
 
1857
1892
  if self._tracking_start_time is None:
@@ -1617,6 +1617,53 @@ class ColorDetectionUseCase(BaseProcessor):
1617
1617
  seconds = round(float(timestamp % 60),2)
1618
1618
  return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
1619
1619
 
1620
+ def _format_timestamp(self, timestamp: Any) -> str:
1621
+ """Format a timestamp to match the current timestamp format: YYYY:MM:DD HH:MM:SS.
1622
+
1623
+ The input can be either:
1624
+ 1. A numeric Unix timestamp (``float`` / ``int``) – it will be converted to datetime.
1625
+ 2. A string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
1626
+
1627
+ The returned value will be in the format: YYYY:MM:DD HH:MM:SS (no milliseconds, no UTC suffix).
1628
+
1629
+ Example
1630
+ -------
1631
+ >>> self._format_timestamp("2025-10-27-19:31:20.187574 UTC")
1632
+ '2025:10:27 19:31:20'
1633
+ """
1634
+
1635
+ # Convert numeric timestamps to datetime first
1636
+ if isinstance(timestamp, (int, float)):
1637
+ dt = datetime.fromtimestamp(timestamp, timezone.utc)
1638
+ return dt.strftime('%Y:%m:%d %H:%M:%S')
1639
+
1640
+ # Ensure we are working with a string from here on
1641
+ if not isinstance(timestamp, str):
1642
+ return str(timestamp)
1643
+
1644
+ # Remove ' UTC' suffix if present
1645
+ timestamp_clean = timestamp.replace(' UTC', '').strip()
1646
+
1647
+ # Remove milliseconds if present (everything after the last dot)
1648
+ if '.' in timestamp_clean:
1649
+ timestamp_clean = timestamp_clean.split('.')[0]
1650
+
1651
+ # Parse the timestamp string and convert to desired format
1652
+ try:
1653
+ # Handle format: YYYY-MM-DD-HH:MM:SS
1654
+ if timestamp_clean.count('-') >= 2:
1655
+ # Replace first two dashes with colons for date part, third with space
1656
+ parts = timestamp_clean.split('-')
1657
+ if len(parts) >= 4:
1658
+ # parts = ['2025', '10', '27', '19:31:20']
1659
+ formatted = f"{parts[0]}:{parts[1]}:{parts[2]} {'-'.join(parts[3:])}"
1660
+ return formatted
1661
+ except Exception:
1662
+ pass
1663
+
1664
+ # If parsing fails, return the cleaned string as-is
1665
+ return timestamp_clean
1666
+
1620
1667
  def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
1621
1668
  """Get formatted current timestamp based on stream type."""
1622
1669
 
@@ -1630,7 +1677,6 @@ class ColorDetectionUseCase(BaseProcessor):
1630
1677
  start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
1631
1678
  stream_time_str = self._format_timestamp_for_video(start_time)
1632
1679
 
1633
-
1634
1680
  return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
1635
1681
  else:
1636
1682
  return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
@@ -1642,7 +1688,8 @@ class ColorDetectionUseCase(BaseProcessor):
1642
1688
  start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
1643
1689
 
1644
1690
  stream_time_str = self._format_timestamp_for_video(start_time)
1645
-
1691
+
1692
+
1646
1693
  return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
1647
1694
  else:
1648
1695
  stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
@@ -1661,26 +1708,60 @@ class ColorDetectionUseCase(BaseProcessor):
1661
1708
  """Get formatted start timestamp for 'TOTAL SINCE' based on stream type."""
1662
1709
  if not stream_info:
1663
1710
  return "00:00:00"
1664
-
1711
+
1665
1712
  if precision:
1666
1713
  if self.start_timer is None:
1667
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC"))
1714
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1715
+ if not candidate or candidate == "NA":
1716
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1717
+ self.start_timer = candidate
1668
1718
  return self._format_timestamp(self.start_timer)
1669
1719
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
1670
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC"))
1720
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1721
+ if not candidate or candidate == "NA":
1722
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1723
+ self.start_timer = candidate
1671
1724
  return self._format_timestamp(self.start_timer)
1672
1725
  else:
1673
1726
  return self._format_timestamp(self.start_timer)
1674
1727
 
1675
1728
  if self.start_timer is None:
1676
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC"))
1729
+ # Prefer direct input_settings.stream_time if available and not NA
1730
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1731
+ if not candidate or candidate == "NA":
1732
+ # Fallback to nested stream_info.stream_time used by current timestamp path
1733
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
1734
+ if stream_time_str:
1735
+ try:
1736
+ timestamp_str = stream_time_str.replace(" UTC", "")
1737
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
1738
+ self._tracking_start_time = dt.replace(tzinfo=timezone.utc).timestamp()
1739
+ candidate = datetime.fromtimestamp(self._tracking_start_time, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1740
+ except:
1741
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1742
+ else:
1743
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1744
+ self.start_timer = candidate
1677
1745
  return self._format_timestamp(self.start_timer)
1678
1746
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
1679
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC"))
1747
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1748
+ if not candidate or candidate == "NA":
1749
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
1750
+ if stream_time_str:
1751
+ try:
1752
+ timestamp_str = stream_time_str.replace(" UTC", "")
1753
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
1754
+ ts = dt.replace(tzinfo=timezone.utc).timestamp()
1755
+ candidate = datetime.fromtimestamp(ts, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1756
+ except:
1757
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1758
+ else:
1759
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1760
+ self.start_timer = candidate
1680
1761
  return self._format_timestamp(self.start_timer)
1681
1762
 
1682
1763
  else:
1683
- if self.start_timer is not None:
1764
+ if self.start_timer is not None and self.start_timer != "NA":
1684
1765
  return self._format_timestamp(self.start_timer)
1685
1766
 
1686
1767
  if self._tracking_start_time is None:
@@ -1699,52 +1780,6 @@ class ColorDetectionUseCase(BaseProcessor):
1699
1780
  dt = dt.replace(minute=0, second=0, microsecond=0)
1700
1781
  return dt.strftime('%Y:%m:%d %H:%M:%S')
1701
1782
 
1702
- def _format_timestamp(self, timestamp: Any) -> str:
1703
- """Format a timestamp so that exactly two digits follow the decimal point (milliseconds).
1704
-
1705
- The input can be either:
1706
- 1. A numeric Unix timestamp (``float`` / ``int``) – it will first be converted to a
1707
- string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
1708
- 2. A string already following the same layout.
1709
-
1710
- The returned value preserves the overall format of the input but truncates or pads
1711
- the fractional seconds portion to **exactly two digits**.
1712
-
1713
- Example
1714
- -------
1715
- >>> self._format_timestamp("2025-08-19-04:22:47.187574 UTC")
1716
- '2025-08-19-04:22:47.18 UTC'
1717
- """
1718
-
1719
- # Convert numeric timestamps to the expected string representation first
1720
- if isinstance(timestamp, (int, float)):
1721
- timestamp = datetime.fromtimestamp(timestamp, timezone.utc).strftime(
1722
- '%Y-%m-%d-%H:%M:%S.%f UTC'
1723
- )
1724
-
1725
- # Ensure we are working with a string from here on
1726
- if not isinstance(timestamp, str):
1727
- return str(timestamp)
1728
-
1729
- # If there is no fractional component, simply return the original string
1730
- if '.' not in timestamp:
1731
- return timestamp
1732
-
1733
- # Split out the main portion (up to the decimal point)
1734
- main_part, fractional_and_suffix = timestamp.split('.', 1)
1735
-
1736
- # Separate fractional digits from the suffix (typically ' UTC')
1737
- if ' ' in fractional_and_suffix:
1738
- fractional_part, suffix = fractional_and_suffix.split(' ', 1)
1739
- suffix = ' ' + suffix # Re-attach the space removed by split
1740
- else:
1741
- fractional_part, suffix = fractional_and_suffix, ''
1742
-
1743
- # Guarantee exactly two digits for the fractional part
1744
- fractional_part = (fractional_part + '00')[:2]
1745
-
1746
- return f"{main_part}.{fractional_part}{suffix}"
1747
-
1748
1783
  def _get_tracking_start_time(self) -> str:
1749
1784
  """Get the tracking start time, formatted as a string."""
1750
1785
  if self._tracking_start_time is None:
@@ -892,11 +892,58 @@ class FireSmokeUseCase(BaseProcessor):
892
892
  seconds = round(float(timestamp % 60), 2)
893
893
  return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
894
894
 
895
+ def _format_timestamp(self, timestamp: Any) -> str:
896
+ """Format a timestamp to match the current timestamp format: YYYY:MM:DD HH:MM:SS.
897
+
898
+ The input can be either:
899
+ 1. A numeric Unix timestamp (``float`` / ``int``) – it will be converted to datetime.
900
+ 2. A string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
901
+
902
+ The returned value will be in the format: YYYY:MM:DD HH:MM:SS (no milliseconds, no UTC suffix).
903
+
904
+ Example
905
+ -------
906
+ >>> self._format_timestamp("2025-10-27-19:31:20.187574 UTC")
907
+ '2025:10:27 19:31:20'
908
+ """
909
+
910
+ # Convert numeric timestamps to datetime first
911
+ if isinstance(timestamp, (int, float)):
912
+ dt = datetime.fromtimestamp(timestamp, timezone.utc)
913
+ return dt.strftime('%Y:%m:%d %H:%M:%S')
914
+
915
+ # Ensure we are working with a string from here on
916
+ if not isinstance(timestamp, str):
917
+ return str(timestamp)
918
+
919
+ # Remove ' UTC' suffix if present
920
+ timestamp_clean = timestamp.replace(' UTC', '').strip()
921
+
922
+ # Remove milliseconds if present (everything after the last dot)
923
+ if '.' in timestamp_clean:
924
+ timestamp_clean = timestamp_clean.split('.')[0]
925
+
926
+ # Parse the timestamp string and convert to desired format
927
+ try:
928
+ # Handle format: YYYY-MM-DD-HH:MM:SS
929
+ if timestamp_clean.count('-') >= 2:
930
+ # Replace first two dashes with colons for date part, third with space
931
+ parts = timestamp_clean.split('-')
932
+ if len(parts) >= 4:
933
+ # parts = ['2025', '10', '27', '19:31:20']
934
+ formatted = f"{parts[0]}:{parts[1]}:{parts[2]} {'-'.join(parts[3:])}"
935
+ return formatted
936
+ except Exception:
937
+ pass
938
+
939
+ # If parsing fails, return the cleaned string as-is
940
+ return timestamp_clean
941
+
895
942
  def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
896
943
  """Get formatted current timestamp based on stream type."""
944
+
897
945
  if not stream_info:
898
946
  return "00:00:00.00"
899
-
900
947
  if precision:
901
948
  if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
902
949
  if frame_id:
@@ -905,7 +952,6 @@ class FireSmokeUseCase(BaseProcessor):
905
952
  start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
906
953
  stream_time_str = self._format_timestamp_for_video(start_time)
907
954
 
908
-
909
955
  return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
910
956
  else:
911
957
  return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
@@ -917,7 +963,8 @@ class FireSmokeUseCase(BaseProcessor):
917
963
  start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
918
964
 
919
965
  stream_time_str = self._format_timestamp_for_video(start_time)
920
-
966
+
967
+
921
968
  return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
922
969
  else:
923
970
  stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
@@ -939,23 +986,57 @@ class FireSmokeUseCase(BaseProcessor):
939
986
 
940
987
  if precision:
941
988
  if self.start_timer is None:
942
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
989
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
990
+ if not candidate or candidate == "NA":
991
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
992
+ self.start_timer = candidate
943
993
  return self._format_timestamp(self.start_timer)
944
994
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
945
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
995
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
996
+ if not candidate or candidate == "NA":
997
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
998
+ self.start_timer = candidate
946
999
  return self._format_timestamp(self.start_timer)
947
1000
  else:
948
1001
  return self._format_timestamp(self.start_timer)
949
1002
 
950
1003
  if self.start_timer is None:
951
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1004
+ # Prefer direct input_settings.stream_time if available and not NA
1005
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1006
+ if not candidate or candidate == "NA":
1007
+ # Fallback to nested stream_info.stream_time used by current timestamp path
1008
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
1009
+ if stream_time_str:
1010
+ try:
1011
+ timestamp_str = stream_time_str.replace(" UTC", "")
1012
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
1013
+ self._tracking_start_time = dt.replace(tzinfo=timezone.utc).timestamp()
1014
+ candidate = datetime.fromtimestamp(self._tracking_start_time, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1015
+ except:
1016
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1017
+ else:
1018
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1019
+ self.start_timer = candidate
952
1020
  return self._format_timestamp(self.start_timer)
953
1021
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
954
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1022
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1023
+ if not candidate or candidate == "NA":
1024
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
1025
+ if stream_time_str:
1026
+ try:
1027
+ timestamp_str = stream_time_str.replace(" UTC", "")
1028
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
1029
+ ts = dt.replace(tzinfo=timezone.utc).timestamp()
1030
+ candidate = datetime.fromtimestamp(ts, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1031
+ except:
1032
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1033
+ else:
1034
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1035
+ self.start_timer = candidate
955
1036
  return self._format_timestamp(self.start_timer)
956
1037
 
957
1038
  else:
958
- if self.start_timer is not None:
1039
+ if self.start_timer is not None and self.start_timer != "NA":
959
1040
  return self._format_timestamp(self.start_timer)
960
1041
 
961
1042
  if self._tracking_start_time is None:
@@ -974,52 +1055,6 @@ class FireSmokeUseCase(BaseProcessor):
974
1055
  dt = dt.replace(minute=0, second=0, microsecond=0)
975
1056
  return dt.strftime('%Y:%m:%d %H:%M:%S')
976
1057
 
977
- def _format_timestamp(self, timestamp: Any) -> str:
978
- """Format a timestamp so that exactly two digits follow the decimal point (milliseconds).
979
-
980
- The input can be either:
981
- 1. A numeric Unix timestamp (``float`` / ``int``) – it will first be converted to a
982
- string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
983
- 2. A string already following the same layout.
984
-
985
- The returned value preserves the overall format of the input but truncates or pads
986
- the fractional seconds portion to **exactly two digits**.
987
-
988
- Example
989
- -------
990
- >>> self._format_timestamp("2025-08-19-04:22:47.187574 UTC")
991
- '2025-08-19-04:22:47.18 UTC'
992
- """
993
-
994
- # Convert numeric timestamps to the expected string representation first
995
- if isinstance(timestamp, (int, float)):
996
- timestamp = datetime.fromtimestamp(timestamp, timezone.utc).strftime(
997
- '%Y-%m-%d-%H:%M:%S.%f UTC'
998
- )
999
-
1000
- # Ensure we are working with a string from here on
1001
- if not isinstance(timestamp, str):
1002
- return str(timestamp)
1003
-
1004
- # If there is no fractional component, simply return the original string
1005
- if '.' not in timestamp:
1006
- return timestamp
1007
-
1008
- # Split out the main portion (up to the decimal point)
1009
- main_part, fractional_and_suffix = timestamp.split('.', 1)
1010
-
1011
- # Separate fractional digits from the suffix (typically ' UTC')
1012
- if ' ' in fractional_and_suffix:
1013
- fractional_part, suffix = fractional_and_suffix.split(' ', 1)
1014
- suffix = ' ' + suffix # Re-attach the space removed by split
1015
- else:
1016
- fractional_part, suffix = fractional_and_suffix, ''
1017
-
1018
- # Guarantee exactly two digits for the fractional part
1019
- fractional_part = (fractional_part + '00')[:2]
1020
-
1021
- return f"{main_part}.{fractional_part}{suffix}"
1022
-
1023
1058
  def get_duration_seconds(self, start_time, end_time):
1024
1059
  def parse_relative_time(t):
1025
1060
  """Parse HH:MM:SS(.f) manually into timedelta"""
@@ -1566,63 +1566,64 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1566
1566
  }
1567
1567
  return canonical_id
1568
1568
 
1569
+ def _format_timestamp_for_stream(self, timestamp: float) -> str:
1570
+ """Format timestamp for streams (YYYY:MM:DD HH:MM:SS format)."""
1571
+ dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
1572
+ return dt.strftime('%Y:%m:%d %H:%M:%S')
1573
+
1574
+ def _format_timestamp_for_video(self, timestamp: float) -> str:
1575
+ """Format timestamp for video chunks (HH:MM:SS.ms format)."""
1576
+ hours = int(timestamp // 3600)
1577
+ minutes = int((timestamp % 3600) // 60)
1578
+ seconds = round(float(timestamp % 60), 2)
1579
+ return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
1580
+
1569
1581
  def _format_timestamp(self, timestamp: Any) -> str:
1570
- """Format a timestamp so that exactly two digits follow the decimal point (milliseconds).
1582
+ """Format a timestamp to match the current timestamp format: YYYY:MM:DD HH:MM:SS.
1571
1583
 
1572
1584
  The input can be either:
1573
- 1. A numeric Unix timestamp (``float`` / ``int``) it will first be converted to a
1574
- string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
1575
- 2. A string already following the same layout.
1585
+ 1. A numeric Unix timestamp (``float`` / ``int``)it will be converted to datetime.
1586
+ 2. A string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
1576
1587
 
1577
- The returned value preserves the overall format of the input but truncates or pads
1578
- the fractional seconds portion to **exactly two digits**.
1588
+ The returned value will be in the format: YYYY:MM:DD HH:MM:SS (no milliseconds, no UTC suffix).
1579
1589
 
1580
1590
  Example
1581
1591
  -------
1582
- >>> self._format_timestamp("2025-08-19-04:22:47.187574 UTC")
1583
- '2025-08-19-04:22:47.18 UTC'
1592
+ >>> self._format_timestamp("2025-10-27-19:31:20.187574 UTC")
1593
+ '2025:10:27 19:31:20'
1584
1594
  """
1585
1595
 
1586
- # Convert numeric timestamps to the expected string representation first
1596
+ # Convert numeric timestamps to datetime first
1587
1597
  if isinstance(timestamp, (int, float)):
1588
- timestamp = datetime.fromtimestamp(timestamp, timezone.utc).strftime(
1589
- '%Y-%m-%d-%H:%M:%S.%f UTC'
1590
- )
1598
+ dt = datetime.fromtimestamp(timestamp, timezone.utc)
1599
+ return dt.strftime('%Y:%m:%d %H:%M:%S')
1591
1600
 
1592
1601
  # Ensure we are working with a string from here on
1593
1602
  if not isinstance(timestamp, str):
1594
1603
  return str(timestamp)
1595
1604
 
1596
- # If there is no fractional component, simply return the original string
1597
- if '.' not in timestamp:
1598
- return timestamp
1599
-
1600
- # Split out the main portion (up to the decimal point)
1601
- main_part, fractional_and_suffix = timestamp.split('.', 1)
1602
-
1603
- # Separate fractional digits from the suffix (typically ' UTC')
1604
- if ' ' in fractional_and_suffix:
1605
- fractional_part, suffix = fractional_and_suffix.split(' ', 1)
1606
- suffix = ' ' + suffix # Re-attach the space removed by split
1607
- else:
1608
- fractional_part, suffix = fractional_and_suffix, ''
1609
-
1610
- # Guarantee exactly two digits for the fractional part
1611
- fractional_part = (fractional_part + '00')[:2]
1605
+ # Remove ' UTC' suffix if present
1606
+ timestamp_clean = timestamp.replace(' UTC', '').strip()
1612
1607
 
1613
- return f"{main_part}.{fractional_part}{suffix}"
1608
+ # Remove milliseconds if present (everything after the last dot)
1609
+ if '.' in timestamp_clean:
1610
+ timestamp_clean = timestamp_clean.split('.')[0]
1614
1611
 
1615
- def _format_timestamp_for_stream(self, timestamp: float) -> str:
1616
- """Format timestamp for streams (YYYY:MM:DD HH:MM:SS format)."""
1617
- dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
1618
- return dt.strftime('%Y:%m:%d %H:%M:%S')
1619
-
1620
- def _format_timestamp_for_video(self, timestamp: float) -> str:
1621
- """Format timestamp for video chunks (HH:MM:SS.ms format)."""
1622
- hours = int(timestamp // 3600)
1623
- minutes = int((timestamp % 3600) // 60)
1624
- seconds = round(float(timestamp % 60), 2)
1625
- return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
1612
+ # Parse the timestamp string and convert to desired format
1613
+ try:
1614
+ # Handle format: YYYY-MM-DD-HH:MM:SS
1615
+ if timestamp_clean.count('-') >= 2:
1616
+ # Replace first two dashes with colons for date part, third with space
1617
+ parts = timestamp_clean.split('-')
1618
+ if len(parts) >= 4:
1619
+ # parts = ['2025', '10', '27', '19:31:20']
1620
+ formatted = f"{parts[0]}:{parts[1]}:{parts[2]} {'-'.join(parts[3:])}"
1621
+ return formatted
1622
+ except Exception:
1623
+ pass
1624
+
1625
+ # If parsing fails, return the cleaned string as-is
1626
+ return timestamp_clean
1626
1627
 
1627
1628
  def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
1628
1629
  """Get formatted current timestamp based on stream type."""
@@ -1671,23 +1672,57 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1671
1672
 
1672
1673
  if precision:
1673
1674
  if self.start_timer is None:
1674
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1675
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1676
+ if not candidate or candidate == "NA":
1677
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1678
+ self.start_timer = candidate
1675
1679
  return self._format_timestamp(self.start_timer)
1676
1680
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
1677
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1681
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1682
+ if not candidate or candidate == "NA":
1683
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1684
+ self.start_timer = candidate
1678
1685
  return self._format_timestamp(self.start_timer)
1679
1686
  else:
1680
1687
  return self._format_timestamp(self.start_timer)
1681
1688
 
1682
1689
  if self.start_timer is None:
1683
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1690
+ # Prefer direct input_settings.stream_time if available and not NA
1691
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1692
+ if not candidate or candidate == "NA":
1693
+ # Fallback to nested stream_info.stream_time used by current timestamp path
1694
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
1695
+ if stream_time_str:
1696
+ try:
1697
+ timestamp_str = stream_time_str.replace(" UTC", "")
1698
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
1699
+ self._tracking_start_time = dt.replace(tzinfo=timezone.utc).timestamp()
1700
+ candidate = datetime.fromtimestamp(self._tracking_start_time, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1701
+ except:
1702
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1703
+ else:
1704
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1705
+ self.start_timer = candidate
1684
1706
  return self._format_timestamp(self.start_timer)
1685
1707
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
1686
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
1708
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
1709
+ if not candidate or candidate == "NA":
1710
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
1711
+ if stream_time_str:
1712
+ try:
1713
+ timestamp_str = stream_time_str.replace(" UTC", "")
1714
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
1715
+ ts = dt.replace(tzinfo=timezone.utc).timestamp()
1716
+ candidate = datetime.fromtimestamp(ts, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1717
+ except:
1718
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1719
+ else:
1720
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
1721
+ self.start_timer = candidate
1687
1722
  return self._format_timestamp(self.start_timer)
1688
1723
 
1689
1724
  else:
1690
- if self.start_timer is not None:
1725
+ if self.start_timer is not None and self.start_timer != "NA":
1691
1726
  return self._format_timestamp(self.start_timer)
1692
1727
 
1693
1728
  if self._tracking_start_time is None:
@@ -1705,7 +1740,7 @@ class LicensePlateMonitorUseCase(BaseProcessor):
1705
1740
  dt = datetime.fromtimestamp(self._tracking_start_time, tz=timezone.utc)
1706
1741
  dt = dt.replace(minute=0, second=0, microsecond=0)
1707
1742
  return dt.strftime('%Y:%m:%d %H:%M:%S')
1708
-
1743
+
1709
1744
  def _get_tracking_start_time(self) -> str:
1710
1745
  """Get the tracking start time, formatted as a string."""
1711
1746
  if self._tracking_start_time is None:
@@ -437,50 +437,51 @@ class PeopleCountingUseCase(BaseProcessor):
437
437
  return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
438
438
 
439
439
  def _format_timestamp(self, timestamp: Any) -> str:
440
- """Format a timestamp so that exactly two digits follow the decimal point (milliseconds).
440
+ """Format a timestamp to match the current timestamp format: YYYY:MM:DD HH:MM:SS.
441
441
 
442
442
  The input can be either:
443
- 1. A numeric Unix timestamp (``float`` / ``int``) – it will first be converted to a
444
- string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
445
- 2. A string already following the same layout.
443
+ 1. A numeric Unix timestamp (``float`` / ``int``) – it will be converted to datetime.
444
+ 2. A string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
446
445
 
447
- The returned value preserves the overall format of the input but truncates or pads
448
- the fractional seconds portion to **exactly two digits**.
446
+ The returned value will be in the format: YYYY:MM:DD HH:MM:SS (no milliseconds, no UTC suffix).
449
447
 
450
448
  Example
451
449
  -------
452
- >>> self._format_timestamp("2025-08-19-04:22:47.187574 UTC")
453
- '2025-08-19-04:22:47.18 UTC'
450
+ >>> self._format_timestamp("2025-10-27-19:31:20.187574 UTC")
451
+ '2025:10:27 19:31:20'
454
452
  """
455
453
 
456
- # Convert numeric timestamps to the expected string representation first
454
+ # Convert numeric timestamps to datetime first
457
455
  if isinstance(timestamp, (int, float)):
458
- timestamp = datetime.fromtimestamp(timestamp, timezone.utc).strftime(
459
- '%Y-%m-%d-%H:%M:%S.%f UTC'
460
- )
456
+ dt = datetime.fromtimestamp(timestamp, timezone.utc)
457
+ return dt.strftime('%Y:%m:%d %H:%M:%S')
461
458
 
462
459
  # Ensure we are working with a string from here on
463
460
  if not isinstance(timestamp, str):
464
461
  return str(timestamp)
465
462
 
466
- # If there is no fractional component, simply return the original string
467
- if '.' not in timestamp:
468
- return timestamp
469
-
470
- # Split out the main portion (up to the decimal point)
471
- main_part, fractional_and_suffix = timestamp.split('.', 1)
463
+ # Remove ' UTC' suffix if present
464
+ timestamp_clean = timestamp.replace(' UTC', '').strip()
472
465
 
473
- # Separate fractional digits from the suffix (typically ' UTC')
474
- if ' ' in fractional_and_suffix:
475
- fractional_part, suffix = fractional_and_suffix.split(' ', 1)
476
- suffix = ' ' + suffix # Re-attach the space removed by split
477
- else:
478
- fractional_part, suffix = fractional_and_suffix, ''
479
-
480
- # Guarantee exactly two digits for the fractional part
481
- fractional_part = (fractional_part + '00')[:2]
466
+ # Remove milliseconds if present (everything after the last dot)
467
+ if '.' in timestamp_clean:
468
+ timestamp_clean = timestamp_clean.split('.')[0]
482
469
 
483
- return f"{main_part}.{fractional_part}{suffix}"
470
+ # Parse the timestamp string and convert to desired format
471
+ try:
472
+ # Handle format: YYYY-MM-DD-HH:MM:SS
473
+ if timestamp_clean.count('-') >= 2:
474
+ # Replace first two dashes with colons for date part, third with space
475
+ parts = timestamp_clean.split('-')
476
+ if len(parts) >= 4:
477
+ # parts = ['2025', '10', '27', '19:31:20']
478
+ formatted = f"{parts[0]}:{parts[1]}:{parts[2]} {'-'.join(parts[3:])}"
479
+ return formatted
480
+ except Exception:
481
+ pass
482
+
483
+ # If parsing fails, return the cleaned string as-is
484
+ return timestamp_clean
484
485
 
485
486
  def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
486
487
  """Get formatted current timestamp based on stream type."""
@@ -759,11 +759,58 @@ class VehicleMonitoringUseCase(BaseProcessor):
759
759
  seconds = round(float(timestamp % 60), 2)
760
760
  return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
761
761
 
762
+ def _format_timestamp(self, timestamp: Any) -> str:
763
+ """Format a timestamp to match the current timestamp format: YYYY:MM:DD HH:MM:SS.
764
+
765
+ The input can be either:
766
+ 1. A numeric Unix timestamp (``float`` / ``int``) – it will be converted to datetime.
767
+ 2. A string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
768
+
769
+ The returned value will be in the format: YYYY:MM:DD HH:MM:SS (no milliseconds, no UTC suffix).
770
+
771
+ Example
772
+ -------
773
+ >>> self._format_timestamp("2025-10-27-19:31:20.187574 UTC")
774
+ '2025:10:27 19:31:20'
775
+ """
776
+
777
+ # Convert numeric timestamps to datetime first
778
+ if isinstance(timestamp, (int, float)):
779
+ dt = datetime.fromtimestamp(timestamp, timezone.utc)
780
+ return dt.strftime('%Y:%m:%d %H:%M:%S')
781
+
782
+ # Ensure we are working with a string from here on
783
+ if not isinstance(timestamp, str):
784
+ return str(timestamp)
785
+
786
+ # Remove ' UTC' suffix if present
787
+ timestamp_clean = timestamp.replace(' UTC', '').strip()
788
+
789
+ # Remove milliseconds if present (everything after the last dot)
790
+ if '.' in timestamp_clean:
791
+ timestamp_clean = timestamp_clean.split('.')[0]
792
+
793
+ # Parse the timestamp string and convert to desired format
794
+ try:
795
+ # Handle format: YYYY-MM-DD-HH:MM:SS
796
+ if timestamp_clean.count('-') >= 2:
797
+ # Replace first two dashes with colons for date part, third with space
798
+ parts = timestamp_clean.split('-')
799
+ if len(parts) >= 4:
800
+ # parts = ['2025', '10', '27', '19:31:20']
801
+ formatted = f"{parts[0]}:{parts[1]}:{parts[2]} {'-'.join(parts[3:])}"
802
+ return formatted
803
+ except Exception:
804
+ pass
805
+
806
+ # If parsing fails, return the cleaned string as-is
807
+ return timestamp_clean
808
+
762
809
  def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str]=None) -> str:
763
810
  """Get formatted current timestamp based on stream type."""
811
+
764
812
  if not stream_info:
765
813
  return "00:00:00.00"
766
-
767
814
  if precision:
768
815
  if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
769
816
  if frame_id:
@@ -772,7 +819,6 @@ class VehicleMonitoringUseCase(BaseProcessor):
772
819
  start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
773
820
  stream_time_str = self._format_timestamp_for_video(start_time)
774
821
 
775
-
776
822
  return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
777
823
  else:
778
824
  return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
@@ -784,7 +830,8 @@ class VehicleMonitoringUseCase(BaseProcessor):
784
830
  start_time = stream_info.get("input_settings", {}).get("start_frame", 30)/stream_info.get("input_settings", {}).get("original_fps", 30)
785
831
 
786
832
  stream_time_str = self._format_timestamp_for_video(start_time)
787
-
833
+
834
+
788
835
  return self._format_timestamp(stream_info.get("input_settings", {}).get("stream_time", "NA"))
789
836
  else:
790
837
  stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
@@ -806,23 +853,57 @@ class VehicleMonitoringUseCase(BaseProcessor):
806
853
 
807
854
  if precision:
808
855
  if self.start_timer is None:
809
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
856
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
857
+ if not candidate or candidate == "NA":
858
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
859
+ self.start_timer = candidate
810
860
  return self._format_timestamp(self.start_timer)
811
861
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
812
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
862
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
863
+ if not candidate or candidate == "NA":
864
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
865
+ self.start_timer = candidate
813
866
  return self._format_timestamp(self.start_timer)
814
867
  else:
815
868
  return self._format_timestamp(self.start_timer)
816
869
 
817
870
  if self.start_timer is None:
818
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
871
+ # Prefer direct input_settings.stream_time if available and not NA
872
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
873
+ if not candidate or candidate == "NA":
874
+ # Fallback to nested stream_info.stream_time used by current timestamp path
875
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
876
+ if stream_time_str:
877
+ try:
878
+ timestamp_str = stream_time_str.replace(" UTC", "")
879
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
880
+ self._tracking_start_time = dt.replace(tzinfo=timezone.utc).timestamp()
881
+ candidate = datetime.fromtimestamp(self._tracking_start_time, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
882
+ except:
883
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
884
+ else:
885
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
886
+ self.start_timer = candidate
819
887
  return self._format_timestamp(self.start_timer)
820
888
  elif stream_info.get("input_settings", {}).get("start_frame", "na") == 1:
821
- self.start_timer = stream_info.get("input_settings", {}).get("stream_time", "NA")
889
+ candidate = stream_info.get("input_settings", {}).get("stream_time")
890
+ if not candidate or candidate == "NA":
891
+ stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
892
+ if stream_time_str:
893
+ try:
894
+ timestamp_str = stream_time_str.replace(" UTC", "")
895
+ dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
896
+ ts = dt.replace(tzinfo=timezone.utc).timestamp()
897
+ candidate = datetime.fromtimestamp(ts, timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
898
+ except:
899
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
900
+ else:
901
+ candidate = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
902
+ self.start_timer = candidate
822
903
  return self._format_timestamp(self.start_timer)
823
904
 
824
905
  else:
825
- if self.start_timer is not None:
906
+ if self.start_timer is not None and self.start_timer != "NA":
826
907
  return self._format_timestamp(self.start_timer)
827
908
 
828
909
  if self._tracking_start_time is None:
@@ -841,52 +922,6 @@ class VehicleMonitoringUseCase(BaseProcessor):
841
922
  dt = dt.replace(minute=0, second=0, microsecond=0)
842
923
  return dt.strftime('%Y:%m:%d %H:%M:%S')
843
924
 
844
- def _format_timestamp(self, timestamp: Any) -> str:
845
- """Format a timestamp so that exactly two digits follow the decimal point (milliseconds).
846
-
847
- The input can be either:
848
- 1. A numeric Unix timestamp (``float`` / ``int``) – it will first be converted to a
849
- string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
850
- 2. A string already following the same layout.
851
-
852
- The returned value preserves the overall format of the input but truncates or pads
853
- the fractional seconds portion to **exactly two digits**.
854
-
855
- Example
856
- -------
857
- >>> self._format_timestamp("2025-08-19-04:22:47.187574 UTC")
858
- '2025-08-19-04:22:47.18 UTC'
859
- """
860
-
861
- # Convert numeric timestamps to the expected string representation first
862
- if isinstance(timestamp, (int, float)):
863
- timestamp = datetime.fromtimestamp(timestamp, timezone.utc).strftime(
864
- '%Y-%m-%d-%H:%M:%S.%f UTC'
865
- )
866
-
867
- # Ensure we are working with a string from here on
868
- if not isinstance(timestamp, str):
869
- return str(timestamp)
870
-
871
- # If there is no fractional component, simply return the original string
872
- if '.' not in timestamp:
873
- return timestamp
874
-
875
- # Split out the main portion (up to the decimal point)
876
- main_part, fractional_and_suffix = timestamp.split('.', 1)
877
-
878
- # Separate fractional digits from the suffix (typically ' UTC')
879
- if ' ' in fractional_and_suffix:
880
- fractional_part, suffix = fractional_and_suffix.split(' ', 1)
881
- suffix = ' ' + suffix # Re-attach the space removed by split
882
- else:
883
- fractional_part, suffix = fractional_and_suffix, ''
884
-
885
- # Guarantee exactly two digits for the fractional part
886
- fractional_part = (fractional_part + '00')[:2]
887
-
888
- return f"{main_part}.{fractional_part}{suffix}"
889
-
890
925
  def _count_categories(self, detections: list, config: VehicleMonitoringConfig) -> dict:
891
926
  counts = {}
892
927
  for det in detections:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrice_analytics
3
- Version: 0.1.43
3
+ Version: 0.1.44
4
4
  Summary: Common server utilities for Matrice.ai services
5
5
  Author-email: "Matrice.ai" <dipendra@matrice.ai>
6
6
  License-Expression: MIT
@@ -29,7 +29,7 @@ matrice_analytics/post_processing/core/config_utils.py,sha256=QuAS-_JKSoNOtfUWgr
29
29
  matrice_analytics/post_processing/face_reg/__init__.py,sha256=yntaiGlW9vdjBpPZQXNuovALihJPzRlFyUE88l3MhBA,1364
30
30
  matrice_analytics/post_processing/face_reg/compare_similarity.py,sha256=NlFc8b2a74k0PqSFAbuM_fUbA1BT3pr3VUgvSqRpJzQ,23396
31
31
  matrice_analytics/post_processing/face_reg/embedding_manager.py,sha256=qbh0df3-YbE0qvFDQvjpCg-JrsCZRJ5capjQ2LPOj1k,35619
32
- matrice_analytics/post_processing/face_reg/face_recognition.py,sha256=17RVs_5BtGw2ruksL2JLbpXljbs4-96tP-yKGX3hrOI,90232
32
+ matrice_analytics/post_processing/face_reg/face_recognition.py,sha256=eXzd2hc3qYtYLOxc32l5KAvqS1pIZQTKUhG4wPRMwOc,92552
33
33
  matrice_analytics/post_processing/face_reg/face_recognition_client.py,sha256=YaC73mN0yrHZYkjJ7nZIZen5YCVOwEX2gn6I1XpHTW0,27575
34
34
  matrice_analytics/post_processing/face_reg/people_activity_logging.py,sha256=NhJXy9jCy_mlZiDXv8IeGyrRN_TL0kKCe3ZOlaNbZUw,13676
35
35
  matrice_analytics/post_processing/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -100,7 +100,7 @@ matrice_analytics/post_processing/usecases/cardiomegaly_classification.py,sha256
100
100
  matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py,sha256=eQ_s5u3Vnvja6-FmI6ZPxlNkaZtG-pVjTu8NuLjZJ5M,43714
101
101
  matrice_analytics/post_processing/usecases/chicken_pose_detection.py,sha256=-e8di7Am-E-FCQFrSY8qJTO1aWtdRAVJoE-VKBgcyyI,29291
102
102
  matrice_analytics/post_processing/usecases/child_monitoring.py,sha256=z3oymoqq4hDGwA8MkdEONZW_Vx5CAZmvzZaNLsqmCfw,39380
103
- matrice_analytics/post_processing/usecases/color_detection.py,sha256=gucKGrIexZ8fgARzehrXXPBHxNVw1u5-OqF2HysH-eo,90293
103
+ matrice_analytics/post_processing/usecases/color_detection.py,sha256=56oLhecGN8jaPcRLNpZ-AJYjhYqvcRq_BX7rjkPHW0s,92361
104
104
  matrice_analytics/post_processing/usecases/color_map_utils.py,sha256=SP-AEVcjLmL8rxblu-ixqUJC2fqlcr7ab4hWo4Fcr_k,2677
105
105
  matrice_analytics/post_processing/usecases/concrete_crack_detection.py,sha256=pxhOH_hG4hq9yytNepbGMdk2W_lTG8D1_2RAagaPBkg,40252
106
106
  matrice_analytics/post_processing/usecases/crop_weed_detection.py,sha256=Ao1k5fJDYU_f6yZ8VO-jW8-esECV0-zY5Q570c_fako,35674
@@ -115,7 +115,7 @@ matrice_analytics/post_processing/usecases/face_emotion.py,sha256=eRfqBdryB0uNoO
115
115
  matrice_analytics/post_processing/usecases/face_recognition.py,sha256=T5xAuv6b9OrkmTmoXgZs4LZ5XUsbvp9xCpeLBwdu7eI,40231
116
116
  matrice_analytics/post_processing/usecases/fashion_detection.py,sha256=f9gpzMDhIW-gyn46k9jgf8nY7YeoqAnTxGOzksabFbE,40457
117
117
  matrice_analytics/post_processing/usecases/field_mapping.py,sha256=JDwYX8pd2W-waDvBh98Y_o_uchJu7wEYbFxOliA4Iq4,39822
118
- matrice_analytics/post_processing/usecases/fire_detection.py,sha256=6ZwIWSweFZoeRk1CoOtOdlEV-CtYj3gWnhSQw6ONZxg,52294
118
+ matrice_analytics/post_processing/usecases/fire_detection.py,sha256=3ZZZrIXtZ_Ui3d3nQxvj12pqFTgguRN4OiE0cQYNXhg,54614
119
119
  matrice_analytics/post_processing/usecases/flare_analysis.py,sha256=3nf4fUeUwlP_UII0h5fQkUGPXbr32ZnJjaM-dukNSP8,42680
120
120
  matrice_analytics/post_processing/usecases/flower_segmentation.py,sha256=4I7qMx9Ztxg_hy9KTVX-3qBhAN-QwDt_Yigf9fFjLus,52017
121
121
  matrice_analytics/post_processing/usecases/gas_leak_detection.py,sha256=KL2ft7fXvjTas-65-QgcJm3W8KBsrwF44qibSXjfaLc,40557
@@ -126,7 +126,7 @@ matrice_analytics/post_processing/usecases/leaf.py,sha256=cwgB1ZNxkQFtkk-thSJrkX
126
126
  matrice_analytics/post_processing/usecases/leaf_disease.py,sha256=bkiLccTdf4KUq3he4eCpBlKXb5exr-WBhQ_oWQ7os68,36225
127
127
  matrice_analytics/post_processing/usecases/leak_detection.py,sha256=oOCLLVMuXVeXPHyN8FUrD3U9JYJJwIz-5fcEMgvLdls,40531
128
128
  matrice_analytics/post_processing/usecases/license_plate_detection.py,sha256=dsavd92-wnyXCNrCzaRj24zH7BVvLSa09HkYsrOXYDM,50806
129
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py,sha256=Mu8Hu1EqTwYQ1lqDyz3mioDiTDJddIT2F2HbLWeZSfc,89059
129
+ matrice_analytics/post_processing/usecases/license_plate_monitoring.py,sha256=hjc9tQkUzVwvWuNohw28P2SJNHb25P5ERZbNlRkSKOk,91379
130
130
  matrice_analytics/post_processing/usecases/litter_monitoring.py,sha256=XaHAUGRBDJg_iVbu8hRMjTR-5TqrLj6ZNCRkInbzZTY,33255
131
131
  matrice_analytics/post_processing/usecases/mask_detection.py,sha256=L_s6ZiT5zeXG-BsFcskb3HEG98DhLgqeMSDmCuwOteU,41501
132
132
  matrice_analytics/post_processing/usecases/natural_disaster.py,sha256=ehxdPBoYcZWGVDOVn_mHFoz4lIE8LrveAkuXQj0n9XE,44253
@@ -134,7 +134,7 @@ matrice_analytics/post_processing/usecases/parking.py,sha256=lqTGqcjUZZPFw3tu11H
134
134
  matrice_analytics/post_processing/usecases/parking_space_detection.py,sha256=xwhkJjGGKcT827URbasi3olYqhd95Sh0zsEIphwzcgY,39561
135
135
  matrice_analytics/post_processing/usecases/pcb_defect_detection.py,sha256=xH3q-WoR3TwMUeUvWw1W7vPLdCUfu_Kl_gQ9dZFf1SE,43006
136
136
  matrice_analytics/post_processing/usecases/pedestrian_detection.py,sha256=hPFtvpWXXEsbDavmuiXIhrosMNlOhGya--jukT-ZOHA,39288
137
- matrice_analytics/post_processing/usecases/people_counting.py,sha256=IeNu33nWhzRlbOPf8DPbzQp1QRdPSAIr4nO3A1wUr_k,35272
137
+ matrice_analytics/post_processing/usecases/people_counting.py,sha256=mlzezqESW2qifW2PmsygWMjHLw6v8I34p9L2RwbxZMo,35267
138
138
  matrice_analytics/post_processing/usecases/people_counting_bckp.py,sha256=WM9te7oYyhu5f_bIMye_D5BpEn6CwA-6Kz95IMLmSbs,82209
139
139
  matrice_analytics/post_processing/usecases/people_tracking.py,sha256=iXzGJgqKgWxvIVLqa1cFKkiF0DrHolwghSiJ2P8mDhc,90484
140
140
  matrice_analytics/post_processing/usecases/pipeline_detection.py,sha256=VsLTXMAqx0tRw7Olrxqx7SBLolZR7p2aFOrdSXLS-kE,30796
@@ -158,7 +158,7 @@ matrice_analytics/post_processing/usecases/theft_detection.py,sha256=Rs_zKn2z9YM
158
158
  matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py,sha256=nDlEzHgMlUjy_VtJ7usnEzMcdSs-jouqaoJpJ8DYUMw,34351
159
159
  matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py,sha256=W_2joZStsP0jl2zn89-jtdtqqGv3vJ0amsalbE5WKwo,37647
160
160
  matrice_analytics/post_processing/usecases/underwater_pollution_detection.py,sha256=jqP1ZKfDZe2-56Lyvgb2DxnbqRfvxm6pPL0Ck3esfBk,40356
161
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py,sha256=iH0kqx8VwWldVLnWbDIMyjKBgOMLakbqMMHypXsxGdo,50529
161
+ matrice_analytics/post_processing/usecases/vehicle_monitoring.py,sha256=QsO-coozfy29rY6NszwA6A7nFBOGysfMz5S5VVY7Beg,52849
162
162
  matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py,sha256=5uZXTJL_A3tUEN08T-_ZQpUoJ9gqbuuMc4z2mT4sMnQ,43753
163
163
  matrice_analytics/post_processing/usecases/waterbody_segmentation.py,sha256=JsCxDEMB8s4WDcezfJDr2zrjM-TCjB9hxOztzSvWmpY,45268
164
164
  matrice_analytics/post_processing/usecases/weapon_detection.py,sha256=AhbVpJaa2I3aRCEAdIxovY5xd9370dUY4JllCQ8tdT4,37185
@@ -188,8 +188,8 @@ matrice_analytics/post_processing/utils/format_utils.py,sha256=UTF7A5h9j0_S12xH9
188
188
  matrice_analytics/post_processing/utils/geometry_utils.py,sha256=BWfdM6RsdJTTLR1GqkWfdwpjMEjTCJyuBxA4zVGKdfk,9623
189
189
  matrice_analytics/post_processing/utils/smoothing_utils.py,sha256=78U-yucAcjUiZ0NIAc9NOUSIT0PWP1cqyIPA_Fdrjp0,14699
190
190
  matrice_analytics/post_processing/utils/tracking_utils.py,sha256=rWxuotnJ3VLMHIBOud2KLcu4yZfDp7hVPWUtNAq_2xw,8288
191
- matrice_analytics-0.1.43.dist-info/licenses/LICENSE.txt,sha256=_uQUZpgO0mRYL5-fPoEvLSbNnLPv6OmbeEDCHXhK6Qc,1066
192
- matrice_analytics-0.1.43.dist-info/METADATA,sha256=XEb8jt0ezecP_Phg22fCGsMBFToMhI2iSHb5FAahQhE,14378
193
- matrice_analytics-0.1.43.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
194
- matrice_analytics-0.1.43.dist-info/top_level.txt,sha256=STAPEU-e-rWTerXaspdi76T_eVRSrEfFpURSP7_Dt8E,18
195
- matrice_analytics-0.1.43.dist-info/RECORD,,
191
+ matrice_analytics-0.1.44.dist-info/licenses/LICENSE.txt,sha256=_uQUZpgO0mRYL5-fPoEvLSbNnLPv6OmbeEDCHXhK6Qc,1066
192
+ matrice_analytics-0.1.44.dist-info/METADATA,sha256=fdA7eR44gue1uKeUrOE0fDpgx2bIsqXH2-h4uOuNiDY,14378
193
+ matrice_analytics-0.1.44.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
194
+ matrice_analytics-0.1.44.dist-info/top_level.txt,sha256=STAPEU-e-rWTerXaspdi76T_eVRSrEfFpURSP7_Dt8E,18
195
+ matrice_analytics-0.1.44.dist-info/RECORD,,