Add n8n community node, /api/v1/render alias, and job webhooks for OSS automation.

Payment-free self-hosted builds keep full API access with optional webhookUrl callbacks and the published n8n-nodes-songs2vid package source under integrations/n8n.
This commit is contained in:
Atakan Doğan Özban
2026-08-08 16:32:28 +02:00
parent 848607f9e0
commit f9b2a997a2
27 changed files with 7333 additions and 14 deletions
+7
View File
@@ -0,0 +1,7 @@
node_modules/
dist/
*.tsbuildinfo
.DS_Store
*.tgz
.env
.env.*
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Songs2VID
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+89
View File
@@ -0,0 +1,89 @@
# n8n-nodes-songs2vid
Official community node for **[Songs2VID](https://songs2vid.com)** — turn audio + cover art into YouTube-ready videos from [n8n](https://n8n.io/).
**Documentation:** [https://docs.songs2vid.com/docs/n8n](https://docs.songs2vid.com/docs/n8n) · in-repo [docs/n8n.md](../../docs/n8n.md)
Self-hosted OSS: set **Base URL** to your instance. API keys work without a paid plan (YouTube must be connected).
## Install
### From n8n (recommended)
1. Open n8n → **Settings → Community nodes**
2. **Install a community node**
3. Enter:
```text
n8n-nodes-songs2vid
```
4. Confirm and restart n8n if prompted
5. Search the canvas for **Songs2VID**
Self-hosted n8n needs community packages enabled (`N8N_COMMUNITY_PACKAGES_ENABLED=true`).
### From npm (manual)
```bash
cd ~/.n8n
npm install n8n-nodes-songs2vid
```
Restart n8n.
## Credentials
1. Create an API key under [Dashboard → Settings](https://songs2vid.com/dashboard/settings) (`s2yt_live_…`)
2. Cloud requires **Developer & Automation** (€15) or **Enterprise**, plus YouTube connected
3. In n8n: create a **Songs2VID API** credential
- **API Key:** your token
- **Base URL:** `https://songs2vid.com` (or your self-hosted origin, no trailing slash)
Requests use `Authorization: Bearer …` only.
## Quick start
1. **File → Upload** — cover (`type=image`) → save `path`
2. **File → Upload** — audio (`type=audio`) → save `path` / `filename`
3. **Render → Create Render** — paths + privacy + resolution
Optional: **Additional Fields → Webhook URL** for async completion
4. Handle `job.completed` / `job.item.completed` on an n8n **Webhook** node, or poll **Get Render Status**
## Operations
| Resource | Operation | API |
|----------|-----------|-----|
| Discovery | Get API Catalog | `GET /api/v1` |
| File | Upload | `POST /api/v1/upload` |
| Render | Create Render | `POST /api/v1/render` |
| Render | Create Job (alias) | `POST /api/v1/jobs` |
| Render | Create Batch | `POST /api/v1/jobs/batch` |
| Render | Get Render Status | `GET /api/v1/jobs/:id` |
| Render | List Renders | `GET /api/v1/jobs` |
| Playlist | List / Create | `/api/v1/playlists` |
| API Key | List / Create / Delete | `/api/v1/user/api-keys` |
Layout, watermark, playlist, and multi-track options are under **Additional Fields** on Create Render.
There is no Delete Render endpoint — finished videos live on YouTube.
## Links
- Guide: https://songs2vid.com/docs/n8n
- REST endpoints: https://songs2vid.com/docs/api/endpoints
- Workflow template: https://songs2vid.com/docs/n8n/workflow-template.json
- npm: https://www.npmjs.com/package/n8n-nodes-songs2vid
## Development
```bash
npm install
npm run build
```
`prepublishOnly` runs the build before `npm publish`.
## License
MIT
@@ -0,0 +1,57 @@
import type {
IAuthenticateGeneric,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from "n8n-workflow";
/**
* Songs2VID REST auth — cloud accepts Authorization: Bearer only.
* Paste the raw key (s2yt_live_…) or a full "Bearer …" value.
*/
export class Songs2VidApi implements ICredentialType {
name = "songs2VidApi";
displayName = "Songs2VID API";
documentationUrl = "https://songs2vid.com/docs/n8n";
properties: INodeProperties[] = [
{
displayName: "API Key",
name: "apiKey",
type: "string",
typeOptions: { password: true },
default: "",
required: true,
description:
"Developer+ key from Dashboard → Settings (starts with s2yt_live_). Sent as Authorization: Bearer.",
},
{
displayName: "Base URL",
name: "baseUrl",
type: "string",
default: "https://songs2vid.com",
required: true,
description: "Cloud origin or self-hosted base (no trailing slash).",
},
];
authenticate: IAuthenticateGeneric = {
type: "generic",
properties: {
headers: {
Authorization:
'={{ $credentials.apiKey.toString().startsWith("Bearer ") ? $credentials.apiKey : "Bearer " + $credentials.apiKey }}',
},
},
};
test: ICredentialTestRequest = {
request: {
baseURL: "={{ $credentials.baseUrl.replace(/\\/$/, \"\") }}",
url: "/api/v1/user/api-keys",
method: "GET",
},
};
}
+14
View File
@@ -0,0 +1,14 @@
const { src, dest } = require("gulp");
/**
* Copy node/credential icons (+ codex JSON) into dist so n8n can resolve
* `icon: "file:songs2vid.svg"` next to the compiled .js files.
*/
function copyIcons() {
return src(
["nodes/**/*.{png,svg,json}", "credentials/**/*.{png,svg,json}"],
{ base: "." },
).pipe(dest("dist"));
}
exports["build:icons"] = copyIcons;
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env node
"use strict";
module.exports = {};
@@ -0,0 +1,18 @@
{
"node": "n8n-nodes-songs2vid.songs2Vid",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Marketing", "Productivity"],
"resources": {
"credentialDocumentation": [
{
"url": "https://songs2vid.com/docs/n8n"
}
],
"primaryDocumentation": [
{
"url": "https://songs2vid.com/docs/api/endpoints"
}
]
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" fill="none">
<rect width="60" height="60" rx="12" fill="#0a0a0a"/>
<text x="8" y="38" font-family="Arial,sans-serif" font-size="16" font-weight="700" fill="#fff">S2</text>
<text x="30" y="38" font-family="Arial,sans-serif" font-size="16" font-weight="700" fill="#f87171">VID</text>
</svg>

After

Width:  |  Height:  |  Size: 355 B

+5242
View File
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
{
"name": "n8n-nodes-songs2vid",
"version": "1.0.0",
"description": "n8n community node for Songs2VID — upload audio/covers, create YouTube renders, poll jobs, manage playlists & API keys",
"keywords": [
"n8n-community-node-package",
"n8n-node",
"songs2vid",
"audio-to-video",
"youtube"
],
"license": "MIT",
"homepage": "https://songs2vid.com/docs/n8n",
"author": {
"name": "Songs2VID",
"url": "https://songs2vid.com"
},
"repository": {
"type": "git",
"url": "https://git.atakanozban.com/Songs2VID/songs2vid.git",
"directory": "integrations/n8n"
},
"bugs": {
"url": "https://songs2vid.com/docs/n8n"
},
"main": "index.js",
"scripts": {
"build": "tsc && gulp build:icons",
"dev": "tsc --watch",
"prepublishOnly": "npm run build"
},
"files": [
"dist"
],
"n8n": {
"n8nNodesApiVersion": 1,
"credentials": [
"dist/credentials/Songs2VidApi.credentials.js"
],
"nodes": [
"dist/nodes/Songs2Vid/Songs2Vid.node.js"
]
},
"devDependencies": {
"@types/node": "^20.11.0",
"gulp": "^4.0.2",
"n8n-workflow": "^1.70.0",
"typescript": "^5.3.3"
},
"peerDependencies": {
"n8n-workflow": "*"
},
"engines": {
"node": ">=18.10"
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2019",
"module": "commonjs",
"lib": ["ES2019"],
"declaration": true,
"outDir": "./dist",
"rootDir": ".",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["credentials/**/*", "nodes/**/*"],
"exclude": ["dist", "node_modules"]
}
@@ -0,0 +1,395 @@
{
"name": "Songs2VID — Audio → Render → YouTube (full)",
"meta": {
"templateCredsSetupCompleted": false,
"instanceId": "songs2vid-docs-template-v2"
},
"nodes": [
{
"parameters": {},
"id": "manual-trigger",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
0,
0
]
},
{
"parameters": {
"path": "songs2vid-complete",
"httpMethod": "POST",
"responseMode": "onReceived",
"options": {}
},
"id": "webhook-complete",
"name": "Songs2VID Job Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
360
],
"webhookId": "songs2vid-complete"
},
{
"parameters": {
"content": "## Inputs\nProvide binary fields (`cover`, `audio`) or replace Manual Trigger with Drive/Dropbox.\n\nCredential: Header Auth → Name `Authorization`, Value `Bearer s2yt_live_…`\n\nSet env `SONGS2VID_WEBHOOK_URL` to this workflow's Production Webhook URL.",
"height": 240,
"width": 320
},
"id": "sticky-inputs",
"name": "Setup",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-320,
-40
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.SONGS2VID_BASE_URL || 'https://songs2vid.com' }}/api/v1/upload",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{
"parameterType": "formBinaryData",
"name": "file",
"inputDataFieldName": "cover"
},
{
"name": "type",
"value": "image"
}
]
},
"options": {}
},
"id": "upload-cover",
"name": "Upload Cover Image",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
280,
0
],
"credentials": {
"httpHeaderAuth": {
"id": "REPLACE_ME",
"name": "Songs2VID API Key"
}
}
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.SONGS2VID_BASE_URL || 'https://songs2vid.com' }}/api/v1/upload",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{
"parameterType": "formBinaryData",
"name": "file",
"inputDataFieldName": "audio"
},
{
"name": "type",
"value": "audio"
}
]
},
"options": {}
},
"id": "upload-audio",
"name": "Upload Audio",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
520,
0
],
"credentials": {
"httpHeaderAuth": {
"id": "REPLACE_ME",
"name": "Songs2VID API Key"
}
}
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.SONGS2VID_BASE_URL || 'https://songs2vid.com' }}/api/v1/render",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"imagePath\": \"{{ $('Upload Cover Image').item.json.path }}\",\n \"webhookUrl\": \"{{ $env.SONGS2VID_WEBHOOK_URL || '' }}\",\n \"items\": [{\n \"audioPath\": \"{{ $('Upload Audio').item.json.path }}\",\n \"audioFilename\": \"{{ $('Upload Audio').item.json.filename }}\",\n \"metadata\": {\n \"title\": \"{{ $('Upload Audio').item.json.audioTags?.title || $('Upload Audio').item.json.filename }}\",\n \"songTitle\": \"{{ $('Upload Audio').item.json.audioTags?.title || '' }}\",\n \"artist\": \"{{ $('Upload Audio').item.json.audioTags?.artist || '' }}\",\n \"description\": \"Uploaded via n8n + Songs2VID\",\n \"tags\": \"music,songs2vid\",\n \"privacy\": \"UNLISTED\",\n \"categoryId\": \"10\",\n \"resolution\": \"1920x1080\",\n \"notifySubscribers\": false,\n \"madeForKids\": false,\n \"embeddable\": true,\n \"creativeCommons\": false,\n \"includeWatermark\": false\n }\n }]\n}",
"options": {}
},
"id": "create-render",
"name": "Create Render",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
760,
0
],
"credentials": {
"httpHeaderAuth": {
"id": "REPLACE_ME",
"name": "Songs2VID API Key"
}
}
},
{
"parameters": {
"method": "GET",
"url": "={{ $env.SONGS2VID_BASE_URL || 'https://songs2vid.com' }}{{ $json.statusUrl }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {}
},
"id": "poll-status",
"name": "Get Render Status (optional poll)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1000,
0
],
"credentials": {
"httpHeaderAuth": {
"id": "REPLACE_ME",
"name": "Songs2VID API Key"
}
},
"disabled": true
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "job-done",
"leftValue": "={{ $json.body?.event || $json.event }}",
"rightValue": "job.completed",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"id": "item-done",
"leftValue": "={{ $json.body?.event || $json.event }}",
"rightValue": "job.item.completed",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "or"
},
"options": {}
},
"id": "if-completed",
"name": "Render Completed?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
280,
360
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "job-fail",
"leftValue": "={{ $json.body?.event || $json.event }}",
"rightValue": "job.failed",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"id": "item-fail",
"leftValue": "={{ $json.body?.event || $json.event }}",
"rightValue": "job.item.failed",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "or"
},
"options": {}
},
"id": "if-failed",
"name": "Render Failed?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
280,
560
]
},
{
"parameters": {
"content": "## YouTube live\nVideo ID: `{{ $json.body?.youtubeVideoId || $json.youtubeVideoId }}`\n\nhttps://youtu.be/{{ $json.body?.youtubeVideoId || $json.youtubeVideoId }}",
"height": 180,
"width": 360
},
"id": "sticky-success",
"name": "Success",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
560,
320
]
},
{
"parameters": {
"content": "## Failed\n`{{ $json.body?.error || $json.error }}`",
"height": 160,
"width": 360
},
"id": "sticky-fail",
"name": "Failure",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
560,
520
]
}
],
"connections": {
"When clicking Test workflow": {
"main": [
[
{
"node": "Upload Cover Image",
"type": "main",
"index": 0
}
]
]
},
"Upload Cover Image": {
"main": [
[
{
"node": "Upload Audio",
"type": "main",
"index": 0
}
]
]
},
"Upload Audio": {
"main": [
[
{
"node": "Create Render",
"type": "main",
"index": 0
}
]
]
},
"Create Render": {
"main": [
[
{
"node": "Get Render Status (optional poll)",
"type": "main",
"index": 0
}
]
]
},
"Songs2VID Job Webhook": {
"main": [
[
{
"node": "Render Completed?",
"type": "main",
"index": 0
}
]
]
},
"Render Completed?": {
"main": [
[
{
"node": "Success",
"type": "main",
"index": 0
}
],
[
{
"node": "Render Failed?",
"type": "main",
"index": 0
}
]
]
},
"Render Failed?": {
"main": [
[
{
"node": "Failure",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [
{
"name": "songs2vid"
},
{
"name": "n8n"
},
{
"name": "youtube"
}
],
"triggerCount": 0,
"updatedAt": "2026-08-08T00:00:00.000Z",
"versionId": "2"
}