Add parser.rs
This commit is contained in:
167
src/parser.rs
Normal file
167
src/parser.rs
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
use chrono::Utc;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::path::Path;
|
||||||
|
use crate::metadata::*;
|
||||||
|
use crate::error::{ProbeError, Result};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct FfprobeOutput {
|
||||||
|
format: Option<serde_json::Value>,
|
||||||
|
streams: Option<Vec<serde_json::Value>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse ffprobe JSON output into VideoMetadata
|
||||||
|
pub fn parse_ffprobe_json(json_str: &str, video_path: &str) -> Result<VideoMetadata> {
|
||||||
|
let ffprobe_data: FfprobeOutput = serde_json::from_str(json_str)?;
|
||||||
|
|
||||||
|
let canonical_path = Path::new(video_path)
|
||||||
|
.canonicalize()
|
||||||
|
.map_err(|e| ProbeError::FileNotFound(format!("{}: {}", video_path, e)))?;
|
||||||
|
|
||||||
|
let mut metadata = VideoMetadata {
|
||||||
|
video_path: canonical_path.to_string_lossy().to_string(),
|
||||||
|
probed_at: Utc::now(),
|
||||||
|
format: FormatInfo::default(),
|
||||||
|
video_stream: None,
|
||||||
|
audio_streams: Vec::new(),
|
||||||
|
subtitle_streams: Vec::new(),
|
||||||
|
other_streams: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse format
|
||||||
|
if let Some(fmt) = ffprobe_data.format {
|
||||||
|
metadata.format = parse_format(&fmt)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse streams
|
||||||
|
if let Some(streams) = ffprobe_data.streams {
|
||||||
|
for stream in streams {
|
||||||
|
let codec_type = stream.get("codec_type")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("");
|
||||||
|
|
||||||
|
match codec_type {
|
||||||
|
"video" if metadata.video_stream.is_none() => {
|
||||||
|
metadata.video_stream = Some(parse_video_stream(&stream)?);
|
||||||
|
}
|
||||||
|
"audio" => {
|
||||||
|
metadata.audio_streams.push(parse_audio_stream(&stream)?);
|
||||||
|
}
|
||||||
|
"subtitle" => {
|
||||||
|
metadata.subtitle_streams.push(parse_subtitle_stream(&stream)?);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
metadata.other_streams.push(parse_other_stream(&stream)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_format(fmt: &serde_json::Value) -> Result<FormatInfo> {
|
||||||
|
Ok(FormatInfo {
|
||||||
|
filename: fmt.get("filename").and_then(|v| v.as_str()).map(String::from),
|
||||||
|
format_name: fmt.get("format_name").and_then(|v| v.as_str()).map(String::from),
|
||||||
|
format_long_name: fmt.get("format_long_name").and_then(|v| v.as_str()).map(String::from),
|
||||||
|
duration: fmt.get("duration")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.and_then(|s| s.parse().ok())
|
||||||
|
.unwrap_or(0.0),
|
||||||
|
size: fmt.get("size")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.and_then(|s| s.parse().ok())
|
||||||
|
.unwrap_or(0),
|
||||||
|
bit_rate: fmt.get("bit_rate")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.and_then(|s| s.parse().ok())
|
||||||
|
.unwrap_or(0),
|
||||||
|
probe_score: fmt.get("probe_score")
|
||||||
|
.and_then(|v| v.as_i64())
|
||||||
|
.map(|i| i as i32),
|
||||||
|
tags: fmt.get("tags").cloned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_video_stream(stream: &serde_json::Value) -> Result<VideoStream> {
|
||||||
|
Ok(VideoStream {
|
||||||
|
index: stream.get("index")
|
||||||
|
.and_then(|v| v.as_i64())
|
||||||
|
.map(|i| i as i32)
|
||||||
|
.unwrap_or(0),
|
||||||
|
codec_name: stream.get("codec_name").and_then(|v| v.as_str()).map(String::from),
|
||||||
|
codec_long_name: stream.get("codec_long_name").and_then(|v| v.as_str()).map(String::from),
|
||||||
|
profile: stream.get("profile").and_then(|v| v.as_str()).map(String::from),
|
||||||
|
width: stream.get("width")
|
||||||
|
.and_then(|v| v.as_i64())
|
||||||
|
.map(|i| i as i32)
|
||||||
|
.unwrap_or(0),
|
||||||
|
height: stream.get("height")
|
||||||
|
.and_then(|v| v.as_i64())
|
||||||
|
.map(|i| i as i32)
|
||||||
|
.unwrap_or(0),
|
||||||
|
pix_fmt: stream.get("pix_fmt").and_then(|v| v.as_str()).map(String::from),
|
||||||
|
r_frame_rate: stream.get("r_frame_rate").and_then(|v| v.as_str()).map(String::from),
|
||||||
|
avg_frame_rate: stream.get("avg_frame_rate").and_then(|v| v.as_str()).map(String::from),
|
||||||
|
bit_rate: stream.get("bit_rate")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.and_then(|s| s.parse().ok()),
|
||||||
|
duration: stream.get("duration")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.and_then(|s| s.parse().ok()),
|
||||||
|
tags: stream.get("tags").cloned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_audio_stream(stream: &serde_json::Value) -> Result<AudioStream> {
|
||||||
|
Ok(AudioStream {
|
||||||
|
index: stream.get("index")
|
||||||
|
.and_then(|v| v.as_i64())
|
||||||
|
.map(|i| i as i32)
|
||||||
|
.unwrap_or(0),
|
||||||
|
codec_name: stream.get("codec_name").and_then(|v| v.as_str()).map(String::from),
|
||||||
|
channels: stream.get("channels")
|
||||||
|
.and_then(|v| v.as_i64())
|
||||||
|
.map(|i| i as i32)
|
||||||
|
.unwrap_or(0),
|
||||||
|
sample_rate: stream.get("sample_rate").and_then(|v| v.as_str()).map(String::from),
|
||||||
|
bit_rate: stream.get("bit_rate")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.and_then(|s| s.parse().ok()),
|
||||||
|
duration: stream.get("duration")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.and_then(|s| s.parse().ok()),
|
||||||
|
tags: stream.get("tags").cloned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_subtitle_stream(stream: &serde_json::Value) -> Result<SubtitleStream> {
|
||||||
|
Ok(SubtitleStream {
|
||||||
|
index: stream.get("index")
|
||||||
|
.and_then(|v| v.as_i64())
|
||||||
|
.map(|i| i as i32)
|
||||||
|
.unwrap_or(0),
|
||||||
|
codec_name: stream.get("codec_name").and_then(|v| v.as_str()).map(String::from),
|
||||||
|
language: stream.get("tags")
|
||||||
|
.and_then(|tags| tags.get("language"))
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.map(String::from),
|
||||||
|
tags: stream.get("tags").cloned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_other_stream(stream: &serde_json::Value) -> Result<OtherStream> {
|
||||||
|
Ok(OtherStream {
|
||||||
|
index: stream.get("index")
|
||||||
|
.and_then(|v| v.as_i64())
|
||||||
|
.map(|i| i as i32)
|
||||||
|
.unwrap_or(0),
|
||||||
|
codec_type: stream.get("codec_type")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.map(String::from)
|
||||||
|
.unwrap_or_else(|| "unknown".to_string()),
|
||||||
|
codec_name: stream.get("codec_name").and_then(|v| v.as_str()).map(String::from),
|
||||||
|
tags: stream.get("tags").cloned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user