Use the @mux/ai library to generate video embeddings and build a recommendation engine
You can build a content-based recommendation system that suggests similar videos by converting video transcripts into AI embeddings and performing vector similarity search. The @mux/ai library makes this straightforward by handling transcript fetching, chunking, and embedding generation.
The core concept is to convert text (your video transcripts) into high-dimensional vectors (embeddings) that capture semantic meaning. Videos with similar content will have embeddings that are close together in vector space, allowing you to find and recommend similar content.
Before starting, make sure you have:
npm install @mux/aiSet your environment variables:
# Required
MUX_TOKEN_ID=your_mux_token_id
MUX_TOKEN_SECRET=your_mux_token_secret
# You only need the API key for the provider you're using
OPENAI_API_KEY=your_openai_api_key # OR
GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_keyimport { generateVideoEmbeddings } from "@mux/ai/workflows";
const result = await generateVideoEmbeddings("your-mux-asset-id", {
provider: "openai", // or "google"
languageCode: "en"
});
// Use the averaged embedding for video-level search
console.log(result.averagedEmbedding);
// Array of 1536 numbers (for OpenAI's text-embedding-3-small)
// Or use individual chunks for timestamp-accurate search
console.log(result.chunks.length);
console.log(result.chunks[0].embedding);The function returns:
{
"assetId": "your-asset-id",
"averagedEmbedding": [0.123, -0.456, ...], // Single vector representing the whole video
"chunks": [
{
"chunkId": "chunk_0",
"embedding": [0.234, -0.567, ...],
"metadata": {
"tokenCount": 450,
"startTime": 0,
"endTime": 30.5,
"text": "Welcome to our tutorial..."
}
}
// ... more chunks
],
"metadata": {
"totalChunks": 12,
"totalTokens": 5432,
"embeddingDimensions": 1536,
"languageCode": "en"
}
}@mux/ai supports two embedding providers:
text-embedding-3-small model (default 1536 dimensions) - Fast and cost-effectivetext-embedding-004 model (768 dimensions) - Alternative option// Using OpenAI (default)
const result = await generateVideoEmbeddings("your-mux-asset-id", {
provider: "openai"
});
// Using Google
const result = await generateVideoEmbeddings("your-mux-asset-id", {
provider: "google"
});
// Override the default model
const result = await generateVideoEmbeddings("your-mux-asset-id", {
provider: "openai",
model: "text-embedding-3-large" // 3072 dimensions, higher quality
});For long videos, transcripts are split into chunks to fit within embedding model token limits. Chunking strategy affects the granularity of your search results:
// Token-based chunking (default)
const result = await generateVideoEmbeddings("your-mux-asset-id", {
chunkingStrategy: {
type: "token",
maxTokens: 500,
overlap: 100 // Tokens of overlap between chunks
}
});
// VTT-based chunking (preserves caption boundaries)
const result = await generateVideoEmbeddings("your-mux-asset-id", {
chunkingStrategy: {
type: "vtt",
maxTokens: 500,
overlapCues: 2 // Number of caption cues to overlap
}
});import { Pinecone } from '@pinecone-database/pinecone';
import { generateVideoEmbeddings } from "@mux/ai/workflows";
const pinecone = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
const index = pinecone.index('video-recommendations');
// Generate and store embeddings
const result = await generateVideoEmbeddings("your-mux-asset-id");
// Store the averaged embedding for video-level search
await index.upsert([{
id: "your-mux-asset-id",
values: result.averagedEmbedding,
metadata: {
title: "Video Title",
duration: 300,
totalChunks: result.metadata.totalChunks
}
}]);import { createClient } from '@supabase/supabase-js';
import { generateVideoEmbeddings } from "@mux/ai/workflows";
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_KEY
);
// Generate embeddings
const result = await generateVideoEmbeddings("your-mux-asset-id");
// Store in Supabase
await supabase.from('video_embeddings').insert({
asset_id: "your-mux-asset-id",
embedding: result.averagedEmbedding,
metadata: result.metadata
});Once embeddings are stored, use vector similarity search:
// Generate embedding for the query video
const queryResult = await generateVideoEmbeddings(queryAssetId);
// Search for similar videos in Pinecone
const searchResults = await index.query({
vector: queryResult.averagedEmbedding,
topK: 5, // Return 5 most similar videos
includeMetadata: true
});
// Display recommendations
searchResults.matches.forEach(match => {
console.log(`Similar video: ${match.id}`);
console.log(`Similarity score: ${match.score}`);
});For automated embedding generation when videos are uploaded, you should trigger the call to generate video embeddings from the video.asset.track.ready webhook:
export async function handleWebhook(req, res) {
const event = req.body;
if (event.type === 'video.asset.track.ready' &&
event.data.type === 'text' &&
event.data.language_code === 'en') {
const result = await generateVideoEmbeddings(event.data.asset_id);
await vectorDB.upsert({ id: event.data.asset_id, embedding: result.averagedEmbedding });
}
}Under the hood, @mux/ai handles:
Here are a few popular options for storing embeddings: