chore(fase0): higiene do repositório + corrige gitignore que escondia fcpxml/models/
Fase 0 do roteiro de reestruturação (Engine/docs/10_MAPA_REESTRUTURACAO.md): move code/WHISPERX (2,6 GB de backups órfãos, sem uso ativo, sem .gitmodules) para ~/Archives/G-ART-WHISPERX-backup fora do workspace git; traz admin/ para o gate de lint de run_after_fix.sh; corrige fcpxml/writer/adjustment.py, que gerava um wrapper <adjustment> inexistente no DTD 1.13 (filtros agora vão direto no <clip>, na ordem exigida), com teste de regressão novo. Achado à parte: .gitignore tinha uma regra solta "models/" (pensada só para o cache do Whisper em code/models/) que também escondia do git todo o pacote fcpxml/models/ — nunca commitado, sem proteção nenhuma. Corrigida para /code/models/, ancorada na raiz. Docs atualizados no mesmo commit (02_MODULES, 09_MANUTENCAO, 10_MAPA_REESTRUTURACAO, 05_EXPERIENCIAS #34 e #36), conforme a regra do CLAUDE.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
0fdfe33613
commit
d13f643ebc
@@ -0,0 +1,121 @@
|
||||
"""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)}"
|
||||
)
|
||||
Reference in New Issue
Block a user