Improve progress reporting and video format coverage
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import time
|
||||
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from tgvideo_check.cli import app
|
||||
@@ -167,6 +169,52 @@ def test_scan_command_reports_discovery_and_stage_progress(tmp_path, monkeypatch
|
||||
assert "Stage2 progress: 1/1 (100%) passed_stage2=1" in result.stdout
|
||||
|
||||
|
||||
def test_scan_command_reports_stage2_heartbeat_while_waiting(tmp_path, monkeypatch) -> None:
|
||||
root = tmp_path / "videos"
|
||||
root.mkdir()
|
||||
for index in range(60):
|
||||
(root / f"risky-{index:03d}.mp4").write_bytes(b"risky")
|
||||
|
||||
def fake_run_ffprobe(path, **kwargs):
|
||||
return ProbeExecutionResult(
|
||||
status="completed",
|
||||
command=("ffprobe", str(path)),
|
||||
exit_code=0,
|
||||
stdout_text='{"format":{"format_name":"mov,mp4,m4a,3gp,3g2,mj2","duration":"20.0"},"streams":[{"codec_type":"video"}]}',
|
||||
stderr_text="",
|
||||
elapsed_ms=1,
|
||||
)
|
||||
|
||||
def fake_run_ffmpeg(path, **kwargs):
|
||||
time.sleep(0.02)
|
||||
return FfmpegExecutionResult(
|
||||
status="completed",
|
||||
command=("ffmpeg", str(path)),
|
||||
exit_code=0,
|
||||
stdout_text="frame=10",
|
||||
stderr_text="",
|
||||
elapsed_ms=20,
|
||||
reason_code=None,
|
||||
)
|
||||
|
||||
monkeypatch.setattr("tgvideo_check.cli.PROGRESS_HEARTBEAT_SECONDS", 0.01)
|
||||
monkeypatch.setattr("tgvideo_check.cli.run_ffprobe", fake_run_ffprobe)
|
||||
monkeypatch.setattr("tgvideo_check.cli.run_ffmpeg_verification", fake_run_ffmpeg)
|
||||
|
||||
result = runner.invoke(
|
||||
app,
|
||||
[
|
||||
"scan",
|
||||
str(root),
|
||||
"--db",
|
||||
str(tmp_path / "scan_state.db"),
|
||||
],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Stage2 heartbeat:" in result.stdout
|
||||
|
||||
|
||||
def test_scan_resume_preserves_reason_code_in_exported_report(tmp_path, monkeypatch) -> None:
|
||||
root = tmp_path / "videos"
|
||||
root.mkdir()
|
||||
|
||||
+41
-2
@@ -1,4 +1,4 @@
|
||||
from tgvideo_check.config import ScanConfig
|
||||
from tgvideo_check.config import DEFAULT_VIDEO_EXTENSIONS, ScanConfig
|
||||
from tgvideo_check.profiles import get_profile, resolve_config
|
||||
|
||||
|
||||
@@ -12,6 +12,46 @@ def test_tg_hdd_profile_uses_conservative_defaults() -> None:
|
||||
assert profile.high_risk_extensions == ("mp4", "mov", "m4v", "mkv", "webm")
|
||||
|
||||
|
||||
def test_default_extensions_cover_broad_common_video_formats() -> None:
|
||||
assert DEFAULT_VIDEO_EXTENSIONS == (
|
||||
"mp4",
|
||||
"mov",
|
||||
"m4v",
|
||||
"mkv",
|
||||
"webm",
|
||||
"avi",
|
||||
"flv",
|
||||
"f4v",
|
||||
"ts",
|
||||
"m2ts",
|
||||
"mts",
|
||||
"mpg",
|
||||
"mpeg",
|
||||
"mpe",
|
||||
"mpv",
|
||||
"m2v",
|
||||
"wmv",
|
||||
"asf",
|
||||
"3gp",
|
||||
"3g2",
|
||||
"ogv",
|
||||
"ogm",
|
||||
"vob",
|
||||
"qt",
|
||||
"rm",
|
||||
"rmvb",
|
||||
"mxf",
|
||||
"divx",
|
||||
)
|
||||
assert ScanConfig(roots=()).extensions == DEFAULT_VIDEO_EXTENSIONS
|
||||
|
||||
|
||||
def test_deep_verify_profile_treats_all_supported_formats_as_high_risk() -> None:
|
||||
profile = get_profile("deep-verify")
|
||||
|
||||
assert profile.high_risk_extensions == DEFAULT_VIDEO_EXTENSIONS
|
||||
|
||||
|
||||
def test_resolve_config_applies_profile_overrides() -> None:
|
||||
config = resolve_config(
|
||||
ScanConfig(
|
||||
@@ -26,4 +66,3 @@ def test_resolve_config_applies_profile_overrides() -> None:
|
||||
assert config.stage1_workers == 3
|
||||
assert config.stage2_workers == 1
|
||||
assert config.extensions == ("mp4", "mkv")
|
||||
|
||||
|
||||
@@ -169,3 +169,35 @@ def test_iter_stage_batch_results_yields_items_as_they_finish() -> None:
|
||||
results = list(iter_stage_batch_results(batch, worker))
|
||||
|
||||
assert [(item.file_id, output) for item, output in results] == [(2, 20), (1, 10)]
|
||||
|
||||
|
||||
def test_iter_stage_batch_results_can_emit_heartbeat_while_waiting() -> None:
|
||||
batch = build_stage_plan(
|
||||
stage1_files=[
|
||||
make_stored_file(file_id=1, discovery_order=0, stage1_status="pending_stage1"),
|
||||
],
|
||||
stage2_files=[],
|
||||
stage1_workers=1,
|
||||
stage2_workers=1,
|
||||
queue_limit=64,
|
||||
)[0]
|
||||
heartbeats: list[tuple[int, ...]] = []
|
||||
|
||||
def worker(item: StoredFile) -> int:
|
||||
time.sleep(0.03)
|
||||
return item.file_id
|
||||
|
||||
results = list(
|
||||
iter_stage_batch_results(
|
||||
batch,
|
||||
worker,
|
||||
heartbeat_seconds=0.01,
|
||||
on_heartbeat=lambda items: heartbeats.append(
|
||||
tuple(entry.file_id for entry in items)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
assert results == [(batch.files[0], 1)]
|
||||
assert heartbeats
|
||||
assert heartbeats[0] == (1,)
|
||||
|
||||
Reference in New Issue
Block a user