affaan-m/ECC · error · ValueError

target must be a distinct versioned timeline

Error message

target must be a distinct versioned timeline

What it means

The target timeline must have a non-empty name and must not be the same timeline as the source. The library enforces editing onto a distinct versioned copy so the source master timeline is never mutated. It reads the target's name via GetName() and rejects it if empty or equal to source_timeline.

Solutions

  1. Duplicate the timeline first (Timeline.DuplicateTimeline or CreateCompound/Save as version) and pass the new timeline object
  2. Compare timeline.GetName() to source_timeline in your script before calling and fail fast with a clearer message
  3. Set an explicit name on the target timeline in Resolve
  4. Ensure your clone/apply plumbing passes the duplicate, not the source

Example fix

// before
apply_placements(timeline, pool, placements, source_timeline=timeline.GetName(), ...)  # same timeline
// after
dup = pm.ImportTimeline(source_path) or timeline_media_pool.DuplicateTimeline(timeline)
apply_placements(dup, pool, placements, source_timeline=timeline.GetName(), ...)
Defensive patterns

Strategy: validation

Validate before calling

name = timeline.GetName()
if not name or name == source_timeline:
    raise ValueError("duplicate the source timeline and pass the copy as target")

Type guard

def is_distinct_target(timeline, source_name: str) -> bool:
    name = timeline.GetName()
    return isinstance(name, str) and bool(name) and name != source_name

Try / catch

try:
    apply_placements(target, pool, ...)
except ValueError as exc:
    if "distinct versioned timeline" in str(exc):
        print("Duplicate the source timeline before applying placements")
    raise

Prevention

When it happens

Trigger: Passing the source timeline object itself as the target; a timeline whose GetName() returns None/empty (unnamed or detached timeline); typo making both name arguments identical.

Common situations: Automation that clones timelines but still passes the original object; Resolve timelines created via API without a name set; scripts that lost track of which handle is the duplicate.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/62dcbe4c41e3be77. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/resolve.py:256

    source_end_mode,
    fps,
    base_track_count,
    probe=probe_asset,
):
    """Append and verify overlays; returns an in-memory placement receipt only.

    This does not save/export/render a project. Supply the selected target's
    media pool. Existing overlay tracks must be empty; base tracks are preserved.
    source_end_mode is required: use the endpoint convention verified on this
    Resolve host. No automatic retry occurs if that convention is incorrect.
    """
    if source_end_mode not in ("inclusive", "exclusive"):
        raise ValueError("source_end_mode must be inclusive or exclusive")
    if not isinstance(source_timeline, str) or not source_timeline:
        raise ValueError("source_timeline must be explicit")
    name = timeline.GetName()
    if not name or name == source_timeline:
        raise ValueError("target must be a distinct versioned timeline")
    plan = allocate_placements(
        placements, fps=fps, base_track_count=base_track_count, probe=probe
    )
    if _fps(timeline.GetSetting("timelineFrameRate")) != _fps(fps):
        raise ValueError("target timeline fps mismatch")
    count = timeline.GetTrackCount("video")
    if count < base_track_count:
        raise ValueError("base_track_count exceeds target video tracks")
    for track in range(base_track_count + 1, count + 1):
        if _items(timeline, "video", track):
            raise ValueError("target overlay tracks must be empty")
    before = _base_snapshot(timeline, base_track_count)
    for _ in range(count, max(event["track"] for event in plan)):
        old_count = timeline.GetTrackCount("video")
        if (
            not timeline.AddTrack("video")
            or timeline.GetTrackCount("video") != old_count + 1
        ):

View on GitHub (pinned to 8321021c54)