mixvideo-v2/apps/desktop/src-tauri/src/data/models/material.rs

297 lines
8.9 KiB
Rust

use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
/// 素材类型枚举
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum MaterialType {
Video,
Audio,
Image,
Document,
Other,
}
impl MaterialType {
pub fn from_extension(ext: &str) -> Self {
match ext.to_lowercase().as_str() {
"mp4" | "avi" | "mov" | "mkv" | "wmv" | "flv" | "webm" | "m4v" => MaterialType::Video,
"mp3" | "wav" | "flac" | "aac" | "ogg" | "wma" | "m4a" => MaterialType::Audio,
"jpg" | "jpeg" | "png" | "gif" | "bmp" | "tiff" | "webp" | "svg" => MaterialType::Image,
"pdf" | "doc" | "docx" | "txt" | "md" | "rtf" => MaterialType::Document,
_ => MaterialType::Other,
}
}
}
/// 素材处理状态枚举
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum ProcessingStatus {
Pending, // 待处理
Processing, // 处理中
Completed, // 处理完成
Failed, // 处理失败
Skipped, // 跳过(重复文件)
}
/// 视频元数据结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoMetadata {
pub duration: f64, // 时长(秒)
pub width: u32, // 宽度
pub height: u32, // 高度
pub fps: f64, // 帧率
pub bitrate: u64, // 比特率
pub codec: String, // 编码格式
pub format: String, // 容器格式
pub has_audio: bool, // 是否包含音频
pub audio_codec: Option<String>, // 音频编码格式
pub audio_bitrate: Option<u64>, // 音频比特率
pub audio_sample_rate: Option<u32>, // 音频采样率
}
/// 音频元数据结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioMetadata {
pub duration: f64, // 时长(秒)
pub bitrate: u64, // 比特率
pub codec: String, // 编码格式
pub sample_rate: u32, // 采样率
pub channels: u32, // 声道数
pub format: String, // 音频格式
}
/// 图片元数据结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageMetadata {
pub width: u32, // 宽度
pub height: u32, // 高度
pub format: String, // 图片格式
pub color_space: Option<String>, // 色彩空间
pub has_alpha: bool, // 是否有透明通道
pub dpi: Option<u32>, // DPI
}
/// 素材元数据联合体
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MaterialMetadata {
Video(VideoMetadata),
Audio(AudioMetadata),
Image(ImageMetadata),
None,
}
/// 场景检测结果
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneDetection {
pub scenes: Vec<SceneSegment>,
pub total_scenes: u32,
pub detection_method: String,
pub threshold: f64,
}
/// 场景片段
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneSegment {
pub start_time: f64, // 开始时间(秒)
pub end_time: f64, // 结束时间(秒)
pub duration: f64, // 片段时长(秒)
pub scene_id: u32, // 场景ID
pub confidence: f64, // 检测置信度
}
/// 素材实体模型
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Material {
pub id: String,
pub project_id: String,
pub name: String,
pub original_path: String,
pub file_size: u64,
pub md5_hash: String,
pub material_type: MaterialType,
pub processing_status: ProcessingStatus,
pub metadata: MaterialMetadata,
pub scene_detection: Option<SceneDetection>,
pub segments: Vec<MaterialSegment>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub processed_at: Option<DateTime<Utc>>,
pub error_message: Option<String>,
}
/// 素材片段(切分后的片段)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MaterialSegment {
pub id: String,
pub material_id: String,
pub segment_index: u32,
pub start_time: f64,
pub end_time: f64,
pub duration: f64,
pub file_path: String,
pub file_size: u64,
pub created_at: DateTime<Utc>,
}
/// 创建素材请求
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateMaterialRequest {
pub project_id: String,
pub file_paths: Vec<String>,
pub auto_process: bool,
pub max_segment_duration: Option<f64>, // 最大片段时长(秒)
}
/// 素材处理配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MaterialProcessingConfig {
pub max_segment_duration: f64, // 最大片段时长(秒)
pub scene_detection_threshold: f64, // 场景检测阈值
pub enable_scene_detection: bool, // 是否启用场景检测
pub video_quality: String, // 视频质量设置
pub audio_quality: String, // 音频质量设置
pub output_format: String, // 输出格式
pub auto_process: Option<bool>, // 是否自动处理
}
impl Default for MaterialProcessingConfig {
fn default() -> Self {
Self {
max_segment_duration: 300.0, // 5分钟
scene_detection_threshold: 0.3,
enable_scene_detection: true,
video_quality: "medium".to_string(),
audio_quality: "medium".to_string(),
output_format: "mp4".to_string(),
auto_process: Some(true),
}
}
}
/// 素材导入结果
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MaterialImportResult {
pub total_files: u32,
pub processed_files: u32,
pub skipped_files: u32,
pub failed_files: u32,
pub created_materials: Vec<Material>,
pub errors: Vec<String>,
pub processing_time: f64, // 处理时间(秒)
}
/// 素材统计信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MaterialStats {
pub total_materials: u32,
pub video_count: u32,
pub audio_count: u32,
pub image_count: u32,
pub other_count: u32,
pub total_size: u64,
pub total_duration: f64, // 总时长(秒)
pub processing_status_counts: std::collections::HashMap<String, u32>,
}
impl Material {
/// 创建新的素材实例
pub fn new(
project_id: String,
name: String,
original_path: String,
file_size: u64,
md5_hash: String,
material_type: MaterialType,
) -> Self {
let now = Utc::now();
Self {
id: uuid::Uuid::new_v4().to_string(),
project_id,
name,
original_path,
file_size,
md5_hash,
material_type,
processing_status: ProcessingStatus::Pending,
metadata: MaterialMetadata::None,
scene_detection: None,
segments: Vec::new(),
created_at: now,
updated_at: now,
processed_at: None,
error_message: None,
}
}
/// 更新处理状态
pub fn update_status(&mut self, status: ProcessingStatus, error_message: Option<String>) {
self.processing_status = status;
self.error_message = error_message;
self.updated_at = Utc::now();
if matches!(status, ProcessingStatus::Completed | ProcessingStatus::Failed) {
self.processed_at = Some(Utc::now());
}
}
/// 设置元数据
pub fn set_metadata(&mut self, metadata: MaterialMetadata) {
self.metadata = metadata;
self.updated_at = Utc::now();
}
/// 添加场景检测结果
pub fn set_scene_detection(&mut self, scene_detection: SceneDetection) {
self.scene_detection = Some(scene_detection);
self.updated_at = Utc::now();
}
/// 添加片段
pub fn add_segment(&mut self, segment: MaterialSegment) {
self.segments.push(segment);
self.updated_at = Utc::now();
}
/// 获取总时长
pub fn get_duration(&self) -> Option<f64> {
match &self.metadata {
MaterialMetadata::Video(video) => Some(video.duration),
MaterialMetadata::Audio(audio) => Some(audio.duration),
_ => None,
}
}
/// 检查是否需要切分
pub fn needs_segmentation(&self, max_duration: f64) -> bool {
if let Some(duration) = self.get_duration() {
duration > max_duration
} else {
false
}
}
}
impl MaterialSegment {
/// 创建新的素材片段
pub fn new(
material_id: String,
segment_index: u32,
start_time: f64,
end_time: f64,
file_path: String,
file_size: u64,
) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
material_id,
segment_index,
start_time,
end_time,
duration: end_time - start_time,
file_path,
file_size,
created_at: Utc::now(),
}
}
}