- Created analysis/visual_output_comparison.md with detailed architectural comparison - Added capture utilities for output comparison (capture_output.py, capture_upstream.py, compare_outputs.py) - Captured and compared output from upstream/main vs sideline branch - Documented fundamental architectural differences in rendering approaches - Updated Gitea issue #50 with findings
221 lines
6.6 KiB
Python
221 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Compare captured outputs from different branches or configurations.
|
|
|
|
This script loads two captured recordings and compares them frame-by-frame,
|
|
reporting any differences found.
|
|
"""
|
|
|
|
import argparse
|
|
import difflib
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
def load_recording(file_path: str) -> dict:
|
|
"""Load a recording from a JSON file."""
|
|
with open(file_path, "r") as f:
|
|
return json.load(f)
|
|
|
|
|
|
def compare_frame_buffers(buf1: list[str], buf2: list[str]) -> tuple[int, list[str]]:
|
|
"""Compare two frame buffers and return differences.
|
|
|
|
Returns:
|
|
tuple: (difference_count, list of difference descriptions)
|
|
"""
|
|
differences = []
|
|
|
|
# Check dimensions
|
|
if len(buf1) != len(buf2):
|
|
differences.append(f"Height mismatch: {len(buf1)} vs {len(buf2)}")
|
|
|
|
# Check each line
|
|
max_lines = max(len(buf1), len(buf2))
|
|
for i in range(max_lines):
|
|
if i >= len(buf1):
|
|
differences.append(f"Line {i}: Missing in first buffer")
|
|
continue
|
|
if i >= len(buf2):
|
|
differences.append(f"Line {i}: Missing in second buffer")
|
|
continue
|
|
|
|
line1 = buf1[i]
|
|
line2 = buf2[i]
|
|
|
|
if line1 != line2:
|
|
# Find the specific differences in the line
|
|
if len(line1) != len(line2):
|
|
differences.append(
|
|
f"Line {i}: Length mismatch ({len(line1)} vs {len(line2)})"
|
|
)
|
|
|
|
# Show a snippet of the difference
|
|
max_len = max(len(line1), len(line2))
|
|
snippet1 = line1[:50] + "..." if len(line1) > 50 else line1
|
|
snippet2 = line2[:50] + "..." if len(line2) > 50 else line2
|
|
differences.append(f"Line {i}: '{snippet1}' != '{snippet2}'")
|
|
|
|
return len(differences), differences
|
|
|
|
|
|
def compare_recordings(
|
|
recording1: dict, recording2: dict, max_frames: int = None
|
|
) -> dict:
|
|
"""Compare two recordings frame-by-frame.
|
|
|
|
Returns:
|
|
dict: Comparison results with summary and detailed differences
|
|
"""
|
|
results = {
|
|
"summary": {},
|
|
"frames": [],
|
|
"total_differences": 0,
|
|
"frames_with_differences": 0,
|
|
}
|
|
|
|
# Compare metadata
|
|
results["summary"]["recording1"] = {
|
|
"preset": recording1.get("preset", "unknown"),
|
|
"frame_count": recording1.get("frame_count", 0),
|
|
"width": recording1.get("width", 0),
|
|
"height": recording1.get("height", 0),
|
|
}
|
|
results["summary"]["recording2"] = {
|
|
"preset": recording2.get("preset", "unknown"),
|
|
"frame_count": recording2.get("frame_count", 0),
|
|
"width": recording2.get("width", 0),
|
|
"height": recording2.get("height", 0),
|
|
}
|
|
|
|
# Compare frames
|
|
frames1 = recording1.get("frames", [])
|
|
frames2 = recording2.get("frames", [])
|
|
|
|
num_frames = min(len(frames1), len(frames2))
|
|
if max_frames:
|
|
num_frames = min(num_frames, max_frames)
|
|
|
|
print(f"Comparing {num_frames} frames...")
|
|
|
|
for frame_idx in range(num_frames):
|
|
frame1 = frames1[frame_idx]
|
|
frame2 = frames2[frame_idx]
|
|
|
|
buf1 = frame1.get("buffer", [])
|
|
buf2 = frame2.get("buffer", [])
|
|
|
|
diff_count, differences = compare_frame_buffers(buf1, buf2)
|
|
|
|
if diff_count > 0:
|
|
results["total_differences"] += diff_count
|
|
results["frames_with_differences"] += 1
|
|
results["frames"].append(
|
|
{
|
|
"frame_number": frame_idx,
|
|
"differences": differences,
|
|
"diff_count": diff_count,
|
|
}
|
|
)
|
|
|
|
if frame_idx < 5: # Only print first 5 frames with differences
|
|
print(f"\nFrame {frame_idx} ({diff_count} differences):")
|
|
for diff in differences[:5]: # Limit to 5 differences per frame
|
|
print(f" - {diff}")
|
|
|
|
# Summary
|
|
results["summary"]["total_frames_compared"] = num_frames
|
|
results["summary"]["frames_with_differences"] = results["frames_with_differences"]
|
|
results["summary"]["total_differences"] = results["total_differences"]
|
|
results["summary"]["match_percentage"] = (
|
|
(1 - results["frames_with_differences"] / num_frames) * 100
|
|
if num_frames > 0
|
|
else 0
|
|
)
|
|
|
|
return results
|
|
|
|
|
|
def print_comparison_summary(results: dict):
|
|
"""Print a summary of the comparison results."""
|
|
print("\n" + "=" * 80)
|
|
print("COMPARISON SUMMARY")
|
|
print("=" * 80)
|
|
|
|
r1 = results["summary"]["recording1"]
|
|
r2 = results["summary"]["recording2"]
|
|
|
|
print(f"\nRecording 1: {r1['preset']}")
|
|
print(
|
|
f" Frames: {r1['frame_count']}, Width: {r1['width']}, Height: {r1['height']}"
|
|
)
|
|
|
|
print(f"\nRecording 2: {r2['preset']}")
|
|
print(
|
|
f" Frames: {r2['frame_count']}, Width: {r2['width']}, Height: {r2['height']}"
|
|
)
|
|
|
|
print(f"\nComparison:")
|
|
print(f" Frames compared: {results['summary']['total_frames_compared']}")
|
|
print(f" Frames with differences: {results['summary']['frames_with_differences']}")
|
|
print(f" Total differences: {results['summary']['total_differences']}")
|
|
print(f" Match percentage: {results['summary']['match_percentage']:.2f}%")
|
|
|
|
if results["summary"]["match_percentage"] == 100:
|
|
print("\n✓ Recordings match perfectly!")
|
|
else:
|
|
print("\n⚠ Recordings have differences.")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Compare captured outputs from different branches"
|
|
)
|
|
parser.add_argument(
|
|
"recording1",
|
|
help="First recording file (JSON)",
|
|
)
|
|
parser.add_argument(
|
|
"recording2",
|
|
help="Second recording file (JSON)",
|
|
)
|
|
parser.add_argument(
|
|
"--max-frames",
|
|
type=int,
|
|
help="Maximum number of frames to compare",
|
|
)
|
|
parser.add_argument(
|
|
"--output",
|
|
"-o",
|
|
help="Output file for detailed comparison results (JSON)",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Load recordings
|
|
print(f"Loading {args.recording1}...")
|
|
recording1 = load_recording(args.recording1)
|
|
|
|
print(f"Loading {args.recording2}...")
|
|
recording2 = load_recording(args.recording2)
|
|
|
|
# Compare
|
|
results = compare_recordings(recording1, recording2, args.max_frames)
|
|
|
|
# Print summary
|
|
print_comparison_summary(results)
|
|
|
|
# Save detailed results if requested
|
|
if args.output:
|
|
output_path = Path(args.output)
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(output_path, "w") as f:
|
|
json.dump(results, f, indent=2)
|
|
print(f"\nDetailed results saved to {args.output}")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
exit(main())
|