@mux/ai 0.1.1 → 0.1.2

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 (48) hide show
  1. package/dist/index.cjs +1773 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +164 -0
  4. package/dist/index.d.ts +164 -9
  5. package/dist/index.js +1757 -8
  6. package/dist/index.js.map +1 -1
  7. package/package.json +8 -6
  8. package/dist/audio-translation.d.ts +0 -21
  9. package/dist/audio-translation.d.ts.map +0 -1
  10. package/dist/audio-translation.js +0 -229
  11. package/dist/audio-translation.js.map +0 -1
  12. package/dist/burned-in-captions.d.ts +0 -19
  13. package/dist/burned-in-captions.d.ts.map +0 -1
  14. package/dist/burned-in-captions.js +0 -243
  15. package/dist/burned-in-captions.js.map +0 -1
  16. package/dist/chapters.d.ts +0 -18
  17. package/dist/chapters.d.ts.map +0 -1
  18. package/dist/chapters.js +0 -255
  19. package/dist/chapters.js.map +0 -1
  20. package/dist/index.d.ts.map +0 -1
  21. package/dist/moderation.d.ts +0 -39
  22. package/dist/moderation.d.ts.map +0 -1
  23. package/dist/moderation.js +0 -341
  24. package/dist/moderation.js.map +0 -1
  25. package/dist/summarization.d.ts +0 -26
  26. package/dist/summarization.d.ts.map +0 -1
  27. package/dist/summarization.js +0 -337
  28. package/dist/summarization.js.map +0 -1
  29. package/dist/translation.d.ts +0 -22
  30. package/dist/translation.d.ts.map +0 -1
  31. package/dist/translation.js +0 -196
  32. package/dist/translation.js.map +0 -1
  33. package/dist/types.d.ts +0 -12
  34. package/dist/types.d.ts.map +0 -1
  35. package/dist/types.js +0 -2
  36. package/dist/types.js.map +0 -1
  37. package/dist/utils/image-download.d.ts +0 -65
  38. package/dist/utils/image-download.d.ts.map +0 -1
  39. package/dist/utils/image-download.js +0 -150
  40. package/dist/utils/image-download.js.map +0 -1
  41. package/dist/utils/storyboard-processor.d.ts +0 -40
  42. package/dist/utils/storyboard-processor.d.ts.map +0 -1
  43. package/dist/utils/storyboard-processor.js +0 -202
  44. package/dist/utils/storyboard-processor.js.map +0 -1
  45. package/dist/utils/vtt-parser.d.ts +0 -8
  46. package/dist/utils/vtt-parser.d.ts.map +0 -1
  47. package/dist/utils/vtt-parser.js +0 -43
  48. package/dist/utils/vtt-parser.js.map +0 -1
package/dist/chapters.js DELETED
@@ -1,255 +0,0 @@
1
- import Mux from '@mux/mux-node';
2
- import OpenAI from 'openai';
3
- import Anthropic from '@anthropic-ai/sdk';
4
- import { z } from 'zod';
5
- import { zodTextFormat } from 'openai/helpers/zod';
6
- const chaptersSchema = z.object({
7
- chapters: z.array(z.object({
8
- startTime: z.number(),
9
- title: z.string()
10
- }))
11
- });
12
- const DEFAULT_SYSTEM_PROMPT = `Your role is to segment the following captions into chunked chapters, summarising each chapter with a title.
13
-
14
- Analyze the transcript and create logical chapter breaks based on topic changes, major transitions, or distinct sections of content. Each chapter should represent a meaningful segment of the video.
15
-
16
- You must respond with valid JSON in exactly this format:
17
- {
18
- "chapters": [
19
- {"startTime": 0, "title": "Introduction"},
20
- {"startTime": 45.5, "title": "Main Topic Discussion"},
21
- {"startTime": 120.0, "title": "Conclusion"}
22
- ]
23
- }
24
-
25
- Important rules:
26
- - startTime must be in seconds (not HH:MM:SS format)
27
- - Always start with startTime: 0 for the first chapter
28
- - Create 3-8 chapters depending on content length and natural breaks
29
- - Chapter titles should be concise and descriptive
30
- - Do not include any text before or after the JSON
31
- - The JSON must be valid and parseable`;
32
- const ANTHROPIC_JSON_PROMPT = `You must respond with valid JSON in exactly this format:
33
- {
34
- "chapters": [
35
- {"startTime": 0, "title": "Chapter title here"},
36
- {"startTime": 45.5, "title": "Another chapter title"}
37
- ]
38
- }
39
-
40
- Do not include any text before or after the JSON. The JSON must be valid and parseable.`;
41
- /**
42
- * Converts VTT timestamp (HH:MM:SS.mmm) to seconds
43
- */
44
- function vttTimestampToSeconds(timestamp) {
45
- const parts = timestamp.split(':');
46
- if (parts.length !== 3)
47
- return 0;
48
- const hours = parseInt(parts[0], 10) || 0;
49
- const minutes = parseInt(parts[1], 10) || 0;
50
- const seconds = parseFloat(parts[2]) || 0;
51
- return hours * 3600 + minutes * 60 + seconds;
52
- }
53
- /**
54
- * Extracts timestamps and text from VTT content for chapter generation
55
- */
56
- function extractTimestampsFromVTT(vttContent) {
57
- if (!vttContent.trim()) {
58
- return '';
59
- }
60
- const lines = vttContent.split('\n');
61
- const segments = [];
62
- for (let i = 0; i < lines.length; i++) {
63
- const line = lines[i].trim();
64
- // Find timestamp lines (contain -->)
65
- if (line.includes('-->')) {
66
- const startTime = line.split(' --> ')[0].trim();
67
- const timeInSeconds = vttTimestampToSeconds(startTime);
68
- // Get the subtitle text (next non-empty line)
69
- let j = i + 1;
70
- while (j < lines.length && !lines[j].trim()) {
71
- j++;
72
- }
73
- if (j < lines.length) {
74
- const text = lines[j].trim().replace(/<[^>]*>/g, ''); // Remove formatting tags
75
- if (text) {
76
- segments.push({ time: timeInSeconds, text });
77
- }
78
- }
79
- }
80
- }
81
- // Create a readable transcript with timestamps for the AI
82
- return segments
83
- .map(segment => `[${Math.floor(segment.time)}s] ${segment.text}`)
84
- .join('\n');
85
- }
86
- export async function generateChapters(assetId, languageCode, options = {}) {
87
- const { provider = 'openai', model, muxTokenId, muxTokenSecret, openaiApiKey, anthropicApiKey, ...config } = options;
88
- // Set default models based on provider
89
- const defaultModel = provider === 'anthropic' ? 'claude-3-5-haiku-20241022' : 'gpt-4o-mini';
90
- const finalModel = model || defaultModel;
91
- // Validate required credentials
92
- const muxId = muxTokenId || process.env.MUX_TOKEN_ID;
93
- const muxSecret = muxTokenSecret || process.env.MUX_TOKEN_SECRET;
94
- const openaiKey = openaiApiKey || process.env.OPENAI_API_KEY;
95
- const anthropicKey = anthropicApiKey || process.env.ANTHROPIC_API_KEY;
96
- if (!muxId || !muxSecret) {
97
- throw new Error('Mux credentials are required. Provide muxTokenId and muxTokenSecret in options or set MUX_TOKEN_ID and MUX_TOKEN_SECRET environment variables.');
98
- }
99
- if (provider === 'openai' && !openaiKey) {
100
- throw new Error('OpenAI API key is required for OpenAI provider. Provide openaiApiKey in options or set OPENAI_API_KEY environment variable.');
101
- }
102
- if (provider === 'anthropic' && !anthropicKey) {
103
- throw new Error('Anthropic API key is required for Anthropic provider. Provide anthropicApiKey in options or set ANTHROPIC_API_KEY environment variable.');
104
- }
105
- // Initialize clients
106
- const mux = new Mux({
107
- tokenId: muxId,
108
- tokenSecret: muxSecret,
109
- });
110
- let openaiClient;
111
- let anthropicClient;
112
- if (provider === 'openai') {
113
- openaiClient = new OpenAI({
114
- apiKey: openaiKey,
115
- });
116
- }
117
- else if (provider === 'anthropic') {
118
- anthropicClient = new Anthropic({
119
- apiKey: anthropicKey,
120
- });
121
- }
122
- // Fetch asset data from Mux
123
- let assetData;
124
- try {
125
- const asset = await mux.video.assets.retrieve(assetId);
126
- assetData = asset;
127
- }
128
- catch (error) {
129
- throw new Error(`Failed to fetch asset from Mux: ${error instanceof Error ? error.message : 'Unknown error'}`);
130
- }
131
- // Get playback ID
132
- const playbackId = assetData.playback_ids?.[0]?.id;
133
- if (!playbackId) {
134
- throw new Error('No playback ID found for this asset');
135
- }
136
- // Find caption track in the specified language
137
- if (!assetData.tracks) {
138
- throw new Error('No tracks found for this asset');
139
- }
140
- const captionTrack = assetData.tracks.find((track) => track.type === 'text' &&
141
- track.status === 'ready' &&
142
- track.text_type === 'subtitles' &&
143
- track.language_code === languageCode);
144
- if (!captionTrack) {
145
- throw new Error(`No caption track found for language '${languageCode}'. Available languages: ${assetData.tracks.filter(t => t.type === 'text').map(t => t.language_code).join(', ')}`);
146
- }
147
- // Fetch the VTT content
148
- const transcriptUrl = `https://stream.mux.com/${playbackId}/text/${captionTrack.id}.vtt`;
149
- let vttContent;
150
- try {
151
- const transcriptResponse = await fetch(transcriptUrl);
152
- if (!transcriptResponse.ok) {
153
- throw new Error(`Failed to fetch VTT: ${transcriptResponse.statusText}`);
154
- }
155
- vttContent = await transcriptResponse.text();
156
- }
157
- catch (error) {
158
- throw new Error(`Failed to fetch caption track: ${error instanceof Error ? error.message : 'Unknown error'}`);
159
- }
160
- // Extract timestamped transcript for AI processing
161
- const timestampedTranscript = extractTimestampsFromVTT(vttContent);
162
- if (!timestampedTranscript) {
163
- throw new Error('No usable content found in caption track');
164
- }
165
- // Generate chapters using AI
166
- let chaptersData = null;
167
- if (provider === 'openai') {
168
- try {
169
- const response = await openaiClient.responses.parse({
170
- model: finalModel,
171
- input: [
172
- {
173
- role: "system",
174
- content: DEFAULT_SYSTEM_PROMPT,
175
- },
176
- {
177
- role: "user",
178
- content: [
179
- {
180
- type: "input_text",
181
- text: timestampedTranscript,
182
- },
183
- ],
184
- },
185
- ],
186
- text: {
187
- format: zodTextFormat(chaptersSchema, "chapters"),
188
- },
189
- });
190
- chaptersData = response.output_parsed;
191
- }
192
- catch (error) {
193
- throw new Error(`Failed to generate chapters with OpenAI: ${error instanceof Error ? error.message : 'Unknown error'}`);
194
- }
195
- }
196
- else if (provider === 'anthropic') {
197
- const anthropicPrompt = `${DEFAULT_SYSTEM_PROMPT}
198
-
199
- ${ANTHROPIC_JSON_PROMPT}
200
-
201
- Transcript:
202
- ${timestampedTranscript}`;
203
- try {
204
- const response = await anthropicClient.messages.create({
205
- model: finalModel,
206
- max_tokens: 2000,
207
- messages: [
208
- {
209
- role: "user",
210
- content: anthropicPrompt,
211
- },
212
- ],
213
- });
214
- const content = response.content[0];
215
- if (content.type === 'text') {
216
- const jsonText = content.text.trim();
217
- try {
218
- chaptersData = JSON.parse(jsonText);
219
- }
220
- catch (parseError) {
221
- throw new Error(`Failed to parse JSON response from Anthropic: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`);
222
- }
223
- }
224
- else {
225
- throw new Error('Unexpected response type from Anthropic');
226
- }
227
- }
228
- catch (error) {
229
- throw new Error(`Failed to generate chapters with Anthropic: ${error instanceof Error ? error.message : 'Unknown error'}`);
230
- }
231
- }
232
- else {
233
- throw new Error(`Unsupported provider: ${provider}`);
234
- }
235
- if (!chaptersData || !chaptersData.chapters) {
236
- throw new Error('No chapters generated from AI response');
237
- }
238
- // Validate and sort chapters
239
- const validChapters = chaptersData.chapters
240
- .filter(chapter => typeof chapter.startTime === 'number' && typeof chapter.title === 'string')
241
- .sort((a, b) => a.startTime - b.startTime);
242
- if (validChapters.length === 0) {
243
- throw new Error('No valid chapters found in AI response');
244
- }
245
- // Ensure first chapter starts at 0
246
- if (validChapters[0].startTime !== 0) {
247
- validChapters[0].startTime = 0;
248
- }
249
- return {
250
- assetId,
251
- languageCode,
252
- chapters: validChapters,
253
- };
254
- }
255
- //# sourceMappingURL=chapters.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"chapters.js","sourceRoot":"","sources":["../src/chapters.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AAErC,eAAO,MAAM,OAAO,UAAU,CAAC"}
@@ -1,39 +0,0 @@
1
- import { MuxAIOptions } from './types';
2
- import { ImageDownloadOptions } from './utils/image-download';
3
- export interface ThumbnailModerationScore {
4
- url: string;
5
- sexual: number;
6
- violence: number;
7
- error: boolean;
8
- }
9
- export interface ModerationResult {
10
- assetId: string;
11
- thumbnailScores: ThumbnailModerationScore[];
12
- maxScores: {
13
- sexual: number;
14
- violence: number;
15
- };
16
- exceedsThreshold: boolean;
17
- thresholds: {
18
- sexual: number;
19
- violence: number;
20
- };
21
- }
22
- export interface ModerationOptions extends MuxAIOptions {
23
- provider?: 'openai' | 'hive';
24
- model?: string;
25
- thresholds?: {
26
- sexual?: number;
27
- violence?: number;
28
- };
29
- thumbnailInterval?: number;
30
- thumbnailWidth?: number;
31
- maxConcurrent?: number;
32
- /** Method for submitting images to AI providers (default: 'url') */
33
- imageSubmissionMode?: 'url' | 'base64';
34
- /** Options for image download when using base64 submission mode */
35
- imageDownloadOptions?: ImageDownloadOptions;
36
- hiveApiKey?: string;
37
- }
38
- export declare function getModerationScores(assetId: string, options?: ModerationOptions): Promise<ModerationResult>;
39
- //# sourceMappingURL=moderation.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"moderation.d.ts","sourceRoot":"","sources":["../src/moderation.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAA0B,MAAM,wBAAwB,CAAC;AAEtF,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,wBAAwB,EAAE,CAAC;IAC5C,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,gBAAgB,EAAE,OAAO,CAAC;IAC1B,UAAU,EAAE;QACV,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,iBAAkB,SAAQ,YAAY;IACrD,QAAQ,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE;QACX,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,mBAAmB,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IACvC,mEAAmE;IACnE,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAwUD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAoH3B"}
@@ -1,341 +0,0 @@
1
- import Mux from '@mux/mux-node';
2
- import OpenAI from 'openai';
3
- import { downloadImagesAsBase64 } from './utils/image-download';
4
- const DEFAULT_THRESHOLDS = {
5
- sexual: 0.7,
6
- violence: 0.8
7
- };
8
- // Process promises in batches with maximum concurrency limit
9
- async function processConcurrently(items, processor, maxConcurrent = 5) {
10
- const results = [];
11
- for (let i = 0; i < items.length; i += maxConcurrent) {
12
- const batch = items.slice(i, i + maxConcurrent);
13
- const batchPromises = batch.map(processor);
14
- const batchResults = await Promise.all(batchPromises);
15
- results.push(...batchResults);
16
- }
17
- return results;
18
- }
19
- // Mapping Hive categories to OpenAI-compatible scores
20
- const HIVE_SEXUAL_CATEGORIES = [
21
- 'general_nsfw',
22
- 'general_suggestive',
23
- 'yes_sexual_activity',
24
- 'female_underwear',
25
- 'male_underwear',
26
- 'bra',
27
- 'panties',
28
- 'sex_toys',
29
- 'nudity_female',
30
- 'nudity_male',
31
- 'cleavage',
32
- 'swimwear'
33
- ];
34
- const HIVE_VIOLENCE_CATEGORIES = [
35
- 'gun_in_hand',
36
- 'gun_not_in_hand',
37
- 'animated_gun',
38
- 'knife_in_hand',
39
- 'knife_not_in_hand',
40
- 'culinary_knife_not_in_hand',
41
- 'culinary_knife_in_hand',
42
- 'very_bloody',
43
- 'a_little_bloody',
44
- 'other_blood',
45
- 'hanging',
46
- 'noose',
47
- 'human_corpse',
48
- 'animated_corpse',
49
- 'emaciated_body',
50
- 'self_harm',
51
- 'animal_abuse',
52
- 'fights',
53
- 'garm_death_injury_or_military_conflict'
54
- ];
55
- // Generates thumbnail URLs at regular intervals based on video duration
56
- function getThumbnailUrls(playbackId, duration, options = {}) {
57
- const { interval = 10, width = 640 } = options;
58
- const timestamps = [];
59
- if (duration <= 50) {
60
- // Short videos: 5 evenly spaced thumbnails
61
- const spacing = duration / 6;
62
- for (let i = 1; i <= 5; i++) {
63
- timestamps.push(Math.round(i * spacing));
64
- }
65
- }
66
- else {
67
- // Longer videos: one thumbnail every interval seconds
68
- for (let time = 0; time < duration; time += interval) {
69
- timestamps.push(time);
70
- }
71
- }
72
- return timestamps.map((time) => `https://image.mux.com/${playbackId}/thumbnail.png?time=${time}&width=${width}`);
73
- }
74
- // Sends thumbnail URLs to OpenAI moderation API with concurrency limiting
75
- async function requestOpenAIModeration(imageUrls, openaiClient, model, maxConcurrent = 5, submissionMode = 'url', downloadOptions) {
76
- // If using base64 mode, download all images first
77
- if (submissionMode === 'base64') {
78
- try {
79
- const downloadResults = await downloadImagesAsBase64(imageUrls, downloadOptions, maxConcurrent);
80
- // Process each downloaded image through OpenAI moderation
81
- const processor = async (downloadResult) => {
82
- try {
83
- const moderation = await openaiClient.moderations.create({
84
- model,
85
- input: [
86
- {
87
- type: "image_url",
88
- image_url: {
89
- url: downloadResult.base64Data, // Use base64 data URI
90
- },
91
- },
92
- ],
93
- });
94
- const categoryScores = moderation.results[0].category_scores;
95
- return {
96
- url: downloadResult.url, // Return original URL for tracking
97
- sexual: categoryScores.sexual || 0,
98
- violence: categoryScores.violence || 0,
99
- error: false
100
- };
101
- }
102
- catch (error) {
103
- console.error(`Failed to moderate downloaded image ${downloadResult.url}:`, error);
104
- return {
105
- url: downloadResult.url,
106
- sexual: 0,
107
- violence: 0,
108
- error: true,
109
- };
110
- }
111
- };
112
- return processConcurrently(downloadResults, processor, maxConcurrent);
113
- }
114
- catch (error) {
115
- console.error('Failed to download images for base64 submission:', error);
116
- // Return error results for all URLs
117
- return imageUrls.map(url => ({
118
- url,
119
- sexual: 0,
120
- violence: 0,
121
- error: true,
122
- }));
123
- }
124
- }
125
- // Original URL-based submission mode
126
- const processor = async (url) => {
127
- try {
128
- const moderation = await openaiClient.moderations.create({
129
- model,
130
- input: [
131
- {
132
- type: "image_url",
133
- image_url: {
134
- url: url,
135
- },
136
- },
137
- ],
138
- });
139
- const categoryScores = moderation.results[0].category_scores;
140
- return {
141
- url,
142
- sexual: categoryScores.sexual || 0,
143
- violence: categoryScores.violence || 0,
144
- error: false
145
- };
146
- }
147
- catch (error) {
148
- console.error("Failed to moderate image:", error);
149
- return {
150
- url,
151
- sexual: 0,
152
- violence: 0,
153
- error: true,
154
- };
155
- }
156
- };
157
- return processConcurrently(imageUrls, processor, maxConcurrent);
158
- }
159
- // Sends thumbnail URLs to Hive moderation API with concurrency limiting
160
- async function requestHiveModeration(imageUrls, hiveApiKey, maxConcurrent = 5, submissionMode = 'url', downloadOptions) {
161
- // If using base64 mode, download all images first and upload via multipart/form-data
162
- if (submissionMode === 'base64') {
163
- try {
164
- const downloadResults = await downloadImagesAsBase64(imageUrls, downloadOptions, maxConcurrent);
165
- // Process each downloaded image through Hive moderation via file upload
166
- const processor = async (downloadResult) => {
167
- try {
168
- // Create form data with image buffer
169
- const formData = new FormData();
170
- // Create a Blob from the buffer for form data
171
- const imageBlob = new Blob([downloadResult.buffer], {
172
- type: downloadResult.contentType
173
- });
174
- // Get file extension from content type
175
- const extension = downloadResult.contentType.split('/')[1] || 'png';
176
- formData.append('media', imageBlob, `image.${extension}`);
177
- const response = await fetch('https://api.thehive.ai/api/v2/task/sync', {
178
- method: 'POST',
179
- headers: {
180
- 'Authorization': `Token ${hiveApiKey}`,
181
- // Don't set Content-Type header - let fetch set it with boundary for multipart
182
- },
183
- body: formData
184
- });
185
- if (!response.ok) {
186
- throw new Error(`Hive API error: ${response.statusText}`);
187
- }
188
- const hiveResult = await response.json();
189
- // Extract scores from Hive response and map to OpenAI format
190
- const classes = hiveResult.status?.[0]?.response?.output?.[0]?.classes || [];
191
- const scoreMap = Object.fromEntries(classes.map((c) => [c.class, c.score]));
192
- const sexualScores = HIVE_SEXUAL_CATEGORIES.map(category => scoreMap[category] || 0);
193
- const violenceScores = HIVE_VIOLENCE_CATEGORIES.map(category => scoreMap[category] || 0);
194
- return {
195
- url: downloadResult.url, // Return original URL for tracking
196
- sexual: Math.max(...sexualScores, 0),
197
- violence: Math.max(...violenceScores, 0),
198
- error: false
199
- };
200
- }
201
- catch (error) {
202
- console.error(`Failed to moderate uploaded image ${downloadResult.url}:`, error);
203
- return {
204
- url: downloadResult.url,
205
- sexual: 0,
206
- violence: 0,
207
- error: true,
208
- };
209
- }
210
- };
211
- return processConcurrently(downloadResults, processor, maxConcurrent);
212
- }
213
- catch (error) {
214
- console.error('Failed to download images for Hive multipart upload:', error);
215
- // Return error results for all URLs
216
- return imageUrls.map(url => ({
217
- url,
218
- sexual: 0,
219
- violence: 0,
220
- error: true,
221
- }));
222
- }
223
- }
224
- // Original URL-based submission mode
225
- const processor = async (url) => {
226
- try {
227
- const response = await fetch('https://api.thehive.ai/api/v2/task/sync', {
228
- method: 'POST',
229
- headers: {
230
- 'Authorization': `Token ${hiveApiKey}`,
231
- 'Content-Type': 'application/json'
232
- },
233
- body: JSON.stringify({ url })
234
- });
235
- if (!response.ok) {
236
- throw new Error(`Hive API error: ${response.statusText}`);
237
- }
238
- const hiveResult = await response.json();
239
- // Extract scores from Hive response and map to OpenAI format
240
- // Hive returns scores in status[0].response.output[0].classes as array of {class, score}
241
- const classes = hiveResult.status?.[0]?.response?.output?.[0]?.classes || [];
242
- const scoreMap = Object.fromEntries(classes.map((c) => [c.class, c.score]));
243
- const sexualScores = HIVE_SEXUAL_CATEGORIES.map(category => scoreMap[category] || 0);
244
- const violenceScores = HIVE_VIOLENCE_CATEGORIES.map(category => scoreMap[category] || 0);
245
- return {
246
- url,
247
- sexual: Math.max(...sexualScores, 0),
248
- violence: Math.max(...violenceScores, 0),
249
- error: false
250
- };
251
- }
252
- catch (error) {
253
- console.error("Failed to moderate image with Hive:", error);
254
- return {
255
- url,
256
- sexual: 0,
257
- violence: 0,
258
- error: true,
259
- };
260
- }
261
- };
262
- return processConcurrently(imageUrls, processor, maxConcurrent);
263
- }
264
- export async function getModerationScores(assetId, options = {}) {
265
- const { provider = 'openai', model = 'omni-moderation-latest', thresholds = DEFAULT_THRESHOLDS, thumbnailInterval = 10, thumbnailWidth = 640, maxConcurrent = 5, imageSubmissionMode = 'url', imageDownloadOptions, muxTokenId, muxTokenSecret, openaiApiKey, ...config } = options;
266
- if (provider !== 'openai' && provider !== 'hive') {
267
- throw new Error('Only OpenAI and Hive providers are currently supported');
268
- }
269
- // Validate required credentials
270
- const muxId = muxTokenId || process.env.MUX_TOKEN_ID;
271
- const muxSecret = muxTokenSecret || process.env.MUX_TOKEN_SECRET;
272
- const openaiKey = openaiApiKey || process.env.OPENAI_API_KEY;
273
- const hiveKey = options.hiveApiKey || process.env.HIVE_API_KEY;
274
- if (!muxId || !muxSecret) {
275
- throw new Error('Mux credentials are required. Provide muxTokenId and muxTokenSecret in options or set MUX_TOKEN_ID and MUX_TOKEN_SECRET environment variables.');
276
- }
277
- if (provider === 'openai' && !openaiKey) {
278
- throw new Error('OpenAI API key is required for OpenAI provider. Provide openaiApiKey in options or set OPENAI_API_KEY environment variable.');
279
- }
280
- if (provider === 'hive' && !hiveKey) {
281
- throw new Error('Hive API key is required for Hive provider. Provide hiveApiKey in options or set HIVE_API_KEY environment variable.');
282
- }
283
- // Initialize clients
284
- const mux = new Mux({
285
- tokenId: muxId,
286
- tokenSecret: muxSecret,
287
- });
288
- let openaiClient;
289
- if (provider === 'openai') {
290
- openaiClient = new OpenAI({
291
- apiKey: openaiKey,
292
- });
293
- }
294
- // Fetch asset data from Mux
295
- let assetData;
296
- try {
297
- const asset = await mux.video.assets.retrieve(assetId);
298
- assetData = asset;
299
- }
300
- catch (error) {
301
- throw new Error(`Failed to fetch asset from Mux: ${error instanceof Error ? error.message : 'Unknown error'}`);
302
- }
303
- // Get playback ID - prefer public playback IDs
304
- const publicPlaybackIds = assetData.playback_ids?.filter(pid => pid.policy === 'public') || [];
305
- if (publicPlaybackIds.length === 0) {
306
- throw new Error('No public playback IDs found for this asset. Moderation requires public playback access.');
307
- }
308
- const playbackId = publicPlaybackIds[0].id;
309
- const duration = assetData.duration || 0;
310
- // Generate thumbnail URLs
311
- const thumbnailUrls = getThumbnailUrls(playbackId, duration, {
312
- interval: thumbnailInterval,
313
- width: thumbnailWidth
314
- });
315
- // Request moderation for all thumbnails
316
- let thumbnailScores;
317
- if (provider === 'openai') {
318
- thumbnailScores = await requestOpenAIModeration(thumbnailUrls, openaiClient, model, maxConcurrent, imageSubmissionMode, imageDownloadOptions);
319
- }
320
- else if (provider === 'hive') {
321
- thumbnailScores = await requestHiveModeration(thumbnailUrls, hiveKey, maxConcurrent, imageSubmissionMode, imageDownloadOptions);
322
- }
323
- else {
324
- throw new Error('Unsupported provider');
325
- }
326
- // Find highest scores across all thumbnails
327
- const maxSexual = Math.max(...thumbnailScores.map(s => s.sexual));
328
- const maxViolence = Math.max(...thumbnailScores.map(s => s.violence));
329
- const finalThresholds = { ...DEFAULT_THRESHOLDS, ...thresholds };
330
- return {
331
- assetId,
332
- thumbnailScores,
333
- maxScores: {
334
- sexual: maxSexual,
335
- violence: maxViolence
336
- },
337
- exceedsThreshold: maxSexual > finalThresholds.sexual || maxViolence > finalThresholds.violence,
338
- thresholds: finalThresholds
339
- };
340
- }
341
- //# sourceMappingURL=moderation.js.map