From 18ee2c83427ebcfa41c8736231c306779d346dd2 Mon Sep 17 00:00:00 2001 From: Ryan Walters Date: Thu, 21 Aug 2025 20:14:46 -0500 Subject: [PATCH] feat: add logging via tracing crate --- src-tauri/Cargo.lock | 102 ++++++++++++++++++++++++++++++++++++-- src-tauri/Cargo.toml | 2 + src-tauri/src/ff.rs | 108 ++++++++++++++++++++++++++++++++--------- src-tauri/src/lib.rs | 60 ++++++++++++++++++++--- src-tauri/src/main.rs | 13 +++++ src-tauri/src/media.rs | 39 +++++++++++++-- 6 files changed, 286 insertions(+), 38 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 06e9492..1e4d87e 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -401,6 +401,8 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-opener", + "tracing", + "tracing-subscriber", "ts-rs", ] @@ -2201,6 +2203,15 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "matches" version = "0.1.10" @@ -2325,6 +2336,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2662,6 +2683,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "pango" version = "0.18.3" @@ -3197,8 +3224,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -3209,9 +3245,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -3568,6 +3610,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -4332,6 +4383,15 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.41" @@ -4579,6 +4639,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -4784,6 +4874,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version-compare" version = "0.2.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 4ef6953..02ef262 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -24,3 +24,5 @@ serde = { version = "1", features = ["derive"] } ffprobe = "0.4.0" ts-rs = { version = "11.0", features = ["format"] } infer = "0.19.0" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/src-tauri/src/ff.rs b/src-tauri/src/ff.rs index 5397579..51a3585 100644 --- a/src-tauri/src/ff.rs +++ b/src-tauri/src/ff.rs @@ -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 { 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 } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3111a16..587560c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -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) -> Result, 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) -> Result, 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) -> Result, 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) -> Result, StreamResultErro .duration .and_then(|dur_str| dur_str.parse::().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) -> Result, 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) -> Result, 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) -> Result, StreamResultErro }) } }) - .collect::, _>>() + .collect::, _>>(); + + 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"); -} +} \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index d1112c1..4ae9632 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -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() } diff --git a/src-tauri/src/media.rs b/src-tauri/src/media.rs index 344cd39..fa6d9f4 100644 --- a/src-tauri/src/media.rs +++ b/src-tauri/src/media.rs @@ -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 }