@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.
- package/lib/cjs/activities/generateEmbeddings.js +158 -61
- package/lib/cjs/activities/generateEmbeddings.js.map +1 -1
- package/lib/cjs/activities/generateOrAssignContentType.js +19 -8
- package/lib/cjs/activities/generateOrAssignContentType.js.map +1 -1
- package/lib/cjs/activities/index-dsl.js +4 -2
- package/lib/cjs/activities/index-dsl.js.map +1 -1
- package/lib/cjs/activities/renditions/generateImageRendition.js +57 -0
- package/lib/cjs/activities/renditions/generateImageRendition.js.map +1 -0
- package/lib/cjs/activities/renditions/generateVideoRendition.js +196 -0
- package/lib/cjs/activities/renditions/generateVideoRendition.js.map +1 -0
- package/lib/cjs/dsl/dsl-workflow.js +8 -0
- package/lib/cjs/dsl/dsl-workflow.js.map +1 -1
- package/lib/cjs/index.js +3 -1
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/utils/renditions.js +88 -0
- package/lib/cjs/utils/renditions.js.map +1 -0
- package/lib/esm/activities/generateEmbeddings.js +160 -63
- package/lib/esm/activities/generateEmbeddings.js.map +1 -1
- package/lib/esm/activities/generateOrAssignContentType.js +21 -10
- package/lib/esm/activities/generateOrAssignContentType.js.map +1 -1
- package/lib/esm/activities/index-dsl.js +2 -1
- package/lib/esm/activities/index-dsl.js.map +1 -1
- package/lib/esm/activities/renditions/generateImageRendition.js +54 -0
- package/lib/esm/activities/renditions/generateImageRendition.js.map +1 -0
- package/lib/esm/activities/renditions/generateVideoRendition.js +190 -0
- package/lib/esm/activities/renditions/generateVideoRendition.js.map +1 -0
- package/lib/esm/dsl/dsl-workflow.js +8 -0
- package/lib/esm/dsl/dsl-workflow.js.map +1 -1
- package/lib/esm/index.js +3 -1
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/utils/renditions.js +80 -0
- package/lib/esm/utils/renditions.js.map +1 -0
- package/lib/types/activities/generateEmbeddings.d.ts +1 -1
- package/lib/types/activities/generateEmbeddings.d.ts.map +1 -1
- package/lib/types/activities/generateOrAssignContentType.d.ts.map +1 -1
- package/lib/types/activities/index-dsl.d.ts +2 -1
- package/lib/types/activities/index-dsl.d.ts.map +1 -1
- package/lib/types/activities/{generateImageRendition.d.ts → renditions/generateImageRendition.d.ts} +4 -5
- package/lib/types/activities/renditions/generateImageRendition.d.ts.map +1 -0
- package/lib/types/activities/renditions/generateVideoRendition.d.ts +15 -0
- package/lib/types/activities/renditions/generateVideoRendition.d.ts.map +1 -0
- package/lib/types/dsl/dsl-workflow.d.ts.map +1 -1
- package/lib/types/index.d.ts +3 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/utils/renditions.d.ts +23 -0
- package/lib/types/utils/renditions.d.ts.map +1 -0
- package/lib/workflows-bundle.js +99 -34
- package/package.json +3 -3
- package/src/activities/generateEmbeddings.ts +440 -296
- package/src/activities/generateOrAssignContentType.ts +185 -144
- package/src/activities/index-dsl.ts +2 -1
- package/src/activities/renditions/generateImageRendition.ts +99 -0
- package/src/activities/renditions/generateVideoRendition.ts +288 -0
- package/src/dsl/dsl-workflow.ts +8 -0
- package/src/dsl/workflow-exec-child.test.ts +1 -0
- package/src/dsl/workflow.test.ts +1 -0
- package/src/index.ts +3 -1
- package/src/utils/renditions.ts +124 -0
- package/lib/cjs/activities/generateImageRendition.js +0 -167
- package/lib/cjs/activities/generateImageRendition.js.map +0 -1
- package/lib/esm/activities/generateImageRendition.js +0 -161
- package/lib/esm/activities/generateImageRendition.js.map +0 -1
- package/lib/types/activities/generateImageRendition.d.ts.map +0 -1
- 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
|
+
}
|
package/src/dsl/dsl-workflow.ts
CHANGED
@@ -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
|
});
|
package/src/dsl/workflow.test.ts
CHANGED
@@ -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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"}
|