fix(fcpxml): tracking-shape id duplicado ao cortar clipe com Cinematic

Object-tracker/tracking-shape (dado de rastreamento de objeto preservado
do asset original) mantinha o mesmo id em cada deepcopy feito por
split_clip/cut_clip_ranges, e o FCP acabava rejeitando o arquivo com "ID
tr1 already defined" depois de vários cortes. Mesmo mecanismo do bug já
corrigido para text-style-def, agora coberto também para tracking-shape.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
João Henrique
2026-08-26 16:03:31 -04:00
co-authored by Claude Sonnet 5
parent 2ad5854570
commit 635d1bb553
3 changed files with 42 additions and 0 deletions
+2
View File
@@ -97,6 +97,8 @@ class ModifierCore:
self.fps = self._detect_fps() self.fps = self._detect_fps()
# Lazily filled on the first generated title; see _unique_text_style_id. # Lazily filled on the first generated title; see _unique_text_style_id.
self._text_style_ids: Optional[set] = None self._text_style_ids: Optional[set] = None
# Lazily filled on the first clip split/cut; see _unique_tracking_shape_id.
self._tracking_shape_ids: Optional[set] = None
self._build_resource_index() self._build_resource_index()
self._build_clip_index() self._build_clip_index()
+2
View File
@@ -125,6 +125,7 @@ class CutMixin:
new_clip, current_start, segment_duration new_clip, current_start, segment_duration
) )
self._reassign_text_style_ids(new_clip) self._reassign_text_style_ids(new_clip)
self._reassign_tracking_shape_ids(new_clip)
spine.insert(clip_index + len(new_clips), new_clip) spine.insert(clip_index + len(new_clips), new_clip)
new_clips.append(new_clip) new_clips.append(new_clip)
@@ -243,6 +244,7 @@ class CutMixin:
new_clip.set('duration', seg_duration.to_fcpxml()) new_clip.set('duration', seg_duration.to_fcpxml())
self._filter_children_for_segment(new_clip, seg_start, seg_duration) self._filter_children_for_segment(new_clip, seg_start, seg_duration)
self._reassign_text_style_ids(new_clip) self._reassign_text_style_ids(new_clip)
self._reassign_tracking_shape_ids(new_clip)
spine.insert(clip_index + len(new_clips), new_clip) spine.insert(clip_index + len(new_clips), new_clip)
new_clips.append(new_clip) new_clips.append(new_clip)
current_offset = current_offset + seg_duration current_offset = current_offset + seg_duration
+38
View File
@@ -164,6 +164,44 @@ class TitlesMixin:
for ref_el in clip.findall(f".//text-style[@ref='{old_id}']"): for ref_el in clip.findall(f".//text-style[@ref='{old_id}']"):
ref_el.set('ref', new_id) ref_el.set('ref', new_id)
def _unique_tracking_shape_id(self, base: str) -> str:
"""Return a document-unique ``id`` for a ``<tracking-shape>``."""
stem = base or "tr"
if self._tracking_shape_ids is None:
self._tracking_shape_ids = {
ts.get('id') for ts in self.root.findall('.//tracking-shape')
}
candidate = f"{stem}_0"
counter = 0
while candidate in self._tracking_shape_ids:
counter += 1
candidate = f"{stem}_{counter}"
self._tracking_shape_ids.add(candidate)
return candidate
def _reassign_tracking_shape_ids(self, clip: ET.Element) -> None:
"""Give every ``<tracking-shape>`` inside a just-deepcopy'd *clip* a
fresh document-unique id.
Same mechanism as ``_reassign_text_style_ids``: ``split_clip``/
``cut_clip_ranges`` deepcopy the clip once per resulting segment, so
Cinematic object-tracking data (``<object-tracker><tracking-shape
id="tr1">``, preserved from the source asset's sidecar) keeps the
exact same id in every copy. A single cut is harmless — but the
batch chain re-cuts the same clip at each step, multiplying the
duplicate until the DTD validator rejects the file with "ID tr1
already defined".
"""
for shape in clip.findall('.//tracking-shape'):
old_id = shape.get('id')
if not old_id:
continue
base = re.sub(r'_\d+$', '', old_id)
new_id = self._unique_tracking_shape_id(base)
if new_id == old_id:
continue
shape.set('id', new_id)
def _make_text_title_clip( def _make_text_title_clip(
self, self,
effect_id: str, effect_id: str,