# Qawlak panels for Premiere Pro — UXP (P9-01a) and CEP (P5-08)

## UXP panel, Premiere Pro 25.6+ (primary, 0.3.0)

Files: `manifest.json` (manifestVersion 5, host `premierepro` ≥ 25.6.0), `uxp.html`, `uxp.js`, sharing
`qawlak-panel-core.js` with the CEP panel. Every host call is from Adobe's UXP Premiere API reference
(https://developer.adobe.com/premiere-pro/uxp/ppro-reference/, all "Since 25.6"):
`Project.getActiveProject` → `getActiveSequence` → `getVideoTrack(i).getTrackItems(Constants.TrackItemType.CLIP, false)`
→ `ClipProjectItem.cast(await item.getProjectItem()).getMediaFilePath()`; the source file is read in
ranges with UXP `fs.open`/`fs.read` and sent through the resumable upload; the SRT comes back shifted
into sequence time (`cue - inPoint + start`, clipped to the clip) and is written next to the media,
imported with `Project.importFiles`, and placed with `SequenceEditor.createOverwriteItemAction` inside
`lockedAccess` → `executeTransaction`, with every action created inside the lock (Premiere 26.3 made that
mandatory; Adobe changelog "Action creation in locks"). Both edits target the first track past the creator's
last video and audio track, so they can never overwrite footage. The key is kept in UXP `secureStorage`.

**Caption-track caveat, stated plainly (re-checked 16 Sep against Adobe's docs as of 14 Sep, Premiere 26.5):**
the UXP DOM still has no "SRT → caption track" call (`CaptionTrack` only reads, renames (26.3) and mutes;
`Sequence` only counts and gets caption tracks). The panel imports the SRT, overwrites it onto the sequence,
and reports **placed** only when `Sequence.getCaptionTrackCount()` actually grew; otherwise it tells the creator
to drag the imported `…-qawlak-<job>.srt` onto the timeline (Premiere asks for a start point: keep **Source timecode**; the cues are
already at the clip's sequence time). Each run gets its own file name, because Premiere keeps the first project item imported
under a name and a re-run under the same name placed the old captions (P29 live).
The documented call remains ExtendScript's `Sequence.createCaptionTrack`, which the CEP host below uses.

**One-click placement through the CEP panel (L30, Karan 16 Sep: "hand off to older panel").** UXP and CEP cannot call each
other in Premiere: CSXS events and `CSInterface` exist only in CEP, and UXP's inter-plugin communication
(`pluginManager` / `invokeCommand`) reaches other UXP plugins only (Adobe, "Inter Plugin Communication"). Both panels can
read and write files (UXP `localFileSystem: fullAccess`, CEP `--enable-nodejs`), so they share a folder,
`<home>/.qawlak/premiere-bridge` (`bridgeDir` in `qawlak-panel-core.js`, same rule in both):

1. While the CEP panel is open it writes `alive.json` (`{at}`) every second and looks for requests (`bridgeTick`).
2. After a run the UXP panel writes the SRT (already in sequence time), then `<id>.tmp` → renamed to `<id>.req.json` with
   `{srtPath, sequenceGuid, sequenceName, projectPath, at, created}`, but only if `alive.json` is under 5 s old.
3. The CEP panel claims a request by renaming it to `<id>.work`. A rename succeeds once, so two ticks or two panels never
   place the same request twice. It imports the SRT into the Qawlak bin of the project at `projectPath` and calls
   `createCaptionTrack` on the sequence with that id (`sequenceID`), or on the one sequence with that name, never simply
   the active one (`qawlakPlaceCaptions` in `host/qawlak.jsx`). It answers `<id>.done.json` `{placed, sequence, error}`.
   A request older than 60 s is answered `expired` and not placed.
4. The UXP panel waits 6 s for a claim. Not claimed: it deletes its request (so nothing can place it later) and falls back
   to import + the drag note, which now also says to open Window › Extensions › Qawlak. Claimed but no answer within 30 s:
   it says the older panel took the captions and to check the timeline, and never places them itself.

On Windows Premiere cannot start an invisible CEP extension on launch (CEP cookbook: `applicationActivate` StartOn is
macOS-only for PPRO), so the CEP panel has to be open (docked panels reopen with the workspace).

**Other L30 controls (Kalakar gap table #1–#6, #12–#14, #22)**, all against K30's contract (`research/plans/K30-plugin-api.md`):
- Pre-flight: before any export or upload, `ceil(seconds)` summed over the picked clips (source length from the project
  item's in/out), or the In→Out length, is compared with ping `seconds_left`, and each with `max_duration`; the credit wall
  or the length note shows at once.
- Minutes meter: a bar under Connect, measured against the highest balance the panel has seen (ping gives no plan total),
  refreshed after every run.
- What to caption: clips I pick (uploads the source file), or **Sequence In to Out**: `EncoderManager.exportSequence(sequence,
  ExportType.IMMEDIATELY, <project folder>/<sequence>-inout-<stamp>.mp3, <Premiere's own MP3 128kbps.epr>, false)`, uploaded,
  then deleted; cues move by the In point. Known Adobe issue (forum, DVAPR-4265965): with the Work Area Bar visible,
  26.x exports the work area instead of In/Out.
- Caption shape: words per caption, lines, max characters per line, delay (0.1 s steps), remove punctuation, remove
  tashkeel, digits (western / Arabic-Indic), all sent as K30 export params (`max_words`, `max_lines`, `max_chars`,
  `delay_ms`, `no_punct`, `no_tashkeel`, `digits`), so the server re-cuts with the studio's Arabic rule (never inside a word,
  a lam-alef or a fixed phrase). "Start each caption at its first spoken word" also fetches the per-word export and moves
  an early cue start to its first word (0 minutes; K30 also fixed this on the server for new transcripts).
- Arabizi: `lane=arabizi` (built from the Arabic transcript, 0 minutes); a non-Arabic project answers 422 and keeps its script.
- Recent jobs: `GET /api/plugin/jobs?limit=20`; each finished row can be placed again at the sequence In point (hand-off
  first) or saved as an SRT into the project, with today's caption options, without transcribing.
- Languages: Arabic dialects auto, Gulf, Gulf mixed with English, Egyptian, Levantine, Maghrebi, MSA; also English, Hindi,
  Urdu, Hindi mixed with English (`auto` + `hinglish`), Punjabi (`auto` + `punjabi`).
- Footer: "No tracking: no analytics, no session recording and no third-party chat in this panel."
Not in this round (next): caption templates and fonts, word editing, find and replace in the panel (#9–#11, #15, #16).
The optional alpha MOV is trimmed to the clip (`ClipProjectItem.createSetInOutPointsAction`) and overwritten
at the clip's start on a new top video track; placed only if the video track count grew.

Offline checks: `node --test tests/js/premiere-uxp.test.mjs` (mocked `premierepro` + `fs`; the mock throws
if an action is created outside `lockedAccess`) and `python scripts/premiere_ccx.py --selftest`.

### Load it in Premiere (no UXP Developer Tool needed)
A `.ccx` is a plain ZIP with `manifest.json` at its root (Adobe, "Package a UXP plugin"), and Creative Cloud
already ships Adobe's installer CLI (UPIA):

    python scripts/premiere_ccx.py --install                                   # RELEASE: dist\com.qawlak.premiere.uxp_premierepro.ccx, then UPIA /install
    python scripts/premiere_ccx.py --install --test-server http://127.0.0.1:PORT   # TEST: ..._premierepro-test.ccx, "Qawlak — قولك (test)"
    python scripts/premiere_ccx.py --list                                      # UPIA /list all
    python scripts/premiere_ccx.py --remove "<name as --list shows it>"

**Release vs test build (Karan, 16 Sep).** The repo `manifest.json` is the release one: network `["https://qawlak.com"]`,
`launchProcess` `https` only. `--test-server <url>` packages a test build whose network is `"all"` (and pack links may open
over http), because on Premiere 26.5 (P29 live) a listed `http://127.0.0.1:<port>` origin, with or without the port, was
refused with "Manifest entry not found". The server URL itself is typed in the panel (default `https://qawlak.com`). `--install` removes the old copy
first, because UPIA reports "Installation Successful" for the same version but keeps the old files (removal also clears
the panel's saved key and URL). Restart Premiere, then
**Window → UXP Plugins → Qawlak — قولك**. For live debugging (console, reload) install **UXP Developer Tools**
from Creative Cloud, tick Premiere → Settings → Plugins → **Enable developer mode**, and **Add Plugin** →
this folder's `manifest.json`. A throwaway server + plugin key: `python scripts/premiere_live_server.py`.
The full live script, with pass/fail per step, is `research/plans/P29-premiere-live.md`.

## CEP panel (0.2.0, signed; runs in Premiere Pro 26.5 on this PC since 15 Sep, 9536229)

Status: **panel files shipped (`CSXS/manifest.xml`, `index.html`, `panel.js`,
`qawlak-panel-core.js`, `host/qawlak.jsx`); the server logic is covered by
`node --test tests/js/premiere-panel.test.mjs`; the ExtendScript half can only be
verified inside Premiere** (live on 26.5, 15 Sep: export via AME, upload, transcribe, SRT written; Adobe supports
CEP/ExtendScript in Premiere only for a limited time after UXP, so the UXP panel is the one to keep). L30: it is signed
(`scripts/premiere_zxp.py`), places the UXP panel's captions while open, and has the same pre-flight, meter, languages,
caption-shape options and no-tracking line (no In/Out-only audio export, no recent jobs, no Arabizi: UXP only). The panel talks to the host
through the raw `window.__adobe_cep__` bridge (what Adobe's CSInterface.js wraps), so
no Adobe file is vendored.

## What the panel does

1. Settings: API key (`qwlk_…`, scope **plugin**, created on `plugins.html`) and server
   URL (`https://qawlak.com` by default; a local server needs the UXP test build).
2. Export the sequence's in/out via `app.encoder` (ExtendScript) to a temp MP4.
3. Resumable upload → transcribe → poll → download the SRT (and optionally the alpha MOV).
4. Import the SRT as a caption track / the MOV as a clip into the open project.

## Backend contract (all live, tested in `tests/test_phase2.py::test_p5_07_*`)

Every call carries `Authorization: Bearer qwlk_…`. Because the key is in a header, the
server skips its same-origin/CSRF rule for the request and answers CORS preflights
(`OPTIONS /api/*` → 204, `Access-Control-Allow-Origin: *`, allowed headers
`Authorization, Content-Type, X-Workspace-Id`), so a CEP panel served from a
`file://`/extension origin can call it directly.

| Step | Request | Response |
|---|---|---|
| Handshake | `GET /api/plugin/ping` | `{ok, workspace, minutes_left, seconds_left, min_duration, max_duration, max_duration_render, max_upload_bytes, chunk_size, low_balance, low_threshold_min, buy_url, scope:"plugin", version}`: `seconds_left` and `max_duration` drive the panels' pre-flight (K30); `low_balance` is true under `low_threshold_min` (5) minutes and `buy_url` (`/pricing.html#packs`, relative to the server) is where to buy a pack; 401 = bad or revoked key; 403 = an ordinary API key below Studio (use the workspace plugin key) |
| Begin upload | `POST /api/uploads` `{name, size}` | `{id, chunk_size, offset…}` |
| Send chunks | `PATCH /api/uploads/{id}?offset=N` raw body ≤ `chunk_size` | `{offset}` (409 on a wrong offset → re-read `GET /api/uploads/{id}`) |
| Finish | `POST /api/uploads/{id}/complete` | the project JSON (`id`, `duration`, …) with `auto_transcribe_reason`: null when a transcription started by itself, else why not (`no_credit`, `has_transcript`, `disabled`…) |
| Transcribe | `POST /api/projects/{pid}/transcribe` `{language:"ar", dialect:"gulf"}` | project with `status:"queued"`; 402 = not enough minutes for this file: `{detail:"Arabic. / English.", needed_seconds, available_seconds, buy_url}`; 409 = the project is still processing |
| Poll | `GET /api/projects/{pid}` every 2–3 s | `status` becomes `ready` (or `error` + `error` text). At the credit wall nothing runs: the project stays `uploaded`, and `GET /api/jobs` lists its transcribe job `failed` with `failure: {reason:"no_credit", needed_seconds, available_seconds}` |
| Captions | `GET /api/projects/{pid}/export/srt?lane=original` (`&words=true` for per-word cues; `lane=arabizi`; K30 `max_words`, `max_lines`, `max_chars`, `delay_ms`, `no_punct`, `no_tashkeel`, `digits`) | `text/plain` SRT (0 minutes) |
| Recent jobs | `GET /api/plugin/jobs?limit=20` | `[{id, name, status, language, dialect, duration, created, job, exports}]`, the key holder's own runs (K30) |
| Alpha layer (optional) | `POST /api/projects/{pid}/process/alpha` `{background:"transparent"}` then poll, then `GET …/export/mov` | ProRes 4444 MOV |

A plugin-scoped key is refused (403) on every other route, so a leaked panel key cannot
read the workspace, change settings or delete projects.

## Install (signed ZXP, no PlayerDebugMode)

    python scripts/premiere_zxp.py             # dist\com.qawlak.panel.zxp, signed with ZXPSignCmd and verified
    python scripts/premiere_zxp.py --install   # unpacks it into %APPDATA%\Adobe\CEP\extensions\com.qawlak.panel (replaces the dev copy)
    python scripts/premiere_zxp.py --selftest

`ZXPSignCmd.exe` 4.1.103 comes from Adobe's own repo (Adobe-CEP/CEP-Resources, `ZXPSignCMD/4.1.103/win64`; the script checks
its git blob id). The self-signed certificate (`qawlak-selfsigned.p12`, 10 years) and its random password (`password.txt`)
live in `%LOCALAPPDATA%\Qawlak-sign`, never in the repo. Restart Premiere → `Window > Extensions > Qawlak — قولك`, paste the
plugin key from `plugins.html` and press **Connect**. Keep it docked so the UXP panel can hand captions to it.
Adobe's known issue (Nov 2024): a signed ZXP installed through UPIA-based installers can show a blank panel if it
contains symlinks; this one is staged from plain copies and unpacked directly, and it is signed on Windows for Windows (a
macOS build needs the macOS ZXPSignCmd). Unsigned development copy: set `PlayerDebugMode` (`HKCU\Software\Adobe\CSXS.12`)
instead; the signed build does not need it.

## Selftest without Premiere

The panel's JS logic (chunking, offset recovery, polling back-off, SRT-to-caption
mapping) must run under `node --test tests/js/premiere-panel.test.mjs` with a fake
`fetch` — that is the studio agent's acceptance test; the server side is covered by
`tests/test_phase2.py::test_p5_07_plugin_keys_are_limited_to_upload_transcribe_export`.
