mirror of
https://github.com/cjfranko/NTP-Timeturner.git
synced 2025-11-08 18:32:02 +00:00
fix: Handle drop-frame timecode separator in API and UI
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
parent
22dc01e80f
commit
3ffb54e9aa
3 changed files with 38 additions and 6 deletions
|
|
@ -8,13 +8,13 @@ This document describes the HTTP API for the NTP Timeturner application.
|
||||||
|
|
||||||
- **`GET /api/status`**
|
- **`GET /api/status`**
|
||||||
|
|
||||||
Retrieves the real-time status of the LTC reader and system clock synchronization.
|
Retrieves the real-time status of the LTC reader and system clock synchronization. The `ltc_timecode` field uses `:` as a separator for non-drop-frame timecode, and `;` for drop-frame timecode between seconds and frames (e.g., `10:20:30;00`).
|
||||||
|
|
||||||
**Example Response:**
|
**Example Response:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ltc_status": "LOCK",
|
"ltc_status": "LOCK",
|
||||||
"ltc_timecode": "10:20:30:00",
|
"ltc_timecode": "10:20:30;00",
|
||||||
"frame_rate": "25.00fps",
|
"frame_rate": "25.00fps",
|
||||||
"system_clock": "10:20:30.005",
|
"system_clock": "10:20:30.005",
|
||||||
"system_date": "2025-07-30",
|
"system_date": "2025-07-30",
|
||||||
|
|
|
||||||
32
src/api.rs
32
src/api.rs
|
|
@ -47,7 +47,11 @@ async fn get_status(data: web::Data<AppState>) -> impl Responder {
|
||||||
|
|
||||||
let ltc_status = state.latest.as_ref().map_or("(waiting)".to_string(), |f| f.status.clone());
|
let ltc_status = state.latest.as_ref().map_or("(waiting)".to_string(), |f| f.status.clone());
|
||||||
let ltc_timecode = state.latest.as_ref().map_or("…".to_string(), |f| {
|
let ltc_timecode = state.latest.as_ref().map_or("…".to_string(), |f| {
|
||||||
format!("{:02}:{:02}:{:02}:{:02}", f.hours, f.minutes, f.seconds, f.frames)
|
let sep = if f.is_drop_frame { ';' } else { ':' };
|
||||||
|
format!(
|
||||||
|
"{:02}:{:02}:{:02}{}{:02}",
|
||||||
|
f.hours, f.minutes, f.seconds, sep, f.frames
|
||||||
|
)
|
||||||
});
|
});
|
||||||
let frame_rate = state.latest.as_ref().map_or("…".to_string(), |f| {
|
let frame_rate = state.latest.as_ref().map_or("…".to_string(), |f| {
|
||||||
format!("{:.2}fps", f.frame_rate.to_f64().unwrap_or(0.0))
|
format!("{:.2}fps", f.frame_rate.to_f64().unwrap_or(0.0))
|
||||||
|
|
@ -291,6 +295,32 @@ mod tests {
|
||||||
assert_eq!(resp.hardware_offset_ms, 10);
|
assert_eq!(resp.hardware_offset_ms, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_get_status_drop_frame() {
|
||||||
|
let app_state = get_test_app_state();
|
||||||
|
// Set state to drop frame
|
||||||
|
app_state
|
||||||
|
.ltc_state
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.latest
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.is_drop_frame = true;
|
||||||
|
|
||||||
|
let app = test::init_service(
|
||||||
|
App::new()
|
||||||
|
.app_data(app_state.clone())
|
||||||
|
.service(get_status),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = test::TestRequest::get().uri("/api/status").to_request();
|
||||||
|
let resp: ApiStatus = test::call_and_read_body_json(&app, req).await;
|
||||||
|
|
||||||
|
assert_eq!(resp.ltc_timecode, "01:02:03;04");
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web::test]
|
#[actix_web::test]
|
||||||
async fn test_get_config() {
|
async fn test_get_config() {
|
||||||
let app_state = get_test_app_state();
|
let app_state = get_test_app_state();
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animate LTC Timecode - only if status is LOCK
|
// Animate LTC Timecode - only if status is LOCK
|
||||||
if (lastApiData.ltc_status === 'LOCK' && lastApiData.ltc_timecode && lastApiData.ltc_timecode.includes(':') && lastApiData.frame_rate) {
|
if (lastApiData.ltc_status === 'LOCK' && lastApiData.ltc_timecode && lastApiData.ltc_timecode.match(/[:;]/) && lastApiData.frame_rate) {
|
||||||
const tcParts = lastApiData.ltc_timecode.split(':');
|
const separator = lastApiData.ltc_timecode.includes(';') ? ';' : ':';
|
||||||
|
const tcParts = lastApiData.ltc_timecode.split(/[:;]/);
|
||||||
const frameRate = parseFloat(lastApiData.frame_rate);
|
const frameRate = parseFloat(lastApiData.frame_rate);
|
||||||
|
|
||||||
if (tcParts.length === 4 && !isNaN(frameRate) && frameRate > 0) {
|
if (tcParts.length === 4 && !isNaN(frameRate) && frameRate > 0) {
|
||||||
let h = parseInt(tcParts[0], 10);
|
let h = parseInt(tcParts[0], 10);
|
||||||
let m = parseInt(tcParts[1], 10);
|
let m = parseInt(tcParts[1], 10);
|
||||||
|
|
@ -126,7 +128,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
h %= 24;
|
h %= 24;
|
||||||
|
|
||||||
statusElements.ltcTimecode.textContent =
|
statusElements.ltcTimecode.textContent =
|
||||||
`${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}:${String(f).padStart(2, '0')}`;
|
`${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}${separator}${String(f).padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue