"""Achados de QC e o resultado de uma validação. Extraído de models.py — ver fcpxml/models/__init__.py. """ from dataclasses import dataclass, field from typing import Any, Dict, List, Optional from .enums import FlashFrameSeverity, ValidationIssueType from .timing import Timecode @dataclass class FlashFrame: """ Represents a detected flash frame (ultra-short clip). Flash frames are typically editing errors - clips that are too short to be perceived as intentional cuts. """ clip_name: str clip_id: str start: Timecode duration_frames: int duration_seconds: float severity: 'FlashFrameSeverity' @property def is_critical(self) -> bool: """Check if this is a critical flash frame.""" return self.severity == FlashFrameSeverity.CRITICAL @dataclass class GapInfo: """ Represents a detected gap in the timeline. Gaps can be intentional (black frames) or errors from deleted clips. """ start: Timecode duration_frames: int duration_seconds: float previous_clip: Optional[str] = None # Clip name before the gap next_clip: Optional[str] = None # Clip name after the gap @property def timecode(self) -> str: """Get timecode string for the gap start.""" return self.start.to_smpte() @dataclass class DuplicateGroup: """ Represents a group of clips using the same source media. Useful for detecting duplicate clips that may be unintentional. """ source_ref: str # The asset/media reference ID source_name: str # Human-readable source name clips: List[Dict[str, Any]] = field(default_factory=list) # List of clip info dicts @property def count(self) -> int: """Number of clips using this source.""" return len(self.clips) @property def has_overlapping_ranges(self) -> bool: """Check if any clips use overlapping portions of the source.""" # Sort clips by source_start sorted_clips = sorted(self.clips, key=lambda c: c.get('source_start', 0)) for i in range(len(sorted_clips) - 1): curr_end = sorted_clips[i].get('source_start', 0) + sorted_clips[i].get('source_duration', 0) next_start = sorted_clips[i + 1].get('source_start', 0) if curr_end > next_start: return True return False @dataclass class ValidationIssue: """ Represents a single validation issue found in a timeline. Used by validate_timeline to report problems. """ issue_type: 'ValidationIssueType' severity: str # "error", "warning", "info" message: str timecode: Optional[str] = None clip_name: Optional[str] = None details: Dict[str, Any] = field(default_factory=dict) @dataclass class ValidationResult: """ Result of timeline validation. Provides a health score and categorized list of issues. """ is_valid: bool health_score: int # 0-100 percentage issues: List[ValidationIssue] = field(default_factory=list) flash_frames: List[FlashFrame] = field(default_factory=list) gaps: List[GapInfo] = field(default_factory=list) duplicates: List[DuplicateGroup] = field(default_factory=list) @property def error_count(self) -> int: return len([i for i in self.issues if i.severity == "error"]) @property def warning_count(self) -> int: return len([i for i in self.issues if i.severity == "warning"]) def summary(self) -> str: """Generate a summary string.""" return ( f"Timeline Health: {self.health_score}% | " f"Errors: {self.error_count} | Warnings: {self.warning_count} | " f"Flash frames: {len(self.flash_frames)} | Gaps: {len(self.gaps)}" )