Google Sheets Is a Fine Video Processing Queue with n8n

Your team already tracks video work in a spreadsheet. A source URL, a column saying what needs to happen to it, and an empty cell where the finished file is supposed to land. Then the rows pile up faster than anyone can process them, and somebody loses a Tuesday to downloading files and running FFmpeg by hand.
Quick answer: A Google Sheets video processing queue in n8n is a spreadsheet where each row holds a video URL, an operation, and a status column. A scheduled n8n workflow reads rows markedpending, submits each one to a video API like FFmpeg Micro, flips the row toprocessing, and a webhook writes the finished output URL back into the row when the job completes. No server runs FFmpeg, and the sheet updates itself.
The spreadsheet isn't the bottleneck
Conventional wisdom says a Google Sheet is a toy queue and you should graduate to Postgres, Redis, or SQS the moment you're batching real video. Most spreadsheet-driven pipelines do fall over, so the advice looks correct. But it isn't the sheet that breaks. It's the worker.
The failure is almost always the same: someone puts an "Execute Command" node in n8n and runs ffmpeg inside the workflow run. A 12-minute 1080p transcode holds the execution open for 12 minutes, the HTTP connection times out, n8n retries, and now the same row is being processed twice. Swapping Sheets for Postgres fixes none of that.
The mechanism that actually fixes it is decoupling. The sheet stores state. An async API does the work. A webhook closes the loop. Once the heavy step takes 400 milliseconds of workflow time instead of 12 minutes, a spreadsheet is a perfectly good queue, and it's one your ops teammate can edit without a SQL client.
What the sheet needs to look like
Six columns is enough. The two that matter are status and job_id, because they're what stops the same video from being processed twice.
| Column | Example | Why it exists |
|---|---|---|
| `source_url` | `https://drive.google.com/uc?id=...` | Public or signed URL the API can fetch |
| `operation` | `compress_720p` | Which preset this row wants |
| `status` | `pending` | `pending`, `processing`, `done`, `error` |
| `job_id` | `job_8f21c` | Written on submit, used by the webhook to find the row |
| `output_url` | *(blank)* | Filled in by the webhook |
| `error` | *(blank)* | The real API error message, not "failed" |
Keep the sheet under control. A Google Sheets spreadsheet caps at 10 million cells total, and at six columns that's well over a million rows, so the cell limit is never your constraint. The API quota is: the Google Sheets API v4 allows 300 requests per minute per project and 60 write requests per minute per user, which is the actual ceiling on how fast rows can update.
The n8n workflow, node by node
Five nodes. The whole thing runs in under a second per row because none of them wait for video to finish encoding.
1. Schedule Trigger, then read pending rows
Use a Schedule Trigger on a 1-minute or 5-minute interval, then a Google Sheets node with Get Row(s), filtering on status = pending. Set a return limit of 10 to 25 rows so one run can't submit 400 jobs at once and blow through your Sheets write quota.
2. Claim the row before you submit
This is the step everyone skips and everyone regrets. Immediately after reading, update those rows to status = processing before the job submission node. If the API call is what flips the status, a slow response means the next scheduled run picks up the same row and you pay for the same transcode twice.
3. Submit the job
An HTTP Request node per row. Check the docs for the exact payload for the operation you want. Every job follows the same shape: submit, get an id back, receive a webhook later.
curl -X POST https://api.ffmpeg-micro.com/jobs \
-H "Authorization: Bearer $FFMPEG_MICRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input_url": "https://example.com/raw/interview.mp4",
"operation": "compress",
"options": { "height": 720, "crf": 23 },
"webhook_url": "https://your-n8n.app/webhook/video-done",
"metadata": { "row": 42 }
}'
The response comes back immediately with an id and a queued status. That metadata object is the trick that makes the whole recipe work: whatever you put in it comes back on the webhook, so the callback knows exactly which row to update without a lookup table.
In n8n JSON, that node is roughly:
{
"name": "Submit video job",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"parameters": {
"method": "POST",
"url": "https://api.ffmpeg-micro.com/jobs",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ input_url: $json.source_url, operation: $json.operation, webhook_url: $env.CALLBACK_URL, metadata: { row: $json.row_number } }) }}",
"authentication": "genericCredentialType"
}
}
4. Write the job_id back
One more Google Sheets Update Row node, keyed on row number, storing the returned job id. Now the sheet knows what it's waiting on and you can audit a stuck row later.
5. A second workflow: the webhook receiver
Build this as a separate workflow with a Webhook node, not a branch of the first one. It receives the completion callback, reads metadata.row, and updates that row with status = done and the output URL. On failure it writes status = error plus the message. Two workflows, no polling loop, no execution sitting open. If you're currently polling instead, the tradeoffs are in our n8n timeout recipe.
FFmpeg CLI vs one API call
Each row maps to one of these operations, shown in both forms. Compress to 720p:
ffmpeg -i input.mp4 -vf "scale=-2:720" -c:v libx264 -crf 23 \
-preset medium -c:a aac -b:a 128k output.mp4
Burn in captions:
ffmpeg -i input.mp4 -vf "subtitles=captions.srt" -c:a copy output.mp4
Watermark, bottom right, 15% width:
ffmpeg -i input.mp4 -i logo.png -filter_complex \
"[1]scale=iw*0.15:-1[wm];[0][wm]overlay=W-w-24:H-h-24" \
-c:a copy output.mp4
Each of those is one operation value in the sheet and one POST body in the workflow. The difference isn't the syntax, it's what surrounds it.
| Self-hosted worker | Sheet plus video API | |
|---|---|---|
| Install | FFmpeg build, codec licensing, container image | None |
| Where it runs | A VM or container you keep alive | No servers to run |
| Concurrency | Bounded by your CPU count | Bounded by your plan |
| A 12-minute encode | Holds the n8n execution open | 400 ms of workflow time |
| Failure visibility | Container logs | Error text in the sheet cell |
Cost, throughput, and what a run actually feels like
A faceless-channel operator we've watched work this way keeps one sheet with roughly 200 rows a night: source clip, a watermark op, and a 9:16 crop. The scheduled workflow fires every minute and submits 20 rows per run, so the full queue is submitted in about 10 minutes and the sheet finishes filling itself in as the webhooks land.
Real constraints worth knowing before you build:
- Encoding time scales with duration and resolution, not file count. A 60-second 1080p clip is typically 15 to 25 MB and encodes in well under a minute. A 45-minute podcast recording does not.
- Google Drive
uc?id=links only work if the file is shared publicly. Otherwise the API gets an HTML consent page, not a video, and the job fails on an invalid container. - Pricing is usage-based with a free tier, so a 200-row test batch is a cheap way to find out what your real per-clip cost is. See pricing.
Common pitfalls
- No claim step. Covered above, and it's the number one cause of double billing. Flip to
processingbefore you submit. - Row numbers shift. If anyone sorts or deletes rows mid-run,
metadata.rowpoints at the wrong video. Store a stablejob_idand match on that in the webhook instead of trusting position. - Silent errors. Writing
status = errorwith no message means someone has to re-run the job to find out why. Write the API's actual error string into theerrorcolumn. A dead URL and a corrupt MOV produce different messages, and only one of them is worth retrying. - Retrying forever. Add an
attemptscolumn. Three failures on the same row means the input is bad, not the pipeline. - Blowing the write quota. One update per row per state change is fine. Updating a progress percentage every few seconds across 200 rows will hit the 60-writes-per-minute-per-user ceiling fast.
When a spreadsheet is the wrong queue
Be honest about the ceiling. Move off Sheets when you cross a few thousand active rows, when more than a couple of people edit concurrently, or when you need transactional guarantees rather than a status column and good manners. Postgres or Supabase is the natural next step, and the workflow barely changes because the API contract stays identical. Airtable is a reasonable middle ground with better typing and row-level automations.
Also skip this pattern entirely if your videos arrive as events rather than as a list. An upload trigger beats a polled spreadsheet every time, which is what auto-processing Supabase uploads is for.
FAQ
Can I use Airtable or Notion instead of Google Sheets?
Yes, and the workflow is identical. Swap the two Google Sheets nodes for Airtable or Notion nodes. Airtable handles concurrent editors better and gives you real field types, which matters once your operation column grows past a handful of values.
How many videos can I batch from one sheet?
Practically, a few thousand rows before Sheets gets unpleasant to work with, and the throughput limit is the 60 write requests per minute per user on the Sheets API rather than anything in the video pipeline. Submitting 20 rows per scheduled run on a 1-minute trigger clears 1,200 rows an hour.
Do I need FFmpeg installed anywhere for this?
No. The n8n instance never touches a video file, it only passes URLs. That's the point of the split, and it's why this works on n8n Cloud where you can't install binaries at all.
What happens if a video URL is dead or the file is corrupt?
The job fails and the webhook fires with an error payload instead of an output URL. Your receiver workflow writes status = error and the message into the row, so a dead link reads as a fetch failure and a truncated MP4 reads as an invalid container. You fix the URL and set the row back to pending.
Does this work in Make or Zapier?
Yes. The pattern is the same three moves in any tool: read rows, POST a job, catch the callback. It works with n8n, Make, and Zapier, and from your AI agents through the MCP server. The n8n version is just the one with the cleanest webhook handling.
Once the sheet drives itself, the interesting work moves up a level, into things like turning one long recording into a dozen shorts or assembling faceless videos from an asset list. Grab an API key on the free tier and point your first ten rows at it: sign up free.
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

FFmpeg blackdetect: Find Black Frames and Split Video Automatically
FFmpeg blackdetect finds black frames using duration, pixel, and picture thresholds. Read the log, turn ranges into cut points, and split video automatically.

How to Fix n8n Timeout Errors When Processing Video (Webhooks + Polling Recipe)
Fix n8n timeout errors when processing video by offloading the FFmpeg render to a job-based API, then poll or receive a webhook so no execution times out.

Auto-Process Video Uploads in Supabase Storage with FFmpeg Micro
Automate video compression and thumbnail extraction on Supabase Storage uploads using Edge Functions and the FFmpeg Micro API.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free