@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
@@ -1,161 +0,0 @@
|
|
1
|
-
import { log } from "@temporalio/activity";
|
2
|
-
import { NodeStreamSource } from "@vertesia/client/node";
|
3
|
-
import ffmpeg from 'fluent-ffmpeg';
|
4
|
-
import fs from 'fs';
|
5
|
-
import os from 'os';
|
6
|
-
import path from 'path';
|
7
|
-
import { imageResizer } from "../conversion/image.js";
|
8
|
-
import { setupActivity } from "../dsl/setup/ActivityContext.js";
|
9
|
-
import { NoDocumentFound, WorkflowParamNotFound } from "../errors.js";
|
10
|
-
import { saveBlobToTempFile } from "../utils/blobs.js";
|
11
|
-
export async function generateImageRendition(payload) {
|
12
|
-
const { client, objectId, params: originParams } = await setupActivity(payload);
|
13
|
-
// Fix: Use maxHeightWidth if max_hw is not provided
|
14
|
-
const params = {
|
15
|
-
...originParams,
|
16
|
-
max_hw: originParams.max_hw || originParams.maxHeightWidth || 1024, // Default to 1024 if both are missing
|
17
|
-
format: originParams.format || originParams.format_output || 'png' // Default to png if format is missing
|
18
|
-
};
|
19
|
-
log.info(`Generating image rendition for ${objectId}`, { originParams, params });
|
20
|
-
const inputObject = await client.objects.retrieve(objectId).catch((err) => {
|
21
|
-
log.error(`Failed to retrieve document ${objectId}`, { err });
|
22
|
-
if (err.message.includes("not found")) {
|
23
|
-
throw new NoDocumentFound(`Document ${objectId} not found`, [objectId]);
|
24
|
-
}
|
25
|
-
throw err;
|
26
|
-
});
|
27
|
-
const renditionType = await client.types.getTypeByName("Rendition");
|
28
|
-
if (!params.format) {
|
29
|
-
log.error(`Format not found`);
|
30
|
-
throw new WorkflowParamNotFound(`format`);
|
31
|
-
}
|
32
|
-
if (!renditionType) {
|
33
|
-
log.error(`Rendition type not found`);
|
34
|
-
throw new NoDocumentFound(`Rendition type not found`, [objectId]);
|
35
|
-
}
|
36
|
-
if (!inputObject.content?.source) {
|
37
|
-
log.error(`Document ${objectId} has no source`);
|
38
|
-
throw new NoDocumentFound(`Document ${objectId} has no source`, [objectId]);
|
39
|
-
}
|
40
|
-
if (!inputObject.content.type || (!inputObject.content.type?.startsWith("image/") && !inputObject.content.type?.startsWith("video/"))) {
|
41
|
-
log.error(`Document ${objectId} is not an image or a video: ${inputObject.content.type}`);
|
42
|
-
throw new NoDocumentFound(`Document ${objectId} is not an image or a video: ${inputObject.content.type}`, [objectId]);
|
43
|
-
}
|
44
|
-
//array of rendition files to upload
|
45
|
-
let renditionPages = [];
|
46
|
-
if (inputObject.content.type.startsWith('image/')) {
|
47
|
-
const imageFile = await saveBlobToTempFile(client, inputObject.content.source);
|
48
|
-
log.info(`Image ${objectId} copied to ${imageFile}`);
|
49
|
-
renditionPages.push(imageFile);
|
50
|
-
}
|
51
|
-
else if (inputObject.content.type.startsWith('video/')) {
|
52
|
-
const videoFile = await saveBlobToTempFile(client, inputObject.content.source);
|
53
|
-
const tempOutputDir = fs.mkdtempSync(path.join(os.tmpdir(), 'video-rendition-'));
|
54
|
-
const thumbnailPath = path.join(tempOutputDir, 'thumbnail.png');
|
55
|
-
try {
|
56
|
-
// Extract a frame at 10% of the video duration
|
57
|
-
await new Promise((resolve, reject) => {
|
58
|
-
ffmpeg.ffprobe(videoFile, (err, metadata) => {
|
59
|
-
if (err) {
|
60
|
-
log.error(`Failed to probe video metadata: ${err.message}`);
|
61
|
-
return reject(err);
|
62
|
-
}
|
63
|
-
const duration = metadata.format.duration || 0;
|
64
|
-
const timestamp = Math.max(0.1 * duration, 1);
|
65
|
-
ffmpeg(videoFile)
|
66
|
-
.screenshots({
|
67
|
-
timestamps: [timestamp],
|
68
|
-
filename: 'thumbnail.png',
|
69
|
-
folder: tempOutputDir,
|
70
|
-
size: `${params.max_hw}x?`
|
71
|
-
})
|
72
|
-
.on('end', () => {
|
73
|
-
log.info(`Video frame extraction complete for ${objectId}`);
|
74
|
-
resolve();
|
75
|
-
})
|
76
|
-
.on('error', (err) => {
|
77
|
-
log.error(`Error extracting frame from video: ${err.message}`);
|
78
|
-
reject(err);
|
79
|
-
});
|
80
|
-
});
|
81
|
-
});
|
82
|
-
if (fs.existsSync(thumbnailPath)) {
|
83
|
-
renditionPages.push(thumbnailPath);
|
84
|
-
}
|
85
|
-
else {
|
86
|
-
throw new Error(`Failed to generate thumbnail for video ${objectId}`);
|
87
|
-
}
|
88
|
-
}
|
89
|
-
catch (error) {
|
90
|
-
log.error(`Error generating image rendition for video: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
91
|
-
throw new Error(`Failed to generate image rendition for video: ${objectId}`);
|
92
|
-
}
|
93
|
-
}
|
94
|
-
//generate rendition name, pass an index for multi parts
|
95
|
-
const getRenditionName = (index = 0) => {
|
96
|
-
const name = `renditions/${objectId}/${params.max_hw}/${index}.${params.format}`;
|
97
|
-
return name;
|
98
|
-
};
|
99
|
-
if (!renditionPages || !renditionPages.length) {
|
100
|
-
log.error(`Failed to generate rendition for ${objectId}`);
|
101
|
-
throw new Error(`Failed to generate rendition for ${objectId}`);
|
102
|
-
}
|
103
|
-
log.info(`Uploading rendition for ${objectId} with ${renditionPages.length} pages (max_hw: ${params.max_hw}, format: ${params.format})`, { renditionPages });
|
104
|
-
const uploads = renditionPages.map(async (page, i) => {
|
105
|
-
const pageId = getRenditionName(i);
|
106
|
-
let resizedImagePath = null;
|
107
|
-
try {
|
108
|
-
log.info(`Resizing image for ${objectId} page ${i}`, { page, params });
|
109
|
-
// Resize the image using ImageMagick
|
110
|
-
resizedImagePath = await imageResizer(page, params.max_hw, params.format);
|
111
|
-
// Create a read stream from the resized image file
|
112
|
-
const fileStream = fs.createReadStream(resizedImagePath);
|
113
|
-
const format = "image/" + params.format;
|
114
|
-
const fileId = pageId.split("/").pop() ?? "0." + params.format;
|
115
|
-
const source = new NodeStreamSource(fileStream, fileId, format, pageId);
|
116
|
-
log.info(`Uploading rendition for ${objectId} page ${i} with max_hw: ${params.max_hw} and format: ${params.format}`, {
|
117
|
-
resizedImagePath,
|
118
|
-
fileId,
|
119
|
-
format,
|
120
|
-
pageId,
|
121
|
-
});
|
122
|
-
const result = await client.objects.upload(source).catch((err) => {
|
123
|
-
log.error(`Failed to upload rendition for ${objectId} page ${i}`, {
|
124
|
-
error: err,
|
125
|
-
errorMessage: err.message,
|
126
|
-
stack: err.stack
|
127
|
-
});
|
128
|
-
return Promise.reject(`Upload failed: ${err.message}`);
|
129
|
-
});
|
130
|
-
log.info(`Rendition uploaded for ${objectId} page ${i}`, { result });
|
131
|
-
return result;
|
132
|
-
}
|
133
|
-
catch (error) {
|
134
|
-
log.error(`Failed to process rendition for ${objectId} page ${i}`, { error });
|
135
|
-
return Promise.reject(error instanceof Error ? error.message : null);
|
136
|
-
}
|
137
|
-
});
|
138
|
-
const uploaded = await Promise.all(uploads);
|
139
|
-
if (!uploaded || !uploaded.length || !uploaded[0]) {
|
140
|
-
log.error(`Failed to upload rendition for ${objectId}`, { uploaded });
|
141
|
-
throw new Error(`Failed to upload rendition for ${objectId} - upload object is empty`);
|
142
|
-
}
|
143
|
-
log.info(`Creating rendition for ${objectId} with max_hw: ${params.max_hw} and format: ${params.format}`, {
|
144
|
-
uploaded,
|
145
|
-
});
|
146
|
-
const rendition = await client.objects.create({
|
147
|
-
name: inputObject.name + ` [Rendition ${params.max_hw}]`,
|
148
|
-
type: renditionType.id,
|
149
|
-
parent: inputObject.id,
|
150
|
-
content: uploaded[0],
|
151
|
-
properties: {
|
152
|
-
mime_type: "image/" + params.format,
|
153
|
-
source_etag: inputObject.content.source,
|
154
|
-
height: params.max_hw,
|
155
|
-
width: params.max_hw,
|
156
|
-
},
|
157
|
-
});
|
158
|
-
log.info(`Rendition ${rendition.id} created for ${objectId}`, { rendition });
|
159
|
-
return { id: rendition.id, format: params.format, status: "success" };
|
160
|
-
}
|
161
|
-
//# 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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"}
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"generateImageRendition.d.ts","sourceRoot":"","sources":["../../../src/activities/generateImageRendition.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,2BAA2B,EAAE,eAAe,EAAuB,MAAM,kBAAkB,CAAC;AAUrG,UAAU,4BAA4B;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAuB,SAAQ,eAAe,CAAC,4BAA4B,CAAC;IACzF,IAAI,EAAE,wBAAwB,CAAC;CAClC;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,2BAA2B,CAAC,4BAA4B,CAAC;;;;GAoL9G"}
|
@@ -1,202 +0,0 @@
|
|
1
|
-
import { log } from "@temporalio/activity";
|
2
|
-
import { NodeStreamSource } from "@vertesia/client/node";
|
3
|
-
import { DSLActivityExecutionPayload, DSLActivitySpec, RenditionProperties } from "@vertesia/common";
|
4
|
-
import ffmpeg from 'fluent-ffmpeg';
|
5
|
-
import fs from 'fs';
|
6
|
-
import os from 'os';
|
7
|
-
import path from 'path';
|
8
|
-
import { imageResizer } from "../conversion/image.js";
|
9
|
-
import { setupActivity } from "../dsl/setup/ActivityContext.js";
|
10
|
-
import { NoDocumentFound, WorkflowParamNotFound } from "../errors.js";
|
11
|
-
import { saveBlobToTempFile } from "../utils/blobs.js";
|
12
|
-
|
13
|
-
interface GenerateImageRenditionParams {
|
14
|
-
max_hw: number; //maximum size of the longest side of the image
|
15
|
-
format: string; //format of the output image
|
16
|
-
}
|
17
|
-
|
18
|
-
export interface GenerateImageRendition extends DSLActivitySpec<GenerateImageRenditionParams> {
|
19
|
-
name: "generateImageRendition";
|
20
|
-
}
|
21
|
-
|
22
|
-
export async function generateImageRendition(payload: DSLActivityExecutionPayload<GenerateImageRenditionParams>) {
|
23
|
-
const { client, objectId, params: originParams } = await setupActivity<GenerateImageRenditionParams>(payload);
|
24
|
-
|
25
|
-
// Fix: Use maxHeightWidth if max_hw is not provided
|
26
|
-
const params = {
|
27
|
-
...originParams,
|
28
|
-
max_hw: originParams.max_hw || (originParams as any).maxHeightWidth || 1024, // Default to 1024 if both are missing
|
29
|
-
format: originParams.format || (originParams as any).format_output || 'png' // Default to png if format is missing
|
30
|
-
};
|
31
|
-
|
32
|
-
log.info(`Generating image rendition for ${objectId}`, { originParams, params });
|
33
|
-
|
34
|
-
const inputObject = await client.objects.retrieve(objectId).catch((err) => {
|
35
|
-
log.error(`Failed to retrieve document ${objectId}`, { err });
|
36
|
-
if (err.message.includes("not found")) {
|
37
|
-
throw new NoDocumentFound(`Document ${objectId} not found`, [objectId]);
|
38
|
-
}
|
39
|
-
throw err;
|
40
|
-
});
|
41
|
-
const renditionType = await client.types.getTypeByName("Rendition");
|
42
|
-
|
43
|
-
if (!params.format) {
|
44
|
-
log.error(`Format not found`);
|
45
|
-
throw new WorkflowParamNotFound(`format`);
|
46
|
-
}
|
47
|
-
|
48
|
-
if (!renditionType) {
|
49
|
-
log.error(`Rendition type not found`);
|
50
|
-
throw new NoDocumentFound(`Rendition type not found`, [objectId]);
|
51
|
-
}
|
52
|
-
|
53
|
-
if (!inputObject.content?.source) {
|
54
|
-
log.error(`Document ${objectId} has no source`);
|
55
|
-
throw new NoDocumentFound(`Document ${objectId} has no source`, [objectId]);
|
56
|
-
}
|
57
|
-
|
58
|
-
if (!inputObject.content.type || (!inputObject.content.type?.startsWith("image/") && !inputObject.content.type?.startsWith("video/"))) {
|
59
|
-
log.error(`Document ${objectId} is not an image or a video: ${inputObject.content.type}`);
|
60
|
-
throw new NoDocumentFound(`Document ${objectId} is not an image or a video: ${inputObject.content.type}`, [objectId]);
|
61
|
-
}
|
62
|
-
|
63
|
-
//array of rendition files to upload
|
64
|
-
let renditionPages: string[] = [];
|
65
|
-
|
66
|
-
if (inputObject.content.type.startsWith('image/')) {
|
67
|
-
const imageFile = await saveBlobToTempFile(client, inputObject.content.source);
|
68
|
-
log.info(`Image ${objectId} copied to ${imageFile}`);
|
69
|
-
renditionPages.push(imageFile);
|
70
|
-
} else if (inputObject.content.type.startsWith('video/')) {
|
71
|
-
const videoFile = await saveBlobToTempFile(client, inputObject.content.source);
|
72
|
-
const tempOutputDir = fs.mkdtempSync(path.join(os.tmpdir(), 'video-rendition-'));
|
73
|
-
const thumbnailPath = path.join(tempOutputDir, 'thumbnail.png');
|
74
|
-
|
75
|
-
try {
|
76
|
-
// Extract a frame at 10% of the video duration
|
77
|
-
await new Promise<void>((resolve, reject) => {
|
78
|
-
ffmpeg.ffprobe(videoFile, (err, metadata) => {
|
79
|
-
if (err) {
|
80
|
-
log.error(`Failed to probe video metadata: ${err.message}`);
|
81
|
-
return reject(err);
|
82
|
-
}
|
83
|
-
|
84
|
-
const duration = metadata.format.duration || 0;
|
85
|
-
const timestamp = Math.max(0.1 * duration, 1);
|
86
|
-
|
87
|
-
ffmpeg(videoFile)
|
88
|
-
.screenshots({
|
89
|
-
timestamps: [timestamp],
|
90
|
-
filename: 'thumbnail.png',
|
91
|
-
folder: tempOutputDir,
|
92
|
-
size: `${params.max_hw}x?`
|
93
|
-
})
|
94
|
-
.on('end', () => {
|
95
|
-
log.info(`Video frame extraction complete for ${objectId}`);
|
96
|
-
resolve();
|
97
|
-
})
|
98
|
-
.on('error', (err) => {
|
99
|
-
log.error(`Error extracting frame from video: ${err.message}`);
|
100
|
-
reject(err);
|
101
|
-
});
|
102
|
-
});
|
103
|
-
});
|
104
|
-
|
105
|
-
if (fs.existsSync(thumbnailPath)) {
|
106
|
-
renditionPages.push(thumbnailPath);
|
107
|
-
} else {
|
108
|
-
throw new Error(`Failed to generate thumbnail for video ${objectId}`);
|
109
|
-
}
|
110
|
-
} catch (error) {
|
111
|
-
log.error(`Error generating image rendition for video: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
112
|
-
throw new Error(`Failed to generate image rendition for video: ${objectId}`);
|
113
|
-
}
|
114
|
-
}
|
115
|
-
|
116
|
-
//generate rendition name, pass an index for multi parts
|
117
|
-
const getRenditionName = (index: number = 0) => {
|
118
|
-
const name = `renditions/${objectId}/${params.max_hw}/${index}.${params.format}`;
|
119
|
-
return name;
|
120
|
-
};
|
121
|
-
|
122
|
-
if (!renditionPages || !renditionPages.length) {
|
123
|
-
log.error(`Failed to generate rendition for ${objectId}`);
|
124
|
-
throw new Error(`Failed to generate rendition for ${objectId}`);
|
125
|
-
}
|
126
|
-
|
127
|
-
log.info(
|
128
|
-
`Uploading rendition for ${objectId} with ${renditionPages.length} pages (max_hw: ${params.max_hw}, format: ${params.format})`,
|
129
|
-
{ renditionPages },
|
130
|
-
);
|
131
|
-
const uploads = renditionPages.map(async (page, i) => {
|
132
|
-
const pageId = getRenditionName(i);
|
133
|
-
let resizedImagePath = null;
|
134
|
-
|
135
|
-
try {
|
136
|
-
log.info(`Resizing image for ${objectId} page ${i}`, { page, params });
|
137
|
-
// Resize the image using ImageMagick
|
138
|
-
resizedImagePath = await imageResizer(page, params.max_hw, params.format);
|
139
|
-
|
140
|
-
// Create a read stream from the resized image file
|
141
|
-
const fileStream = fs.createReadStream(resizedImagePath);
|
142
|
-
const format = "image/" + params.format;
|
143
|
-
const fileId = pageId.split("/").pop() ?? "0." + params.format;
|
144
|
-
const source = new NodeStreamSource(
|
145
|
-
fileStream,
|
146
|
-
fileId,
|
147
|
-
format,
|
148
|
-
pageId,
|
149
|
-
);
|
150
|
-
|
151
|
-
log.info(
|
152
|
-
`Uploading rendition for ${objectId} page ${i} with max_hw: ${params.max_hw} and format: ${params.format}`, {
|
153
|
-
resizedImagePath,
|
154
|
-
fileId,
|
155
|
-
format,
|
156
|
-
pageId,
|
157
|
-
}
|
158
|
-
);
|
159
|
-
|
160
|
-
const result = await client.objects.upload(source).catch((err) => {
|
161
|
-
log.error(`Failed to upload rendition for ${objectId} page ${i}`, {
|
162
|
-
error: err,
|
163
|
-
errorMessage: err.message,
|
164
|
-
stack: err.stack
|
165
|
-
});
|
166
|
-
return Promise.reject(`Upload failed: ${err.message}`);
|
167
|
-
});
|
168
|
-
log.info(`Rendition uploaded for ${objectId} page ${i}`, { result });
|
169
|
-
|
170
|
-
return result;
|
171
|
-
} catch (error) {
|
172
|
-
log.error(`Failed to process rendition for ${objectId} page ${i}`, { error });
|
173
|
-
return Promise.reject(error instanceof Error ? error.message : null);
|
174
|
-
}
|
175
|
-
});
|
176
|
-
|
177
|
-
const uploaded = await Promise.all(uploads);
|
178
|
-
if (!uploaded || !uploaded.length || !uploaded[0]) {
|
179
|
-
log.error(`Failed to upload rendition for ${objectId}`, { uploaded });
|
180
|
-
throw new Error(`Failed to upload rendition for ${objectId} - upload object is empty`);
|
181
|
-
}
|
182
|
-
|
183
|
-
log.info(`Creating rendition for ${objectId} with max_hw: ${params.max_hw} and format: ${params.format}`, {
|
184
|
-
uploaded,
|
185
|
-
});
|
186
|
-
const rendition = await client.objects.create({
|
187
|
-
name: inputObject.name + ` [Rendition ${params.max_hw}]`,
|
188
|
-
type: renditionType.id,
|
189
|
-
parent: inputObject.id,
|
190
|
-
content: uploaded[0],
|
191
|
-
properties: {
|
192
|
-
mime_type: "image/" + params.format,
|
193
|
-
source_etag: inputObject.content.source,
|
194
|
-
height: params.max_hw,
|
195
|
-
width: params.max_hw,
|
196
|
-
} satisfies RenditionProperties,
|
197
|
-
});
|
198
|
-
|
199
|
-
log.info(`Rendition ${rendition.id} created for ${objectId}`, { rendition });
|
200
|
-
|
201
|
-
return { id: rendition.id, format: params.format, status: "success" };
|
202
|
-
}
|