Initial implementation of video_probe (Rust)
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
This commit is contained in:
319
init_video_probe_rust.sh
Executable file
319
init_video_probe_rust.sh
Executable file
@@ -0,0 +1,319 @@
|
||||
#!/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 ""
|
||||
Reference in New Issue
Block a user