| 1 | """Per-device diagnostic: probes each video index, reports real FOURCC/size/brightness.""" |
| 2 | from __future__ import annotations |
| 3 | |
| 4 | import sys |
| 5 | import time |
| 6 | from pathlib import Path |
| 7 | |
| 8 | import cv2 |
| 9 | import numpy as np |
| 10 | from rich.console import Console |
| 11 | |
| 12 | console = Console() |
| 13 | LOG_DIR = Path(__file__).resolve().parents[1] / "logs" |
| 14 | |
| 15 | def fourcc_to_str(val: float) -> str: |
| 16 | v = int(val) |
| 17 | if v <= 0: |
| 18 | return "NONE" |
| 19 | return "".join(chr((v >> (8 * i)) & 0xFF) for i in range(4)) |
| 20 | |
| 21 | def probe(idx: int, target_w: int = 1920, target_h: int = 1080, target_fps: int = 60) -> dict: |
| 22 | result: dict = {"index": idx, "opened": False} |
| 23 | cap = cv2.VideoCapture(idx, cv2.CAP_DSHOW) |
| 24 | if not cap.isOpened(): |
| 25 | cap.release() |
| 26 | return result |
| 27 | result["opened"] = True |
| 28 | |
| 29 | result["default_size"] = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))) |
| 30 | result["default_fourcc"] = fourcc_to_str(cap.get(cv2.CAP_PROP_FOURCC)) |
| 31 | result["default_fps"] = cap.get(cv2.CAP_PROP_FPS) |
| 32 | |
| 33 | mjpg = cv2.VideoWriter_fourcc(*"MJPG") |
| 34 | cap.set(cv2.CAP_PROP_FOURCC, mjpg) |
| 35 | cap.set(cv2.CAP_PROP_FRAME_WIDTH, target_w) |
| 36 | cap.set(cv2.CAP_PROP_FRAME_HEIGHT, target_h) |
| 37 | cap.set(cv2.CAP_PROP_FPS, target_fps) |
| 38 | |
| 39 | result["set_size"] = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))) |
| 40 | result["set_fourcc"] = fourcc_to_str(cap.get(cv2.CAP_PROP_FOURCC)) |
| 41 | result["set_fps"] = cap.get(cv2.CAP_PROP_FPS) |
| 42 | |
| 43 | frame_count = 0 |
| 44 | first: np.ndarray | None = None |
| 45 | mean_brightness: list[float] = [] |
| 46 | t_start = time.perf_counter() |
| 47 | deadline = t_start + 3.0 |
| 48 | while time.perf_counter() < deadline: |
| 49 | ok, frame = cap.read() |
| 50 | if not ok or frame is None: |
| 51 | continue |
| 52 | if first is None: |
| 53 | first = frame.copy() |
| 54 | mean_brightness.append(float(frame.mean())) |
| 55 | frame_count += 1 |
| 56 | elapsed = time.perf_counter() - t_start |
| 57 | cap.release() |
| 58 | |
| 59 | result["frames"] = frame_count |
| 60 | result["elapsed"] = elapsed |
| 61 | result["fps"] = frame_count / elapsed if elapsed > 0 else 0.0 |
| 62 | if first is not None: |
| 63 | result["frame_shape"] = first.shape |
| 64 | result["brightness_mean"] = float(np.mean(mean_brightness)) if mean_brightness else 0.0 |
| 65 | result["brightness_std"] = float(np.std(mean_brightness)) if mean_brightness else 0.0 |
| 66 | out = LOG_DIR / f"diag_device_{idx}.png" |
| 67 | cv2.imwrite(str(out), first) |
| 68 | result["saved"] = str(out) |
| 69 | return result |
| 70 | |
| 71 | def main() -> int: |
| 72 | LOG_DIR.mkdir(parents=True, exist_ok=True) |
| 73 | console.print("[bold cyan]Per-device diagnostic (each opens/probes for 3s)[/bold cyan]\n") |
| 74 | for idx in range(0, 4): |
| 75 | r = probe(idx) |
| 76 | console.print(f"[bold]Device {idx}[/bold]") |
| 77 | if not r["opened"]: |
| 78 | console.print(" [dim]not openable[/dim]\n") |
| 79 | continue |
| 80 | console.print( |
| 81 | f" default: {r['default_size'][0]}x{r['default_size'][1]} " |
| 82 | f"fourcc={r['default_fourcc']} fps={r['default_fps']:.1f}" |
| 83 | ) |
| 84 | console.print( |
| 85 | f" after set MJPG+1080p60: {r['set_size'][0]}x{r['set_size'][1]} " |
| 86 | f"fourcc={r['set_fourcc']} fps={r['set_fps']:.1f}" |
| 87 | ) |
| 88 | if "frame_shape" in r: |
| 89 | console.print( |
| 90 | f" captured {r['frames']} frames in {r['elapsed']:.2f}s = [bold]{r['fps']:.2f} FPS[/bold] " |
| 91 | f"shape={r['frame_shape']}" |
| 92 | ) |
| 93 | b = r["brightness_mean"] |
| 94 | std = r["brightness_std"] |
| 95 | tag = "[red]ALL BLACK (no signal or HDCP block)[/red]" if b < 3.0 else ( |
| 96 | "[yellow]very dim[/yellow]" if b < 20.0 else "[green]normal[/green]") |
| 97 | console.print(f" brightness: mean={b:.2f} std={std:.2f} {tag}") |
| 98 | console.print(f" saved first frame: {r['saved']}") |
| 99 | else: |
| 100 | console.print(" [red]opened but produced no frames[/red]") |
| 101 | console.print("") |
| 102 | return 0 |
| 103 | |
| 104 | if __name__ == "__main__": |
| 105 | sys.exit(main()) |