Tutorial
Tutorial
Tutorial
Building Video Semantic Recommendation with TwelveLabs Embedding and Qdrant Search


Hrishikesh Yadav
Hrishikesh Yadav
Hrishikesh Yadav
In this tutorial, we'll build a recommendation system that understands video content at a semantic level. By combining TwelveLabs' video embedding capabilities with Qdrant vector similarity search, we'll create an engine that finds relevant videos based on their actual meaning—not just keyword matches.
In this tutorial, we'll build a recommendation system that understands video content at a semantic level. By combining TwelveLabs' video embedding capabilities with Qdrant vector similarity search, we'll create an engine that finds relevant videos based on their actual meaning—not just keyword matches.


Join our newsletter
Receive the latest advancements, tutorials, and industry insights in video understanding
Apr 11, 2025
Apr 11, 2025
Apr 11, 2025
10 Min
10 Min
10 Min
Copy link to article
Copy link to article
Copy link to article
Introduction
What if your content recommendation system could truly understand what's happening inside videos, instead of just relying on tags, transcriptions, and keywords? 🔍
In this tutorial, we'll build a recommendation system that understands video content at a semantic level. By combining TwelveLabs' video embedding capabilities with Qdrant vector similarity search, we'll create an engine that finds relevant videos based on their actual meaning—not just keyword matches.
Let's explore how this application works and how you can build similar solutions using the TwelveLabs Python SDK and Qdrant Cloud Quickstart.
You can explore the demo of the application here: TwelveLabs Content Recommendation

Prerequisites
Generate an API key by signing up at the TwelveLabs Playground.
Set up Qdrant Cloud by following the Setup Guide.
Find the repository for this application on Github.
You should be familiar with Python, Flask, and Next.js
Demo Application
This demo application showcases the power of semantic video content recommendations. Users can select an intent-based category, and the system suggests relevant content through semantic similarity matching. Users can also specify their mood to receive content that aligns with their emotional state.
The system stores embeddings from a large collection of animated videos across different categories in Qdrant Cloud.
Working of the Application
The application operates in two main stages:
Embedding Generation & Storage – The system creates video embeddings using Marengo 2.7 and generates a public URL via an S3 bucket. These embeddings and their metadata are stored in a Qdrant Cloud Vector database collection.
Search & Retrieval - When users enter their preferences and search query, the system converts the text into embeddings using Marengo 2.7 and performs a semantic search for relevant videos. Users can refine results by adjusting their mood or selecting new preferences, and the system fetches updated recommendations from the Qdrant Cloud collection.

To make the application work as expected, you must insert the embedding data into the collection, or you can refer to the sample data provided here used in this application.
Preparation Steps
Obtain your API key from the TwelveLabs Playground and set up your environment variable.
Clone the project from Github.
Create a .env file containing your TwelveLabs and Qdrant credentials.
Set up a Qdrant Cloud instance. Follow the setup guide for the Qdrant Cloud Cluster.
Once you've completed these steps, you're ready to start developing!
Walkthrough for Content Recommendation App
This tutorial shows you how to build a video content recommendation application. The app uses Next.js for the frontend and Flask API with CORS enabled for the backend. We'll focus on implementing the core backend functionality for content recommendations and setting up the application.
You'll learn how to set up the Qdrant Cloud client, generate and insert embeddings, and interact with the collection to retrieve relevant videos. For detailed code structure and setup instructions, check the README.md on GitHub.
1 - Guide for Embedding Generation and Insertion into Qdrant
Step 1 - Setup and Dependencies
Let's start by installing the necessary dependencies.
Now let's import the libraries we'll need:
import os import uuid import boto3 from botocore.exceptions import ClientError import requests from IPython.display import display, HTML import shutil import pandas as pd from twelvelabs import TwelveLabs from qdrant_client import QdrantClient, models from qdrant_client.models import PointStruct import time
Step 2 - Configuring Services
Let's configure AWS S3, TwelveLabs, and Qdrant. First, set up an AWS S3 bucket to generate public video URLs for streaming. Then, initialize the Qdrant and TwelveLabs clients to enable efficient video embedding and search.
# AWS S3 Configuration AWS_ACCESS_KEY = "YOUR_AWS_ACCESS_KEY" AWS_SECRET_KEY = "YOUR_AWS_SECRET_KEY" AWS_BUCKET_NAME = "YOUR_BUCKET_NAME" AWS_REGION = "us-east-1" # Change to your region # Initialize S3 client s3_client = boto3.client( 's3', aws_access_key_id=AWS_ACCESS_KEY, aws_secret_access_key=AWS_SECRET_KEY, region_name=AWS_REGION ) # Twelve Labs Configuration TWELVE_LABS_API_KEY = "YOUR_TWELVE_LABS_API_KEY" twelvelabs_client = TwelveLabs(api_key=TWELVE_LABS_API_KEY) # Qdrant Configuration QDRANT_HOST = "YOUR_QDRANT_HOST" QDRANT_API_KEY = "YOUR_QDRANT_API_KEY" COLLECTION_NAME = "content_collection" VECTOR_SIZE = 1024 # Size of embeddings from Twelve Labs # Initialize Qdrant client qdrant_client = QdrantClient( url=f"https://{QDRANT_HOST}", api_key=QDRANT_API_KEY, timeout=20, prefer_grpc=False )
When configuring the Qdrant client, set timeout=20
to prevent indefinite waiting and avoid delays. Set prefer_grpc=False
since Flask natively supports HTTP REST APIs rather than gRPC. This ensures smooth communication between Flask and Qdrant using standard HTTP.
Define the video directory and access the MP4 folder within it. This setup is essential for the subsequent processing steps.
# Get a list of video files video_dir = "downloads/video_content" video_files = [f for f in os.listdir(video_dir) if f.endswith('.mp4')]
Step 3 - Uploading Videos to AWS S3
First, we need a function to upload videos to AWS S3 Bucket and generate public URLs. The public URL is returned and stored as metadata for each video.
def upload_to_s3(file_path, filename): try: # Upload the file s3_client.upload_file( file_path, AWS_BUCKET_NAME, f"videos-embed/{filename}", ExtraArgs={ 'ACL': 'public-read', 'ContentType': 'video/mp4' } ) # Generate the public URL url = f"https://{AWS_BUCKET_NAME}.s3.{AWS_REGION}.amazonaws.com/videos-embed/{filename}" print(f"Uploaded to S3: {url}") return url except ClientError as e: print(f"Error uploading to S3: {str(e)}") raise
Step 4 - Generating Video Embedding with Marengo 2.7
Now we'll create a function to generate video embeddings using TwelveLabs Marengo-retrieval-2.7 Engine. You can find the code for the video embedding generation and insertion here.
def create_video_embedding(video_path, max_retries=3, retry_delay=5): if not twelvelabs_client: raise ValueError("Twelve Labs API key not configured") retries = 0 while retries < max_retries: try: print(f"Creating whole video embedding for {video_path}... (Attempt {retries+1}/{max_retries})") # Use video_embedding_scopes parameter set to ["clip", "video"] to get whole video embedding task = twelvelabs_client.embed.task.create( model_name="Marengo-retrieval-2.7", video_file=video_path, video_embedding_scopes=["clip", "video"] ) print(f"Created task: id={task.id}, status={task.status}") task.wait_for_done(sleep_interval=3) task_result = twelvelabs_client.embed.task.retrieve(task.id) if task_result.status != 'ready': raise ValueError(f"Task failed with status: {task_result.status}") return task_result except Exception as e: print(f"Error creating embedding (attempt {retries+1}): {str(e)}") retries += 1 if retries < max_retries: print(f"Retrying in {retry_delay} seconds...") time.sleep(retry_delay) retry_delay *= 2 else: print("Max retries reached, giving up.") raise
The video_embedding_scopes
is set to both clip and video to generate embeddings for the entire video clip.
Step 5 - Insertion of Embeddings into Qdrant with metadata
Let's create a function to store the generated embeddings in our Qdrant vector database. The video embedding is stored as a point with metadata payload, improving searchability. The video URL in the metadata enables streaming.
def store_in_qdrant(task_result, video_id, s3_url, original_filename): if not qdrant_client: raise ValueError("Qdrant client not configured") try: print(f"Processing video embedding for {video_id}...") # The embedding will be in the segments with embedding_scope="video" if task_result.video_embedding and task_result.video_embedding.segments: video_segments = [s for s in task_result.video_embedding.segments if hasattr(s, 'embedding_scope') and s.embedding_scope == 'video'] if video_segments: print(f"Found video-scope embedding") embedding_vector = video_segments[0].embeddings_float else: # If no video scope segment is found, use the first segment as fallback print(f"No video-scope embedding found, using first available segment") embedding_vector = task_result.video_embedding.segments[0].embeddings_float else: raise ValueError("No embeddings found in the response") # Create a unique point structure for Qdrant storage point = PointStruct( id=uuid.uuid4().int & ((1<<64)-1), # Generate a unique 64-bit integer ID vector=embedding_vector, # Store the extracted embedding vector payload={ 'video_id': video_id, 'video_url': s3_url, # Store the public S3 URL of the video 'is_url': True, 'original_filename': original_filename # Save the original filename } ) # Insert the generated embedding point into the Qdrant collection qdrant_client.upsert(collection_name=COLLECTION_NAME, points=[point]) print(f"Stored whole video embedding in Qdrant") return 1 except Exception as e: print(f"Error storing in Qdrant: {str(e)}") raise
Step 6 - Video Processing Pipeline
Now we'll define a streamlined flow that connects all components, processing videos through our pipeline.
Process all videos in a directory through the full pipeline:
Upload to AWS S3 Bucket
Generate embeddings using TwelveLabs
Store embeddings in Qdrant
# Process each video for filename in video_files[:5]: # Process first 5 videos or you can setup as per convenience try: print(f"\nProcessing {filename}...") video_path = os.path.join(video_dir, filename) video_id = f"{str(uuid.uuid4())[:8]}_{filename}" # Upload to S3 s3_url = upload_to_s3(video_path, video_id) # Generate embeddings task_result = create_video_embedding(video_path) # Store in Qdrant store_in_qdrant(task_result, video_id, s3_url, filename) print(f"Successfully processed {filename}") except Exception as e: print(f"Error processing {filename}: {str(e)}")
The video embeddings and metadata are now stored in the Qdrant Cloud. The next step is to connect our application to the collection for retrieval.
2 - Building Search API with Flask
Step 1 - Setup CORS Origin
To enable cross-origin requests, we need to configure CORS (Cross-Origin Resource Sharing) in our Flask application. This allows web clients hosted on different domains to access our API. You can find the complete backend implementation in app.py.
app = Flask(__name__) CORS(app, resources={r"/*": {"origins": "*"}})
Step 2 - Initializing Qdrant Collection
We need to initialize our Qdrant collection if it doesn't exist. This setup ensures our vector database can properly retrieve video embeddings. The TwelveLabs client is initialized to generate search query embeddings for semantic search capability.
# Get credentials from environment variables API_KEY = os.getenv('API_KEY') QDRANT_HOST = os.getenv('QDRANT_HOST') QDRANT_API_KEY = os.getenv('QDRANT_API_KEY') # Qdrant Configuration COLLECTION_NAME = "content_collection" VECTOR_SIZE = 1024 # Dimension of vector embeddings # Initialize clients try: client = TwelveLabs(api_key=API_KEY) qdrant_client = QdrantClient( url=f"https://{QDRANT_HOST}", api_key=QDRANT_API_KEY, timeout=20 ) logger.info("Successfully initialized API clients") except Exception as e: logger.error(f"Failed to initialize clients: {str(e)}") raise def init_qdrant(): try: # Fetch all existing collections collections = qdrant_client.get_collections().collections collection_exists = any(col.name == COLLECTION_NAME for col in collections) if not collection_exists: # Create the collection with specified vector configuration if it doesn't exist qdrant_client.recreate_collection( collection_name=COLLECTION_NAME, vectors_config=VectorParams( size=VECTOR_SIZE, distance=Distance.COSINE # Use cosine similarity for retrieval ) ) logger.info(f"Created collection: {COLLECTION_NAME}") except Exception as e: logger.error(f"Qdrant initialization error: {str(e)}") raise
Step 3 - Creating Simple Search Functionality
This section implements a search endpoint that enables users to search for videos. The endpoint handles search queries, generates embeddings via TwelveLabs, retrieves similar vectors from Qdrant, and returns matching results.

Let's create a search endpoint for video searches.
@app.route('/search', methods=['POST']) def search(): # Ensure the request contains JSON data if not request.is_json: logger.warning("Missing JSON data") return jsonify({'error': 'Request must be JSON format'}), 400 # Get and validate query data = request.get_json() query = data.get('query') if not query: logger.warning("Empty query parameter") return jsonify({'error': 'Missing query parameter'}), 400 logger.info(f"Processing search: '{query}'") try: # Generate embedding for the search query formatted_query = f"Recommend - {query}" embedding_response = client.embed.create( model_name="Marengo-retrieval-2.7", text=formatted_query ) # Get the embedding vector vector = embedding_response.text_embedding.segments[0].embeddings_float # Similarity search from the Qdrant collection query_response = qdrant_client.query_points( collection_name=COLLECTION_NAME, query=vector, limit=10, with_payload=True ) # Extract and format results search_results = query_response.points logger.info(f"Found {len(search_results)} matching results") # If no results, return empty list if not search_results: return jsonify([]) # Build formatted response formatted_results = [] for result in search_results: point_id = result.id score = float(result.score) payload = result.payload formatted_results.append({ 'video_id': payload.get('video_id', f"video_{point_id}"), 'filename': payload.get('original_filename', payload.get('filename', 'video.mp4')), 'start_time': float(payload.get('start_time', 0)), 'end_time': float(payload.get('end_time', 30)), 'score': score, 'confidence': 'high' if score > 0.7 else 'medium', 'url': payload.get('video_url') }) logger.info(f"Returning {len(formatted_results)} results") return jsonify(formatted_results) except Exception as e: logger.exception(f"Search error: {str(e)}") return jsonify({'error': 'Search failed', 'details': str(e)}), 500
Here's how it works:
Receives a search query from the request
Generates an embedding vector using TwelveLabs
Searches for similar vectors in Qdrant
Returns the matching videos in a structured format

More Ideas to Experiment with the Tutorial
Understanding how applications work helps you create innovative products that meet user needs. Here are some potential use cases for video content embeddings:
🎯 Personalized Ad Insertion — Dynamically insert context-relevant ads into videos.
⚙️ Real-Time Similarity Matching — Instantly find similar videos as new content is uploaded.
📊 Trend Analysis & Insights — Cluster and analyze video trends based on embedding patterns.
Conclusion
This tutorial shows how video understanding creates smarter, more accurate content recommendations. Using TwelveLabs for video embeddings and Qdrant for fast vector search, we've built a system that understands video content beyond manual transcription, tags, or keywords. This approach delivers better recommendations, keeps users engaged, and scales easily with large video collections. As an open-source solution, it can be customized for various industries—from education to entertainment and beyond.
Additional Resources
Learn more about the embedding generation engine—Marengo-retrieval-2.7. To explore TwelveLabs further and enhance your understanding of video content analysis, check out these resources:
Try the Integration: Sign up for the TwelveLabs Embed API Open Beta and start building your own AI-powered video applications with Qdrant today.
Explore More Use Cases: Visit the Qdrant Cloud QuickStart Guide to learn how to implement similar workflows tailored to your business needs.
Join the Conversation: Share your feedback on this integration in the TwelveLabs Discord.
Explore Tutorials: Dive deeper into TwelveLabs capabilities with our comprehensive tutorials
We encourage you to use these resources to expand your knowledge and create innovative applications using TwelveLabs video understanding technology.
Introduction
What if your content recommendation system could truly understand what's happening inside videos, instead of just relying on tags, transcriptions, and keywords? 🔍
In this tutorial, we'll build a recommendation system that understands video content at a semantic level. By combining TwelveLabs' video embedding capabilities with Qdrant vector similarity search, we'll create an engine that finds relevant videos based on their actual meaning—not just keyword matches.
Let's explore how this application works and how you can build similar solutions using the TwelveLabs Python SDK and Qdrant Cloud Quickstart.
You can explore the demo of the application here: TwelveLabs Content Recommendation

Prerequisites
Generate an API key by signing up at the TwelveLabs Playground.
Set up Qdrant Cloud by following the Setup Guide.
Find the repository for this application on Github.
You should be familiar with Python, Flask, and Next.js
Demo Application
This demo application showcases the power of semantic video content recommendations. Users can select an intent-based category, and the system suggests relevant content through semantic similarity matching. Users can also specify their mood to receive content that aligns with their emotional state.
The system stores embeddings from a large collection of animated videos across different categories in Qdrant Cloud.
Working of the Application
The application operates in two main stages:
Embedding Generation & Storage – The system creates video embeddings using Marengo 2.7 and generates a public URL via an S3 bucket. These embeddings and their metadata are stored in a Qdrant Cloud Vector database collection.
Search & Retrieval - When users enter their preferences and search query, the system converts the text into embeddings using Marengo 2.7 and performs a semantic search for relevant videos. Users can refine results by adjusting their mood or selecting new preferences, and the system fetches updated recommendations from the Qdrant Cloud collection.

To make the application work as expected, you must insert the embedding data into the collection, or you can refer to the sample data provided here used in this application.
Preparation Steps
Obtain your API key from the TwelveLabs Playground and set up your environment variable.
Clone the project from Github.
Create a .env file containing your TwelveLabs and Qdrant credentials.
Set up a Qdrant Cloud instance. Follow the setup guide for the Qdrant Cloud Cluster.
Once you've completed these steps, you're ready to start developing!
Walkthrough for Content Recommendation App
This tutorial shows you how to build a video content recommendation application. The app uses Next.js for the frontend and Flask API with CORS enabled for the backend. We'll focus on implementing the core backend functionality for content recommendations and setting up the application.
You'll learn how to set up the Qdrant Cloud client, generate and insert embeddings, and interact with the collection to retrieve relevant videos. For detailed code structure and setup instructions, check the README.md on GitHub.
1 - Guide for Embedding Generation and Insertion into Qdrant
Step 1 - Setup and Dependencies
Let's start by installing the necessary dependencies.
Now let's import the libraries we'll need:
import os import uuid import boto3 from botocore.exceptions import ClientError import requests from IPython.display import display, HTML import shutil import pandas as pd from twelvelabs import TwelveLabs from qdrant_client import QdrantClient, models from qdrant_client.models import PointStruct import time
Step 2 - Configuring Services
Let's configure AWS S3, TwelveLabs, and Qdrant. First, set up an AWS S3 bucket to generate public video URLs for streaming. Then, initialize the Qdrant and TwelveLabs clients to enable efficient video embedding and search.
# AWS S3 Configuration AWS_ACCESS_KEY = "YOUR_AWS_ACCESS_KEY" AWS_SECRET_KEY = "YOUR_AWS_SECRET_KEY" AWS_BUCKET_NAME = "YOUR_BUCKET_NAME" AWS_REGION = "us-east-1" # Change to your region # Initialize S3 client s3_client = boto3.client( 's3', aws_access_key_id=AWS_ACCESS_KEY, aws_secret_access_key=AWS_SECRET_KEY, region_name=AWS_REGION ) # Twelve Labs Configuration TWELVE_LABS_API_KEY = "YOUR_TWELVE_LABS_API_KEY" twelvelabs_client = TwelveLabs(api_key=TWELVE_LABS_API_KEY) # Qdrant Configuration QDRANT_HOST = "YOUR_QDRANT_HOST" QDRANT_API_KEY = "YOUR_QDRANT_API_KEY" COLLECTION_NAME = "content_collection" VECTOR_SIZE = 1024 # Size of embeddings from Twelve Labs # Initialize Qdrant client qdrant_client = QdrantClient( url=f"https://{QDRANT_HOST}", api_key=QDRANT_API_KEY, timeout=20, prefer_grpc=False )
When configuring the Qdrant client, set timeout=20
to prevent indefinite waiting and avoid delays. Set prefer_grpc=False
since Flask natively supports HTTP REST APIs rather than gRPC. This ensures smooth communication between Flask and Qdrant using standard HTTP.
Define the video directory and access the MP4 folder within it. This setup is essential for the subsequent processing steps.
# Get a list of video files video_dir = "downloads/video_content" video_files = [f for f in os.listdir(video_dir) if f.endswith('.mp4')]
Step 3 - Uploading Videos to AWS S3
First, we need a function to upload videos to AWS S3 Bucket and generate public URLs. The public URL is returned and stored as metadata for each video.
def upload_to_s3(file_path, filename): try: # Upload the file s3_client.upload_file( file_path, AWS_BUCKET_NAME, f"videos-embed/{filename}", ExtraArgs={ 'ACL': 'public-read', 'ContentType': 'video/mp4' } ) # Generate the public URL url = f"https://{AWS_BUCKET_NAME}.s3.{AWS_REGION}.amazonaws.com/videos-embed/{filename}" print(f"Uploaded to S3: {url}") return url except ClientError as e: print(f"Error uploading to S3: {str(e)}") raise
Step 4 - Generating Video Embedding with Marengo 2.7
Now we'll create a function to generate video embeddings using TwelveLabs Marengo-retrieval-2.7 Engine. You can find the code for the video embedding generation and insertion here.
def create_video_embedding(video_path, max_retries=3, retry_delay=5): if not twelvelabs_client: raise ValueError("Twelve Labs API key not configured") retries = 0 while retries < max_retries: try: print(f"Creating whole video embedding for {video_path}... (Attempt {retries+1}/{max_retries})") # Use video_embedding_scopes parameter set to ["clip", "video"] to get whole video embedding task = twelvelabs_client.embed.task.create( model_name="Marengo-retrieval-2.7", video_file=video_path, video_embedding_scopes=["clip", "video"] ) print(f"Created task: id={task.id}, status={task.status}") task.wait_for_done(sleep_interval=3) task_result = twelvelabs_client.embed.task.retrieve(task.id) if task_result.status != 'ready': raise ValueError(f"Task failed with status: {task_result.status}") return task_result except Exception as e: print(f"Error creating embedding (attempt {retries+1}): {str(e)}") retries += 1 if retries < max_retries: print(f"Retrying in {retry_delay} seconds...") time.sleep(retry_delay) retry_delay *= 2 else: print("Max retries reached, giving up.") raise
The video_embedding_scopes
is set to both clip and video to generate embeddings for the entire video clip.
Step 5 - Insertion of Embeddings into Qdrant with metadata
Let's create a function to store the generated embeddings in our Qdrant vector database. The video embedding is stored as a point with metadata payload, improving searchability. The video URL in the metadata enables streaming.
def store_in_qdrant(task_result, video_id, s3_url, original_filename): if not qdrant_client: raise ValueError("Qdrant client not configured") try: print(f"Processing video embedding for {video_id}...") # The embedding will be in the segments with embedding_scope="video" if task_result.video_embedding and task_result.video_embedding.segments: video_segments = [s for s in task_result.video_embedding.segments if hasattr(s, 'embedding_scope') and s.embedding_scope == 'video'] if video_segments: print(f"Found video-scope embedding") embedding_vector = video_segments[0].embeddings_float else: # If no video scope segment is found, use the first segment as fallback print(f"No video-scope embedding found, using first available segment") embedding_vector = task_result.video_embedding.segments[0].embeddings_float else: raise ValueError("No embeddings found in the response") # Create a unique point structure for Qdrant storage point = PointStruct( id=uuid.uuid4().int & ((1<<64)-1), # Generate a unique 64-bit integer ID vector=embedding_vector, # Store the extracted embedding vector payload={ 'video_id': video_id, 'video_url': s3_url, # Store the public S3 URL of the video 'is_url': True, 'original_filename': original_filename # Save the original filename } ) # Insert the generated embedding point into the Qdrant collection qdrant_client.upsert(collection_name=COLLECTION_NAME, points=[point]) print(f"Stored whole video embedding in Qdrant") return 1 except Exception as e: print(f"Error storing in Qdrant: {str(e)}") raise
Step 6 - Video Processing Pipeline
Now we'll define a streamlined flow that connects all components, processing videos through our pipeline.
Process all videos in a directory through the full pipeline:
Upload to AWS S3 Bucket
Generate embeddings using TwelveLabs
Store embeddings in Qdrant
# Process each video for filename in video_files[:5]: # Process first 5 videos or you can setup as per convenience try: print(f"\nProcessing {filename}...") video_path = os.path.join(video_dir, filename) video_id = f"{str(uuid.uuid4())[:8]}_{filename}" # Upload to S3 s3_url = upload_to_s3(video_path, video_id) # Generate embeddings task_result = create_video_embedding(video_path) # Store in Qdrant store_in_qdrant(task_result, video_id, s3_url, filename) print(f"Successfully processed {filename}") except Exception as e: print(f"Error processing {filename}: {str(e)}")
The video embeddings and metadata are now stored in the Qdrant Cloud. The next step is to connect our application to the collection for retrieval.
2 - Building Search API with Flask
Step 1 - Setup CORS Origin
To enable cross-origin requests, we need to configure CORS (Cross-Origin Resource Sharing) in our Flask application. This allows web clients hosted on different domains to access our API. You can find the complete backend implementation in app.py.
app = Flask(__name__) CORS(app, resources={r"/*": {"origins": "*"}})
Step 2 - Initializing Qdrant Collection
We need to initialize our Qdrant collection if it doesn't exist. This setup ensures our vector database can properly retrieve video embeddings. The TwelveLabs client is initialized to generate search query embeddings for semantic search capability.
# Get credentials from environment variables API_KEY = os.getenv('API_KEY') QDRANT_HOST = os.getenv('QDRANT_HOST') QDRANT_API_KEY = os.getenv('QDRANT_API_KEY') # Qdrant Configuration COLLECTION_NAME = "content_collection" VECTOR_SIZE = 1024 # Dimension of vector embeddings # Initialize clients try: client = TwelveLabs(api_key=API_KEY) qdrant_client = QdrantClient( url=f"https://{QDRANT_HOST}", api_key=QDRANT_API_KEY, timeout=20 ) logger.info("Successfully initialized API clients") except Exception as e: logger.error(f"Failed to initialize clients: {str(e)}") raise def init_qdrant(): try: # Fetch all existing collections collections = qdrant_client.get_collections().collections collection_exists = any(col.name == COLLECTION_NAME for col in collections) if not collection_exists: # Create the collection with specified vector configuration if it doesn't exist qdrant_client.recreate_collection( collection_name=COLLECTION_NAME, vectors_config=VectorParams( size=VECTOR_SIZE, distance=Distance.COSINE # Use cosine similarity for retrieval ) ) logger.info(f"Created collection: {COLLECTION_NAME}") except Exception as e: logger.error(f"Qdrant initialization error: {str(e)}") raise
Step 3 - Creating Simple Search Functionality
This section implements a search endpoint that enables users to search for videos. The endpoint handles search queries, generates embeddings via TwelveLabs, retrieves similar vectors from Qdrant, and returns matching results.

Let's create a search endpoint for video searches.
@app.route('/search', methods=['POST']) def search(): # Ensure the request contains JSON data if not request.is_json: logger.warning("Missing JSON data") return jsonify({'error': 'Request must be JSON format'}), 400 # Get and validate query data = request.get_json() query = data.get('query') if not query: logger.warning("Empty query parameter") return jsonify({'error': 'Missing query parameter'}), 400 logger.info(f"Processing search: '{query}'") try: # Generate embedding for the search query formatted_query = f"Recommend - {query}" embedding_response = client.embed.create( model_name="Marengo-retrieval-2.7", text=formatted_query ) # Get the embedding vector vector = embedding_response.text_embedding.segments[0].embeddings_float # Similarity search from the Qdrant collection query_response = qdrant_client.query_points( collection_name=COLLECTION_NAME, query=vector, limit=10, with_payload=True ) # Extract and format results search_results = query_response.points logger.info(f"Found {len(search_results)} matching results") # If no results, return empty list if not search_results: return jsonify([]) # Build formatted response formatted_results = [] for result in search_results: point_id = result.id score = float(result.score) payload = result.payload formatted_results.append({ 'video_id': payload.get('video_id', f"video_{point_id}"), 'filename': payload.get('original_filename', payload.get('filename', 'video.mp4')), 'start_time': float(payload.get('start_time', 0)), 'end_time': float(payload.get('end_time', 30)), 'score': score, 'confidence': 'high' if score > 0.7 else 'medium', 'url': payload.get('video_url') }) logger.info(f"Returning {len(formatted_results)} results") return jsonify(formatted_results) except Exception as e: logger.exception(f"Search error: {str(e)}") return jsonify({'error': 'Search failed', 'details': str(e)}), 500
Here's how it works:
Receives a search query from the request
Generates an embedding vector using TwelveLabs
Searches for similar vectors in Qdrant
Returns the matching videos in a structured format

More Ideas to Experiment with the Tutorial
Understanding how applications work helps you create innovative products that meet user needs. Here are some potential use cases for video content embeddings:
🎯 Personalized Ad Insertion — Dynamically insert context-relevant ads into videos.
⚙️ Real-Time Similarity Matching — Instantly find similar videos as new content is uploaded.
📊 Trend Analysis & Insights — Cluster and analyze video trends based on embedding patterns.
Conclusion
This tutorial shows how video understanding creates smarter, more accurate content recommendations. Using TwelveLabs for video embeddings and Qdrant for fast vector search, we've built a system that understands video content beyond manual transcription, tags, or keywords. This approach delivers better recommendations, keeps users engaged, and scales easily with large video collections. As an open-source solution, it can be customized for various industries—from education to entertainment and beyond.
Additional Resources
Learn more about the embedding generation engine—Marengo-retrieval-2.7. To explore TwelveLabs further and enhance your understanding of video content analysis, check out these resources:
Try the Integration: Sign up for the TwelveLabs Embed API Open Beta and start building your own AI-powered video applications with Qdrant today.
Explore More Use Cases: Visit the Qdrant Cloud QuickStart Guide to learn how to implement similar workflows tailored to your business needs.
Join the Conversation: Share your feedback on this integration in the TwelveLabs Discord.
Explore Tutorials: Dive deeper into TwelveLabs capabilities with our comprehensive tutorials
We encourage you to use these resources to expand your knowledge and create innovative applications using TwelveLabs video understanding technology.
Introduction
What if your content recommendation system could truly understand what's happening inside videos, instead of just relying on tags, transcriptions, and keywords? 🔍
In this tutorial, we'll build a recommendation system that understands video content at a semantic level. By combining TwelveLabs' video embedding capabilities with Qdrant vector similarity search, we'll create an engine that finds relevant videos based on their actual meaning—not just keyword matches.
Let's explore how this application works and how you can build similar solutions using the TwelveLabs Python SDK and Qdrant Cloud Quickstart.
You can explore the demo of the application here: TwelveLabs Content Recommendation

Prerequisites
Generate an API key by signing up at the TwelveLabs Playground.
Set up Qdrant Cloud by following the Setup Guide.
Find the repository for this application on Github.
You should be familiar with Python, Flask, and Next.js
Demo Application
This demo application showcases the power of semantic video content recommendations. Users can select an intent-based category, and the system suggests relevant content through semantic similarity matching. Users can also specify their mood to receive content that aligns with their emotional state.
The system stores embeddings from a large collection of animated videos across different categories in Qdrant Cloud.
Working of the Application
The application operates in two main stages:
Embedding Generation & Storage – The system creates video embeddings using Marengo 2.7 and generates a public URL via an S3 bucket. These embeddings and their metadata are stored in a Qdrant Cloud Vector database collection.
Search & Retrieval - When users enter their preferences and search query, the system converts the text into embeddings using Marengo 2.7 and performs a semantic search for relevant videos. Users can refine results by adjusting their mood or selecting new preferences, and the system fetches updated recommendations from the Qdrant Cloud collection.

To make the application work as expected, you must insert the embedding data into the collection, or you can refer to the sample data provided here used in this application.
Preparation Steps
Obtain your API key from the TwelveLabs Playground and set up your environment variable.
Clone the project from Github.
Create a .env file containing your TwelveLabs and Qdrant credentials.
Set up a Qdrant Cloud instance. Follow the setup guide for the Qdrant Cloud Cluster.
Once you've completed these steps, you're ready to start developing!
Walkthrough for Content Recommendation App
This tutorial shows you how to build a video content recommendation application. The app uses Next.js for the frontend and Flask API with CORS enabled for the backend. We'll focus on implementing the core backend functionality for content recommendations and setting up the application.
You'll learn how to set up the Qdrant Cloud client, generate and insert embeddings, and interact with the collection to retrieve relevant videos. For detailed code structure and setup instructions, check the README.md on GitHub.
1 - Guide for Embedding Generation and Insertion into Qdrant
Step 1 - Setup and Dependencies
Let's start by installing the necessary dependencies.
Now let's import the libraries we'll need:
import os import uuid import boto3 from botocore.exceptions import ClientError import requests from IPython.display import display, HTML import shutil import pandas as pd from twelvelabs import TwelveLabs from qdrant_client import QdrantClient, models from qdrant_client.models import PointStruct import time
Step 2 - Configuring Services
Let's configure AWS S3, TwelveLabs, and Qdrant. First, set up an AWS S3 bucket to generate public video URLs for streaming. Then, initialize the Qdrant and TwelveLabs clients to enable efficient video embedding and search.
# AWS S3 Configuration AWS_ACCESS_KEY = "YOUR_AWS_ACCESS_KEY" AWS_SECRET_KEY = "YOUR_AWS_SECRET_KEY" AWS_BUCKET_NAME = "YOUR_BUCKET_NAME" AWS_REGION = "us-east-1" # Change to your region # Initialize S3 client s3_client = boto3.client( 's3', aws_access_key_id=AWS_ACCESS_KEY, aws_secret_access_key=AWS_SECRET_KEY, region_name=AWS_REGION ) # Twelve Labs Configuration TWELVE_LABS_API_KEY = "YOUR_TWELVE_LABS_API_KEY" twelvelabs_client = TwelveLabs(api_key=TWELVE_LABS_API_KEY) # Qdrant Configuration QDRANT_HOST = "YOUR_QDRANT_HOST" QDRANT_API_KEY = "YOUR_QDRANT_API_KEY" COLLECTION_NAME = "content_collection" VECTOR_SIZE = 1024 # Size of embeddings from Twelve Labs # Initialize Qdrant client qdrant_client = QdrantClient( url=f"https://{QDRANT_HOST}", api_key=QDRANT_API_KEY, timeout=20, prefer_grpc=False )
When configuring the Qdrant client, set timeout=20
to prevent indefinite waiting and avoid delays. Set prefer_grpc=False
since Flask natively supports HTTP REST APIs rather than gRPC. This ensures smooth communication between Flask and Qdrant using standard HTTP.
Define the video directory and access the MP4 folder within it. This setup is essential for the subsequent processing steps.
# Get a list of video files video_dir = "downloads/video_content" video_files = [f for f in os.listdir(video_dir) if f.endswith('.mp4')]
Step 3 - Uploading Videos to AWS S3
First, we need a function to upload videos to AWS S3 Bucket and generate public URLs. The public URL is returned and stored as metadata for each video.
def upload_to_s3(file_path, filename): try: # Upload the file s3_client.upload_file( file_path, AWS_BUCKET_NAME, f"videos-embed/{filename}", ExtraArgs={ 'ACL': 'public-read', 'ContentType': 'video/mp4' } ) # Generate the public URL url = f"https://{AWS_BUCKET_NAME}.s3.{AWS_REGION}.amazonaws.com/videos-embed/{filename}" print(f"Uploaded to S3: {url}") return url except ClientError as e: print(f"Error uploading to S3: {str(e)}") raise
Step 4 - Generating Video Embedding with Marengo 2.7
Now we'll create a function to generate video embeddings using TwelveLabs Marengo-retrieval-2.7 Engine. You can find the code for the video embedding generation and insertion here.
def create_video_embedding(video_path, max_retries=3, retry_delay=5): if not twelvelabs_client: raise ValueError("Twelve Labs API key not configured") retries = 0 while retries < max_retries: try: print(f"Creating whole video embedding for {video_path}... (Attempt {retries+1}/{max_retries})") # Use video_embedding_scopes parameter set to ["clip", "video"] to get whole video embedding task = twelvelabs_client.embed.task.create( model_name="Marengo-retrieval-2.7", video_file=video_path, video_embedding_scopes=["clip", "video"] ) print(f"Created task: id={task.id}, status={task.status}") task.wait_for_done(sleep_interval=3) task_result = twelvelabs_client.embed.task.retrieve(task.id) if task_result.status != 'ready': raise ValueError(f"Task failed with status: {task_result.status}") return task_result except Exception as e: print(f"Error creating embedding (attempt {retries+1}): {str(e)}") retries += 1 if retries < max_retries: print(f"Retrying in {retry_delay} seconds...") time.sleep(retry_delay) retry_delay *= 2 else: print("Max retries reached, giving up.") raise
The video_embedding_scopes
is set to both clip and video to generate embeddings for the entire video clip.
Step 5 - Insertion of Embeddings into Qdrant with metadata
Let's create a function to store the generated embeddings in our Qdrant vector database. The video embedding is stored as a point with metadata payload, improving searchability. The video URL in the metadata enables streaming.
def store_in_qdrant(task_result, video_id, s3_url, original_filename): if not qdrant_client: raise ValueError("Qdrant client not configured") try: print(f"Processing video embedding for {video_id}...") # The embedding will be in the segments with embedding_scope="video" if task_result.video_embedding and task_result.video_embedding.segments: video_segments = [s for s in task_result.video_embedding.segments if hasattr(s, 'embedding_scope') and s.embedding_scope == 'video'] if video_segments: print(f"Found video-scope embedding") embedding_vector = video_segments[0].embeddings_float else: # If no video scope segment is found, use the first segment as fallback print(f"No video-scope embedding found, using first available segment") embedding_vector = task_result.video_embedding.segments[0].embeddings_float else: raise ValueError("No embeddings found in the response") # Create a unique point structure for Qdrant storage point = PointStruct( id=uuid.uuid4().int & ((1<<64)-1), # Generate a unique 64-bit integer ID vector=embedding_vector, # Store the extracted embedding vector payload={ 'video_id': video_id, 'video_url': s3_url, # Store the public S3 URL of the video 'is_url': True, 'original_filename': original_filename # Save the original filename } ) # Insert the generated embedding point into the Qdrant collection qdrant_client.upsert(collection_name=COLLECTION_NAME, points=[point]) print(f"Stored whole video embedding in Qdrant") return 1 except Exception as e: print(f"Error storing in Qdrant: {str(e)}") raise
Step 6 - Video Processing Pipeline
Now we'll define a streamlined flow that connects all components, processing videos through our pipeline.
Process all videos in a directory through the full pipeline:
Upload to AWS S3 Bucket
Generate embeddings using TwelveLabs
Store embeddings in Qdrant
# Process each video for filename in video_files[:5]: # Process first 5 videos or you can setup as per convenience try: print(f"\nProcessing {filename}...") video_path = os.path.join(video_dir, filename) video_id = f"{str(uuid.uuid4())[:8]}_{filename}" # Upload to S3 s3_url = upload_to_s3(video_path, video_id) # Generate embeddings task_result = create_video_embedding(video_path) # Store in Qdrant store_in_qdrant(task_result, video_id, s3_url, filename) print(f"Successfully processed {filename}") except Exception as e: print(f"Error processing {filename}: {str(e)}")
The video embeddings and metadata are now stored in the Qdrant Cloud. The next step is to connect our application to the collection for retrieval.
2 - Building Search API with Flask
Step 1 - Setup CORS Origin
To enable cross-origin requests, we need to configure CORS (Cross-Origin Resource Sharing) in our Flask application. This allows web clients hosted on different domains to access our API. You can find the complete backend implementation in app.py.
app = Flask(__name__) CORS(app, resources={r"/*": {"origins": "*"}})
Step 2 - Initializing Qdrant Collection
We need to initialize our Qdrant collection if it doesn't exist. This setup ensures our vector database can properly retrieve video embeddings. The TwelveLabs client is initialized to generate search query embeddings for semantic search capability.
# Get credentials from environment variables API_KEY = os.getenv('API_KEY') QDRANT_HOST = os.getenv('QDRANT_HOST') QDRANT_API_KEY = os.getenv('QDRANT_API_KEY') # Qdrant Configuration COLLECTION_NAME = "content_collection" VECTOR_SIZE = 1024 # Dimension of vector embeddings # Initialize clients try: client = TwelveLabs(api_key=API_KEY) qdrant_client = QdrantClient( url=f"https://{QDRANT_HOST}", api_key=QDRANT_API_KEY, timeout=20 ) logger.info("Successfully initialized API clients") except Exception as e: logger.error(f"Failed to initialize clients: {str(e)}") raise def init_qdrant(): try: # Fetch all existing collections collections = qdrant_client.get_collections().collections collection_exists = any(col.name == COLLECTION_NAME for col in collections) if not collection_exists: # Create the collection with specified vector configuration if it doesn't exist qdrant_client.recreate_collection( collection_name=COLLECTION_NAME, vectors_config=VectorParams( size=VECTOR_SIZE, distance=Distance.COSINE # Use cosine similarity for retrieval ) ) logger.info(f"Created collection: {COLLECTION_NAME}") except Exception as e: logger.error(f"Qdrant initialization error: {str(e)}") raise
Step 3 - Creating Simple Search Functionality
This section implements a search endpoint that enables users to search for videos. The endpoint handles search queries, generates embeddings via TwelveLabs, retrieves similar vectors from Qdrant, and returns matching results.

Let's create a search endpoint for video searches.
@app.route('/search', methods=['POST']) def search(): # Ensure the request contains JSON data if not request.is_json: logger.warning("Missing JSON data") return jsonify({'error': 'Request must be JSON format'}), 400 # Get and validate query data = request.get_json() query = data.get('query') if not query: logger.warning("Empty query parameter") return jsonify({'error': 'Missing query parameter'}), 400 logger.info(f"Processing search: '{query}'") try: # Generate embedding for the search query formatted_query = f"Recommend - {query}" embedding_response = client.embed.create( model_name="Marengo-retrieval-2.7", text=formatted_query ) # Get the embedding vector vector = embedding_response.text_embedding.segments[0].embeddings_float # Similarity search from the Qdrant collection query_response = qdrant_client.query_points( collection_name=COLLECTION_NAME, query=vector, limit=10, with_payload=True ) # Extract and format results search_results = query_response.points logger.info(f"Found {len(search_results)} matching results") # If no results, return empty list if not search_results: return jsonify([]) # Build formatted response formatted_results = [] for result in search_results: point_id = result.id score = float(result.score) payload = result.payload formatted_results.append({ 'video_id': payload.get('video_id', f"video_{point_id}"), 'filename': payload.get('original_filename', payload.get('filename', 'video.mp4')), 'start_time': float(payload.get('start_time', 0)), 'end_time': float(payload.get('end_time', 30)), 'score': score, 'confidence': 'high' if score > 0.7 else 'medium', 'url': payload.get('video_url') }) logger.info(f"Returning {len(formatted_results)} results") return jsonify(formatted_results) except Exception as e: logger.exception(f"Search error: {str(e)}") return jsonify({'error': 'Search failed', 'details': str(e)}), 500
Here's how it works:
Receives a search query from the request
Generates an embedding vector using TwelveLabs
Searches for similar vectors in Qdrant
Returns the matching videos in a structured format

More Ideas to Experiment with the Tutorial
Understanding how applications work helps you create innovative products that meet user needs. Here are some potential use cases for video content embeddings:
🎯 Personalized Ad Insertion — Dynamically insert context-relevant ads into videos.
⚙️ Real-Time Similarity Matching — Instantly find similar videos as new content is uploaded.
📊 Trend Analysis & Insights — Cluster and analyze video trends based on embedding patterns.
Conclusion
This tutorial shows how video understanding creates smarter, more accurate content recommendations. Using TwelveLabs for video embeddings and Qdrant for fast vector search, we've built a system that understands video content beyond manual transcription, tags, or keywords. This approach delivers better recommendations, keeps users engaged, and scales easily with large video collections. As an open-source solution, it can be customized for various industries—from education to entertainment and beyond.
Additional Resources
Learn more about the embedding generation engine—Marengo-retrieval-2.7. To explore TwelveLabs further and enhance your understanding of video content analysis, check out these resources:
Try the Integration: Sign up for the TwelveLabs Embed API Open Beta and start building your own AI-powered video applications with Qdrant today.
Explore More Use Cases: Visit the Qdrant Cloud QuickStart Guide to learn how to implement similar workflows tailored to your business needs.
Join the Conversation: Share your feedback on this integration in the TwelveLabs Discord.
Explore Tutorials: Dive deeper into TwelveLabs capabilities with our comprehensive tutorials
We encourage you to use these resources to expand your knowledge and create innovative applications using TwelveLabs video understanding technology.
Related articles
© 2021
-
2025
TwelveLabs, Inc. All Rights Reserved
© 2021
-
2025
TwelveLabs, Inc. All Rights Reserved
© 2021
-
2025
TwelveLabs, Inc. All Rights Reserved