Create app.py
Browse files
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)
|