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

11
.cargo/config.toml Normal file
View File

@@ -0,0 +1,11 @@
[target.aarch64-apple-darwin]
rustflags = [
"-L", "/opt/homebrew/lib",
"-l", "c++"
]
[target.x86_64-apple-darwin]
rustflags = [
"-L", "/usr/local/lib",
"-l", "c++"
]

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/target/
*.rs.bk
.DS_Store
test.mp4

279
Cargo.lock generated Normal file
View File

@@ -0,0 +1,279 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "bindgen"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "cc"
version = "1.2.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "ffmpeg-next"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d658424d233cbd993a972dd73a66ca733acd12a494c68995c9ac32ae1fe65b40"
dependencies = [
"bitflags",
"ffmpeg-sys-next",
"libc",
]
[[package]]
name = "ffmpeg-sys-next"
version = "8.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bca20aa4ee774fe384c2490096c122b0b23cf524a9910add0686691003d797b"
dependencies = [
"bindgen",
"cc",
"libc",
"num_cpus",
"pkg-config",
"vcpkg",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "libloading"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link",
]
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num_cpus"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "video_analyzer"
version = "0.1.0"
dependencies = [
"ffmpeg-next",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"

12
Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "video_analyzer"
version = "0.1.0"
edition = "2024"
[dependencies]
# 啟用 "static" feature 進行靜態編譯
# 注意:版本號可能需要根據實際情況調整,目前推薦使用 7.0 或最新版本
ffmpeg-next = { version = "8.0", features = ["static"] }
# [可選] 如果你希望進一步減小體積或指定編譯選項,可以添加 build-dependencies
# 但通常 ffmpeg-next 的 static feature 會自動處理大部分事情

View File

@@ -0,0 +1,14 @@
# Video Analyzer
A Rust-based tool to analyze video file metadata using FFmpeg.
## Features
- Extract video format, resolution, duration
- Analyze audio streams (sample rate, channels)
- Static/Dynamic linking support
## Usage
```bash
cargo build --release
./target/release/video_analyzer <video_file>
```

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(())
}