main
anastasia Premiere Pro Bridge 0.1.0: Rust bridge, UXP plugin, CEP helper, skills, macOS manifest 32fb583 10d ago
// Adom Bridge — ExtendScript side of the CEP helper (ES3: no JSON, no let/const).
// adom_dispatch(jsonString) -> JSON string {ok, result|error, errorCode}

function adom_json(v) {
  var t = typeof v;
  if (v === null || v === undefined) return "null";
  if (t === "number") return isFinite(v) ? String(v) : "null";
  if (t === "boolean") return v ? "true" : "false";
  if (t === "string") {
    return '"' + v.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t") + '"';
  }
  if (v instanceof Array) {
    var a = [];
    for (var i = 0; i < v.length; i++) a.push(adom_json(v[i]));
    return "[" + a.join(",") + "]";
  }
  if (t === "object") {
    var parts = [];
    for (var k in v) {
      if (v.hasOwnProperty(k) && typeof v[k] !== "function") parts.push(adom_json(String(k)) + ":" + adom_json(v[k]));
    }
    return "{" + parts.join(",") + "}";
  }
  return adom_json(String(v));
}

function adom_hello() {
  var proj = app.project ? app.project.name : null;
  var seq = app.project && app.project.activeSequence ? app.project.activeSequence.name : null;
  return adom_json({ premiereVersion: app.version, build: app.build, project: proj, sequence: seq });
}

function adom_fail(code, msg) {
  var e = new Error(msg);
  e.adomCode = code;
  return e;
}

function adom_seq() {
  var seq = app.project.activeSequence;
  if (!seq) throw adom_fail("no_sequence", "No active sequence");
  app.enableQE();
  var qseq = qe.project.getActiveSequence();
  if (!qseq) throw adom_fail("no_sequence", "QE has no active sequence");
  return { seq: seq, qseq: qseq };
}

// "V1" -> {video:true, index:0}
function adom_track(ref) {
  var m = /^([VvAa])(\d+)$/.exec(String(ref));
  if (!m) throw adom_fail("bad_track", "Bad track '" + ref + "' (use V1, A2 …)");
  return { video: m[1].toUpperCase() === "V", index: parseInt(m[2], 10) - 1 };
}

function adom_timecode(seq, seconds) {
  var t = new Time();
  t.seconds = Number(seconds);
  var s = seq.getSettings();
  return t.getFormatted(s.videoFrameRate, s.videoDisplayFormat);
}

// The QE item at (track, clipIndex) where clipIndex counts real clips in time order —
// the same numbering the UXP side uses ("V1:0"). QE lists gaps as "Empty" items.
function adom_qeClip(qtrack, clipIndex) {
  var n = qtrack.numItems;
  var k = 0;
  for (var i = 0; i < n; i++) {
    var it = qtrack.getItemAt(i);
    if (!it || it.type === "Empty") continue;
    if (k === clipIndex) return it;
    k++;
  }
  throw adom_fail("clip_not_found", "No clip index " + clipIndex + " on that track");
}

var ADOM_VERBS = {
  ping: function () {
    return eval("(" + adom_hello() + ")");
  },

  razor: function (a) {
    var c = adom_seq();
    var tc = adom_timecode(c.seq, a.time);
    var cut = [];
    var i;
    var want = null;
    if (a.tracks) {
      want = {};
      for (i = 0; i < a.tracks.length; i++) want[String(a.tracks[i]).toUpperCase()] = true;
    }
    for (i = 0; i < c.qseq.numVideoTracks; i++) {
      if (want && !want["V" + (i + 1)]) continue;
      c.qseq.getVideoTrackAt(i).razor(tc);
      cut.push("V" + (i + 1));
    }
    for (i = 0; i < c.qseq.numAudioTracks; i++) {
      if (want && !want["A" + (i + 1)]) continue;
      c.qseq.getAudioTrackAt(i).razor(tc);
      cut.push("A" + (i + 1));
    }
    return { cutTracks: cut, timecode: tc, method: "qe-razor" };
  },

  set_speed: function (a) {
    var c = adom_seq();
    var m = /^([VvAa]\d+):(\d+)$/.exec(String(a.clip));
    if (!m) throw adom_fail("bad_clip", "clip must look like V1:0");
    var tr = adom_track(m[1]);
    var qtrack = tr.video ? c.qseq.getVideoTrackAt(tr.index) : c.qseq.getAudioTrackAt(tr.index);
    var item = adom_qeClip(qtrack, parseInt(m[2], 10));
    // setSpeed(speed, unused, reverse, ripple, maintainAudioPitch)
    item.setSpeed(Number(a.speed), "", !!a.reverse, !!a.ripple, a.maintainPitch !== false);
    return { clip: a.clip, speed: Number(a.speed), reverse: !!a.reverse, ripple: !!a.ripple };
  },

  add_tracks: function (a) {
    var c = adom_seq();
    var v = Number(a.video || 0);
    var au = Number(a.audio || 0);
    // addTracks(numVideo, insertAfterVideoIdx, numAudio, audioType(0 mono,1 stereo,2 5.1), insertAfterAudioIdx, numSubmix, submixType, insertAfterSubmix)
    c.qseq.addTracks(v, c.qseq.numVideoTracks, au, a.audioType === "mono" ? 0 : a.audioType === "5.1" ? 2 : 1, c.qseq.numAudioTracks, 0, 0, 0);
    return { videoTracks: c.qseq.numVideoTracks, audioTracks: c.qseq.numAudioTracks };
  },

  remove_track: function (a) {
    var c = adom_seq();
    var tr = adom_track(a.track);
    if (tr.video) c.qseq.removeVideoTrack(tr.index);
    else c.qseq.removeAudioTrack(tr.index);
    return { removed: a.track };
  },

  lock_track: function (a) {
    var c = adom_seq();
    var tr = adom_track(a.track);
    var t = tr.video ? c.seq.videoTracks[tr.index] : c.seq.audioTracks[tr.index];
    t.setLocked(a.locked !== false ? 1 : 0);
    return { track: a.track, locked: a.locked !== false };
  },

  set_track_target: function (a) {
    var c = adom_seq();
    var tr = adom_track(a.track);
    var t = tr.video ? c.seq.videoTracks[tr.index] : c.seq.audioTracks[tr.index];
    t.setTargeted(a.targeted !== false, true);
    return { track: a.track, targeted: a.targeted !== false };
  },

  export_frame_qe: function (a) {
    var c = adom_seq();
    var tc = adom_timecode(c.seq, a.time);
    c.qseq.exportFramePNG(tc, a.path);
    return { path: a.path, timecode: tc };
  },

  extendscript: function (a) {
    // a.code is evaluated as a function body; `return` a value. Has app, qe (after app.enableQE()).
    app.enableQE();
    var f = new Function("args", a.code);
    return { value: f(a.args || {}) };
  },
};

function adom_dispatch(payload) {
  try {
    var req = eval("(" + payload + ")");
    var fn = ADOM_VERBS[req.verb];
    if (!fn) return adom_json({ ok: false, errorCode: "not_in_cep", error: "CEP helper has no verb '" + req.verb + "'" });
    return adom_json({ ok: true, result: fn(req.args || {}) });
  } catch (e) {
    return adom_json({ ok: false, errorCode: e.adomCode || "extendscript_error", error: String(e.message || e), line: e.line });
  }
}