"""Enumerações do domínio: tipos e cores de marcador, transições, ritmo. Extraído de models.py — ver fcpxml/models/__init__.py. """ from enum import Enum # Maximum length for marker type strings to prevent memory abuse _MAX_MARKER_TYPE_LENGTH = 64 class MarkerType(Enum): """Types of markers in Final Cut Pro. Members: STANDARD — Default marker with no completion state. INCOMPLETE — Task marker (completed="0" in FCPXML). ← canonical name TODO — Alias for INCOMPLETE. Kept for backward compatibility; resolves to the same object (``MarkerType.TODO is MarkerType.INCOMPLETE``). Python enums treat the first member with a given value as canonical; all subsequent members sharing that value become aliases. CHAPTER — Chapter marker (```` element). COMPLETED — Task marker with completed="1". Serialization helpers: ``from_string()`` — Accepts values, names, and legacy aliases (e.g. ``"todo-marker"``). Always returns the canonical member. ``from_xml_element()`` — Reads an ``lxml``/``ElementTree`` element and returns the appropriate type based on the tag name and ``completed`` attribute. ``xml_tag`` — The FCPXML element tag to emit when writing. ``xml_attrs`` — Extra attributes required when writing (e.g. ``completed="0"`` for INCOMPLETE). """ STANDARD = "standard" INCOMPLETE = "todo" TODO = "todo" # Backward-compat alias — resolves to INCOMPLETE at runtime CHAPTER = "chapter" COMPLETED = "completed" @classmethod def from_string(cls, value: str) -> 'MarkerType': """Convert a string to MarkerType, accepting both enum names and values. Includes input validation: rejects null bytes, control characters, and excessively long strings to prevent injection and memory abuse. Examples: MarkerType.from_string("todo") -> MarkerType.INCOMPLETE MarkerType.from_string("TODO") -> MarkerType.INCOMPLETE MarkerType.from_string("completed") -> MarkerType.COMPLETED """ if not isinstance(value, str): raise TypeError(f"Expected str, got {type(value).__name__}") if '\x00' in value or any(ord(c) < 32 and c not in ('\n', '\r', '\t') for c in value): raise ValueError("Marker type contains invalid control characters") if len(value) > _MAX_MARKER_TYPE_LENGTH: raise ValueError( f"Marker type exceeds maximum length ({_MAX_MARKER_TYPE_LENGTH} chars)" ) lowered = value.strip().lower() if not lowered: raise ValueError("Marker type cannot be empty") # Accept legacy aliases from older specs (e.g. "todo-marker" → INCOMPLETE) aliases = { "todo-marker": "todo", "completed-marker": "completed", "chapter-marker": "chapter", } lowered = aliases.get(lowered, lowered) try: return cls(lowered) except ValueError: raise ValueError( f"Invalid marker type: '{value}'. " f"Valid types: {', '.join(m.value for m in cls)}" ) @classmethod def from_xml_element(cls, elem) -> 'MarkerType': """Determine MarkerType from an XML element's tag and attributes. Centralises the parse-side mapping so the parser doesn't need to know about completed-attribute semantics. Rules (in priority order): 1. tag → CHAPTER (completed attr ignored) 2. completed='0' (exact) → INCOMPLETE 3. completed='1' (exact) → COMPLETED 4. Everything else → STANDARD (including whitespace-padded, absent, empty, or non-boolean completed values) Matching is intentionally strict — no .strip(), no case folding. This prevents whitespace-injected attributes like ' 0 ' from being misclassified. """ if elem.tag == 'chapter-marker': return cls.CHAPTER completed = elem.get('completed') if completed == '0': return cls.INCOMPLETE if completed == '1': return cls.COMPLETED return cls.STANDARD @property def xml_tag(self) -> str: """Return the FCPXML element tag for this marker type.""" return 'chapter-marker' if self == MarkerType.CHAPTER else 'marker' @property def xml_attrs(self) -> dict: """Return extra XML attributes this marker type requires when writing. Centralises the write-side mapping so both FCPXMLModifier and FCPXMLWriter use a single source of truth. """ if self == MarkerType.CHAPTER: return {'posterOffset': '0s'} if self == MarkerType.INCOMPLETE: return {'completed': '0'} if self == MarkerType.COMPLETED: return {'completed': '1'} return {} # Recognised marker XML tags — used by the parser for single-pass collection # and by the writer to validate element creation. MARKER_XML_TAGS = ('marker', 'chapter-marker') class MarkerColor(Enum): """Marker color options (FCP internal values).""" BLUE = 0 CYAN = 1 GREEN = 2 YELLOW = 3 ORANGE = 4 RED = 5 PINK = 6 PURPLE = 7 class TransitionType(Enum): """Built-in transition types.""" CROSS_DISSOLVE = "Cross Dissolve" FADE_TO_BLACK = "Fade to Color" FADE_FROM_BLACK = "Fade from Color" DIP_TO_COLOR = "Dip to Color" WIPE = "Wipe" SLIDE = "Slide" class PacingStyle(Enum): """Pacing presets for rough cut generation.""" SLOW = "slow" # 5-10 second cuts MEDIUM = "medium" # 2-5 second cuts FAST = "fast" # 0.5-2 second cuts DYNAMIC = "dynamic" # Varies throughout class FlashFrameSeverity(Enum): """Severity levels for flash frame detection.""" CRITICAL = "critical" # < 2 frames, almost certainly an error WARNING = "warning" # < 6 frames, potentially intentional but suspicious class PacingCurve(Enum): """Pacing curves for montage generation.""" CONSTANT = "constant" # Same clip duration throughout ACCELERATING = "accelerating" # Starts slow, gets faster DECELERATING = "decelerating" # Starts fast, gets slower PYRAMID = "pyramid" # Slow → fast → slow class ValidationIssueType(Enum): """Types of timeline validation issues.""" FLASH_FRAME = "flash_frame" GAP = "gap" DUPLICATE = "duplicate" ORPHAN_REF = "orphan_ref" INVALID_OFFSET = "invalid_offset" # DTD validation types (v0.6.0) ELEMENT_ORDER = "element_order" MISSING_ATTRIBUTE = "missing_attribute" INVALID_TIMEBASE = "invalid_timebase" FRAME_MISALIGNMENT = "frame_misalignment" MISSING_EFFECT_REF = "missing_effect_ref" MISSING_MEDIA_REP = "missing_media_rep"