LumenClip Docs
LumenClip Docs
LumenClip engineering documentation
Output generationSlideshow generation pipelineX and Threads generation pipelineLinkedIn generation pipelineUGC video generation pipeline
Output generation

Slideshow generation pipeline

Transform a slideshow automation and its asset collections into rendered slides and an optional MP4.

Slideshow generation pipeline

The pipeline transforms an automation snapshot, asset collections, and generation history into a stored slideshow. "...output": "stage-N output" means the complete preceding output is spread into the next stage input; it is documentation shorthand, not a literal runtime field. The stage envelopes below make internal values visible even when the implementation passes them as function arguments rather than serializing them between stages.

The interactive path is orchestrated by lib/automation-runner.ts. Scheduled Railway jobs use railway/functions/job-worker/src/slideshow-automation.js. Both delegate hook, text, and image decisions to lib/slideshow-generation-engine.ts and use the shared slideshow render contract.

Stage map

#StageAdds to the preceding output
1Validate generation inputNormalized automation, collections, history, and model settings
2Resolve slide countHook, body, CTA, and total counts
3Select and expand hookConcrete hook text, substitutions, and per-hook overrides
4Optional web researchExact-hook facts and source URLs
5Build structured promptOpenRouter messages and response schema
6Generate slideshow textMetadata and non-hook placeholder text
7Similarity retryAccepted first draft or one rewritten draft
8Optional visual conceptsConcrete visual search concepts per AI-selected slide
9Build image shortlistsAt most 12 caption-ranked candidates per AI-selected slide
10Select imagesOne pinned, deterministic, or model-selected image per slide
11Assemble slideshow planComplete hook, content, and CTA slide objects
12Optional translationLocalized displayed text
13Render and store PNG slidesOrdered image artifacts and thumbnail
14Optional MP4 renderVideo artifact for video publish type
15Validate generated outputDeterministic QA report
16Finalize generated outputResult record, run state, and reuse-memory entries

Stage 1 — Validate generation input

Input

{
  "automationId": "automation-astrology-01",
  "automationName": "Daily zodiac signs",
  "scheduledFor": "2026-08-01T03:00:00.000Z",
  "schema": {
    "hooks": [
      {
        "id": "hook-1",
        "text": "3 signs that need to hear this",
        "enabled": true
      }
    ],
    "hook_slots": { "SIGN": "zodiac-signs" },
    "formatting": [
      { "id": "hook", "slideCount": 1 },
      { "id": "content", "slideCount": 3 },
      { "id": "cta", "slideCount": 1 }
    ],
    "image_collection_ids": {
      "first_slide": { "collection_id": "zodiac-covers", "mode": "collection" },
      "content_slides": { "collection_id": "zodiac-scenes" },
      "cta_slide": { "collection_id": "zodiac-cta", "check": true }
    },
    "language": "English",
    "web_search_enabled": true
  },
  "collections": [
    { "id": "zodiac-covers", "assetCount": 20 },
    { "id": "zodiac-scenes", "assetCount": 80 },
    { "id": "zodiac-cta", "assetCount": 5 }
  ],
  "wordCollections": [
    { "id": "zodiac-signs", "values": ["Aries", "Cancer", "Pisces"] }
  ],
  "usageHistory": [],
  "generationSettings": { "slideshowTextModel": "openai/gpt-5.6-luna" }
}

Processing: normalize the schema and require enabled hooks, referenced collections, usable assets, valid word-collection bindings, slide sections, and an OpenRouter key.

Output

{
  "automation": {
    "id": "automation-astrology-01",
    "name": "Daily zodiac signs"
  },
  "scheduledFor": "2026-08-01T03:00:00.000Z",
  "schema": {
    "status": "valid",
    "language": "English",
    "webSearchEnabled": true
  },
  "collectionsById": {
    "zodiac-covers": { "assetCount": 20 },
    "zodiac-scenes": { "assetCount": 80 },
    "zodiac-cta": { "assetCount": 5 }
  },
  "wordCollectionsById": { "zodiac-signs": { "valueCount": 3 } },
  "usageHistory": [],
  "textModel": "openai/gpt-5.6-luna",
  "blockers": []
}

Model/provider: none.

Stage 2 — Resolve slide count

Input

{
  "...output": "stage-1 output",
  "formatting": {
    "hook": { "slideCount": 1 },
    "content": { "slideCountMode": "static", "slideCount": 3 },
    "cta": { "slideCount": 1 }
  }
}

Processing: resolve static or ranged body count. A hook-specific bodySlideCount overrides the automation default when that hook is later selected.

Output

{
  "...output": "stage-1 output",
  "slideCount": {
    "mode": "static",
    "hook": 1,
    "body": 3,
    "cta": 1,
    "total": 5,
    "minimum": 3,
    "maximum": 3
  }
}

Model/provider: none.

Stage 3 — Select and expand hook

Input

{
  "...output": "stage-2 output",
  "enabledHooks": [
    {
      "id": "hook-1",
      "text": "3 signs that need to hear this",
      "enabled": true
    },
    { "id": "hook-2", "text": "Why [[SIGN]] goes quiet", "enabled": true }
  ],
  "hookSlots": { "SIGN": "zodiac-signs" },
  "recentPublishedHookKeys": [],
  "distinctVariableDraws": true
}

Processing: exclude recently published hooks and combinations, choose a hook, draw variable values, enforce distinct draws, and apply casing plus hook-specific tone/count overrides.

Output

{
  "...output": "stage-2 output",
  "hook": "Why Cancer goes quiet",
  "hookId": "hook-2",
  "hookTemplate": "Why [[SIGN]] goes quiet",
  "hookSubstitutions": { "SIGN": "Cancer" },
  "hookToneOverride": null,
  "bodySlideCountOverride": null
}

Model/provider: none.

Stage 4 — Optional web research

Input

{
  "...output": "stage-3 output",
  "enabled": true,
  "hook": "Why Cancer goes quiet",
  "automationName": "Daily zodiac signs",
  "model": "openai/gpt-5.4-mini",
  "maxResults": 5
}

Processing: research the exact hook and keep concise facts with source URLs. When disabled, research is null and the preceding output passes through.

Output

{
  "...output": "stage-3 output",
  "research": {
    "content": "Concise source-grounded facts relevant to the selected hook.",
    "sources": [
      { "title": "Source title", "url": "https://example.com/source" }
    ]
  },
  "webSearchSources": ["https://example.com/source"]
}

Model/provider: openai/gpt-5.4-mini via OpenRouter, using the Exa web plugin.

Stage 5 — Build structured generation prompt

Input

{
  "...output": "stage-4 output",
  "tone": "Conversational & Relatable",
  "promptInstructions": [
    "Keep every body slide specific to the selected hook.",
    "Use the selected repeatable content route when one matches."
  ],
  "placeholders": [
    {
      "id": "content-2__body",
      "section": "content",
      "contentDirection": "Explain one concrete behavior",
      "wordLengthMin": 12,
      "wordLengthMax": 35
    }
  ],
  "recentTextExclusions": [],
  "recentHeadingExclusions": []
}

Processing: compile the automation name, fixed hook, tone, metadata requirements, non-hook placeholder directions, structure/content-route instructions, reuse exclusions, and optional research into a structured OpenRouter request. The hook is context and is not a model-fillable placeholder.

Output

{
  "...output": "stage-4 output",
  "promptPayload": {
    "model": "openai/gpt-5.6-luna",
    "stream": false,
    "plugins": [{ "id": "response-healing" }],
    "messages": [
      {
        "role": "system",
        "content": "Slideshow generation rules and JSON contract"
      },
      {
        "role": "user",
        "content": "Automation, slide, research, and avoidance instructions"
      }
    ],
    "response_format": {
      "type": "json_schema",
      "json_schema": { "name": "temp_slide_testing_text", "strict": true }
    }
  },
  "responseSchema": {
    "name": "temp_slide_testing_text",
    "required": ["title", "caption", "hashtags", "text"]
  }
}

Model/provider: none; this stage only constructs the next provider request.

Stage 6 — Generate slideshow text

Input

{
  "...output": "stage-5 output",
  "model": "openai/gpt-5.6-luna",
  "promptPayload": { "messages": "stage-5 messages" },
  "responseSchema": { "name": "slideshow_text" }
}

Processing: require complete structured output, normalize punctuation, enforce the configured tone casing, validate required fields and word ranges, and retry once with exact repair feedback when the provider response is incomplete or invalid. The provider returns hashtags as an array; the normalized stage output stores them as one space-separated string.

Output

{
  "...output": "stage-5 output",
  "generatedText": {
    "title": "Why Cancer Goes Quiet",
    "caption": "Silence is sometimes how Cancer makes room to process.",
    "hashtags": "#cancer #zodiac #astrology",
    "text": {
      "content-2__body": "They notice the emotional shift before anyone names it.",
      "content-3__body": "Distance gives them time to separate instinct from reaction.",
      "content-4__body": "They return when their words feel honest instead of defensive.",
      "cta-5__body": "Save this for the Cancer in your life."
    }
  },
  "textModel": "openai/gpt-5.6-luna",
  "violations": [],
  "transformations": []
}

Model/provider: configured slideshowTextModel; default openai/gpt-5.6-luna via OpenRouter.

Stage 7 — Similarity retry

Input

{
  "...output": "stage-6 output",
  "generatedSignature": "why cancer goes quiet they notice the emotional shift",
  "recentPublishedSignatures": [
    "why cancer goes quiet they feel every emotional shift"
  ],
  "similarityThreshold": 0.85
}

Processing: compare normalized output with recent published text. When the threshold is met, regenerate once with exact text and heading exclusions. Otherwise pass stage 6 through unchanged.

Output

{
  "...output": "stage-6 output",
  "generatedText": {
    "title": "What Cancer's Silence Is Doing",
    "caption": "Cancer often gets quiet before choosing an honest response.",
    "hashtags": "#cancer #zodiac #astrology",
    "text": {
      "content-2__body": "They register the shift before deciding whether it is safe to respond.",
      "content-3__body": "Time alone helps them separate the feeling from the first reaction.",
      "content-4__body": "They speak again once the answer feels honest rather than defensive.",
      "cta-5__body": "Save this for the Cancer you understand differently now."
    }
  },
  "textSimilarityRetry": true
}

The retry result becomes authoritative; the pipeline does not calculate or expose a second similarity score after rewriting.

Model/provider: same configured slideshow model via OpenRouter.

Stage 8 — Derive visual concepts

Input

{
  "...output": "stage-7 output",
  "enabled": true,
  "model": "openai/gpt-5.6-luna",
  "slides": [
    {
      "id": "content-1",
      "text": "They notice the emotional shift before anyone names it."
    },
    {
      "id": "content-2",
      "text": "Distance gives them time to separate instinct from reaction."
    }
  ]
}

Processing: when at least one slide enables AI image selection, convert abstract copy into concrete subjects, objects, settings, lighting, and colors suitable for caption matching. Provider failure returns an empty concept array for each slide. When no slide enables AI selection, this stage passes stage 7 through without calling a model.

Output

{
  "...output": "stage-7 output",
  "visualConceptsBySlide": [
    {
      "slideId": "content-1",
      "concepts": ["person noticing a mood change", "dim room", "blue light"]
    },
    {
      "slideId": "content-2",
      "concepts": ["solitary figure by water", "moonlight", "quiet reflection"]
    }
  ]
}

Model/provider: same configured slideshow model via OpenRouter.

Stage 9 — Build image shortlists

Input

{
  "...output": "stage-8 output",
  "collectionBindings": {
    "hook": "zodiac-covers",
    "content": "zodiac-scenes",
    "cta": "zodiac-cta"
  },
  "visualConceptsBySlide": "stage-8 concepts",
  "recentImageUsage": ["image-used-last-week"],
  "shortlistLimit": 12
}

Processing: load the collection bound to each section, prefer section-tagged captions, honor pinned assets, remove duplicates already chosen in the current slideshow, and rank caption/concept overlap locally. AI-selected slides retain at most 12 candidates. Recent-use history controls the deterministic fresh/least-recently-used path and is recorded as a warning if an AI choice reuses an image; it is not a hard exclusion from the AI shortlist.

Output

{
  "...output": "stage-8 output",
  "shortlists": [
    {
      "slideId": "content-1",
      "candidates": [
        {
          "index": 0,
          "id": "image-44",
          "caption": "Person in blue light watching a quiet room"
        },
        {
          "index": 1,
          "id": "image-12",
          "caption": "Moonlit portrait near water"
        }
      ]
    }
  ]
}

Model/provider: none.

Stage 10 — Select images

Input

{
  "...output": "stage-9 output",
  "model": "openai/gpt-5.6-luna",
  "slideText": "They notice the emotional shift before anyone names it.",
  "visualConcepts": ["person noticing a mood change", "dim room", "blue light"],
  "candidates": [
    {
      "index": 0,
      "id": "image-44",
      "caption": "Person in blue light watching a quiet room"
    },
    { "index": 1, "id": "image-12", "caption": "Moonlit portrait near water" }
  ]
}

Processing: a pinned slide uses its configured image. An AI-selected slide with multiple candidates asks the model for one candidate index; a malformed or out-of-range result falls back to candidate 0. Every other slide selects a fresh candidate deterministically, then falls back to the least recently used candidate when its fresh pool is empty.

Output

{
  "...output": "stage-9 output",
  "selectedImages": [
    {
      "id": "image-44",
      "key": "image-44",
      "imageUrl": "/api/local-assets/image-44.jpg",
      "imageCaption": "Person in blue light watching a quiet room",
      "reusedRecently": false
    }
  ]
}

Model/provider: same configured slideshow model via OpenRouter.

Stage 11 — Assemble slideshow plan

Input

{
  "...output": "stage-10 output",
  "generatedText": "stage-7 structured text",
  "selectedImages": "stage-10 selected images",
  "layout": {
    "aspectRatio": "9:16",
    "imageFit": "cover",
    "font": "Inter",
    "slideDurationSeconds": 3
  }
}

Processing: attach each text item to its selected image, role, overlay, icon layout, aspect ratio, and placement. Duration is applied later from the slideshow render settings.

Output

{
  "...output": "stage-10 output",
  "plan": {
    "title": "What Cancer's Silence Is Doing",
    "caption": "Silence is sometimes how Cancer makes room to process.",
    "hashtags": "#cancer #zodiac #astrology",
    "hook": "Why Cancer goes quiet",
    "textModel": "openai/gpt-5.6-luna",
    "slides": [
      {
        "id": "slide-1",
        "role": "hook",
        "imageUrl": "/api/local-assets/hook.jpg",
        "textItems": [{ "id": "hook-text", "text": "Why Cancer goes quiet" }],
        "aspectRatio": "9:16"
      }
    ]
  }
}

Model/provider: none.

Stage 12 — Optional translation

Input

{
  "...output": "stage-11 output",
  "language": "German",
  "texts": [
    "Why Cancer goes quiet",
    "They notice the emotional shift before anyone names it."
  ]
}

Processing: translate every displayed text item when the language maps to a DeepL target. English and unsupported targets pass through unchanged. Missing translations fail the stage.

Output

{
  "...output": "stage-11 output",
  "localizedPlan": {
    "language": "German",
    "slides": [
      {
        "id": "slide-1",
        "textItems": [{ "id": "hook-text", "text": "Warum Krebs still wird" }]
      }
    ]
  }
}

Model/provider: DeepL API. LumenClip does not select a DeepL model.

Stage 13 — Render and store PNG slides

Input

{
  "...output": "stage-12 output",
  "slideshowId": "slideshow-run-01",
  "plan": "localized stage-12 plan",
  "renderSettings": {
    "aspectRatio": "9:16",
    "font": "Inter",
    "imageFit": "cover"
  }
}

Processing: load source/overlay/icon bytes, render each slide to SVG, rasterize it with sharp, and store ordered PNGs. The result/output record is created with these artifacts before QA runs; QA controls downstream readiness rather than whether the generated media exists.

Output

{
  "...output": "stage-12 output",
  "slideshowId": "slideshow-run-01",
  "outputImages": [
    "/api/local-assets/slideshows/outputs/slideshow-run-01/slide-001.png",
    "/api/local-assets/slideshows/outputs/slideshow-run-01/slide-002.png"
  ],
  "thumbnailUrl": "/api/local-assets/slideshows/outputs/slideshow-run-01/slide-001.png",
  "renderedSlides": [{ "id": "slide-1", "role": "hook", "durationMs": 3000 }]
}

Model/provider: none; local SVG renderer and sharp.

Stage 14 — Optional MP4 render

Input

{
  "...output": "stage-13 output",
  "publishType": "video",
  "frames": ["slide-001.png", "slide-002.png"],
  "durationSecondsPerFrame": 3,
  "outputFormat": "mp4"
}

Processing: upload frames to Rendi, build an FFmpeg concat command, render H.264, download the video, and persist it. Slideshow publish type skips this stage.

Output

{
  "...output": "stage-13 output",
  "videoUrl": "/api/local-assets/slideshows/outputs/slideshow-run-01/slideshow-export.mp4",
  "videoProvider": "rendi",
  "videoProcessor": "ffmpeg"
}

Model/provider: no AI model; Rendi executes FFmpeg.

Stage 15 — Validate generated output

Input

{
  "...output": "stage-14 output",
  "expectedSlideCount": 5,
  "slides": "rendered stage-13 slides",
  "hookSubstitutions": { "SIGN": "Cancer" },
  "reusePolicy": { "textSimilarityThreshold": 0.85 }
}

Processing: check count, empty text, unresolved tokens, word ranges, and duplicate variable draws. The validator can also emit a near-duplicate warning when a caller supplies prior runs; the normal scheduled call performs its text-similarity retry earlier at stage 7 instead.

Output

{
  "...output": "stage-14 output",
  "qa": {
    "valid": true,
    "actualSlideCount": 5,
    "bodySlideCount": 3,
    "findings": []
  }
}

Model/provider: none.

Stage 16 — Finalize generated output

Input

{
  "...output": "stage-15 output",
  "automationId": "automation-astrology-01",
  "runId": "run-01",
  "slideshowId": "slideshow-run-01"
}

Processing: finalize the run with the slideshow/result identifiers and QA state, then append generated image, text, and heading reuse-memory entries. The PNGs, optional MP4, and base result record were already stored during rendering. Hook and hook-combination exclusion is based only on later publication evidence.

Output

{
  "result": {
    "id": "result-run-01",
    "automationId": "automation-astrology-01",
    "runId": "run-01",
    "workflowType": "slideshow",
    "title": "What Cancer's Silence Is Doing",
    "status": "succeeded",
    "artifacts": {
      "slideshowId": "slideshow-run-01",
      "outputImages": [
        "/api/local-assets/slideshows/outputs/slideshow-run-01/slide-001.png",
        "/api/local-assets/slideshows/outputs/slideshow-run-01/slide-002.png"
      ],
      "videoUrl": "/api/local-assets/slideshows/outputs/slideshow-run-01/slideshow-export.mp4",
      "thumbnailUrl": "/api/local-assets/slideshows/outputs/slideshow-run-01/slide-001.png"
    },
    "payload": {
      "type": "slideshow",
      "caption": "Cancer often gets quiet before choosing an honest response.",
      "hashtags": "#cancer #zodiac #astrology"
    }
  },
  "run": {
    "id": "run-01",
    "status": "succeeded",
    "slideshowId": "slideshow-run-01",
    "qa": {
      "valid": true,
      "actualSlideCount": 5,
      "bodySlideCount": 3,
      "findings": []
    }
  },
  "reuseMemory": {
    "images": 5,
    "textSignatures": 1,
    "headingSignatures": 4
  }
}

Model/provider: none; the result store and slideshow storage (Railway-backed in the scheduled worker). Publication consumes this generated output downstream and is not part of this workflow.

Output generation

The generation pipelines that transform application inputs into content outputs.

X and Threads generation pipeline

Transform a persisted X/Threads automation and optional trend source into validated social text and optional generated media.

On this page

Slideshow generation pipelineStage mapStage 1 — Validate generation inputStage 2 — Resolve slide countStage 3 — Select and expand hookStage 4 — Optional web researchStage 5 — Build structured generation promptStage 6 — Generate slideshow textStage 7 — Similarity retryStage 8 — Derive visual conceptsStage 9 — Build image shortlistsStage 10 — Select imagesStage 11 — Assemble slideshow planStage 12 — Optional translationStage 13 — Render and store PNG slidesStage 14 — Optional MP4 renderStage 15 — Validate generated outputStage 16 — Finalize generated output