Skip to content

Commit c5c414f

Browse files
authored
New Components - rendi (#16713)
* new components * pnpm-lock.yaml * updates * updates
1 parent 5784a41 commit c5c414f

File tree

11 files changed

+346
-6
lines changed

11 files changed

+346
-6
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import rendi from "../../rendi.app.mjs";
2+
3+
export default {
4+
key: "rendi-get-ffmpeg-command-status",
5+
name: "Get FFmpeg Command Status",
6+
description: "Get the status of a previously submitted FFmpeg command. [See the documentation](https://docs.rendi.dev/api-reference/endpoint/poll-command)",
7+
version: "0.0.1",
8+
type: "action",
9+
props: {
10+
rendi,
11+
commandId: {
12+
type: "string",
13+
label: "Command ID",
14+
description: "ID of the FFmpeg command to check",
15+
},
16+
},
17+
async run({ $ }) {
18+
const response = await this.rendi.getFfmpegCommand({
19+
$,
20+
commandId: this.commandId,
21+
});
22+
$.export("$summary", `Successfully retrieved status of FFmpeg command with ID: ${this.commandId}`);
23+
return response;
24+
},
25+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import rendi from "../../rendi.app.mjs";
2+
3+
export default {
4+
key: "rendi-list-stored-files",
5+
name: "List Stored Files",
6+
description: "Get the list of all stored files for an account. [See the documentation](https://docs.rendi.dev/api-reference/endpoint/list-files)",
7+
version: "0.0.1",
8+
type: "action",
9+
props: {
10+
rendi,
11+
},
12+
async run({ $ }) {
13+
const response = await this.rendi.listFiles({
14+
$,
15+
});
16+
$.export("$summary", `Successfully retrieved ${response.length} file${response.length === 1
17+
? ""
18+
: "s"}`);
19+
return response;
20+
},
21+
};
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import rendi from "../../rendi.app.mjs";
2+
import { ConfigurationError } from "@pipedream/platform";
3+
import { parseObject } from "../../common/utils.mjs";
4+
import { axios } from "@pipedream/platform";
5+
import fs from "fs";
6+
7+
export default {
8+
key: "rendi-run-ffmpeg-command",
9+
name: "Run FFmpeg Command",
10+
description: "Submit an FFmpeg command for processing with input and output file specifications. [See the documentation](https://docs.rendi.dev/api-reference/endpoint/run-ffmpeg-command)",
11+
version: "0.0.1",
12+
type: "action",
13+
props: {
14+
rendi,
15+
inputFiles: {
16+
type: "object",
17+
label: "Input File URL(s)",
18+
description: "Dictionary mapping file aliases to their publicly accessible paths, file name should appear in the end of the url, keys must start with 'in_'. You can use public file urls, google drive, dropbox, rendi stored files, s3 stored files, etc. as long as they are publicly accessible. [See the documentation](https://docs.rendi.dev/api-reference/endpoint/run-ffmpeg-command) for more information",
19+
},
20+
outputFiles: {
21+
type: "object",
22+
label: "Output File Name(s)",
23+
description: "Dictionary mapping file aliases to their desired output file names, keys must start with 'out_'. [See the documentation](https://docs.rendi.dev/api-reference/endpoint/run-ffmpeg-command) for more information",
24+
},
25+
command: {
26+
type: "string",
27+
label: "FFmpeg Command",
28+
description: "FFmpeg command string using {{alias}} placeholders for input and output files. `{{}}` is a reserved string, instead you can use `\\{\\{\\}\\}` , so for example `{{in_1}}` should be `\\{\\{in_1\\}\\}`. Example: `-i \\{\\{in_1\\}\\} \\{\\{out_1\\}\\}`",
29+
},
30+
maxCommandRunSeconds: {
31+
type: "string",
32+
label: "Max Command Run Seconds",
33+
description: "Maximum allowed runtime in seconds for a single FFmpeg command, the default is 300 seconds",
34+
optional: true,
35+
},
36+
waitForCompletion: {
37+
type: "boolean",
38+
label: "Wait for Completion",
39+
description: "Set to `true` to poll the API in 3-second intervals until the command is completed",
40+
optional: true,
41+
reloadProps: true,
42+
},
43+
},
44+
additionalProps() {
45+
if (this.waitForCompletion) {
46+
return {
47+
downloadFilesToTmp: {
48+
type: "boolean",
49+
label: "Download Files to /tmp",
50+
description: "Set to `true` to download the output files to the workflow's /tmp directory",
51+
optional: true,
52+
},
53+
};
54+
}
55+
return {};
56+
},
57+
async run({ $ }) {
58+
const inputFiles = parseObject(this.inputFiles);
59+
const outputFiles = parseObject(this.outputFiles);
60+
61+
if (Object.keys(inputFiles).some((key) => !key.startsWith("in_"))) {
62+
throw new ConfigurationError("Input file keys must start with 'in_'");
63+
}
64+
if (Object.keys(outputFiles).some((key) => !key.startsWith("out_"))) {
65+
throw new ConfigurationError("Output file keys must start with 'out_'");
66+
}
67+
68+
let response = await this.rendi.runFfmpegCommand({
69+
$,
70+
data: {
71+
input_files: inputFiles,
72+
output_files: outputFiles,
73+
ffmpeg_command: this.command,
74+
max_command_run_seconds: this.maxCommandRunSeconds,
75+
},
76+
});
77+
78+
if (this.waitForCompletion) {
79+
const commandId = response.command_id;
80+
const timer = (ms) => new Promise((res) => setTimeout(res, ms));
81+
while (response?.status !== "SUCCESS" && response?.status !== "FAILED") {
82+
response = await this.rendi.getFfmpegCommand({
83+
$,
84+
commandId,
85+
});
86+
await timer(3000);
87+
}
88+
if (response?.status === "SUCCESS" && this.downloadFilesToTmp) {
89+
response.tmpFiles = [];
90+
for (const value of Object.values(response.output_files)) {
91+
const resp = await axios($, {
92+
url: value.storage_url,
93+
responseType: "arraybuffer",
94+
});
95+
const filename = value.storage_url.split("/").pop();
96+
const downloadedFilepath = `/tmp/${filename}`;
97+
fs.writeFileSync(downloadedFilepath, resp);
98+
99+
response.tmpFiles.push({
100+
filename,
101+
downloadedFilepath,
102+
});
103+
}
104+
}
105+
106+
if (response?.error_message) {
107+
throw new ConfigurationError(response.error_message);
108+
}
109+
}
110+
111+
$.export("$summary", `FFmpeg command ${this.waitForCompletion
112+
? "submitted and completed"
113+
: "submitted"} successfully`);
114+
return response;
115+
},
116+
};

components/rendi/common/utils.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function parseObject(obj) {
2+
if (!obj) return undefined;
3+
4+
if (typeof obj === "string") {
5+
return JSON.parse(obj);
6+
}
7+
8+
return obj;
9+
}

components/rendi/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pipedream/rendi",
3-
"version": "0.0.1",
3+
"version": "0.1.0",
44
"description": "Pipedream Rendi Components",
55
"main": "rendi.app.mjs",
66
"keywords": [
@@ -11,5 +11,8 @@
1111
"author": "Pipedream <[email protected]> (https://pipedream.com/)",
1212
"publishConfig": {
1313
"access": "public"
14+
},
15+
"dependencies": {
16+
"@pipedream/platform": "^3.0.3"
1417
}
15-
}
18+
}

components/rendi/rendi.app.mjs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,52 @@
1+
import { axios } from "@pipedream/platform";
2+
13
export default {
24
type: "app",
35
app: "rendi",
46
propDefinitions: {},
57
methods: {
6-
// this.$auth contains connected account data
7-
authKeys() {
8-
console.log(Object.keys(this.$auth));
8+
_baseUrl() {
9+
return "https://api.rendi.dev/v1";
10+
},
11+
_makeRequest({
12+
$ = this,
13+
path,
14+
...otherOpts
15+
}) {
16+
return axios($, {
17+
url: `${this._baseUrl()}${path}`,
18+
headers: {
19+
"x-api-key": `${this.$auth.api_key}`,
20+
},
21+
...otherOpts,
22+
});
23+
},
24+
listFiles(opts = {}) {
25+
return this._makeRequest({
26+
path: "/files",
27+
...opts,
28+
});
29+
},
30+
listCommands(opts = {}) {
31+
return this._makeRequest({
32+
path: "/commands",
33+
...opts,
34+
});
35+
},
36+
getFfmpegCommand({
37+
commandId, ...opts
38+
}) {
39+
return this._makeRequest({
40+
path: `/commands/${commandId}`,
41+
...opts,
42+
});
43+
},
44+
runFfmpegCommand(opts = {}) {
45+
return this._makeRequest({
46+
method: "POST",
47+
path: "/run-ffmpeg-command",
48+
...opts,
49+
});
950
},
1051
},
1152
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import rendi from "../../rendi.app.mjs";
2+
import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform";
3+
import sampleEmit from "./test-event.mjs";
4+
5+
export default {
6+
key: "rendi-new-ffmpeg-command",
7+
name: "New FFmpeg Command",
8+
description: "Emit new event when a new FFmpeg command is submitted. [See the documentation](https://docs.rendi.dev/api-reference/endpoint/list-commands)",
9+
version: "0.0.1",
10+
type: "source",
11+
dedupe: "unique",
12+
props: {
13+
rendi,
14+
db: "$.service.db",
15+
timer: {
16+
label: "Polling interval",
17+
description: "Pipedream will poll the Trello API on this schedule",
18+
type: "$.interface.timer",
19+
default: {
20+
intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
21+
},
22+
},
23+
},
24+
methods: {
25+
_getLastTs() {
26+
return this.db.get("lastTs") || 0;
27+
},
28+
_setLastTs(ts) {
29+
this.db.set("lastTs", ts);
30+
},
31+
generateMeta(command) {
32+
return {
33+
id: command.command_id,
34+
summary: `New FFmpeg command: ${command.command_id}`,
35+
ts: Date.parse(command.created_at),
36+
};
37+
},
38+
async processEvent(max) {
39+
const lastTs = this._getLastTs();
40+
let maxTs = lastTs;
41+
42+
let commands = [];
43+
const results = await this.rendi.listCommands();
44+
for (const command of results) {
45+
const ts = Date.parse(command.created_at);
46+
if (ts > lastTs) {
47+
commands.push(command);
48+
maxTs = Math.max(maxTs, ts);
49+
}
50+
}
51+
52+
if (max && commands.length > max) {
53+
commands = commands.slice(-1 * max);
54+
}
55+
56+
commands.forEach((command) => {
57+
const meta = this.generateMeta(command);
58+
this.$emit(command, meta);
59+
});
60+
61+
this._setLastTs(maxTs);
62+
},
63+
},
64+
hooks: {
65+
async deploy() {
66+
await this.processEvent(25);
67+
},
68+
},
69+
async run() {
70+
await this.processEvent();
71+
},
72+
sampleEmit,
73+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
"command_id": "5eda862d-b5d5-41f5-ac92-e6872b054",
3+
"status": "SUCCESS",
4+
"created_at": "2025-05-19T18:15:20.349538Z"
5+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import rendi from "../../rendi.app.mjs";
2+
import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform";
3+
import sampleEmit from "./test-event.mjs";
4+
5+
export default {
6+
key: "rendi-new-stored-file",
7+
name: "New Stored File",
8+
description: "Emit new event when a new file is uploaded to an account. [See the documentation](https://docs.rendi.dev/api-reference/endpoint/list-files)",
9+
version: "0.0.1",
10+
type: "source",
11+
dedupe: "unique",
12+
props: {
13+
rendi,
14+
timer: {
15+
type: "$.interface.timer",
16+
default: {
17+
intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
18+
},
19+
},
20+
},
21+
methods: {
22+
generateMeta(file) {
23+
return {
24+
id: file.file_id,
25+
summary: `New Stored File with ID: ${file.file_id}`,
26+
ts: Date.now(),
27+
};
28+
},
29+
},
30+
async run() {
31+
const files = await this.rendi.listFiles();
32+
for (const file of files) {
33+
const meta = this.generateMeta(file);
34+
this.$emit(file, meta);
35+
}
36+
},
37+
sampleEmit,
38+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
"file_id": "8e30e8fb-a037-48e0-aca2-eaa6df7ec",
3+
"size_mbytes": 34.04952430725098,
4+
"storage_url": "https://storage.rendi.dev/trial_files/86f043b9-dc30-465b-995c-371382ee1c74/5eda862d-b5d5-41f5-ac92-e6872b054/output_one.avi"
5+
}

0 commit comments

Comments
 (0)