Improve scan escalation, watch stability, and plan output

This commit is contained in:
2025-12-31 22:50:01 -05:00
parent 0e751a407d
commit a6cb9addb0
5 changed files with 93 additions and 6 deletions

View File

@@ -30,6 +30,12 @@ vid-repair scan /path/to/videos
# Scan and watch for new files
vid-repair scan --watch /path/to/videos
# Scan depth (quick|standard|deep)
vid-repair scan --scan-depth quick /path/to/videos
# Disable recursive scanning
vid-repair scan --no-recursive /path/to/videos
# Fix (safe policy, in-place)
vid-repair fix /path/to/videos

View File

@@ -41,7 +41,7 @@ pub fn plan_fix(issues: &[Issue], policy: FixPolicy) -> FixPlan {
} else {
actions.push(FixAction {
kind,
command: Vec::new(),
command: command_template(kind),
destructive: true,
});
}
@@ -56,12 +56,52 @@ pub fn plan_fix(issues: &[Issue], policy: FixPolicy) -> FixPlan {
}
pub fn plan_outcome(plan: FixPlan) -> FixOutcome {
let message = if let Some(kind) = plan.recommended {
format!("Fix plan generated: {:?}", kind)
} else {
"Fix plan generated".to_string()
};
FixOutcome {
plan,
applied: false,
success: false,
message: "Fix plan generated".to_string(),
message,
output_path: None,
re_scan_required: false,
}
}
fn command_template(kind: FixKind) -> Vec<String> {
let mut cmd = vec![
"ffmpeg".to_string(),
"-v".to_string(),
"error".to_string(),
"-i".to_string(),
"<input>".to_string(),
];
match kind {
FixKind::Remux => {
cmd.push("-c".to_string());
cmd.push("copy".to_string());
}
FixKind::Faststart => {
cmd.push("-c".to_string());
cmd.push("copy".to_string());
cmd.push("-movflags".to_string());
cmd.push("+faststart".to_string());
}
FixKind::Reencode => {
cmd.push("-c:v".to_string());
cmd.push("libx264".to_string());
cmd.push("-c:a".to_string());
cmd.push("aac".to_string());
cmd.push("-movflags".to_string());
cmd.push("+faststart".to_string());
}
}
cmd.push("<output>".to_string());
cmd
}

View File

@@ -22,7 +22,17 @@ pub fn render_fix_line(scan: &ScanOutcome, fix: &FixOutcome) -> String {
} else if fix.applied {
format!("[FAILED] {} - {}", scan.path.display(), fix.message)
} else {
format!("[SKIPPED] {} - {}", scan.path.display(), fix.message)
let action = fix
.plan
.recommended
.map(|kind| format!("{:?}", kind))
.unwrap_or_else(|| "none".to_string());
format!(
"[SKIPPED] {} - {} (plan: {})",
scan.path.display(),
fix.message,
action
)
}
}

View File

@@ -18,21 +18,33 @@ pub fn scan_file(path: &Path, config: &Config, ruleset: &RuleSet) -> Result<Scan
let context = build_context(&probe);
let mut matches = ruleset.match_lines(&decode.lines, &context);
let had_decode_errors = !decode.lines.is_empty();
if config.scan.auto_escalate
&& config.scan.depth != ScanDepth::Deep
&& !decode.early_stop
&& !matches.is_empty()
&& (had_decode_errors || !matches.is_empty())
{
decode = decode::run_decode(path, &config.ffmpeg_path, ruleset, ScanDepth::Deep)?;
matches = ruleset.match_lines(&decode.lines, &context);
}
let issues = matches
let mut issues = matches
.iter()
.map(|hit| issue_from_match(hit))
.collect::<Vec<_>>();
if issues.is_empty() && !decode.lines.is_empty() {
issues.push(Issue {
code: "UNKNOWN_DECODE_ERROR".to_string(),
severity: crate::rules::Severity::High,
fix_tier: crate::rules::FixTier::Reencode,
message: "Decoder reported errors not matched by ruleset".to_string(),
evidence: decode.lines.iter().take(3).cloned().collect(),
action: None,
});
}
Ok(ScanOutcome {
path: path.to_path_buf(),
probe,

View File

@@ -86,8 +86,27 @@ where
.collect();
for path in ready {
let before = std::fs::metadata(&path).ok();
entries.remove(&path);
handler(path);
handler(path.clone());
if let Some(meta) = before {
let size = meta.len();
let mtime = meta.modified().unwrap_or_else(|_| std::time::SystemTime::UNIX_EPOCH);
if let Ok(after) = std::fs::metadata(&path) {
if after.len() != size || after.modified().unwrap_or_else(|_| std::time::SystemTime::UNIX_EPOCH) != mtime {
entries.insert(
path,
WatchEntry {
last_event: Instant::now(),
size: after.len(),
mtime: after
.modified()
.unwrap_or_else(|_| std::time::SystemTime::UNIX_EPOCH),
},
);
}
}
}
}
}