ruv commited on
Commit
a85897a
·
verified ·
1 Parent(s): 779b986

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +144 -0
app.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # _____ .__ ____ ___.__ .___
3
+ # / _ \ |__| \ \ / |__| __| _/____ ____
4
+ # / /_\ \| | \ Y /| |/ __ _/ __ \/ _ \
5
+ # / | | | \ / | / /_/ \ ___( <_> )
6
+ # \____|__ |__| \___/ |__\____ |\___ \____/
7
+ # \/ \/ \/
8
+ # created by rUv
9
+
10
+ import base64
11
+ import os
12
+ import cv2
13
+ import re
14
+ import numpy as np
15
+ import httpx
16
+ import asyncio
17
+ from quart import Quart, request, jsonify, render_template
18
+
19
+ app = Quart(__name__)
20
+
21
+ API_URL = "https://api.openai.com/v1/chat/completions"
22
+ API_KEY = os.getenv("OPENAI_API_KEY")
23
+
24
+ def preprocess_image(image: np.ndarray) -> np.ndarray:
25
+ return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
26
+
27
+ def encode_image_to_base64(image: np.ndarray) -> str:
28
+ success, buffer = cv2.imencode('.jpg', image)
29
+ if not success:
30
+ raise ValueError("Could not encode image to JPEG format.")
31
+ encoded_image = base64.b64encode(buffer).decode('utf-8')
32
+ return encoded_image
33
+
34
+ def compose_payload(image_base64: str, prompt: str) -> dict:
35
+ return {
36
+ "model": "gpt-4-vision-preview",
37
+ "messages": [
38
+ {
39
+ "role": "user",
40
+ "content": [
41
+ {
42
+ "type": "text",
43
+ "text": (
44
+ f"You are an expert in analyzing visual content. Please analyze the provided image for the following details:\n"
45
+ f"1. Identify any text present in the image and provide a summary.\n"
46
+ f"2. Describe the main objects and their arrangement.\n"
47
+ f"3. Identify the context of the video frame (e.g., work environment, outdoor scene).\n"
48
+ f"4. Provide any notable observations about lighting, colors, and overall composition.\n"
49
+ f"5. Format using markdown.\n"
50
+ f"Here is the Video Frame still:\n{prompt}"
51
+ )
52
+ },
53
+ {
54
+ "type": "image_url",
55
+ "image_url": {
56
+ "url": f"data:image/jpeg;base64,{image_base64}"
57
+ }
58
+ }
59
+ ]
60
+ }
61
+ ],
62
+ "max_tokens": 2300
63
+ }
64
+
65
+ def compose_headers(api_key: str) -> dict:
66
+ return {
67
+ "Content-Type": "application/json",
68
+ "Authorization": f"Bearer {api_key}"
69
+ }
70
+
71
+ async def prompt_image(image_base64: str, prompt: str, api_key: str) -> str:
72
+ headers = compose_headers(api_key=api_key)
73
+ payload = compose_payload(image_base64=image_base64, prompt=prompt)
74
+
75
+ async with httpx.AsyncClient() as client:
76
+ while True:
77
+ try:
78
+ response = await client.post(API_URL, headers=headers, json=payload, timeout=30.0)
79
+ response.raise_for_status() # Raise an error for bad HTTP status codes
80
+ try:
81
+ response_json = response.json()
82
+ except ValueError:
83
+ raise ValueError("Failed to parse response as JSON")
84
+
85
+ if 'error' in response_json:
86
+ raise ValueError(response_json['error']['message'])
87
+ return response_json['choices'][0]['message']['content']
88
+ except httpx.HTTPStatusError as http_err:
89
+ if response.status_code == 429:
90
+ error_message = response.json().get('error', {}).get('message', '')
91
+ wait_time = parse_wait_time(error_message)
92
+ if wait_time:
93
+ print(f"Rate limit exceeded. Waiting for {wait_time} seconds.")
94
+ await asyncio.sleep(wait_time)
95
+ else:
96
+ raise ValueError(f"Rate limit exceeded but could not parse wait time from message: {error_message}")
97
+ else:
98
+ raise ValueError(f"HTTP error occurred: {http_err}")
99
+ except httpx.RequestError as req_err:
100
+ raise ValueError(f"Request error occurred: {req_err}")
101
+ except httpx.TimeoutException:
102
+ raise ValueError("Request timed out. Please try again later.")
103
+
104
+ def parse_wait_time(error_message: str) -> int:
105
+ match = re.search(r"try again in (\d+m)?(\d+\.\ds)?", error_message)
106
+ if match:
107
+ minutes = match.group(1)
108
+ seconds = match.group(2)
109
+
110
+ total_wait_time = 0
111
+ if minutes:
112
+ total_wait_time += int(minutes[:-1]) * 60 # Convert minutes to seconds
113
+ if seconds:
114
+ total_wait_time += float(seconds[:-1]) # Add seconds
115
+
116
+ return int(total_wait_time)
117
+ return None
118
+
119
+ @app.route('/')
120
+ async def index():
121
+ return await render_template('index.html')
122
+
123
+ @app.route('/process_frame', methods=['POST'])
124
+ async def process_frame():
125
+ data = await request.json
126
+ image_data = data['image'].split(',')[1]
127
+ image = np.frombuffer(base64.b64decode(image_data), dtype=np.uint8)
128
+ image = cv2.imdecode(image, cv2.IMREAD_COLOR)
129
+ processed_image = preprocess_image(image)
130
+ image_base64 = encode_image_to_base64(processed_image)
131
+ prompt = data.get('prompt', "Analyze this frame")
132
+ api_key = data.get('api_key') or API_KEY
133
+ if not api_key:
134
+ return jsonify({'response': 'API key is required.'}), 400
135
+ try:
136
+ response = await prompt_image(image_base64, prompt, api_key)
137
+ except ValueError as e:
138
+ response = str(e)
139
+ return jsonify({'response': response})
140
+
141
+ if __name__ == '__main__':
142
+ if API_KEY is None:
143
+ raise ValueError("Please set the OPENAI_API_KEY environment variable")
144
+ app.run(debug=True)