"""Clip de ajuste (adjustment layer) — criação do elemento FCPXML. No Final Cut, uma "camada de ajuste" é um ```` que carrega filtros (``filter-video`` / ``filter-audio``) diretamente como filhos — o DTD do FCPXML 1.13 não define nenhum elemento ```` como wrapper (ver ```` 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 ```` correspondentes na seção ```` 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 ```` de ajuste e seus recursos ````. ``resources`` é a seção ```` do documento (onde os ```` 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 ). 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 ```` 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 ````/```` 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