deenasun commited on
Commit
dadcb61
·
1 Parent(s): adcbc15

add cloudflare upload and base64 for video output response to gradio

Browse files
Files changed (3) hide show
  1. README.md +80 -0
  2. app.py +209 -38
  3. example_usage.py +186 -0
README.md CHANGED
@@ -11,3 +11,83 @@ license: apache-2.0
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
14
+
15
+ # AI-SL API
16
+
17
+ Convert text documents to American Sign Language (ASL) videos using AI.
18
+
19
+ ## Video Output Options
20
+
21
+ The Gradio interface provides multiple ways for users to receive and download the generated ASL videos:
22
+
23
+ ### 1. R2 Cloud Storage (Recommended)
24
+ - Videos are automatically uploaded to Cloudflare R2 storage
25
+ - Returns a public URL that users can download directly
26
+ - Videos persist and can be shared via URL
27
+ - Includes a styled download button in the interface
28
+
29
+ ### 2. Base64 Encoding (Alternative)
30
+ - Videos are embedded as base64 data directly in the response
31
+ - No external storage required
32
+ - Good for smaller videos or when you want to avoid cloud storage
33
+ - Can be downloaded directly from the interface
34
+
35
+ ### 3. Programmatic Access
36
+ Users can access the video output programmatically using:
37
+
38
+ ```python
39
+ from gradio_client import Client
40
+
41
+ # Connect to the running interface
42
+ client = Client("http://localhost:7860")
43
+
44
+ # Upload a document and get results
45
+ result = client.predict(
46
+ "path/to/document.pdf",
47
+ api_name="/predict"
48
+ )
49
+
50
+ # The result contains: (json_data, video_output, download_html)
51
+ json_data, video_url, download_html = result
52
+
53
+ # Download the video
54
+ import requests
55
+ response = requests.get(video_url)
56
+ with open("asl_video.mp4", "wb") as f:
57
+ f.write(response.content)
58
+ ```
59
+
60
+ ### 4. Direct Download from Interface
61
+ - The interface includes a styled download button
62
+ - Users can right-click and "Save As" if automatic download doesn't work
63
+ - Video files are named `asl_video.mp4` by default
64
+
65
+ ## Example Usage
66
+
67
+ See `example_usage.py` for complete examples of how to:
68
+ - Download videos from URLs
69
+ - Process base64 video data
70
+ - Use the interface programmatically
71
+ - Perform further video processing
72
+
73
+ ## Requirements
74
+
75
+ - Python 3.7+
76
+ - Required packages listed in `requirements.txt`
77
+ - Cloudflare R2 credentials (for cloud storage option)
78
+ - Supabase credentials for video database
79
+
80
+ ## Setup
81
+
82
+ 1. Install dependencies: `pip install -r requirements.txt`
83
+ 2. Set up environment variables in `.env` file
84
+ 3. Run the interface: `python app.py`
85
+
86
+ ## Video Processing
87
+
88
+ Once you have the video file, you can:
89
+ - Upload to YouTube, Google Drive, or other services
90
+ - Analyze with OpenCV for computer vision tasks
91
+ - Convert to different formats
92
+ - Extract frames for further processing
93
+ - Add subtitles or overlays
app.py CHANGED
@@ -10,6 +10,8 @@ from botocore.config import Config
10
  from dotenv import load_dotenv
11
  import requests
12
  import tempfile
 
 
13
 
14
  # Load environment variables from .env file
15
  load_dotenv()
@@ -30,7 +32,8 @@ article = ("<p style='text-align: center'><a href='https://github.com/deenasun'
30
  inputs = gr.File(label="Upload Document (pdf, txt, docx, or epub)")
31
  outputs = [
32
  gr.JSON(label="Processing Results"),
33
- gr.Video(label="ASL Video Output")
 
34
  ]
35
 
36
  asl_converter = DocumentToASLConverter()
@@ -57,6 +60,88 @@ def clean_gloss_token(token):
57
  cleaned = cleaned.lower()
58
  return cleaned
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  async def parse_vectorize_and_search(file):
61
  print(file)
62
  gloss = asl_converter.convert_document(file)
@@ -109,61 +194,147 @@ async def parse_vectorize_and_search(file):
109
  # If only one video, just use it directly
110
  stitched_video_path = video_files[0]
111
 
 
 
 
 
 
 
 
112
  # Clean up individual video files after stitching
113
  for video_file in video_files:
114
  if video_file != stitched_video_path: # Don't delete the final output
115
  cleanup_temp_video(video_file)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  return {
118
  "status": "success",
119
  "videos": videos,
120
  "video_count": len(videos),
121
  "gloss": gloss,
122
- "cleaned_tokens": cleaned_tokens
123
- }, stitched_video_path
 
124
 
125
  # Create a synchronous wrapper for Gradio
126
  def parse_vectorize_and_search_sync(file):
127
  return asyncio.run(parse_vectorize_and_search(file))
128
 
129
- def download_video_from_url(video_url):
130
- """
131
- Download a video from a public R2 URL
132
- Returns the local file path where the video is saved
133
- """
134
- try:
135
- # Create a temporary file with .mp4 extension
136
- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
137
- temp_path = temp_file.name
138
- temp_file.close()
139
-
140
- # Download the video
141
- print(f"Downloading video from: {video_url}")
142
- response = requests.get(video_url, stream=True)
143
- response.raise_for_status()
144
-
145
- # Save to temporary file
146
- with open(temp_path, 'wb') as f:
147
- for chunk in response.iter_content(chunk_size=8192):
148
- f.write(chunk)
149
-
150
- print(f"Video downloaded to: {temp_path}")
151
- return temp_path
152
-
153
- except Exception as e:
154
- print(f"Error downloading video: {e}")
155
- return None
156
 
157
- def cleanup_temp_video(file_path):
158
  """
159
- Clean up temporary video file
160
  """
161
- try:
162
- if file_path and os.path.exists(file_path):
163
- os.unlink(file_path)
164
- print(f"Cleaned up: {file_path}")
165
- except Exception as e:
166
- print(f"Error cleaning up file: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
  intf = gr.Interface(
169
  fn=parse_vectorize_and_search_sync,
 
10
  from dotenv import load_dotenv
11
  import requests
12
  import tempfile
13
+ import uuid
14
+ import base64
15
 
16
  # Load environment variables from .env file
17
  load_dotenv()
 
32
  inputs = gr.File(label="Upload Document (pdf, txt, docx, or epub)")
33
  outputs = [
34
  gr.JSON(label="Processing Results"),
35
+ gr.Video(label="ASL Video Output"),
36
+ gr.HTML(label="Download Link")
37
  ]
38
 
39
  asl_converter = DocumentToASLConverter()
 
60
  cleaned = cleaned.lower()
61
  return cleaned
62
 
63
+
64
+ def upload_video_to_r2(video_path, bucket_name="ai-sl-videos"):
65
+ """
66
+ Upload a video file to R2 and return a public URL
67
+ """
68
+ try:
69
+ # Generate a unique filename
70
+ file_extension = os.path.splitext(video_path)[1]
71
+ unique_filename = f"{uuid.uuid4()}{file_extension}"
72
+
73
+ # Upload to R2
74
+ with open(video_path, 'rb') as video_file:
75
+ s3.upload_fileobj(
76
+ video_file,
77
+ bucket_name,
78
+ unique_filename,
79
+ ExtraArgs={'ACL': 'public-read'}
80
+ )
81
+
82
+ # Generate the public URL
83
+ video_url = f"{R2_ENDPOINT}/{bucket_name}/{unique_filename}"
84
+ print(f"Video uploaded to R2: {video_url}")
85
+ return video_url
86
+
87
+ except Exception as e:
88
+ print(f"Error uploading video to R2: {e}")
89
+ return None
90
+
91
+ def video_to_base64(video_path):
92
+ """
93
+ Convert a video file to base64 string for direct download
94
+ """
95
+ try:
96
+ with open(video_path, 'rb') as video_file:
97
+ video_data = video_file.read()
98
+ base64_data = base64.b64encode(video_data).decode('utf-8')
99
+ return f"data:video/mp4;base64,{base64_data}"
100
+ except Exception as e:
101
+ print(f"Error converting video to base64: {e}")
102
+ return None
103
+
104
+ def download_video_from_url(video_url):
105
+ """
106
+ Download a video from a public R2 URL
107
+ Returns the local file path where the video is saved
108
+ """
109
+ try:
110
+ # Create a temporary file with .mp4 extension
111
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
112
+ temp_path = temp_file.name
113
+ temp_file.close()
114
+
115
+ # Download the video
116
+ print(f"Downloading video from: {video_url}")
117
+ response = requests.get(video_url, stream=True)
118
+ response.raise_for_status()
119
+
120
+ # Save to temporary file
121
+ with open(temp_path, 'wb') as f:
122
+ for chunk in response.iter_content(chunk_size=8192):
123
+ f.write(chunk)
124
+
125
+ print(f"Video downloaded to: {temp_path}")
126
+ return temp_path
127
+
128
+ except Exception as e:
129
+ print(f"Error downloading video: {e}")
130
+ return None
131
+
132
+
133
+ def cleanup_temp_video(file_path):
134
+ """
135
+ Clean up temporary video file
136
+ """
137
+ try:
138
+ if file_path and os.path.exists(file_path):
139
+ os.unlink(file_path)
140
+ print(f"Cleaned up: {file_path}")
141
+ except Exception as e:
142
+ print(f"Error cleaning up file: {e}")
143
+
144
+
145
  async def parse_vectorize_and_search(file):
146
  print(file)
147
  gloss = asl_converter.convert_document(file)
 
194
  # If only one video, just use it directly
195
  stitched_video_path = video_files[0]
196
 
197
+ # Upload final video to R2 and get public URL
198
+ final_video_url = None
199
+ if stitched_video_path:
200
+ final_video_url = upload_video_to_r2(stitched_video_path)
201
+ # Clean up the local file after upload
202
+ cleanup_temp_video(stitched_video_path)
203
+
204
  # Clean up individual video files after stitching
205
  for video_file in video_files:
206
  if video_file != stitched_video_path: # Don't delete the final output
207
  cleanup_temp_video(video_file)
208
+
209
+ # Create download link HTML
210
+ download_html = ""
211
+ if final_video_url:
212
+ download_html = f"""
213
+ <div style="text-align: center; padding: 20px;">
214
+ <h3>Download Your ASL Video</h3>
215
+ <a href="{final_video_url}" download="asl_video.mp4"
216
+ style="background-color: #4CAF50; color: white;
217
+ padding: 12px 24px; text-decoration: none;
218
+ border-radius: 4px; display: inline-block;">
219
+ Download Video
220
+ </a>
221
+ <p style="margin-top: 10px; color: #666;">
222
+ <small>Right-click and "Save As" if the download doesn't
223
+ start automatically</small>
224
+ </p>
225
+ </div>
226
+ """
227
 
228
  return {
229
  "status": "success",
230
  "videos": videos,
231
  "video_count": len(videos),
232
  "gloss": gloss,
233
+ "cleaned_tokens": cleaned_tokens,
234
+ "final_video_url": final_video_url
235
+ }, final_video_url, download_html
236
 
237
  # Create a synchronous wrapper for Gradio
238
  def parse_vectorize_and_search_sync(file):
239
  return asyncio.run(parse_vectorize_and_search(file))
240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
+ async def parse_vectorize_and_search_base64(file):
243
  """
244
+ Alternative version that returns video as base64 data instead of uploading to R2
245
  """
246
+ print(file)
247
+ gloss = asl_converter.convert_document(file)
248
+ print("ASL", gloss)
249
+
250
+ # Split by spaces and clean each token
251
+ gloss_tokens = gloss.split()
252
+ cleaned_tokens = []
253
+
254
+ for token in gloss_tokens:
255
+ cleaned = clean_gloss_token(token)
256
+ if cleaned: # Only add non-empty tokens
257
+ cleaned_tokens.append(cleaned)
258
+
259
+ print("Cleaned tokens:", cleaned_tokens)
260
+
261
+ videos = []
262
+ video_files = [] # Store local file paths for stitching
263
+
264
+ for g in cleaned_tokens:
265
+ print(f"Processing {g}")
266
+ try:
267
+ result = await vectorizer.vector_query_from_supabase(query=g)
268
+ print("result", result)
269
+ if result.get("match", False):
270
+ video_url = result["video_url"]
271
+ videos.append(video_url)
272
+
273
+ # Download the video
274
+ local_path = download_video_from_url(video_url)
275
+ if local_path:
276
+ video_files.append(local_path)
277
+
278
+ except Exception as e:
279
+ print(f"Error processing {g}: {e}")
280
+ continue
281
+
282
+ # Create stitched video if we have multiple videos
283
+ stitched_video_path = None
284
+ if len(video_files) > 1:
285
+ try:
286
+ print(f"Creating stitched video from {len(video_files)} videos...")
287
+ stitched_video_path = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4').name
288
+ create_multi_stitched_video(video_files, stitched_video_path)
289
+ print(f"Stitched video created: {stitched_video_path}")
290
+ except Exception as e:
291
+ print(f"Error creating stitched video: {e}")
292
+ stitched_video_path = None
293
+ elif len(video_files) == 1:
294
+ # If only one video, just use it directly
295
+ stitched_video_path = video_files[0]
296
+
297
+ # Convert final video to base64
298
+ final_video_base64 = None
299
+ if stitched_video_path:
300
+ final_video_base64 = video_to_base64(stitched_video_path)
301
+ # Clean up the local file after conversion
302
+ cleanup_temp_video(stitched_video_path)
303
+
304
+ # Clean up individual video files after stitching
305
+ for video_file in video_files:
306
+ if video_file != stitched_video_path: # Don't delete the final output
307
+ cleanup_temp_video(video_file)
308
+
309
+ # Create download link HTML for base64
310
+ download_html = ""
311
+ if final_video_base64:
312
+ download_html = f"""
313
+ <div style="text-align: center; padding: 20px;">
314
+ <h3>Download Your ASL Video</h3>
315
+ <a href="{final_video_base64}" download="asl_video.mp4"
316
+ style="background-color: #4CAF50; color: white;
317
+ padding: 12px 24px; text-decoration: none;
318
+ border-radius: 4px; display: inline-block;">
319
+ Download Video
320
+ </a>
321
+ <p style="margin-top: 10px; color: #666;">
322
+ <small>Video is embedded directly - click to download</small>
323
+ </p>
324
+ </div>
325
+ """
326
+
327
+ return {
328
+ "status": "success",
329
+ "videos": videos,
330
+ "video_count": len(videos),
331
+ "gloss": gloss,
332
+ "cleaned_tokens": cleaned_tokens,
333
+ "video_format": "base64"
334
+ }, final_video_base64, download_html
335
+
336
+ def parse_vectorize_and_search_base64_sync(file):
337
+ return asyncio.run(parse_vectorize_and_search_base64(file))
338
 
339
  intf = gr.Interface(
340
  fn=parse_vectorize_and_search_sync,
example_usage.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Example: How to programmatically access video output from the AI-SL API
3
+
4
+ This file demonstrates different ways users can receive and process video
5
+ output from the Gradio interface.
6
+ """
7
+
8
+ import requests
9
+ import base64
10
+ from pathlib import Path
11
+
12
+
13
+ def download_video_from_url(video_url, output_path="downloaded_video.mp4"):
14
+ """
15
+ Download a video from a public URL
16
+ """
17
+ try:
18
+ response = requests.get(video_url, stream=True)
19
+ response.raise_for_status()
20
+
21
+ with open(output_path, 'wb') as f:
22
+ for chunk in response.iter_content(chunk_size=8192):
23
+ f.write(chunk)
24
+
25
+ print(f"Video downloaded to: {output_path}")
26
+ return output_path
27
+ except Exception as e:
28
+ print(f"Error downloading video: {e}")
29
+ return None
30
+
31
+
32
+ def save_base64_video(base64_data, output_path="video_from_base64.mp4"):
33
+ """
34
+ Save a base64-encoded video to a file
35
+ """
36
+ try:
37
+ # Remove the data URL prefix if present
38
+ if base64_data.startswith('data:video/mp4;base64,'):
39
+ base64_data = base64_data.split(',')[1]
40
+
41
+ # Decode and save
42
+ video_data = base64.b64decode(base64_data)
43
+ with open(output_path, 'wb') as f:
44
+ f.write(video_data)
45
+
46
+ print(f"Video saved from base64 to: {output_path}")
47
+ return output_path
48
+ except Exception as e:
49
+ print(f"Error saving base64 video: {e}")
50
+ return None
51
+
52
+
53
+ def process_gradio_output(gradio_result):
54
+ """
55
+ Process the output from the Gradio interface
56
+
57
+ gradio_result should be a tuple: (json_data, video_output, download_html)
58
+ """
59
+ json_data, video_output, download_html = gradio_result
60
+
61
+ print("Processing Results:")
62
+ print(f"Status: {json_data['status']}")
63
+ print(f"Video Count: {json_data['video_count']}")
64
+ print(f"Gloss: {json_data['gloss']}")
65
+
66
+ # Handle video output based on format
67
+ if json_data.get('video_format') == 'base64':
68
+ # Video is base64 encoded
69
+ print("Video format: Base64")
70
+ video_path = save_base64_video(video_output, "asl_output.mp4")
71
+ else:
72
+ # Video is a URL (from R2 upload)
73
+ print("Video format: URL")
74
+ video_path = download_video_from_url(video_output, "asl_output.mp4")
75
+
76
+ return video_path
77
+
78
+
79
+ # Example usage scenarios:
80
+
81
+ def example_1_direct_download():
82
+ """
83
+ Example 1: Direct download from R2 URL
84
+ """
85
+ print("=== Example 1: Direct Download ===")
86
+
87
+ # Simulate getting a video URL from the interface
88
+ video_url = "https://your-r2-endpoint.com/bucket/video.mp4"
89
+
90
+ # Download the video
91
+ video_path = download_video_from_url(video_url)
92
+
93
+ if video_path:
94
+ print(f"Video ready for processing: {video_path}")
95
+ # Now you can use the video file for further processing
96
+ # e.g., upload to another service, analyze with OpenCV, etc.
97
+
98
+
99
+ def example_2_base64_processing():
100
+ """
101
+ Example 2: Processing base64 video data
102
+ """
103
+ print("=== Example 2: Base64 Processing ===")
104
+
105
+ # Simulate getting base64 data from the interface
106
+ base64_video = ("data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAA...") # noqa: E501
107
+
108
+ # Save the video
109
+ video_path = save_base64_video(base64_video)
110
+
111
+ if video_path:
112
+ print(f"Video ready for processing: {video_path}")
113
+
114
+
115
+ def example_3_programmatic_interface():
116
+ """
117
+ Example 3: Using the Gradio interface programmatically
118
+ """
119
+ print("=== Example 3: Programmatic Interface ===")
120
+
121
+ # If you want to call the Gradio interface programmatically
122
+ # You can use the gradio_client library
123
+
124
+ try:
125
+ from gradio_client import Client
126
+
127
+ # Connect to your running Gradio interface
128
+ client = Client("http://localhost:7860") # Adjust URL as needed
129
+
130
+ # Upload a document and get results
131
+ result = client.predict(
132
+ "path/to/your/document.pdf", # File path
133
+ api_name="/predict" # Adjust based on your interface
134
+ )
135
+
136
+ # Process the results
137
+ video_path = process_gradio_output(result)
138
+ print(f"Processed video: {video_path}")
139
+
140
+ except ImportError:
141
+ print("gradio_client not installed. Install with: "
142
+ "pip install gradio_client")
143
+ except Exception as e:
144
+ print(f"Error calling Gradio interface: {e}")
145
+
146
+
147
+ def example_4_video_processing():
148
+ """
149
+ Example 4: Further video processing
150
+ """
151
+ print("=== Example 4: Video Processing ===")
152
+
153
+ # Once you have the video file, you can process it further
154
+ video_path = "asl_output.mp4"
155
+
156
+ if Path(video_path).exists():
157
+ print(f"Processing video: {video_path}")
158
+
159
+ # Example: Get video information
160
+ try:
161
+ import cv2
162
+ cap = cv2.VideoCapture(video_path)
163
+ fps = cap.get(cv2.CAP_PROP_FPS)
164
+ frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
165
+ duration = frame_count / fps
166
+ cap.release()
167
+
168
+ print(f"Video info: {duration:.2f} seconds, {fps} FPS, "
169
+ f"{frame_count} frames")
170
+
171
+ except ImportError:
172
+ print("OpenCV not installed. Install with: "
173
+ "pip install opencv-python")
174
+
175
+ # Example: Upload to another service
176
+ # upload_to_youtube(video_path)
177
+ # upload_to_drive(video_path)
178
+ # etc.
179
+
180
+
181
+ if __name__ == "__main__":
182
+ # Run examples
183
+ example_1_direct_download()
184
+ example_2_base64_processing()
185
+ example_3_programmatic_interface()
186
+ example_4_video_processing()