@twick/render-server 0.14.20 → 0.15.1

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 CHANGED
@@ -48,8 +48,9 @@ The server will start on port 3001 by default. You can change this by setting th
48
48
 
49
49
  ### Option 2: Use Programmatically
50
50
 
51
- Import and use the `renderTwickVideo` function directly:
51
+ Import and use the `renderTwickVideo` function directly. The package supports both ESM and CommonJS:
52
52
 
53
+ **ESM (import):**
53
54
  ```typescript
54
55
  import { renderTwickVideo } from "@twick/render-server";
55
56
 
@@ -67,6 +68,24 @@ const videoPath = await renderTwickVideo(
67
68
  );
68
69
  ```
69
70
 
71
+ **CommonJS (require):**
72
+ ```javascript
73
+ const { renderTwickVideo } = require("@twick/render-server");
74
+
75
+ const videoPath = await renderTwickVideo(
76
+ {
77
+ input: {
78
+ properties: { width: 1920, height: 1080 },
79
+ // ... your project variables
80
+ }
81
+ },
82
+ {
83
+ outFile: "my-video.mp4",
84
+ quality: "high"
85
+ }
86
+ );
87
+ ```
88
+
70
89
  ## API Endpoints
71
90
 
72
91
  ### POST /api/render-video
@@ -168,7 +187,7 @@ Renders a Twick video with the provided variables and settings.
168
187
 
169
188
  **Returns:** `Promise<string>` - Path to the rendered video file
170
189
 
171
- **Example:**
190
+ **Example (ESM):**
172
191
  ```typescript
173
192
  import { renderTwickVideo } from "@twick/render-server";
174
193
 
@@ -187,12 +206,39 @@ const videoPath = await renderTwickVideo(
187
206
  );
188
207
  ```
189
208
 
209
+ **Example (CommonJS):**
210
+ ```javascript
211
+ const { renderTwickVideo } = require("@twick/render-server");
212
+
213
+ const videoPath = await renderTwickVideo(
214
+ {
215
+ input: {
216
+ properties: { width: 1920, height: 1080 },
217
+ tracks: [/* ... */]
218
+ }
219
+ },
220
+ {
221
+ outFile: "my-video.mp4",
222
+ quality: "high",
223
+ outDir: "./output"
224
+ }
225
+ );
226
+ ```
227
+
190
228
  > **Note:** This server will work on Linux and macOS only. Windows is not supported.
191
229
 
230
+ ## Module Support
231
+
232
+ This package supports both ESM and CommonJS:
233
+ - **ESM**: Use `import { renderTwickVideo } from "@twick/render-server"`
234
+ - **CommonJS**: Use `const { renderTwickVideo } = require("@twick/render-server")`
235
+
236
+ The package automatically provides the correct format based on your module system.
237
+
192
238
  ## Browser Support
193
239
 
194
240
  This package requires a Node.js environment with support for:
195
- - Node.js 18 or higher
241
+ - Node.js 20 or higher
196
242
  - Puppeteer for video rendering
197
243
  - File system operations
198
244
 
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
+
3
+ // src/cli.ts
2
4
  import fs from "fs";
3
5
  import path from "path";
4
6
  import { fileURLToPath } from "url";
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = path.dirname(__filename);
7
- const SERVER_TEMPLATE = `import express from "express";
7
+ var __filename = fileURLToPath(import.meta.url);
8
+ var __dirname = path.dirname(__filename);
9
+ var SERVER_TEMPLATE = `import express from "express";
8
10
  import cors from "cors";
9
11
  import path from "path";
10
12
  import { fileURLToPath } from "url";
@@ -201,7 +203,7 @@ app.listen(PORT, () => {
201
203
  console.log(\`Download endpoint rate limited: \${RATE_LIMIT_MAX_REQUESTS} requests per \${RATE_LIMIT_WINDOW_MS / 60000} minutes\`);
202
204
  });
203
205
  `;
204
- const PACKAGE_JSON_TEMPLATE = `{
206
+ var PACKAGE_JSON_TEMPLATE = `{
205
207
  "name": "twick-render-server",
206
208
  "version": "1.0.0",
207
209
  "type": "module",
@@ -224,7 +226,7 @@ const PACKAGE_JSON_TEMPLATE = `{
224
226
  }
225
227
  }
226
228
  `;
227
- const TSCONFIG_TEMPLATE = `{
229
+ var TSCONFIG_TEMPLATE = `{
228
230
  "compilerOptions": {
229
231
  "target": "ES2022",
230
232
  "module": "ESNext",
@@ -247,49 +249,59 @@ const TSCONFIG_TEMPLATE = `{
247
249
  "exclude": ["node_modules", "dist"]
248
250
  }
249
251
  `;
250
- const GITIGNORE_TEMPLATE = `node_modules/
252
+ var GITIGNORE_TEMPLATE = `node_modules/
251
253
  dist/
252
254
  output/
253
255
  .env
254
256
  *.log
255
257
  `;
256
258
  function scaffoldServer() {
257
- const currentDir = process.cwd();
258
- const serverDir = path.join(currentDir, "twick-render-server");
259
- // Check if directory already exists
260
- if (fs.existsSync(serverDir)) {
261
- console.error(`Error: Directory "${serverDir}" already exists.`);
262
- console.error("Please remove it or choose a different location.");
263
- process.exit(1);
264
- }
265
- // Create server directory
266
- fs.mkdirSync(serverDir, { recursive: true });
267
- fs.mkdirSync(path.join(serverDir, "output"), { recursive: true });
268
- // Write server.ts
269
- fs.writeFileSync(path.join(serverDir, "server.ts"), SERVER_TEMPLATE, "utf-8");
270
- // Write package.json
271
- fs.writeFileSync(path.join(serverDir, "package.json"), PACKAGE_JSON_TEMPLATE, "utf-8");
272
- // Write tsconfig.json
273
- fs.writeFileSync(path.join(serverDir, "tsconfig.json"), TSCONFIG_TEMPLATE, "utf-8");
274
- // Write .gitignore
275
- fs.writeFileSync(path.join(serverDir, ".gitignore"), GITIGNORE_TEMPLATE, "utf-8");
276
- console.log(`✅ Successfully scaffolded Twick render server!`);
277
- console.log(`\n📁 Server created at: ${serverDir}`);
278
- console.log(`\n📝 Next steps:`);
279
- console.log(` 1. cd ${path.basename(serverDir)}`);
280
- console.log(` 2. npm install`);
281
- console.log(` 3. npm run dev (for development)`);
282
- console.log(` 4. npm run build && npm start (for production)`);
259
+ const currentDir = process.cwd();
260
+ const serverDir = path.join(currentDir, "twick-render-server");
261
+ if (fs.existsSync(serverDir)) {
262
+ console.error(`Error: Directory "${serverDir}" already exists.`);
263
+ console.error("Please remove it or choose a different location.");
264
+ process.exit(1);
265
+ }
266
+ fs.mkdirSync(serverDir, { recursive: true });
267
+ fs.mkdirSync(path.join(serverDir, "output"), { recursive: true });
268
+ fs.writeFileSync(
269
+ path.join(serverDir, "server.ts"),
270
+ SERVER_TEMPLATE,
271
+ "utf-8"
272
+ );
273
+ fs.writeFileSync(
274
+ path.join(serverDir, "package.json"),
275
+ PACKAGE_JSON_TEMPLATE,
276
+ "utf-8"
277
+ );
278
+ fs.writeFileSync(
279
+ path.join(serverDir, "tsconfig.json"),
280
+ TSCONFIG_TEMPLATE,
281
+ "utf-8"
282
+ );
283
+ fs.writeFileSync(
284
+ path.join(serverDir, ".gitignore"),
285
+ GITIGNORE_TEMPLATE,
286
+ "utf-8"
287
+ );
288
+ console.log(`\u2705 Successfully scaffolded Twick render server!`);
289
+ console.log(`
290
+ \u{1F4C1} Server created at: ${serverDir}`);
291
+ console.log(`
292
+ \u{1F4DD} Next steps:`);
293
+ console.log(` 1. cd ${path.basename(serverDir)}`);
294
+ console.log(` 2. npm install`);
295
+ console.log(` 3. npm run dev (for development)`);
296
+ console.log(` 4. npm run build && npm start (for production)`);
283
297
  }
284
- // Parse command line arguments
285
- const args = process.argv.slice(2);
286
- const command = args[0];
298
+ var args = process.argv.slice(2);
299
+ var command = args[0];
287
300
  if (command === "init") {
288
- scaffoldServer();
289
- }
290
- else {
291
- console.log("Usage: npx twick-render-server init");
292
- console.log("\nThis command scaffolds a new Twick render server in the current directory.");
293
- process.exit(1);
301
+ scaffoldServer();
302
+ } else {
303
+ console.log("Usage: npx twick-render-server init");
304
+ console.log("\nThis command scaffolds a new Twick render server in the current directory.");
305
+ process.exit(1);
294
306
  }
295
- //# sourceMappingURL=cli.js.map
307
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst SERVER_TEMPLATE = `import express from \"express\";\nimport cors from \"cors\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { renderTwickVideo } from \"@twick/render-server\";\n\nconst PORT = process.env.PORT || 3001;\nconst BASE_PATH = \\`http://localhost:\\${PORT}\\`;\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Rate limiting configuration\nconst RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000; // 15 minutes\nconst RATE_LIMIT_MAX_REQUESTS = 100; // Maximum requests per window\nconst RATE_LIMIT_CLEANUP_INTERVAL_MS = 60 * 1000; // Cleanup every minute\n\n// In-memory store for rate limiting\ninterface RateLimitEntry {\n count: number;\n resetTime: number;\n}\n\nconst rateLimitStore = new Map<string, RateLimitEntry>();\n\n// Cleanup expired entries periodically\nsetInterval(() => {\n const now = Date.now();\n for (const [key, entry] of rateLimitStore.entries()) {\n if (now > entry.resetTime) {\n rateLimitStore.delete(key);\n }\n }\n}, RATE_LIMIT_CLEANUP_INTERVAL_MS);\n\n/**\n * Rate limiting middleware for API endpoints.\n * Tracks request counts per IP address and enforces rate limits\n * to prevent abuse of the render server.\n *\n * @param req - Express request object\n * @param res - Express response object\n * @param next - Express next function\n * \n * @example\n * \\`\\`\\`js\n * app.use('/api', rateLimitMiddleware);\n * // Applies rate limiting to all /api routes\n * \\`\\`\\`\n */\nconst rateLimitMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction) => {\n const clientIP = req.ip || req.connection.remoteAddress || 'unknown';\n const now = Date.now();\n \n // Get or create rate limit entry for this IP\n let entry = rateLimitStore.get(clientIP);\n \n if (!entry || now > entry.resetTime) {\n // New window or expired entry\n entry = {\n count: 1,\n resetTime: now + RATE_LIMIT_WINDOW_MS\n };\n rateLimitStore.set(clientIP, entry);\n } else {\n // Increment count in current window\n entry.count++;\n \n if (entry.count > RATE_LIMIT_MAX_REQUESTS) {\n // Rate limit exceeded\n const retryAfter = Math.ceil((entry.resetTime - now) / 1000);\n res.set('Retry-After', retryAfter.toString());\n res.set('X-RateLimit-Limit', RATE_LIMIT_MAX_REQUESTS.toString());\n res.set('X-RateLimit-Remaining', '0');\n res.set('X-RateLimit-Reset', new Date(entry.resetTime).toISOString());\n \n return res.status(429).json({\n success: false,\n error: 'Too many requests',\n message: \\`Rate limit exceeded. Try again in \\${retryAfter} seconds.\\`,\n retryAfter\n });\n }\n }\n \n // Add rate limit headers\n res.set('X-RateLimit-Limit', RATE_LIMIT_MAX_REQUESTS.toString());\n res.set('X-RateLimit-Remaining', (RATE_LIMIT_MAX_REQUESTS - entry.count).toString());\n res.set('X-RateLimit-Reset', new Date(entry.resetTime).toISOString());\n \n next();\n};\n\nconst app = express();\n\napp.use(cors());\napp.use(express.json());\n\n// Serve static files from output directory\napp.use(\"/output\", express.static(path.join(__dirname, \"output\")));\n\n/**\n * POST endpoint for video rendering requests.\n * Accepts project variables and settings, renders the video,\n * and returns a download URL for the completed video.\n *\n * @param req - Express request object containing variables and settings\n * @param res - Express response object\n * \n * @example\n * \\`\\`\\`js\n * POST /api/render-video\n * Body: { variables: {...}, settings: {...} }\n * Response: { success: true, downloadUrl: \"...\" }\n * \\`\\`\\`\n */\napp.post(\"/api/render-video\", async (req, res) => {\n const { variables, settings } = req.body;\n\n try {\n const outputPath = await renderTwickVideo(variables, settings);\n res.json({\n success: true,\n downloadUrl: \\`\\${BASE_PATH}/download/\\${path.basename(outputPath)}\\`,\n });\n } catch (error) {\n console.error(\"Render error:\", error);\n res.status(500).json({\n success: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n});\n\n/**\n * GET endpoint for downloading rendered videos.\n * Serves video files with rate limiting and security checks\n * to prevent path traversal attacks.\n *\n * @param req - Express request object with filename parameter\n * @param res - Express response object\n * \n * @example\n * \\`\\`\\`js\n * GET /download/video-123.mp4\n * // Downloads the specified video file\n * \\`\\`\\`\n */\napp.get(\"/download/:filename\", rateLimitMiddleware, (req, res) => {\n const outputDir = path.resolve(__dirname, \"output\");\n const requestedPath = path.resolve(outputDir, req.params.filename);\n if (!requestedPath.startsWith(outputDir + path.sep)) {\n // Attempted path traversal or access outside output directory\n res.status(403).json({\n success: false,\n error: \"Forbidden\",\n });\n return;\n }\n res.download(requestedPath, (err) => {\n if (err) {\n res.status(404).json({\n success: false,\n error: \"File not found\",\n });\n }\n });\n});\n\n/**\n * Health check endpoint for monitoring server status.\n * Returns server status and current timestamp for health monitoring.\n *\n * @param req - Express request object\n * @param res - Express response object\n * \n * @example\n * \\`\\`\\`js\n * GET /health\n * Response: { status: \"ok\", timestamp: \"2024-01-01T00:00:00.000Z\" }\n * \\`\\`\\`\n */\napp.get(\"/health\", (req, res) => {\n res.json({\n status: \"ok\",\n timestamp: new Date().toISOString(),\n });\n});\n\n// Start the server\napp.listen(PORT, () => {\n console.log(\\`Render server running on port \\${PORT}\\`);\n console.log(\\`Health check: \\${BASE_PATH}/health\\`);\n console.log(\\`API endpoint: \\${BASE_PATH}/api/render-video\\`);\n console.log(\\`Download endpoint rate limited: \\${RATE_LIMIT_MAX_REQUESTS} requests per \\${RATE_LIMIT_WINDOW_MS / 60000} minutes\\`);\n});\n`;\n\nconst PACKAGE_JSON_TEMPLATE = `{\n \"name\": \"twick-render-server\",\n \"version\": \"1.0.0\",\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"tsx watch server.ts\",\n \"build\": \"tsc\",\n \"start\": \"node dist/server.js\"\n },\n \"dependencies\": {\n \"@twick/render-server\": \"^0.14.11\",\n \"cors\": \"^2.8.5\",\n \"express\": \"^4.18.2\"\n },\n \"devDependencies\": {\n \"@types/cors\": \"^2.8.17\",\n \"@types/express\": \"^4.17.21\",\n \"@types/node\": \"^20.10.0\",\n \"tsx\": \"^4.7.0\",\n \"typescript\": \"5.4.2\"\n }\n}\n`;\n\nconst TSCONFIG_TEMPLATE = `{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"allowSyntheticDefaultImports\": true,\n \"esModuleInterop\": true,\n \"allowJs\": true,\n \"strict\": true,\n \"skipLibCheck\": true,\n \"forceConsistentCasingInFileNames\": true,\n \"outDir\": \"./dist\",\n \"rootDir\": \".\",\n \"declaration\": true,\n \"declarationMap\": true,\n \"sourceMap\": true,\n \"resolveJsonModule\": true,\n \"noEmit\": false\n },\n \"include\": [\"server.ts\"],\n \"exclude\": [\"node_modules\", \"dist\"]\n}\n`;\n\nconst GITIGNORE_TEMPLATE = `node_modules/\ndist/\noutput/\n.env\n*.log\n`;\n\nfunction scaffoldServer() {\n const currentDir = process.cwd();\n const serverDir = path.join(currentDir, \"twick-render-server\");\n\n // Check if directory already exists\n if (fs.existsSync(serverDir)) {\n console.error(`Error: Directory \"${serverDir}\" already exists.`);\n console.error(\"Please remove it or choose a different location.\");\n process.exit(1);\n }\n\n // Create server directory\n fs.mkdirSync(serverDir, { recursive: true });\n fs.mkdirSync(path.join(serverDir, \"output\"), { recursive: true });\n\n // Write server.ts\n fs.writeFileSync(\n path.join(serverDir, \"server.ts\"),\n SERVER_TEMPLATE,\n \"utf-8\"\n );\n\n // Write package.json\n fs.writeFileSync(\n path.join(serverDir, \"package.json\"),\n PACKAGE_JSON_TEMPLATE,\n \"utf-8\"\n );\n\n // Write tsconfig.json\n fs.writeFileSync(\n path.join(serverDir, \"tsconfig.json\"),\n TSCONFIG_TEMPLATE,\n \"utf-8\"\n );\n\n // Write .gitignore\n fs.writeFileSync(\n path.join(serverDir, \".gitignore\"),\n GITIGNORE_TEMPLATE,\n \"utf-8\"\n );\n\n console.log(`✅ Successfully scaffolded Twick render server!`);\n console.log(`\\n📁 Server created at: ${serverDir}`);\n console.log(`\\n📝 Next steps:`);\n console.log(` 1. cd ${path.basename(serverDir)}`);\n console.log(` 2. npm install`);\n console.log(` 3. npm run dev (for development)`);\n console.log(` 4. npm run build && npm start (for production)`);\n}\n\n// Parse command line arguments\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nif (command === \"init\") {\n scaffoldServer();\n} else {\n console.log(\"Usage: npx twick-render-server init\");\n console.log(\"\\nThis command scaffolds a new Twick render server in the current directory.\");\n process.exit(1);\n}\n\n"],"mappings":"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","names":[]}
@@ -17,5 +17,5 @@
17
17
  * ```
18
18
  */
19
19
  declare const renderTwickVideo: (variables: any, settings: any) => Promise<string>;
20
- export default renderTwickVideo;
21
- //# sourceMappingURL=renderer.d.ts.map
20
+
21
+ export { renderTwickVideo };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,21 @@
1
- export { default as renderTwickVideo } from "./renderer";
2
- //# sourceMappingURL=index.d.ts.map
1
+ /**
2
+ * Renders a Twick video with the provided variables and settings.
3
+ * Processes project variables, merges settings with defaults, and
4
+ * generates a video file using the Twick renderer.
5
+ *
6
+ * @param variables - Project variables containing input configuration
7
+ * @param settings - Optional render settings to override defaults
8
+ * @returns Promise resolving to the path of the rendered video file
9
+ *
10
+ * @example
11
+ * ```js
12
+ * const videoPath = await renderTwickVideo(
13
+ * { input: { properties: { width: 1920, height: 1080 } } },
14
+ * { quality: "high", outFile: "my-video.mp4" }
15
+ * );
16
+ * // videoPath = "./output/my-video.mp4"
17
+ * ```
18
+ */
19
+ declare const renderTwickVideo: (variables: any, settings: any) => Promise<string>;
20
+
21
+ export { renderTwickVideo };
package/dist/index.js CHANGED
@@ -1,3 +1,67 @@
1
- // Export the renderer function
2
- export { default as renderTwickVideo } from "./renderer";
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ renderTwickVideo: () => renderer_default
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/renderer.ts
28
+ var import_renderer = require("@twick/renderer");
29
+ var renderTwickVideo = async (variables, settings) => {
30
+ try {
31
+ const { input } = variables;
32
+ const { properties } = input;
33
+ const mergedSettings = {
34
+ logProgress: true,
35
+ outDir: "./output",
36
+ outFile: properties.reqesutId ?? `video-${Date.now()}.mp4`,
37
+ quality: "medium",
38
+ projectSettings: {
39
+ exporter: {
40
+ name: "@twick/core/wasm"
41
+ },
42
+ size: {
43
+ x: properties.width,
44
+ y: properties.height
45
+ }
46
+ },
47
+ ...settings
48
+ // Allow user settings to override defaults
49
+ };
50
+ const file = await (0, import_renderer.renderVideo)({
51
+ projectFile: "@twick/visualizer/dist/project.js",
52
+ variables,
53
+ settings: mergedSettings
54
+ });
55
+ console.log("Successfully rendered: ", file);
56
+ return file;
57
+ } catch (error) {
58
+ console.error("Render error:", error);
59
+ throw error;
60
+ }
61
+ };
62
+ var renderer_default = renderTwickVideo;
63
+ // Annotate the CommonJS export names for ESM import in node:
64
+ 0 && (module.exports = {
65
+ renderTwickVideo
66
+ });
3
67
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"sources":["../src/index.ts","../src/renderer.ts"],"sourcesContent":["// Export the renderer function\nexport { default as renderTwickVideo } from \"./renderer\";\n\n","import { renderVideo } from \"@twick/renderer\";\n\n/**\n * Renders a Twick video with the provided variables and settings.\n * Processes project variables, merges settings with defaults, and\n * generates a video file using the Twick renderer.\n *\n * @param variables - Project variables containing input configuration\n * @param settings - Optional render settings to override defaults\n * @returns Promise resolving to the path of the rendered video file\n * \n * @example\n * ```js\n * const videoPath = await renderTwickVideo(\n * { input: { properties: { width: 1920, height: 1080 } } },\n * { quality: \"high\", outFile: \"my-video.mp4\" }\n * );\n * // videoPath = \"./output/my-video.mp4\"\n * ```\n */\nconst renderTwickVideo = async (variables: any, settings: any) => {\n try {\n const { input } = variables;\n const { properties } = input;\n // Merge user settings with defaults\n const mergedSettings = {\n logProgress: true,\n outDir: \"./output\",\n outFile: properties.reqesutId ?? `video-${Date.now()}` + \".mp4\",\n quality: \"medium\",\n projectSettings: {\n exporter: {\n name: \"@twick/core/wasm\",\n },\n size: {\n x: properties.width,\n y: properties.height,\n },\n },\n ...settings, // Allow user settings to override defaults\n };\n\n const file = await renderVideo({\n projectFile: \"@twick/visualizer/dist/project.js\",\n variables: variables,\n settings: mergedSettings,\n });\n console.log(\"Successfully rendered: \", file);\n return file;\n } catch (error) {\n console.error(\"Render error:\", error);\n throw error;\n }\n};\n\nexport default renderTwickVideo;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,sBAA4B;AAoB5B,IAAM,mBAAmB,OAAO,WAAgB,aAAkB;AAChE,MAAI;AACF,UAAM,EAAE,MAAM,IAAI;AAClB,UAAM,EAAE,WAAW,IAAI;AAEvB,UAAM,iBAAiB;AAAA,MACrB,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,SAAS,WAAW,aAAa,SAAS,KAAK,IAAI,CAAC;AAAA,MACpD,SAAS;AAAA,MACT,iBAAiB;AAAA,QACf,UAAU;AAAA,UACR,MAAM;AAAA,QACR;AAAA,QACA,MAAM;AAAA,UACJ,GAAG,WAAW;AAAA,UACd,GAAG,WAAW;AAAA,QAChB;AAAA,MACF;AAAA,MACA,GAAG;AAAA;AAAA,IACL;AAEA,UAAM,OAAO,UAAM,6BAAY;AAAA,MAC7B,aAAa;AAAA,MACb;AAAA,MACA,UAAU;AAAA,IACZ,CAAC;AACD,YAAQ,IAAI,2BAA2B,IAAI;AAC3C,WAAO;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,MAAM,iBAAiB,KAAK;AACpC,UAAM;AAAA,EACR;AACF;AAEA,IAAO,mBAAQ;","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,40 @@
1
+ // src/renderer.ts
2
+ import { renderVideo } from "@twick/renderer";
3
+ var renderTwickVideo = async (variables, settings) => {
4
+ try {
5
+ const { input } = variables;
6
+ const { properties } = input;
7
+ const mergedSettings = {
8
+ logProgress: true,
9
+ outDir: "./output",
10
+ outFile: properties.reqesutId ?? `video-${Date.now()}.mp4`,
11
+ quality: "medium",
12
+ projectSettings: {
13
+ exporter: {
14
+ name: "@twick/core/wasm"
15
+ },
16
+ size: {
17
+ x: properties.width,
18
+ y: properties.height
19
+ }
20
+ },
21
+ ...settings
22
+ // Allow user settings to override defaults
23
+ };
24
+ const file = await renderVideo({
25
+ projectFile: "@twick/visualizer/dist/project.js",
26
+ variables,
27
+ settings: mergedSettings
28
+ });
29
+ console.log("Successfully rendered: ", file);
30
+ return file;
31
+ } catch (error) {
32
+ console.error("Render error:", error);
33
+ throw error;
34
+ }
35
+ };
36
+ var renderer_default = renderTwickVideo;
37
+ export {
38
+ renderer_default as renderTwickVideo
39
+ };
40
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/renderer.ts"],"sourcesContent":["import { renderVideo } from \"@twick/renderer\";\n\n/**\n * Renders a Twick video with the provided variables and settings.\n * Processes project variables, merges settings with defaults, and\n * generates a video file using the Twick renderer.\n *\n * @param variables - Project variables containing input configuration\n * @param settings - Optional render settings to override defaults\n * @returns Promise resolving to the path of the rendered video file\n * \n * @example\n * ```js\n * const videoPath = await renderTwickVideo(\n * { input: { properties: { width: 1920, height: 1080 } } },\n * { quality: \"high\", outFile: \"my-video.mp4\" }\n * );\n * // videoPath = \"./output/my-video.mp4\"\n * ```\n */\nconst renderTwickVideo = async (variables: any, settings: any) => {\n try {\n const { input } = variables;\n const { properties } = input;\n // Merge user settings with defaults\n const mergedSettings = {\n logProgress: true,\n outDir: \"./output\",\n outFile: properties.reqesutId ?? `video-${Date.now()}` + \".mp4\",\n quality: \"medium\",\n projectSettings: {\n exporter: {\n name: \"@twick/core/wasm\",\n },\n size: {\n x: properties.width,\n y: properties.height,\n },\n },\n ...settings, // Allow user settings to override defaults\n };\n\n const file = await renderVideo({\n projectFile: \"@twick/visualizer/dist/project.js\",\n variables: variables,\n settings: mergedSettings,\n });\n console.log(\"Successfully rendered: \", file);\n return file;\n } catch (error) {\n console.error(\"Render error:\", error);\n throw error;\n }\n};\n\nexport default renderTwickVideo;\n"],"mappings":";AAAA,SAAS,mBAAmB;AAoB5B,IAAM,mBAAmB,OAAO,WAAgB,aAAkB;AAChE,MAAI;AACF,UAAM,EAAE,MAAM,IAAI;AAClB,UAAM,EAAE,WAAW,IAAI;AAEvB,UAAM,iBAAiB;AAAA,MACrB,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,SAAS,WAAW,aAAa,SAAS,KAAK,IAAI,CAAC;AAAA,MACpD,SAAS;AAAA,MACT,iBAAiB;AAAA,QACf,UAAU;AAAA,UACR,MAAM;AAAA,QACR;AAAA,QACA,MAAM;AAAA,UACJ,GAAG,WAAW;AAAA,UACd,GAAG,WAAW;AAAA,QAChB;AAAA,MACF;AAAA,MACA,GAAG;AAAA;AAAA,IACL;AAEA,UAAM,OAAO,MAAM,YAAY;AAAA,MAC7B,aAAa;AAAA,MACb;AAAA,MACA,UAAU;AAAA,IACZ,CAAC;AACD,YAAQ,IAAI,2BAA2B,IAAI;AAC3C,WAAO;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,MAAM,iBAAiB,KAAK;AACpC,UAAM;AAAA,EACR;AACF;AAEA,IAAO,mBAAQ;","names":[]}
@@ -0,0 +1,147 @@
1
+ // src/server.ts
2
+ import express from "express";
3
+ import cors from "cors";
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+
7
+ // src/renderer.ts
8
+ import { renderVideo } from "@twick/renderer";
9
+ var renderTwickVideo = async (variables, settings) => {
10
+ try {
11
+ const { input } = variables;
12
+ const { properties } = input;
13
+ const mergedSettings = {
14
+ logProgress: true,
15
+ outDir: "./output",
16
+ outFile: properties.reqesutId ?? `video-${Date.now()}.mp4`,
17
+ quality: "medium",
18
+ projectSettings: {
19
+ exporter: {
20
+ name: "@twick/core/wasm"
21
+ },
22
+ size: {
23
+ x: properties.width,
24
+ y: properties.height
25
+ }
26
+ },
27
+ ...settings
28
+ // Allow user settings to override defaults
29
+ };
30
+ const file = await renderVideo({
31
+ projectFile: "@twick/visualizer/dist/project.js",
32
+ variables,
33
+ settings: mergedSettings
34
+ });
35
+ console.log("Successfully rendered: ", file);
36
+ return file;
37
+ } catch (error) {
38
+ console.error("Render error:", error);
39
+ throw error;
40
+ }
41
+ };
42
+ var renderer_default = renderTwickVideo;
43
+
44
+ // src/server.ts
45
+ var PORT = process.env.PORT || 3001;
46
+ var BASE_PATH = `http://localhost:${PORT}`;
47
+ var __filename = fileURLToPath(import.meta.url);
48
+ var __dirname = path.dirname(__filename);
49
+ var RATE_LIMIT_WINDOW_MS = 15 * 60 * 1e3;
50
+ var RATE_LIMIT_MAX_REQUESTS = 100;
51
+ var RATE_LIMIT_CLEANUP_INTERVAL_MS = 60 * 1e3;
52
+ var rateLimitStore = /* @__PURE__ */ new Map();
53
+ setInterval(() => {
54
+ const now = Date.now();
55
+ for (const [key, entry] of rateLimitStore.entries()) {
56
+ if (now > entry.resetTime) {
57
+ rateLimitStore.delete(key);
58
+ }
59
+ }
60
+ }, RATE_LIMIT_CLEANUP_INTERVAL_MS);
61
+ var rateLimitMiddleware = (req, res, next) => {
62
+ const clientIP = req.ip || req.connection.remoteAddress || "unknown";
63
+ const now = Date.now();
64
+ let entry = rateLimitStore.get(clientIP);
65
+ if (!entry || now > entry.resetTime) {
66
+ entry = {
67
+ count: 1,
68
+ resetTime: now + RATE_LIMIT_WINDOW_MS
69
+ };
70
+ rateLimitStore.set(clientIP, entry);
71
+ } else {
72
+ entry.count++;
73
+ if (entry.count > RATE_LIMIT_MAX_REQUESTS) {
74
+ const retryAfter = Math.ceil((entry.resetTime - now) / 1e3);
75
+ res.set("Retry-After", retryAfter.toString());
76
+ res.set("X-RateLimit-Limit", RATE_LIMIT_MAX_REQUESTS.toString());
77
+ res.set("X-RateLimit-Remaining", "0");
78
+ res.set("X-RateLimit-Reset", new Date(entry.resetTime).toISOString());
79
+ return res.status(429).json({
80
+ success: false,
81
+ error: "Too many requests",
82
+ message: `Rate limit exceeded. Try again in ${retryAfter} seconds.`,
83
+ retryAfter
84
+ });
85
+ }
86
+ }
87
+ res.set("X-RateLimit-Limit", RATE_LIMIT_MAX_REQUESTS.toString());
88
+ res.set("X-RateLimit-Remaining", (RATE_LIMIT_MAX_REQUESTS - entry.count).toString());
89
+ res.set("X-RateLimit-Reset", new Date(entry.resetTime).toISOString());
90
+ next();
91
+ };
92
+ var nodeApp = express();
93
+ nodeApp.use(cors());
94
+ nodeApp.use(express.json());
95
+ nodeApp.use("/output", express.static(path.join(__dirname, "../output")));
96
+ nodeApp.post("/api/render-video", async (req, res) => {
97
+ const { variables, settings } = req.body;
98
+ try {
99
+ const outputPath = await renderer_default(variables, settings);
100
+ res.json({
101
+ success: true,
102
+ downloadUrl: `${BASE_PATH}/download/${path.basename(outputPath)}`
103
+ });
104
+ } catch (error) {
105
+ console.error("Render error:", error);
106
+ res.status(500).json({
107
+ success: false,
108
+ error: error instanceof Error ? error.message : "Unknown error"
109
+ });
110
+ }
111
+ });
112
+ nodeApp.get("/download/:filename", rateLimitMiddleware, (req, res) => {
113
+ const outputDir = path.resolve(__dirname, "../output");
114
+ const requestedPath = path.resolve(outputDir, req.params.filename);
115
+ if (!requestedPath.startsWith(outputDir + path.sep)) {
116
+ res.status(403).json({
117
+ success: false,
118
+ error: "Forbidden"
119
+ });
120
+ return;
121
+ }
122
+ res.download(requestedPath, (err) => {
123
+ if (err) {
124
+ res.status(404).json({
125
+ success: false,
126
+ error: "File not found"
127
+ });
128
+ }
129
+ });
130
+ });
131
+ nodeApp.get("/health", (req, res) => {
132
+ res.json({
133
+ status: "ok",
134
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
135
+ });
136
+ });
137
+ var server_default = nodeApp;
138
+ nodeApp.listen(PORT, () => {
139
+ console.log(`Render server running on port ${PORT}`);
140
+ console.log(`Health check: ${BASE_PATH}/health`);
141
+ console.log(`API endpoint: ${BASE_PATH}/api/render-video`);
142
+ console.log(`Download endpoint rate limited: ${RATE_LIMIT_MAX_REQUESTS} requests per ${RATE_LIMIT_WINDOW_MS / 6e4} minutes`);
143
+ });
144
+ export {
145
+ server_default as default
146
+ };
147
+ //# sourceMappingURL=server.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/renderer.ts"],"sourcesContent":["import express from \"express\";\nimport cors from \"cors\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport renderTwickVideo from \"./renderer\";\n\nconst PORT = process.env.PORT || 3001;\nconst BASE_PATH = `http://localhost:${PORT}`;\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Rate limiting configuration\nconst RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000; // 15 minutes\nconst RATE_LIMIT_MAX_REQUESTS = 100; // Maximum requests per window\nconst RATE_LIMIT_CLEANUP_INTERVAL_MS = 60 * 1000; // Cleanup every minute\n\n// In-memory store for rate limiting\ninterface RateLimitEntry {\n count: number;\n resetTime: number;\n}\n\nconst rateLimitStore = new Map<string, RateLimitEntry>();\n\n// Cleanup expired entries periodically\nsetInterval(() => {\n const now = Date.now();\n for (const [key, entry] of rateLimitStore.entries()) {\n if (now > entry.resetTime) {\n rateLimitStore.delete(key);\n }\n }\n}, RATE_LIMIT_CLEANUP_INTERVAL_MS);\n\n/**\n * Rate limiting middleware for API endpoints.\n * Tracks request counts per IP address and enforces rate limits\n * to prevent abuse of the render server.\n *\n * @param req - Express request object\n * @param res - Express response object\n * @param next - Express next function\n * \n * @example\n * ```js\n * app.use('/api', rateLimitMiddleware);\n * // Applies rate limiting to all /api routes\n * ```\n */\nconst rateLimitMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction) => {\n const clientIP = req.ip || req.connection.remoteAddress || 'unknown';\n const now = Date.now();\n \n // Get or create rate limit entry for this IP\n let entry = rateLimitStore.get(clientIP);\n \n if (!entry || now > entry.resetTime) {\n // New window or expired entry\n entry = {\n count: 1,\n resetTime: now + RATE_LIMIT_WINDOW_MS\n };\n rateLimitStore.set(clientIP, entry);\n } else {\n // Increment count in current window\n entry.count++;\n \n if (entry.count > RATE_LIMIT_MAX_REQUESTS) {\n // Rate limit exceeded\n const retryAfter = Math.ceil((entry.resetTime - now) / 1000);\n res.set('Retry-After', retryAfter.toString());\n res.set('X-RateLimit-Limit', RATE_LIMIT_MAX_REQUESTS.toString());\n res.set('X-RateLimit-Remaining', '0');\n res.set('X-RateLimit-Reset', new Date(entry.resetTime).toISOString());\n \n return res.status(429).json({\n success: false,\n error: 'Too many requests',\n message: `Rate limit exceeded. Try again in ${retryAfter} seconds.`,\n retryAfter\n });\n }\n }\n \n // Add rate limit headers\n res.set('X-RateLimit-Limit', RATE_LIMIT_MAX_REQUESTS.toString());\n res.set('X-RateLimit-Remaining', (RATE_LIMIT_MAX_REQUESTS - entry.count).toString());\n res.set('X-RateLimit-Reset', new Date(entry.resetTime).toISOString());\n \n next();\n};\n\nconst nodeApp: import(\"express\").Express = express();\n\nnodeApp.use(cors());\nnodeApp.use(express.json());\n\n// Serve static files from output directory\nnodeApp.use(\"/output\", express.static(path.join(__dirname, \"../output\")));\n\n/**\n * POST endpoint for video rendering requests.\n * Accepts project variables and settings, renders the video,\n * and returns a download URL for the completed video.\n *\n * @param req - Express request object containing variables and settings\n * @param res - Express response object\n * \n * @example\n * ```js\n * POST /api/render-video\n * Body: { variables: {...}, settings: {...} }\n * Response: { success: true, downloadUrl: \"...\" }\n * ```\n */\nnodeApp.post(\"/api/render-video\", async (req, res) => {\n const { variables, settings } = req.body;\n\n try {\n const outputPath = await renderTwickVideo(variables, settings);\n res.json({\n success: true,\n downloadUrl: `${BASE_PATH}/download/${path.basename(outputPath)}`,\n });\n } catch (error) {\n console.error(\"Render error:\", error);\n res.status(500).json({\n success: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n});\n\n/**\n * GET endpoint for downloading rendered videos.\n * Serves video files with rate limiting and security checks\n * to prevent path traversal attacks.\n *\n * @param req - Express request object with filename parameter\n * @param res - Express response object\n * \n * @example\n * ```js\n * GET /download/video-123.mp4\n * // Downloads the specified video file\n * ```\n */\nnodeApp.get(\"/download/:filename\", rateLimitMiddleware, (req, res) => {\n const outputDir = path.resolve(__dirname, \"../output\");\n const requestedPath = path.resolve(outputDir, req.params.filename);\n if (!requestedPath.startsWith(outputDir + path.sep)) {\n // Attempted path traversal or access outside output directory\n res.status(403).json({\n success: false,\n error: \"Forbidden\",\n });\n return;\n }\n res.download(requestedPath, (err) => {\n if (err) {\n res.status(404).json({\n success: false,\n error: \"File not found\",\n });\n }\n });\n});\n\n/**\n * Health check endpoint for monitoring server status.\n * Returns server status and current timestamp for health monitoring.\n *\n * @param req - Express request object\n * @param res - Express response object\n * \n * @example\n * ```js\n * GET /health\n * Response: { status: \"ok\", timestamp: \"2024-01-01T00:00:00.000Z\" }\n * ```\n */\nnodeApp.get(\"/health\", (req, res) => {\n res.json({\n status: \"ok\",\n timestamp: new Date().toISOString(),\n });\n});\n\n// Export the app for programmatic usage\nexport default nodeApp;\n\n// Start the server\nnodeApp.listen(PORT, () => {\n console.log(`Render server running on port ${PORT}`);\n console.log(`Health check: ${BASE_PATH}/health`);\n console.log(`API endpoint: ${BASE_PATH}/api/render-video`);\n console.log(`Download endpoint rate limited: ${RATE_LIMIT_MAX_REQUESTS} requests per ${RATE_LIMIT_WINDOW_MS / 60000} minutes`);\n});\n","import { renderVideo } from \"@twick/renderer\";\n\n/**\n * Renders a Twick video with the provided variables and settings.\n * Processes project variables, merges settings with defaults, and\n * generates a video file using the Twick renderer.\n *\n * @param variables - Project variables containing input configuration\n * @param settings - Optional render settings to override defaults\n * @returns Promise resolving to the path of the rendered video file\n * \n * @example\n * ```js\n * const videoPath = await renderTwickVideo(\n * { input: { properties: { width: 1920, height: 1080 } } },\n * { quality: \"high\", outFile: \"my-video.mp4\" }\n * );\n * // videoPath = \"./output/my-video.mp4\"\n * ```\n */\nconst renderTwickVideo = async (variables: any, settings: any) => {\n try {\n const { input } = variables;\n const { properties } = input;\n // Merge user settings with defaults\n const mergedSettings = {\n logProgress: true,\n outDir: \"./output\",\n outFile: properties.reqesutId ?? `video-${Date.now()}` + \".mp4\",\n quality: \"medium\",\n projectSettings: {\n exporter: {\n name: \"@twick/core/wasm\",\n },\n size: {\n x: properties.width,\n y: properties.height,\n },\n },\n ...settings, // Allow user settings to override defaults\n };\n\n const file = await renderVideo({\n projectFile: \"@twick/visualizer/dist/project.js\",\n variables: variables,\n settings: mergedSettings,\n });\n console.log(\"Successfully rendered: \", file);\n return file;\n } catch (error) {\n console.error(\"Render error:\", error);\n throw error;\n }\n};\n\nexport default renderTwickVideo;\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,28 +1,37 @@
1
1
  {
2
2
  "name": "@twick/render-server",
3
- "version": "0.14.20",
3
+ "version": "0.15.1",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
- "type": "module",
6
- "main": "dist/index.js",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
7
15
  "bin": {
8
16
  "twick-render-server": "./dist/cli.js"
9
17
  },
10
18
  "scripts": {
11
- "build": "tsc",
19
+ "build": "tsup",
12
20
  "dev": "tsx watch src/server.ts",
13
21
  "start": "node dist/server.js",
22
+ "test:render": "tsx src/test-render.ts",
14
23
  "clean": "rimraf dist"
15
24
  },
16
25
  "publishConfig": {
17
26
  "access": "public"
18
27
  },
19
28
  "dependencies": {
20
- "@twick/2d": "^0.14.20",
21
- "@twick/core": "^0.14.20",
22
- "@twick/ffmpeg": "^0.14.20",
23
- "@twick/renderer": "^0.14.20",
24
- "@twick/ui": "^0.14.20",
25
- "@twick/visualizer": "0.14.20",
29
+ "@twick/2d": "^0.15.1",
30
+ "@twick/core": "^0.15.1",
31
+ "@twick/ffmpeg": "^0.15.1",
32
+ "@twick/renderer": "^0.15.1",
33
+ "@twick/ui": "^0.15.1",
34
+ "@twick/visualizer": "^0.15.1",
26
35
  "cors": "^2.8.5",
27
36
  "express": "^4.18.2",
28
37
  "express-rate-limit": "^8.0.1",
@@ -34,10 +43,11 @@
34
43
  "@types/express": "^4.17.21",
35
44
  "@types/node": "^20.10.0",
36
45
  "rimraf": "^5.0.5",
46
+ "tsup": "^8.0.0",
37
47
  "tsx": "^4.7.0",
38
48
  "typescript": "5.4.2"
39
49
  },
40
50
  "engines": {
41
- "node": ">=18.0.0"
51
+ "node": ">=20.0.0"
42
52
  }
43
53
  }
package/package.json.bak CHANGED
@@ -1,28 +1,37 @@
1
1
  {
2
2
  "name": "@twick/render-server",
3
- "version": "0.14.20",
3
+ "version": "0.15.1",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
- "type": "module",
6
- "main": "dist/index.js",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
7
15
  "bin": {
8
16
  "twick-render-server": "./dist/cli.js"
9
17
  },
10
18
  "scripts": {
11
- "build": "tsc",
19
+ "build": "tsup",
12
20
  "dev": "tsx watch src/server.ts",
13
21
  "start": "node dist/server.js",
22
+ "test:render": "tsx src/test-render.ts",
14
23
  "clean": "rimraf dist"
15
24
  },
16
25
  "publishConfig": {
17
26
  "access": "public"
18
27
  },
19
28
  "dependencies": {
20
- "@twick/2d": "^0.14.20",
21
- "@twick/core": "^0.14.20",
22
- "@twick/ffmpeg": "^0.14.20",
23
- "@twick/renderer": "^0.14.20",
24
- "@twick/ui": "^0.14.20",
25
- "@twick/visualizer": "0.14.20",
29
+ "@twick/2d": "^0.15.1",
30
+ "@twick/core": "^0.15.1",
31
+ "@twick/ffmpeg": "^0.15.1",
32
+ "@twick/renderer": "^0.15.1",
33
+ "@twick/ui": "^0.15.1",
34
+ "@twick/visualizer": "^0.15.1",
26
35
  "cors": "^2.8.5",
27
36
  "express": "^4.18.2",
28
37
  "express-rate-limit": "^8.0.1",
@@ -34,10 +43,11 @@
34
43
  "@types/express": "^4.17.21",
35
44
  "@types/node": "^20.10.0",
36
45
  "rimraf": "^5.0.5",
46
+ "tsup": "^8.0.0",
37
47
  "tsx": "^4.7.0",
38
48
  "typescript": "5.4.2"
39
49
  },
40
50
  "engines": {
41
- "node": ">=18.0.0"
51
+ "node": ">=20.0.0"
42
52
  }
43
53
  }
package/src/server.ts CHANGED
@@ -2,7 +2,7 @@ import express from "express";
2
2
  import cors from "cors";
3
3
  import path from "path";
4
4
  import { fileURLToPath } from "url";
5
- import renderTwickVideo from "./renderer.js";
5
+ import renderTwickVideo from "./renderer";
6
6
 
7
7
  const PORT = process.env.PORT || 3001;
8
8
  const BASE_PATH = `http://localhost:${PORT}`;
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Test file for rendering a video with an example project
3
+ *
4
+ * This script demonstrates how to use the renderTwickVideo function
5
+ * to create a video from a Twick project configuration.
6
+ *
7
+ * Run with: tsx src/test-render.ts
8
+ */
9
+
10
+ import { renderTwickVideo } from "./index.js";
11
+ import { fileURLToPath } from "url";
12
+ import { dirname, join } from "path";
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+
17
+ /**
18
+ * Example project configuration
19
+ * This creates a simple video with:
20
+ * - A colored background rectangle
21
+ * - Animated text elements
22
+ * - Audio track
23
+ */
24
+ const exampleProject = {
25
+ input: {
26
+ properties: {
27
+ width: 1920,
28
+ height: 1080,
29
+ fps: 30,
30
+ },
31
+ tracks: [
32
+ {
33
+ id: "t-background",
34
+ type: "element",
35
+ name: "background",
36
+ elements: [
37
+ {
38
+ id: "e-bg-1",
39
+ trackId: "t-background",
40
+ type: "rect",
41
+ s: 0,
42
+ e: 10,
43
+ props: {
44
+ width: 1920,
45
+ height: 1080,
46
+ fill: "#1a1a2e",
47
+ },
48
+ },
49
+ ],
50
+ },
51
+ {
52
+ id: "t-text",
53
+ type: "element",
54
+ name: "text",
55
+ elements: [
56
+ {
57
+ id: "e-text-1",
58
+ trackId: "t-text",
59
+ type: "text",
60
+ s: 1,
61
+ e: 4,
62
+ t: "Welcome to Twick!",
63
+ props: {
64
+ fill: "#ffffff",
65
+ fontSize: 72,
66
+ fontFamily: "Arial",
67
+ fontWeight: "bold",
68
+ x: 0,
69
+ y: -200,
70
+ },
71
+ animation: {
72
+ name: "fade",
73
+ animate: "enter",
74
+ duration: 1,
75
+ },
76
+ textEffect: {
77
+ name: "typewriter",
78
+ duration: 2,
79
+ },
80
+ },
81
+ {
82
+ id: "e-text-2",
83
+ trackId: "t-text",
84
+ type: "text",
85
+ s: 4,
86
+ e: 7,
87
+ t: "Create Amazing Videos",
88
+ props: {
89
+ fill: "#4ecdc4",
90
+ fontSize: 64,
91
+ fontFamily: "Arial",
92
+ fontWeight: "bold",
93
+ x: 0,
94
+ y: 0,
95
+ },
96
+ animation: {
97
+ name: "rise",
98
+ animate: "enter",
99
+ duration: 1,
100
+ },
101
+ },
102
+ {
103
+ id: "e-text-3",
104
+ trackId: "t-text",
105
+ type: "text",
106
+ s: 7,
107
+ e: 10,
108
+ t: "Programmatically!",
109
+ props: {
110
+ fill: "#ffd700",
111
+ fontSize: 80,
112
+ fontFamily: "Arial",
113
+ fontWeight: "bold",
114
+ x: 0,
115
+ y: 200,
116
+ },
117
+ animation: {
118
+ name: "fade",
119
+ animate: "enter",
120
+ duration: 1,
121
+ },
122
+ },
123
+ ],
124
+ },
125
+ {
126
+ id: "t-shapes",
127
+ type: "element",
128
+ name: "shapes",
129
+ elements: [
130
+ {
131
+ id: "e-circle-1",
132
+ trackId: "t-shapes",
133
+ type: "circle",
134
+ s: 2,
135
+ e: 8,
136
+ props: {
137
+ width: 200,
138
+ height: 200,
139
+ fill: "#ff6b6b",
140
+ x: -400,
141
+ y: 0,
142
+ },
143
+ animation: {
144
+ name: "fade",
145
+ animate: "enter",
146
+ duration: 1,
147
+ },
148
+ },
149
+ {
150
+ id: "e-rect-1",
151
+ trackId: "t-shapes",
152
+ type: "rect",
153
+ s: 5,
154
+ e: 10,
155
+ props: {
156
+ width: 300,
157
+ height: 150,
158
+ fill: "#4ecdc4",
159
+ x: 400,
160
+ y: 0,
161
+ },
162
+ animation: {
163
+ name: "rise",
164
+ animate: "enter",
165
+ duration: 1,
166
+ },
167
+ },
168
+ ],
169
+ },
170
+ ],
171
+ version: 1,
172
+ },
173
+ };
174
+
175
+ /**
176
+ * Render settings
177
+ */
178
+ const renderSettings = {
179
+ outFile: `test-video-${Date.now()}.mp4`,
180
+ outDir: join(__dirname, "../output"),
181
+ quality: "medium",
182
+ logProgress: true,
183
+ };
184
+
185
+ /**
186
+ * Main test function
187
+ */
188
+ async function testVideoRender() {
189
+ console.log("🎬 Starting video render test...\n");
190
+ console.log("📋 Project Configuration:");
191
+ console.log(` Resolution: ${exampleProject.input.properties.width}x${exampleProject.input.properties.height}`);
192
+ console.log(` FPS: ${exampleProject.input.properties.fps}`);
193
+ console.log(` Tracks: ${exampleProject.input.tracks.length}`);
194
+ console.log(` Total Elements: ${exampleProject.input.tracks.reduce((sum, track) => sum + track.elements.length, 0)}\n`);
195
+
196
+ try {
197
+ console.log("🔄 Rendering video...");
198
+ const startTime = Date.now();
199
+
200
+ const outputPath = await renderTwickVideo(exampleProject, renderSettings);
201
+
202
+ const endTime = Date.now();
203
+ const duration = ((endTime - startTime) / 1000).toFixed(2);
204
+
205
+ console.log("\n✅ Video rendered successfully!");
206
+ console.log(`📁 Output file: ${outputPath}`);
207
+ console.log(`⏱️ Render time: ${duration}s`);
208
+ console.log(`\n🎉 Test completed successfully!`);
209
+
210
+ return outputPath;
211
+ } catch (error) {
212
+ console.error("\n❌ Render failed:");
213
+ console.error(error);
214
+ if (error instanceof Error) {
215
+ console.error(` Error: ${error.message}`);
216
+ if (error.stack) {
217
+ console.error(` Stack: ${error.stack}`);
218
+ }
219
+ }
220
+ process.exit(1);
221
+ }
222
+ }
223
+
224
+ // Run the test
225
+ testVideoRender().catch((error) => {
226
+ console.error("Fatal error:", error);
227
+ process.exit(1);
228
+ });
package/tsconfig.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
6
  "allowSyntheticDefaultImports": true,
7
7
  "esModuleInterop": true,
8
8
  "allowJs": true,
@@ -18,5 +18,5 @@
18
18
  "noEmit": false
19
19
  },
20
20
  "include": ["src/**/*"],
21
- "exclude": ["node_modules", "dist"]
21
+ "exclude": ["node_modules", "dist", "src/**/*.test.ts", "src/test-render.ts"]
22
22
  }
package/tsup.config.ts ADDED
@@ -0,0 +1,49 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig([
4
+ // Build index.ts in both CJS and ESM for library exports
5
+ {
6
+ entry: ['src/index.ts'],
7
+ format: ['cjs', 'esm'],
8
+ dts: true,
9
+ splitting: false,
10
+ sourcemap: true,
11
+ clean: true,
12
+ outDir: 'dist',
13
+ external: [
14
+ '@twick/2d',
15
+ '@twick/core',
16
+ '@twick/ffmpeg',
17
+ '@twick/renderer',
18
+ '@twick/ui',
19
+ '@twick/visualizer',
20
+ 'cors',
21
+ 'express',
22
+ 'express-rate-limit',
23
+ 'node-fetch',
24
+ 'path'
25
+ ],
26
+ },
27
+ // Build cli.ts and server.ts only as ESM (they use import.meta)
28
+ {
29
+ entry: ['src/cli.ts', 'src/server.ts'],
30
+ format: ['esm'],
31
+ dts: false, // No need for declarations for CLI/server files
32
+ splitting: false,
33
+ sourcemap: true,
34
+ outDir: 'dist',
35
+ external: [
36
+ '@twick/2d',
37
+ '@twick/core',
38
+ '@twick/ffmpeg',
39
+ '@twick/renderer',
40
+ '@twick/ui',
41
+ '@twick/visualizer',
42
+ 'cors',
43
+ 'express',
44
+ 'express-rate-limit',
45
+ 'node-fetch',
46
+ 'path'
47
+ ],
48
+ },
49
+ ]);
package/dist/cli.d.ts DELETED
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
3
- //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,gBAAgB,GAAU,WAAW,GAAG,EAAE,UAAU,GAAG,oBAiC5D,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
package/dist/renderer.js DELETED
@@ -1,55 +0,0 @@
1
- import { renderVideo } from "@twick/renderer";
2
- /**
3
- * Renders a Twick video with the provided variables and settings.
4
- * Processes project variables, merges settings with defaults, and
5
- * generates a video file using the Twick renderer.
6
- *
7
- * @param variables - Project variables containing input configuration
8
- * @param settings - Optional render settings to override defaults
9
- * @returns Promise resolving to the path of the rendered video file
10
- *
11
- * @example
12
- * ```js
13
- * const videoPath = await renderTwickVideo(
14
- * { input: { properties: { width: 1920, height: 1080 } } },
15
- * { quality: "high", outFile: "my-video.mp4" }
16
- * );
17
- * // videoPath = "./output/my-video.mp4"
18
- * ```
19
- */
20
- const renderTwickVideo = async (variables, settings) => {
21
- try {
22
- const { input } = variables;
23
- const { properties } = input;
24
- // Merge user settings with defaults
25
- const mergedSettings = {
26
- logProgress: true,
27
- outDir: "./output",
28
- outFile: properties.reqesutId ?? `video-${Date.now()}` + ".mp4",
29
- quality: "medium",
30
- projectSettings: {
31
- exporter: {
32
- name: "@twick/core/wasm",
33
- },
34
- size: {
35
- x: properties.width,
36
- y: properties.height,
37
- },
38
- },
39
- ...settings, // Allow user settings to override defaults
40
- };
41
- const file = await renderVideo({
42
- projectFile: "@twick/visualizer/dist/project.js",
43
- variables: variables,
44
- settings: mergedSettings,
45
- });
46
- console.log("Successfully rendered: ", file);
47
- return file;
48
- }
49
- catch (error) {
50
- console.error("Render error:", error);
51
- throw error;
52
- }
53
- };
54
- export default renderTwickVideo;
55
- //# sourceMappingURL=renderer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,gBAAgB,GAAG,KAAK,EAAE,SAAc,EAAE,QAAa,EAAE,EAAE;IAC/D,IAAI,CAAC;QACH,MAAM,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC;QAC5B,MAAM,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;QAC7B,oCAAoC;QACpC,MAAM,cAAc,GAAG;YACrB,WAAW,EAAE,IAAI;YACjB,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,UAAU,CAAC,SAAS,IAAI,SAAS,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,MAAM;YAC/D,OAAO,EAAE,QAAQ;YACjB,eAAe,EAAE;gBACf,QAAQ,EAAE;oBACR,IAAI,EAAE,kBAAkB;iBACzB;gBACD,IAAI,EAAE;oBACJ,CAAC,EAAE,UAAU,CAAC,KAAK;oBACnB,CAAC,EAAE,UAAU,CAAC,MAAM;iBACrB;aACF;YACD,GAAG,QAAQ,EAAE,2CAA2C;SACzD,CAAC;QAEF,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC;YAC7B,WAAW,EAAE,mCAAmC;YAChD,SAAS,EAAE,SAAS;YACpB,QAAQ,EAAE,cAAc;SACzB,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,yBAAyB,EAAE,IAAI,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;QACtC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
package/dist/server.d.ts DELETED
@@ -1,3 +0,0 @@
1
- declare const nodeApp: import("express").Express;
2
- export default nodeApp;
3
- //# sourceMappingURL=server.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AA6FA,QAAA,MAAM,OAAO,EAAE,OAAO,SAAS,EAAE,OAAmB,CAAC;AAiGrD,eAAe,OAAO,CAAC"}
package/dist/server.js DELETED
@@ -1,175 +0,0 @@
1
- import express from "express";
2
- import cors from "cors";
3
- import path from "path";
4
- import { fileURLToPath } from "url";
5
- import renderTwickVideo from "./renderer.js";
6
- const PORT = process.env.PORT || 3001;
7
- const BASE_PATH = `http://localhost:${PORT}`;
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
- // Rate limiting configuration
11
- const RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000; // 15 minutes
12
- const RATE_LIMIT_MAX_REQUESTS = 100; // Maximum requests per window
13
- const RATE_LIMIT_CLEANUP_INTERVAL_MS = 60 * 1000; // Cleanup every minute
14
- const rateLimitStore = new Map();
15
- // Cleanup expired entries periodically
16
- setInterval(() => {
17
- const now = Date.now();
18
- for (const [key, entry] of rateLimitStore.entries()) {
19
- if (now > entry.resetTime) {
20
- rateLimitStore.delete(key);
21
- }
22
- }
23
- }, RATE_LIMIT_CLEANUP_INTERVAL_MS);
24
- /**
25
- * Rate limiting middleware for API endpoints.
26
- * Tracks request counts per IP address and enforces rate limits
27
- * to prevent abuse of the render server.
28
- *
29
- * @param req - Express request object
30
- * @param res - Express response object
31
- * @param next - Express next function
32
- *
33
- * @example
34
- * ```js
35
- * app.use('/api', rateLimitMiddleware);
36
- * // Applies rate limiting to all /api routes
37
- * ```
38
- */
39
- const rateLimitMiddleware = (req, res, next) => {
40
- const clientIP = req.ip || req.connection.remoteAddress || 'unknown';
41
- const now = Date.now();
42
- // Get or create rate limit entry for this IP
43
- let entry = rateLimitStore.get(clientIP);
44
- if (!entry || now > entry.resetTime) {
45
- // New window or expired entry
46
- entry = {
47
- count: 1,
48
- resetTime: now + RATE_LIMIT_WINDOW_MS
49
- };
50
- rateLimitStore.set(clientIP, entry);
51
- }
52
- else {
53
- // Increment count in current window
54
- entry.count++;
55
- if (entry.count > RATE_LIMIT_MAX_REQUESTS) {
56
- // Rate limit exceeded
57
- const retryAfter = Math.ceil((entry.resetTime - now) / 1000);
58
- res.set('Retry-After', retryAfter.toString());
59
- res.set('X-RateLimit-Limit', RATE_LIMIT_MAX_REQUESTS.toString());
60
- res.set('X-RateLimit-Remaining', '0');
61
- res.set('X-RateLimit-Reset', new Date(entry.resetTime).toISOString());
62
- return res.status(429).json({
63
- success: false,
64
- error: 'Too many requests',
65
- message: `Rate limit exceeded. Try again in ${retryAfter} seconds.`,
66
- retryAfter
67
- });
68
- }
69
- }
70
- // Add rate limit headers
71
- res.set('X-RateLimit-Limit', RATE_LIMIT_MAX_REQUESTS.toString());
72
- res.set('X-RateLimit-Remaining', (RATE_LIMIT_MAX_REQUESTS - entry.count).toString());
73
- res.set('X-RateLimit-Reset', new Date(entry.resetTime).toISOString());
74
- next();
75
- };
76
- const nodeApp = express();
77
- nodeApp.use(cors());
78
- nodeApp.use(express.json());
79
- // Serve static files from output directory
80
- nodeApp.use("/output", express.static(path.join(__dirname, "../output")));
81
- /**
82
- * POST endpoint for video rendering requests.
83
- * Accepts project variables and settings, renders the video,
84
- * and returns a download URL for the completed video.
85
- *
86
- * @param req - Express request object containing variables and settings
87
- * @param res - Express response object
88
- *
89
- * @example
90
- * ```js
91
- * POST /api/render-video
92
- * Body: { variables: {...}, settings: {...} }
93
- * Response: { success: true, downloadUrl: "..." }
94
- * ```
95
- */
96
- nodeApp.post("/api/render-video", async (req, res) => {
97
- const { variables, settings } = req.body;
98
- try {
99
- const outputPath = await renderTwickVideo(variables, settings);
100
- res.json({
101
- success: true,
102
- downloadUrl: `${BASE_PATH}/download/${path.basename(outputPath)}`,
103
- });
104
- }
105
- catch (error) {
106
- console.error("Render error:", error);
107
- res.status(500).json({
108
- success: false,
109
- error: error instanceof Error ? error.message : "Unknown error",
110
- });
111
- }
112
- });
113
- /**
114
- * GET endpoint for downloading rendered videos.
115
- * Serves video files with rate limiting and security checks
116
- * to prevent path traversal attacks.
117
- *
118
- * @param req - Express request object with filename parameter
119
- * @param res - Express response object
120
- *
121
- * @example
122
- * ```js
123
- * GET /download/video-123.mp4
124
- * // Downloads the specified video file
125
- * ```
126
- */
127
- nodeApp.get("/download/:filename", rateLimitMiddleware, (req, res) => {
128
- const outputDir = path.resolve(__dirname, "../output");
129
- const requestedPath = path.resolve(outputDir, req.params.filename);
130
- if (!requestedPath.startsWith(outputDir + path.sep)) {
131
- // Attempted path traversal or access outside output directory
132
- res.status(403).json({
133
- success: false,
134
- error: "Forbidden",
135
- });
136
- return;
137
- }
138
- res.download(requestedPath, (err) => {
139
- if (err) {
140
- res.status(404).json({
141
- success: false,
142
- error: "File not found",
143
- });
144
- }
145
- });
146
- });
147
- /**
148
- * Health check endpoint for monitoring server status.
149
- * Returns server status and current timestamp for health monitoring.
150
- *
151
- * @param req - Express request object
152
- * @param res - Express response object
153
- *
154
- * @example
155
- * ```js
156
- * GET /health
157
- * Response: { status: "ok", timestamp: "2024-01-01T00:00:00.000Z" }
158
- * ```
159
- */
160
- nodeApp.get("/health", (req, res) => {
161
- res.json({
162
- status: "ok",
163
- timestamp: new Date().toISOString(),
164
- });
165
- });
166
- // Export the app for programmatic usage
167
- export default nodeApp;
168
- // Start the server
169
- nodeApp.listen(PORT, () => {
170
- console.log(`Render server running on port ${PORT}`);
171
- console.log(`Health check: ${BASE_PATH}/health`);
172
- console.log(`API endpoint: ${BASE_PATH}/api/render-video`);
173
- console.log(`Download endpoint rate limited: ${RATE_LIMIT_MAX_REQUESTS} requests per ${RATE_LIMIT_WINDOW_MS / 60000} minutes`);
174
- });
175
- //# sourceMappingURL=server.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}