Add real media-file probing (session 9, sixteenth delivery)

Wires real container-header probing into the Rename panel's Match step, the
first item on the porting roadmap: aurora-curator-core::probe reads actual
resolution, frame rate, duration, and video/audio codecs straight from a
file's own header (mp4 crate for MP4/M4V/MOV, matroska crate for MKV/WebM,
no ffmpeg/MediaInfo dependency), batched through a new probe_media_files
Tauri command. Probed values fill {resolution}/{video codec}/{audio codec}
only when a filename release tag hasn't already, and populate five bindings
with no filename-tag equivalent at all: {width}, {height}, {fps},
{duration}, {file size}. 8 new Rust unit tests against real ffmpeg-generated
fixture files (161 -> 169 total), verified end to end with a 15-check
Playwright script against npm run dev, and README updated (status
paragraph, project layout, a new "Real media-file probing" section, and
roadmap item 1 narrowed to what's still missing).
This commit is contained in:
Renamr Scaffold
2026-08-29 18:07:54 +00:00
parent 03768c398d
commit 1291ae086d
12 changed files with 614 additions and 26 deletions
+9 -5
View File
@@ -5,6 +5,7 @@ mod history;
mod list;
mod metadata;
mod presets;
mod probe;
mod settings;
mod sfv;
mod subtitles;
@@ -47,11 +48,13 @@ fn guess_movie(filename: String) -> Option<MovieGuess> {
/// Guesses release-tag metadata (resolution, source, video/audio codec, release
/// group) from a filename's own text using local regex patterns only - no network
/// calls, no real media probing (Aurora Curator never ships an ffprobe/MediaInfo dependency,
/// so filename tags are the only source for these fields). Independent of
/// `guess_episode`/`guess_movie`: called alongside whichever of those two applies,
/// since a release name carries (or doesn't carry) these tags regardless of whether
/// it's an episode or a movie.
/// calls, no file I/O. Complements `probe::probe_media_files` below, which reads the
/// same kind of resolution/codec data straight from a file's real container header
/// instead of guessing it from filename text - the Rename panel merges both,
/// preferring an explicit filename tag over a probed value when a file has both.
/// Independent of `guess_episode`/`guess_movie`: called alongside whichever of those
/// two applies, since a release name carries (or doesn't carry) these tags regardless
/// of whether it's an episode or a movie.
#[tauri::command]
fn guess_release_info(filename: String) -> Option<ReleaseInfo> {
matching::guess_release_info(&filename)
@@ -123,6 +126,7 @@ pub fn run() {
sfv::save_checksum_file,
sfv::verify_checksum_file,
list::export_text_list,
probe::probe_media_files,
history::list_rename_history,
history::undo_rename_batch,
presets::list_presets,
+41
View File
@@ -0,0 +1,41 @@
//! The Tauri-facing side of real media-file probing: reads real container metadata
//! (resolution, duration, frame rate, codecs) straight from each loaded file's own
//! header via `aurora_curator_core::probe`, batched the same way `sfv::compute_checksums`
//! batches checksum hashing - one Tauri round trip for the whole loaded set, run off
//! the main thread since this is real file I/O (a probe only reads a container's
//! header/index boxes, not the whole file, but a movie file can still be tens of GB
//! and may live on a slow network share).
use std::path::Path;
use aurora_curator_core::probe::{self, MediaProbe};
use serde::Serialize;
/// One file's probe result, paired with the path it came from so the frontend can
/// match results back to the loaded items it requested (order isn't otherwise
/// guaranteed to be preserved end to end through `spawn_blocking`).
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProbeOutcome {
pub path: String,
pub probe: MediaProbe,
}
/// Probes a batch of loaded files. Never fails per-file - `aurora_curator_core::probe::probe_media_file`
/// is always best-effort (see its doc comment), so every path in `paths` gets exactly
/// one `ProbeOutcome` back, even a file that isn't a supported container or can't be
/// read at all (its `MediaProbe` just comes back mostly empty).
#[tauri::command]
pub async fn probe_media_files(paths: Vec<String>) -> Vec<ProbeOutcome> {
tauri::async_runtime::spawn_blocking(move || {
paths
.into_iter()
.map(|path| {
let probe = probe::probe_media_file(Path::new(&path));
ProbeOutcome { path, probe }
})
.collect()
})
.await
.unwrap_or_default()
}