SamirDze commited on
Commit
3da52e9
·
verified ·
1 Parent(s): 8c565e5

Upload 4 files

Browse files
Files changed (2) hide show
  1. app.py +109 -26
  2. requirements.txt +2 -1
app.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
- CLIP Image Embedding API - Lightweight version for HF Spaces free tier
3
- Supports both URL and base64 image input
4
  """
5
 
6
  import gradio as gr
@@ -10,7 +10,8 @@ from transformers import CLIPProcessor, CLIPModel
10
  import requests
11
  from io import BytesIO
12
  import base64
13
- import re
 
14
 
15
  # Use CPU and smaller memory footprint
16
  model = None
@@ -24,58 +25,140 @@ def load_model():
24
  model.eval()
25
  return model, processor
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  def get_embedding(image_input: str):
28
- """Get CLIP embedding from image URL or base64 string"""
29
  try:
30
  if not image_input:
31
- return {"success": False, "error": "Please provide an image URL or base64 string"}
32
 
33
  # Load model on first use
34
  model, processor = load_model()
35
 
36
- image = None
 
 
 
 
 
 
 
 
 
37
 
38
  # Check if it's base64 (data:image/... or raw base64)
39
- if image_input.startswith('data:image'):
40
- # Extract base64 data after the comma
41
  base64_data = image_input.split(',')[1] if ',' in image_input else image_input
42
  image_bytes = base64.b64decode(base64_data)
43
- image = Image.open(BytesIO(image_bytes)).convert('RGB')
 
44
  elif not image_input.startswith('http'):
45
  # Try as raw base64
46
  try:
47
  image_bytes = base64.b64decode(image_input)
48
- image = Image.open(BytesIO(image_bytes)).convert('RGB')
49
  except:
50
  return {"success": False, "error": "Invalid input: provide URL or base64"}
51
  else:
52
- # It's a URL - download it
53
  response = requests.get(image_input, timeout=30)
54
- image = Image.open(BytesIO(response.content)).convert('RGB')
55
 
56
- # Get embedding
57
- inputs = processor(images=image, return_tensors="pt")
58
- with torch.no_grad():
59
- features = model.get_image_features(**inputs)
 
 
 
 
 
 
60
 
61
- # Normalize
62
- embedding = features / features.norm(dim=-1, keepdim=True)
63
-
64
- return {
65
- "success": True,
66
- "embedding": embedding[0].tolist(),
67
- "dimensions": 512
68
- }
 
 
 
 
 
 
 
 
 
 
 
69
  except Exception as e:
70
  return {"success": False, "error": str(e)}
71
 
72
  # Gradio interface with API enabled
73
  demo = gr.Interface(
74
  fn=get_embedding,
75
- inputs=gr.Textbox(label="Image (URL or base64)", placeholder="https://example.com/image.jpg or data:image/jpeg;base64,..."),
 
 
 
76
  outputs=gr.JSON(label="Result"),
77
  title="CLIP Embedding API",
78
- description="Get 512-dim CLIP embeddings from image URL or base64",
79
  api_name="predict"
80
  )
81
 
 
1
  """
2
+ CLIP Image & Video Embedding API - Lightweight version for HF Spaces free tier
3
+ Supports URL, base64 image input, and video URLs (extracts frames)
4
  """
5
 
6
  import gradio as gr
 
10
  import requests
11
  from io import BytesIO
12
  import base64
13
+ import tempfile
14
+ import os
15
 
16
  # Use CPU and smaller memory footprint
17
  model = None
 
25
  model.eval()
26
  return model, processor
27
 
28
+ def extract_video_frames(video_url: str, num_frames: int = 3):
29
+ """Extract frames from video URL using cv2"""
30
+ try:
31
+ import cv2
32
+ import numpy as np
33
+
34
+ # Download video to temp file
35
+ response = requests.get(video_url, timeout=60, stream=True)
36
+ with tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) as tmp:
37
+ for chunk in response.iter_content(chunk_size=8192):
38
+ tmp.write(chunk)
39
+ tmp_path = tmp.name
40
+
41
+ # Open video
42
+ cap = cv2.VideoCapture(tmp_path)
43
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
44
+
45
+ if total_frames == 0:
46
+ os.unlink(tmp_path)
47
+ return []
48
+
49
+ # Calculate frame positions (start, middle, end)
50
+ if num_frames == 1:
51
+ positions = [0]
52
+ elif num_frames == 2:
53
+ positions = [0, total_frames - 1]
54
+ else:
55
+ positions = [0, total_frames // 2, max(0, total_frames - 10)]
56
+
57
+ frames = []
58
+ for pos in positions[:num_frames]:
59
+ cap.set(cv2.CAP_PROP_POS_FRAMES, pos)
60
+ ret, frame = cap.read()
61
+ if ret:
62
+ # Convert BGR to RGB
63
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
64
+ pil_image = Image.fromarray(frame_rgb)
65
+ frames.append(pil_image)
66
+
67
+ cap.release()
68
+ os.unlink(tmp_path)
69
+
70
+ return frames
71
+ except Exception as e:
72
+ print(f"Video frame extraction error: {e}")
73
+ return []
74
+
75
+ def is_video_url(url: str) -> bool:
76
+ """Check if URL is a video"""
77
+ video_extensions = ['.mp4', '.mov', '.avi', '.webm', '.mkv']
78
+ url_lower = url.lower()
79
+ return any(ext in url_lower for ext in video_extensions) or '/video/' in url_lower
80
+
81
  def get_embedding(image_input: str):
82
+ """Get CLIP embedding from image URL, base64 string, or video URL"""
83
  try:
84
  if not image_input:
85
+ return {"success": False, "error": "Please provide an image/video URL or base64 string"}
86
 
87
  # Load model on first use
88
  model, processor = load_model()
89
 
90
+ images = []
91
+ is_video = False
92
+
93
+ # Check if it's a video URL
94
+ if image_input.startswith('http') and is_video_url(image_input):
95
+ is_video = True
96
+ frames = extract_video_frames(image_input, num_frames=3)
97
+ if not frames:
98
+ return {"success": False, "error": "Could not extract frames from video"}
99
+ images = frames
100
 
101
  # Check if it's base64 (data:image/... or raw base64)
102
+ elif image_input.startswith('data:image'):
 
103
  base64_data = image_input.split(',')[1] if ',' in image_input else image_input
104
  image_bytes = base64.b64decode(base64_data)
105
+ images = [Image.open(BytesIO(image_bytes)).convert('RGB')]
106
+
107
  elif not image_input.startswith('http'):
108
  # Try as raw base64
109
  try:
110
  image_bytes = base64.b64decode(image_input)
111
+ images = [Image.open(BytesIO(image_bytes)).convert('RGB')]
112
  except:
113
  return {"success": False, "error": "Invalid input: provide URL or base64"}
114
  else:
115
+ # It's an image URL - download it
116
  response = requests.get(image_input, timeout=30)
117
+ images = [Image.open(BytesIO(response.content)).convert('RGB')]
118
 
119
+ # Get embeddings for all images/frames
120
+ all_embeddings = []
121
+ for img in images:
122
+ inputs = processor(images=img, return_tensors="pt")
123
+ with torch.no_grad():
124
+ features = model.get_image_features(**inputs)
125
+
126
+ # Normalize
127
+ embedding = features / features.norm(dim=-1, keepdim=True)
128
+ all_embeddings.append(embedding[0].tolist())
129
 
130
+ # For single image, return single embedding
131
+ # For video, return array of frame embeddings
132
+ if len(all_embeddings) == 1:
133
+ return {
134
+ "success": True,
135
+ "embedding": all_embeddings[0],
136
+ "dimensions": 512,
137
+ "type": "image"
138
+ }
139
+ else:
140
+ return {
141
+ "success": True,
142
+ "embeddings": all_embeddings,
143
+ "embedding": all_embeddings[0], # First frame as default
144
+ "dimensions": 512,
145
+ "frames": len(all_embeddings),
146
+ "type": "video"
147
+ }
148
+
149
  except Exception as e:
150
  return {"success": False, "error": str(e)}
151
 
152
  # Gradio interface with API enabled
153
  demo = gr.Interface(
154
  fn=get_embedding,
155
+ inputs=gr.Textbox(
156
+ label="Image/Video (URL or base64)",
157
+ placeholder="https://example.com/image.jpg or video.mp4 or data:image/jpeg;base64,..."
158
+ ),
159
  outputs=gr.JSON(label="Result"),
160
  title="CLIP Embedding API",
161
+ description="Get 512-dim CLIP embeddings from image URL, base64, or video URL (extracts 3 frames)",
162
  api_name="predict"
163
  )
164
 
requirements.txt CHANGED
@@ -1,4 +1,5 @@
1
  torch
2
  transformers
3
  Pillow
4
- requests
 
 
1
  torch
2
  transformers
3
  Pillow
4
+ requests
5
+ opencv-python-headless