Fix motion activity endpoint returning invalid timestamps after pandas 3.0 upgrade

pandas 3.0 changed DatetimeIndex internal storage from datetime64[ns]
(nanoseconds) to datetime64[us] (microseconds). The motion activity
endpoint in review.py converted DatetimeIndex to epoch seconds using:

    df.index = df.index.astype(int) // (10**9)

This assumed nanosecond resolution, dividing by 10^9 to get seconds.
With microsecond resolution the division produces values ~1000x too
small (e.g. 1774785 instead of 1774785600), causing every entry to
have a start_time near zero. The frontend timeline could not match
these timestamps to the visible range, so motion indicator bars
disappeared entirely — despite the underlying recording data being
correct.

Replace the resolution-dependent integer division with pandas
Timedelta arithmetic:

    df.index = (df.index - _EPOCH) // _ONE_SECOND

This is resolution-independent (produces correct results on
datetime64[s], [ms], [us], and [ns]), ~148x faster than the
per-element .timestamp() alternative, produces native Python int
types that serialize cleanly to JSON, and is backwards-compatible
with older pandas versions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eric W 2026-03-29 08:43:42 -04:00
parent 148e11afc5
commit 317d1acfe1

View File

@ -40,6 +40,11 @@ from frigate.util.time import get_dst_transitions
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Pre-computed constants for resolution-independent datetime-to-epoch conversion
# (pandas 3.0+ stores datetime64 as microseconds, not nanoseconds)
_EPOCH = pd.Timestamp("1970-01-01")
_ONE_SECOND = pd.Timedelta("1s")
router = APIRouter(tags=[Tags.review]) router = APIRouter(tags=[Tags.review])
@ -659,7 +664,7 @@ def motion_activity(
df.iloc[i : i + chunk, 0] = 0.0 df.iloc[i : i + chunk, 0] = 0.0
# change types for output # change types for output
df.index = df.index.astype(int) // (10**9) df.index = (df.index - _EPOCH) // _ONE_SECOND
normalized = df.reset_index().to_dict("records") normalized = df.reset_index().to_dict("records")
return JSONResponse(content=normalized) return JSONResponse(content=normalized)