n8n-nodes-zapcap 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -0
- package/credentials/ZapcapApi.credentials.ts +23 -0
- package/dist/credentials/ZapcapApi.credentials.d.ts +7 -0
- package/dist/credentials/ZapcapApi.credentials.js +24 -0
- package/dist/nodes/Zapcap/GenericFunctions.d.ts +3 -0
- package/dist/nodes/Zapcap/GenericFunctions.js +68 -0
- package/dist/nodes/Zapcap/Zapcap.node.d.ts +5 -0
- package/dist/nodes/Zapcap/Zapcap.node.js +144 -0
- package/dist/nodes/Zapcap/ZapcapDescription.d.ts +2 -0
- package/dist/nodes/Zapcap/ZapcapDescription.js +416 -0
- package/dist/nodes/Zapcap/icon.svg +13 -0
- package/n8n-nodes-zapcap-1.0.0.tgz +0 -0
- package/nodes/Zapcap/GenericFunctions.js +68 -0
- package/nodes/Zapcap/GenericFunctions.ts +92 -0
- package/nodes/Zapcap/Zapcap.node.js +144 -0
- package/nodes/Zapcap/Zapcap.node.ts +142 -0
- package/nodes/Zapcap/ZapcapDescription.js +416 -0
- package/nodes/Zapcap/ZapcapDescription.ts +427 -0
- package/nodes/Zapcap/icon.svg +13 -0
- package/package.json +33 -0
- package/test-node.js +220 -0
- package/test-node.ts +259 -0
- package/tsconfig.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# n8n-nodes-zapcap
|
|
2
|
+
|
|
3
|
+
This is an n8n community node that integrates with the [Zapcap.ai](https://zapcap.ai/) REST API. It allows you to programmatically add animated, styled subtitles to videos and automatically slice viral clips/shorts from long-form content.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Video Management**: Direct upload of local video files or URL-based uploads.
|
|
8
|
+
- **Templates**: Fetch designer templates directly.
|
|
9
|
+
- **Tasks**: Create video rendering and captioning tasks with customizable parameters (language, auto-approve, template, and advanced style properties).
|
|
10
|
+
- **Transcript Editing**: Retrieve and edit transcription text/timestamps before triggering rendering.
|
|
11
|
+
- **AI Clipping**: Automatically cut engaging viral shorts from longer videos with specific target durations.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### Community Nodes (n8n Web UI)
|
|
18
|
+
If this package is published on npm, you can install it via the n8n interface:
|
|
19
|
+
1. Go to **Settings** > **Community Nodes**.
|
|
20
|
+
2. Click **Install a new node**.
|
|
21
|
+
3. Enter `n8n-nodes-zapcap` and click **Install**.
|
|
22
|
+
|
|
23
|
+
### Manual Installation (Local Development)
|
|
24
|
+
For local testing or offline deployment:
|
|
25
|
+
1. Clone or copy this directory to your machine.
|
|
26
|
+
2. Build the project:
|
|
27
|
+
```bash
|
|
28
|
+
npm install
|
|
29
|
+
npm run build
|
|
30
|
+
```
|
|
31
|
+
3. Copy the compiled files into your local n8n custom directory:
|
|
32
|
+
- **Windows**: Copy the entire directory or link it inside `%USERPROFILE%\.n8n\custom\`
|
|
33
|
+
- **Linux / macOS**: Copy or link inside `~/.n8n/custom/`
|
|
34
|
+
4. Restart your n8n instance.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Configuration & Credentials
|
|
39
|
+
|
|
40
|
+
To use the Zapcap node, you must set up a **Zapcap API** credential:
|
|
41
|
+
1. Go to the [ZapCap Dashboard API Key page](https://platform.zapcap.ai/dashboard/api-key) and copy your API Key.
|
|
42
|
+
2. In n8n, create a new Credential of type **Zapcap API**.
|
|
43
|
+
3. Paste your **API Key** into the credential field.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Operations Overview
|
|
48
|
+
|
|
49
|
+
### Video
|
|
50
|
+
- **Upload File**: Upload a local video from a previous node's binary data (form-data upload).
|
|
51
|
+
- **Upload via URL**: Send a public URL of a remote video file (e.g. from S3, Google Drive, etc.).
|
|
52
|
+
- **Get All Tasks**: Retrieve a list of all render tasks or clip tasks associated with a video.
|
|
53
|
+
|
|
54
|
+
### Task
|
|
55
|
+
- **Create**: Initialize subtitle transcription and rendering. You can specify:
|
|
56
|
+
- `templateId` (Style Template)
|
|
57
|
+
- `language` (48 supported languages)
|
|
58
|
+
- `autoApprove` (Set to `false` if you want to edit transcripts before rendering)
|
|
59
|
+
- `renderOptions` (JSON schema for fonts, colors, emoji inclusion, etc.)
|
|
60
|
+
- **Get Status**: Retrieve status of rendering (`pending`, `transcribing`, `rendering`, `completed`, `failed`).
|
|
61
|
+
|
|
62
|
+
### Transcript
|
|
63
|
+
- **Get**: Fetch the JSON array of words, start/end timestamps.
|
|
64
|
+
- **Update**: Commit transcript edits (useful for correcting names/jargon before rendering).
|
|
65
|
+
- **Approve**: Approve a transcript manually to trigger video rendering (if `autoApprove` was set to false).
|
|
66
|
+
|
|
67
|
+
### Clipping (Viral Shorts)
|
|
68
|
+
- **Create Clipping Task**: Extract 1-5 engaging viral clips. Specify the desired duration range (e.g., `< 30s`, `30-60s`).
|
|
69
|
+
- **Get Status**: Check completion and retrieve direct download URLs and generated themes for each clip.
|
|
70
|
+
|
|
71
|
+
### Template
|
|
72
|
+
- **Get All**: Return list of all designer subtitle templates available to your account.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ICredentialType,
|
|
3
|
+
INodeProperties,
|
|
4
|
+
} from 'n8n-workflow';
|
|
5
|
+
|
|
6
|
+
export class ZapcapApi implements ICredentialType {
|
|
7
|
+
name = 'zapcapApi';
|
|
8
|
+
displayName = 'Zapcap API';
|
|
9
|
+
documentationUrl = 'https://platform.zapcap.ai/docs/';
|
|
10
|
+
properties: INodeProperties[] = [
|
|
11
|
+
{
|
|
12
|
+
displayName: 'API Key',
|
|
13
|
+
name: 'apiKey',
|
|
14
|
+
type: 'string',
|
|
15
|
+
typeOptions: {
|
|
16
|
+
password: true,
|
|
17
|
+
},
|
|
18
|
+
default: '',
|
|
19
|
+
required: true,
|
|
20
|
+
description: 'The API Key to authenticate with Zapcap (obtain from platform.zapcap.ai)',
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ZapcapApi = void 0;
|
|
4
|
+
class ZapcapApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'zapcapApi';
|
|
7
|
+
this.displayName = 'Zapcap API';
|
|
8
|
+
this.documentationUrl = 'https://platform.zapcap.ai/docs/';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'API Key',
|
|
12
|
+
name: 'apiKey',
|
|
13
|
+
type: 'string',
|
|
14
|
+
typeOptions: {
|
|
15
|
+
password: true,
|
|
16
|
+
},
|
|
17
|
+
default: '',
|
|
18
|
+
required: true,
|
|
19
|
+
description: 'The API Key to authenticate with Zapcap (obtain from platform.zapcap.ai)',
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.ZapcapApi = ZapcapApi;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { IExecuteFunctions, IHookFunctions, ILoadOptionsFunctions, IBinaryData } from 'n8n-workflow';
|
|
2
|
+
export declare function zapcapApiRequest(this: IExecuteFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body?: any, qs?: any, uri?: string, option?: any): Promise<any>;
|
|
3
|
+
export declare function zapcapApiUploadRequest(this: IExecuteFunctions, binaryData: IBinaryData, binaryBuffer: Buffer): Promise<any>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.zapcapApiRequest = zapcapApiRequest;
|
|
4
|
+
exports.zapcapApiUploadRequest = zapcapApiUploadRequest;
|
|
5
|
+
async function zapcapApiRequest(method, resource, body = {}, qs = {}, uri, option = {}) {
|
|
6
|
+
const credentials = await this.getCredentials('zapcapApi');
|
|
7
|
+
if (credentials === undefined) {
|
|
8
|
+
throw new Error('No credentials configured!');
|
|
9
|
+
}
|
|
10
|
+
const baseUrl = 'https://api.zapcap.ai';
|
|
11
|
+
const endpoint = uri || `${baseUrl}${resource}`;
|
|
12
|
+
const options = {
|
|
13
|
+
headers: {
|
|
14
|
+
'x-api-key': credentials.apiKey,
|
|
15
|
+
Accept: 'application/json',
|
|
16
|
+
},
|
|
17
|
+
method,
|
|
18
|
+
uri: endpoint,
|
|
19
|
+
qs,
|
|
20
|
+
json: true,
|
|
21
|
+
...option,
|
|
22
|
+
};
|
|
23
|
+
if (Object.keys(body).length > 0) {
|
|
24
|
+
options.body = body;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return await this.helpers.request(options);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new Error(`Zapcap API error: ${error.message || error}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function zapcapApiUploadRequest(binaryData, binaryBuffer) {
|
|
34
|
+
const credentials = await this.getCredentials('zapcapApi');
|
|
35
|
+
if (credentials === undefined) {
|
|
36
|
+
throw new Error('No credentials configured!');
|
|
37
|
+
}
|
|
38
|
+
const boundary = `----n8nFormBoundary${Date.now()}`;
|
|
39
|
+
const fileName = binaryData.fileName || 'video.mp4';
|
|
40
|
+
const mimeType = binaryData.mimeType || 'video/mp4';
|
|
41
|
+
const preamble = `--${boundary}\r\n` +
|
|
42
|
+
`Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n` +
|
|
43
|
+
`Content-Type: ${mimeType}\r\n\r\n`;
|
|
44
|
+
const closing = `\r\n--${boundary}--\r\n`;
|
|
45
|
+
const bodyBuffer = Buffer.concat([
|
|
46
|
+
Buffer.from(preamble, 'utf8'),
|
|
47
|
+
binaryBuffer,
|
|
48
|
+
Buffer.from(closing, 'utf8'),
|
|
49
|
+
]);
|
|
50
|
+
const options = {
|
|
51
|
+
headers: {
|
|
52
|
+
'x-api-key': credentials.apiKey,
|
|
53
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
54
|
+
'Content-Length': bodyBuffer.length.toString(),
|
|
55
|
+
Accept: 'application/json',
|
|
56
|
+
},
|
|
57
|
+
method: 'POST',
|
|
58
|
+
uri: 'https://api.zapcap.ai/videos',
|
|
59
|
+
body: bodyBuffer,
|
|
60
|
+
json: true,
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
return await this.helpers.request(options);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
throw new Error(`Zapcap Upload API error: ${error.message || error}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Zapcap = void 0;
|
|
4
|
+
const ZapcapDescription_1 = require("./ZapcapDescription");
|
|
5
|
+
const GenericFunctions_1 = require("./GenericFunctions");
|
|
6
|
+
class Zapcap {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.description = {
|
|
9
|
+
displayName: 'Zapcap',
|
|
10
|
+
name: 'zapcap',
|
|
11
|
+
icon: 'file:icon.svg',
|
|
12
|
+
group: ['transform'],
|
|
13
|
+
version: 1,
|
|
14
|
+
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
|
|
15
|
+
description: 'Automated video captioning and editing using Zapcap.ai API',
|
|
16
|
+
defaults: {
|
|
17
|
+
name: 'Zapcap',
|
|
18
|
+
},
|
|
19
|
+
inputs: ['main'],
|
|
20
|
+
outputs: ['main'],
|
|
21
|
+
credentials: [
|
|
22
|
+
{
|
|
23
|
+
name: 'zapcapApi',
|
|
24
|
+
required: true,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
properties: ZapcapDescription_1.zapcapNodeProperties,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async execute() {
|
|
31
|
+
const items = this.getInputData();
|
|
32
|
+
const returnData = [];
|
|
33
|
+
const resource = this.getNodeParameter('resource', 0);
|
|
34
|
+
const operation = this.getNodeParameter('operation', 0);
|
|
35
|
+
for (let i = 0; i < items.length; i++) {
|
|
36
|
+
try {
|
|
37
|
+
let responseData;
|
|
38
|
+
if (resource === 'video') {
|
|
39
|
+
if (operation === 'upload') {
|
|
40
|
+
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
|
|
41
|
+
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
|
|
42
|
+
const binaryBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
|
|
43
|
+
responseData = await GenericFunctions_1.zapcapApiUploadRequest.call(this, binaryData, binaryBuffer);
|
|
44
|
+
}
|
|
45
|
+
else if (operation === 'uploadUrl') {
|
|
46
|
+
const url = this.getNodeParameter('videoUrl', i);
|
|
47
|
+
responseData = await GenericFunctions_1.zapcapApiRequest.call(this, 'POST', '/videos/url', { url });
|
|
48
|
+
}
|
|
49
|
+
else if (operation === 'getAllTasks') {
|
|
50
|
+
const videoId = this.getNodeParameter('videoId', i);
|
|
51
|
+
responseData = await GenericFunctions_1.zapcapApiRequest.call(this, 'GET', `/videos/${videoId}/tasks`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (resource === 'task') {
|
|
55
|
+
if (operation === 'create') {
|
|
56
|
+
const videoId = this.getNodeParameter('videoId', i);
|
|
57
|
+
const templateId = this.getNodeParameter('templateId', i);
|
|
58
|
+
const language = this.getNodeParameter('language', i);
|
|
59
|
+
const autoApprove = this.getNodeParameter('autoApprove', i);
|
|
60
|
+
const additionalFields = this.getNodeParameter('additionalFields', i);
|
|
61
|
+
const body = {
|
|
62
|
+
templateId,
|
|
63
|
+
language,
|
|
64
|
+
autoApprove,
|
|
65
|
+
};
|
|
66
|
+
if (additionalFields.transcriptTaskId) {
|
|
67
|
+
body.transcriptTaskId = additionalFields.transcriptTaskId;
|
|
68
|
+
}
|
|
69
|
+
if (additionalFields.renderOptionsJson) {
|
|
70
|
+
try {
|
|
71
|
+
body.renderOptions = typeof additionalFields.renderOptionsJson === 'string'
|
|
72
|
+
? JSON.parse(additionalFields.renderOptionsJson)
|
|
73
|
+
: additionalFields.renderOptionsJson;
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
throw new Error('Render Options JSON is invalid: ' + err.message);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
responseData = await GenericFunctions_1.zapcapApiRequest.call(this, 'POST', `/videos/${videoId}/task`, body);
|
|
80
|
+
}
|
|
81
|
+
else if (operation === 'get') {
|
|
82
|
+
const videoId = this.getNodeParameter('videoId', i);
|
|
83
|
+
const taskId = this.getNodeParameter('taskId', i);
|
|
84
|
+
responseData = await GenericFunctions_1.zapcapApiRequest.call(this, 'GET', `/videos/${videoId}/task/${taskId}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (resource === 'transcript') {
|
|
88
|
+
const videoId = this.getNodeParameter('videoId', i);
|
|
89
|
+
const taskId = this.getNodeParameter('taskId', i);
|
|
90
|
+
if (operation === 'get') {
|
|
91
|
+
responseData = await GenericFunctions_1.zapcapApiRequest.call(this, 'GET', `/videos/${videoId}/task/${taskId}/transcript`);
|
|
92
|
+
}
|
|
93
|
+
else if (operation === 'update') {
|
|
94
|
+
const transcriptJson = this.getNodeParameter('transcriptJson', i);
|
|
95
|
+
const body = typeof transcriptJson === 'string' ? JSON.parse(transcriptJson) : transcriptJson;
|
|
96
|
+
responseData = await GenericFunctions_1.zapcapApiRequest.call(this, 'PUT', `/videos/${videoId}/task/${taskId}/transcript`, body);
|
|
97
|
+
}
|
|
98
|
+
else if (operation === 'approve') {
|
|
99
|
+
responseData = await GenericFunctions_1.zapcapApiRequest.call(this, 'POST', `/videos/${videoId}/task/${taskId}/approve-transcript`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (resource === 'clipping') {
|
|
103
|
+
const videoId = this.getNodeParameter('videoId', i);
|
|
104
|
+
if (operation === 'create') {
|
|
105
|
+
const maxClips = this.getNodeParameter('maxClips', i);
|
|
106
|
+
const durationRange = this.getNodeParameter('durationRange', i);
|
|
107
|
+
const body = {
|
|
108
|
+
maxClips,
|
|
109
|
+
durationRange,
|
|
110
|
+
};
|
|
111
|
+
responseData = await GenericFunctions_1.zapcapApiRequest.call(this, 'POST', `/videos/${videoId}/clipTask`, body);
|
|
112
|
+
}
|
|
113
|
+
else if (operation === 'get') {
|
|
114
|
+
const clipTaskId = this.getNodeParameter('clipTaskId', i);
|
|
115
|
+
responseData = await GenericFunctions_1.zapcapApiRequest.call(this, 'GET', `/videos/${videoId}/clipTask/${clipTaskId}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (resource === 'template') {
|
|
119
|
+
if (operation === 'getAll') {
|
|
120
|
+
responseData = await GenericFunctions_1.zapcapApiRequest.call(this, 'GET', '/templates');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(responseData)) {
|
|
124
|
+
const executionData = this.helpers.returnJsonArray(responseData);
|
|
125
|
+
returnData.push(...executionData);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
const executionData = this.helpers.returnJsonArray([responseData]);
|
|
129
|
+
returnData.push(...executionData);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (this.continueOnFail()) {
|
|
134
|
+
returnData.push({ json: { error: error.message } });
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return [returnData];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.Zapcap = Zapcap;
|