Add JSON report for WCX detection
This commit is contained in:
@ -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(
|
||||
|
||||
Reference in New Issue
Block a user