@ossy/media-tasks 1.23.3
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 +28 -0
- package/package.json +32 -0
- package/src/index.js +2 -0
- package/src/openai.integration.js +52 -0
- package/src/resize-common-web.task.js +55 -0
- package/src/visual-content-descriptors.task.js +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @ossy/media-tasks
|
|
2
|
+
|
|
3
|
+
Changestream-triggered media processing tasks for the Ossy platform.
|
|
4
|
+
|
|
5
|
+
Tasks in this package are registered with `JobsService.RegisterTask()` at API startup and fire automatically when matching events are detected in the event store change stream.
|
|
6
|
+
|
|
7
|
+
## Tasks
|
|
8
|
+
|
|
9
|
+
| Task | Trigger | Description |
|
|
10
|
+
|------|---------|-------------|
|
|
11
|
+
| `resize-common-web` | `Resource` `Created` (`image/*`) | Generates common web thumbnail and gallery sizes using `sharp` |
|
|
12
|
+
| `visual-content-descriptors` | `Resource` `Created` (`image/*`, `video/*`) | Uses GPT-4o to generate title, description, tags, and alt text |
|
|
13
|
+
|
|
14
|
+
## Task contract
|
|
15
|
+
|
|
16
|
+
Each `*.task.js` file exports:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
export const metadata = {
|
|
20
|
+
id: 'my-task',
|
|
21
|
+
triggers: [{ aggregateType, event, resource?, location? }],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default async function ({ event, sdk }) { ... }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- `event` — the raw eventstore `fullDocument` that triggered this handler
|
|
28
|
+
- `sdk` — platform SDK instance (passed from API startup; `null` during local dev if unconfigured)
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ossy/media-tasks",
|
|
3
|
+
"version": "1.23.3",
|
|
4
|
+
"description": "Changestream-triggered media processing tasks (image resize, AI descriptors)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"module": "./src/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.js"
|
|
10
|
+
},
|
|
11
|
+
"ossy": {
|
|
12
|
+
"src": "./src"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"typecheck": "echo \"No TypeScript in media-tasks package\" && exit 0"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"openai": ">=4",
|
|
19
|
+
"sharp": ">=0.34"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"registry": "https://registry.npmjs.org"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"/src",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"author": "Ossy <yourfriends@ossy.se> (https://ossy.se)",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"gitHead": "dfb68d196df3c7c291ea321901c4a1017fb0c8c1"
|
|
32
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import OpenAiSDK from 'openai'
|
|
2
|
+
|
|
3
|
+
const openai = new OpenAiSDK({
|
|
4
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
5
|
+
organization: process.env.OPENAI_ORGANIZATION,
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
export class OpenAi {
|
|
9
|
+
static async getVisualContentDescriptors(imageSrc) {
|
|
10
|
+
const systemPrompt = `
|
|
11
|
+
You will be provided with images, and your task is to create a json object including the following properties:
|
|
12
|
+
-title: Short attention grabbing title that describes the image
|
|
13
|
+
-description: A short text describing the image, suitable for SEO purposes
|
|
14
|
+
-tags: an array of tags describing the image that will be used for categorization and SEO purposes
|
|
15
|
+
-alt: A short text that describes the image, suitable for alt text
|
|
16
|
+
`
|
|
17
|
+
|
|
18
|
+
const response = await openai.chat.completions.create({
|
|
19
|
+
model: 'gpt-4o-mini',
|
|
20
|
+
max_tokens: 4000,
|
|
21
|
+
messages: [
|
|
22
|
+
{ role: 'system', content: systemPrompt },
|
|
23
|
+
{ role: 'user', content: [{ type: 'image_url', image_url: { url: imageSrc } }] },
|
|
24
|
+
],
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const parsedResponse = await OpenAi.ensureJSONResponse(response)
|
|
28
|
+
const visualDescriptors = JSON.parse(
|
|
29
|
+
parsedResponse?.choices?.[0]?.message?.content ?? '{}'
|
|
30
|
+
)
|
|
31
|
+
return visualDescriptors
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static ensureJSONResponse(response) {
|
|
35
|
+
const systemPrompt = `
|
|
36
|
+
You will be recieving a text string that contains a json object.
|
|
37
|
+
Your task is to parse the text string and convert the response into a json object.
|
|
38
|
+
`
|
|
39
|
+
|
|
40
|
+
const textString = response?.choices?.[0]?.message?.content
|
|
41
|
+
|
|
42
|
+
return openai.chat.completions.create({
|
|
43
|
+
model: 'gpt-4-turbo-preview',
|
|
44
|
+
max_tokens: 4000,
|
|
45
|
+
response_format: { type: 'json_object' },
|
|
46
|
+
messages: [
|
|
47
|
+
{ role: 'system', content: systemPrompt },
|
|
48
|
+
{ role: 'user', content: textString ?? '' },
|
|
49
|
+
],
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import sharp from 'sharp'
|
|
2
|
+
|
|
3
|
+
const Sizes = [
|
|
4
|
+
{ width: 24, height: 24, name: 'thumbnailSmall' },
|
|
5
|
+
{ width: 48, height: 48, name: 'thumbnailLarge' },
|
|
6
|
+
{ width: 274, height: undefined, name: 'galleryMedium' },
|
|
7
|
+
{ width: 548, height: undefined, name: 'galleryLarge' },
|
|
8
|
+
{ width: 4, height: undefined, name: 'loader-square-blurred-before' },
|
|
9
|
+
{ width: 4, height: undefined, name: 'loader-square-blurred-after' },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
export const metadata = {
|
|
13
|
+
id: 'resize-common-web',
|
|
14
|
+
triggers: [
|
|
15
|
+
{
|
|
16
|
+
aggregateType: 'Resource',
|
|
17
|
+
event: 'Created',
|
|
18
|
+
resource: { type: 'image/*' },
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default async function ({ event, sdk }) {
|
|
24
|
+
const resourceId = event.aggregateId
|
|
25
|
+
|
|
26
|
+
console.log(`[resize-common-web] starting for resource ${resourceId}`)
|
|
27
|
+
|
|
28
|
+
const resource = await sdk.resources.get({ id: resourceId })
|
|
29
|
+
const image = await fetch(resource.content.src).then((response) => response.arrayBuffer())
|
|
30
|
+
|
|
31
|
+
console.log(`[resize-common-web] Resizing ${resourceId}`)
|
|
32
|
+
|
|
33
|
+
for (const { width, height, name } of Sizes) {
|
|
34
|
+
const imageBuffer = await sharp(image).resize(width, height).toBuffer()
|
|
35
|
+
|
|
36
|
+
console.log(`[resize-common-web] Created image buffer for ${name}`)
|
|
37
|
+
|
|
38
|
+
const file = new File([new Uint8Array(imageBuffer)], name, { type: resource.type })
|
|
39
|
+
|
|
40
|
+
console.log(`[resize-common-web] Uploading ${name} for ${resourceId}`)
|
|
41
|
+
try {
|
|
42
|
+
await sdk.resources.uploadNamedVersion({
|
|
43
|
+
id: resourceId,
|
|
44
|
+
name: name,
|
|
45
|
+
file: file,
|
|
46
|
+
})
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.log(`[resize-common-web] Error uploading ${name} for ${resourceId}`)
|
|
49
|
+
console.error(error)
|
|
50
|
+
throw error
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log(`[resize-common-web] Uploaded ${name} for ${resourceId}`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { OpenAi } from './openai.integration.js'
|
|
2
|
+
|
|
3
|
+
export const metadata = {
|
|
4
|
+
id: 'visual-content-descriptors',
|
|
5
|
+
triggers: [
|
|
6
|
+
{
|
|
7
|
+
aggregateType: 'Resource',
|
|
8
|
+
event: 'Created',
|
|
9
|
+
resource: { type: 'image/*' },
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
aggregateType: 'Resource',
|
|
13
|
+
event: 'Created',
|
|
14
|
+
resource: { type: 'video/*' },
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default async function ({ event, sdk }) {
|
|
20
|
+
const resourceId = event.aggregateId
|
|
21
|
+
|
|
22
|
+
const resource = await sdk.resources.get({ id: resourceId })
|
|
23
|
+
const visualContentDescriptors = await OpenAi.getVisualContentDescriptors(resource.content.src)
|
|
24
|
+
|
|
25
|
+
await sdk.resources.rename({ id: resource.id, name: visualContentDescriptors.title })
|
|
26
|
+
await sdk.resources.updateContent({
|
|
27
|
+
id: resource.id,
|
|
28
|
+
content: {
|
|
29
|
+
...resource.content,
|
|
30
|
+
...visualContentDescriptors,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
}
|