File size: 5,980 Bytes
3fc013c
3da52e9
 
3fc013c
 
0a9b7fb
3fc013c
 
 
 
 
8c565e5
3da52e9
 
3fc013c
0a9b7fb
 
 
 
3fc013c
0a9b7fb
 
 
 
 
3fc013c
 
3da52e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8c565e5
3da52e9
3fc013c
8c565e5
3da52e9
0a9b7fb
 
 
 
3da52e9
 
 
 
 
 
 
 
 
 
8c565e5
 
3da52e9
8c565e5
 
3da52e9
 
8c565e5
 
 
 
3da52e9
8c565e5
 
 
3da52e9
8c565e5
3da52e9
0a9b7fb
3da52e9
 
 
 
 
 
 
 
 
 
0a9b7fb
3da52e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3fc013c
0a9b7fb
 
59d403e
0a9b7fb
8c565e5
3da52e9
 
 
 
0a9b7fb
 
3da52e9
59d403e
0a9b7fb
3fc013c
0a9b7fb
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
"""

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

# Use CPU and smaller memory footprint
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
        
        # Download video to temp file
        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
        
        # Open video
        cap = cv2.VideoCapture(tmp_path)
        total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        
        if total_frames == 0:
            os.unlink(tmp_path)
            return []
        
        # Calculate frame positions (start, middle, end)
        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:
                # Convert BGR to RGB
                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"}
        
        # Load model on first use
        model, processor = load_model()
        
        images = []
        is_video = False
        
        # Check if it's a video URL
        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
        
        # Check if it's base64 (data:image/... or raw base64)
        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 as raw base64
            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:
            # It's an image URL - download it
            response = requests.get(image_input, timeout=30)
            images = [Image.open(BytesIO(response.content)).convert('RGB')]
        
        # Get embeddings for all images/frames
        all_embeddings = []
        for img in images:
            inputs = processor(images=img, return_tensors="pt")
            with torch.no_grad():
                features = model.get_image_features(**inputs)
            
            # Normalize
            embedding = features / features.norm(dim=-1, keepdim=True)
            all_embeddings.append(embedding[0].tolist())
        
        # For single image, return single embedding
        # For video, return array of frame embeddings
        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],  # First frame as default
                "dimensions": 512,
                "frames": len(all_embeddings),
                "type": "video"
            }
            
    except Exception as e:
        return {"success": False, "error": str(e)}

# Gradio interface with API enabled
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()