The n8n workflow that turns a finished course video into everything around it
On this page
A finished course video is never actually finished. It needs a transcript, chapter markers, a description, captions, a thumbnail frame, and a filing decision. On a course with forty lessons that is forty rounds of the same small tasks, and every one of them is a place to be inconsistent.
This is the workflow I built to remove most of it. Drop an export into a watched folder, and by the time I look again there is a transcript, chapter markers derived from the actual content, a draft description, and the file has been renamed and filed. Self-hosted n8n does the orchestration and a local model through Ollama does the language work.
The reason it runs locally is not ideology. It is that client footage should not be uploaded to a third-party API, and at forty videos a month the per-call cost of doing this properly through a hosted model stops being trivial.
Ad space, reserved
The shape of it
- A folder watcher fires when a new mp4 appears.
- Whisper produces a timestamped transcript.
- A local model reads the transcript and returns chapter markers and a description as structured JSON.
- The workflow writes a sidecar file, renames the video, and moves it into the right course folder.
- A summary lands in chat so I can see what happened without opening anything.
Nothing here is clever. The value is entirely in it running the same way every time.
Running n8n and Ollama together
Both in Docker, on one network, so n8n can reach Ollama by container name. The media directory is mounted into n8n so the workflow can see the files.
# docker-compose.yml
services:
n8n:
image: docker.n8n.io/n8nio/n8n:latest
restart: unless-stopped
environment:
- N8N_HOST=n8n.example.com
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.example.com/
- GENERIC_TIMEZONE=Asia/Dhaka
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
volumes:
- n8n_data:/home/node/.n8n
- /srv/media:/data/media
networks:
- homelab
ollama:
image: ollama/ollama:latest
restart: unless-stopped
volumes:
- ollama_models:/root/.ollama
networks:
- homelab
volumes:
n8n_data:
ollama_models:
networks:
homelab:
external: trueNote there is no ports block on either service. Both sit behind a Cloudflare Tunnel, so nothing is published to the host. If you expose n8n directly, put authentication in front of it before you do anything else: an n8n instance reachable without a login is a remote code execution endpoint with a friendly interface.
Pull a model once the stack is up. For this job a small instruct model is enough, because summarising a transcript is not a reasoning-heavy task.
docker exec -it ollama ollama pull llama3.1:8bGetting structured output, reliably
This is the part that decides whether the workflow is dependable or a toy. If you ask a model for chapters in prose, you will spend more time parsing its answer than you saved. Ask for JSON and constrain it at the API level rather than in the prompt.
Ollama supports a format parameter that forces valid JSON. Use it. A prompt that politely requests JSON will comply most of the time, and most of the time is the worst possible reliability for an unattended workflow.
{
"model": "llama3.1:8b",
"stream": false,
"format": "json",
"options": { "temperature": 0.2 },
"prompt": "You are given a timestamped transcript of a software tutorial.
Return JSON only, matching this shape:
{"chapters":[{"time":"MM:SS","title":"string"}],"description":"string"}
Rules:
- A chapter starts where the task being demonstrated changes, not every pause.
- Between 3 and 8 chapters. Fewer is better than more.
- Titles are noun phrases under 6 words. No numbering.
- description is 2 sentences, plain, no marketing language.
Transcript:
{{ $json.transcript }}"
}Temperature at 0.2 rather than 0. Fully deterministic output sounds appealing but tends to produce flat, repetitive chapter titles across a whole course.
Validate before you trust it
Valid JSON is not the same as correct JSON. The model will occasionally return a chapter at 00:00 and nothing else, or a timestamp past the end of the video. A Code node between the model and the filesystem catches both.
// n8n Code node, runs once per item
const out = $input.first().json;
let parsed;
try {
parsed = typeof out.response === 'string'
? JSON.parse(out.response)
: out.response;
} catch (e) {
throw new Error('Model did not return parseable JSON');
}
const durationSec = $('Probe').first().json.durationSec;
const toSec = (t) => {
const p = String(t).split(':').map(Number);
return p.length === 3
? p[0] * 3600 + p[1] * 60 + p[2]
: p[0] * 60 + p[1];
};
const chapters = (parsed.chapters || [])
.filter((ch) => ch && ch.title && ch.time)
.filter((ch) => toSec(ch.time) < durationSec) // drop impossible timestamps
.sort((a, b) => toSec(a.time) - toSec(b.time));
if (chapters.length < 2) {
throw new Error('Only ' + chapters.length + ' usable chapters, needs review');
}
return [{ json: { chapters, description: parsed.description || '' } }];Throwing here is deliberate. A failed execution I can see in the n8n log is a much better outcome than a silently wrong sidecar file that I discover three weeks later when a client asks why lesson twelve has one chapter.
What I did not automate
The description draft still gets read before it goes anywhere public. Not because the model is bad at it, but because a description is the thing a prospective student reads first, and a slightly generic one costs more than the ninety seconds of review.
Chapter titles get scanned too. The model is good at spotting where a task changes and mediocre at knowing which change matters to a learner. Roughly one course in three has a lesson where I move a marker.
That split is the general rule I keep arriving at. Automate the retrieval, the formatting, the moving of files, and the parts with one correct answer. Keep the judgment, and keep a human between the judgment and the client.
Is it worth building
For one video, no. Doing this by hand takes about fifteen minutes and building the workflow took an evening.
For a forty lesson course it pays for itself before lesson five, and the real return is not the time. It is that lesson forty is filed and described exactly like lesson one, which is the thing clients actually notice on a large library and the thing that is hardest to hold by hand.
Sources
- n8n Docker installation docs
- Ollama API reference, including the format parameter
- n8n guide 2026, features and workflow automation
Ad space, reserved




