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>
184 lines
7.1 KiB
Python
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"
|