Add ffmpeg.rs FFmpeg wrapper
This commit is contained in:
155
src/player/ffmpeg.rs
Normal file
155
src/player/ffmpeg.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
//! FFmpeg 封裝
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio, Child, ChildStdout};
|
||||
use std::io::{Read, BufReader};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VideoInfo {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub fps: f64,
|
||||
pub duration_ms: u64,
|
||||
pub frame_count: u64,
|
||||
pub codec: String,
|
||||
}
|
||||
|
||||
pub struct FFmpegDecoder {
|
||||
path: String,
|
||||
process: Option<Child>,
|
||||
stdout: Option<BufReader<ChildStdout>>,
|
||||
info: VideoInfo,
|
||||
}
|
||||
|
||||
impl FFmpegDecoder {
|
||||
pub fn new(path: &Path) -> Result<Self> {
|
||||
let path_str = path.to_string_lossy().to_string();
|
||||
let info = Self::probe(path)?;
|
||||
|
||||
Ok(Self {
|
||||
path: path_str,
|
||||
process: None,
|
||||
stdout: None,
|
||||
info,
|
||||
})
|
||||
}
|
||||
|
||||
fn probe(path: &Path) -> Result<VideoInfo> {
|
||||
let output = Command::new("ffprobe")
|
||||
.args([
|
||||
"-v", "quiet",
|
||||
"-print_format", "json",
|
||||
"-show_format",
|
||||
"-show_streams",
|
||||
path.to_str().unwrap_or(""),
|
||||
])
|
||||
.output()
|
||||
.context("Failed to run ffprobe")?;
|
||||
|
||||
let json: serde_json::Value = serde_json::from_slice(&output.stdout)
|
||||
.context("Failed to parse ffprobe output")?;
|
||||
|
||||
let video_stream = json["streams"]
|
||||
.as_array()
|
||||
.and_then(|streams| {
|
||||
streams.iter().find(|s| s["codec_type"] == "video")
|
||||
})
|
||||
.context("No video stream found")?;
|
||||
|
||||
let width = video_stream["width"].as_u64().unwrap_or(0) as u32;
|
||||
let height = video_stream["height"].as_u64().unwrap_or(0) as u32;
|
||||
|
||||
let fps_str = video_stream["r_frame_rate"].as_str().unwrap_or("30/1");
|
||||
let (num, den) = {
|
||||
let parts: Vec<&str> = fps_str.split('/').collect();
|
||||
if parts.len() == 2 {
|
||||
(parts[0].parse::<f64>().unwrap_or(30.0), parts[1].parse::<f64>().unwrap_or(1.0))
|
||||
} else {
|
||||
(fps_str.parse::<f64>().unwrap_or(30.0), 1.0)
|
||||
}
|
||||
};
|
||||
let fps = num / den;
|
||||
|
||||
let duration_str = json["format"]["duration"].as_str().unwrap_or("0");
|
||||
let duration_sec: f64 = duration_str.parse().unwrap_or(0.0);
|
||||
let duration_ms = (duration_sec * 1000.0) as u64;
|
||||
|
||||
let frame_count = (duration_sec * fps) as u64;
|
||||
let codec = video_stream["codec_name"].as_str().unwrap_or("unknown").to_string();
|
||||
|
||||
Ok(VideoInfo {
|
||||
width,
|
||||
height,
|
||||
fps,
|
||||
duration_ms,
|
||||
frame_count,
|
||||
codec,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_info(&self) -> VideoInfo {
|
||||
self.info.clone()
|
||||
}
|
||||
|
||||
pub fn start_decoding(&mut self, start_ms: u64) -> Result<()> {
|
||||
self.stop()?;
|
||||
|
||||
let start_sec = start_ms as f64 / 1000.0;
|
||||
|
||||
let mut child = Command::new("ffmpeg")
|
||||
.args([
|
||||
"-ss", &format!("{}", start_sec),
|
||||
"-i", &self.path,
|
||||
"-f", "rawvideo",
|
||||
"-pix_fmt", "rgb24",
|
||||
"-",
|
||||
])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.context("Failed to start ffmpeg")?;
|
||||
|
||||
let stdout = child.stdout.take()
|
||||
.context("Failed to capture stdout")?;
|
||||
|
||||
self.process = Some(child);
|
||||
self.stdout = Some(BufReader::new(stdout));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn seek(&mut self, timestamp_ms: u64) -> Result<()> {
|
||||
self.start_decoding(timestamp_ms)
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
if let Some(mut child) = self.process.take() {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
}
|
||||
self.stdout = None;
|
||||
}
|
||||
|
||||
pub fn read_frame(&mut self) -> Result<Option<Vec<u8>>> {
|
||||
let frame_size = (self.info.width * self.info.height * 3) as usize;
|
||||
let mut buffer = vec![0u8; frame_size];
|
||||
|
||||
if let Some(ref mut reader) = self.stdout {
|
||||
match reader.read_exact(&mut buffer) {
|
||||
Ok(_) => Ok(Some(buffer)),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FFmpegDecoder {
|
||||
fn drop(&mut self) {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user