AI Bedtime Story Generator โ n8n + KokoroTTS + ComfyUI + Blotato (100% Free)
Build a fully automated bedtime story pipeline: n8n orchestrates a local LLM to write the story, KokoroTTS narrates it, ComfyUI generates illustrated frames, NCA Toolkit assembles the video, and Blotato publishes it โ all running locally at zero per-run cost.
You should have n8n, ComfyUI, KokoroTTS, MinIO, and NCA Toolkit already running in Docker. See <a href="/lessons/self-hosting-n8n-minio-kokoro-nca">Self-Hosting n8n, MinIO, KokoroTTS and NCA Toolkit</a> for setup. You should also have a Blotato account with your API key ready โ see <a href="/lessons/automate-social-media-n8n-comfyui-blotato">Automate Social Media Posts with n8n + ComfyUI + Blotato</a> for Blotato setup.
Lesson Notes
What You'll Build
A zero-cost bedtime story factory: submit a form with a topic, voice, and art style and the workflow writes the story, narrates it with a chosen voice, generates matching AI illustrations, composes a video with a zoom-pan effect on each image, merges the voiceover, and uploads the finished video to Google Drive and YouTube via Blotato โ completely automated.
What You Need
- n8n running in Docker
- Baserow running in Docker (port 85) โ local no-code database used as the form backend and job tracker
- Ollama with a model loaded (tutorial uses the GPT OSS 20B parameter model)
- KokoroTTS running locally on port 8880
- ComfyUI with Flux 1 Dev checkpoint
- MinIO with a
bedtime-storiesbucket - NCA Toolkit in Docker (for zoom-pan, concatenation, and ffmpeg audio merge)
- Google Drive with a shared Bedtime Stories folder and credentials set up in n8n
- Blotato account with API key and social accounts connected
Per-run infrastructure cost once set up: $0. Everything except Blotato's subscription runs locally.
Workflow Overview
The pipeline has five logical phases: intake โ write โ narrate โ illustrate โ publish. Each phase uses a different tool, all wired together in n8n.
Phase 1 โ Baserow Setup
Install Baserow via Docker:
docker run -d --network host -v baserow_data:/baserow/data
--restart unless-stopped baserow/baserow:latest
Visit http://localhost:85, create an account, then create a database named Bedtime Stories with a table named stories. Add these fields:
languageโ Single line textvoicesโ Single select: Nicole, Bella, Jessica, Adam, Michael, Georgetopicโ Single line textaudienceโ Single line textcharacterโ Single line textart_styleโ Single select (values must match ComfyUI style node exactly, e.g.illustration, children's book)lengthโ Number (decimal allowed, e.g. 2.5 for 2.5 minutes)speedโ Number (decimal, voice playback speed)imagesโ Number (integer, how many illustrations to generate)statusโ Single select: pending (yellow), complete (green)final_videoโ URL field
Create a Baserow API token under My Settings โ Database Tokens with full CRUD permissions. You'll use this in n8n's Baserow credentials.
Phase 2 โ n8n Intake Form
Start the workflow with an On Form Submission trigger. Add one form field for each Baserow column above. Set all fields as required. The form doubles as your editorial interface โ fill it out to queue a new story.
After the form trigger, add a Baserow โ Create Row node to write the submission straight into your stories table. Then add Baserow โ Get Many Rows filtered to status contains pending, limit 1. This ensures the workflow always processes one pending row at a time even if you batch-submit via Google Sheets later.
Add a Set node to extract the three numeric controls you'll reference throughout: length, speed, and images.
Phase 3 โ Story Writing (Ollama LLM)
Add a Basic LLM Chain node with source set to Define Below. Write a system prompt that references {{ $json.audience }}, {{ $json.topic }}, {{ $json.character }}, and {{ $json.length }}. The Google Doc linked below has a copy-paste version.
Choose Ollama Chat Model as the sub-node. Critical setting: under Add Option โ Keep Alive set it to 0. This forces Ollama to unload the model from VRAM as soon as the chain finishes โ essential when ComfyUI needs that same VRAM in the next phase.
After the chain, add a Set node to strip
line breaks from the story text before feeding it to TTS. Use this expression in a String field named text:
{{ $json.text.replace(/\n/g, " ") }}
Phase 4 โ Narration (KokoroTTS)
Add an HTTP Request node:
- Method: POST
- URL:
http://host.docker.internal:8880/v1/audio/speech - Body: JSON with
input(the cleaned text),voice(from the form), andspeed
Voice names are case-sensitive and must match the KokoroTTS API exactly. Check available voices at http://localhost:8880/v1/voices if you're unsure. Then upload the returned binary to MinIO with the row's UUID as the filename (e.g. {{ $json.uuid }}.mp3).
After the upload, call NCA Toolkit's ffmpeg compose endpoint to get the audio duration in seconds โ you'll use this to calculate per-image clip length.
Phase 5 โ Image Prompts (Second LLM Chain)
Add a second Basic LLM Chain with a system prompt that asks the model to produce N image prompts based on the story text. Use a Structured Output Parser to ensure the model returns valid JSON. The number of prompts should match the images parameter from Phase 2.
After this chain, Split Out the prompts array, then add a Crypto node (type: UUID, property: uuid) to give each prompt a unique identifier. Add a final Set node that calculates clip_duration = audio_duration / images and builds the MinIO file path for each image.
Phase 6 โ Image Generation Loop (ComfyUI)
Wrap the following nodes in a Loop (batch size 1):
- HTTP POST to ComfyUI (
http://host.docker.internal:8188/prompt) with your API-export workflow JSON. Inject{{ $json.prompt }}into the positive text node and{{ $json.art_style }}into the style node. Use{{ Math.ceil(Math.random() * 1e12) }}as the seed so every image is unique. - Wait 30 seconds (adjust based on your GPU โ faster cards may need less).
- HTTP GET to ComfyUI history (
/history/{{ $json.prompt_id }}) to retrieve the output filename. - Code node โ paste the JavaScript snippet from the Google Doc to extract the image URL from ComfyUI's nested history response.
- HTTP GET the image from ComfyUI's view endpoint.
- MinIO S3 upload โ use the UUID filename from Phase 5 (
{{ $json.uuid }}.png).
After the loop exits, add an NCA Toolkit HTTP POST to apply a zoom-pan effect to each image clip for its calculated duration. Aggregate all four clip responses, run a Code node to extract their URLs into an array, then concatenate them with NCA Toolkit's video concatenate endpoint.
Finally, call NCA Toolkit's ffmpeg merge endpoint to combine the video clips with the MP3 narration into a single output file.
Phase 7 โ Update Baserow and Publish
Update the Baserow row: set status = complete and write the final video URL into the final_video field.
Download the final video file via HTTP GET, then upload it to Google Drive. Use your shared Bedtime Stories folder's ID (grab it from the Drive URL when the folder is open). The web content link Google Drive returns is what Blotato needs for upload.
Add two final HTTP POST nodes:
- Blotato upload โ POST to
/v2/mediawith your API key header and the Google Drive web content link as the URL. - Blotato post โ POST to
/postswithaccountId(your YouTube channel ID from Blotato settings),title,content, andprivacy: "unlisted". Unlisted means it won't go live until you review and publish manually from YouTube Studio.
Key Files and Links
- Story system prompt โ Google Doc linked in the YouTube description
- Image prompt system prompt โ Google Doc linked in the YouTube description
- ComfyUI workflow JSON (Flux 1 Dev + style node) โ included in the Google Doc
- Code node JavaScript (extract ComfyUI history URL) โ included in the Google Doc
Set <code>keep_alive = 0</code> on your Ollama Chat Model node. Without this, Ollama keeps the LLM loaded in VRAM after it finishes, and ComfyUI won't have enough VRAM left to generate images โ leading to very slow generation or out-of-memory errors.
Tools mentioned in this lesson: