This workflow corresponds to n8n.io template #16735 — we link there as the canonical source.
This workflow follows the Google Drive → Google Drive Trigger recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "Aam0MiNTRlIk0307",
"name": "Automatic Video Subtitles",
"tags": [],
"nodes": [
{
"id": "25815aa9-f6f8-436b-8672-c55035889687",
"name": "Listen to new file",
"type": "n8n-nodes-base.googleDriveTrigger",
"position": [
-400,
-48
],
"parameters": {
"event": "fileCreated",
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"triggerOn": "specificFolder",
"folderToWatch": {
"__rl": true,
"mode": "list",
"value": "1H47ZBdBi-qmNiC83IuZjYjG_4kLQuSp0",
"cachedResultUrl": "https://drive.google.com/drive/folders/1H47ZBdBi-qmNiC83IuZjYjG_4kLQuSp0",
"cachedResultName": "videos"
}
},
"typeVersion": 1
},
{
"id": "936fdf71-65bd-4995-ab57-97ec1d2ddc68",
"name": "Download file",
"type": "n8n-nodes-base.googleDrive",
"position": [
64,
-64
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {},
"operation": "download"
},
"typeVersion": 3
},
{
"id": "4b1b5cbc-04fc-4992-9475-b6f7d85b2f5d",
"name": "Check if new file is audio",
"type": "n8n-nodes-base.if",
"position": [
-192,
-48
],
"parameters": {
"options": {
"ignoreCase": true
},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cddb00f9-2237-4e8b-bad7-8bd7f5d7524c",
"operator": {
"type": "string",
"operation": "regex"
},
"leftValue": "={{ $json.fileExtension }}",
"rightValue": "^(mp3|wav|aac|ogg|flac|m4a|wma)$"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "f394bf23-4eb3-4821-a902-b771713c9440",
"name": "Transcribe audio",
"type": "n8n-nodes-smallestai.smallestai",
"position": [
272,
-64
],
"parameters": {
"resource": "stt",
"additionalOptions": {}
},
"credentials": {},
"typeVersion": 1
},
{
"id": "b07e86d1-0a38-4ea3-bf2d-a5eaeab2077f",
"name": "Generate subtitles",
"type": "n8n-nodes-base.code",
"position": [
480,
-64
],
"parameters": {
"jsCode": "const NL = String.fromCharCode(10);\nconst root = $input.first().json;\n\nconst TEXT_KEYS = ['transcript', 'transcription', 'text', 'result', 'output', 'content'];\nfunction findText(o) {\n let best = '';\n (function walk(x) {\n if (x == null) return;\n if (Array.isArray(x)) { x.forEach(walk); return; }\n if (typeof x === 'object') {\n for (const k of Object.keys(x)) {\n const v = x[k];\n if (typeof v === 'string' && TEXT_KEYS.indexOf(k.toLowerCase()) !== -1 && v.trim().length > best.length) best = v;\n walk(v);\n }\n }\n })(o);\n if (best && best.trim()) return best.trim();\n (function walk2(x) {\n if (x == null) return;\n if (typeof x === 'string') { if (x.trim().length > best.length) best = x; return; }\n if (Array.isArray(x)) { x.forEach(walk2); return; }\n if (typeof x === 'object') Object.keys(x).forEach(k => walk2(x[k]));\n })(o);\n return (best || '').trim();\n}\n\nfunction findSegments(o) {\n let found = null;\n (function walk(x) {\n if (found) return;\n if (Array.isArray(x)) {\n if (x.length && x[0] && typeof x[0] === 'object' && x[0].start != null) { found = x; return; }\n x.forEach(walk); return;\n }\n if (x && typeof x === 'object') Object.keys(x).forEach(k => walk(x[k]));\n })(o);\n return found;\n}\n\nfunction findDuration(o) {\n let d = null;\n (function walk(x) {\n if (d != null) return;\n if (Array.isArray(x)) { x.forEach(walk); return; }\n if (x && typeof x === 'object') {\n for (const k of Object.keys(x)) {\n if (/duration/i.test(k) && typeof x[k] === 'number' && x[k] > 0) { d = x[k]; return; }\n walk(x[k]);\n }\n }\n })(o);\n return d;\n}\n\nconst transcriptText = findText(root);\nconst segs = findSegments(root);\nconst duration = findDuration(root);\n\nfunction p(n, l) { l = l || 2; return String(n).padStart(l, '0'); }\nfunction fmt(sec) { if (sec < 0) sec = 0; const h = Math.floor(sec / 3600); const m = Math.floor((sec % 3600) / 60); const s = Math.floor(sec % 60); const ms = Math.round((sec - Math.floor(sec)) * 1000); return p(h) + ':' + p(m) + ':' + p(s) + ',' + p(ms, 3); }\n\nlet cues = [];\nlet estimated = true;\nif (segs && segs.length && segs[0].start != null) {\n if (segs[0].text != null) {\n cues = segs.map(s => ({ start: s.start, end: (s.end != null ? s.end : s.start + 2), text: (s.text || '').toString().trim() }));\n estimated = false;\n } else {\n const g = 8;\n for (let i = 0; i < segs.length; i += g) {\n const c = segs.slice(i, i + g);\n cues.push({ start: c[0].start, end: (c[c.length - 1].end != null ? c[c.length - 1].end : c[c.length - 1].start + 1), text: c.map(w => (w.word || w.text || '').toString()).join(' ').trim() });\n }\n estimated = false;\n }\n}\nif (!cues.length) {\n const words = transcriptText.split(/\\s+/).filter(Boolean);\n const per = 8;\n const total = Math.max(1, Math.ceil(words.length / per));\n const dur = (duration && duration > 0) ? duration : (words.length / 2.5);\n const cd = total > 0 ? dur / total : 0;\n for (let i = 0; i < total; i++) {\n cues.push({ start: i * cd, end: (i + 1) * cd, text: words.slice(i * per, (i + 1) * per).join(' ') });\n }\n estimated = true;\n}\n\nlet srt = '';\ncues.forEach((c, i) => { srt += (i + 1) + NL + fmt(c.start) + ' --> ' + fmt(c.end) + NL + c.text + NL + NL; });\nsrt = srt.trim() + NL;\n\nreturn [{ json: { cueCount: cues.length, estimatedTiming: estimated, transcriptFound: transcriptText.slice(0, 80), responseTopLevelKeys: Object.keys(root || {}), srt: srt } }];\n"
},
"typeVersion": 2
},
{
"id": "f4bf664d-c103-4781-ae25-da4f67eb169f",
"name": "Prepare binary for Drive File",
"type": "n8n-nodes-base.code",
"position": [
688,
-64
],
"parameters": {
"jsCode": "const items = $input.all();\n\nfor (const item of items) {\n const originalName = $('Listen to new file').first().json.name;\n item.json.srtFileName = originalName.replace(/\\.[^/.]+$/, \".srt\");\n \n item.binary = {\n data: {\n data: Buffer.from(item.json.srt, 'utf8').toString('base64'),\n mimeType: 'text/plain',\n fileName: item.json.srtFileName\n }\n };\n}\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "f88e76cd-c70f-472d-9ea4-fdee2ac578a5",
"name": "Upload subtitles file",
"type": "n8n-nodes-base.googleDrive",
"position": [
896,
-64
],
"parameters": {
"name": "={{ $json.srtFileName }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "list",
"value": "1H47ZBdBi-qmNiC83IuZjYjG_4kLQuSp0",
"cachedResultUrl": "https://drive.google.com/drive/folders/1H47ZBdBi-qmNiC83IuZjYjG_4kLQuSp0",
"cachedResultName": "videos"
},
"inputDataFieldName": "=data"
},
"typeVersion": 3
},
{
"id": "b204909c-3e97-4a39-acc9-e77ee7e6b96e",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-928,
-224
],
"parameters": {
"width": 464,
"height": 624,
"content": "## Automatic Video Subtitles Generator\n\n### How it works\n\nThis workflow automatically listens for new files added to a specific Google Drive folder, verifies if they match supported audio/video formats, and downloads the content. It then transcribes the audio using Smallest.ai, processes the transcription timestamps into standard SRT subtitle format via a custom JavaScript node, and uploads the final `.srt` subtitle file back to Google Drive.\n\n### Setup steps\n\n- Connect your Google Drive account credentials in the **Listen to new file**, **Download file**, and **Upload subtitles file** nodes.\n- Update the target Folder ID in both the trigger node and the final upload node to point to your specific folder.\n- Connect your Smallest.ai credentials in the **Transcribe audio** node to enable speech-to-text parsing.\n\n### Customization\n\nAdjust the regular expression in the format validation node if you want to support or restrict different extensions. You can also customize the **Generate subtitles** Code node logic to change word grouping limits, styling, or timestamp spacing defaults."
},
"typeVersion": 1
},
{
"id": "370419a7-2325-47a1-a6a1-8f4fd2f1353c",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-416,
-176
],
"parameters": {
"color": 7,
"width": 432,
"height": 336,
"content": "## Detect & Download Media\n\nTriggers on new files added to the monitored folder, validates the extension against media formats, and pulls the binary data."
},
"typeVersion": 1
},
{
"id": "3d18a360-bd1d-4cec-9b5f-4a2ca098bb4e",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
464,
-176
],
"parameters": {
"color": 7,
"width": 576,
"height": 336,
"content": "## Save & Export SRT\n\nConverts the text-based SRT output back into a binary file stream and pushes the complete subtitle document right back to your Google Drive directory."
},
"typeVersion": 1
},
{
"id": "6dfed1f8-3713-46c2-92cf-c6ba9b7523cf",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
48,
-176
],
"parameters": {
"color": 7,
"width": 384,
"height": 336,
"content": "## Transcribe & Process\n\nSends the downloaded media file to Smallest.ai for Speech-to-Text translation and formats the payload into valid timestamped SRT subtitle blocks."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "a921835b-b5c5-468e-8186-38a636d12a64",
"nodeGroups": [],
"connections": {
"Download file": {
"main": [
[
{
"node": "Transcribe audio",
"type": "main",
"index": 0
}
]
]
},
"Transcribe audio": {
"main": [
[
{
"node": "Generate subtitles",
"type": "main",
"index": 0
}
]
]
},
"Generate subtitles": {
"main": [
[
{
"node": "Prepare binary for Drive File",
"type": "main",
"index": 0
}
]
]
},
"Listen to new file": {
"main": [
[
{
"node": "Check if new file is audio",
"type": "main",
"index": 0
}
]
]
},
"Check if new file is audio": {
"main": [
[
{
"node": "Download file",
"type": "main",
"index": 0
}
]
]
},
"Prepare binary for Drive File": {
"main": [
[
{
"node": "Upload subtitles file",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow watches a Google Drive folder for new audio files, transcribes them with Smallest.ai, converts the transcription into an SRT subtitle file, and uploads the .srt back to the same Google Drive folder. Triggers every minute when a new file is created in a specified…
Source: https://n8n.io/workflows/16735/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Code Filter. Uses stickyNote, googleDrive, noOp, googleDriveTrigger. Event-driven trigger; 20 nodes.
Purpose: Automate the process of finding and managing duplicate files in your Google Drive.
Remove Advanced Background from Google Drive Images. Uses httpRequest, splitOut, googleDrive, editImage. Event-driven trigger; 16 nodes.
Create Custom Presentations per Lead. Uses googleDrive, googleSheets, googleDriveTrigger, googleSlides. Event-driven trigger; 14 nodes.
Important: This workflow uses the Autype community node and requires a self-hosted n8n instance.