use std::process::{Child, ChildStdin, ChildStdout};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
pub struct PersistentProcess {
pub child: Child,
pub stdin: ChildStdin,
pub stdout: ChildStdout,
pub video_path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThumbnailOptions {
pub time: f64,
pub width: Option<u32>,
pub height: Option<u32>,
pub output_path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FrameExtractOptions {
pub output_dir: String,
pub filename_pattern: Option<String>,
pub fps: Option<f64>,
pub start: Option<f64>,
pub duration: Option<f64>,
pub width: Option<u32>,
pub height: Option<u32>,
pub format: String,
pub quality: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PreviewQuality {
Excellent,
High,
Medium,
Low,
VeryLow,
}
impl PreviewQuality {
pub fn as_q_value(&self) -> u32 {
match self {
PreviewQuality::Excellent => 2,
PreviewQuality::High => 6,
PreviewQuality::Medium => 10,
PreviewQuality::Low => 15,
PreviewQuality::VeryLow => 20,
}
}
}
#[derive(Debug, Clone)]
pub struct BasicMetadata {
pub duration: f64,
pub width: f64,
pub height: f64,
pub fps: f64,
pub codec: String,
pub bitrate: u64,
}
#[derive(Debug, Clone)]
pub struct ImageMetadata {
pub width: f64,
pub height: f64,
pub fps: f64,
pub duration: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioMetadata {
pub duration: f64, pub sample_rate: u32, pub channels: u32, pub codec: String, pub file_size: u64, pub bit_depth: Option<u32>, pub bitrate: Option<u64>, pub sample_format: Option<String>, pub channel_layout: Option<String>, pub title: Option<String>, pub artist: Option<String>, pub album: Option<String>, pub genre: Option<String>, pub year: Option<u32>, pub track_number: Option<u32>, }
impl AudioMetadata {
pub fn from_json(json: &serde_json::Value, path: &str) -> Result<Self, String> {
let streams = json["streams"].as_array().ok_or("No streams found")?;
let audio_stream = streams.iter().find(|s| s["codec_type"].as_str() == Some("audio")).ok_or("No audio stream found")?;
let format = &json["format"];
let duration = format["duration"]
.as_str()
.and_then(|s| s.parse::<f64>().ok())
.or_else(|| format["duration"].as_f64())
.or_else(|| audio_stream["duration"].as_str().and_then(|s| s.parse::<f64>().ok()))
.or_else(|| audio_stream["duration"].as_f64())
.unwrap_or(0.0);
let sample_rate = audio_stream["sample_rate"]
.as_str()
.and_then(|s| s.parse::<u32>().ok())
.or_else(|| audio_stream["sample_rate"].as_u64().map(|v| v as u32))
.unwrap_or(0);
let channels = audio_stream["channels"].as_u64().map(|v| v as u32).unwrap_or(0);
let codec = audio_stream["codec_name"].as_str().unwrap_or("unknown").to_string();
let file_size = format["size"].as_str().and_then(|s| s.parse::<u64>().ok()).or_else(|| format["size"].as_u64()).unwrap_or(0);
let bit_depth = audio_stream["bits_per_sample"]
.as_str()
.and_then(|s| s.parse::<u32>().ok())
.or_else(|| audio_stream["bits_per_sample"].as_u64().map(|v| v as u32));
let bitrate = format["bit_rate"]
.as_str()
.and_then(|s| s.parse::<u64>().ok())
.or_else(|| format["bit_rate"].as_u64())
.or_else(|| audio_stream["bit_rate"].as_str().and_then(|s| s.parse::<u64>().ok()))
.or_else(|| audio_stream["bit_rate"].as_u64());
let sample_format = audio_stream["sample_fmt"].as_str().map(|s| s.to_string());
let channel_layout = audio_stream["channel_layout"].as_str().map(|s| s.to_string());
let tags = format["tags"].as_object();
let title = tags.and_then(|t| t.get("TITLE")).or_else(|| tags.and_then(|t| t.get("title"))).and_then(|v| v.as_str()).map(|s| s.to_string());
let artist =
tags.and_then(|t| t.get("ARTIST")).or_else(|| tags.and_then(|t| t.get("artist"))).and_then(|v| v.as_str()).map(|s| s.to_string());
let album = tags.and_then(|t| t.get("ALBUM")).or_else(|| tags.and_then(|t| t.get("album"))).and_then(|v| v.as_str()).map(|s| s.to_string());
let genre = tags.and_then(|t| t.get("GENRE")).or_else(|| tags.and_then(|t| t.get("genre"))).and_then(|v| v.as_str()).map(|s| s.to_string());
let year = tags
.and_then(|t| t.get("DATE"))
.or_else(|| tags.and_then(|t| t.get("date")))
.or_else(|| tags.and_then(|t| t.get("YEAR")))
.or_else(|| tags.and_then(|t| t.get("year")))
.and_then(|v| v.as_str())
.and_then(|s| s.parse::<u32>().ok());
let track_number = tags
.and_then(|t| t.get("TRACK"))
.or_else(|| tags.and_then(|t| t.get("track")))
.and_then(|v| v.as_str())
.and_then(|s| s.parse::<u32>().ok());
Ok(Self {
duration,
sample_rate,
channels,
codec,
file_size,
bit_depth,
bitrate,
sample_format,
channel_layout,
title,
artist,
album,
genre,
year,
track_number,
})
}
pub fn to_json(&self) -> Result<serde_json::Value, String> {
serde_json::to_value(self).map_err(|e| format!("Failed to serialize audio metadata: {}", e))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoMetadata {
pub width: f64, pub height: f64, pub duration: f64, pub fps: f64, pub bitrate: u64, pub codec: String, pub resource_path: String, pub aspect_ratio: Option<String>, pub pixel_format: Option<String>, pub color_space: Option<String>, pub bit_depth: Option<u32>, pub frame_count: Option<u64>, pub keyframe_count: Option<u64>, pub has_audio: bool, pub audio_codec: Option<String>, pub audio_sample_rate: Option<u32>, pub audio_channels: Option<u32>, pub audio_bitrate: Option<u64>, pub file_size: Option<u64>, pub container_format: Option<String>, pub creation_time: Option<String>, pub tags: Option<serde_json::Value>, pub video_stream_index: Option<u32>, pub audio_stream_index: Option<u32>, pub track_start_time: f64, pub track_end_time: f64, pub internal_start_time: f64, pub internal_end_time: f64, pub track_id: String, pub track_block_id: String, pub visible: bool, pub resource_frames: Option<String>, }
impl VideoMetadata {
pub fn from_json(json: &serde_json::Value, path: &str) -> Result<Self, String> {
let streams = json["streams"].as_array().ok_or("No streams found")?;
let video_stream = streams.iter().find(|s| s["codec_type"].as_str() == Some("video")).ok_or("No video stream found")?;
let audio_stream = streams.iter().find(|s| s["codec_type"].as_str() == Some("audio"));
let format = &json["format"];
let width = video_stream["width"].as_f64().unwrap_or(0.0) as f64;
let height = video_stream["height"].as_f64().unwrap_or(0.0) as f64;
let fps_str = video_stream["r_frame_rate"].as_str().unwrap_or("0/0");
let fps = parse_fraction(fps_str).unwrap_or(0.0);
let codec = video_stream["codec_name"].as_str().unwrap_or("unknown").to_string();
let frame_count = video_stream["nb_frames"].as_str().and_then(|s| s.parse::<u64>().ok());
let file_size = format["size"].as_str().and_then(|s| s.parse::<u64>().ok());
let duration_from_frames = frame_count.and_then(|fc| if fps > 0.0 { Some(fc as f64 / fps) } else { None }).unwrap_or(0.0);
let duration = format["duration"]
.as_str()
.and_then(|s| s.parse::<f64>().ok())
.or_else(|| format["duration"].as_f64())
.or_else(|| video_stream["duration"].as_str().and_then(|s| s.parse::<f64>().ok()))
.or_else(|| video_stream["duration"].as_f64())
.or_else(|| if duration_from_frames > 0.0 { Some(duration_from_frames) } else { None })
.unwrap_or(0.0);
let bitrate = format["bit_rate"]
.as_str()
.and_then(|s| s.parse::<u64>().ok())
.or_else(|| format["bit_rate"].as_u64())
.or_else(|| video_stream["bit_rate"].as_str().and_then(|s| s.parse::<u64>().ok()))
.or_else(|| video_stream["bit_rate"].as_u64())
.unwrap_or(0);
let mut final_bitrate = bitrate;
if final_bitrate == 0 {
if let Some(size) = file_size {
let dur = if duration > 0.0 { duration } else { duration_from_frames };
if dur > 0.0 {
final_bitrate = ((size as f64 * 8.0) / dur) as u64;
}
}
}
let aspect_ratio = if width > 0.0 && height > 0.0 {
let gcd = gcd(width as u64, height as u64);
Some(format!("{}:{}", width / gcd as f64, height / gcd as f64))
} else {
None
};
let pixel_format = video_stream["pix_fmt"].as_str().map(|s| s.to_string());
let color_space = video_stream["color_space"].as_str().map(|s| s.to_string());
let bit_depth = video_stream["bit_depth"].as_u64().map(|v| v as u32);
let video_index = video_stream["index"].as_u64().map(|v| v as u32);
let audio_index = audio_stream.and_then(|s| s["index"].as_u64().map(|v| v as u32));
let container_format = format["format_name"].as_str().map(|s| s.to_string());
let creation_time = format["creation_time"].as_str().map(|s| s.to_string());
let tags = if let Some(tags_obj) = format["tags"].as_object() { Some(serde_json::Value::Object(tags_obj.clone())) } else { None };
let has_audio = audio_stream.is_some();
let audio_codec = audio_stream.and_then(|s| s["codec_name"].as_str()).map(|s| s.to_string());
let audio_sample_rate = audio_stream.and_then(|s| s["sample_rate"].as_str()).and_then(|s| s.parse::<u32>().ok());
let audio_channels = audio_stream.and_then(|s| s["channels"].as_u64()).map(|v| v as u32);
let audio_bitrate = audio_stream.and_then(|s| s["bit_rate"].as_str()).and_then(|s| s.parse::<u64>().ok());
Ok(Self {
width,
height,
duration: if duration > 0.0 { duration } else { duration_from_frames },
fps,
bitrate: final_bitrate,
codec,
resource_path: path.to_string(),
aspect_ratio,
pixel_format,
color_space,
bit_depth,
frame_count,
keyframe_count: None,
has_audio,
audio_codec,
audio_sample_rate,
audio_channels,
audio_bitrate,
file_size,
container_format,
creation_time,
tags,
video_stream_index: video_index,
audio_stream_index: audio_index,
track_start_time: 0.0,
track_end_time: 0.0,
internal_start_time: 0.0,
internal_end_time: duration,
track_id: Uuid::new_v4().to_string(),
track_block_id: Uuid::new_v4().to_string(),
visible: true,
resource_frames: None,
})
}
pub fn to_json(&self) -> Result<serde_json::Value, String> {
serde_json::to_value(self).map_err(|e| format!("Failed to serialize video metadata: {}", e))
}
}
fn gcd(a: u64, b: u64) -> u64 {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
pub fn parse_fraction(s: &str) -> Option<f64> {
if s.contains('/') {
let parts: Vec<&str> = s.split('/').collect();
if parts.len() == 2 {
let num = parts[0].parse::<f64>().ok()?;
let den = parts[1].parse::<f64>().ok()?;
if den != 0.0 {
return Some(num / den);
}
}
None
} else {
s.parse::<f64>().ok()
}
}