AutomationFlowsWeb Scraping › Tunova — Ambient Long-mix Factory (scheduled Batch for Hours-long Videos)

Tunova — Ambient Long-mix Factory (scheduled Batch for Hours-long Videos)

Tunova — Ambient long-mix factory (scheduled batch for hours-long videos). Uses httpRequest. Scheduled trigger; 9 nodes.

Cron / scheduled trigger★★★★☆ complexity9 nodesHTTP Request
Web Scraping Trigger: Cron / scheduled Nodes: 9 Complexity: ★★★★☆ Added:

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 →

Download .json
{
  "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.

Pro

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 →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

Birthday Automation - Production (Fixed). Uses stopAndError, httpRequest, emailSend, bannerbear. Scheduled trigger; 86 nodes.

Stop And Error, HTTP Request, Email Send +1
Web Scraping

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

Microsoft SharePoint, Microsoft Teams, Microsoft Entra +1
Web Scraping

Jira-Allure-Auto-Qa. Uses httpRequest, jira. Scheduled trigger; 68 nodes.

HTTP Request, Jira
Web Scraping

Spotify-Sync-Surrealdb-V1. Uses httpRequest, n8n-nodes-surrealdb, spotify. Scheduled trigger; 62 nodes.

HTTP Request, N8N Nodes Surrealdb, Spotify
Web Scraping

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

HTTP Request, n8n, N8N Trigger +1