| """
|
| CLIP Image & Video Embedding API - Lightweight version for HF Spaces free tier
|
| Supports URL, base64 image input, and video URLs (extracts frames)
|
| """
|
|
|
| import gradio as gr
|
| import torch
|
| from PIL import Image
|
| from transformers import CLIPProcessor, CLIPModel
|
| import requests
|
| from io import BytesIO
|
| import base64
|
| import tempfile
|
| import os
|
|
|
|
|
| model = None
|
| processor = None
|
|
|
| def load_model():
|
| global model, processor
|
| if model is None:
|
| model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
|
| processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
|
| model.eval()
|
| return model, processor
|
|
|
| def extract_video_frames(video_url: str, num_frames: int = 3):
|
| """Extract frames from video URL using cv2"""
|
| try:
|
| import cv2
|
| import numpy as np
|
|
|
|
|
| response = requests.get(video_url, timeout=60, stream=True)
|
| with tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) as tmp:
|
| for chunk in response.iter_content(chunk_size=8192):
|
| tmp.write(chunk)
|
| tmp_path = tmp.name
|
|
|
|
|
| cap = cv2.VideoCapture(tmp_path)
|
| total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
|
| if total_frames == 0:
|
| os.unlink(tmp_path)
|
| return []
|
|
|
|
|
| if num_frames == 1:
|
| positions = [0]
|
| elif num_frames == 2:
|
| positions = [0, total_frames - 1]
|
| else:
|
| positions = [0, total_frames // 2, max(0, total_frames - 10)]
|
|
|
| frames = []
|
| for pos in positions[:num_frames]:
|
| cap.set(cv2.CAP_PROP_POS_FRAMES, pos)
|
| ret, frame = cap.read()
|
| if ret:
|
|
|
| frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| pil_image = Image.fromarray(frame_rgb)
|
| frames.append(pil_image)
|
|
|
| cap.release()
|
| os.unlink(tmp_path)
|
|
|
| return frames
|
| except Exception as e:
|
| print(f"Video frame extraction error: {e}")
|
| return []
|
|
|
| def is_video_url(url: str) -> bool:
|
| """Check if URL is a video"""
|
| video_extensions = ['.mp4', '.mov', '.avi', '.webm', '.mkv']
|
| url_lower = url.lower()
|
| return any(ext in url_lower for ext in video_extensions) or '/video/' in url_lower
|
|
|
| def get_embedding(image_input: str):
|
| """Get CLIP embedding from image URL, base64 string, or video URL"""
|
| try:
|
| if not image_input:
|
| return {"success": False, "error": "Please provide an image/video URL or base64 string"}
|
|
|
|
|
| model, processor = load_model()
|
|
|
| images = []
|
| is_video = False
|
|
|
|
|
| if image_input.startswith('http') and is_video_url(image_input):
|
| is_video = True
|
| frames = extract_video_frames(image_input, num_frames=3)
|
| if not frames:
|
| return {"success": False, "error": "Could not extract frames from video"}
|
| images = frames
|
|
|
|
|
| elif image_input.startswith('data:image'):
|
| base64_data = image_input.split(',')[1] if ',' in image_input else image_input
|
| image_bytes = base64.b64decode(base64_data)
|
| images = [Image.open(BytesIO(image_bytes)).convert('RGB')]
|
|
|
| elif not image_input.startswith('http'):
|
|
|
| try:
|
| image_bytes = base64.b64decode(image_input)
|
| images = [Image.open(BytesIO(image_bytes)).convert('RGB')]
|
| except:
|
| return {"success": False, "error": "Invalid input: provide URL or base64"}
|
| else:
|
|
|
| response = requests.get(image_input, timeout=30)
|
| images = [Image.open(BytesIO(response.content)).convert('RGB')]
|
|
|
|
|
| all_embeddings = []
|
| for img in images:
|
| inputs = processor(images=img, return_tensors="pt")
|
| with torch.no_grad():
|
| features = model.get_image_features(**inputs)
|
|
|
|
|
| embedding = features / features.norm(dim=-1, keepdim=True)
|
| all_embeddings.append(embedding[0].tolist())
|
|
|
|
|
|
|
| if len(all_embeddings) == 1:
|
| return {
|
| "success": True,
|
| "embedding": all_embeddings[0],
|
| "dimensions": 512,
|
| "type": "image"
|
| }
|
| else:
|
| return {
|
| "success": True,
|
| "embeddings": all_embeddings,
|
| "embedding": all_embeddings[0],
|
| "dimensions": 512,
|
| "frames": len(all_embeddings),
|
| "type": "video"
|
| }
|
|
|
| except Exception as e:
|
| return {"success": False, "error": str(e)}
|
|
|
|
|
| demo = gr.Interface(
|
| fn=get_embedding,
|
| inputs=gr.Textbox(
|
| label="Image/Video (URL or base64)",
|
| placeholder="https://example.com/image.jpg or video.mp4 or data:image/jpeg;base64,..."
|
| ),
|
| outputs=gr.JSON(label="Result"),
|
| title="CLIP Embedding API",
|
| description="Get 512-dim CLIP embeddings from image URL, base64, or video URL (extracts 3 frames)",
|
| api_name="predict"
|
| )
|
|
|
| if __name__ == "__main__":
|
| demo.launch()
|
|
|