Case study · 2026 · Personal project
An automated YouTube channel for children: animation, voices, quality checks and approvals, orchestrated with n8n
Aksil the Tiger teaches English to four friends in one-minute episodes for children aged 3 to 6. Every episode is produced by a pipeline — script, voices, lip-synced animation, subtitles, thumbnail, upload — and nothing reaches YouTube until I approve the script and the finished video from my phone.

- Role
- Sole developer — curriculum, characters, animation engine, pipeline, orchestration
- Stack
- Python · Remotion (React) · Kokoro TTS · faster-whisper · Rhubarb · n8n · Telegram · YouTube Data API
- Scale
- 101-episode curriculum · 91 board pictures + 8 scenes · 13 episodes produced · 2 per week
- Status
- Live channel, two episodes a week; uploads are reviewed in Telegram
What it does
- A curriculum, not random videos. 101 episodes in 10 units — colours, numbers, animals, food, family, school, the world — each teaching two words and one sentence the child says out loud. Every unit ends with a song, a review quiz and a "Friends Adventure".
- A fixed cast with fixed jobs. Aksil the teacher introduces each word, Yaz the parrot repeats it, Zazi the monkey makes a funny mistake the child corrects, Tiziri the giraffe asks "What's that?", and Anzar the elephant runs the review. Children know what comes next, and so does the script format.
- One-minute episodes, fully produced. Voices, lip-synced 2D animation, pictures on the class board, "Your turn!" pauses, a quiz, English subtitles, a thumbnail, chapters and titles in English, French, Arabic and Spanish.
- A human in the loop. Twice a week the pipeline sends me the script in Telegram, then the finished video; nothing is uploaded without two taps.
Architecture
n8n is the conductor, not the worker. It never gets shell access: it calls a small local HTTP service that exposes a handful of fixed actions — next episode, produce, approve, reject, upload next — so a mistake in a workflow can't run arbitrary commands on the server.
An episode is a JSON script
Each episode is a list of lines (who speaks, what they say, pauses) with events pinned to words: show a picture on the board, write a word, start a "Your turn!" countdown, flag Zazi's mistake. A validator checks every script before anything is rendered — line count, target words actually spoken, events pointing at real words, only pictures that exist, YouTube fields within limits. Scripts can be written ahead in batches, so the Monday and Thursday runs never wait on anyone.
Voices, and a second listener
Each character is a Kokoro text-to-speech voice with its own speed and pitch. Then faster-whisper listens to every generated line and compares what it hears with what was written. That check is the reason the channel sounds right: it caught "parrot" heard as "parent", "doll" as "dial", "stop" as "Istapa". The same word timings drive the animation — a word appears on the board exactly when it is spoken, and Rhubarb turns the audio into mouth shapes for lip sync.
Animation as code
The characters are SVG rigs drawn in Python — heads, eyes, eyelids, mouths, arms and poses as separate parts — and Remotion renders the scene frame by frame in React from the timeline. The board pictures come from a library of 91 drawings and 8 backgrounds (classroom, race track, park, farm, beach, camping night, living room, party room). Every picture is checked by a script that measures it against the safe area of the board and renders it next to the class at real size, so nothing overlaps the chalk tray or looks too small.
Approvals and uploads
On Monday and Thursday at 9:00 n8n asks for the next episode and sends me the script with Produce / Reject buttons. If I approve, the video is produced (about four minutes on CPU) and sent back as a preview with Publish / Reject. Approved episodes wait in a queue; at 16:00 the next one is uploaded with its thumbnail, subtitles, chapters, playlists and localized titles, marked made for kids. Every outcome — uploaded, nothing to upload, an error, a login problem — ends as a Telegram message, and a warning appears when fewer than five ready episodes are left.
Bugs that taught me something
Short words came out garbled. The voice engine synthesizes each fragment between pauses on its own. After a suspense pause — "Red means… stop!" — a one-syllable word was spoken alone and came out as noise: "stop" was heard as "Istapa", "doll" as "dial". The speech-recognition check flagged it; I tested more than 60 alternative phrasings and kept only those heard correctly ("stop, stop!", "This is a doll!"). Lesson: design scripts for the engine, then verify with a second model instead of trusting your ears on a laptop speaker.
The checker lied twice. Whisper writes numbers as digits, so "two" looked missing and every counting episode was flagged — and the same mismatch made number words pop onto the board at the start of the line instead of when they were said. A name alias ("Yes" as a mishearing of "Yaz") silently swallowed every "Yes!". Both were fixed in one normalisation step shared by the check and the timing code. Lesson: a QA tool needs its own tests.
An unanswered approval deleted an episode. When n8n's "send and wait" times out, it passes the original input through, and my workflow read "no answer" as "rejected" — the episode dropped out of the curriculum without a trace. Now there is an explicit third path: no answer means keep it and ask again at the next run.
Child processes crashed only under the process manager. Rendering worked from a terminal but aborted under pm2: the Node child processes inherited pm2's IPC file descriptor variable and exited on start. Sub-processes now get a sanitized environment.
Result
The channel publishes two episodes a week from a written curriculum, with every picture, background and voice line checked before a child sees it, and a person approving every script and video. The same pipeline can produce the whole 101-episode plan: new episodes only need a script batch and two taps a week.