{
  "name": "Cloudinary — Embeddable Video Player (trim → optimized stream → paste-in embed)",
  "nodes": [
    {
      "parameters": {},
      "id": "44440000-0000-4a01-9001-000000000001",
      "name": "Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        240,
        320
      ]
    },
    {
      "parameters": {
        "resource": "upload",
        "operation": "uploadUrl",
        "url": "https://res.cloudinary.com/demo/video/upload/samples/sea-turtle.mp4",
        "resource_type": "video",
        "additionalFields": {
          "public_id": "n8n_blog/player_demo",
          "tags": "n8n-blog,player-demo"
        }
      },
      "id": "44440000-0000-4a01-9001-000000000002",
      "name": "Upload Sample Video",
      "type": "n8n-nodes-cloudinary.cloudinary",
      "typeVersion": 1,
      "position": [
        520,
        320
      ]
    },
    {
      "parameters": {
        "resource": "transform",
        "operation": "trimVideo",
        "transformPublicId": "={{ $json.public_id }}",
        "type": "upload",
        "trimStart": "0",
        "trimEnd": "4"
      },
      "id": "44440000-0000-4a01-9001-000000000003",
      "name": "Trim (0–4s)",
      "type": "n8n-nodes-cloudinary.cloudinary",
      "typeVersion": 1,
      "position": [
        820,
        320
      ]
    },
    {
      "parameters": {
        "resource": "transform",
        "operation": "videoThumbnail",
        "transformPublicId": "={{ $('Upload Sample Video').item.json.public_id }}",
        "type": "upload",
        "thumbnailFrameMode": "time",
        "thumbnailTimestamp": "2",
        "thumbnailFormat": "jpg",
        "continueFromTransformation": "={{ $json.transformation }}"
      },
      "id": "44440000-0000-4a01-9001-000000000004",
      "name": "Poster Frame (@2s)",
      "type": "n8n-nodes-cloudinary.cloudinary",
      "typeVersion": 1,
      "position": [
        1120,
        320
      ]
    },
    {
      "parameters": {
        "resource": "widget",
        "operation": "videoPlayer",
        "transformPublicId": "={{ $('Upload Sample Video').item.json.public_id }}",
        "type": "upload",
        "playerSourceTypes": [
          "hls",
          "mp4"
        ],
        "playerTransformation": "={{ $('Trim (0–4s)').item.json.transformation }}",
        "playerPoster": "={{ $json.secure_url }}",
        "playerPlayback": {
          "autoplayMode": "on-scroll",
          "muted": true,
          "loop": false,
          "playsinline": true,
          "controls": true
        },
        "playerLayout": {
          "fluid": true,
          "aspectRatio": "16:9",
          "cropMode": "fill"
        },
        "playerAppearance": {
          "skin": "dark"
        },
        "playerFeatures": {
          "seekThumbnails": true,
          "pictureInPictureToggle": true
        }
      },
      "id": "44440000-0000-4a01-9001-000000000005",
      "name": "Video Player Embed",
      "type": "n8n-nodes-cloudinary.cloudinary",
      "typeVersion": 2,
      "position": [
        1420,
        320
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Turn the player output into a drop-in <iframe> for any page or CMS.\nconst embed = $json.embed_url;\nconst iframe = `<iframe src=\"${embed}\" width=\"640\" height=\"360\" style=\"width:100%;aspect-ratio:16/9;border:0;\" allow=\"autoplay; fullscreen; encrypted-media; picture-in-picture\" allowfullscreen></iframe>`;\nreturn [{ json: { embed_url: embed, iframe_html: iframe, player_config: $json.player_config } }];"
      },
      "id": "44440000-0000-4a01-9001-000000000006",
      "name": "Build <iframe> Snippet",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1720,
        320
      ]
    },
    {
      "parameters": {
        "content": "## ▶️ Embeddable Video Player\n\n**A source video → a polished, adaptive-streaming player embed** you can paste into any page, email-friendly landing page, or CMS — no JS build step.\n\n### What it does\n1. **Upload Sample Video** pulls a public demo clip into *your* Cloudinary account (so the rest works on your own cloud). Returns its `public_id`.\n2. **Trim (0–4s)** builds a short preview cut (a resize/crop/trim transformation — *not* a format-pinned one, so it stays compatible with adaptive streaming).\n3. **Poster Frame** grabs a still at 2s, chained on top of the trim (**Continue From Transformation** = `{{ $json.transformation }}`) so the poster matches the clip.\n4. **Video Player Embed** generates the player: **HLS + MP4** source types (adaptive bitrate with MP4 fallback), the poster, `playsinline`, picture-in-picture, autoplay-on-scroll *while muted*, fluid `16:9`. It outputs both an **`embed_url`** and a self-hosting **`player_config`** JSON.\n5. **Build <iframe> Snippet** wraps the embed URL in a responsive `<iframe>`.\n\n### Make it yours\n- **Your video:** point *Upload Sample Video* at your own URL, or swap it for an *Upload File* / trigger that brings in real footage.\n- **Tweak the player:** skin/colors, autoplay mode, controls — all on the *Video Player Embed* node.\n- **Use it:** copy `iframe_html` into your page, or hand `player_config` to the [Cloudinary Video Player SDK](https://cloudinary.com/documentation/cloudinary_video_player) for a self-hosted player.\n\n> ℹ️ **HLS + a format-pinned transform don't mix.** Adaptive streaming (HLS/DASH) is delivered via a streaming profile, which Cloudinary won't combine with an `f_…` component (e.g. an *Optimize* step's `f_auto:video`). That's why the player is fed the **Trim** transformation, not an Optimize one. The node will flag this conflict for you if you wire it the other way.\n\n### Setup\n👉 Add your **Cloudinary API** credential to the Cloudinary nodes (Cloud name + API Key + Secret from the [Console](https://console.cloudinary.com/settings/api-keys)). Not included in this template.\n\n💾 **Then press `Cmd/Ctrl+S` to save before running.** That one save persists the credential to *every* Cloudinary node at once, so a full **Execute Workflow** run finds credentials everywhere. Skip the save and the run can fail node-after-node with *\"Node does not have any credentials set\"* — even though the credential looks selected. (If **Execute step** works but **Execute Workflow** doesn't, you forgot the save.)",
        "height": 1190,
        "width": 600
      },
      "id": "44440000-0000-4a01-9001-000000000007",
      "name": "README",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        200,
        560
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Upload Sample Video",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Sample Video": {
      "main": [
        [
          {
            "node": "Trim (0–4s)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trim (0–4s)": {
      "main": [
        [
          {
            "node": "Poster Frame (@2s)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Poster Frame (@2s)": {
      "main": [
        [
          {
            "node": "Video Player Embed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Video Player Embed": {
      "main": [
        [
          {
            "node": "Build <iframe> Snippet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {}
}
