Files
gart/code/fcpxml/models/enums.py
T
João HenriqueandClaude Sonnet 5 d13f643ebc 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>
2026-09-23 08:28:44 -04:00

184 lines
7.1 KiB
Python

"""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 (``<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. <chapter-marker> 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"