297 lines
8.9 KiB
Rust
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(),
|
|
}
|
|
}
|
|
}
|