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": "tunovaAmbientMixFactory",
"name": "Tunova \u2014 Ambient long-mix factory (scheduled batch for hours-long videos)",
"nodes": [
{
"parameters": {
"content": "## Ambient long-mix factory\n\nEvery morning this workflow batch-generates a set of seamless ambient tracks with Suno (v5.5) via [Tunova](https://tunova.ai) and assembles an **M3U playlist + a JSON manifest** (audio URLs + total runtime) \u2014 ready to feed into ffmpeg / Creatomate / your video renderer for those hours-long ambience videos.\n\n**Why batch generation needs bill-on-success:** on a per-credit API one failed render in a 6-track batch quietly costs you money and leaves a hole in the mix. Tunova auto-refunds failed renders \u2014 completed tracks still arrive, and re-running the gap costs nothing extra.\n\n### Setup (2 min)\n1. Key from [tunova.ai](https://tunova.ai) \u2014 **50 free tokens, no card** (\u22485 tracks; 10 tokens per track).\n2. **Header Auth** credential: name `X-API-Key`, value `sk_live_\u2026` \u2014 select it in both Tunova nodes.\n3. Tune *Mix config* (theme / tags / tracks) and the schedule.\n\n### Making it HOURS long\nOne run \u2248 15\u201320 min of audio. Either raise *tracks*, schedule 2\u20133 runs/day and merge manifests, or loop the mix in your renderer:\n`ffmpeg -f concat -safe 0 -i mix.txt -stream_loop 3 \u2026`",
"height": 560,
"width": 440
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-500,
-140
],
"id": "bbbb0001-0000-4000-8000-000000000001",
"name": "How to use"
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "days",
"triggerAtHour": 6
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
0,
0
],
"id": "bbbb0002-0000-4000-8000-000000000002",
"name": "Every morning"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "m1",
"name": "theme",
"value": "gentle ocean waves at dawn",
"type": "string"
},
{
"id": "m2",
"name": "tags",
"value": "ambient, calm, no drums, field recording, soft pads",
"type": "string"
},
{
"id": "m3",
"name": "tracks",
"value": 5,
"type": "number"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
200,
0
],
"id": "bbbb0003-0000-4000-8000-000000000003",
"name": "Mix config"
},
{
"parameters": {
"jsCode": "// One item per track; vary the scene so the long mix evolves instead of repeating.\nconst cfg = $input.first().json;\nconst scenes = [\n 'distant waves, very sparse, almost still',\n 'slow swell, warm low pads underneath',\n 'midpoint, a little brighter, gulls far away',\n 'receding tide, softer and slower',\n 'deep calm, low and enveloping',\n 'first light, airy and open',\n 'drifting, weightless and thin',\n 'final stillness, fading to quiet',\n];\nconst n = Math.max(1, Math.min(Number(cfg.tracks) || 5, scenes.length));\nreturn scenes.slice(0, n).map((scene, i) => ({\n json: {\n track_no: i + 1,\n prompt: `${cfg.theme} \u2014 ${scene}. Instrumental only. Style: ${cfg.tags}. Seamless, loopable, no sudden changes.`,\n },\n}));"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
400,
0
],
"id": "bbbb0004-0000-4000-8000-000000000004",
"name": "Build ambient prompts"
},
{
"parameters": {
"method": "POST",
"url": "https://api.tunova.ai/api/generate",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"prompt\": {{ JSON.stringify($json.prompt) }},\n \"make_instrumental\": true\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
600,
0
],
"id": "bbbb0005-0000-4000-8000-000000000005",
"name": "Tunova: submit track",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"amount": 300,
"unit": "seconds"
},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
800,
0
],
"id": "bbbb0006-0000-4000-8000-000000000006",
"name": "Let them render (~5 min)"
},
{
"parameters": {
"url": "=https://api.tunova.ai/api/jobs/{{ $json.job_id }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1000,
0
],
"id": "bbbb0007-0000-4000-8000-000000000007",
"name": "Tunova: get result",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "g1",
"leftValue": "={{ $json.status }}",
"rightValue": "complete",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
1200,
0
],
"id": "bbbb0008-0000-4000-8000-000000000008",
"name": "Keep completed"
},
{
"parameters": {
"jsCode": "// Assemble the mix: M3U playlist + JSON manifest for your renderer.\n// Failed tracks simply aren't here \u2014 they were auto-refunded, the mix still ships.\nconst clips = $input.all().flatMap((i) => i.json.clips || []);\nconst urls = clips.map((c) => c.audio_url).filter(Boolean);\nconst totalSec = clips.reduce((s, c) => s + (Number(c.duration) || 0), 0);\nconst m3u = ['#EXTM3U', ...clips.map((c) => `#EXTINF:${Math.round(c.duration || 0)},${c.title || 'Ambient track'}\\n${c.audio_url}`)].join('\\n');\nconst ffmpegConcat = urls.map((u) => `file '${u}'`).join('\\n');\nreturn [{\n json: {\n tracks_delivered: urls.length,\n total_minutes: Math.round(totalSec / 60),\n audio_urls: urls,\n m3u,\n ffmpeg_concat: ffmpegConcat,\n },\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1400,
0
],
"id": "bbbb0009-0000-4000-8000-000000000009",
"name": "Assemble mix manifest"
}
],
"connections": {
"Every morning": {
"main": [
[
{
"node": "Mix config",
"type": "main",
"index": 0
}
]
]
},
"Mix config": {
"main": [
[
{
"node": "Build ambient prompts",
"type": "main",
"index": 0
}
]
]
},
"Build ambient prompts": {
"main": [
[
{
"node": "Tunova: submit track",
"type": "main",
"index": 0
}
]
]
},
"Tunova: submit track": {
"main": [
[
{
"node": "Let them render (~5 min)",
"type": "main",
"index": 0
}
]
]
},
"Let them render (~5 min)": {
"main": [
[
{
"node": "Tunova: get result",
"type": "main",
"index": 0
}
]
]
},
"Tunova: get result": {
"main": [
[
{
"node": "Keep completed",
"type": "main",
"index": 0
}
]
]
},
"Keep completed": {
"main": [
[
{
"node": "Assemble mix manifest",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
httpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Tunova — Ambient long-mix factory (scheduled batch for hours-long videos). Uses httpRequest. Scheduled trigger; 9 nodes.
Source: https://github.com/erliona/tunova-templates/blob/main/n8n/04-ambient-mix-factory.json — 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.
Birthday Automation - Production (Fixed). Uses stopAndError, httpRequest, emailSend, bannerbear. Scheduled trigger; 86 nodes.
This template runs two scheduled workflows to govern Microsoft Entra ID (Azure AD) guest accounts by detecting stale users via Microsoft Graph, staging deletions in SharePoint with a 72-hour window, n
Jira-Allure-Auto-Qa. Uses httpRequest, jira. Scheduled trigger; 68 nodes.
Spotify-Sync-Surrealdb-V1. Uses httpRequest, n8n-nodes-surrealdb, spotify. Scheduled trigger; 62 nodes.
As n8n instances scale, teams often lose track of sub-workflows—who uses them, where they are referenced, and whether they can be safely updated. This leads to inefficiencies like unnecessary copies o