Initial commit: Momentry Core v0.1
- Rust-based digital asset management system - Video analysis: ASR, OCR, YOLO, Face, Pose - RAG capabilities with Qdrant vector database - Multi-database support: PostgreSQL, Redis, MongoDB - Monitoring system with launchd plists - n8n workflow automation integration
This commit is contained in:
108
src/core/thumbnail/mod.rs
Normal file
108
src/core/thumbnail/mod.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ThumbnailResult {
|
||||
pub uuid: String,
|
||||
pub count: usize,
|
||||
pub files: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct ThumbnailExtractor {
|
||||
output_dir: PathBuf,
|
||||
count: u32,
|
||||
}
|
||||
|
||||
impl ThumbnailExtractor {
|
||||
pub fn new(output_dir: PathBuf, count: u32) -> Self {
|
||||
Self { output_dir, count }
|
||||
}
|
||||
|
||||
pub fn extract(&self, video_path: &str, uuid: &str) -> Result<ThumbnailResult> {
|
||||
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("scripts")
|
||||
.join("thumbnail_extractor.py");
|
||||
|
||||
// 使用 venv 中的 Python,確保版本正確且隔離依賴
|
||||
let venv_python = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("venv")
|
||||
.join("bin")
|
||||
.join("python");
|
||||
|
||||
let output = Command::new(venv_python)
|
||||
.arg(script_path)
|
||||
.arg(video_path)
|
||||
.arg(uuid)
|
||||
.arg("-o")
|
||||
.arg(&self.output_dir)
|
||||
.arg("-c")
|
||||
.arg(self.count.to_string())
|
||||
.output()
|
||||
.context("Failed to run thumbnail extractor")?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
anyhow::bail!("Thumbnail extraction failed: {}", stderr);
|
||||
}
|
||||
|
||||
let json_str = String::from_utf8_lossy(&output.stdout);
|
||||
let result: ThumbnailResult =
|
||||
serde_json::from_str(&json_str).context("Failed to parse thumbnail result")?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn get_or_create(&self, video_path: &str, uuid: &str) -> Result<ThumbnailResult> {
|
||||
let thumb_dir = self.output_dir.join(uuid);
|
||||
|
||||
// Check if thumbnails already exist
|
||||
if thumb_dir.exists() {
|
||||
let files: Vec<String> = (0..self.count)
|
||||
.map(|i| thumb_dir.join(format!("thumb_{:03}.jpg", i)))
|
||||
.filter(|p| p.exists())
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.collect();
|
||||
|
||||
if files.len() as u32 == self.count {
|
||||
return Ok(ThumbnailResult {
|
||||
uuid: uuid.to_string(),
|
||||
count: files.len(),
|
||||
files,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Extract new thumbnails
|
||||
self.extract(video_path, uuid)
|
||||
}
|
||||
|
||||
pub fn get_thumbnails(&self, uuid: &str) -> Option<Vec<String>> {
|
||||
let thumb_dir = self.output_dir.join(uuid);
|
||||
|
||||
if !thumb_dir.exists() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let files: Vec<String> = (0..10)
|
||||
.map(|i| thumb_dir.join(format!("thumb_{:03}.jpg", i)))
|
||||
.filter(|p| p.exists())
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.collect();
|
||||
|
||||
if files.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(files)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cleanup(&self, uuid: &str) -> Result<()> {
|
||||
let thumb_dir = self.output_dir.join(uuid);
|
||||
if thumb_dir.exists() {
|
||||
std::fs::remove_dir_all(&thumb_dir).context("Failed to remove thumbnail directory")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user