Add JSON report for WCX detection

This commit is contained in:
2026-07-21 19:50:40 +02:00
parent a60ef87ddc
commit 1b651be90e
3 changed files with 398 additions and 25 deletions

View File

@ -15,10 +15,13 @@ Example:
"""
import argparse
import json
import os
import shlex
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Callable, Sequence
@ -90,6 +93,12 @@ def parse_arguments() -> argparse.Namespace:
),
)
parser.add_argument(
"--json-report",
type=Path,
help="Write a machine-readable JSON report after a batch run.",
)
return parser.parse_args()
@ -484,38 +493,141 @@ def analyze_video_for_batch(
def process_batch(
video_files: Sequence[Path],
analyzer: Callable[[Path], tuple[float, int, str]],
) -> int:
counts = {
) -> tuple[list[dict[str, object]], dict[str, int]]:
summary = {
"processed": 0,
"wcx": 0,
"uncertain": 0,
"not_wcx": 0,
"errors": 0,
}
files = []
for video_file in video_files:
counts["processed"] += 1
absolute_path = str(video_file.resolve())
summary["processed"] += 1
try:
timestamp, score, classification = analyzer(video_file)
except (OSError, RuntimeError, ValueError) as error:
counts["errors"] += 1
summary["errors"] += 1
files.append(
{
"path": absolute_path,
"classification": "error",
"score": None,
"best_timestamp": None,
"error": str(error),
}
)
print(f"error - - {video_file}: {error}")
continue
counts[classification] += 1
summary[classification] += 1
files.append(
{
"path": absolute_path,
"classification": classification,
"score": score,
"best_timestamp": timestamp,
}
)
print(
f"{classification:<10} {score}/8 "
f"{timestamp:>4.1f} {video_file}"
)
print()
print(f"processed: {counts['processed']}")
print(f"wcx: {counts['wcx']}")
print(f"uncertain: {counts['uncertain']}")
print(f"not_wcx: {counts['not_wcx']}")
print(f"errors: {counts['errors']}")
print(f"processed: {summary['processed']}")
print(f"wcx: {summary['wcx']}")
print(f"uncertain: {summary['uncertain']}")
print(f"not_wcx: {summary['not_wcx']}")
print(f"errors: {summary['errors']}")
return 1 if counts["errors"] else 0
return files, summary
def build_json_report(
source_directory: Path,
recursive: bool,
endings: set[str],
summary: dict[str, int],
files: list[dict[str, object]],
) -> dict[str, object]:
return {
"schema_version": 1,
"source_directory": str(source_directory.resolve()),
"recursive": recursive,
"endings": sorted(endings),
"summary": dict(summary),
"files": list(files),
}
def write_json_report(
report_path: Path,
report: dict[str, object],
) -> None:
if report_path.exists():
raise FileExistsError(
f"JSON report already exists: {report_path}"
)
report_path.parent.mkdir(parents=True, exist_ok=True)
temporary_path = None
try:
with tempfile.NamedTemporaryFile(
mode="w",
encoding="utf-8",
dir=report_path.parent,
prefix=f".{report_path.name}.",
suffix=".tmp",
delete=False,
) as temporary_file:
temporary_path = Path(temporary_file.name)
json.dump(
report,
temporary_file,
indent=2,
ensure_ascii=False,
)
temporary_file.write("\n")
temporary_file.flush()
os.fsync(temporary_file.fileno())
if report_path.exists():
raise FileExistsError(
f"JSON report already exists: {report_path}"
)
temporary_path.replace(report_path)
temporary_path = None
finally:
if temporary_path is not None:
temporary_path.unlink(missing_ok=True)
def write_optional_json_report(
report_path: Path | None,
report: dict[str, object],
writer: Callable[
[Path, dict[str, object]],
None,
] = write_json_report,
) -> int:
if report_path is None:
return 0
try:
writer(report_path, report)
except (OSError, TypeError, ValueError) as error:
print(
f"Error: Could not write JSON report {report_path}: {error}",
file=sys.stderr,
)
return 1
return 0
def diagnose_layout(
@ -663,6 +775,16 @@ def main() -> int:
)
return 2
if (
args.json_report is not None
and args.json_report.exists()
):
print(
f"Error: JSON report already exists: {args.json_report}",
file=sys.stderr,
)
return 2
if not args.input_path.is_dir():
print(
f"Error: Batch directory does not exist: {args.input_path}",
@ -686,20 +808,41 @@ def main() -> int:
f"No matching video files found in: "
f"{args.input_path.resolve()}"
)
return process_batch((), lambda _path: (0.0, 0, "not_wcx"))
analyzer = lambda _path: (0.0, 0, "not_wcx")
else:
ffmpeg = shutil.which("ffmpeg")
if ffmpeg is None:
print("Error: ffmpeg was not found in PATH.", file=sys.stderr)
return 1
ffmpeg = shutil.which("ffmpeg")
if ffmpeg is None:
print("Error: ffmpeg was not found in PATH.", file=sys.stderr)
return 1
return process_batch(
video_files,
lambda video_file: analyze_video_for_batch(
analyzer = lambda video_file: analyze_video_for_batch(
ffmpeg,
video_file,
),
)
files, summary = process_batch(
video_files,
analyzer,
)
report = build_json_report(
source_directory=args.input_path,
recursive=args.recursive,
endings=endings,
summary=summary,
files=files,
)
report_exit_code = write_optional_json_report(
args.json_report,
report,
)
return 1 if summary["errors"] or report_exit_code else 0
if args.json_report is not None:
print(
"Error: --json-report requires --batch.",
file=sys.stderr,
)
return 2
if args.recursive:
print(