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,140 @@
|
||||
"""Clip de ajuste (adjustment layer) — criação do elemento FCPXML.
|
||||
|
||||
No Final Cut, uma "camada de ajuste" é um ``<clip>`` que carrega filtros
|
||||
(``filter-video`` / ``filter-audio``) diretamente como filhos — o DTD do
|
||||
FCPXML 1.13 não define nenhum elemento ``<adjustment>`` como wrapper (ver
|
||||
``<!ELEMENT clip>`` em ``FCPXMLv1_13.dtd``: ``filter-video``/``filter-audio``
|
||||
vêm depois de ``audio-channel-source*`` e antes de ``metadata?``, sem
|
||||
elemento intermediário). Tudo que está abaixo do clip na timeline herda
|
||||
esses filtros — é como se o efeito fosse aplicado a uma faixa inteira de
|
||||
uma vez.
|
||||
|
||||
Esta classe monta esse elemento a partir de dados de alto nível (duração +
|
||||
lista de ``EfeitoAjuste``), cuidando de criar os recursos ``<effect>``
|
||||
correspondentes na seção ``<resources>`` e de referenciá-los pelos filtros.
|
||||
"""
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
from ..models.timeline import EfeitoAjuste
|
||||
from ..models.timing import TimeValue
|
||||
|
||||
|
||||
def _para_racional(tempo) -> str:
|
||||
"""Aceita ``TimeValue`` ou uma string FCPXML já formatada ("90/30s")."""
|
||||
if isinstance(tempo, TimeValue):
|
||||
return tempo.to_fcpxml()
|
||||
if tempo is None:
|
||||
return "0/1s"
|
||||
return str(tempo)
|
||||
|
||||
|
||||
def _id_recurso_unico(resources: ET.Element, prefixo: str = "r_ajuste") -> str:
|
||||
"""Gera um ``id`` de recurso ainda ausente em ``resources``."""
|
||||
existentes = {r.get("id") for r in resources.findall("*") if r.get("id")}
|
||||
contador = 1
|
||||
while f"{prefixo}_{contador}" in existentes:
|
||||
contador += 1
|
||||
return f"{prefixo}_{contador}"
|
||||
|
||||
|
||||
class ClipDeAjuste:
|
||||
"""Cria um clip de ajuste (adjustment layer) pronto para a spine.
|
||||
|
||||
Exemplo::
|
||||
|
||||
from fcpxml.models.timing import TimeValue
|
||||
from fcpxml.models.timeline import EfeitoAjuste, ParametroEfeito
|
||||
from fcpxml.writer.adjustment import ClipDeAjuste
|
||||
|
||||
efeito = EfeitoAjuste(
|
||||
nome="Color Curves", uid="...UUID...", tipo="video",
|
||||
parametros=[ParametroEfeito(nome="Amount", valor="0.5",
|
||||
chave=".../9999")],
|
||||
)
|
||||
clip = ClipDeAjuste(
|
||||
nome="Ajuste de cor",
|
||||
duracao=TimeValue(300, 30),
|
||||
efeitos=[efeito],
|
||||
).criar(resources)
|
||||
spine.append(clip)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
nome: str,
|
||||
duracao,
|
||||
efeitos: List[EfeitoAjuste],
|
||||
offset=None,
|
||||
formato_tc: str = "NDF",
|
||||
):
|
||||
self.nome = nome
|
||||
self.duracao = duracao
|
||||
self.efeitos = efeitos
|
||||
self.offset = offset
|
||||
self.formato_tc = formato_tc
|
||||
|
||||
def criar(
|
||||
self,
|
||||
resources: ET.Element,
|
||||
proximo_id: Optional[Callable[[], str]] = None,
|
||||
) -> ET.Element:
|
||||
"""Monta o ``<clip>`` de ajuste e seus recursos ``<effect>``.
|
||||
|
||||
``resources`` é a seção ``<resources>`` do documento (onde os
|
||||
``<effect>`` são registrados). ``proximo_id`` é um gerador opcional
|
||||
de ids de recurso; sem ele, usa um id único baseado em ``resources``.
|
||||
"""
|
||||
def gerar_id() -> str:
|
||||
if proximo_id:
|
||||
return proximo_id()
|
||||
return _id_recurso_unico(resources)
|
||||
|
||||
filtros: List[ET.Element] = []
|
||||
for efeito in self.efeitos:
|
||||
efeito_id = self._garantir_recurso(resources, efeito, gerar_id)
|
||||
filtros.append(self._montar_filtro(efeito, efeito_id))
|
||||
|
||||
clip = ET.Element(
|
||||
"clip",
|
||||
name=self.nome,
|
||||
duration=_para_racional(self.duracao),
|
||||
tcFormat=self.formato_tc,
|
||||
)
|
||||
if self.offset is not None:
|
||||
clip.set("offset", _para_racional(self.offset))
|
||||
|
||||
# O DTD exige filter-video* antes de filter-audio* como filhos
|
||||
# diretos do clip (sem wrapper <adjustment>).
|
||||
for filtro in sorted(filtros, key=lambda f: f.tag != "filter-video"):
|
||||
clip.append(filtro)
|
||||
return clip
|
||||
|
||||
def _garantir_recurso(
|
||||
self, resources: ET.Element, efeito: EfeitoAjuste, gerar_id: Callable[[], str]
|
||||
) -> str:
|
||||
"""Devolve o ``id`` do ``<effect>`` de *efeito*, criando-o se ausente."""
|
||||
for existente in resources.findall("effect"):
|
||||
if existente.get("uid") == efeito.uid:
|
||||
return existente.get("id")
|
||||
efeito_id = gerar_id()
|
||||
recurso = ET.SubElement(resources, "effect")
|
||||
recurso.set("id", efeito_id)
|
||||
recurso.set("name", efeito.nome)
|
||||
recurso.set("uid", efeito.uid)
|
||||
return efeito_id
|
||||
|
||||
def _montar_filtro(self, efeito: EfeitoAjuste, efeito_id: str) -> ET.Element:
|
||||
"""Monta o ``<filter-video>``/``<filter-audio>`` de um efeito."""
|
||||
tag = "filter-video" if efeito.tipo == "video" else "filter-audio"
|
||||
filtro = ET.Element(tag, ref=efeito_id, name=efeito.nome)
|
||||
for parametro in efeito.parametros:
|
||||
param = ET.SubElement(filtro, "param")
|
||||
param.set("name", parametro.nome)
|
||||
if parametro.chave:
|
||||
param.set("key", parametro.chave)
|
||||
param.set("value", parametro.valor)
|
||||
if parametro.metadado:
|
||||
param.set("metadata", parametro.metadado)
|
||||
return filtro
|
||||
Reference in New Issue
Block a user