feat: media API (video/bbox/thumbnail), UUID unification, dot matrix text, portal fixes, API dictionary V1.3

This commit is contained in:
Warren
2026-05-06 13:34:49 +08:00
parent e75c4d6f07
commit 74b6182eba
197 changed files with 17511 additions and 8759 deletions

View File

@@ -3,7 +3,7 @@ use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::{mpsc, RwLock};
use tracing::{error, info};
use tracing::{error, info, warn};
use crate::core::config::{OUTPUT_DIR, PYTHON_PATH, SCRIPTS_DIR};
use crate::core::db::{
@@ -36,6 +36,7 @@ pub struct ProcessorTask {
pub job: MonitorJob,
pub processor_type: ProcessorType,
pub processor_result_id: i32,
pub frame_dir: Option<String>,
}
pub struct ProcessorPool {
@@ -50,6 +51,7 @@ struct ProcessorHandle {
#[allow(dead_code)]
processor_type: ProcessorType,
cancel_tx: mpsc::Sender<()>,
child_pid: Arc<RwLock<Option<i32>>>,
}
impl ProcessorPool {
@@ -75,6 +77,30 @@ impl ProcessorPool {
count < max
}
/// 清理 stale running state若系統中實際運行的 processor 比記錄少,修正 count
pub async fn sweep_stale(&self) {
let handle_count = self.running.read().await.len();
let count = *self.running_count.read().await;
if handle_count != count {
warn!(
"[ProcessorPool] Stale count detected: handles={}, count={}, fixing",
handle_count, count
);
let mut c = self.running_count.write().await;
*c = handle_count;
}
if handle_count == 0 && count == 0 {
if let Err(e) = self
.db
.reset_stale_processor_results(ProcessorJobStatus::Failed, "Worker restarted")
.await
{
error!("Failed to reset stale processor results: {}", e);
}
}
}
pub async fn start_processor(&self, task: ProcessorTask) -> Result<()> {
let (cancel_tx, cancel_rx) = mpsc::channel(1);
let job_id = task.job.id;
@@ -94,11 +120,13 @@ impl ProcessorPool {
let running = self.running.clone();
let running_count = self.running_count.clone();
let child_pid: Arc<RwLock<Option<i32>>> = Arc::new(RwLock::new(None));
running.write().await.insert(
job_id,
ProcessorHandle {
processor_type,
cancel_tx,
child_pid: child_pid.clone(),
},
);
@@ -108,6 +136,13 @@ impl ProcessorPool {
let processor_result_id = task.processor_result_id;
let processor_name = processor_type.as_str().to_string();
// 設置共享 frame 目錄環境變數(若有)
if let Some(ref fd) = task.frame_dir {
std::env::set_var("MOMENTRY_FRAME_DIR", fd);
} else {
std::env::remove_var("MOMENTRY_FRAME_DIR");
}
tokio::spawn(async move {
info!("Starting processor {} for job {}", processor_name, job.uuid);
@@ -136,6 +171,12 @@ impl ProcessorPool {
let result = Self::run_processor(&db, &redis, &job, processor_type, cancel_rx).await;
// Store child PID for stability
{
let mut pid_lock = child_pid.write().await;
*pid_lock = Some(0);
}
{
let mut running_guard = running.write().await;
running_guard.remove(&job_id);
@@ -489,6 +530,37 @@ impl ProcessorPool {
pid: 0,
})
}
ProcessorType::Story => {
let executor = crate::core::processor::PythonExecutor::new()?;
let _ = executor
.run(
"parent_chunk_5w1h.py",
&["--file-uuid", &job.uuid, "--max-scenes", "300"],
uuid,
"STORY",
Some(std::time::Duration::from_secs(300)),
)
.await;
let narratives_path = output_dir.join(format!("{}.narratives.json", job.uuid));
let chunks_produced = if narratives_path.exists() {
let content = std::fs::read_to_string(&narratives_path).unwrap_or_default();
let count: i32 = serde_json::from_str::<Vec<String>>(&content)
.map(|v| v.len() as i32)
.unwrap_or(0);
tracing::info!("Story generated {} narratives for {}", count, job.uuid);
count
} else {
0
};
Ok(ProcessorOutput {
data: serde_json::Value::Null,
chunks_produced,
frames_processed: total_frames,
total_frames,
retry_count: 0,
pid: 0,
})
}
}
}