feat: add logging via tracing crate

This commit is contained in:
Ryan Walters
2025-08-21 20:14:46 -05:00
parent f34a67b949
commit 18ee2c8342
6 changed files with 286 additions and 38 deletions

View File

@@ -1,45 +1,109 @@
use crate::models::StreamDetail;
use tracing::{debug, info, instrument};
#[instrument(skip(info), fields(stream_count = info.streams.len()))]
pub fn extract_streams(info: &ffprobe::FfProbe) -> Vec<StreamDetail> {
let mut streams = Vec::new();
let mut video_count = 0;
let mut audio_count = 0;
let mut subtitle_count = 0;
for stream in &info.streams {
info!(total_streams = info.streams.len(), "Extracting streams from media file");
for (index, stream) in info.streams.iter().enumerate() {
match stream.codec_type.as_deref() {
Some("video") => {
video_count += 1;
let codec = stream
.codec_name
.clone()
.unwrap_or_else(|| "unknown".to_string());
let width = stream.width.map(|w| w as u32);
let height = stream.height.map(|h| h as u32);
let bit_rate = stream.bit_rate.as_ref().map(|b| b.to_string());
let frame_rate = Some(stream.r_frame_rate.clone());
debug!(
stream_index = index,
codec = %codec,
width = ?width,
height = ?height,
bit_rate = ?bit_rate,
frame_rate = ?frame_rate,
"Extracted video stream"
);
streams.push(StreamDetail::Video {
codec: stream
.codec_name
.clone()
.unwrap_or_else(|| "unknown".to_string()),
width: stream.width.map(|w| w as u32),
height: stream.height.map(|h| h as u32),
bit_rate: stream.bit_rate.as_ref().map(|b| b.to_string()),
frame_rate: Some(stream.r_frame_rate.clone()),
codec,
width,
height,
bit_rate,
frame_rate,
});
}
Some("audio") => {
audio_count += 1;
let codec = stream
.codec_name
.clone()
.unwrap_or_else(|| "unknown".to_string());
let sample_rate = stream.sample_rate.clone();
let channels = stream.channels.map(|c| c as u32);
let bit_rate = stream.bit_rate.as_ref().map(|b| b.to_string());
debug!(
stream_index = index,
codec = %codec,
sample_rate = ?sample_rate,
channels = ?channels,
bit_rate = ?bit_rate,
"Extracted audio stream"
);
streams.push(StreamDetail::Audio {
codec: stream
.codec_name
.clone()
.unwrap_or_else(|| "unknown".to_string()),
sample_rate: stream.sample_rate.clone(),
channels: stream.channels.map(|c| c as u32),
bit_rate: stream.bit_rate.as_ref().map(|b| b.to_string()),
codec,
sample_rate,
channels,
bit_rate,
});
}
Some("subtitle") => {
subtitle_count += 1;
let codec = stream
.codec_name
.clone()
.unwrap_or_else(|| "unknown".to_string());
let language = stream.tags.as_ref().and_then(|tags| tags.language.clone());
debug!(
stream_index = index,
codec = %codec,
language = ?language,
"Extracted subtitle stream"
);
streams.push(StreamDetail::Subtitle {
codec: stream
.codec_name
.clone()
.unwrap_or_else(|| "unknown".to_string()),
language: stream.tags.as_ref().and_then(|tags| tags.language.clone()),
codec,
language,
});
}
_ => {}
other => {
debug!(
stream_index = index,
codec_type = ?other,
"Skipping unknown stream type"
);
}
}
}
info!(
video_streams = video_count,
audio_streams = audio_count,
subtitle_streams = subtitle_count,
total_extracted = streams.len(),
"Stream extraction completed"
);
streams
}

View File

@@ -1,29 +1,45 @@
mod ff;
mod media;
mod models;
pub mod ff;
pub mod media;
pub mod models;
pub mod strings;
use ff::extract_streams;
use media::{detect_media_type, is_media_file};
use models::{StreamResult, StreamResultError};
use strings::transform_filename;
use std::path::Path;
use tracing::{debug, error, info, instrument, warn};
// detection, helpers moved to modules above
#[tauri::command]
#[instrument(skip(paths), fields(file_count = paths.len()))]
fn has_streams(paths: Vec<String>) -> Result<Vec<StreamResult>, StreamResultError> {
paths
info!(file_count = paths.len(), "Processing files for stream analysis");
let results = paths
.into_iter()
.map(|path_str| {
.enumerate()
.map(|(index, path_str)| {
let path = Path::new(&path_str);
let filename = path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("unknown")
.to_string();
// Log full path only on first occurrence, then use truncated filename
if index == 0 {
debug!(full_path = %path_str, filename = %filename, "Processing first file");
} else {
let truncated_name = transform_filename(&filename, 15);
debug!(filename = %truncated_name, "Processing file");
}
// Check if file exists
if !path.exists() {
let truncated_name = transform_filename(&filename, 15);
warn!(filename = %truncated_name, "File does not exist");
return Err(StreamResultError {
filename: Some(filename),
reason: "File does not exist".to_string(),
@@ -33,6 +49,8 @@ fn has_streams(paths: Vec<String>) -> Result<Vec<StreamResult>, StreamResultErro
// Check if it's a file (not directory)
if !path.is_file() {
let truncated_name = transform_filename(&filename, 15);
warn!(filename = %truncated_name, "Path is not a file");
return Err(StreamResultError {
filename: Some(filename),
reason: "Not a file (directory or other)".to_string(),
@@ -45,11 +63,17 @@ fn has_streams(paths: Vec<String>) -> Result<Vec<StreamResult>, StreamResultErro
.map(|metadata| metadata.len())
.unwrap_or(0);
let truncated_name = transform_filename(&filename, 15);
debug!(filename = %truncated_name, size = size, "File metadata retrieved");
// Detect media type using magic numbers and fallback to extensions
let media_type = detect_media_type(path);
debug!(filename = %truncated_name, media_type = ?media_type, "Media type detected");
// Only try to analyze media files with ffprobe
if is_media_file(&media_type) {
info!(filename = %truncated_name, media_type = ?media_type, "Analyzing media file with ffprobe");
// Analyze with ffprobe
match ffprobe::ffprobe(&path_str) {
Ok(info) => {
@@ -59,6 +83,13 @@ fn has_streams(paths: Vec<String>) -> Result<Vec<StreamResult>, StreamResultErro
.duration
.and_then(|dur_str| dur_str.parse::<f64>().ok());
info!(
filename = %truncated_name,
stream_count = streams.len(),
duration = ?duration,
"Successfully analyzed media file"
);
Ok(StreamResult {
filename,
path: path_str,
@@ -69,7 +100,7 @@ fn has_streams(paths: Vec<String>) -> Result<Vec<StreamResult>, StreamResultErro
})
}
Err(err) => {
eprintln!("Could not analyze media file with ffprobe: {err:?}");
error!(filename = %truncated_name, error = %err, "Failed to analyze media file with ffprobe");
Err(StreamResultError {
filename: Some(filename),
reason: format!("Could not analyze media file: {err}"),
@@ -78,6 +109,7 @@ fn has_streams(paths: Vec<String>) -> Result<Vec<StreamResult>, StreamResultErro
}
}
} else {
debug!(filename = %truncated_name, media_type = ?media_type, "Skipping non-media file");
// For non-media files, return an error indicating it's not a media file
Err(StreamResultError {
filename: Some(filename),
@@ -86,14 +118,26 @@ fn has_streams(paths: Vec<String>) -> Result<Vec<StreamResult>, StreamResultErro
})
}
})
.collect::<Result<Vec<_>, _>>()
.collect::<Result<Vec<_>, _>>();
match &results {
Ok(streams) => {
info!(successful_files = streams.len(), "Successfully processed all files");
}
Err(_) => {
warn!("Some files failed to process");
}
}
results
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
info!("Initializing Tauri application");
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![has_streams])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
}

View File

@@ -1,6 +1,19 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
fn main() {
// Initialize tracing with env-filter
tracing_subscriber::registry()
.with(
EnvFilter::from_default_env()
.add_directive("byte_me=debug".parse().unwrap())
.add_directive("tauri=info".parse().unwrap()),
)
.with(tracing_subscriber::fmt::layer())
.init();
tracing::info!("Starting byte-me application");
byte_me_lib::run()
}

View File

@@ -1,13 +1,22 @@
use crate::models::MediaType;
use std::{fs::File, io::Read, path::Path};
use tracing::{debug, instrument, trace, warn};
#[instrument(skip(path), fields(path = %path.display()))]
pub fn detect_media_type(path: &Path) -> MediaType {
debug!("Starting media type detection");
// First try to detect using infer crate (magic number detection)
if let Ok(mut file) = File::open(path) {
let mut buffer = [0; 512];
if let Ok(bytes_read) = file.read(&mut buffer) {
trace!(bytes_read = bytes_read, "Read file header for magic number detection");
if let Some(kind) = infer::get(&buffer[..bytes_read]) {
return match kind.mime_type() {
let mime_type = kind.mime_type();
debug!(mime_type = %mime_type, "Detected MIME type from magic numbers");
let media_type = match mime_type {
// Audio types
"audio/mpeg" | "audio/mp3" | "audio/m4a" | "audio/ogg" | "audio/x-flac"
| "audio/x-wav" | "audio/amr" | "audio/aac" | "audio/x-aiff"
@@ -90,13 +99,25 @@ pub fn detect_media_type(path: &Path) -> MediaType {
// Library types (covered by executable types above, but keeping for clarity)
_ => MediaType::Unknown,
};
debug!(media_type = ?media_type, "Detected media type from magic numbers");
return media_type;
} else {
debug!("Magic number detection failed, falling back to extension-based detection");
}
} else {
warn!("Failed to read file for magic number detection");
}
} else {
warn!("Failed to open file for magic number detection");
}
// Fallback to extension-based detection
if let Some(extension) = path.extension() {
match extension.to_str().unwrap_or("").to_lowercase().as_str() {
let ext_str = extension.to_str().unwrap_or("").to_lowercase();
debug!(extension = %ext_str, "Detecting media type from file extension");
let media_type = match ext_str.as_str() {
// Audio extensions
"mp3" | "wav" | "flac" | "ogg" | "m4a" | "aac" | "wma" | "mid" | "amr" | "aiff"
| "dsf" | "ape" => MediaType::Audio,
@@ -127,15 +148,23 @@ pub fn detect_media_type(path: &Path) -> MediaType {
"so" | "dylib" => MediaType::Library,
_ => MediaType::Unknown,
}
};
debug!(media_type = ?media_type, "Detected media type from extension");
media_type
} else {
debug!("No file extension found, returning Unknown");
MediaType::Unknown
}
}
#[instrument(skip(media_type))]
pub fn is_media_file(media_type: &MediaType) -> bool {
matches!(
let is_media = matches!(
media_type,
MediaType::Audio | MediaType::Video | MediaType::Image
)
);
debug!(media_type = ?media_type, is_media = is_media, "Checking if file is media type");
is_media
}