STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
backend.video Namespace Reference

Classes

class  Clip
class  Timing

Functions

 _boxes (f, int start, int end)
tuple[int, int]|None _full_box_times (f, int at)
 _child (f, int a, int b, bytes kind)
float|None mp4_frame_seconds (Path path)
float|None mp4_duration (Path path)
Timing|None read_timing (Path path)
tuple[float, float|None]|None _header_cached (Path path, os.stat_result st)
 _timing_cached (Path path)
float|None folder_time (str name, str tz)
bool available ()
list[Clip] list_clips ()
list[Clip] clips_overlapping (float t0, float t1)
Path|None clip_file (str recording, str stream)

Variables

 RECORDING_RE
 STREAM_RE = re.compile(r"^[A-Za-z0-9_-]{1,64}$")
int TIMING_VERSION = 1
dict _dur_cache = {}
dict _timing_cache = {}
 _lock = threading.Lock()

Detailed Description

Video recordings from the gateway, placed on a run's timeline.

The video agent (daq-server/video/agent.mjs) keeps each Save and Record as a folder
`<YYYY-MM-DD_HHMMSS>_<label>/` holding one video-only `<stream>.mp4` per stream. That
folder is mounted read-only here (`config.VIDEO_DIR`); this module lists the clips in it,
says which overlap a run, and resolves a clip to its file. It never writes.

Timing. Every clip the agent has kept since timing files existed has one beside it,
`<stream>.json` (agent.mjs, timingFor): pieces of `{fileS, wallMs}`, each saying the file
at `fileS` seconds was filmed at `wallMs` and runs on at real speed to the next piece. The
pieces carry the keyframe cut (the first frame is not the press) and any outage (the file
plays straight through a gap; the wall clock does not). Such a clip is exact to the
gateway's clock, and the file also carries `clockOffsetMs`, the gateway's clock minus the
DAQ's as the DAQ backend measured it at the press (`clockUncertaintyMs` either way). The
sensors are stamped on the DAQ's clock, so the offset is taken off every piece. A file
without one (an older agent or backend) is taken to share the DAQ's clock.

A clip with no timing file is placed by its folder name, which the agent writes in the
gateway machine's local time to the second (`config.VIDEO_TZ`):

- `_record`: the name is when Record was pressed, so that is the clip's start;
- anything else (`_save`): the name is when Save was pressed, and the clip is the buffer
  *before* it, so the clip ends there and starts one duration earlier.

That is only approximate: the second is truncated, and the agent cuts the file at a
keyframe, which can move the first frame by a keyframe interval. Every such clip is
marked `approx`. A clip's duration is read from its own mp4 header (the agent writes
with +faststart, so the header is near the front), never by decoding.

Cost: one directory listing plus a `stat` per clip on each call; the mp4 header is read
once per file and cached on (mtime, size). A folder still being recorded holds no mp4
yet, and a file ffmpeg has not finished has no readable duration, so neither is listed.

Function Documentation

◆ _boxes()

backend.video._boxes ( f,
int start,
int end )
protected
(type, payload offset, payload end) for each box in f[start:end].

Definition at line 101 of file video.py.

References min.

Referenced by _child(), mp4_duration(), and mp4_frame_seconds().

◆ _child()

backend.video._child ( f,
int a,
int b,
bytes kind )
protected

Definition at line 145 of file video.py.

References _boxes().

Referenced by mp4_duration(), and mp4_frame_seconds().

◆ _full_box_times()

tuple[int, int] | None backend.video._full_box_times ( f,
int at )
protected
(timescale, duration) from an mvhd/mdhd payload at `at` (both share the layout).

Definition at line 125 of file video.py.

Referenced by mp4_duration(), and mp4_frame_seconds().

◆ _header_cached()

tuple[float, float | None] | None backend.video._header_cached ( Path path,
os.stat_result st )
protected
(duration, seconds per frame) from the file's header, read once per version.

Definition at line 260 of file video.py.

References mp4_duration(), and mp4_frame_seconds().

Referenced by list_clips().

◆ _timing_cached()

backend.video._timing_cached ( Path path)
protected

Definition at line 276 of file video.py.

References read_timing().

Referenced by list_clips().

◆ available()

bool backend.video.available ( )

Definition at line 304 of file video.py.

Referenced by clip_file(), and list_clips().

◆ clip_file()

Path | None backend.video.clip_file ( str recording,
str stream )
The file for a clip this module would list, else None. Nothing from the request
reaches the filesystem until both names have passed the listing's own patterns.

Definition at line 365 of file video.py.

References available(), and list_clips().

◆ clips_overlapping()

list[Clip] backend.video.clips_overlapping ( float t0,
float t1 )
Clips any part of which falls inside [t0, t1].

Definition at line 360 of file video.py.

References list_clips().

◆ folder_time()

float | None backend.video.folder_time ( str name,
str tz )
Epoch seconds of a recording folder's name, read in the gateway's time zone.

Definition at line 292 of file video.py.

Referenced by list_clips().

◆ list_clips()

list[Clip] backend.video.list_clips ( )
Every finished clip in the recordings folder.

Definition at line 308 of file video.py.

References _header_cached(), _timing_cached(), available(), and folder_time().

Referenced by clip_file(), and clips_overlapping().

◆ mp4_duration()

float | None backend.video.mp4_duration ( Path path)
Duration in seconds from the movie header (moov/mvhd), or None if the file has
no complete header — not an mp4, or still being written.

Definition at line 193 of file video.py.

References _boxes(), _child(), and _full_box_times().

Referenced by _header_cached().

◆ mp4_frame_seconds()

float | None backend.video.mp4_frame_seconds ( Path path)
Seconds per frame of the first video track: its duration over its sample count
(mdhd, stsz). The kept files are video only, constant rate, so this is the step one
arrow press moves. None if the header does not say.

Definition at line 152 of file video.py.

References _boxes(), _child(), and _full_box_times().

Referenced by _header_cached().

◆ read_timing()

Timing | None backend.video.read_timing ( Path path)
A clip's timing file, on the DAQ's clock, or None if there is none or it is not one
this viewer understands — then the clip falls back to its name.

Definition at line 226 of file video.py.

Referenced by _timing_cached().

Variable Documentation

◆ _dur_cache

dict backend.video._dur_cache = {}
protected

Definition at line 255 of file video.py.

◆ _lock

backend.video._lock = threading.Lock()
protected

Definition at line 257 of file video.py.

◆ _timing_cache

dict backend.video._timing_cache = {}
protected

Definition at line 256 of file video.py.

◆ RECORDING_RE

backend.video.RECORDING_RE
Initial value:
1= re.compile(
2 r"^(\d{4})-(\d{2})-(\d{2})_(\d{2})(\d{2})(\d{2})_([a-z0-9-]{1,32})$", re.IGNORECASE
3)

Definition at line 51 of file video.py.

◆ STREAM_RE

backend.video.STREAM_RE = re.compile(r"^[A-Za-z0-9_-]{1,64}$")

Definition at line 55 of file video.py.

◆ TIMING_VERSION

int backend.video.TIMING_VERSION = 1

Definition at line 215 of file video.py.