@vertesia/workflow 0.56.0 → 0.58.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.
Files changed (64) hide show
  1. package/lib/cjs/activities/generateEmbeddings.js +158 -61
  2. package/lib/cjs/activities/generateEmbeddings.js.map +1 -1
  3. package/lib/cjs/activities/generateOrAssignContentType.js +19 -8
  4. package/lib/cjs/activities/generateOrAssignContentType.js.map +1 -1
  5. package/lib/cjs/activities/index-dsl.js +4 -2
  6. package/lib/cjs/activities/index-dsl.js.map +1 -1
  7. package/lib/cjs/activities/renditions/generateImageRendition.js +57 -0
  8. package/lib/cjs/activities/renditions/generateImageRendition.js.map +1 -0
  9. package/lib/cjs/activities/renditions/generateVideoRendition.js +196 -0
  10. package/lib/cjs/activities/renditions/generateVideoRendition.js.map +1 -0
  11. package/lib/cjs/dsl/dsl-workflow.js +8 -0
  12. package/lib/cjs/dsl/dsl-workflow.js.map +1 -1
  13. package/lib/cjs/index.js +3 -1
  14. package/lib/cjs/index.js.map +1 -1
  15. package/lib/cjs/utils/renditions.js +88 -0
  16. package/lib/cjs/utils/renditions.js.map +1 -0
  17. package/lib/esm/activities/generateEmbeddings.js +160 -63
  18. package/lib/esm/activities/generateEmbeddings.js.map +1 -1
  19. package/lib/esm/activities/generateOrAssignContentType.js +21 -10
  20. package/lib/esm/activities/generateOrAssignContentType.js.map +1 -1
  21. package/lib/esm/activities/index-dsl.js +2 -1
  22. package/lib/esm/activities/index-dsl.js.map +1 -1
  23. package/lib/esm/activities/renditions/generateImageRendition.js +54 -0
  24. package/lib/esm/activities/renditions/generateImageRendition.js.map +1 -0
  25. package/lib/esm/activities/renditions/generateVideoRendition.js +190 -0
  26. package/lib/esm/activities/renditions/generateVideoRendition.js.map +1 -0
  27. package/lib/esm/dsl/dsl-workflow.js +8 -0
  28. package/lib/esm/dsl/dsl-workflow.js.map +1 -1
  29. package/lib/esm/index.js +3 -1
  30. package/lib/esm/index.js.map +1 -1
  31. package/lib/esm/utils/renditions.js +80 -0
  32. package/lib/esm/utils/renditions.js.map +1 -0
  33. package/lib/types/activities/generateEmbeddings.d.ts +1 -1
  34. package/lib/types/activities/generateEmbeddings.d.ts.map +1 -1
  35. package/lib/types/activities/generateOrAssignContentType.d.ts.map +1 -1
  36. package/lib/types/activities/index-dsl.d.ts +2 -1
  37. package/lib/types/activities/index-dsl.d.ts.map +1 -1
  38. package/lib/types/activities/{generateImageRendition.d.ts → renditions/generateImageRendition.d.ts} +4 -5
  39. package/lib/types/activities/renditions/generateImageRendition.d.ts.map +1 -0
  40. package/lib/types/activities/renditions/generateVideoRendition.d.ts +15 -0
  41. package/lib/types/activities/renditions/generateVideoRendition.d.ts.map +1 -0
  42. package/lib/types/dsl/dsl-workflow.d.ts.map +1 -1
  43. package/lib/types/index.d.ts +3 -1
  44. package/lib/types/index.d.ts.map +1 -1
  45. package/lib/types/utils/renditions.d.ts +23 -0
  46. package/lib/types/utils/renditions.d.ts.map +1 -0
  47. package/lib/workflows-bundle.js +99 -34
  48. package/package.json +3 -3
  49. package/src/activities/generateEmbeddings.ts +440 -296
  50. package/src/activities/generateOrAssignContentType.ts +185 -144
  51. package/src/activities/index-dsl.ts +2 -1
  52. package/src/activities/renditions/generateImageRendition.ts +99 -0
  53. package/src/activities/renditions/generateVideoRendition.ts +288 -0
  54. package/src/dsl/dsl-workflow.ts +8 -0
  55. package/src/dsl/workflow-exec-child.test.ts +1 -0
  56. package/src/dsl/workflow.test.ts +1 -0
  57. package/src/index.ts +3 -1
  58. package/src/utils/renditions.ts +124 -0
  59. package/lib/cjs/activities/generateImageRendition.js +0 -167
  60. package/lib/cjs/activities/generateImageRendition.js.map +0 -1
  61. package/lib/esm/activities/generateImageRendition.js +0 -161
  62. package/lib/esm/activities/generateImageRendition.js.map +0 -1
  63. package/lib/types/activities/generateImageRendition.d.ts.map +0 -1
  64. package/src/activities/generateImageRendition.ts +0 -202
@@ -0,0 +1,288 @@
1
+ import { log } from "@temporalio/activity";
2
+ import { DSLActivityExecutionPayload, DSLActivitySpec } from "@vertesia/common";
3
+ import { exec } from "child_process";
4
+ import fs from "fs";
5
+ import os from "os";
6
+ import path from "path";
7
+ import { promisify } from "util";
8
+ import { setupActivity } from "../../dsl/setup/ActivityContext.js";
9
+ import { NoDocumentFound, WorkflowParamNotFound } from "../../errors.js";
10
+ import { saveBlobToTempFile } from "../../utils/blobs.js";
11
+ import {
12
+ ImageRenditionParams,
13
+ uploadRenditionPages,
14
+ } from "../../utils/renditions.js";
15
+
16
+ const execAsync = promisify(exec);
17
+
18
+ interface GenerateVideoRenditionParams extends ImageRenditionParams { }
19
+
20
+ export interface GenerateVideoRendition
21
+ extends DSLActivitySpec<GenerateVideoRenditionParams> {
22
+ name: "generateImageRendition";
23
+ }
24
+
25
+ interface VideoMetadata {
26
+ duration: number;
27
+ width: number;
28
+ height: number;
29
+ }
30
+
31
+ async function getVideoMetadata(videoPath: string): Promise<VideoMetadata> {
32
+ try {
33
+ const command = `ffprobe -v quiet -print_format json -show_format -show_streams "${videoPath}"`;
34
+ const { stdout } = await execAsync(command);
35
+ const metadata = JSON.parse(stdout);
36
+
37
+ const videoStream = metadata.streams.find(
38
+ (stream: any) => stream.codec_type === "video",
39
+ );
40
+ const duration = parseFloat(metadata.format.duration) || 0;
41
+ const width = videoStream?.width || 0;
42
+ const height = videoStream?.height || 0;
43
+
44
+ return { duration, width, height };
45
+ } catch (error) {
46
+ log.error(
47
+ `Failed to get video metadata: ${error instanceof Error ? error.message : "Unknown error"}`,
48
+ );
49
+ throw new Error(
50
+ `Failed to probe video metadata: ${error instanceof Error ? error.message : "Unknown error"}`,
51
+ );
52
+ }
53
+ }
54
+
55
+ async function generateThumbnail(
56
+ videoPath: string,
57
+ outputDir: string,
58
+ timestamp: number,
59
+ maxSize: number,
60
+ ): Promise<string | undefined> {
61
+ //pad timestamp to 5 digits as filename
62
+ const outputFile = path.join(
63
+ outputDir,
64
+ `thumb-${timestamp.toString().padStart(5, "0")}.jpg`,
65
+ );
66
+
67
+ // FFmpeg command to extract thumbnail at specific timestamp
68
+ // Use proper scale filter syntax: scale=w:h:force_original_aspect_ratio=decrease
69
+ const scaleFilter = `scale=${maxSize}:${maxSize}:force_original_aspect_ratio=decrease`;
70
+
71
+ const command = [
72
+ "ffmpeg",
73
+ "-y", // Overwrite output files
74
+ "-ss",
75
+ timestamp.toString(), // Seek to timestamp
76
+ "-i",
77
+ `"${videoPath}"`, // Input file
78
+ "-vframes",
79
+ "1", // Extract only 1 frame
80
+ "-vf",
81
+ `"${scaleFilter}"`, // Scale maintaining aspect ratio
82
+ "-q:v",
83
+ "2", // High quality
84
+ `"${outputFile}"`,
85
+ ].join(" ");
86
+ log.info(`Generating thumbnail at ${timestamp}s`), { command };
87
+ try {
88
+ const { stderr } = await execAsync(command);
89
+
90
+ // Log any warnings from ffmpeg
91
+ if (stderr && !stderr.includes("frame=")) {
92
+ log.debug(
93
+ `FFmpeg stderr for thumbnail at ${timestamp}s: ${stderr}`,
94
+ );
95
+ }
96
+
97
+ // Verify the file was created
98
+ if (fs.existsSync(outputFile)) {
99
+ log.debug(`Generated thumbnail at ${timestamp}s`);
100
+ return outputFile;
101
+ } else {
102
+ log.warn(`Thumbnail not generated for timestamp ${timestamp}s`);
103
+ return undefined;
104
+ }
105
+ } catch (error) {
106
+ log.error(
107
+ `Failed to generate thumbnail at ${timestamp}s: ${error instanceof Error ? error.message : "Unknown error"}`,
108
+ );
109
+ return undefined;
110
+ }
111
+ }
112
+
113
+ export async function generateVideoRendition(
114
+ payload: DSLActivityExecutionPayload<GenerateVideoRenditionParams>,
115
+ ) {
116
+ const {
117
+ client,
118
+ objectId,
119
+ params: originParams,
120
+ } = await setupActivity<GenerateVideoRenditionParams>(payload);
121
+
122
+ // Fix: Use maxHeightWidth if max_hw is not provided
123
+ const params = {
124
+ ...originParams,
125
+ max_hw:
126
+ originParams.max_hw || (originParams as any).maxHeightWidth || 1024, // Default to 1024 if both are missing
127
+ format:
128
+ originParams.format || (originParams as any).format_output || "png", // Default to png if format is missing
129
+ };
130
+
131
+ log.info(`Generating video rendition for ${objectId}`, {
132
+ originParams,
133
+ params,
134
+ });
135
+
136
+ const inputObject = await client.objects.retrieve(objectId).catch((err) => {
137
+ log.error(`Failed to retrieve document ${objectId}`, { err });
138
+ if (err.message.includes("not found")) {
139
+ throw new NoDocumentFound(`Document ${objectId} not found`, [
140
+ objectId,
141
+ ]);
142
+ }
143
+ throw err;
144
+ });
145
+
146
+ if (!params.format) {
147
+ log.error(`Format not found`);
148
+ throw new WorkflowParamNotFound(`format`);
149
+ }
150
+
151
+ if (!inputObject.content?.source) {
152
+ log.error(`Document ${objectId} has no source`);
153
+ throw new NoDocumentFound(`Document ${objectId} has no source`, [
154
+ objectId,
155
+ ]);
156
+ }
157
+
158
+ if (
159
+ !inputObject.content.type ||
160
+ !inputObject.content.type?.startsWith("video/")
161
+ ) {
162
+ log.error(
163
+ `Document ${objectId} is not a video: ${inputObject.content.type}`,
164
+ );
165
+ throw new NoDocumentFound(
166
+ `Document ${objectId} is not a video: ${inputObject.content.type}`,
167
+ [objectId],
168
+ );
169
+ }
170
+
171
+ //array of rendition files to upload
172
+ let renditionPages: string[] = [];
173
+
174
+ const videoFile = await saveBlobToTempFile(
175
+ client,
176
+ inputObject.content.source,
177
+ );
178
+ const tempOutputDir = fs.mkdtempSync(
179
+ path.join(os.tmpdir(), "video-rendition-"),
180
+ );
181
+
182
+ try {
183
+ // Get video metadata using command line ffprobe
184
+ const metadata = await getVideoMetadata(videoFile);
185
+ const duration = metadata.duration;
186
+
187
+ // Calculate optimal number of thumbnails based on video length
188
+ const calculateThumbnailCount = (videoDuration: number): number => {
189
+ if (videoDuration <= 60) return 3; // Short videos: 3 thumbnails
190
+ if (videoDuration <= 300) return 5; // 5min videos: 5 thumbnails
191
+ if (videoDuration <= 600) return 8; // 10min videos: 8 thumbnails
192
+ if (videoDuration <= 1800) return 12; // 30min videos: 12 thumbnails
193
+ if (videoDuration <= 3600) return 16; // 1hr videos: 16 thumbnails
194
+ return 20; // Longer videos: max 20 thumbnails
195
+ };
196
+
197
+ const thumbnailCount = calculateThumbnailCount(duration);
198
+
199
+ // Generate evenly spaced timestamps, avoiding very beginning and end
200
+ const timestamps: number[] = [];
201
+ const startOffset = Math.min(duration * 0.05, 5); // Skip first 5% or 5 seconds
202
+ const endOffset = Math.min(duration * 0.05, 5); // Skip last 5% or 5 seconds
203
+ const usableDuration = duration - startOffset - endOffset;
204
+
205
+ for (let i = 0; i < thumbnailCount; i++) {
206
+ const progress = (i + 1) / (thumbnailCount + 1); // Evenly distribute
207
+ const timestamp = startOffset + usableDuration * progress;
208
+ timestamps.push(Math.max(timestamp, 1));
209
+ }
210
+
211
+ log.info(
212
+ `Generating ${thumbnailCount} thumbnails for ${duration}s video`,
213
+ {
214
+ objectId,
215
+ duration,
216
+ thumbnailCount,
217
+ timestamps: timestamps.map((t) => Math.round(t)),
218
+ tempOutputDir,
219
+ },
220
+ );
221
+
222
+ // Generate thumbnails using command line ffmpeg
223
+ const generatedThumbnails = await Promise.all(
224
+ timestamps.map(async (timestamp) => {
225
+ return await generateThumbnail(
226
+ videoFile,
227
+ tempOutputDir,
228
+ timestamp,
229
+ params.max_hw,
230
+ );
231
+ }),
232
+ );
233
+
234
+ if (generatedThumbnails.length === 0) {
235
+ log.info(`No thumbnails were generated for video ${objectId}`, {
236
+ objectId,
237
+ thumbnailCount,
238
+ tempOutputDir,
239
+ });
240
+ throw new Error(
241
+ `No thumbnails were generated for video ${objectId}`,
242
+ );
243
+ }
244
+
245
+ renditionPages.push(
246
+ ...generatedThumbnails.filter(
247
+ (thumbnail) => thumbnail !== undefined,
248
+ ),
249
+ );
250
+ log.info(
251
+ `Successfully generated ${generatedThumbnails.length} thumbnails for ${objectId}`,
252
+ {
253
+ objectId,
254
+ generatedCount: generatedThumbnails.length,
255
+ requestedCount: thumbnailCount,
256
+ },
257
+ );
258
+ } catch (error) {
259
+ log.error(
260
+ `Error generating thumbnails for video: ${error instanceof Error ? error.message : "Unknown error"}`,
261
+ );
262
+ throw new Error(`Failed to generate thumbnails for video: ${objectId}`);
263
+ } finally {
264
+ // Clean up temporary video file
265
+ try {
266
+ if (fs.existsSync(videoFile)) {
267
+ fs.unlinkSync(videoFile);
268
+ }
269
+ } catch (cleanupError) {
270
+ log.warn(`Failed to cleanup temporary video file: ${videoFile}`);
271
+ }
272
+ }
273
+
274
+ // Update the final upload call to handle multiple thumbnails
275
+ const uploaded = await uploadRenditionPages(
276
+ client,
277
+ objectId,
278
+ renditionPages,
279
+ params,
280
+ );
281
+
282
+ return {
283
+ uploads: uploaded.map((u) => u),
284
+ format: params.format,
285
+ thumbnailCount: renditionPages.length,
286
+ status: "success",
287
+ };
288
+ }
@@ -169,10 +169,14 @@ async function startChildWorkflow(step: DSLChildWorkflowStep, payload: DSLWorkfl
169
169
  workflow: step.spec,
170
170
  vars: resolvedVars
171
171
  }],
172
+ memo: {
173
+ InitiatedBy: payload.initiated_by,
174
+ },
172
175
  searchAttributes: {
173
176
  AccountId: [payload.account_id],
174
177
  DocumentId: getDocumentIds(payload),
175
178
  ProjectId: [payload.project_id],
179
+ InitiatedBy: payload.initiated_by ? [payload.initiated_by] : [],
176
180
  },
177
181
  });
178
182
  if (step.output) {
@@ -196,10 +200,14 @@ async function executeChildWorkflow(step: DSLChildWorkflowStep, payload: DSLWork
196
200
  workflow: step.spec,
197
201
  vars: resolvedVars,
198
202
  }],
203
+ memo: {
204
+ InitiatedBy: payload.initiated_by,
205
+ },
199
206
  searchAttributes: {
200
207
  AccountId: [payload.account_id],
201
208
  DocumentId: getDocumentIds(payload),
202
209
  ProjectId: [payload.project_id],
210
+ InitiatedBy: payload.initiated_by ? [payload.initiated_by] : [],
203
211
  },
204
212
  });
205
213
 
@@ -100,6 +100,7 @@ describe('DSL Workflow with child workflows', () => {
100
100
  AccountId: protos.temporal.api.enums.v1.IndexedValueType.INDEXED_VALUE_TYPE_KEYWORD,
101
101
  DocumentId: protos.temporal.api.enums.v1.IndexedValueType.INDEXED_VALUE_TYPE_KEYWORD,
102
102
  ProjectId: protos.temporal.api.enums.v1.IndexedValueType.INDEXED_VALUE_TYPE_KEYWORD,
103
+ InitiatedBy: protos.temporal.api.enums.v1.IndexedValueType.INDEXED_VALUE_TYPE_KEYWORD,
103
104
  },
104
105
  });
105
106
  });
@@ -60,6 +60,7 @@ describe('DSL Workflow', () => {
60
60
  AccountId: protos.temporal.api.enums.v1.IndexedValueType.INDEXED_VALUE_TYPE_KEYWORD,
61
61
  DocumentId: protos.temporal.api.enums.v1.IndexedValueType.INDEXED_VALUE_TYPE_KEYWORD,
62
62
  ProjectId: protos.temporal.api.enums.v1.IndexedValueType.INDEXED_VALUE_TYPE_KEYWORD,
63
+ InitiatedBy: protos.temporal.api.enums.v1.IndexedValueType.INDEXED_VALUE_TYPE_KEYWORD,
63
64
  },
64
65
  });
65
66
  });
package/src/index.ts CHANGED
@@ -18,7 +18,8 @@ export * from "./activities/executeInteraction.js";
18
18
  export * from "./activities/extractDocumentText.js";
19
19
  export * from "./activities/generateDocumentProperties.js";
20
20
  export * from "./activities/generateEmbeddings.js";
21
- export * from "./activities/generateImageRendition.js";
21
+ export * from "./activities/renditions/generateImageRendition.js";
22
+ export * from "./activities/renditions/generateVideoRendition.js";
22
23
  export * from "./activities/generateOrAssignContentType.js";
23
24
  export * from "./activities/notifyWebhook.js";
24
25
  export * from "./activities/setDocumentStatus.js";
@@ -31,5 +32,6 @@ export * from "./utils/blobs.js";
31
32
  export * from "./utils/client.js";
32
33
  export * from "./utils/memory.js";
33
34
  export * from "./utils/tokens.js";
35
+ export * from "./utils/renditions.js";
34
36
 
35
37
  export * from "./conversion/image.js";
@@ -0,0 +1,124 @@
1
+ import { log } from "@temporalio/activity";
2
+ import { imageResizer } from "../conversion/image.js";
3
+ import fs from "fs";
4
+ import { VertesiaClient } from "@vertesia/client";
5
+ import { NodeStreamSource } from "@vertesia/client/node";
6
+ import { ImageRenditionFormat } from "@vertesia/common";
7
+ import path from "path";
8
+
9
+ export interface ImageRenditionParams {
10
+ max_hw: number; //maximum size of the longest side of the image
11
+ format: ImageRenditionFormat;
12
+ }
13
+
14
+ /**
15
+ * Get the path for all files for a rendition
16
+ * @param objectId
17
+ * @param params
18
+ * @param pageNumber
19
+ * @returns
20
+ */
21
+ export function getRenditionsPath(
22
+ objectId: string,
23
+ params: ImageRenditionParams,
24
+ ) {
25
+ const path = `renditions/${objectId}/${params.max_hw}`;
26
+ return path;
27
+ }
28
+
29
+ /**
30
+ * Get a specific page path for a rendition
31
+ */
32
+ export function getRenditionPagePath(
33
+ objectId: string,
34
+ params: ImageRenditionParams,
35
+ pageNumber: number | string = 1,
36
+ ) {
37
+ //if number, pad to 5 char
38
+ if (typeof pageNumber === "number") {
39
+ pageNumber = String(pageNumber).padStart(5, "0");
40
+ }
41
+ const path = getRenditionsPath(objectId, params);
42
+ const pagePath = `${path}/${pageNumber}.${params.format}`;
43
+ return pagePath;
44
+ }
45
+
46
+ /**
47
+ * Upload Rendition page to the cloud
48
+ */
49
+ export async function uploadRenditionPages(
50
+ client: VertesiaClient,
51
+ objectId: string,
52
+ files: string[],
53
+ params: ImageRenditionParams,
54
+ ) {
55
+ log.info(
56
+ `Uploading rendition for ${objectId} with ${files.length} pages (max_hw: ${params.max_hw}, format: ${params.format})`,
57
+ { files },
58
+ );
59
+ const uploads = files.map(async (file, i) => {
60
+ const filename = path.basename(file).split(".")[0];
61
+ const pageId = getRenditionPagePath(objectId, params, filename);
62
+ let resizedImagePath = null;
63
+
64
+ try {
65
+ log.info(`Resizing image for ${objectId} page ${i}`, {
66
+ file,
67
+ params,
68
+ });
69
+ // Resize the image using ImageMagick
70
+ resizedImagePath = await imageResizer(
71
+ file,
72
+ params.max_hw,
73
+ params.format,
74
+ );
75
+
76
+ // Create a read stream from the resized image file
77
+ const fileStream = fs.createReadStream(resizedImagePath);
78
+ const format = "image/" + params.format;
79
+ const fileId = pageId.split("/").pop() ?? pageId;
80
+ const source = new NodeStreamSource(
81
+ fileStream,
82
+ fileId,
83
+ format,
84
+ pageId,
85
+ );
86
+
87
+ log.info(
88
+ `Uploading rendition for ${objectId} page ${i} with max_hw: ${params.max_hw} and format: ${params.format}`,
89
+ {
90
+ resizedImagePath,
91
+ fileId,
92
+ format,
93
+ pageId,
94
+ },
95
+ );
96
+
97
+ const result = await client.files
98
+ .uploadFile(source)
99
+ .catch((err) => {
100
+ log.error(
101
+ `Failed to upload rendition for ${objectId} page ${i}`,
102
+ {
103
+ error: err,
104
+ errorMessage: err.message,
105
+ stack: err.stack,
106
+ },
107
+ );
108
+ return Promise.reject(`Upload failed: ${err.message}`);
109
+ });
110
+ log.info(`Rendition uploaded for ${objectId} page ${i}`, {
111
+ result,
112
+ });
113
+
114
+ return result;
115
+ } catch (err: any) {
116
+ log.error(`Failed to upload rendition for ${objectId} page ${i}`, {
117
+ error: err,
118
+ });
119
+ return Promise.reject(`Upload failed: ${err.message}`);
120
+ }
121
+ });
122
+
123
+ return Promise.all(uploads);
124
+ }
@@ -1,167 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.generateImageRendition = generateImageRendition;
7
- const activity_1 = require("@temporalio/activity");
8
- const node_1 = require("@vertesia/client/node");
9
- const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
10
- const fs_1 = __importDefault(require("fs"));
11
- const os_1 = __importDefault(require("os"));
12
- const path_1 = __importDefault(require("path"));
13
- const image_js_1 = require("../conversion/image.js");
14
- const ActivityContext_js_1 = require("../dsl/setup/ActivityContext.js");
15
- const errors_js_1 = require("../errors.js");
16
- const blobs_js_1 = require("../utils/blobs.js");
17
- async function generateImageRendition(payload) {
18
- const { client, objectId, params: originParams } = await (0, ActivityContext_js_1.setupActivity)(payload);
19
- // Fix: Use maxHeightWidth if max_hw is not provided
20
- const params = {
21
- ...originParams,
22
- max_hw: originParams.max_hw || originParams.maxHeightWidth || 1024, // Default to 1024 if both are missing
23
- format: originParams.format || originParams.format_output || 'png' // Default to png if format is missing
24
- };
25
- activity_1.log.info(`Generating image rendition for ${objectId}`, { originParams, params });
26
- const inputObject = await client.objects.retrieve(objectId).catch((err) => {
27
- activity_1.log.error(`Failed to retrieve document ${objectId}`, { err });
28
- if (err.message.includes("not found")) {
29
- throw new errors_js_1.NoDocumentFound(`Document ${objectId} not found`, [objectId]);
30
- }
31
- throw err;
32
- });
33
- const renditionType = await client.types.getTypeByName("Rendition");
34
- if (!params.format) {
35
- activity_1.log.error(`Format not found`);
36
- throw new errors_js_1.WorkflowParamNotFound(`format`);
37
- }
38
- if (!renditionType) {
39
- activity_1.log.error(`Rendition type not found`);
40
- throw new errors_js_1.NoDocumentFound(`Rendition type not found`, [objectId]);
41
- }
42
- if (!inputObject.content?.source) {
43
- activity_1.log.error(`Document ${objectId} has no source`);
44
- throw new errors_js_1.NoDocumentFound(`Document ${objectId} has no source`, [objectId]);
45
- }
46
- if (!inputObject.content.type || (!inputObject.content.type?.startsWith("image/") && !inputObject.content.type?.startsWith("video/"))) {
47
- activity_1.log.error(`Document ${objectId} is not an image or a video: ${inputObject.content.type}`);
48
- throw new errors_js_1.NoDocumentFound(`Document ${objectId} is not an image or a video: ${inputObject.content.type}`, [objectId]);
49
- }
50
- //array of rendition files to upload
51
- let renditionPages = [];
52
- if (inputObject.content.type.startsWith('image/')) {
53
- const imageFile = await (0, blobs_js_1.saveBlobToTempFile)(client, inputObject.content.source);
54
- activity_1.log.info(`Image ${objectId} copied to ${imageFile}`);
55
- renditionPages.push(imageFile);
56
- }
57
- else if (inputObject.content.type.startsWith('video/')) {
58
- const videoFile = await (0, blobs_js_1.saveBlobToTempFile)(client, inputObject.content.source);
59
- const tempOutputDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'video-rendition-'));
60
- const thumbnailPath = path_1.default.join(tempOutputDir, 'thumbnail.png');
61
- try {
62
- // Extract a frame at 10% of the video duration
63
- await new Promise((resolve, reject) => {
64
- fluent_ffmpeg_1.default.ffprobe(videoFile, (err, metadata) => {
65
- if (err) {
66
- activity_1.log.error(`Failed to probe video metadata: ${err.message}`);
67
- return reject(err);
68
- }
69
- const duration = metadata.format.duration || 0;
70
- const timestamp = Math.max(0.1 * duration, 1);
71
- (0, fluent_ffmpeg_1.default)(videoFile)
72
- .screenshots({
73
- timestamps: [timestamp],
74
- filename: 'thumbnail.png',
75
- folder: tempOutputDir,
76
- size: `${params.max_hw}x?`
77
- })
78
- .on('end', () => {
79
- activity_1.log.info(`Video frame extraction complete for ${objectId}`);
80
- resolve();
81
- })
82
- .on('error', (err) => {
83
- activity_1.log.error(`Error extracting frame from video: ${err.message}`);
84
- reject(err);
85
- });
86
- });
87
- });
88
- if (fs_1.default.existsSync(thumbnailPath)) {
89
- renditionPages.push(thumbnailPath);
90
- }
91
- else {
92
- throw new Error(`Failed to generate thumbnail for video ${objectId}`);
93
- }
94
- }
95
- catch (error) {
96
- activity_1.log.error(`Error generating image rendition for video: ${error instanceof Error ? error.message : 'Unknown error'}`);
97
- throw new Error(`Failed to generate image rendition for video: ${objectId}`);
98
- }
99
- }
100
- //generate rendition name, pass an index for multi parts
101
- const getRenditionName = (index = 0) => {
102
- const name = `renditions/${objectId}/${params.max_hw}/${index}.${params.format}`;
103
- return name;
104
- };
105
- if (!renditionPages || !renditionPages.length) {
106
- activity_1.log.error(`Failed to generate rendition for ${objectId}`);
107
- throw new Error(`Failed to generate rendition for ${objectId}`);
108
- }
109
- activity_1.log.info(`Uploading rendition for ${objectId} with ${renditionPages.length} pages (max_hw: ${params.max_hw}, format: ${params.format})`, { renditionPages });
110
- const uploads = renditionPages.map(async (page, i) => {
111
- const pageId = getRenditionName(i);
112
- let resizedImagePath = null;
113
- try {
114
- activity_1.log.info(`Resizing image for ${objectId} page ${i}`, { page, params });
115
- // Resize the image using ImageMagick
116
- resizedImagePath = await (0, image_js_1.imageResizer)(page, params.max_hw, params.format);
117
- // Create a read stream from the resized image file
118
- const fileStream = fs_1.default.createReadStream(resizedImagePath);
119
- const format = "image/" + params.format;
120
- const fileId = pageId.split("/").pop() ?? "0." + params.format;
121
- const source = new node_1.NodeStreamSource(fileStream, fileId, format, pageId);
122
- activity_1.log.info(`Uploading rendition for ${objectId} page ${i} with max_hw: ${params.max_hw} and format: ${params.format}`, {
123
- resizedImagePath,
124
- fileId,
125
- format,
126
- pageId,
127
- });
128
- const result = await client.objects.upload(source).catch((err) => {
129
- activity_1.log.error(`Failed to upload rendition for ${objectId} page ${i}`, {
130
- error: err,
131
- errorMessage: err.message,
132
- stack: err.stack
133
- });
134
- return Promise.reject(`Upload failed: ${err.message}`);
135
- });
136
- activity_1.log.info(`Rendition uploaded for ${objectId} page ${i}`, { result });
137
- return result;
138
- }
139
- catch (error) {
140
- activity_1.log.error(`Failed to process rendition for ${objectId} page ${i}`, { error });
141
- return Promise.reject(error instanceof Error ? error.message : null);
142
- }
143
- });
144
- const uploaded = await Promise.all(uploads);
145
- if (!uploaded || !uploaded.length || !uploaded[0]) {
146
- activity_1.log.error(`Failed to upload rendition for ${objectId}`, { uploaded });
147
- throw new Error(`Failed to upload rendition for ${objectId} - upload object is empty`);
148
- }
149
- activity_1.log.info(`Creating rendition for ${objectId} with max_hw: ${params.max_hw} and format: ${params.format}`, {
150
- uploaded,
151
- });
152
- const rendition = await client.objects.create({
153
- name: inputObject.name + ` [Rendition ${params.max_hw}]`,
154
- type: renditionType.id,
155
- parent: inputObject.id,
156
- content: uploaded[0],
157
- properties: {
158
- mime_type: "image/" + params.format,
159
- source_etag: inputObject.content.source,
160
- height: params.max_hw,
161
- width: params.max_hw,
162
- },
163
- });
164
- activity_1.log.info(`Rendition ${rendition.id} created for ${objectId}`, { rendition });
165
- return { id: rendition.id, format: params.format, status: "success" };
166
- }
167
- //# sourceMappingURL=generateImageRendition.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"generateImageRendition.js","sourceRoot":"","sources":["../../../src/activities/generateImageRendition.ts"],"names":[],"mappings":"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"}