[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6XCXM02QFKHW8MXJG23YMFC":3,"skill-same:01M6XCXM02QFKHW8MXJG23YMFC":101},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":37,"linkOnly":38,"markdownContent":39,"markdownBody":40,"frontMatter":41,"refName":42,"commitSha":43,"skillDirectory":44,"forksCount":14,"repositoryHtmlUrl":45,"repositoryHomepageUrl":6,"ownerHtmlUrl":46,"ownerBlogUrl":47,"ownerDescription":6,"updatedAt":48,"install":49,"resources":62},"01M6XCXM02QFKHW8MXJG23YMFC","bitmovin-encoding-live",null,"Guide a user through creating and starting a Bitmovin live encoding (RTMP, redundant RTMP, SRT, or a custom-built template assembled from the user's free-text use case using the Encoding Template schema and a cross-field rulebook) end-to-end via the Bitmovin CLI's Encoding Templates commands.","bitmovin\u002Fskills","bitmovin","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F3273006?v=4","01M6XCX24ZDCBTPHWAAM18VJQY","Bitmovin","01M6XCX29H5E446MM0E5XAVQTC",1,"MIT","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-encoding-live\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],"encoding","bitmovin encoding live","live","template","text","user","guide","use","schema","commands","templates","end","free","via","case","srt","custom","cli","2026-11-10 10:12:30",false,"---\nname: bitmovin-encoding-live\ndescription: Guide a user through creating and starting a Bitmovin live encoding (RTMP, redundant RTMP, SRT, or a custom-built template assembled from the user's free-text use case using the Encoding Template schema and a cross-field rulebook) end-to-end via the Bitmovin CLI's Encoding Templates commands.\n---\n\n# Bitmovin Live Encoding Skill\n\nWalks the user through configuring and starting a Bitmovin live encoding. The\nheavy lifting is done by the Bitmovin **Encoding Templates API**: a single\n`POST \u002Fencoding\u002Ftemplates\u002Fstart` creates inputs, codec configs, encoding,\nstreams, muxings, manifests **and** starts the live encoding from one YAML\ndocument.\n\nThis skill renders that YAML from a small parameter file, validates and\nsubmits it via the **Bitmovin CLI** (`bitmovin encoding ...`), polls until\nthe encoder is `RUNNING`, and asks the CLI for the ingest details.\n\n## Prerequisites\n\n- **Bitmovin CLI** (`@bitmovin\u002Fcli`, ≥ 0.3.0) — drives `templates validate`,\n  `templates start`, `outputs list\u002Fcreate`, `jobs get`, `jobs status`,\n  `jobs live`, `jobs stop`. `jobs live` (added in 0.3.0) returns\n  `encoderIp` plus the per-template ingest details (`streamKeys[]` for\n  RTMP \u002F redundant RTMP, `srtInputs[]` with mode\u002Fhost\u002Fport\u002Fpath for SRT)\n  and is the reason this skill no longer ships its own polling helper.\n  Install with `npm install -g @bitmovin\u002Fcli`.\n- **Python ≥ 3.10** — Step 1's `scripts\u002Fensure_venv.sh` auto-creates a\n  dedicated venv and installs `pyyaml` and `jinja2` into it if they aren't\n  already importable from the user's `python3`. Used only by the inline\n  Jinja render block in Step 5; every other workflow step is the CLI.\n- `BITMOVIN_API_KEY` environment variable set.\n- For creating a new output (optional): `BITMOVIN_OUTPUT_ACCESS_KEY` \u002F\n  `BITMOVIN_OUTPUT_SECRET_KEY` env vars (see Step 3). Credentials are read\n  from the environment and passed to the CLI via shell expansion at\n  invocation time — never hardcoded into scripts, the params file, or the\n  rendered template.\n\nThe skill never embeds secrets in the rendered template or log output.\n\n## Path Conventions\n\n- `SKILL_DIR`: install location of this skill (e.g.\n  `~\u002F.claude\u002Fskills\u002Fbitmovin-encoding-live` or\n  `~\u002F.codex\u002Fskills\u002Fbitmovin-encoding-live`). Used to locate `scripts\u002F` and\n  `templates\u002F`.\n- `RUN_DIR`: per-run cache directory at\n  `~\u002F.cache\u002Fbitmovin\u002Fbitmovin-encoding-live\u002F\u003Crun-id>\u002F`, where `\u003Crun-id>` is\n  `YYYYMMDD-HHMMSS-\u003Cshort-hash>`. Holds the rendered template and a copy\n  of the params file. RUN_DIR MUST stay outside the skill directory so its\n  contents are never published or installed.\n\n## Scripts\n\n| Script | Purpose |\n|---|---|\n| `scripts\u002Fensure_venv.sh` | Prints the path to a Python interpreter that has `pyyaml` and `jinja2` available. Uses the user's `python3` if it already has them; otherwise creates \u002F reuses a venv at `~\u002F.cache\u002Fbitmovin\u002Fbitmovin-encoding-live\u002F.venv\u002F` and installs the deps there. |\n| `scripts\u002Fvalidate_rules.py \u003Ctemplate.yaml>` | Runs cross-field rule checks (see `references\u002Frulebook.yaml`) against a rendered Encoding Template. Catches semantic errors the JSON schema can't express — live-encoding-mode subset, fMP4-stream-count, AV1 min bitrate, the `inputs.rtmp` silent-drop, `$\u002F...` streamKeyId refs that don't resolve. Exits non-zero on violations. Always run between `bitmovin encoding templates validate` and `bitmovin encoding templates start`. |\n\nAll other workflow steps — output listing \u002F creation, template validation\n\u002F submission, status polling, ingest details, stop — are driven directly\nby the Bitmovin CLI from the inline shell snippets in this document.\nSKILL.md is responsible for prompting the user.\n\n## References\n\n| File | Purpose |\n|---|---|\n| `references\u002Frulebook.yaml` | Cross-field semantic rules for live encodings, derived from Bitmovin's server-side validators plus live-ingest specifics observed in practice. Each rule has an `id`, a `rule` description, and an `applies_when` predicate. Consulted during the custom-build flow (Step 2b) to prune options the API would reject; enforced post-render by `validate_rules.py`. |\n\n## Templates\n\nJinja2 templates in `\u003CSKILL_DIR>\u002Ftemplates\u002F`:\n\n| Template | Use case |\n|---|---|\n| `rtmp-live.yaml.j2` | Single RTMP ingest point. Uses `inputs.redundantRtmp` with one `staticIngestPoint` — Bitmovin's API has no separate non-redundant RTMP input type. |\n| `redundant-rtmp-live.yaml.j2` | Two RTMP ingest points for HA ingest. |\n| `srt-live.yaml.j2` | SRT ingest (CALLER or LISTENER mode). |\n\nAll templates produce CMAF-style output: fmp4 muxings serving both DASH and\nHLS manifests with `manifestGenerator: V2`. Customers can hand-edit the\nrendered YAML before `bitmovin encoding templates start` if they need\nvariants (additional renditions, different segment naming, additional\nmuxing types).\n\n`\u003CSKILL_DIR>\u002Fexamples\u002Fparams.example.yaml` documents every parameter.\n\n## Workflow\n\n### Step 1 — Verify prerequisites\n\nConfirm the Bitmovin CLI is installed and resolve the Python interpreter\nthat has the runtime deps (used by the inline Jinja render in Step 5):\n\n```bash\ncommand -v bitmovin >\u002Fdev\u002Fnull \\\n  || { echo \"error: bitmovin CLI not found. Install with: npm install -g @bitmovin\u002Fcli\" >&2; exit 1; }\nPYTHON=$(bash \u003CSKILL_DIR>\u002Fscripts\u002Fensure_venv.sh) || exit 1\ntest -n \"$BITMOVIN_API_KEY\" || { echo \"error: BITMOVIN_API_KEY is not set\" >&2; exit 1; }\necho \"OK ($PYTHON)\"\n```\n\n`$PYTHON` is reused in Step 5. If `BITMOVIN_API_KEY` is empty, ask the user\nto export it before continuing. The CLI reads the key from the same env\nvar, so no extra config is required.\n\nIf `python3` itself is missing, `ensure_venv.sh` prints a clear error and\nexits non-zero — point the user at https:\u002F\u002Fwww.python.org\u002Fdownloads\u002F\n(Python ≥ 3.10).\n\n### Step 2 — Pick scenario\n\nAsk the user which scenario fits. These are the only valid choices —\ndo **not** offer an `Other (specify)` fallback. The fourth option is the\nexplicit escape hatch:\n\n1. `rtmp` — single RTMP ingest (most common)\n2. `redundant-rtmp` — two RTMP ingest points for HA\n3. `srt` — SRT ingest\n4. `custom` — none of the above fits; describe the use case in free text\n   and the skill assembles a template from the schema + rulebook (see\n   Step 2b)\n\nFor options 1–3, map to the matching template under\n`\u003CSKILL_DIR>\u002Ftemplates\u002F`. For option 4, jump to **Step 2b** before Step 3.\n\n### Step 2b — Custom build (only when scenario = `custom`)\n\nThe user has described a use case the three prebaked scenarios don't fit\n(e.g. HEVC ingest + AV1 distribution, Zixi or HLS pulled-input, a custom\nladder with non-standard segment naming, DRM-wrapped HLS). The skill\nguides them through assembling a template from primitives, filtering\noptions at each decision using the cross-field rulebook so choices that\nthe API would reject never appear.\n\n**Sources of truth, in order of authority:**\n\n1. `\u003CSKILL_DIR>\u002Freferences\u002Frulebook.yaml` — semantic compatibility rules\n   derived from Bitmovin's server-side validators. Each rule has an\n   `applies_when` predicate; if it matches the user's choices, apply the\n   rule's constraint. Skip rules tagged `scope: vod-only` — they don't\n   apply to live encodings.\n2. `~\u002F.config\u002Fbitmovin\u002Ftemplate-schema-v1.json` — the published Encoding\n   Template JSON Schema (24h-cached by `bitmovin encoding templates\n   validate`). Use it to discover allowed values for enum fields (input\n   types, codec types, manifest types, DRM types, ACL permissions,\n   `liveEncodingMode`, …). If the cache is missing, run\n   `bitmovin encoding templates validate` against any prebaked template\n   first to warm it.\n3. `\u003CSKILL_DIR>\u002Ftemplates\u002F*.yaml.j2` — worked examples of valid live\n   template shapes. Read at least one before authoring (`rtmp-live` is\n   the simplest reference).\n\n**Workflow:**\n\n1. **Capture the use case.** Take the user's free-text description.\n   Re-state your understanding back in one sentence and confirm before\n   continuing.\n\n2. **Walk a decision tree.** Ask **one question at a time** with a\n   selectable picker. After each answer, consult the rulebook: drop any\n   downstream option whose `applies_when` would conflict with the\n   answers so far. Order:\n\n   a. **Input type** — choose from `inputs.*` enum values that make\n      sense for live: `redundantRtmp` (single or HA RTMP via\n      staticIngestPoints), `srt` (LISTENER \u002F CALLER), `zixi`, `hls`\n      (live-from-stream pull). Apply rule `live.rtmp_input_type` —\n      never offer `inputs.rtmp`, that key is silently dropped.\n   b. **Stream-key handling (RTMP variants only)** — `Use my own`\n      (Step 4b pre-creates \u002F reuses; ASSIGN with concrete UUID) or\n      `Auto-generate` (API mints, GENERATE). Apply rule\n      `live.stream_key_assignment` — `streamKeyId` must be a real\n      UUID, never a `$\u002F...` ref.\n   c. **Video codec(s)** — h264 (broadest device support), h265\n      (~50% bandwidth savings), av1 (~50% savings, newer; rule\n      `codec.av1.min_bitrate` requires bitrate ≥ 10000 — live has no\n      per-title carve-out, so always set bitrate). Multi-codec ladders\n      allowed.\n   d. **Encoding mode** — `STANDARD` (default), `SINGLE_PASS`, or\n      `TWO_PASS`. Apply rule `encoding_mode.live_subset` — never\n      offer `THREE_PASS`, the API rejects it for live even though the\n      schema enum includes it.\n   e. **Video ladder** — fixed list of `{height, bitrate}`. Per-Title\n      is **VOD-only** (rule `hw.no_per_title` is for hardware; for\n      live, per-title simply isn't a live.start primitive). Multi-stream\n      live encodings hit rule `encoding_mode.all_video_streams_match`\n      — pick one mode for all video streams.\n   f. **Audio** — AAC (default), Dolby Atmos \u002F DD+ (rule\n      `dd.drm_encoder_features` gates with DRM; rule\n      `dd.invalid_filters` blocks AUDIO_VOLUME \u002F WATERMARK \u002F\n      ENHANCED_WATERMARK), pass-through, or none (video-only).\n   g. **Muxing format** — fmp4 (CMAF-style; works with DASH and HLS;\n      rule `muxing.fmp4.exactly_one_stream` means one muxing per\n      stream), TS (HLS only). Live encodings rarely use progressive\n      MP4 since there's nothing to download until the encoding stops.\n   h. **Manifest format** — DASH only, HLS only, both, none. Pin\n      `manifestGenerator: V2` (rule `live.manifest_generator_v2`).\n      **V1 (LEGACY) is deprecated and will be discontinued soon** —\n      do not offer V1 even though the API still accepts it (today the\n      manifest-generator selection silently falls back to V1 when V2\n      isn't supported by the encoder version). New live encodings must\n      target V2.\n   i. **DRM** (optional) — none (default), Widevine, PlayReady,\n      FairPlay, multi-DRM via CencDrm. Rulebook gates:\n      `drm.fmp4.fairplay_codec` requires HEVC for fmp4+FairPlay\n      unless encoder version supports `FIXED_ENCRYPTION_METHOD_SELECTION`.\n      `drm.hls.unsupported_drms` blocks MARLIN \u002F PLAYREADY \u002F PRIMETIME\n      \u002F CLEARKEY for live HLS — only Widevine, FairPlay, or AES-128\n      \u002F SAMPLE-AES are usable.\n   j. **Auto-shutdown \u002F encoder version** — same as the prebaked\n      scenarios (Step 4 questions 11, 10).\n   k. **Out-of-scope features** — Dolby Vision \u002F HDR10, sprites,\n      thumbnails, SCTE-35 \u002F ESAM, ad insertion, multi-period DASH,\n      captions\u002Fsubtitles, hardware acceleration (VOD-only — see the\n      VOD skill if needed), standby pools. If the user needs any of\n      these, point them at https:\u002F\u002Fdeveloper.bitmovin.com\u002Fencoding\u002Fdocs\n      and stop the custom build.\n\n3. **Author the YAML.** Once the decision tree is complete, write\n   `$RUN_DIR\u002Ftemplate.yaml` directly. Use the prebaked templates as\n   structural examples. Required top-level keys for live: `metadata`\n   (with `type: LIVE`), `inputs`, `configurations`, `encodings`,\n   `manifests` (when manifests are emitted), `live` (only if\n   `streamKeyMode: Use my own` and stream keys need pre-creation —\n   see Step 4b).\n\n   Live encoding's nested `live.start.properties` block needs:\n   - `streamKey` (legacy field, schema-required)\n   - `manifestGenerator: V2`\n   - `autoShutdownConfiguration` (`streamTimeoutMinutes`,\n     `bytesReadTimeoutSeconds`)\n   - `dashManifests` \u002F `hlsManifests` lists referencing\n     `$\u002Fmanifests\u002F\u003Ckind>\u002F\u003Cid>` resources\n\n   Reference inputs\u002Foutputs by id (Step 3 + Step 4b for stream keys).\n   Show the assembled YAML to the user and confirm before continuing.\n\n4. **Rejoin Step 4b → Step 6.** If the custom design uses RTMP +\n   user-supplied stream keys, run **Step 4b** to pre-create \u002F reuse\n   them and substitute the concrete UUIDs into the authored template\n   (in-place edit; do not use `$\u002Flive\u002FstreamKeys\u002F\u003Cid>` refs — rule\n   `live.stream_key_assignment`). Then skip Step 5 (gather params) — the\n   YAML is already authored — and run Step 6's\n   `bitmovin encoding templates validate` plus `validate_rules.py` to\n   catch any rule the decision tree missed.\n\nIf, during the decision tree, the user picks a combination that the\nrulebook says is impossible (e.g. `inputs.rtmp` typo, or `THREE_PASS`\nfor live), stop, explain which rule blocks it (cite `rule.id`), and\nask the user to revisit the prior decision.\n\n### Step 3 — Output (reuse or create)\n\nAsk whether to **reuse** an existing output or **create** a new one.\n\n**Reuse**: optionally run `bitmovin encoding outputs list --type s3` (or\n`gcs`, `azure`) to surface candidates. Capture the `id` from the user. If\nyou list outputs first, stop there and ask the user to choose one before\nmoving on. Do not start Step 4 until `outputId` is captured.\n\n**Create**: confirm provider (`s3` or `gcs`), bucket name, and that the\nrelevant env vars are set:\n\n- `s3`: `BITMOVIN_OUTPUT_ACCESS_KEY`, `BITMOVIN_OUTPUT_SECRET_KEY` (AWS keys)\n- `gcs`: `BITMOVIN_OUTPUT_ACCESS_KEY`, `BITMOVIN_OUTPUT_SECRET_KEY` (GCS\n  HMAC interoperability keys)\n\nThen run (env-var expansion only — never inline literals):\n\n```bash\nbitmovin encoding outputs create s3 \\\n  --name \"\u003CNAME>\" --bucket \"\u003CBUCKET>\" --region \"\u003CAWS_REGION>\" \\\n  --access-key \"$BITMOVIN_OUTPUT_ACCESS_KEY\" \\\n  --secret-key \"$BITMOVIN_OUTPUT_SECRET_KEY\" \\\n  --json --jq .id\n```\n\n(Use `bitmovin encoding outputs create gcs ...` for GCS — same flag shape,\nno `--region`.) Capture the printed `output_id` immediately and do not start\nStep 4 until it has been written into the working `params.yaml`.\n\nNever accept access keys \u002F secret keys \u002F service-account JSON contents as\nliteral CLI args or in the params file. If env vars are missing, stop and\nask the user to export them.\n\n### Step 4 — Gather parameters\n\nAsk the user **one question at a time** with a selectable options menu (e.g.\nvia `AskUserQuestion` in Claude Code, or an equivalent picker in other\nhosts). Don't ask the user to fill out the whole `params.yaml` in one go.\nStep 3 must be fully complete before Step 4 begins: once output selection or\ncreation starts, keep the user in that flow until `outputId` is resolved and\npersisted.\n\nFor every question, present the listed options. The first option is the\ndefault — pre-select it. Always include an `Other (specify)` choice that\nfalls back to free-text input. Once an answer is captured, write it to the\nrunning `params.yaml` and move on to the next question.\n\nThe list below is the authoritative ordering. Ask in this order; skip any\nparameter the user has already supplied earlier in the conversation. The\nSRT block is only relevant for the `srt` scenario.\n\n1. **encodingName** — free text. Suggest `live-\u003Cshort-purpose>`.\n2. **cloudRegion** — always ask (no default).\n   - `AWS_EU_WEST_1`\n   - `AWS_US_EAST_1`\n   - `AWS_AP_SOUTHEAST_1`\n   - `GOOGLE_EUROPE_WEST_1`\n   - `GOOGLE_US_CENTRAL_1`\n   - `AZURE_EUROPE_WEST`\n   - `Other (specify)`\n3. **outputBasePath** — free text. Suggest `live\u002F\u003CencodingName>\u002F`.\n4. **streamKeyMode** — RTMP scenarios only (`rtmp`, `redundant-rtmp`);\n   skip for `srt`. Decides how the ingest stream key is set.\n   - `Use my own` *(default — the skill pre-creates or reuses a stream key with the value you pick; ingest URL embeds that value, predictable across re-runs)*\n   - `Auto-generate` *(API mints a random stream key; you read the value from `bitmovin encoding jobs live` once the encoder is RUNNING. Simpler, no Step 4b, no residue)*\n5. **streamKey** — only when `streamKeyMode == Use my own`. Skip\n   otherwise. **Must be globally unique within the Bitmovin\n   organization** (3–20 chars, `[a-zA-Z0-9]`); if the value already\n   exists Step 4b reuses the existing stream-key id rather than\n   creating a duplicate. For `redundant-rtmp` the workflow auto-derives\n   the backup key as `\u003CstreamKey>-backup`. Free text.\n   - `live-\u003CencodingName>` *(default — encodingName-derived; usually unique enough on first run)*\n   - `bitmovin` *(short \u002F memorable; likely already taken in established orgs)*\n   - `Other (specify)`\n6. **outputAcl** — always ask, surface security implications. An ACL\n   must always be set; do **not** offer an `Other (specify)` fallback\n   for this question.\n   - `PUBLIC_READ` *(default — anyone with the URL can play)*\n   - `PRIVATE` *(signed URLs only)*\n7. **manifests**\n   - `[dash, hls]` *(default)*\n   - `[hls]`\n   - `[dash]`\n8. **videoLadder**\n   - `240p + 480p + 720p (default)` → `[{240,800k}, {480,1.6M}, {720,3M}]`\n   - `360p + 720p + 1080p` → `[{360,1.2M}, {720,3M}, {1080,5M}]`\n   - `720p only` → `[{720,3M}]`\n   - `Other (specify)` — free-text JSON \u002F YAML list of `{height,bitrate}`\n9. **audioBitrate** (bps)\n   - `128000` *(default)*\n   - `96000`\n   - `192000`\n   - `256000`\n10. **segmentLength** (seconds)\n    - `4.0` *(default)*\n    - `2.0`\n    - `6.0`\n    - `Other (specify)`\n11. **encoderVersion** — these are the only valid choices; do **not**\n    offer an `Other (specify)` fallback for this question.\n    - `STABLE` *(default)*\n    - `BETA`\n12. **autoShutdownStreamTimeoutMin**\n    - `30` *(default)*\n    - `5`\n    - `15`\n    - `60`\n    - `Other (specify)`\n13. **autoShutdownBytesReadTimeoutSec**\n    - `30` *(default)*\n    - `60`\n    - `120`\n    - `Other (specify)`\n\nSRT-only questions (`srt-live.yaml.j2`):\n\n14. **srtMode** — always ask.\n    - `LISTENER` *(default — encoder listens; you push to it)*\n    - `CALLER` *(encoder dials out to your host:port)*\n15. **srtHost** — only when `srtMode == CALLER`. Free text.\n16. **srtPort**\n    - `2088` *(default; required to be 2088 \u002F 2089 \u002F 2090 \u002F 2091 in LISTENER mode)*\n    - `2089`\n    - `2090`\n    - `2091`\n    - `Other (specify)` *(LISTENER mode rejects ports outside that set)*\n17. **srtPath** — free text.\n    - `live` *(default)*\n    - `Other (specify)`\n18. **srtLatencyMs**\n    - `2000` *(default)*\n    - `500`\n    - `1000`\n    - `4000`\n    - `Other (specify)`\n19. **srtPassphrase** — optional AES passphrase for the SRT input. Free\n    text. Must be exactly 16 characters (AES-128) or 32 characters\n    (AES-256); the template infers `keyLength` from the length. Skip the\n    question when the user picks `None`.\n    - `None` *(default — unencrypted SRT)*\n    - `Other (specify)` — paste a 16- or 32-character passphrase\n\n`manifestGenerator` is hardcoded to `V2` in all templates and is no longer a\nconfigurable parameter.\n\nAfter the last question, echo the resolved `params.yaml` back to the user\nin full and confirm before proceeding to Step 4b (RTMP scenarios) or\nStep 5 (SRT).\n\n### Step 4b — Pre-create or reuse stream key(s) (RTMP, user-supplied mode only)\n\nSkip this step entirely for the `srt` scenario, **and** for RTMP runs\nwhere the user picked `streamKeyMode: Auto-generate` in Step 4 — in that\ncase the templates fall back to `streamKeyConfiguration: { type:\nGENERATE }` and the API mints the keys for you. Step 7 surfaces the\ngenerated value(s) via `bitmovin encoding jobs live`.\n\nThe Templates API accepts `streamKeyConfiguration: { type: ASSIGN,\nstreamKeyId: \u003Cuuid> }` on a `staticIngestPoint`, but only against a\n**concrete, already-existing** stream-key UUID. Forward references to a\ntop-level `live.streamKeys.\u003Clocal-id>` block declared in the same\ntemplate do **not** resolve — the API errors with\n`Could not find reference resource: streamKeyId`. The Bitmovin CLI\ndoesn't (yet) expose stream-key management commands either, so the\nskill calls `POST \u002Fencoding\u002Flive\u002Fstream-keys` directly via curl.\n\nStream-key `value`s are globally unique within a Bitmovin organization,\nso re-running the skill with the same `streamKey` value would normally\nfail with `code: 3002, \"Stream key is not unique\"`. We avoid that by\n**creating-or-reusing**: try POST first, on 3002 fall back to a\npaginated GET that finds the existing key by value and returns its id.\nIf the existing key is currently `ASSIGNED` to another encoding, the\nTemplates API will reject the assignment at submit time with a clear\nerror — the user can pick a different value and retry.\n\nDefine a small helper once, then call it from both scenarios:\n\n```bash\nensure_stream_key_id() {\n  local value=\"$1\" name=\"$2\"\n  local resp id code\n\n  resp=$(curl -sS -X POST https:\u002F\u002Fapi.bitmovin.com\u002Fv1\u002Fencoding\u002Flive\u002Fstream-keys \\\n    -H \"X-Api-Key: $BITMOVIN_API_KEY\" -H 'Content-Type: application\u002Fjson' \\\n    -d \"{\\\"name\\\":\\\"$name\\\",\\\"value\\\":\\\"$value\\\"}\")\n  id=$(printf '%s' \"$resp\" | jq -r '.data.result.id \u002F\u002F empty')\n  if [ -n \"$id\" ]; then printf '%s' \"$id\"; return 0; fi\n\n  code=$(printf '%s' \"$resp\" | jq -r '.data.code \u002F\u002F empty')\n  [ \"$code\" = \"3002\" ] || { echo \"stream-key create failed: $resp\" >&2; return 1; }\n\n  # 3002 = uniqueness violation -> the value already exists. Page through\n  # the org's stream keys and return the existing id. limit=100 is the\n  # API max per page.\n  local offset=0 limit=100 page count\n  while :; do\n    page=$(curl -fsS -H \"X-Api-Key: $BITMOVIN_API_KEY\" \\\n      \"https:\u002F\u002Fapi.bitmovin.com\u002Fv1\u002Fencoding\u002Flive\u002Fstream-keys?limit=$limit&offset=$offset\")\n    id=$(printf '%s' \"$page\" | jq -r --arg v \"$value\" \\\n      '.data.result.items[]? | select(.value == $v) | .id' | head -1)\n    [ -n \"$id\" ] && { printf '%s' \"$id\"; return 0; }\n    count=$(printf '%s' \"$page\" | jq '.data.result.items | length')\n    [ \"$count\" -lt \"$limit\" ] && { echo \"stream-key '$value' not found via lookup\" >&2; return 1; }\n    offset=$((offset + limit))\n  done\n}\n```\n\nFor `rtmp`:\n\n```bash\nmainStreamKeyId=$(ensure_stream_key_id \"$streamKey\" \"$encodingName main stream key\") \\\n  || exit 1\n```\n\nFor `redundant-rtmp` (primary uses the user's value, backup auto-derives\nas `\u003Cvalue>-backup`):\n\n```bash\nprimaryStreamKeyId=$(ensure_stream_key_id \"$streamKey\" \"$encodingName primary stream key\") \\\n  || exit 1\nbackupStreamKeyId=$(ensure_stream_key_id \"${streamKey}-backup\" \"$encodingName backup stream key\") \\\n  || exit 1\n```\n\nAdd the captured `mainStreamKeyId` (rtmp) or `primaryStreamKeyId` \u002F\n`backupStreamKeyId` (redundant-rtmp) to the running `params.yaml`\nbefore Step 5 — the Jinja templates reference these keys directly.\n\nStream-key resources persist after the encoding finishes (returning to\n`status: UNASSIGNED`); the next run with the same value reuses them via\nthe lookup path above. If the user wants to delete them outright, use\n`DELETE \u002Fencoding\u002Flive\u002Fstream-keys\u002F\u003Cid>`.\n\n### Step 5 — Render template\n\nRender the chosen Jinja template into `\u003CRUN_DIR>\u002Ftemplate.yaml`:\n\n```bash\nRUN_ID=$(date -u +%Y%m%d-%H%M%S)-$(openssl rand -hex 3)\nRUN_DIR=\"$HOME\u002F.cache\u002Fbitmovin\u002Fbitmovin-encoding-live\u002F$RUN_ID\"\nmkdir -p \"$RUN_DIR\"\ncp params.yaml \"$RUN_DIR\u002Fparams.yaml\"\n\"$PYTHON\" -c \"\nimport jinja2, sys, yaml, pathlib\nparams = yaml.safe_load(pathlib.Path('$RUN_DIR\u002Fparams.yaml').read_text())\nenv = jinja2.Environment(\n    loader=jinja2.FileSystemLoader('\u003CSKILL_DIR>\u002Ftemplates'),\n    undefined=jinja2.StrictUndefined,\n    trim_blocks=True, lstrip_blocks=True,\n)\nout = env.get_template('\u003CTEMPLATE_NAME>.yaml.j2').render(**params)\npathlib.Path('$RUN_DIR\u002Ftemplate.yaml').write_text(out)\nprint('$RUN_DIR\u002Ftemplate.yaml')\n\"\n```\n\nShow the rendered YAML to the user (or its key sections) and confirm before\nposting.\n\n### Step 6 — Validate + start\n\nValidate the rendered YAML in two passes — schema (the CLI) and\ncross-field semantic rules (`validate_rules.py`) — then submit it. The\nCLI fetches the schema once and caches it for 24h at\n`~\u002F.config\u002Fbitmovin\u002Ftemplate-schema-v1.json`.\n\n```bash\n# 1. Schema check — structural validity, required fields, enum values.\nbitmovin encoding templates validate \"$RUN_DIR\u002Ftemplate.yaml\"\n\n# 2. Cross-field rule check — semantic constraints the schema can't\n#    express (live-encoding-mode subset, fMP4 stream count, AV1 min\n#    bitrate, the `inputs.rtmp` silent-drop, `$\u002F...` streamKeyId refs).\n#    Skip only as a last resort if a rule fires that the user is sure\n#    is a false positive (the API may still reject server-side).\n\"$PYTHON\" \u003CSKILL_DIR>\u002Fscripts\u002Fvalidate_rules.py \"$RUN_DIR\u002Ftemplate.yaml\" \\\n  || { echo \"error: rule checks failed; fix the template before submitting\" >&2; exit 1; }\n\n# 3. Submit.\nENCODING_ID=$(\n  bitmovin encoding templates start \"$RUN_DIR\u002Ftemplate.yaml\" \\\n    | awk '\u002F^Encoding started:\u002F {print $3}'\n)\ntest -n \"$ENCODING_ID\" || { echo \"error: failed to capture encoding id\" >&2; exit 1; }\necho \"encoding id: $ENCODING_ID\"\n```\n\nDo **not** pass `--watch` — the CLI's watch mode waits for the encoding to\nreach `FINISHED\u002FERROR\u002FCANCELED\u002FTRANSFER_ERROR`, which a live encoding never\ndoes until it is stopped. Step 7 polls for `RUNNING` (the success state for\nlive).\n\nIf `templates validate` reports schema errors, or `validate_rules.py`\nprints any violations, abort and surface them to the user. For prebaked\nscenarios the structure usually points at a missing param; for `custom`,\nthe rule_id in the violation output points directly at\n`references\u002Frulebook.yaml` for the explanation.\n\n### Step 7 — Wait for `RUNNING`, then surface ingest details\n\nThree short polling loops. All three call the Bitmovin CLI; no Python\nhelper is involved.\n\n**7a — defensive partial-parse check (≈15s).** The Templates API\noccasionally returns 200 + encoding-id but silently drops sections it\ndidn't recognize (e.g. `inputs.rtmp` instead of `inputs.redundantRtmp`).\nConfirm the encoding's `type` settled to `LIVE` before doing anything else\n— this is what catches that bug:\n\n```bash\nDEADLINE=$(( $(date +%s) + 15 ))\nACTUAL_TYPE=\"\"\nwhile [ \"$(date +%s)\" -lt \"$DEADLINE\" ]; do\n  ACTUAL_TYPE=$(bitmovin encoding jobs get \"$ENCODING_ID\" --jq .type 2>\u002Fdev\u002Fnull | tr -d '\"')\n  [ \"$ACTUAL_TYPE\" = \"LIVE\" ] && break\n  sleep 2\ndone\nif [ \"$ACTUAL_TYPE\" != \"LIVE\" ]; then\n  echo \"error: Templates API silently dropped most of the template\" >&2\n  echo \"  encoding $ENCODING_ID has type='$ACTUAL_TYPE' (expected 'LIVE') after 15s\" >&2\n  echo \"  inspect the rendered template at $RUN_DIR\u002Ftemplate.yaml for unknown keys\" >&2\n  echo \"  (e.g. 'inputs.rtmp' instead of 'inputs.redundantRtmp')\" >&2\n  exit 1\nfi\n```\n\n**7b — wait for `RUNNING` (10 min).** Live encoders take 30–120s to come\nup, sometimes longer. Bail out on the terminal-bad statuses, otherwise\nkeep polling. Transient CLI errors (network blips, 5xx) are ignored:\n\n```bash\nDEADLINE=$(( $(date +%s) + 600 ))\nSTATUS=\"\"\nwhile [ \"$(date +%s)\" -lt \"$DEADLINE\" ]; do\n  STATUS=$(bitmovin encoding jobs status \"$ENCODING_ID\" --jq .status 2>\u002Fdev\u002Fnull | tr -d '\"')\n  case \"$STATUS\" in\n    RUNNING) break ;;\n    ERROR|CANCELED|FINISHED) echo \"error: encoding $ENCODING_ID reached terminal status $STATUS\" >&2; exit 1 ;;\n  esac\n  sleep 10\ndone\n[ \"$STATUS\" = \"RUNNING\" ] || { echo \"error: timed out after 10 min waiting for RUNNING\" >&2; exit 1; }\n```\n\n**7c — wait for ingest details, then print.** Right after RUNNING the\nencoder may still be allocating an IP. `bitmovin encoding jobs live`\nreturns `available: false` during that window:\n\n```bash\nDEADLINE=$(( $(date +%s) + 300 ))\nwhile [ \"$(date +%s)\" -lt \"$DEADLINE\" ]; do\n  AVAILABLE=$(bitmovin encoding jobs live \"$ENCODING_ID\" --jq '.available \u002F\u002F true' 2>\u002Fdev\u002Fnull)\n  [ \"$AVAILABLE\" = \"true\" ] && break\n  sleep 10\ndone\n\nbitmovin encoding jobs live \"$ENCODING_ID\"\necho\necho \"Dashboard:    https:\u002F\u002Fdashboard.bitmovin.com\u002Flive\u002Fencodings\u002F$ENCODING_ID\"\necho \"Output base:  \u003CoutputBasePath from params> (manifests are listed in the dashboard)\"\n```\n\nThe `bitmovin encoding jobs live` output covers all three template\nshapes:\n\n- **`rtmp`** and **`redundant-rtmp`** — `streamKeys[]` lists each static\n  ingest point's stream key (one entry for `rtmp`, two for\n  `redundant-rtmp`). Combine with `encoderIp` \u002F `application` to construct\n  `rtmp:\u002F\u002F\u003CencoderIp>\u002F\u003Capplication>\u002F\u003CstreamKey>`.\n- **`srt`** — `srtInputs[]` carries `mode`, `host`, `port`, `path`. For\n  `LISTENER` mode the user pushes to `srt:\u002F\u002F\u003CencoderIp>:\u003Cport>` (with\n  `?streamid=\u003Cpath>` if `path` is set); for `CALLER` mode the encoder\n  dials the host\u002Fport the user runs their own listener on.\n\nTranslate the JSON to a usable ingest URL when reporting back to the user.\n\n### Stop when the user is done\n\nRemind the user that an encoding is now running.\nOffer to stop the encoding for the user.\n\n```bash\nbitmovin encoding jobs stop \"$ENCODING_ID\"\n```\n\nThe encoding shell remains in the account (not deleted). The user can\ninspect logs and statistics afterwards via the dashboard.\n\nFor an ad-hoc status check at any point, use the CLI directly:\n\n```bash\nbitmovin encoding jobs status \"$ENCODING_ID\"\n```\n\n## Safety Rules\n\n- Never log `BITMOVIN_API_KEY`, `BITMOVIN_OUTPUT_ACCESS_KEY`,\n  `BITMOVIN_OUTPUT_SECRET_KEY`, or any value resembling a secret.\n- Never accept credentials as literal CLI args or in `params.yaml`. The\n  Bitmovin CLI's `outputs create` requires `--access-key` \u002F `--secret-key`\n  flags — pass these only via shell env-var expansion (`--access-key\n  \"$BITMOVIN_OUTPUT_ACCESS_KEY\"`) so the literal value never appears in\n  scripts, history, or rendered templates.\n- Never write secrets into the rendered template.\n- If a user pastes a secret into the prompt, refuse to use it; tell them to\n  export it as an env var instead.\n","# Bitmovin Live Encoding Skill\n\nWalks the user through configuring and starting a Bitmovin live encoding. The\nheavy lifting is done by the Bitmovin **Encoding Templates API**: a single\n`POST \u002Fencoding\u002Ftemplates\u002Fstart` creates inputs, codec configs, encoding,\nstreams, muxings, manifests **and** starts the live encoding from one YAML\ndocument.\n\nThis skill renders that YAML from a small parameter file, validates and\nsubmits it via the **Bitmovin CLI** (`bitmovin encoding ...`), polls until\nthe encoder is `RUNNING`, and asks the CLI for the ingest details.\n\n## Prerequisites\n\n- **Bitmovin CLI** (`@bitmovin\u002Fcli`, ≥ 0.3.0) — drives `templates validate`,\n  `templates start`, `outputs list\u002Fcreate`, `jobs get`, `jobs status`,\n  `jobs live`, `jobs stop`. `jobs live` (added in 0.3.0) returns\n  `encoderIp` plus the per-template ingest details (`streamKeys[]` for\n  RTMP \u002F redundant RTMP, `srtInputs[]` with mode\u002Fhost\u002Fport\u002Fpath for SRT)\n  and is the reason this skill no longer ships its own polling helper.\n  Install with `npm install -g @bitmovin\u002Fcli`.\n- **Python ≥ 3.10** — Step 1's `scripts\u002Fensure_venv.sh` auto-creates a\n  dedicated venv and installs `pyyaml` and `jinja2` into it if they aren't\n  already importable from the user's `python3`. Used only by the inline\n  Jinja render block in Step 5; every other workflow step is the CLI.\n- `BITMOVIN_API_KEY` environment variable set.\n- For creating a new output (optional): `BITMOVIN_OUTPUT_ACCESS_KEY` \u002F\n  `BITMOVIN_OUTPUT_SECRET_KEY` env vars (see Step 3). Credentials are read\n  from the environment and passed to the CLI via shell expansion at\n  invocation time — never hardcoded into scripts, the params file, or the\n  rendered template.\n\nThe skill never embeds secrets in the rendered template or log output.\n\n## Path Conventions\n\n- `SKILL_DIR`: install location of this skill (e.g.\n  `~\u002F.claude\u002Fskills\u002Fbitmovin-encoding-live` or\n  `~\u002F.codex\u002Fskills\u002Fbitmovin-encoding-live`). Used to locate `scripts\u002F` and\n  `templates\u002F`.\n- `RUN_DIR`: per-run cache directory at\n  `~\u002F.cache\u002Fbitmovin\u002Fbitmovin-encoding-live\u002F\u003Crun-id>\u002F`, where `\u003Crun-id>` is\n  `YYYYMMDD-HHMMSS-\u003Cshort-hash>`. Holds the rendered template and a copy\n  of the params file. RUN_DIR MUST stay outside the skill directory so its\n  contents are never published or installed.\n\n## Scripts\n\n| Script | Purpose |\n|---|---|\n| `scripts\u002Fensure_venv.sh` | Prints the path to a Python interpreter that has `pyyaml` and `jinja2` available. Uses the user's `python3` if it already has them; otherwise creates \u002F reuses a venv at `~\u002F.cache\u002Fbitmovin\u002Fbitmovin-encoding-live\u002F.venv\u002F` and installs the deps there. |\n| `scripts\u002Fvalidate_rules.py \u003Ctemplate.yaml>` | Runs cross-field rule checks (see `references\u002Frulebook.yaml`) against a rendered Encoding Template. Catches semantic errors the JSON schema can't express — live-encoding-mode subset, fMP4-stream-count, AV1 min bitrate, the `inputs.rtmp` silent-drop, `$\u002F...` streamKeyId refs that don't resolve. Exits non-zero on violations. Always run between `bitmovin encoding templates validate` and `bitmovin encoding templates start`. |\n\nAll other workflow steps — output listing \u002F creation, template validation\n\u002F submission, status polling, ingest details, stop — are driven directly\nby the Bitmovin CLI from the inline shell snippets in this document.\nSKILL.md is responsible for prompting the user.\n\n## References\n\n| File | Purpose |\n|---|---|\n| `references\u002Frulebook.yaml` | Cross-field semantic rules for live encodings, derived from Bitmovin's server-side validators plus live-ingest specifics observed in practice. Each rule has an `id`, a `rule` description, and an `applies_when` predicate. Consulted during the custom-build flow (Step 2b) to prune options the API would reject; enforced post-render by `validate_rules.py`. |\n\n## Templates\n\nJinja2 templates in `\u003CSKILL_DIR>\u002Ftemplates\u002F`:\n\n| Template | Use case |\n|---|---|\n| `rtmp-live.yaml.j2` | Single RTMP ingest point. Uses `inputs.redundantRtmp` with one `staticIngestPoint` — Bitmovin's API has no separate non-redundant RTMP input type. |\n| `redundant-rtmp-live.yaml.j2` | Two RTMP ingest points for HA ingest. |\n| `srt-live.yaml.j2` | SRT ingest (CALLER or LISTENER mode). |\n\nAll templates produce CMAF-style output: fmp4 muxings serving both DASH and\nHLS manifests with `manifestGenerator: V2`. Customers can hand-edit the\nrendered YAML before `bitmovin encoding templates start` if they need\nvariants (additional renditions, different segment naming, additional\nmuxing types).\n\n`\u003CSKILL_DIR>\u002Fexamples\u002Fparams.example.yaml` documents every parameter.\n\n## Workflow\n\n### Step 1 — Verify prerequisites\n\nConfirm the Bitmovin CLI is installed and resolve the Python interpreter\nthat has the runtime deps (used by the inline Jinja render in Step 5):\n\n```bash\ncommand -v bitmovin >\u002Fdev\u002Fnull \\\n  || { echo \"error: bitmovin CLI not found. Install with: npm install -g @bitmovin\u002Fcli\" >&2; exit 1; }\nPYTHON=$(bash \u003CSKILL_DIR>\u002Fscripts\u002Fensure_venv.sh) || exit 1\ntest -n \"$BITMOVIN_API_KEY\" || { echo \"error: BITMOVIN_API_KEY is not set\" >&2; exit 1; }\necho \"OK ($PYTHON)\"\n```\n\n`$PYTHON` is reused in Step 5. If `BITMOVIN_API_KEY` is empty, ask the user\nto export it before continuing. The CLI reads the key from the same env\nvar, so no extra config is required.\n\nIf `python3` itself is missing, `ensure_venv.sh` prints a clear error and\nexits non-zero — point the user at https:\u002F\u002Fwww.python.org\u002Fdownloads\u002F\n(Python ≥ 3.10).\n\n### Step 2 — Pick scenario\n\nAsk the user which scenario fits. These are the only valid choices —\ndo **not** offer an `Other (specify)` fallback. The fourth option is the\nexplicit escape hatch:\n\n1. `rtmp` — single RTMP ingest (most common)\n2. `redundant-rtmp` — two RTMP ingest points for HA\n3. `srt` — SRT ingest\n4. `custom` — none of the above fits; describe the use case in free text\n   and the skill assembles a template from the schema + rulebook (see\n   Step 2b)\n\nFor options 1–3, map to the matching template under\n`\u003CSKILL_DIR>\u002Ftemplates\u002F`. For option 4, jump to **Step 2b** before Step 3.\n\n### Step 2b — Custom build (only when scenario = `custom`)\n\nThe user has described a use case the three prebaked scenarios don't fit\n(e.g. HEVC ingest + AV1 distribution, Zixi or HLS pulled-input, a custom\nladder with non-standard segment naming, DRM-wrapped HLS). The skill\nguides them through assembling a template from primitives, filtering\noptions at each decision using the cross-field rulebook so choices that\nthe API would reject never appear.\n\n**Sources of truth, in order of authority:**\n\n1. `\u003CSKILL_DIR>\u002Freferences\u002Frulebook.yaml` — semantic compatibility rules\n   derived from Bitmovin's server-side validators. Each rule has an\n   `applies_when` predicate; if it matches the user's choices, apply the\n   rule's constraint. Skip rules tagged `scope: vod-only` — they don't\n   apply to live encodings.\n2. `~\u002F.config\u002Fbitmovin\u002Ftemplate-schema-v1.json` — the published Encoding\n   Template JSON Schema (24h-cached by `bitmovin encoding templates\n   validate`). Use it to discover allowed values for enum fields (input\n   types, codec types, manifest types, DRM types, ACL permissions,\n   `liveEncodingMode`, …). If the cache is missing, run\n   `bitmovin encoding templates validate` against any prebaked template\n   first to warm it.\n3. `\u003CSKILL_DIR>\u002Ftemplates\u002F*.yaml.j2` — worked examples of valid live\n   template shapes. Read at least one before authoring (`rtmp-live` is\n   the simplest reference).\n\n**Workflow:**\n\n1. **Capture the use case.** Take the user's free-text description.\n   Re-state your understanding back in one sentence and confirm before\n   continuing.\n\n2. **Walk a decision tree.** Ask **one question at a time** with a\n   selectable picker. After each answer, consult the rulebook: drop any\n   downstream option whose `applies_when` would conflict with the\n   answers so far. Order:\n\n   a. **Input type** — choose from `inputs.*` enum values that make\n      sense for live: `redundantRtmp` (single or HA RTMP via\n      staticIngestPoints), `srt` (LISTENER \u002F CALLER), `zixi`, `hls`\n      (live-from-stream pull). Apply rule `live.rtmp_input_type` —\n      never offer `inputs.rtmp`, that key is silently dropped.\n   b. **Stream-key handling (RTMP variants only)** — `Use my own`\n      (Step 4b pre-creates \u002F reuses; ASSIGN with concrete UUID) or\n      `Auto-generate` (API mints, GENERATE). Apply rule\n      `live.stream_key_assignment` — `streamKeyId` must be a real\n      UUID, never a `$\u002F...` ref.\n   c. **Video codec(s)** — h264 (broadest device support), h265\n      (~50% bandwidth savings), av1 (~50% savings, newer; rule\n      `codec.av1.min_bitrate` requires bitrate ≥ 10000 — live has no\n      per-title carve-out, so always set bitrate). Multi-codec ladders\n      allowed.\n   d. **Encoding mode** — `STANDARD` (default), `SINGLE_PASS`, or\n      `TWO_PASS`. Apply rule `encoding_mode.live_subset` — never\n      offer `THREE_PASS`, the API rejects it for live even though the\n      schema enum includes it.\n   e. **Video ladder** — fixed list of `{height, bitrate}`. Per-Title\n      is **VOD-only** (rule `hw.no_per_title` is for hardware; for\n      live, per-title simply isn't a live.start primitive). Multi-stream\n      live encodings hit rule `encoding_mode.all_video_streams_match`\n      — pick one mode for all video streams.\n   f. **Audio** — AAC (default), Dolby Atmos \u002F DD+ (rule\n      `dd.drm_encoder_features` gates with DRM; rule\n      `dd.invalid_filters` blocks AUDIO_VOLUME \u002F WATERMARK \u002F\n      ENHANCED_WATERMARK), pass-through, or none (video-only).\n   g. **Muxing format** — fmp4 (CMAF-style; works with DASH and HLS;\n      rule `muxing.fmp4.exactly_one_stream` means one muxing per\n      stream), TS (HLS only). Live encodings rarely use progressive\n      MP4 since there's nothing to download until the encoding stops.\n   h. **Manifest format** — DASH only, HLS only, both, none. Pin\n      `manifestGenerator: V2` (rule `live.manifest_generator_v2`).\n      **V1 (LEGACY) is deprecated and will be discontinued soon** —\n      do not offer V1 even though the API still accepts it (today the\n      manifest-generator selection silently falls back to V1 when V2\n      isn't supported by the encoder version). New live encodings must\n      target V2.\n   i. **DRM** (optional) — none (default), Widevine, PlayReady,\n      FairPlay, multi-DRM via CencDrm. Rulebook gates:\n      `drm.fmp4.fairplay_codec` requires HEVC for fmp4+FairPlay\n      unless encoder version supports `FIXED_ENCRYPTION_METHOD_SELECTION`.\n      `drm.hls.unsupported_drms` blocks MARLIN \u002F PLAYREADY \u002F PRIMETIME\n      \u002F CLEARKEY for live HLS — only Widevine, FairPlay, or AES-128\n      \u002F SAMPLE-AES are usable.\n   j. **Auto-shutdown \u002F encoder version** — same as the prebaked\n      scenarios (Step 4 questions 11, 10).\n   k. **Out-of-scope features** — Dolby Vision \u002F HDR10, sprites,\n      thumbnails, SCTE-35 \u002F ESAM, ad insertion, multi-period DASH,\n      captions\u002Fsubtitles, hardware acceleration (VOD-only — see the\n      VOD skill if needed), standby pools. If the user needs any of\n      these, point them at https:\u002F\u002Fdeveloper.bitmovin.com\u002Fencoding\u002Fdocs\n      and stop the custom build.\n\n3. **Author the YAML.** Once the decision tree is complete, write\n   `$RUN_DIR\u002Ftemplate.yaml` directly. Use the prebaked templates as\n   structural examples. Required top-level keys for live: `metadata`\n   (with `type: LIVE`), `inputs`, `configurations`, `encodings`,\n   `manifests` (when manifests are emitted), `live` (only if\n   `streamKeyMode: Use my own` and stream keys need pre-creation —\n   see Step 4b).\n\n   Live encoding's nested `live.start.properties` block needs:\n   - `streamKey` (legacy field, schema-required)\n   - `manifestGenerator: V2`\n   - `autoShutdownConfiguration` (`streamTimeoutMinutes`,\n     `bytesReadTimeoutSeconds`)\n   - `dashManifests` \u002F `hlsManifests` lists referencing\n     `$\u002Fmanifests\u002F\u003Ckind>\u002F\u003Cid>` resources\n\n   Reference inputs\u002Foutputs by id (Step 3 + Step 4b for stream keys).\n   Show the assembled YAML to the user and confirm before continuing.\n\n4. **Rejoin Step 4b → Step 6.** If the custom design uses RTMP +\n   user-supplied stream keys, run **Step 4b** to pre-create \u002F reuse\n   them and substitute the concrete UUIDs into the authored template\n   (in-place edit; do not use `$\u002Flive\u002FstreamKeys\u002F\u003Cid>` refs — rule\n   `live.stream_key_assignment`). Then skip Step 5 (gather params) — the\n   YAML is already authored — and run Step 6's\n   `bitmovin encoding templates validate` plus `validate_rules.py` to\n   catch any rule the decision tree missed.\n\nIf, during the decision tree, the user picks a combination that the\nrulebook says is impossible (e.g. `inputs.rtmp` typo, or `THREE_PASS`\nfor live), stop, explain which rule blocks it (cite `rule.id`), and\nask the user to revisit the prior decision.\n\n### Step 3 — Output (reuse or create)\n\nAsk whether to **reuse** an existing output or **create** a new one.\n\n**Reuse**: optionally run `bitmovin encoding outputs list --type s3` (or\n`gcs`, `azure`) to surface candidates. Capture the `id` from the user. If\nyou list outputs first, stop there and ask the user to choose one before\nmoving on. Do not start Step 4 until `outputId` is captured.\n\n**Create**: confirm provider (`s3` or `gcs`), bucket name, and that the\nrelevant env vars are set:\n\n- `s3`: `BITMOVIN_OUTPUT_ACCESS_KEY`, `BITMOVIN_OUTPUT_SECRET_KEY` (AWS keys)\n- `gcs`: `BITMOVIN_OUTPUT_ACCESS_KEY`, `BITMOVIN_OUTPUT_SECRET_KEY` (GCS\n  HMAC interoperability keys)\n\nThen run (env-var expansion only — never inline literals):\n\n```bash\nbitmovin encoding outputs create s3 \\\n  --name \"\u003CNAME>\" --bucket \"\u003CBUCKET>\" --region \"\u003CAWS_REGION>\" \\\n  --access-key \"$BITMOVIN_OUTPUT_ACCESS_KEY\" \\\n  --secret-key \"$BITMOVIN_OUTPUT_SECRET_KEY\" \\\n  --json --jq .id\n```\n\n(Use `bitmovin encoding outputs create gcs ...` for GCS — same flag shape,\nno `--region`.) Capture the printed `output_id` immediately and do not start\nStep 4 until it has been written into the working `params.yaml`.\n\nNever accept access keys \u002F secret keys \u002F service-account JSON contents as\nliteral CLI args or in the params file. If env vars are missing, stop and\nask the user to export them.\n\n### Step 4 — Gather parameters\n\nAsk the user **one question at a time** with a selectable options menu (e.g.\nvia `AskUserQuestion` in Claude Code, or an equivalent picker in other\nhosts). Don't ask the user to fill out the whole `params.yaml` in one go.\nStep 3 must be fully complete before Step 4 begins: once output selection or\ncreation starts, keep the user in that flow until `outputId` is resolved and\npersisted.\n\nFor every question, present the listed options. The first option is the\ndefault — pre-select it. Always include an `Other (specify)` choice that\nfalls back to free-text input. Once an answer is captured, write it to the\nrunning `params.yaml` and move on to the next question.\n\nThe list below is the authoritative ordering. Ask in this order; skip any\nparameter the user has already supplied earlier in the conversation. The\nSRT block is only relevant for the `srt` scenario.\n\n1. **encodingName** — free text. Suggest `live-\u003Cshort-purpose>`.\n2. **cloudRegion** — always ask (no default).\n   - `AWS_EU_WEST_1`\n   - `AWS_US_EAST_1`\n   - `AWS_AP_SOUTHEAST_1`\n   - `GOOGLE_EUROPE_WEST_1`\n   - `GOOGLE_US_CENTRAL_1`\n   - `AZURE_EUROPE_WEST`\n   - `Other (specify)`\n3. **outputBasePath** — free text. Suggest `live\u002F\u003CencodingName>\u002F`.\n4. **streamKeyMode** — RTMP scenarios only (`rtmp`, `redundant-rtmp`);\n   skip for `srt`. Decides how the ingest stream key is set.\n   - `Use my own` *(default — the skill pre-creates or reuses a stream key with the value you pick; ingest URL embeds that value, predictable across re-runs)*\n   - `Auto-generate` *(API mints a random stream key; you read the value from `bitmovin encoding jobs live` once the encoder is RUNNING. Simpler, no Step 4b, no residue)*\n5. **streamKey** — only when `streamKeyMode == Use my own`. Skip\n   otherwise. **Must be globally unique within the Bitmovin\n   organization** (3–20 chars, `[a-zA-Z0-9]`); if the value already\n   exists Step 4b reuses the existing stream-key id rather than\n   creating a duplicate. For `redundant-rtmp` the workflow auto-derives\n   the backup key as `\u003CstreamKey>-backup`. Free text.\n   - `live-\u003CencodingName>` *(default — encodingName-derived; usually unique enough on first run)*\n   - `bitmovin` *(short \u002F memorable; likely already taken in established orgs)*\n   - `Other (specify)`\n6. **outputAcl** — always ask, surface security implications. An ACL\n   must always be set; do **not** offer an `Other (specify)` fallback\n   for this question.\n   - `PUBLIC_READ` *(default — anyone with the URL can play)*\n   - `PRIVATE` *(signed URLs only)*\n7. **manifests**\n   - `[dash, hls]` *(default)*\n   - `[hls]`\n   - `[dash]`\n8. **videoLadder**\n   - `240p + 480p + 720p (default)` → `[{240,800k}, {480,1.6M}, {720,3M}]`\n   - `360p + 720p + 1080p` → `[{360,1.2M}, {720,3M}, {1080,5M}]`\n   - `720p only` → `[{720,3M}]`\n   - `Other (specify)` — free-text JSON \u002F YAML list of `{height,bitrate}`\n9. **audioBitrate** (bps)\n   - `128000` *(default)*\n   - `96000`\n   - `192000`\n   - `256000`\n10. **segmentLength** (seconds)\n    - `4.0` *(default)*\n    - `2.0`\n    - `6.0`\n    - `Other (specify)`\n11. **encoderVersion** — these are the only valid choices; do **not**\n    offer an `Other (specify)` fallback for this question.\n    - `STABLE` *(default)*\n    - `BETA`\n12. **autoShutdownStreamTimeoutMin**\n    - `30` *(default)*\n    - `5`\n    - `15`\n    - `60`\n    - `Other (specify)`\n13. **autoShutdownBytesReadTimeoutSec**\n    - `30` *(default)*\n    - `60`\n    - `120`\n    - `Other (specify)`\n\nSRT-only questions (`srt-live.yaml.j2`):\n\n14. **srtMode** — always ask.\n    - `LISTENER` *(default — encoder listens; you push to it)*\n    - `CALLER` *(encoder dials out to your host:port)*\n15. **srtHost** — only when `srtMode == CALLER`. Free text.\n16. **srtPort**\n    - `2088` *(default; required to be 2088 \u002F 2089 \u002F 2090 \u002F 2091 in LISTENER mode)*\n    - `2089`\n    - `2090`\n    - `2091`\n    - `Other (specify)` *(LISTENER mode rejects ports outside that set)*\n17. **srtPath** — free text.\n    - `live` *(default)*\n    - `Other (specify)`\n18. **srtLatencyMs**\n    - `2000` *(default)*\n    - `500`\n    - `1000`\n    - `4000`\n    - `Other (specify)`\n19. **srtPassphrase** — optional AES passphrase for the SRT input. Free\n    text. Must be exactly 16 characters (AES-128) or 32 characters\n    (AES-256); the template infers `keyLength` from the length. Skip the\n    question when the user picks `None`.\n    - `None` *(default — unencrypted SRT)*\n    - `Other (specify)` — paste a 16- or 32-character passphrase\n\n`manifestGenerator` is hardcoded to `V2` in all templates and is no longer a\nconfigurable parameter.\n\nAfter the last question, echo the resolved `params.yaml` back to the user\nin full and confirm before proceeding to Step 4b (RTMP scenarios) or\nStep 5 (SRT).\n\n### Step 4b — Pre-create or reuse stream key(s) (RTMP, user-supplied mode only)\n\nSkip this step entirely for the `srt` scenario, **and** for RTMP runs\nwhere the user picked `streamKeyMode: Auto-generate` in Step 4 — in that\ncase the templates fall back to `streamKeyConfiguration: { type:\nGENERATE }` and the API mints the keys for you. Step 7 surfaces the\ngenerated value(s) via `bitmovin encoding jobs live`.\n\nThe Templates API accepts `streamKeyConfiguration: { type: ASSIGN,\nstreamKeyId: \u003Cuuid> }` on a `staticIngestPoint`, but only against a\n**concrete, already-existing** stream-key UUID. Forward references to a\ntop-level `live.streamKeys.\u003Clocal-id>` block declared in the same\ntemplate do **not** resolve — the API errors with\n`Could not find reference resource: streamKeyId`. The Bitmovin CLI\ndoesn't (yet) expose stream-key management commands either, so the\nskill calls `POST \u002Fencoding\u002Flive\u002Fstream-keys` directly via curl.\n\nStream-key `value`s are globally unique within a Bitmovin organization,\nso re-running the skill with the same `streamKey` value would normally\nfail with `code: 3002, \"Stream key is not unique\"`. We avoid that by\n**creating-or-reusing**: try POST first, on 3002 fall back to a\npaginated GET that finds the existing key by value and returns its id.\nIf the existing key is currently `ASSIGNED` to another encoding, the\nTemplates API will reject the assignment at submit time with a clear\nerror — the user can pick a different value and retry.\n\nDefine a small helper once, then call it from both scenarios:\n\n```bash\nensure_stream_key_id() {\n  local value=\"$1\" name=\"$2\"\n  local resp id code\n\n  resp=$(curl -sS -X POST https:\u002F\u002Fapi.bitmovin.com\u002Fv1\u002Fencoding\u002Flive\u002Fstream-keys \\\n    -H \"X-Api-Key: $BITMOVIN_API_KEY\" -H 'Content-Type: application\u002Fjson' \\\n    -d \"{\\\"name\\\":\\\"$name\\\",\\\"value\\\":\\\"$value\\\"}\")\n  id=$(printf '%s' \"$resp\" | jq -r '.data.result.id \u002F\u002F empty')\n  if [ -n \"$id\" ]; then printf '%s' \"$id\"; return 0; fi\n\n  code=$(printf '%s' \"$resp\" | jq -r '.data.code \u002F\u002F empty')\n  [ \"$code\" = \"3002\" ] || { echo \"stream-key create failed: $resp\" >&2; return 1; }\n\n  # 3002 = uniqueness violation -> the value already exists. Page through\n  # the org's stream keys and return the existing id. limit=100 is the\n  # API max per page.\n  local offset=0 limit=100 page count\n  while :; do\n    page=$(curl -fsS -H \"X-Api-Key: $BITMOVIN_API_KEY\" \\\n      \"https:\u002F\u002Fapi.bitmovin.com\u002Fv1\u002Fencoding\u002Flive\u002Fstream-keys?limit=$limit&offset=$offset\")\n    id=$(printf '%s' \"$page\" | jq -r --arg v \"$value\" \\\n      '.data.result.items[]? | select(.value == $v) | .id' | head -1)\n    [ -n \"$id\" ] && { printf '%s' \"$id\"; return 0; }\n    count=$(printf '%s' \"$page\" | jq '.data.result.items | length')\n    [ \"$count\" -lt \"$limit\" ] && { echo \"stream-key '$value' not found via lookup\" >&2; return 1; }\n    offset=$((offset + limit))\n  done\n}\n```\n\nFor `rtmp`:\n\n```bash\nmainStreamKeyId=$(ensure_stream_key_id \"$streamKey\" \"$encodingName main stream key\") \\\n  || exit 1\n```\n\nFor `redundant-rtmp` (primary uses the user's value, backup auto-derives\nas `\u003Cvalue>-backup`):\n\n```bash\nprimaryStreamKeyId=$(ensure_stream_key_id \"$streamKey\" \"$encodingName primary stream key\") \\\n  || exit 1\nbackupStreamKeyId=$(ensure_stream_key_id \"${streamKey}-backup\" \"$encodingName backup stream key\") \\\n  || exit 1\n```\n\nAdd the captured `mainStreamKeyId` (rtmp) or `primaryStreamKeyId` \u002F\n`backupStreamKeyId` (redundant-rtmp) to the running `params.yaml`\nbefore Step 5 — the Jinja templates reference these keys directly.\n\nStream-key resources persist after the encoding finishes (returning to\n`status: UNASSIGNED`); the next run with the same value reuses them via\nthe lookup path above. If the user wants to delete them outright, use\n`DELETE \u002Fencoding\u002Flive\u002Fstream-keys\u002F\u003Cid>`.\n\n### Step 5 — Render template\n\nRender the chosen Jinja template into `\u003CRUN_DIR>\u002Ftemplate.yaml`:\n\n```bash\nRUN_ID=$(date -u +%Y%m%d-%H%M%S)-$(openssl rand -hex 3)\nRUN_DIR=\"$HOME\u002F.cache\u002Fbitmovin\u002Fbitmovin-encoding-live\u002F$RUN_ID\"\nmkdir -p \"$RUN_DIR\"\ncp params.yaml \"$RUN_DIR\u002Fparams.yaml\"\n\"$PYTHON\" -c \"\nimport jinja2, sys, yaml, pathlib\nparams = yaml.safe_load(pathlib.Path('$RUN_DIR\u002Fparams.yaml').read_text())\nenv = jinja2.Environment(\n    loader=jinja2.FileSystemLoader('\u003CSKILL_DIR>\u002Ftemplates'),\n    undefined=jinja2.StrictUndefined,\n    trim_blocks=True, lstrip_blocks=True,\n)\nout = env.get_template('\u003CTEMPLATE_NAME>.yaml.j2').render(**params)\npathlib.Path('$RUN_DIR\u002Ftemplate.yaml').write_text(out)\nprint('$RUN_DIR\u002Ftemplate.yaml')\n\"\n```\n\nShow the rendered YAML to the user (or its key sections) and confirm before\nposting.\n\n### Step 6 — Validate + start\n\nValidate the rendered YAML in two passes — schema (the CLI) and\ncross-field semantic rules (`validate_rules.py`) — then submit it. The\nCLI fetches the schema once and caches it for 24h at\n`~\u002F.config\u002Fbitmovin\u002Ftemplate-schema-v1.json`.\n\n```bash\n# 1. Schema check — structural validity, required fields, enum values.\nbitmovin encoding templates validate \"$RUN_DIR\u002Ftemplate.yaml\"\n\n# 2. Cross-field rule check — semantic constraints the schema can't\n#    express (live-encoding-mode subset, fMP4 stream count, AV1 min\n#    bitrate, the `inputs.rtmp` silent-drop, `$\u002F...` streamKeyId refs).\n#    Skip only as a last resort if a rule fires that the user is sure\n#    is a false positive (the API may still reject server-side).\n\"$PYTHON\" \u003CSKILL_DIR>\u002Fscripts\u002Fvalidate_rules.py \"$RUN_DIR\u002Ftemplate.yaml\" \\\n  || { echo \"error: rule checks failed; fix the template before submitting\" >&2; exit 1; }\n\n# 3. Submit.\nENCODING_ID=$(\n  bitmovin encoding templates start \"$RUN_DIR\u002Ftemplate.yaml\" \\\n    | awk '\u002F^Encoding started:\u002F {print $3}'\n)\ntest -n \"$ENCODING_ID\" || { echo \"error: failed to capture encoding id\" >&2; exit 1; }\necho \"encoding id: $ENCODING_ID\"\n```\n\nDo **not** pass `--watch` — the CLI's watch mode waits for the encoding to\nreach `FINISHED\u002FERROR\u002FCANCELED\u002FTRANSFER_ERROR`, which a live encoding never\ndoes until it is stopped. Step 7 polls for `RUNNING` (the success state for\nlive).\n\nIf `templates validate` reports schema errors, or `validate_rules.py`\nprints any violations, abort and surface them to the user. For prebaked\nscenarios the structure usually points at a missing param; for `custom`,\nthe rule_id in the violation output points directly at\n`references\u002Frulebook.yaml` for the explanation.\n\n### Step 7 — Wait for `RUNNING`, then surface ingest details\n\nThree short polling loops. All three call the Bitmovin CLI; no Python\nhelper is involved.\n\n**7a — defensive partial-parse check (≈15s).** The Templates API\noccasionally returns 200 + encoding-id but silently drops sections it\ndidn't recognize (e.g. `inputs.rtmp` instead of `inputs.redundantRtmp`).\nConfirm the encoding's `type` settled to `LIVE` before doing anything else\n— this is what catches that bug:\n\n```bash\nDEADLINE=$(( $(date +%s) + 15 ))\nACTUAL_TYPE=\"\"\nwhile [ \"$(date +%s)\" -lt \"$DEADLINE\" ]; do\n  ACTUAL_TYPE=$(bitmovin encoding jobs get \"$ENCODING_ID\" --jq .type 2>\u002Fdev\u002Fnull | tr -d '\"')\n  [ \"$ACTUAL_TYPE\" = \"LIVE\" ] && break\n  sleep 2\ndone\nif [ \"$ACTUAL_TYPE\" != \"LIVE\" ]; then\n  echo \"error: Templates API silently dropped most of the template\" >&2\n  echo \"  encoding $ENCODING_ID has type='$ACTUAL_TYPE' (expected 'LIVE') after 15s\" >&2\n  echo \"  inspect the rendered template at $RUN_DIR\u002Ftemplate.yaml for unknown keys\" >&2\n  echo \"  (e.g. 'inputs.rtmp' instead of 'inputs.redundantRtmp')\" >&2\n  exit 1\nfi\n```\n\n**7b — wait for `RUNNING` (10 min).** Live encoders take 30–120s to come\nup, sometimes longer. Bail out on the terminal-bad statuses, otherwise\nkeep polling. Transient CLI errors (network blips, 5xx) are ignored:\n\n```bash\nDEADLINE=$(( $(date +%s) + 600 ))\nSTATUS=\"\"\nwhile [ \"$(date +%s)\" -lt \"$DEADLINE\" ]; do\n  STATUS=$(bitmovin encoding jobs status \"$ENCODING_ID\" --jq .status 2>\u002Fdev\u002Fnull | tr -d '\"')\n  case \"$STATUS\" in\n    RUNNING) break ;;\n    ERROR|CANCELED|FINISHED) echo \"error: encoding $ENCODING_ID reached terminal status $STATUS\" >&2; exit 1 ;;\n  esac\n  sleep 10\ndone\n[ \"$STATUS\" = \"RUNNING\" ] || { echo \"error: timed out after 10 min waiting for RUNNING\" >&2; exit 1; }\n```\n\n**7c — wait for ingest details, then print.** Right after RUNNING the\nencoder may still be allocating an IP. `bitmovin encoding jobs live`\nreturns `available: false` during that window:\n\n```bash\nDEADLINE=$(( $(date +%s) + 300 ))\nwhile [ \"$(date +%s)\" -lt \"$DEADLINE\" ]; do\n  AVAILABLE=$(bitmovin encoding jobs live \"$ENCODING_ID\" --jq '.available \u002F\u002F true' 2>\u002Fdev\u002Fnull)\n  [ \"$AVAILABLE\" = \"true\" ] && break\n  sleep 10\ndone\n\nbitmovin encoding jobs live \"$ENCODING_ID\"\necho\necho \"Dashboard:    https:\u002F\u002Fdashboard.bitmovin.com\u002Flive\u002Fencodings\u002F$ENCODING_ID\"\necho \"Output base:  \u003CoutputBasePath from params> (manifests are listed in the dashboard)\"\n```\n\nThe `bitmovin encoding jobs live` output covers all three template\nshapes:\n\n- **`rtmp`** and **`redundant-rtmp`** — `streamKeys[]` lists each static\n  ingest point's stream key (one entry for `rtmp`, two for\n  `redundant-rtmp`). Combine with `encoderIp` \u002F `application` to construct\n  `rtmp:\u002F\u002F\u003CencoderIp>\u002F\u003Capplication>\u002F\u003CstreamKey>`.\n- **`srt`** — `srtInputs[]` carries `mode`, `host`, `port`, `path`. For\n  `LISTENER` mode the user pushes to `srt:\u002F\u002F\u003CencoderIp>:\u003Cport>` (with\n  `?streamid=\u003Cpath>` if `path` is set); for `CALLER` mode the encoder\n  dials the host\u002Fport the user runs their own listener on.\n\nTranslate the JSON to a usable ingest URL when reporting back to the user.\n\n### Stop when the user is done\n\nRemind the user that an encoding is now running.\nOffer to stop the encoding for the user.\n\n```bash\nbitmovin encoding jobs stop \"$ENCODING_ID\"\n```\n\nThe encoding shell remains in the account (not deleted). The user can\ninspect logs and statistics afterwards via the dashboard.\n\nFor an ad-hoc status check at any point, use the CLI directly:\n\n```bash\nbitmovin encoding jobs status \"$ENCODING_ID\"\n```\n\n## Safety Rules\n\n- Never log `BITMOVIN_API_KEY`, `BITMOVIN_OUTPUT_ACCESS_KEY`,\n  `BITMOVIN_OUTPUT_SECRET_KEY`, or any value resembling a secret.\n- Never accept credentials as literal CLI args or in `params.yaml`. The\n  Bitmovin CLI's `outputs create` requires `--access-key` \u002F `--secret-key`\n  flags — pass these only via shell env-var expansion (`--access-key\n  \"$BITMOVIN_OUTPUT_ACCESS_KEY\"`) so the literal value never appears in\n  scripts, history, or rendered templates.\n- Never write secrets into the rendered template.\n- If a user pastes a secret into the prompt, refuse to use it; tell them to\n  export it as an env var instead.\n",{"name":5,"description":7},"main","14b211bb07d3e4057b85fb9140369fc73b1eb33b","skills\u002Fbitmovin-encoding-live","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills","https:\u002F\u002Fgithub.com\u002Fbitmovin","https:\u002F\u002Fbitmovin.com","2026-09-11 10:36:33",{"source":8,"skillName":5,"commands":50,"prompt":60,"zipAvailable":61},[51,54,57],{"tool":52,"command":53},"npx","npx skills add bitmovin\u002Fskills --skill bitmovin-encoding-live",{"tool":55,"command":56},"bunx","bunx skills add bitmovin\u002Fskills --skill bitmovin-encoding-live",{"tool":58,"command":59},"pnpm","pnpm dlx skills add bitmovin\u002Fskills --skill bitmovin-encoding-live","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-encoding-live\u002FSKILL.md",true,[63,69,73,77,83,87,93,97],{"path":64,"resourceType":65,"resourceTypeDesc":66,"sizeBytes":67,"sourceHtmlUrl":68},"examples\u002Fparams.example.yaml","REFERENCE","参考文档",3438,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-encoding-live\u002Fexamples\u002Fparams.example.yaml",{"path":70,"resourceType":65,"resourceTypeDesc":66,"sizeBytes":71,"sourceHtmlUrl":72},"README.md",5255,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-encoding-live\u002FREADME.md",{"path":74,"resourceType":65,"resourceTypeDesc":66,"sizeBytes":75,"sourceHtmlUrl":76},"references\u002Frulebook.yaml",8911,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-encoding-live\u002Freferences\u002Frulebook.yaml",{"path":78,"resourceType":79,"resourceTypeDesc":80,"sizeBytes":81,"sourceHtmlUrl":82},"scripts\u002Fensure_venv.sh","SCRIPT","脚本",2049,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-encoding-live\u002Fscripts\u002Fensure_venv.sh",{"path":84,"resourceType":79,"resourceTypeDesc":80,"sizeBytes":85,"sourceHtmlUrl":86},"scripts\u002Fvalidate_rules.py",15725,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-encoding-live\u002Fscripts\u002Fvalidate_rules.py",{"path":88,"resourceType":89,"resourceTypeDesc":90,"sizeBytes":91,"sourceHtmlUrl":92},"templates\u002Fredundant-rtmp-live.yaml.j2","OTHER","其他",7485,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-encoding-live\u002Ftemplates\u002Fredundant-rtmp-live.yaml.j2",{"path":94,"resourceType":89,"resourceTypeDesc":90,"sizeBytes":95,"sourceHtmlUrl":96},"templates\u002Frtmp-live.yaml.j2",7577,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-encoding-live\u002Ftemplates\u002Frtmp-live.yaml.j2",{"path":98,"resourceType":89,"resourceTypeDesc":90,"sizeBytes":99,"sourceHtmlUrl":100},"templates\u002Fsrt-live.yaml.j2",6742,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-encoding-live\u002Ftemplates\u002Fsrt-live.yaml.j2",[102,132,157,179,199,221],{"skillNo":103,"name":9,"nameZh":6,"description":104,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":115,"publishedAt":37},"01M6XCZ365TGD9DCJG6Z45S0T4","Hub skill for Bitmovin video infrastructure. Use when the user mentions Bitmovin, asks about video playback, encoding, transcoding, streaming, player integration, video analytics, content delivery optimization, or video AI. Routes to the right Bitmovin product (Player, VOD\u002FLive Encoding, Observability, AI Scene Analysis, Streams, Stream Lab) and walks through MCP\u002FCLI\u002FSDK setup.","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin\u002FSKILL.md",[107,112],{"categoryNo":108,"categoryName":109,"taxonomyType":110,"taxonomyTypeDesc":111},"01JBSEEDCAT000000000000016","图像与多媒体","SCENARIO","使用场景",{"categoryNo":113,"categoryName":114,"taxonomyType":110,"taxonomyTypeDesc":111},"01JBSEEDCAT000000000000023","Skill与智能体开发",[9,116,36,117,118,119,120,121,122,123,124,26,125,126,21,127,128,129,130,131],"MCP","analytics","infrastructure","product","observability","streaming","Optimization","analysis","Video","sdk setup","delivery","streams","lab","player","for","when",{"skillNo":133,"name":134,"nameZh":6,"description":135,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":136,"categories":137,"keywords":141,"publishedAt":37},"01M6XCYXP57QRYXCW7QN1HXNVC","bitmovin-player-web","Integrate the Bitmovin Web Player SDK into a web app. Use when the user asks to add video playback, embed a player, play HLS\u002FDASH\u002FMP4\u002FWHEP\u002FMOQ, set up DRM (Widevine\u002FPlayReady\u002FFairPlay), integrate ads, customize the Player UI, or work with the Bitmovin Player in any way. Covers both Player v8 (stable) and Player Web X \u002F PWX (next-gen).","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-player-web\u002FSKILL.md",[138],{"categoryNo":139,"categoryName":140,"taxonomyType":110,"taxonomyTypeDesc":111},"01JBSEEDCAT000000000000009","前端开发",[142,129,143,9,144,145,146,124,147,148,149,26,150,151,152,153,154,155,156,131],"web","bitmovin player web","play","ui","SET","ads","mp4","work","integrate","up","next","app","add","dash","hls",{"skillNo":158,"name":159,"nameZh":6,"description":160,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":161,"categories":162,"keywords":163,"publishedAt":37},"01M6XCYRHQ60DC0YXWRCASET1X","bitmovin-player-ios","Integrate and troubleshoot the Bitmovin Player SDK for iOS, tvOS, and visionOS in Swift, SwiftUI, UIKit, and existing Objective-C apps, including playback setup, player lifecycle, source loading, DRM, ads, AirPlay, Google Cast \u002F Chromecast, PiP, platform-specific UX, logs, network diagnostics, and stream validation.","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-player-ios\u002FSKILL.md",[],[164,129,165,9,166,167,168,169,170,147,171,172,150,173,174,175,130,176,177,178],"ios","bitmovin player ios","google","swift","validation","logs","lifecycle","swiftui","uikit","platform","apps","pip","Existing","stream","objective",{"skillNo":180,"name":181,"nameZh":6,"description":182,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":183,"categories":184,"keywords":185,"publishedAt":37},"01M6XCYM297KGT0QKD7J9DNC4G","bitmovin-player-android","Integrate and troubleshoot the Bitmovin Android Player SDK in Kotlin\u002FCompose Android apps, including playback setup, Web UI, PiP\u002Ffullscreen handlers, media session, cast, TV variants, and source-loading robustness.","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-player-android\u002FSKILL.md",[],[186,129,187,9,188,142,145,189,190,191,150,192,193,174,194,195,175,196,197,198],"android","bitmovin player android","troubleshoot","Media","kotlin","session","compose","cast","web ui","loading","source","robustness","variants",{"skillNo":200,"name":201,"nameZh":6,"description":202,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":203,"categories":204,"keywords":206,"publishedAt":37},"01M6XCYCHP945BKSJ37BMDR0KR","bitmovin-observability-root-cause-analysis","Root-cause analysis for a video QoE metric that moved — startup time, rebuffer ratio, error rate, bitrate, video-start-failures — using the Bitmovin Observability MCP. Use whenever someone asks WHY a QoE metric changed, spiked, dropped, regressed, or \"looks off\", whether a release or CDN change hurt quality, or what drives a delta between two windows or cohorts. Trigger even without \"RCA\" — \"rebuffering is up in APAC\", \"did v11 regress startup?\", \"what happened to error rate Tuesday\" all belong here. Drives a disciplined Define→Gather→Reflect→Conclude investigation with mix-vs-rate (Simpson's paradox) protection and a falsification step, producing a structured findings object.","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-root-cause-analysis\u002FSKILL.md",[205],{"categoryNo":113,"categoryName":114,"taxonomyType":110,"taxonomyTypeDesc":111},[207,120,123,208,209,9,210,116,211,212,213,214,124,215,151,216,217,218,219,220],"root cause analysis","root","root cause","bitmovin observability root cause analysis","change","reflect","release","start","why","time","error","mix","metric","rate",{"skillNo":222,"name":223,"nameZh":6,"description":224,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":225,"categories":226,"keywords":235,"publishedAt":37},"01M6XCY63KD9ZQ5S5P4KSV881A","bitmovin-observability-collector-integration","Integrate the Bitmovin Observability (Analytics) collector into any video player on any platform. Use WHENEVER a developer wants to add Bitmovin Analytics \u002F Observability tracking, \"set up the collector\", \"enable analytics\", \"track playback \u002F QoE \u002F impressions\", wire up a player to the Bitmovin dashboard, or asks why their analytics data is missing, empty, or wrong after integrating. Covers Web, Android\u002FFire TV, iOS\u002FtvOS\u002FvisionOS, and Roku, for both the Bitmovin Player and third-party engines (HLS.js, Shaka, Video.js, HTML5, ExoPlayer, Media3, AVPlayer, THEOplayer, CAFv3). Use it even when the user only names their player (\"add analytics to my Shaka player\") without saying \"Bitmovin collector\". Knows that the Bitmovin Player has analytics pre-integrated while third-party players need a separate collector with a managed attach\u002Fdetach lifecycle.","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-collector-integration\u002FSKILL.md",[227,230],{"categoryNo":228,"categoryName":229,"taxonomyType":110,"taxonomyTypeDesc":111},"01JBSEEDCAT000000000000013","分析与可视化",{"categoryNo":231,"categoryName":232,"taxonomyType":233,"taxonomyTypeDesc":234},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[120,236,237,9,238,239,240,146,124,215,241,150,242,151,243,129,244,245,246,131],"integration","collector","bitmovin observability collector integration","dashboard","data","video player","only","attach","my","track","managed"]