Muhammad Waqas commited on
Commit
2e40bb4
·
1 Parent(s): 2677214

Added: Generate image to video

Browse files
app.py CHANGED
@@ -6,6 +6,7 @@ import random
6
  import urllib.request
7
  import urllib.parse
8
  import websocket
 
9
  import uuid
10
  from dotenv import load_dotenv
11
  from flask import Flask, request, jsonify, render_template, send_file
@@ -27,44 +28,8 @@ ws_address = os.getenv("WS_ADDRESS")
27
  # Generate a unique client ID
28
  client_id = str(uuid.uuid4())
29
 
30
- def allowed_file(filename):
31
- """Check if the uploaded file has an allowed extension."""
32
- return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
33
-
34
- def save_base64_image(b64_string):
35
- """Decode a base64 string and save it as an image."""
36
- header, encoded = b64_string.split(',', 1) # Handle data URI schemes if provided
37
- image_data = base64.b64decode(encoded)
38
-
39
- # Determine image extension from data URI or use a default one
40
- ext = header.split('/')[1].split(';')[0] if '/' in header else 'png'
41
- image_path = f"/tmp/{uuid.uuid4()}.{ext}"
42
 
43
- with open(image_path, 'wb') as f:
44
- f.write(image_data)
45
- return image_path
46
-
47
- def make_request(url, data=None, headers=None):
48
- req = urllib.request.Request(url, data=data, headers=headers)
49
- try:
50
- with urllib.request.urlopen(req) as response:
51
- response_body = response.read().decode() # Decode the response
52
- # print(response_body)
53
- return json.loads(response_body) # Convert to JSON if valid
54
- except urllib.error.HTTPError as e:
55
- print(f"HTTPError: {e.code}, {e.reason}")
56
- print(e.read().decode()) # Print detailed error response
57
- except urllib.error.URLError as e:
58
- print(f"URLError: {e.reason}")
59
 
60
- def queue_prompt(prompt, token):
61
- payload = {"prompt": prompt, "client_id": client_id}
62
- data = json.dumps(payload).encode('utf-8')
63
- headers = {
64
- 'Authorization': f'Bearer {token}',
65
- 'Content-Type': 'application/json'
66
- }
67
- return make_request(f"{server_address}/prompt", data=data, headers=headers)
68
 
69
  def get_image(filename, subfolder, image_type, token):
70
  url_values = {'filename': filename, 'subfolder': subfolder, 'type': image_type}
@@ -78,15 +43,9 @@ def get_image(filename, subfolder, image_type, token):
78
  print(e.read())
79
  raise
80
 
81
- def get_history(prompt_id, token):
82
- headers = {
83
- 'Authorization': f'Bearer {token}',
84
- 'Content-Type': 'application/json'
85
- }
86
- return make_request(f"{server_address}/history/{prompt_id}", headers=headers)
87
 
88
- def get_images(ws, prompt, token):
89
- prompt_id = queue_prompt(prompt, token)['prompt_id']
90
  output_images = {}
91
 
92
  while True:
@@ -110,6 +69,7 @@ def get_images(ws, prompt, token):
110
 
111
  return output_images
112
 
 
113
  def fetch_video(video_data, token):
114
  video_url = f"{server_address}/download?file={video_data['filename']}"
115
  req = urllib.request.Request(video_url)
@@ -151,12 +111,12 @@ def generate_image():
151
  with open(file_path, 'r', encoding='utf-8') as file:
152
  workflow_jsondata = file.read()
153
 
154
- prompt = json.loads(workflow_jsondata)
155
- prompt["6"]["inputs"]["text"] = text_prompt
156
 
157
- # prompt["7"]["inputs"]["text"] = "text, watermark, low quality, extra hands, extra legs."
158
  # seednum = random.randint(1, 9999999999999)
159
- # prompt["3"]["inputs"]["seed"] = seednum
160
 
161
  #######################
162
  # For model Flux1.dev #
@@ -164,7 +124,7 @@ def generate_image():
164
 
165
  # Generate a random 15-digit seed as an integer
166
  seednum = random.randint(100000000000000, 999999999999999)
167
- prompt["31"]["inputs"]["seed"] = seednum
168
 
169
  ws = websocket.WebSocket()
170
 
@@ -174,7 +134,7 @@ def generate_image():
174
  except websocket.WebSocketException as e:
175
  return jsonify({'error': f'WebSocket connection failed: {str(e)}'}), 500
176
 
177
- images = get_images(ws, prompt, token)
178
  ws.close()
179
 
180
  output_images_base64 = []
@@ -194,88 +154,200 @@ def generate_image():
194
  def get_image_file(filename):
195
  return send_file(filename, mimetype='image/png')
196
 
197
- # Generate image to video route
198
- @app.route('/image_to_video', methods=['POST'])
199
- def image_to_video():
200
 
201
- # return "Route works!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  data = request.json
204
 
205
- # Extract token from headers
206
  token = request.headers.get('Authorization')
207
- if token is None:
208
- return jsonify({'error': 'No token provided'}), 400
209
- if token.startswith("Bearer "):
210
- token = token.split(" ")[1]
211
-
212
- # token = base64.b64decode(token).decode("utf-8")
213
 
214
- # Extract text prompt
215
- text_prompt = data['text_prompt']
216
  if not text_prompt:
217
  return jsonify({'error': 'Text prompt is required'}), 400
218
 
219
- # Handle uploaded image or base64-encoded image
220
  image_file = request.files.get('image')
221
- base64_image = data['base64_image']
222
 
223
  if image_file:
224
- # Validate and save uploaded image
225
  if not allowed_file(image_file.filename):
226
  return jsonify({'error': 'Unsupported image format'}), 400
227
  filename = secure_filename(image_file.filename)
228
  image_path = f"/tmp/{uuid.uuid4()}_{filename}"
229
- image_file.save(image_path)
230
 
 
 
231
  elif base64_image:
232
- # Save base64-encoded image
233
  try:
234
  image_path = save_base64_image(base64_image)
 
 
 
 
 
235
  except Exception as e:
236
  return jsonify({'error': f'Invalid base64 image data: {str(e)}'}), 400
237
  else:
238
- return jsonify({'error': 'Image is required (either file or base64)'}), 400
239
 
240
- # Get the path to the workflow configuration file
241
  current_dir = os.path.dirname(os.path.abspath(__file__))
242
- file_path = os.path.join(current_dir, 'workflows/cogvideox_image_to_video_workflow_api.json')
243
-
244
- print(f"Modified workflow: {file_path}", flush=True)
245
 
 
 
 
 
 
246
 
247
- # Load and modify workflow
248
- with open(file_path, 'r', encoding='utf-8') as file:
249
- workflow = json.load(file)
250
- workflow["30"]["inputs"]["prompt"] = text_prompt # Text prompt
251
- workflow["36"]["inputs"]["upload"] = image_path # Image path
252
- workflow["31"]["inputs"]["prompt"] = "Low quality, watermark, strange motion" # Negative prompt
253
-
254
- seed = random.randint(1e14, 9e14)
255
- workflow["57"]["inputs"]["seed"] = seed # Set reproducibility seed
256
-
257
- # WebSocket connection to trigger workflow
258
  ws = websocket.WebSocket()
259
- ws.connect(f"{ws_address}?clientId={client_id}&token={token}", header=
260
- {"Authorization": f"Bearer {token}"})
261
- ws.send(json.dumps({"workflow": workflow})) # Send the modified workflow
 
262
 
263
- # Receive video processing result
 
 
 
 
 
264
  while True:
265
- out = ws.recv()
266
- message = json.loads(out)
267
- if message.get('type') == 'completed':
268
- video_data = message['data']
269
  break
270
 
271
- # Fetch and return the generated video
272
- video_content = fetch_video(video_data, token)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  return send_file(
274
- io.BytesIO(video_content),
275
  mimetype='video/mp4',
276
  as_attachment=True,
277
  download_name='generated_video.mp4'
278
  )
279
 
 
280
  if __name__ == '__main__':
281
- app.run(host='0.0.0.0', port=7860) # Removed 'debug=True'
 
6
  import urllib.request
7
  import urllib.parse
8
  import websocket
9
+ import requests
10
  import uuid
11
  from dotenv import load_dotenv
12
  from flask import Flask, request, jsonify, render_template, send_file
 
28
  # Generate a unique client ID
29
  client_id = str(uuid.uuid4())
30
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
 
 
 
 
 
 
 
 
33
 
34
  def get_image(filename, subfolder, image_type, token):
35
  url_values = {'filename': filename, 'subfolder': subfolder, 'type': image_type}
 
43
  print(e.read())
44
  raise
45
 
 
 
 
 
 
 
46
 
47
+ def get_images(ws, workflow, token):
48
+ prompt_id = queue_prompt(workflow, token)['prompt_id']
49
  output_images = {}
50
 
51
  while True:
 
69
 
70
  return output_images
71
 
72
+
73
  def fetch_video(video_data, token):
74
  video_url = f"{server_address}/download?file={video_data['filename']}"
75
  req = urllib.request.Request(video_url)
 
111
  with open(file_path, 'r', encoding='utf-8') as file:
112
  workflow_jsondata = file.read()
113
 
114
+ workflow = json.loads(workflow_jsondata)
115
+ workflow["6"]["inputs"]["text"] = text_prompt
116
 
117
+ # workflow["7"]["inputs"]["text"] = "text, watermark, low quality, extra hands, extra legs."
118
  # seednum = random.randint(1, 9999999999999)
119
+ # workflow["3"]["inputs"]["seed"] = seednum
120
 
121
  #######################
122
  # For model Flux1.dev #
 
124
 
125
  # Generate a random 15-digit seed as an integer
126
  seednum = random.randint(100000000000000, 999999999999999)
127
+ workflow["31"]["inputs"]["seed"] = seednum
128
 
129
  ws = websocket.WebSocket()
130
 
 
134
  except websocket.WebSocketException as e:
135
  return jsonify({'error': f'WebSocket connection failed: {str(e)}'}), 500
136
 
137
+ images = get_images(ws, workflow, token)
138
  ws.close()
139
 
140
  output_images_base64 = []
 
154
  def get_image_file(filename):
155
  return send_file(filename, mimetype='image/png')
156
 
 
 
 
157
 
158
+ def allowed_file(filename):
159
+ """Check if the uploaded file has an allowed extension."""
160
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
161
+
162
+ def save_base64_image(b64_string):
163
+ """Decode a base64 string and save it as an image."""
164
+ try:
165
+ # Handle Data URI scheme if present
166
+ if ',' in b64_string:
167
+ header, encoded = b64_string.split(',', 1)
168
+ ext = header.split('/')[1].split(';')[0] if '/' in header else 'png'
169
+ else:
170
+ encoded = b64_string
171
+ ext = 'png'
172
+
173
+ # Decode the image data
174
+ image_data = base64.b64decode(encoded)
175
+
176
+ # Generate a unique path for the image
177
+ image_path = f"/tmp/{uuid.uuid4()}.{ext}"
178
 
179
+ # Ensure directory exists
180
+ os.makedirs('/tmp/', exist_ok=True)
181
+
182
+ # Save the image
183
+ with open(image_path, 'wb') as f:
184
+ f.write(image_data)
185
+
186
+ print(f"Image saved at: {image_path}")
187
+ return (f"https://gosign-de-comfyui-api.hf.space{image_path}")
188
+
189
+ except Exception as e:
190
+ raise ValueError(f"Failed to save image: {e}")
191
+
192
+ def make_request(url, data=None, headers=None):
193
+ req = urllib.request.Request(url, data=data, headers=headers)
194
+ try:
195
+ with urllib.request.urlopen(req) as response:
196
+ response_body = response.read().decode() # Decode the response
197
+ # print(response_body)
198
+ return json.loads(response_body) # Convert to JSON if valid
199
+ except urllib.error.HTTPError as e:
200
+ print(f"HTTPError: {e.code}, {e.reason}")
201
+ print(e.read().decode()) # Print detailed error response
202
+ except urllib.error.URLError as e:
203
+ print(f"URLError: {e.reason}")
204
+
205
+ # Helper: Queue the prompt
206
+ def queue_prompt(workflow, token):
207
+ payload = {"prompt": workflow, "client_id": client_id}
208
+ headers = {
209
+ 'Authorization': f'Bearer {token}',
210
+ 'Content-Type': 'application/json'
211
+ }
212
+ response = make_request(f"{server_address}/prompt", data=json.dumps(payload).encode('utf-8'), headers=headers)
213
+ if not response or 'prompt_id' not in response:
214
+ raise ValueError("Failed to queue the prompt. Check the request or API response.")
215
+ return response['prompt_id']
216
+
217
+ def get_history(prompt_id, token):
218
+ headers = {
219
+ 'Authorization': f'Bearer {token}',
220
+ 'Content-Type': 'application/json'
221
+ }
222
+ return make_request(f"{server_address}/history/{prompt_id}", headers=headers)
223
+
224
+ def get_video_data(filename, subfolder, token):
225
+ url_values = {'filename': filename, 'subfolder': subfolder}
226
+ url = f"{server_address}/view?{urllib.parse.urlencode(url_values)}"
227
+ req = urllib.request.Request(url)
228
+ req.add_header("Authorization", f"Bearer {token}")
229
+
230
+ try:
231
+ return urllib.request.urlopen(req).read() # Return video content
232
+ except urllib.error.HTTPError as e:
233
+ print(f"HTTP Error: {e.code} - {e.reason}")
234
+ print(e.read())
235
+ raise
236
+
237
+ # Helper: Upload Image to Hugging Face
238
+ def upload_image_to_hf(image_path, token):
239
+ with open(image_path, 'rb') as img:
240
+ headers = {
241
+ "Authorization": f"Bearer {token}",
242
+ "Content-Type": "image/jpeg" # Change as per your image type
243
+ }
244
+ response = requests.post(
245
+ "https://gosign-de-image-to-video.hf.space",
246
+ headers=headers,
247
+ files={'file': img}
248
+ )
249
+
250
+ if response.status_code != 200:
251
+ raise ValueError(f"Failed to upload image: {response.text}")
252
+
253
+ return response.json().get('url')
254
+
255
+ # Route: Image to Video
256
+ @app.route('/image_to_video', methods=['POST'])
257
+ def image_to_video():
258
  data = request.json
259
 
260
+ # Extract and validate token
261
  token = request.headers.get('Authorization')
262
+ if not token or not token.startswith("Bearer "):
263
+ return jsonify({'error': 'Valid Bearer token required'}), 400
264
+ token = token.split(" ")[1]
 
 
 
265
 
266
+ # Validate text prompt
267
+ text_prompt = data.get('text_prompt')
268
  if not text_prompt:
269
  return jsonify({'error': 'Text prompt is required'}), 400
270
 
271
+ # Handle uploaded image or base64 image
272
  image_file = request.files.get('image')
273
+ base64_image = data.get('base64_image')
274
 
275
  if image_file:
276
+ # Validate and save the uploaded image
277
  if not allowed_file(image_file.filename):
278
  return jsonify({'error': 'Unsupported image format'}), 400
279
  filename = secure_filename(image_file.filename)
280
  image_path = f"/tmp/{uuid.uuid4()}_{filename}"
 
281
 
282
+
283
+ image_file.save(image_path)
284
  elif base64_image:
285
+ # Save base64 image
286
  try:
287
  image_path = save_base64_image(base64_image)
288
+ # return jsonify({'image_path': image_path})
289
+ # image_path = (upload_image_to_hf(image_path, token))
290
+
291
+ print(f"Image saved at HF: {image_path}")
292
+
293
  except Exception as e:
294
  return jsonify({'error': f'Invalid base64 image data: {str(e)}'}), 400
295
  else:
296
+ return jsonify({'error': 'Image is required (file or base64)'}), 400
297
 
298
+ # Load workflow configuration
299
  current_dir = os.path.dirname(os.path.abspath(__file__))
300
+ workflow_path = os.path.join(current_dir, 'workflows/cogvideox_image_to_video_workflow_api.json')
301
+ with open(workflow_path, 'r', encoding='utf-8') as f:
302
+ workflow = json.load(f)
303
 
304
+ # Modify workflow with inputs
305
+ workflow["30"]["inputs"]["prompt"] = text_prompt
306
+ workflow["36"]["inputs"]["image"] = image_path
307
+ workflow["31"]["inputs"]["prompt"] = "Low quality, watermark, strange motion"
308
+ workflow["57"]["inputs"]["seed"] = random.randint(100000000000000, 999999999999999)
309
 
310
+ # WebSocket connection to queue and monitor workflow
 
 
 
 
 
 
 
 
 
 
311
  ws = websocket.WebSocket()
312
+ try:
313
+ ws.connect(f"{ws_address}?clientId={client_id}&token={token}", header={"Authorization": f"Bearer {token}"})
314
+ except websocket.WebSocketException as e:
315
+ return jsonify({'error': f'WebSocket connection failed: {str(e)}'}), 500
316
 
317
+ try:
318
+ prompt_id = queue_prompt(workflow, token)
319
+ except ValueError as e:
320
+ return jsonify({'error': str(e)}), 400
321
+
322
+ # Wait for workflow execution to complete
323
  while True:
324
+ message = json.loads(ws.recv())
325
+ if message.get('type') == 'executing' and message['data']['node'] is None and message['data']['prompt_id'] == prompt_id:
 
 
326
  break
327
 
328
+ # Fetch video details
329
+ history = get_history(prompt_id, token).get(prompt_id, {})
330
+ video_data = None
331
+ for node_id, node_output in history.get('outputs', {}).items():
332
+ if 'videos' in node_output:
333
+ video = node_output['videos'][0]
334
+ video_data = get_video_data(video['filename'], video['subfolder'], token)
335
+
336
+ if not video_data:
337
+ return jsonify({'error': 'Failed to generate video'}), 500
338
+
339
+ # Save and return the generated video
340
+ local_video_path = f"/tmp/generated_video_{uuid.uuid4()}.mp4"
341
+ with open(local_video_path, 'wb') as f:
342
+ f.write(video_data)
343
+
344
  return send_file(
345
+ io.BytesIO(video_data),
346
  mimetype='video/mp4',
347
  as_attachment=True,
348
  download_name='generated_video.mp4'
349
  )
350
 
351
+
352
  if __name__ == '__main__':
353
+ app.run(host='0.0.0.0', port=7860, debug=True) # Removed 'debug=True'
requirements.txt CHANGED
@@ -5,3 +5,4 @@ websocket-client
5
  Pillow
6
  uuid
7
  gunicorn
 
 
5
  Pillow
6
  uuid
7
  gunicorn
8
+ requests
workflows/cogvideox_image_to_video_workflow_api.json CHANGED
@@ -54,7 +54,7 @@
54
  },
55
  "36": {
56
  "inputs": {
57
- "image": "Webimage-1-720x480.jpg",
58
  "upload": "image"
59
  },
60
  "class_type": "LoadImage",
@@ -131,8 +131,8 @@
131
  "steps": 50,
132
  "cfg": 6,
133
  "seed": 65334758276105,
134
- "scheduler": "DPM",
135
- "denoise_strength": 16,
136
  "pipeline": [
137
  "1",
138
  0
 
54
  },
55
  "36": {
56
  "inputs": {
57
+ "image": "https://huggingface.co/spaces/gosign-de/comfyui-api/raw/main/images/Webimage-1-720x480.jpg",
58
  "upload": "image"
59
  },
60
  "class_type": "LoadImage",
 
131
  "steps": 50,
132
  "cfg": 6,
133
  "seed": 65334758276105,
134
+ "scheduler": "DPM++",
135
+ "denoise_strength": 1.0,
136
  "pipeline": [
137
  "1",
138
  0