Core modules: - probe.rs: ffprobe execution logic - parser.rs: JSON parsing logic - output.rs: Output formatting - lib.rs: Library interface - main.rs: CLI entry point Features: - Extract video metadata using ffprobe - Parse video/audio/subtitle streams - Save to JSON file - Console summary output Documentation: - Added QUICKSTART.md - Added ENVIRONMENT_SETUP_REPORT.md
320 lines
7.0 KiB
Bash
Executable File
320 lines
7.0 KiB
Bash
Executable File
#!/bin/bash
|
||
# 快速启动脚本 - 创建 video_probe Rust 项目
|
||
|
||
set -e
|
||
|
||
# 设置 PATH(macOS with Homebrew)
|
||
export PATH="/opt/homebrew/bin:$PATH"
|
||
|
||
echo "======================================"
|
||
echo "Video Probe (Rust) - 项目初始化"
|
||
echo "======================================"
|
||
echo ""
|
||
|
||
# 检查 Rust 是否已安装
|
||
if ! command -v rustc &> /dev/null; then
|
||
echo "❌ Rust 未安装"
|
||
echo ""
|
||
echo "请先安装 Rust:"
|
||
echo " curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
|
||
exit 1
|
||
fi
|
||
|
||
echo "✓ Rust 已安装: $(rustc --version)"
|
||
echo "✓ Cargo 已安装: $(cargo --version)"
|
||
echo ""
|
||
|
||
# 检查 ffprobe
|
||
if ! command -v ffprobe &> /dev/null; then
|
||
echo "⚠️ 警告: ffprobe 未找到"
|
||
echo " macOS: brew install ffmpeg"
|
||
echo " Linux: sudo apt-get install ffmpeg"
|
||
echo " Windows: 从 https://ffmpeg.org 下载"
|
||
echo ""
|
||
fi
|
||
|
||
# 创建项目
|
||
PROJECT_NAME="video_probe"
|
||
|
||
echo "1. 创建 Cargo 项目..."
|
||
if [ -d "$PROJECT_NAME" ]; then
|
||
echo " 目录已存在: $PROJECT_NAME"
|
||
read -p " 删除并重新创建? (y/N): " confirm
|
||
if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
|
||
rm -rf "$PROJECT_NAME"
|
||
else
|
||
echo " 取消操作"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
cargo new "$PROJECT_NAME"
|
||
cd "$PROJECT_NAME"
|
||
echo " ✓ 项目创建成功: $PROJECT_NAME/"
|
||
echo ""
|
||
|
||
# 创建目录结构
|
||
echo "2. 创建目录结构..."
|
||
mkdir -p src tests docs tests/fixtures
|
||
touch src/{lib.rs,probe.rs,parser.rs,metadata.rs,output.rs,error.rs}
|
||
touch tests/integration_test.rs
|
||
touch docs/{USAGE.md,DEVELOPMENT.md}
|
||
echo " ✓ 目录结构创建完成"
|
||
echo ""
|
||
|
||
# 更新 Cargo.toml
|
||
echo "3. 配置 Cargo.toml..."
|
||
cat > Cargo.toml << 'EOF'
|
||
[package]
|
||
name = "video_probe"
|
||
version = "0.1.0"
|
||
edition = "2021"
|
||
authors = ["Your Name <your.email@example.com>"]
|
||
description = "Extract video metadata using ffprobe"
|
||
license = "MIT"
|
||
repository = "https://gitea.example.com/yourname/video_probe"
|
||
keywords = ["video", "metadata", "ffprobe", "ffmpeg"]
|
||
categories = ["command-line-utilities", "multimedia"]
|
||
|
||
[dependencies]
|
||
serde = { version = "1.0", features = ["derive"] }
|
||
serde_json = "1.0"
|
||
chrono = { version = "0.4", features = ["serde"] }
|
||
anyhow = "1.0"
|
||
thiserror = "1.0"
|
||
clap = { version = "4.0", features = ["derive"] }
|
||
|
||
[dev-dependencies]
|
||
tempfile = "3.8"
|
||
|
||
[[bin]]
|
||
name = "video_probe"
|
||
path = "src/main.rs"
|
||
EOF
|
||
echo " ✓ Cargo.toml 已配置"
|
||
echo ""
|
||
|
||
# 创建 .gitignore
|
||
echo "4. 创建 .gitignore..."
|
||
cat > .gitignore << 'EOF'
|
||
/target/
|
||
**/*.rs.bk
|
||
*.pdb
|
||
Cargo.lock
|
||
*.probe.json
|
||
*.mp4
|
||
*.avi
|
||
*.mov
|
||
*.mkv
|
||
.DS_Store
|
||
.vscode/
|
||
.idea/
|
||
EOF
|
||
echo " ✓ .gitignore 已创建"
|
||
echo ""
|
||
|
||
# 创建基础代码模板
|
||
echo "5. 创建基础代码模板..."
|
||
|
||
# src/metadata.rs
|
||
cat > src/metadata.rs << 'EOF'
|
||
use serde::{Deserialize, Serialize};
|
||
use chrono::{DateTime, Utc};
|
||
|
||
#[derive(Debug, Serialize, Deserialize)]
|
||
pub struct VideoMetadata {
|
||
pub video_path: String,
|
||
pub probed_at: DateTime<Utc>,
|
||
pub format: FormatInfo,
|
||
pub video_stream: Option<VideoStream>,
|
||
pub audio_streams: Vec<AudioStream>,
|
||
pub subtitle_streams: Vec<SubtitleStream>,
|
||
pub other_streams: Vec<OtherStream>,
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||
pub struct FormatInfo {
|
||
pub filename: Option<String>,
|
||
pub format_name: Option<String>,
|
||
pub format_long_name: Option<String>,
|
||
pub duration: f64,
|
||
pub size: u64,
|
||
pub bit_rate: u64,
|
||
pub probe_score: Option<i32>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub tags: Option<serde_json::Value>,
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize)]
|
||
pub struct VideoStream {
|
||
pub index: i32,
|
||
pub codec_name: Option<String>,
|
||
pub codec_long_name: Option<String>,
|
||
pub profile: Option<String>,
|
||
pub width: i32,
|
||
pub height: i32,
|
||
pub pix_fmt: Option<String>,
|
||
pub r_frame_rate: Option<String>,
|
||
pub avg_frame_rate: Option<String>,
|
||
pub bit_rate: Option<u64>,
|
||
pub duration: Option<f64>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub tags: Option<serde_json::Value>,
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize)]
|
||
pub struct AudioStream {
|
||
pub index: i32,
|
||
pub codec_name: Option<String>,
|
||
pub channels: i32,
|
||
pub sample_rate: Option<String>,
|
||
pub bit_rate: Option<u64>,
|
||
pub duration: Option<f64>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub tags: Option<serde_json::Value>,
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize)]
|
||
pub struct SubtitleStream {
|
||
pub index: i32,
|
||
pub codec_name: Option<String>,
|
||
pub language: Option<String>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub tags: Option<serde_json::Value>,
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize)]
|
||
pub struct OtherStream {
|
||
pub index: i32,
|
||
pub codec_type: String,
|
||
pub codec_name: Option<String>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub tags: Option<serde_json::Value>,
|
||
}
|
||
EOF
|
||
|
||
# src/error.rs
|
||
cat > src/error.rs << 'EOF'
|
||
use thiserror::Error;
|
||
|
||
#[derive(Debug, Error)]
|
||
pub enum ProbeError {
|
||
#[error("Video file not found: {0}")]
|
||
FileNotFound(String),
|
||
|
||
#[error("Failed to execute ffprobe: {0}")]
|
||
FfprobeExecution(#[from] std::io::Error),
|
||
|
||
#[error("Failed to parse ffprobe output: {0}")]
|
||
ParseError(#[from] serde_json::Error),
|
||
|
||
#[error("ffprobe returned non-zero exit code: {0}")]
|
||
FfprobeFailed(String),
|
||
|
||
#[error("No video stream found")]
|
||
NoVideoStream,
|
||
}
|
||
|
||
pub type Result<T> = std::result::Result<T, ProbeError>;
|
||
EOF
|
||
|
||
echo " ✓ 基础代码模板已创建"
|
||
echo ""
|
||
|
||
# 创建 README
|
||
echo "6. 创建 README.md..."
|
||
cat > README.md << 'EOF'
|
||
# video_probe (Rust)
|
||
|
||
Extract video metadata using ffprobe
|
||
|
||
## Installation
|
||
|
||
### From Source
|
||
|
||
```bash
|
||
git clone https://gitea.example.com/yourname/video_probe.git
|
||
cd video_probe
|
||
cargo install --path .
|
||
```
|
||
|
||
## Usage
|
||
|
||
```bash
|
||
video_probe video.mp4
|
||
```
|
||
|
||
Output: `video.probe.json`
|
||
|
||
## Features
|
||
|
||
- ✅ Fast and efficient (written in Rust)
|
||
- ✅ Cross-platform (Linux, macOS, Windows)
|
||
- ✅ Comprehensive metadata extraction
|
||
- ✅ JSON output format
|
||
- ✅ User-friendly console output
|
||
|
||
## Development
|
||
|
||
```bash
|
||
# Build
|
||
cargo build
|
||
|
||
# Run
|
||
cargo run -- video.mp4
|
||
|
||
# Test
|
||
cargo test
|
||
|
||
# Format code
|
||
cargo fmt
|
||
|
||
# Lint
|
||
cargo clippy
|
||
```
|
||
|
||
## Requirements
|
||
|
||
- Rust 1.70+
|
||
- ffprobe (from FFmpeg)
|
||
|
||
## License
|
||
|
||
MIT
|
||
EOF
|
||
echo " ✓ README.md 已创建"
|
||
echo ""
|
||
|
||
# 初始化 Git
|
||
echo "7. 初始化 Git 仓库..."
|
||
git init
|
||
git add .
|
||
git commit -m "Initial commit: video_probe Rust project"
|
||
echo " ✓ Git 仓库已初始化"
|
||
echo ""
|
||
|
||
# 构建项目
|
||
echo "8. 构建项目..."
|
||
cargo build
|
||
echo " ✓ 项目构建成功"
|
||
echo ""
|
||
|
||
# 完成
|
||
echo "======================================"
|
||
echo "✅ 项目初始化完成!"
|
||
echo "======================================"
|
||
echo ""
|
||
echo "项目位置: $(pwd)"
|
||
echo ""
|
||
echo "下一步:"
|
||
echo " 1. cd $PROJECT_NAME"
|
||
echo " 2. 实现核心功能(参考: VIDEO_PROBE_RUST_DEVELOPMENT.md)"
|
||
echo " 3. cargo run -- <video_path>"
|
||
echo " 4. cargo test"
|
||
echo ""
|
||
echo "创建 Gitea 仓库:"
|
||
echo " 1. 在 Gitea 创建新仓库: video_probe"
|
||
echo " 2. git remote add origin <your-gitea-url>"
|
||
echo " 3. git push -u origin main"
|
||
echo ""
|