Handle invalid ffprobe JSON during stage1
This commit is contained in:
@@ -435,6 +435,26 @@ def _execute_stage1_scan(config: ScanConfig) -> list[ReportRow]:
|
|||||||
|
|
||||||
payload = _parse_probe_payload(probe_result.stdout_text)
|
payload = _parse_probe_payload(probe_result.stdout_text)
|
||||||
if probe_result.stdout_text and payload is None:
|
if probe_result.stdout_text and payload is None:
|
||||||
|
classification = classify_stage1_result(
|
||||||
|
exit_code=probe_result.exit_code,
|
||||||
|
stderr_text=probe_result.stderr_text,
|
||||||
|
probe_payload=None,
|
||||||
|
extension=stored_file.extension,
|
||||||
|
high_risk_extensions=config.high_risk_extensions,
|
||||||
|
)
|
||||||
|
if classification.reason_code in {"ffprobe_exit_nonzero", "hard_error_keyword"}:
|
||||||
|
final_status = classification.status
|
||||||
|
store.record_stage1_status(
|
||||||
|
stored_file.file_id,
|
||||||
|
stage1_status=classification.status,
|
||||||
|
final_status=final_status,
|
||||||
|
reason_code=classification.reason_code,
|
||||||
|
)
|
||||||
|
reasons[stored_file.file_id] = classification.reason_code
|
||||||
|
if stage1_progress is not None:
|
||||||
|
stage1_progress.record(classification.status)
|
||||||
|
continue
|
||||||
|
|
||||||
stage1_status = "command_error"
|
stage1_status = "command_error"
|
||||||
store.record_stage1_status(
|
store.record_stage1_status(
|
||||||
stored_file.file_id,
|
stored_file.file_id,
|
||||||
@@ -656,7 +676,10 @@ def _reload_probe_payload(path: Path) -> dict[str, object] | None:
|
|||||||
def _parse_probe_payload(stdout_text: str) -> dict[str, object] | None:
|
def _parse_probe_payload(stdout_text: str) -> dict[str, object] | None:
|
||||||
if not stdout_text.strip():
|
if not stdout_text.strip():
|
||||||
return None
|
return None
|
||||||
|
try:
|
||||||
payload = orjson.loads(stdout_text)
|
payload = orjson.loads(stdout_text)
|
||||||
|
except orjson.JSONDecodeError:
|
||||||
|
return None
|
||||||
if isinstance(payload, dict):
|
if isinstance(payload, dict):
|
||||||
return payload
|
return payload
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -161,6 +161,88 @@ def test_scan_command_processes_stage1_results_and_exports_reports(
|
|||||||
assert csv_path.exists()
|
assert csv_path.exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_scan_command_treats_invalid_probe_json_with_nonzero_exit_as_confirmed_broken(
|
||||||
|
tmp_path: Path, monkeypatch
|
||||||
|
) -> None:
|
||||||
|
root = tmp_path / "videos"
|
||||||
|
root.mkdir()
|
||||||
|
broken = root / "broken.mp4"
|
||||||
|
broken.write_bytes(b"broken")
|
||||||
|
db_path = tmp_path / "scan_state.db"
|
||||||
|
|
||||||
|
def fake_run_ffprobe(path, **kwargs):
|
||||||
|
return ProbeExecutionResult(
|
||||||
|
status="completed",
|
||||||
|
command=("ffprobe", str(path)),
|
||||||
|
exit_code=1,
|
||||||
|
stdout_text='{\n',
|
||||||
|
stderr_text="",
|
||||||
|
elapsed_ms=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.setattr("tgvideo_check.cli.run_ffprobe", fake_run_ffprobe)
|
||||||
|
|
||||||
|
result = runner.invoke(
|
||||||
|
app,
|
||||||
|
[
|
||||||
|
"scan",
|
||||||
|
str(root),
|
||||||
|
"--db",
|
||||||
|
str(db_path),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Scanned 1 file(s)" in result.stdout
|
||||||
|
assert "confirmed_broken=1" in result.stdout
|
||||||
|
|
||||||
|
store = ScanStore(db_path)
|
||||||
|
rows = store.list_files(1)
|
||||||
|
assert rows[0].final_status == "confirmed_broken"
|
||||||
|
assert rows[0].reason_code == "ffprobe_exit_nonzero"
|
||||||
|
|
||||||
|
|
||||||
|
def test_scan_command_treats_invalid_probe_json_with_zero_exit_as_command_error(
|
||||||
|
tmp_path: Path, monkeypatch
|
||||||
|
) -> None:
|
||||||
|
root = tmp_path / "videos"
|
||||||
|
root.mkdir()
|
||||||
|
broken = root / "broken.mp4"
|
||||||
|
broken.write_bytes(b"broken")
|
||||||
|
db_path = tmp_path / "scan_state.db"
|
||||||
|
|
||||||
|
def fake_run_ffprobe(path, **kwargs):
|
||||||
|
return ProbeExecutionResult(
|
||||||
|
status="completed",
|
||||||
|
command=("ffprobe", str(path)),
|
||||||
|
exit_code=0,
|
||||||
|
stdout_text='{\n',
|
||||||
|
stderr_text="",
|
||||||
|
elapsed_ms=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.setattr("tgvideo_check.cli.run_ffprobe", fake_run_ffprobe)
|
||||||
|
|
||||||
|
result = runner.invoke(
|
||||||
|
app,
|
||||||
|
[
|
||||||
|
"scan",
|
||||||
|
str(root),
|
||||||
|
"--db",
|
||||||
|
str(db_path),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Scanned 1 file(s)" in result.stdout
|
||||||
|
assert "command_error=1" in result.stdout
|
||||||
|
|
||||||
|
store = ScanStore(db_path)
|
||||||
|
rows = store.list_files(1)
|
||||||
|
assert rows[0].final_status == "command_error"
|
||||||
|
assert rows[0].reason_code == "invalid_probe_output"
|
||||||
|
|
||||||
|
|
||||||
def test_scan_command_leaves_stage2_pending_by_default(tmp_path, monkeypatch) -> None:
|
def test_scan_command_leaves_stage2_pending_by_default(tmp_path, monkeypatch) -> None:
|
||||||
root = tmp_path / "videos"
|
root = tmp_path / "videos"
|
||||||
root.mkdir()
|
root.mkdir()
|
||||||
|
|||||||
Reference in New Issue
Block a user