cleanup: remove dead code and duplicate docs

- Remove session-ses_2f27.md (161KB raw session log)
- Remove 49 ROOT_* duplicate files across REFERENCE/
- Remove 14 duplicate files between REFERENCE/ root and history/
- Remove asr_legacy.rs (dead code, replaced by asr.rs)
- Remove src/core/worker/ (duplicate JobWorker)
- Remove src/core/layers/ (empty directory)
- Remove 4 .bak files in src/
- Remove 7 dead private methods in worker/processor.rs
- Remove backup directory from git tracking
This commit is contained in:
Warren
2026-05-04 01:31:21 +08:00
parent ee81e343ce
commit e75c4d6f07
3270 changed files with 35190 additions and 53367 deletions

268
src/bin/cli.rs Normal file
View File

@@ -0,0 +1,268 @@
use chrono::Local;
use clap::{Parser, Subcommand};
use reqwest;
use serde_json;
use std::time::Duration;
use std::time::Instant;
fn get_timestamp() -> String {
Local::now().format("%H:%M:%S").to_string()
}
#[derive(Parser)]
#[command(name = "momentry-cli")]
#[command(about = "CLI Agent Interface for Momentry Portal")]
struct Cli {
#[command(subcommand)]
command: Commands,
/// API Base URL (default: http://localhost:3002)
#[arg(long, default_value = "http://localhost:3002")]
api_url: String,
}
#[derive(Subcommand)]
enum Commands {
/// 註冊檔案 (Register File)
Register { path: String },
/// 取消註冊 (Unregister File)
Unregister { file_uuid: String },
/// 探測檔案 (Probe File) - 需要 UUID
Probe { file_uuid: String },
/// 開始處理 (Start Processing)
Process { file_uuid: String },
/// 監控處理進度 (Watch Progress until completed)
Watch { file_uuid: String },
/// 列出未綁定臉部 (List Unbound Faces)
ListFaces {
file_uuid: String,
#[arg(long, default_value = "0.5")]
min_conf: f64,
#[arg(long, default_value = "20")]
limit: usize,
},
/// 綁定身份 (Bind Identity)
Bind {
file_uuid: String,
face_id: String,
identity_id: String,
},
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
let start_time = Instant::now();
println!("⏱️ [TIME] Start Command: {}", get_timestamp());
let result = run_command(&cli).await;
let duration = start_time.elapsed();
println!(
"⏱️ [TIME] End Command: {} | Duration: {:.2}s",
get_timestamp(),
duration.as_secs_f64()
);
result
}
async fn run_command(cli: &Cli) -> Result<(), Box<dyn std::error::Error>> {
let api_key = "muser_test_001";
let client = reqwest::Client::new();
let auth_header = reqwest::header::HeaderMap::from_iter([
("X-API-Key".parse().unwrap(), api_key.parse().unwrap()),
(
"Content-Type".parse().unwrap(),
"application/json".parse().unwrap(),
),
]);
match &cli.command {
Commands::Register { path } => {
println!("📥 [CLI] 正在註冊: {}", path);
let res = client
.post(&format!("{}/api/v1/files/register", cli.api_url))
.headers(auth_header.clone())
.json(&serde_json::json!({ "file_path": path }))
.send()
.await?;
if res.status().is_success() {
let json: serde_json::Value = res.json().await?;
println!("✅ [CLI] 註冊成功:");
println!(
" UUID: {}",
json.get("file_uuid")
.unwrap_or(&serde_json::Value::String("-".into()))
);
} else {
println!("❌ [CLI] 註冊失敗: {}", res.status());
}
}
Commands::Unregister { file_uuid } => {
println!("🗑️ [CLI] 正在取消註冊: {}", file_uuid);
let res = client
.post(&format!("{}/api/v1/unregister", cli.api_url))
.headers(auth_header.clone())
.json(&serde_json::json!({ "uuid": file_uuid }))
.send()
.await?;
if res.status().is_success() {
println!("✅ [CLI] 取消註冊成功");
} else {
println!("❌ [CLI] 取消註冊失敗: {}", res.status());
}
}
Commands::Probe { file_uuid } => {
println!("🔍 [CLI] 正在探測檔案 (UUID): {}", file_uuid);
let res = client
.get(&format!(
"{}/api/v1/assets/{}/probe",
cli.api_url, file_uuid
))
.headers(auth_header.clone())
.send()
.await?;
if res.status().is_success() {
let json: serde_json::Value = res.json().await?;
println!("✅ [CLI] 探測成功:");
println!(
" 時長: {}s",
json.get("duration")
.map(|v| v.to_string())
.unwrap_or("-".into())
);
} else {
println!("❌ [CLI] 探測失敗: {}", res.status());
}
}
Commands::Process { file_uuid } => {
println!("⚙️ [CLI] 正在觸發處理: {}", file_uuid);
let res = client
.post(&format!(
"{}/api/v1/assets/{}/process",
cli.api_url, file_uuid
))
.headers(auth_header.clone())
.json(&serde_json::json!({})) // Send empty JSON object
.send()
.await?;
if res.status().is_success() {
println!(
"✅ [CLI] 處理已啟動。請使用 'momentry-cli watch {}' 監控進度。",
file_uuid
);
} else {
println!("❌ [CLI] 啟動處理失敗: {}", res.status());
}
}
Commands::Watch { file_uuid } => {
println!("👀 [CLI] 開始監控進度: {}", file_uuid);
loop {
let res = client
.get(&format!("{}/api/v1/progress/{}", cli.api_url, file_uuid))
.headers(auth_header.clone())
.send()
.await?;
if !res.status().is_success() {
println!("❌ [CLI] 無法取得進度: {}", res.status());
break;
}
let json: serde_json::Value = res.json().await?;
let progress = json["overall_progress"].as_u64().unwrap_or(0);
let current = json["current_processor"].as_str().unwrap_or("Unknown");
println!(" 📊 進度: {}% | 當前階段: {}", progress, current);
if progress >= 100 || json["status"].as_str().unwrap_or("") == "completed" {
println!("✅ [CLI] 處理完成!");
break;
}
tokio::time::sleep(Duration::from_secs(3)).await;
}
}
Commands::ListFaces {
file_uuid,
min_conf,
limit,
} => {
println!("🕵️ [CLI] 正在查詢臉部: {}", file_uuid);
let url = format!(
"{}/api/v1/faces/candidates?file_uuid={}&min_confidence={}&page_size={}",
cli.api_url, file_uuid, min_conf, limit
);
let res = client.get(&url).headers(auth_header.clone()).send().await?;
if res.status().is_success() {
let json: serde_json::Value = res.json().await?;
println!("✅ [CLI] 查詢成功:");
if let Some(candidates) = json.get("candidates") {
if candidates.as_array().map_or(0, |v| v.len()) > 0 {
println!(
" 找到 {} 個未綁定臉部:",
candidates.as_array().unwrap().len()
);
for (i, c) in candidates.as_array().unwrap().iter().enumerate() {
let id = c.get("id").map(|v| v.to_string()).unwrap_or("N/A".into());
let conf = c
.get("confidence")
.map(|v| v.to_string())
.unwrap_or("0".into());
println!(" {}. ID: {} | Confidence: {}", i + 1, id, conf);
}
} else {
println!(" 未找到符合條件的臉部。");
}
}
} else {
println!("❌ [CLI] 查詢失敗: {}", res.status());
}
}
Commands::Bind {
file_uuid,
face_id,
identity_id,
} => {
println!(
"🔗 [CLI] 正在綁定 Identity {} -> Face {} (File: {})",
identity_id, face_id, file_uuid
);
let payload = serde_json::json!({
"identity_id": identity_id.parse::<i64>().unwrap_or(0),
"binding_type": "face",
"binding_value": face_id
});
let res = client
.post(&format!("{}/api/v1/identities/bind", cli.api_url))
.headers(auth_header.clone())
.json(&payload)
.send()
.await?;
if res.status().is_success() {
let json: serde_json::Value = res.json().await?;
println!(
"✅ [CLI] 綁定成功: {}",
json.get("message")
.map(|v| v.to_string())
.unwrap_or("".into())
);
} else {
let text = res.text().await?;
println!("❌ [CLI] 綁定失敗: {}", text);
}
}
}
Ok(())
}

View File

@@ -1,711 +0,0 @@
use anyhow::{Context, Result};
use clap::Parser;
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
#[derive(Parser, Debug)]
#[command(name = "integrated_player")]
#[command(about = "Integrated player for ASR, Face, ASRX, and Pose")]
struct Args {
#[arg(short, long)]
video: PathBuf,
#[arg(short = 'r', long)]
asr: Option<PathBuf>,
#[arg(short = 'f', long)]
face: Option<PathBuf>,
#[arg(short = 'x', long)]
asrx: Option<PathBuf>,
#[arg(short = 'p', long)]
pose: Option<PathBuf>,
#[arg(short = 's', long, default_value = "0.0")]
start: f64,
#[arg(long)]
speaker_name: Option<String>,
#[arg(long)]
auto_play_speaker: bool,
#[arg(long)]
demo: bool,
#[arg(long, default_value = "3")]
demo_segments_per_speaker: usize,
#[arg(long, default_value = "2.0")]
demo_speed: f64,
#[arg(long)]
show_video: bool,
#[arg(long, default_value = "800")]
video_width: u32,
#[arg(long, default_value = "600")]
video_height: u32,
#[arg(long)]
continuous_demo: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct AsrSegment {
start: f64,
end: f64,
text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct AsrData {
language: Option<String>,
segments: Vec<AsrSegment>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct FaceDetection {
frame: u64,
timestamp: f64,
x: i32,
y: i32,
width: i32,
height: i32,
confidence: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct FaceResult {
results: FaceResults,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct FaceResults {
detections: Vec<FaceDetection>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct AsrxSegment {
index: usize,
start: f64,
end: f64,
duration: f64,
speaker: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct AsrxData {
segments: Vec<AsrxSegment>,
speaker_stats: HashMap<String, SpeakerStats>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SpeakerStats {
count: usize,
duration: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Keypoint {
name: String,
x: f32,
y: f32,
confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PersonPose {
keypoints: Vec<Keypoint>,
bbox: Bbox,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Bbox {
x: i32,
y: i32,
width: i32,
height: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PoseFrame {
frame: u64,
timestamp: f64,
persons: Vec<PersonPose>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PoseData {
frames: Vec<PoseFrame>,
}
#[derive(Debug, Clone)]
struct IntegratedSegment {
start: f64,
end: f64,
text: Option<String>,
speaker: Option<String>,
face: Option<FaceDetection>,
mouth_landmarks: Option<Vec<Keypoint>>,
}
struct IntegratedPlayer {
asr_data: Option<AsrData>,
face_data: Option<FaceResult>,
asrx_data: Option<AsrxData>,
pose_data: Option<PoseData>,
current_time: f64,
is_playing: bool,
speaker_names: HashMap<String, (String, String)>,
}
impl IntegratedPlayer {
fn new() -> Self {
let mut speaker_names = HashMap::new();
speaker_names.insert(
"SPEAKER_0".to_string(),
("Cary Grant".to_string(), "Peter Joshua".to_string()),
);
speaker_names.insert(
"SPEAKER_1".to_string(),
("Audrey Hepburn".to_string(), "Regina Lampert".to_string()),
);
speaker_names.insert(
"SPEAKER_2".to_string(),
(
"Walter Matthau".to_string(),
"Hamilton Bartholomew".to_string(),
),
);
speaker_names.insert(
"SPEAKER_4".to_string(),
("James Coburn".to_string(), "Tex Panthollow".to_string()),
);
Self {
asr_data: None,
face_data: None,
asrx_data: None,
pose_data: None,
current_time: 0.0,
is_playing: false,
speaker_names,
}
}
fn load_asr(&mut self, path: &PathBuf) -> Result<()> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read ASR file: {:?}", path))?;
self.asr_data = Some(serde_json::from_str(&content)?);
println!(
"✓ Loaded {} ASR segments",
self.asr_data.as_ref().unwrap().segments.len()
);
Ok(())
}
fn load_face(&mut self, path: &PathBuf) -> Result<()> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read Face file: {:?}", path))?;
self.face_data = Some(serde_json::from_str(&content)?);
println!(
"✓ Loaded {} face detections",
self.face_data.as_ref().unwrap().results.detections.len()
);
Ok(())
}
fn load_asrx(&mut self, path: &PathBuf) -> Result<()> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read ASRX file: {:?}", path))?;
self.asrx_data = Some(serde_json::from_str(&content)?);
println!(
"✓ Loaded {} ASRX segments, {} speakers",
self.asrx_data.as_ref().unwrap().segments.len(),
self.asrx_data.as_ref().unwrap().speaker_stats.len()
);
Ok(())
}
fn load_pose(&mut self, path: &PathBuf) -> Result<()> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read Pose file: {:?}", path))?;
self.pose_data = Some(serde_json::from_str(&content)?);
println!(
"✓ Loaded {} pose frames",
self.pose_data.as_ref().unwrap().frames.len()
);
Ok(())
}
fn get_current_segment(&self, time: f64) -> Option<IntegratedSegment> {
let mut segment = IntegratedSegment {
start: 0.0,
end: 0.0,
text: None,
speaker: None,
face: None,
mouth_landmarks: None,
};
if let Some(asr) = &self.asr_data {
for seg in &asr.segments {
if time >= seg.start && time <= seg.end {
segment.start = seg.start;
segment.end = seg.end;
segment.text = Some(seg.text.clone());
break;
}
}
}
if let Some(asrx) = &self.asrx_data {
for seg in &asrx.segments {
if time >= seg.start && time <= seg.end {
segment.start = seg.start;
segment.end = seg.end;
segment.speaker = Some(seg.speaker.clone());
break;
}
}
}
if let Some(face) = &self.face_data {
for det in &face.results.detections {
if (det.timestamp - time).abs() < 1.0 {
segment.face = Some(det.clone());
break;
}
}
}
if let Some(pose) = &self.pose_data {
for frame in &pose.frames {
if (frame.timestamp - time).abs() < 0.5 {
if let Some(person) = frame.persons.first() {
let mouth_points: Vec<Keypoint> = person
.keypoints
.iter()
.filter(|kp| {
kp.name.contains("mouth")
|| kp.name.contains("lip")
|| kp.name == "nose"
})
.cloned()
.collect();
if !mouth_points.is_empty() {
segment.mouth_landmarks = Some(mouth_points);
break;
}
}
}
}
}
if segment.text.is_some()
|| segment.speaker.is_some()
|| segment.face.is_some()
|| segment.mouth_landmarks.is_some()
{
Some(segment)
} else {
None
}
}
fn get_speaker_info(&self, speaker_id: &str) -> (String, String) {
self.speaker_names
.get(speaker_id)
.cloned()
.unwrap_or_else(|| ("Unknown".to_string(), "Unknown".to_string()))
}
fn print_segment(&self, segment: &IntegratedSegment) {
println!("\n{:=<80}", "");
println!("⏱ Time: {:.2}s - {:.2}s", segment.start, segment.end);
if let Some(text) = &segment.text {
println!("📝 Text: {}", text);
}
if let Some(speaker) = &segment.speaker {
let (actor, character) = self.get_speaker_info(speaker);
println!("🎤 Speaker: {}{} ({})", speaker, actor, character);
}
if let Some(face) = &segment.face {
println!(
"👤 Face: bbox=({},{}) {}x{}, confidence={:.2}",
face.x, face.y, face.width, face.height, face.confidence
);
}
if let Some(landmarks) = &segment.mouth_landmarks {
println!("👄 Mouth landmarks: {} points", landmarks.len());
for kp in landmarks.iter().take(3) {
println!(
"{}: ({:.1}, {:.1}) conf={:.2}",
kp.name, kp.x, kp.y, kp.confidence
);
}
}
println!("{:=<80}", "");
}
fn list_speakers(&self) {
if let Some(asrx) = &self.asrx_data {
println!("\n📊 Speaker Statistics:");
println!("{:-<80}", "");
println!(
"{:15} {:20} {:20} {:>10} {:>10}",
"Speaker ID", "Actor", "Character", "Segments", "Duration"
);
println!("{:-<80}", "");
for (speaker_id, stats) in &asrx.speaker_stats {
let (actor, character) = self.get_speaker_info(speaker_id);
println!(
"{:15} {:20} {:20} {:>10} {:>9.1}s",
speaker_id, actor, character, stats.count, stats.duration
);
}
println!("{:-<80}", "");
}
}
}
fn play_segment(video_path: &PathBuf, start: f64, duration: f64, show_video: bool) -> Result<()> {
println!("▶️ Playing {:.2}s - {:.2}s", start, start + duration);
let mut cmd = Command::new("ffplay");
if show_video {
cmd.args([
"-ss",
&format!("{:.2}", start),
"-t",
&format!("{:.2}", duration),
"-autoexit",
video_path.to_str().unwrap(),
]);
} else {
cmd.args([
"-ss",
&format!("{:.2}", start),
"-t",
&format!("{:.2}", duration),
"-autoexit",
"-nodisp",
video_path.to_str().unwrap(),
]);
}
let _child = cmd
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.context("Failed to start ffplay")?;
Ok(())
}
fn play_speaker_segments(
player: &IntegratedPlayer,
video_path: &PathBuf,
speaker_id: &str,
limit: Option<usize>,
) -> Result<()> {
if let Some(asrx) = &player.asrx_data {
let segments: Vec<&AsrxSegment> = asrx
.segments
.iter()
.filter(|s| s.speaker == speaker_id)
.collect();
let total = segments.len();
let count = limit.unwrap_or(total).min(total);
println!("\n🎬 Playing {} segments for {}", count, speaker_id);
for (i, seg) in segments.iter().take(count).enumerate() {
println!("\n[{}/{}] Segment {}", i + 1, count, seg.index);
if let Some(segment) = player.get_current_segment(seg.start + 0.1) {
player.print_segment(&segment);
}
play_segment(video_path, seg.start, seg.duration, false)?;
thread::sleep(Duration::from_millis(500));
}
println!("\n✅ Finished playing {} segments", count);
}
Ok(())
}
fn run_demo(player: &IntegratedPlayer, args: &Args) -> Result<()> {
}
}
});
if let Some(asr) = &player.asr_data {
let total_segments = asr.segments.len();
for (i, seg) in asr.segments.iter().enumerate() {
// 檢查是否退出
if quit.load(Ordering::SeqCst) {
println!("\n⏹️ Stopped by user");
break;
}
// 檢查是否暫停
while paused.load(Ordering::SeqCst) {
println!("\r⏸️ Paused - Press SPACE to resume",);
std::io::stdout().flush()?;
thread::sleep(Duration::from_millis(100));
if quit.load(Ordering::SeqCst) {
println!("\n⏹️ Stopped by user");
return Ok(());
}
}
println!("\n[{}/{}] Segment", i + 1, total_segments);
println!("{:=<80}", "");
// 顯示所有信息
if let Some(segment) = player.get_current_segment(seg.start + 0.01) {
player.print_segment(&segment);
}
// 播放音頻/視頻
let duration = seg.end - seg.start;
println!(
"▶️ Playing: {:.2}s - {:.2}s ({:.2}s)",
seg.start, seg.end, duration
);
let mut cmd = Command::new("ffplay");
if args.show_video {
cmd.args([
"-ss",
&format!("{:.2}", seg.start),
"-t",
&format!("{:.2}", duration),
"-autoexit",
"-x",
&format!("{}", args.video_width),
"-y",
&format!("{}", args.video_height),
args.video.to_str().unwrap(),
]);
} else {
cmd.args([
"-ss",
&format!("{:.2}", seg.start),
"-t",
&format!("{:.2}", duration),
"-autoexit",
"-nodisp",
args.video.to_str().unwrap(),
]);
}
let _child = cmd
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.context("Failed to start ffplay")?;
// 等待播放完成
thread::sleep(Duration::from_millis((duration * 1000.0) as u64 + 100));
}
println!("\n{:=<80}", "");
println!("✅ Demo completed! Played {} segments", total_segments);
println!("{:=<80}", "");
} else {
println!("⚠️ No ASR data loaded");
}
Ok(())
}
fn run_demo(player: &IntegratedPlayer, args: &Args) -> Result<()> {
println!("\n🎬 Auto Demo Mode");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("Segments per speaker: {}", args.demo_segments_per_speaker);
println!("Demo speed: {:.1}x", args.demo_speed);
println!();
if let Some(asrx) = &player.asrx_data {
let mut speaker_ids: Vec<String> = asrx.speaker_stats.keys().cloned().collect();
speaker_ids.sort();
for speaker_id in &speaker_ids {
let (actor, character) = player.get_speaker_info(speaker_id);
println!("\n{:=<80}", "");
println!("🎭 Demo: {}{} ({})", speaker_id, actor, character);
println!("{:=<80}", "");
let segments: Vec<&AsrxSegment> = asrx
.segments
.iter()
.filter(|s| s.speaker == *speaker_id)
.collect();
let count = args.demo_segments_per_speaker.min(segments.len());
for (i, seg) in segments.iter().take(count).enumerate() {
println!("\n[Segment {}/{}]", i + 1, count);
if let Some(segment) = player.get_current_segment(seg.start + 0.1) {
player.print_segment(&segment);
}
println!(
"⏳ Playing audio ({:.1}s)...",
seg.duration / args.demo_speed
);
let _child = Command::new("ffplay")
.args([
"-ss",
&format!("{:.2}", seg.start),
"-t",
&format!("{:.2}", seg.duration / args.demo_speed),
"-autoexit",
"-nodisp",
args.video.to_str().unwrap(),
])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.context("Failed to start ffplay")?;
thread::sleep(Duration::from_millis(
((seg.duration / args.demo_speed) * 1000.0) as u64 + 500,
));
}
println!("\n⏸️ Pausing 2 seconds before next speaker...");
thread::sleep(Duration::from_secs(2));
}
println!("\n{:=<80}", "");
println!("✅ Demo completed!");
println!("{:=<80}", "");
}
Ok(())
}
fn main() -> Result<()> {
let args = Args::parse();
if !args.video.exists() {
anyhow::bail!("Video file not found: {:?}", args.video);
}
println!("🎬 Integrated Player for ASR/Face/ASRX/Pose");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("Video: {:?}", args.video);
let mut player = IntegratedPlayer::new();
if let Some(asr_path) = &args.asr {
if asr_path.exists() {
player.load_asr(asr_path)?;
}
}
if let Some(face_path) = &args.face {
if face_path.exists() {
player.load_face(face_path)?;
}
}
if let Some(asrx_path) = &args.asrx {
if asrx_path.exists() {
player.load_asrx(asrx_path)?;
}
}
if let Some(pose_path) = &args.pose {
if pose_path.exists() {
player.load_pose(pose_path)?;
}
}
player.list_speakers();
if args.demo {
run_demo(&player, &args)?;
} else if args.continuous_demo {
run_continuous_demo(&player, &args)?;
} else if args.auto_play_speaker {
if let Some(speaker_id) = &args.speaker_name {
play_speaker_segments(&player, &args.video, speaker_id, Some(5))?;
} else {
println!("\n⚠️ --speaker-name required for --auto-play-speaker");
}
} else {
println!("\n🎮 Interactive Mode");
println!(" Commands:");
println!(" • Enter time in seconds to seek");
println!(" • 's' to show current segment");
println!(" • 'l' to list speakers");
println!(" • 'p <speaker>' to play speaker segments");
println!(" • 'q' to quit");
println!();
loop {
print!("> ");
std::io::Write::flush(&mut std::io::stdout())?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let input = input.trim();
if input == "q" || input == "quit" || input == "exit" {
break;
} else if input == "s" || input == "show" {
if let Some(segment) = player.get_current_segment(player.current_time) {
player.print_segment(&segment);
} else {
println!("No segment at time {:.2}s", player.current_time);
}
} else if input == "l" || input == "list" {
player.list_speakers();
} else if input.starts_with("p ") {
let speaker_id = input.strip_prefix("p ").unwrap();
play_speaker_segments(&player, &args.video, speaker_id, Some(3))?;
} else if let Ok(time) = input.parse::<f64>() {
player.current_time = time;
println!("Seeked to {:.2}s", time);
if let Some(segment) = player.get_current_segment(time) {
player.print_segment(&segment);
} else {
println!("No segment at this time");
}
}
}
}
Ok(())
}

View File

@@ -1,56 +0,0 @@
use anyhow::{Context, Result};
use momentry_core::core::db::{Database, PostgresDb};
use momentry_core::core::text::tokenizer::{contains_chinese, tokenize_chinese_text};
use momentry_core::core::text::{global_synonym_expander, normalize_chinese_query};
use std::env;
#[tokio::main]
async fn main() -> Result<()> {
env::set_var("RUST_LOG", "info");
println!("=== 同義詞擴展測試 ===\n");
// 初始化 PostgreSQL
let pg = PostgresDb::init()
.await
.context("Failed to initialize PostgreSQL database")?;
let expander = global_synonym_expander();
// 測試查詢
let test_queries = vec![
"電腦",
"視頻",
"分析",
"工作",
"檔案",
"電腦工作",
"工作檔案",
];
for query_str in test_queries {
println!("\n🔍 測試查詢: '{}'", query_str);
// 顯示同義詞擴展
if contains_chinese(query_str) {
let normalized = normalize_chinese_query(query_str);
let expanded = expander.expand_chinese_query(&normalized);
println!(" 同義詞擴展: {}", expanded);
}
// 顯示轉換後的 tsquery
match pg.prepare_tsquery(query_str) {
Ok(tsquery) => println!(" TSQUERY: {}", tsquery),
Err(e) => println!(" TSQUERY 錯誤: {}", e),
}
// 執行搜索(即使沒有結果)
let results = pg.search_bm25(query_str, None, 2).await?;
println!(" 找到 {} 筆結果", results.len());
for (i, r) in results.iter().enumerate() {
println!(" {}. [{}] {}", i + 1, r.uuid, r.text);
}
}
Ok(())
}