release: v1.3.0 - TKG node type renaming

Changes:
- Rust: face_trace → face_track (45 occurrences in 8 files)
- Rust: gaze_trace → gaze_track, lip_trace → lip_track
- Python: tkg_builder.py unified + pipeline_checklist.py fixed
- Swift: swift_hand.swift hand state detection (empty vs holding)

Node type changes:
  face_trace    → face_track
  person_trace  → body_track
  gaze_trace    → gaze_track
  lip_trace     → lip_track
  hand_trace    → hand_track
  speaker       → speaker_segment
  object        → detected_object
  text_trace    → text_region

Migration:
  PUBLIC schema: 12970 + 892 + 305 rows updated
This commit is contained in:
Accusys
2026-06-22 07:18:21 +08:00
parent bce9435823
commit 7e548f8b08
35 changed files with 2789 additions and 481 deletions
+42 -37
View File
@@ -14,7 +14,10 @@ fn t(name: &str) -> String {
}
}
pub async fn exec_find_file(pool: &sqlx::PgPool, args: &serde_json::Value) -> Result<String, String> {
pub async fn exec_find_file(
pool: &sqlx::PgPool,
args: &serde_json::Value,
) -> Result<String, String> {
let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
let videos = schema::table_name("videos");
let fd_table = schema::table_name("face_detections");
@@ -41,7 +44,10 @@ pub async fn exec_find_file(pool: &sqlx::PgPool, args: &serde_json::Value) -> Re
Ok(serde_json::json!({"found": true, "files": files}).to_string())
}
pub async fn exec_list_files(pool: &sqlx::PgPool, args: &serde_json::Value) -> Result<String, String> {
pub async fn exec_list_files(
pool: &sqlx::PgPool,
args: &serde_json::Value,
) -> Result<String, String> {
let limit = args.get("limit").and_then(|v| v.as_i64()).unwrap_or(10);
let videos = schema::table_name("videos");
let fd_table = schema::table_name("face_detections");
@@ -63,7 +69,10 @@ pub async fn exec_list_files(pool: &sqlx::PgPool, args: &serde_json::Value) -> R
Ok(serde_json::json!({"files": files}).to_string())
}
pub async fn exec_tkg_query(pool: &sqlx::PgPool, args: &serde_json::Value) -> Result<String, String> {
pub async fn exec_tkg_query(
pool: &sqlx::PgPool,
args: &serde_json::Value,
) -> Result<String, String> {
let file_uuid = args.get("file_uuid").and_then(|v| v.as_str()).unwrap_or("");
let query_type = args
.get("query_type")
@@ -137,8 +146,8 @@ pub async fn exec_tkg_query(pool: &sqlx::PgPool, args: &serde_json::Value) -> Re
FROM {} e \
JOIN {} a ON a.id = e.source_node_id \
JOIN {} b ON b.id = e.target_node_id \
JOIN {} fd_a ON fd_a.file_uuid = $1 AND fd_a.trace_id = REPLACE(a.external_id, 'trace_', '')::int \
JOIN {} fd_b ON fd_b.file_uuid = $1 AND fd_b.trace_id = REPLACE(b.external_id, 'trace_', '')::int \
JOIN {} fd_a ON fd_a.file_uuid = $1 AND fd_a.face_track_id = REPLACE(a.external_id, 'face_track_', '')::int \
JOIN {} fd_b ON fd_b.file_uuid = $1 AND fd_b.face_track_id = REPLACE(b.external_id, 'face_track_', '')::int \
JOIN {} ia ON ia.id = fd_a.identity_id \
JOIN {} ib ON ib.id = fd_b.identity_id \
WHERE e.file_uuid = $1 AND ia.name ILIKE $2 AND ib.name ILIKE $3 \
@@ -156,8 +165,8 @@ pub async fn exec_tkg_query(pool: &sqlx::PgPool, args: &serde_json::Value) -> Re
FROM {} e \
JOIN {} a ON a.id = e.source_node_id \
JOIN {} b ON b.id = e.target_node_id \
JOIN {} fd_a ON fd_a.trace_id = REPLACE(a.external_id, 'trace_', '')::int AND fd_a.file_uuid = $1 \
JOIN {} fd_b ON fd_b.trace_id = REPLACE(b.external_id, 'trace_', '')::int AND fd_b.file_uuid = $1 \
JOIN {} fd_a ON fd_a.face_track_id = REPLACE(a.external_id, 'face_track_', '')::int AND fd_a.file_uuid = $1 \
JOIN {} fd_b ON fd_b.face_track_id = REPLACE(b.external_id, 'face_track_', '')::int AND fd_b.file_uuid = $1 \
JOIN {} ia ON ia.id = fd_a.identity_id \
JOIN {} ib ON ib.id = fd_b.identity_id \
WHERE e.file_uuid = $1 AND e.edge_type = 'CO_OCCURS_WITH' \
@@ -174,10 +183,10 @@ pub async fn exec_tkg_query(pool: &sqlx::PgPool, args: &serde_json::Value) -> Re
"identity_traces" => {
let name = identity_name.unwrap_or("");
let rows: Vec<(i32, i64, i64, i64)> = sqlx::query_as(&format!(
"SELECT fd.trace_id, COUNT(*)::bigint, MIN(fd.frame_number)::bigint, MAX(fd.frame_number)::bigint \
"SELECT fd.face_track_id, COUNT(*)::bigint, MIN(fd.frame_number)::bigint, MAX(fd.frame_number)::bigint \
FROM {} fd JOIN {} i ON i.id = fd.identity_id \
WHERE fd.file_uuid = $1 AND i.name ILIKE $2 \
GROUP BY fd.trace_id ORDER BY COUNT(*) DESC LIMIT $3",
GROUP BY fd.face_track_id ORDER BY COUNT(*) DESC LIMIT $3",
fd_table, id_table
))
.bind(file_uuid).bind(name).bind(limit)
@@ -203,8 +212,8 @@ pub async fn exec_tkg_query(pool: &sqlx::PgPool, args: &serde_json::Value) -> Re
FROM {} i \
JOIN {} fd ON fd.identity_id = i.id AND ($2::text IS NULL OR fd.file_uuid = $2) \
JOIN {} fn ON fn.file_uuid = fd.file_uuid \
AND fn.node_type = 'face_trace' \
AND fn.external_id = CONCAT('trace_', fd.trace_id) \
AND fn.node_type = 'face_track' \
AND fn.external_id = CONCAT('face_track_', fd.face_track_id) \
JOIN {} e ON e.source_node_id = fn.id \
AND e.edge_type = 'SPEAKS_AS' \
AND ($2::text IS NULL OR e.file_uuid = $2) \
@@ -242,8 +251,8 @@ pub async fn exec_tkg_query(pool: &sqlx::PgPool, args: &serde_json::Value) -> Re
FROM {} i \
JOIN {} fd ON fd.identity_id = i.id AND ($3::text IS NULL OR fd.file_uuid = $3) \
JOIN {} fn ON fn.file_uuid = fd.file_uuid \
AND fn.node_type = 'face_trace' \
AND fn.external_id = CONCAT('trace_', fd.trace_id) \
AND fn.node_type = 'face_track' \
AND fn.external_id = CONCAT('face_track_', fd.face_track_id) \
JOIN {} e ON e.source_node_id = fn.id \
AND e.edge_type = 'SPEAKS_AS' \
AND ($3::text IS NULL OR e.file_uuid = $3) \
@@ -371,7 +380,7 @@ pub async fn exec_identity_text(
let sql = format!(
"SELECT c.chunk_id, c.start_time, c.end_time, c.text_content, \
i.name AS identity_name, fd.trace_id, i.source AS identity_source \
i.name AS identity_name, fd.face_track_id, i.source AS identity_source \
FROM {} c \
JOIN {} fd ON fd.file_uuid = c.file_uuid \
AND fd.frame_number BETWEEN c.start_frame AND c.end_frame \
@@ -408,7 +417,7 @@ pub async fn exec_identity_text(
"end_time": et,
"text": txt,
"identity_name": name,
"trace_id": tid,
"face_track_id": tid,
"source": src
})
} ).collect::<Vec<_>>()})
@@ -435,7 +444,7 @@ pub async fn exec_identities_search(
let sql = format!(
"SELECT DISTINCT ON (i.name, c.chunk_id) \
i.name, c.chunk_id, c.start_time, c.end_time, c.text_content, fd.trace_id \
i.name, c.chunk_id, c.start_time, c.end_time, c.text_content, fd.face_track_id \
FROM {} i \
JOIN {} fd ON fd.identity_id = i.id \
JOIN {} c ON c.file_uuid = fd.file_uuid \
@@ -465,7 +474,7 @@ pub async fn exec_identities_search(
"start_time": st,
"end_time": et,
"text": txt,
"trace_id": tid,
"face_track_id": tid,
})
}).collect::<Vec<_>>()})
.to_string(),
@@ -549,29 +558,25 @@ pub async fn exec_analyze_frame(
let frame_number = match args.get("frame_number").and_then(|v| v.as_i64()) {
Some(f) => f,
None => {
match query_auto_representative_frame(pool, file_uuid)
None => match query_auto_representative_frame(pool, file_uuid).await {
Ok(r) => r.frame_number,
Err(_) => {
let duration: f64 = sqlx::query_scalar(&format!(
"SELECT COALESCE(duration, 0) FROM {} WHERE file_uuid = $1",
videos
))
.bind(file_uuid)
.fetch_optional(pool)
.await
{
Ok(r) => r.frame_number,
Err(_) => {
let duration: f64 = sqlx::query_scalar(&format!(
"SELECT COALESCE(duration, 0) FROM {} WHERE file_uuid = $1",
videos
))
.bind(file_uuid)
.fetch_optional(pool)
.await
.map_err(|e| e.to_string())?
.unwrap_or(0.0);
if duration > 0.0 {
((duration / 2.0) * fps) as i64
} else {
0
}
.map_err(|e| e.to_string())?
.unwrap_or(0.0);
if duration > 0.0 {
((duration / 2.0) * fps) as i64
} else {
0
}
}
}
},
};
let timestamp_secs = frame_number as f64 / fps;