@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 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,2 @@
1
+ // Task files are not component exports — they are discovered by task ID via metadata.
2
+ // Import individual task files directly: e.g. import './resize-common-web.task.js'
@@ -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
+ }