Initial commit: Video analyzer with ffmpeg-next

This commit is contained in:
2026-02-25 17:06:09 +08:00
parent 96af1c7e18
commit 35574fcdb8
7 changed files with 590 additions and 0 deletions

135
src/main.rs Normal file
View File

@@ -0,0 +1,135 @@
use ffmpeg_next as ffmpeg;
use ffmpeg::format::{input, Pixel};
use ffmpeg::media::Type;
use ffmpeg::codec::{context::Context, decoder};
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
ffmpeg::init()?;
let input_path = "test.mp4";
if !Path::new(input_path).exists() {
eprintln!("錯誤:找不到檔案 '{}'", input_path);
eprintln!("請將一個視頻文件重命名為 'test.mp4' 並放在專案根目錄。");
return Ok(());
}
let ictx = input(&input_path)?;
println!("=== 檔案基本資訊 ===");
println!("格式名稱 (Format): {}", ictx.format().name());
println!("長描述: {}", ictx.format().description());
let duration_sec = ictx.duration() as f64 / ffmpeg::ffi::AV_TIME_BASE as f64;
println!("總長度 (Duration): {:.2}", duration_sec);
println!("\n=== 串流資訊 ===");
for (index, stream) in ictx.streams().enumerate() {
let codec_params = stream.parameters();
let codec_id = codec_params.id();
let media_type = codec_params.medium();
println!("[串流 #{}]", index);
println!(" 類型: {:?}", media_type);
println!(" 編碼器 ID: {:?}", codec_id);
if let Some(codec_descriptor) = decoder::find(codec_id) {
let mut context = Context::new_with_codec(codec_descriptor);
if let Err(e) = context.set_parameters(codec_params) {
eprintln!(" 警告:無法設置參數: {}", e);
continue;
}
unsafe {
let ptr = context.as_mut_ptr();
match media_type {
Type::Video => {
let width = (*ptr).width;
let height = (*ptr).height;
let pix_fmt_val = (*ptr).pix_fmt;
let format = Pixel::from(pix_fmt_val);
let frame_rate = stream.avg_frame_rate();
let fps = if frame_rate.numerator() != 0 {
frame_rate.numerator() as f64 / frame_rate.denominator() as f64
} else {
0.0
};
println!(" 解析度: {}x{}", width, height);
println!(" 像素格式: {:?}", format);
println!(" 幀率: {:.2} fps", fps);
if let Some(codec_name) = context.codec() {
println!(" 編碼器名稱: {}", codec_name.name());
}
},
Type::Audio => {
let sample_rate = (*ptr).sample_rate;
// 【關鍵修復】新版本使用 ch_layout.nb_channels 獲取聲道數
// ch_layout 是一個 AVChannelLayout 結構體
let channels = (*ptr).ch_layout.nb_channels;
let sample_fmt_val = (*ptr).sample_fmt;
let format = ffmpeg::format::Sample::from(sample_fmt_val);
println!(" 採樣率: {} Hz", sample_rate);
println!(" 聲道數: {}", channels);
println!(" 音訊格式: {:?}", format);
// 可選:打印聲道佈局描述 (例如 "stereo", "5.1")
// 需要引入 ffi 來調用 av_channel_layout_describe
/*
let mut buf = vec![0u8; 1024];
let ret = ffmpeg::ffi::av_channel_layout_describe(
&(*ptr).ch_layout,
buf.as_mut_ptr() as *mut i8,
buf.len() as i32
);
if ret >= 0 {
if let Ok(layout_str) = std::ffi::CStr::from_bytes_until_nul(&buf) {
println!(" 聲道佈局: {}", layout_str.to_string_lossy());
}
}
*/
if let Some(codec_name) = context.codec() {
println!(" 編碼器名稱: {}", codec_name.name());
}
},
Type::Subtitle => {
println!(" (字幕串流)");
if let Some(codec_name) = context.codec() {
println!(" 編碼器名稱: {}", codec_name.name());
}
},
_ => {
println!(" (其他類型串流)");
if let Some(codec_name) = context.codec() {
println!(" 編碼器名稱: {}", codec_name.name());
}
}
}
} // end unsafe
} else {
println!(" (未找到對應的解碼器)");
}
// 輸出 Metadata
for (key, value) in stream.metadata().iter() {
println!(" Metadata [{}]: {}", key, value);
}
}
println!("\n=== 檔案 Metadata ===");
for (key, value) in ictx.metadata().iter() {
println!("{}: {}", key, value);
}
Ok(())
}

135
src/main.rs.bak Normal file
View File

@@ -0,0 +1,135 @@
use ffmpeg_next as ffmpeg;
use ffmpeg::format::{input, Pixel};
use ffmpeg::media::Type;
use ffmpeg::codec::{context::Context, decoder};
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
ffmpeg::init()?;
let input_path = "test.mp4";
if !Path::new(input_path).exists() {
eprintln!("錯誤:找不到檔案 '{}'", input_path);
eprintln!("請將一個視頻文件重命名為 'test.mp4' 並放在專案根目錄。");
return Ok(());
}
let mut ictx = input(&input_path)?;
println!("=== 檔案基本資訊 ===");
println!("格式名稱 (Format): {}", ictx.format().name());
println!("長描述: {}", ictx.format().description());
let duration_sec = ictx.duration() as f64 / ffmpeg::ffi::AV_TIME_BASE as f64;
println!("總長度 (Duration): {:.2}", duration_sec);
println!("\n=== 串流資訊 ===");
for (index, stream) in ictx.streams().enumerate() {
let codec_params = stream.parameters();
let codec_id = codec_params.id();
let media_type = codec_params.medium();
println!("[串流 #{}]", index);
println!(" 類型: {:?}", media_type);
println!(" 編碼器 ID: {:?}", codec_id);
if let Some(codec_descriptor) = decoder::find(codec_id) {
let mut context = Context::new_with_codec(codec_descriptor);
if let Err(e) = context.set_parameters(codec_params) {
eprintln!(" 警告:無法設置參數: {}", e);
continue;
}
unsafe {
let ptr = context.as_mut_ptr();
match media_type {
Type::Video => {
let width = (*ptr).width;
let height = (*ptr).height;
let pix_fmt_val = (*ptr).pix_fmt;
let format = Pixel::from(pix_fmt_val);
let frame_rate = stream.avg_frame_rate();
let fps = if frame_rate.numerator() != 0 {
frame_rate.numerator() as f64 / frame_rate.denominator() as f64
} else {
0.0
};
println!(" 解析度: {}x{}", width, height);
println!(" 像素格式: {:?}", format);
println!(" 幀率: {:.2} fps", fps);
if let Some(codec_name) = context.codec() {
println!(" 編碼器名稱: {}", codec_name.name());
}
},
Type::Audio => {
let sample_rate = (*ptr).sample_rate;
// 【關鍵修復】新版本使用 ch_layout.nb_channels 獲取聲道數
// ch_layout 是一個 AVChannelLayout 結構體
let channels = (*ptr).ch_layout.nb_channels;
let sample_fmt_val = (*ptr).sample_fmt;
let format = ffmpeg::format::Sample::from(sample_fmt_val);
println!(" 採樣率: {} Hz", sample_rate);
println!(" 聲道數: {}", channels);
println!(" 音訊格式: {:?}", format);
// 可選:打印聲道佈局描述 (例如 "stereo", "5.1")
// 需要引入 ffi 來調用 av_channel_layout_describe
/*
let mut buf = vec![0u8; 1024];
let ret = ffmpeg::ffi::av_channel_layout_describe(
&(*ptr).ch_layout,
buf.as_mut_ptr() as *mut i8,
buf.len() as i32
);
if ret >= 0 {
if let Ok(layout_str) = std::ffi::CStr::from_bytes_until_nul(&buf) {
println!(" 聲道佈局: {}", layout_str.to_string_lossy());
}
}
*/
if let Some(codec_name) = context.codec() {
println!(" 編碼器名稱: {}", codec_name.name());
}
},
Type::Subtitle => {
println!(" (字幕串流)");
if let Some(codec_name) = context.codec() {
println!(" 編碼器名稱: {}", codec_name.name());
}
},
_ => {
println!(" (其他類型串流)");
if let Some(codec_name) = context.codec() {
println!(" 編碼器名稱: {}", codec_name.name());
}
}
}
} // end unsafe
} else {
println!(" (未找到對應的解碼器)");
}
// 輸出 Metadata
for (key, value) in stream.metadata().iter() {
println!(" Metadata [{}]: {}", key, value);
}
}
println!("\n=== 檔案 Metadata ===");
for (key, value) in ictx.metadata().iter() {
println!("{}: {}", key, value);
}
Ok(())
}