Hug0endob commited on
Commit
f224ad2
·
verified ·
1 Parent(s): 2428ed2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +87 -68
app.py CHANGED
@@ -1,34 +1,68 @@
1
  import os
2
  import re
 
3
  import base64
 
 
4
  from io import BytesIO
5
  from PIL import Image
6
  import requests
7
  import gradio as gr
8
  from mistralai import Mistral
9
 
10
- Mistralclient = Mistral(api_key=os.getenv("MISTRAL_API_KEY"))
11
 
12
- def is_video_or_gif(url_or_path: str):
13
- lower = url_or_path.lower()
14
- return any(lower.endswith(ext) for ext in (".gif", ".mp4", ".mov", ".webm", ".avi"))
 
 
 
15
 
16
  def fetch_bytes(source: str):
17
- if source.startswith("http://") or source.startswith("https://"):
18
  r = requests.get(source, timeout=30)
19
  r.raise_for_status()
20
  return r.content
21
- else:
22
- with open(source, "rb") as f:
23
- return f.read()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
  def convert_media_to_jpeg_bytes(media_bytes: bytes, filename_hint: str = "input") -> bytes:
26
- # If input already an image (jpg/png/webp), open with PIL and convert.
27
  try:
28
  img = Image.open(BytesIO(media_bytes))
29
  if img.mode != "RGB":
30
  img = img.convert("RGB")
31
- # Resize height to 512 maintaining aspect ratio
32
  base_h = 512
33
  w = int(img.width * (base_h / img.height))
34
  img = img.resize((w, base_h), Image.LANCZOS)
@@ -36,55 +70,38 @@ def convert_media_to_jpeg_bytes(media_bytes: bytes, filename_hint: str = "input"
36
  img.save(buf, format="JPEG", quality=90)
37
  return buf.getvalue()
38
  except Exception:
39
- # For GIF/MP4 etc., use ezgif conversion: upload the file then fetch a single frame JPG.
40
- # This is a simple flow relying on ezgif.org endpoints; replace with your own converter if desired.
41
- files = {"new-image": (f"{filename_hint}", media_bytes)}
42
- # Upload to ezgif conversion page to get a conversion token
43
- r = requests.post("https://s.ezgif.com/upload", files=files, timeout=60)
44
- r.raise_for_status()
45
- # extract the file key from returned HTML
46
- m = re.search(r'name="file" value="([^"]+)"', r.text)
47
- if not m:
48
- raise RuntimeError("Conversion upload failed")
49
- file_key = m.group(1)
50
- # Request convert to JPG using ezgif convert endpoint
51
- conv = requests.post("https://s.ezgif.com/gif-to-jpg", data={"file": file_key}, timeout=60)
52
- conv.raise_for_status()
53
- # Find the first resulting JPG link
54
- m2 = re.search(r'<img src="(https?://s.ezgif.com/tmp/[^"]+)"', conv.text)
55
- if not m2:
56
- # try alternate pattern
57
- m2 = re.search(r'<a href="(https?://s.ezgif.com/tmp/[^"]+)"', conv.text)
58
- if not m2:
59
- raise RuntimeError("Conversion failed to produce JPG")
60
- jpg_url = m2.group(1)
61
- r2 = requests.get(jpg_url, timeout=60)
62
- r2.raise_for_status()
63
- return r2.content
64
 
65
  def image_bytes_to_base64_jpeg(image_bytes: bytes):
66
  return base64.b64encode(image_bytes).decode("utf-8")
67
 
68
- def build_prompt(auto_prompt: bool, custom_prompt: str):
69
- if custom_prompt and custom_prompt.strip():
70
- return custom_prompt.strip()
71
- if auto_prompt:
72
- return ("Provide a highly detailed, vivid, and exhaustive visual description of the image. "
73
- "Include objects, textures, colors, lighting, perspective, environment, clothing, "
74
- "facial expressions, poses, mood, camera lens and settings, and any notable small details. "
75
- "Write in descriptive, actionable phrases suitable for use as image-generation prompts.")
76
  return "Provide a detailed description of this image."
77
 
78
- def generate_description(image_source: str, use_auto_prompt: bool, custom_prompt: str):
79
  try:
80
  raw = fetch_bytes(image_source)
81
- jpg_bytes = convert_media_to_jpeg_bytes(raw, filename_hint=image_source.split("/")[-1])
82
  except Exception as e:
83
- return f"Error processing image/media: {e}"
84
-
85
- b64 = image_bytes_to_base64_jpeg(jpg_bytes)
86
- prompt = build_prompt(use_auto_prompt, custom_prompt)
87
 
 
 
88
  model = "pixtral-12b-2409"
89
  messages = [{
90
  "role": "user",
@@ -95,57 +112,59 @@ def generate_description(image_source: str, use_auto_prompt: bool, custom_prompt
95
  "stream": False
96
  }]
97
 
 
98
  try:
99
  partial = ""
100
- for chunk in Mistralclient.chat.stream(model=model, messages=messages):
101
  if chunk.data.choices[0].delta.content is not None:
102
  partial += chunk.data.choices[0].delta.content
103
  yield partial
104
  except Exception as e:
105
- yield f"Error from model: {e}"
106
 
107
  with gr.Blocks() as demo:
108
  gr.Markdown("Image To Flux Prompt")
109
 
110
  with gr.Row():
111
  with gr.Column(scale=1):
112
- url_input = gr.Textbox(label="Image URL or local path", placeholder="https://... or upload below")
113
- upload = gr.File(label="Or upload image/video (gif/mp4 allowed)", file_types=["image","video"])
114
  preview = gr.Image(label="Preview", type="pil")
115
  submit = gr.Button("Submit")
116
  with gr.Column(scale=1):
117
  custom = gr.Textbox(label="Custom prompt (optional)", lines=4, placeholder="Leave blank to use auto prompt")
118
  auto_toggle = gr.Checkbox(label="Use detailed auto prompt", value=True)
119
- out = gr.Textbox(label="Generated Detailed Description", lines=12)
 
120
 
121
  def prepare_source(url_val, upload_file):
122
- if upload_file is not None:
123
  return upload_file.name
124
  if url_val and url_val.strip():
125
  return url_val.strip()
126
  return ""
127
 
128
- def update_preview(source):
 
129
  if not source:
130
- return None
131
  try:
132
- if source.startswith("http://") or source.startswith("https://"):
133
  r = requests.get(source, timeout=30)
134
  r.raise_for_status()
135
- return Image.open(BytesIO(r.content)).convert("RGB")
136
- else:
137
- return Image.open(source).convert("RGB")
138
  except Exception:
139
- return None
140
 
141
- submit.click(fn=lambda url, f: prepare_source(url, f), inputs=[url_input, upload], outputs=[preview], _js=None)
142
- # On submit, generate description stream. Use a small wrapper to pass other inputs.
143
- def run_and_stream(src, use_auto, custom_p):
144
- if not src:
145
  return "No image provided."
146
- for chunk in generate_description(src, use_auto, custom_p):
 
147
  yield chunk
148
 
149
- submit.click(fn=run_and_stream, inputs=[url_input, auto_toggle, custom], outputs=[out])
 
150
 
151
  demo.launch()
 
1
  import os
2
  import re
3
+ import shutil
4
  import base64
5
+ import tempfile
6
+ import subprocess
7
  from io import BytesIO
8
  from PIL import Image
9
  import requests
10
  import gradio as gr
11
  from mistralai import Mistral
12
 
13
+ DEFAULT_KEY = os.getenv("MISTRAL_API_KEY")
14
 
15
+ def get_mistral_client(alt_key: str = None):
16
+ key = alt_key.strip() if alt_key and alt_key.strip() else DEFAULT_KEY
17
+ return Mistral(api_key=key)
18
+
19
+ def is_remote(src: str):
20
+ return src.startswith("http://") or src.startswith("https://")
21
 
22
  def fetch_bytes(source: str):
23
+ if is_remote(source):
24
  r = requests.get(source, timeout=30)
25
  r.raise_for_status()
26
  return r.content
27
+ with open(source, "rb") as f:
28
+ return f.read()
29
+
30
+ def try_ffmpeg_extract_frame(media_path: str, out_path: str):
31
+ ffmpeg = shutil.which("ffmpeg")
32
+ if not ffmpeg:
33
+ return False
34
+ cmd = [
35
+ ffmpeg, "-y", "-i", media_path, "-vf", "scale=-2:512", "-frames:v", "1", out_path
36
+ ]
37
+ try:
38
+ subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=30)
39
+ return os.path.exists(out_path)
40
+ except Exception:
41
+ return False
42
+
43
+ def ezgif_convert_to_jpg_bytes(media_bytes: bytes, filename_hint: str = "input"):
44
+ files = {"new-image": (filename_hint, media_bytes)}
45
+ r = requests.post("https://s.ezgif.com/upload", files=files, timeout=60)
46
+ r.raise_for_status()
47
+ m = re.search(r'name="file" value="([^"]+)"', r.text)
48
+ if not m:
49
+ raise RuntimeError("ezgif upload failed")
50
+ file_key = m.group(1)
51
+ conv = requests.post("https://s.ezgif.com/gif-to-jpg", data={"file": file_key}, timeout=60)
52
+ conv.raise_for_status()
53
+ m2 = re.search(r'<img src="(https?://s.ezgif.com/tmp/[^"]+)"', conv.text) or re.search(r'<a href="(https?://s.ezgif.com/tmp/[^"]+)"', conv.text)
54
+ if not m2:
55
+ raise RuntimeError("ezgif conversion failed")
56
+ jpg_url = m2.group(1)
57
+ r2 = requests.get(jpg_url, timeout=60)
58
+ r2.raise_for_status()
59
+ return r2.content
60
 
61
  def convert_media_to_jpeg_bytes(media_bytes: bytes, filename_hint: str = "input") -> bytes:
 
62
  try:
63
  img = Image.open(BytesIO(media_bytes))
64
  if img.mode != "RGB":
65
  img = img.convert("RGB")
 
66
  base_h = 512
67
  w = int(img.width * (base_h / img.height))
68
  img = img.resize((w, base_h), Image.LANCZOS)
 
70
  img.save(buf, format="JPEG", quality=90)
71
  return buf.getvalue()
72
  except Exception:
73
+ with tempfile.TemporaryDirectory() as td:
74
+ tmp_in = os.path.join(td, filename_hint)
75
+ with open(tmp_in, "wb") as f:
76
+ f.write(media_bytes)
77
+ tmp_out = os.path.join(td, "frame.jpg")
78
+ if try_ffmpeg_extract_frame(tmp_in, tmp_out) and os.path.exists(tmp_out):
79
+ with open(tmp_out, "rb") as f:
80
+ return f.read()
81
+ return ezgif_convert_to_jpg_bytes(media_bytes, filename_hint=filename_hint)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  def image_bytes_to_base64_jpeg(image_bytes: bytes):
84
  return base64.b64encode(image_bytes).decode("utf-8")
85
 
86
+ def build_prompt(use_auto: bool, custom: str):
87
+ if custom and custom.strip():
88
+ return custom.strip()
89
+ if use_auto:
90
+ return ("Provide an exhaustive, highly detailed visual description suitable as an image-generation prompt. "
91
+ "Include objects, materials, textures, colors, lighting, viewpoint, camera lens and settings, "
92
+ "poses, facial expressions, clothing, background elements, small distinguishing details, mood, and composition.")
 
93
  return "Provide a detailed description of this image."
94
 
95
+ def generate_description_stream(image_source: str, use_auto: bool, custom_prompt: str, alt_key: str):
96
  try:
97
  raw = fetch_bytes(image_source)
98
+ jpg = convert_media_to_jpeg_bytes(raw, filename_hint=os.path.basename(image_source) or "input")
99
  except Exception as e:
100
+ yield f"Error processing media: {e}"
101
+ return
 
 
102
 
103
+ b64 = image_bytes_to_base64_jpeg(jpg)
104
+ prompt = build_prompt(use_auto, custom_prompt)
105
  model = "pixtral-12b-2409"
106
  messages = [{
107
  "role": "user",
 
112
  "stream": False
113
  }]
114
 
115
+ client = get_mistral_client(alt_key)
116
  try:
117
  partial = ""
118
+ for chunk in client.chat.stream(model=model, messages=messages):
119
  if chunk.data.choices[0].delta.content is not None:
120
  partial += chunk.data.choices[0].delta.content
121
  yield partial
122
  except Exception as e:
123
+ yield f"Model error: {e}"
124
 
125
  with gr.Blocks() as demo:
126
  gr.Markdown("Image To Flux Prompt")
127
 
128
  with gr.Row():
129
  with gr.Column(scale=1):
130
+ url_input = gr.Textbox(label="Image URL or local path", placeholder="https://... or leave blank if uploading")
131
+ upload = gr.File(label="Or upload image/video (gif/mp4 allowed)", file_types=None)
132
  preview = gr.Image(label="Preview", type="pil")
133
  submit = gr.Button("Submit")
134
  with gr.Column(scale=1):
135
  custom = gr.Textbox(label="Custom prompt (optional)", lines=4, placeholder="Leave blank to use auto prompt")
136
  auto_toggle = gr.Checkbox(label="Use detailed auto prompt", value=True)
137
+ alt_key = gr.Textbox(label="Alternate Mistral API Key (optional)", type="password")
138
+ out = gr.Textbox(label="Generated Detailed Description", lines=16)
139
 
140
  def prepare_source(url_val, upload_file):
141
+ if upload_file:
142
  return upload_file.name
143
  if url_val and url_val.strip():
144
  return url_val.strip()
145
  return ""
146
 
147
+ def update_preview_and_return_source(url_val, upload_file):
148
+ source = prepare_source(url_val, upload_file)
149
  if not source:
150
+ return None, ""
151
  try:
152
+ if is_remote(source):
153
  r = requests.get(source, timeout=30)
154
  r.raise_for_status()
155
+ return Image.open(BytesIO(r.content)).convert("RGB"), source
156
+ return Image.open(source).convert("RGB"), source
 
157
  except Exception:
158
+ return None, source
159
 
160
+ def start_generation(source, use_auto, custom_p, alt_k):
161
+ if not source:
 
 
162
  return "No image provided."
163
+ # stream generator wrapper
164
+ for chunk in generate_description_stream(source, use_auto, custom_p, alt_k):
165
  yield chunk
166
 
167
+ submit.click(fn=update_preview_and_return_source, inputs=[url_input, upload], outputs=[preview, url_input])
168
+ submit.click(fn=start_generation, inputs=[url_input, auto_toggle, custom, alt_key], outputs=[out])
169
 
170
  demo.launch()