Spaces:
Sleeping
Sleeping
Muhammad Waqas
commited on
Commit
Β·
346f789
1
Parent(s):
75efe1b
Added: Generate image to video
Browse files- .DS_Store +0 -0
- .env +4 -1
- app.py +79 -27
- sample-response.json +54 -0
- static/0e65b3a0-cc05-4f5c-8b20-d5854b2fd6d7.jpeg +0 -0
- static/127a05b0-0395-45a5-a0c0-ad8ffad070b5.jpeg +0 -0
- static/71a7077f-cd9c-4d9c-8da8-4975f8853bc5.jpeg +0 -0
- static/eefb530f-7841-4050-9b04-c51444e1a6eb.jpeg +0 -0
- workflows/cogvideox_image_to_video_workflow_api.json +1 -1
.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
.env
CHANGED
@@ -1,2 +1,5 @@
|
|
1 |
SERVER_ADDRESS=https://gosign-de-image-to-video.hf.space
|
2 |
-
WS_ADDRESS=wss://gosign-de-image-to-video.hf.space/ws
|
|
|
|
|
|
|
|
1 |
SERVER_ADDRESS=https://gosign-de-image-to-video.hf.space
|
2 |
+
WS_ADDRESS=wss://gosign-de-image-to-video.hf.space/ws
|
3 |
+
|
4 |
+
# SERVER_ADDRESS=http://127.0.0.1:8188/
|
5 |
+
# WS_ADDRESS=ws://127.0.0.1:8188/ws
|
app.py
CHANGED
@@ -12,6 +12,8 @@ from dotenv import load_dotenv
|
|
12 |
from flask import Flask, request, jsonify, render_template, send_file, send_from_directory
|
13 |
from PIL import Image
|
14 |
from werkzeug.utils import secure_filename
|
|
|
|
|
15 |
|
16 |
# Load environment variables from the .env file
|
17 |
load_dotenv()
|
@@ -42,7 +44,7 @@ def get_image(filename, subfolder, image_type, token):
|
|
42 |
|
43 |
|
44 |
def get_images(ws, workflow, token):
|
45 |
-
prompt_id = queue_prompt(workflow, token)
|
46 |
output_images = {}
|
47 |
|
48 |
while True:
|
@@ -66,13 +68,6 @@ def get_images(ws, workflow, token):
|
|
66 |
|
67 |
return output_images
|
68 |
|
69 |
-
|
70 |
-
def fetch_video(video_data, token):
|
71 |
-
video_url = f"{server_address}/download?file={video_data['filename']}"
|
72 |
-
req = urllib.request.Request(video_url)
|
73 |
-
req.add_header("Authorization", f"Bearer {token}")
|
74 |
-
return urllib.request.urlopen(req).read()
|
75 |
-
|
76 |
# Default route for home welcome
|
77 |
@app.route('/')
|
78 |
def home():
|
@@ -226,18 +221,47 @@ def get_history(prompt_id, token):
|
|
226 |
return make_request(f"{server_address}/history/{prompt_id}", headers=headers)
|
227 |
|
228 |
def get_video_data(filename, subfolder, token):
|
229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
230 |
url = f"{server_address}/view?{urllib.parse.urlencode(url_values)}"
|
|
|
|
|
|
|
231 |
req = urllib.request.Request(url)
|
232 |
req.add_header("Authorization", f"Bearer {token}")
|
233 |
|
234 |
try:
|
235 |
-
return
|
|
|
|
|
|
|
236 |
except urllib.error.HTTPError as e:
|
237 |
print(f"HTTP Error: {e.code} - {e.reason}")
|
238 |
-
print(e.read())
|
239 |
raise
|
240 |
|
|
|
|
|
|
|
|
|
|
|
241 |
# Route: Image to Video
|
242 |
@app.route('/image_to_video', methods=['POST'])
|
243 |
def image_to_video():
|
@@ -259,19 +283,31 @@ def image_to_video():
|
|
259 |
base64_image = data.get('base64_image')
|
260 |
|
261 |
if image_file:
|
262 |
-
#
|
263 |
if not allowed_file(image_file.filename):
|
264 |
return jsonify({'error': 'Unsupported image format'}), 400
|
|
|
|
|
265 |
filename = secure_filename(image_file.filename)
|
266 |
-
image_path = f"/tmp/{uuid.uuid4()}_{filename}"
|
267 |
|
|
|
|
|
|
|
268 |
|
|
|
|
|
|
|
|
|
269 |
image_file.save(image_path)
|
|
|
|
|
|
|
|
|
270 |
elif base64_image:
|
271 |
# Save base64 image
|
272 |
try:
|
273 |
-
|
274 |
-
# return jsonify({'
|
275 |
|
276 |
except Exception as e:
|
277 |
return jsonify({'error': f'Invalid base64 image data: {str(e)}'}), 400
|
@@ -286,7 +322,7 @@ def image_to_video():
|
|
286 |
|
287 |
# Modify workflow with inputs
|
288 |
workflow["30"]["inputs"]["prompt"] = text_prompt
|
289 |
-
|
290 |
workflow["31"]["inputs"]["prompt"] = "Low quality, watermark, strange motion"
|
291 |
workflow["57"]["inputs"]["seed"] = random.randint(100000000000000, 999999999999999)
|
292 |
|
@@ -308,29 +344,45 @@ def image_to_video():
|
|
308 |
if message.get('type') == 'executing' and message['data']['node'] is None and message['data']['prompt_id'] == prompt_id:
|
309 |
break
|
310 |
|
311 |
-
|
312 |
history = get_history(prompt_id, token).get(prompt_id, {})
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
video = node_output['videos'][0]
|
317 |
-
video_data = get_video_data(video['filename'], video['subfolder'], token)
|
318 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
319 |
if not video_data:
|
320 |
return jsonify({'error': 'Failed to generate video'}), 500
|
321 |
|
322 |
-
# Save
|
323 |
-
local_video_path = f"/tmp/generated_video_{uuid.uuid4()}.mp4"
|
324 |
-
with open(local_video_path, 'wb') as f:
|
325 |
-
|
326 |
|
327 |
-
|
|
|
328 |
io.BytesIO(video_data),
|
329 |
mimetype='video/mp4',
|
330 |
as_attachment=True,
|
331 |
download_name='generated_video.mp4'
|
332 |
)
|
333 |
|
|
|
|
|
|
|
|
|
|
|
334 |
|
335 |
if __name__ == '__main__':
|
336 |
app.run(host='0.0.0.0', port=7860, debug=True) # Removed 'debug=True'
|
|
|
12 |
from flask import Flask, request, jsonify, render_template, send_file, send_from_directory
|
13 |
from PIL import Image
|
14 |
from werkzeug.utils import secure_filename
|
15 |
+
import urllib.parse
|
16 |
+
import urllib.request
|
17 |
|
18 |
# Load environment variables from the .env file
|
19 |
load_dotenv()
|
|
|
44 |
|
45 |
|
46 |
def get_images(ws, workflow, token):
|
47 |
+
prompt_id = queue_prompt(workflow, token)
|
48 |
output_images = {}
|
49 |
|
50 |
while True:
|
|
|
68 |
|
69 |
return output_images
|
70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
# Default route for home welcome
|
72 |
@app.route('/')
|
73 |
def home():
|
|
|
221 |
return make_request(f"{server_address}/history/{prompt_id}", headers=headers)
|
222 |
|
223 |
def get_video_data(filename, subfolder, token):
|
224 |
+
"""
|
225 |
+
Retrieve a video from the server using filename, subfolder, and token.
|
226 |
+
"""
|
227 |
+
# Handle empty subfolder case gracefully
|
228 |
+
subfolder = subfolder or '' # Default to empty string if None
|
229 |
+
|
230 |
+
# Construct query parameters
|
231 |
+
# url_values = {
|
232 |
+
# 'filename': filename,
|
233 |
+
# 'subfolder': subfolder,
|
234 |
+
# 'type': 'video'
|
235 |
+
# }
|
236 |
+
|
237 |
+
# Construct query parameters
|
238 |
+
url_values = {
|
239 |
+
'filename': filename
|
240 |
+
}
|
241 |
+
|
242 |
+
# Build the URL with encoded query parameters
|
243 |
url = f"{server_address}/view?{urllib.parse.urlencode(url_values)}"
|
244 |
+
print(f"Requesting URL: {url}")
|
245 |
+
|
246 |
+
# Prepare the request with authorization token
|
247 |
req = urllib.request.Request(url)
|
248 |
req.add_header("Authorization", f"Bearer {token}")
|
249 |
|
250 |
try:
|
251 |
+
# Fetch and return the video data
|
252 |
+
response = urllib.request.urlopen(req)
|
253 |
+
return response.read()
|
254 |
+
|
255 |
except urllib.error.HTTPError as e:
|
256 |
print(f"HTTP Error: {e.code} - {e.reason}")
|
257 |
+
print(e.read().decode()) # Decode error message for readability
|
258 |
raise
|
259 |
|
260 |
+
except urllib.error.URLError as e:
|
261 |
+
print(f"URL Error: {e.reason}")
|
262 |
+
raise
|
263 |
+
|
264 |
+
|
265 |
# Route: Image to Video
|
266 |
@app.route('/image_to_video', methods=['POST'])
|
267 |
def image_to_video():
|
|
|
283 |
base64_image = data.get('base64_image')
|
284 |
|
285 |
if image_file:
|
286 |
+
# Check if the file has an allowed extension
|
287 |
if not allowed_file(image_file.filename):
|
288 |
return jsonify({'error': 'Unsupported image format'}), 400
|
289 |
+
|
290 |
+
# Secure the filename
|
291 |
filename = secure_filename(image_file.filename)
|
|
|
292 |
|
293 |
+
# Generate a unique path for the image
|
294 |
+
unique_filename = f"{uuid.uuid4()}_{filename}"
|
295 |
+
image_path = os.path.join('static', unique_filename)
|
296 |
|
297 |
+
# Ensure the 'static' directory exists
|
298 |
+
os.makedirs('static', exist_ok=True)
|
299 |
+
|
300 |
+
# Save the image to the static directory
|
301 |
image_file.save(image_path)
|
302 |
+
|
303 |
+
# Construct the public URL to access the image
|
304 |
+
image_url = f"https://gosign-de-comfyui-api.hf.space/{image_path}"
|
305 |
+
|
306 |
elif base64_image:
|
307 |
# Save base64 image
|
308 |
try:
|
309 |
+
image_url = save_base64_image(base64_image)
|
310 |
+
# return jsonify({'image_url': image_url})
|
311 |
|
312 |
except Exception as e:
|
313 |
return jsonify({'error': f'Invalid base64 image data: {str(e)}'}), 400
|
|
|
322 |
|
323 |
# Modify workflow with inputs
|
324 |
workflow["30"]["inputs"]["prompt"] = text_prompt
|
325 |
+
workflow["73"]["inputs"]["url"] = image_url
|
326 |
workflow["31"]["inputs"]["prompt"] = "Low quality, watermark, strange motion"
|
327 |
workflow["57"]["inputs"]["seed"] = random.randint(100000000000000, 999999999999999)
|
328 |
|
|
|
344 |
if message.get('type') == 'executing' and message['data']['node'] is None and message['data']['prompt_id'] == prompt_id:
|
345 |
break
|
346 |
|
347 |
+
# Fetch the complete history for the provided prompt_id
|
348 |
history = get_history(prompt_id, token).get(prompt_id, {})
|
349 |
+
print(f"History for prompt {prompt_id}: {history}")
|
350 |
+
|
351 |
+
video_data = None # Initialize video data
|
|
|
|
|
352 |
|
353 |
+
# Loop through history outputs to find video or gif data
|
354 |
+
for node_id, node_output in history.get('outputs', {}).items():
|
355 |
+
if 'gifs' in node_output:
|
356 |
+
video = node_output['gifs'][0] # Take the first GIF or video
|
357 |
+
try:
|
358 |
+
print(f"Fetching video: {video['filename']} from {video['subfolder']}")
|
359 |
+
video_data = get_video_data(video['filename'], video['subfolder'], token)
|
360 |
+
break # Stop after successfully fetching the first video or GIF
|
361 |
+
except Exception as e:
|
362 |
+
print(f"Failed to retrieve video: {str(e)}")
|
363 |
+
|
364 |
+
# Check if video data was retrieved successfully
|
365 |
if not video_data:
|
366 |
return jsonify({'error': 'Failed to generate video'}), 500
|
367 |
|
368 |
+
# Save the video locally
|
369 |
+
# local_video_path = f"/tmp/generated_video_{uuid.uuid4()}.mp4"
|
370 |
+
# with open(local_video_path, 'wb') as f:
|
371 |
+
# f.write(video_data)
|
372 |
|
373 |
+
# Return the video as an HTTP response
|
374 |
+
response = send_file(
|
375 |
io.BytesIO(video_data),
|
376 |
mimetype='video/mp4',
|
377 |
as_attachment=True,
|
378 |
download_name='generated_video.mp4'
|
379 |
)
|
380 |
|
381 |
+
# Delete the saved image after sending the response
|
382 |
+
if image_path and os.path.exists(image_path):
|
383 |
+
os.remove(image_path) # Remove the image file
|
384 |
+
|
385 |
+
return response
|
386 |
|
387 |
if __name__ == '__main__':
|
388 |
app.run(host='0.0.0.0', port=7860, debug=True) # Removed 'debug=True'
|
sample-response.json
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{'prompt': [
|
2 |
+
3, '2d7683a1-f132-4f75-bb02-ced4975a745b',
|
3 |
+
{'1': {'inputs': {'model': 'THUDM/CogVideoX-5b-I2V', 'precision': 'bf16', 'fp8_transformer': 'disabled', 'compile': 'disabled', 'enable_sequential_cpu_offload': False
|
4 |
+
}, 'class_type': 'DownloadAndLoadCogVideoModel', '_meta': {'title': '(Down)load CogVideo Model'
|
5 |
+
}
|
6 |
+
}, '20': {'inputs': {'clip_name': 't5\\google_t5-v1_1-xxl_encoderonly-fp8_e4m3fn.safetensors', 'type': 'sd3'
|
7 |
+
}, 'class_type': 'CLIPLoader', '_meta': {'title': 'Load CLIP'
|
8 |
+
}
|
9 |
+
}, '30': {'inputs': {'prompt': 'A beautiful sunset over the mountains', 'strength': 1.0, 'force_offload': True, 'clip': ['20',
|
10 |
+
0
|
11 |
+
]
|
12 |
+
}, 'class_type': 'CogVideoTextEncode', '_meta': {'title': 'CogVideo TextEncode'
|
13 |
+
}
|
14 |
+
}, '31': {'inputs': {'prompt': 'Low quality, watermark, strange motion', 'strength': 1.0, 'force_offload': True, 'clip': ['20',
|
15 |
+
0
|
16 |
+
]
|
17 |
+
}, 'class_type': 'CogVideoTextEncode', '_meta': {'title': 'CogVideo TextEncode'
|
18 |
+
}
|
19 |
+
}, '37': {'inputs': {'width': 720, 'height': 480, 'upscale_method': 'lanczos', 'keep_proportion': False, 'divisible_by': 16, 'crop': 'disabled', 'image': ['73',
|
20 |
+
0
|
21 |
+
]
|
22 |
+
}, 'class_type': 'ImageResizeKJ', '_meta': {'title': 'Resize Image'
|
23 |
+
}
|
24 |
+
}, '44': {'inputs': {'frame_rate': 12.0, 'loop_count': 0, 'filename_prefix': 'CogVideoX-I2V', 'format': 'video/h264-mp4', 'pix_fmt': 'yuv420p', 'crf': 19, 'save_metadata': True, 'pingpong': False, 'save_output': True, 'images': ['56',
|
25 |
+
0
|
26 |
+
]
|
27 |
+
}, 'class_type': 'VHS_VideoCombine', '_meta': {'title': 'Video Combine π₯π
₯π
π
’'
|
28 |
+
}
|
29 |
+
}, '56': {'inputs': {'enable_vae_tiling': False, 'tile_sample_min_height': 96, 'tile_sample_min_width': 96, 'tile_overlap_factor_height': 0.083, 'tile_overlap_factor_width': 0.083, 'auto_tile_size': True, 'pipeline': ['57',
|
30 |
+
0
|
31 |
+
], 'samples': ['57',
|
32 |
+
1
|
33 |
+
]
|
34 |
+
}, 'class_type': 'CogVideoDecode', '_meta': {'title': 'CogVideo Decode'
|
35 |
+
}
|
36 |
+
}, '57': {'inputs': {'height': 480, 'width': 720, 'num_frames': 49, 'steps': 5, 'cfg': 6.0, 'seed': 660821088584312, 'scheduler': 'DPM++', 'denoise_strength': 1.0, 'pipeline': ['1',
|
37 |
+
0
|
38 |
+
], 'positive': ['30',
|
39 |
+
0
|
40 |
+
], 'negative': ['31',
|
41 |
+
0
|
42 |
+
], 'image_cond_latents': ['58',
|
43 |
+
0
|
44 |
+
]
|
45 |
+
}, 'class_type': 'CogVideoSampler', '_meta': {'title': 'CogVideo Sampler'
|
46 |
+
}
|
47 |
+
}, '58': {'inputs': {'chunk_size': 16, 'enable_tiling': True, 'pipeline': ['1',
|
48 |
+
0
|
49 |
+
], 'image': ['37',
|
50 |
+
0
|
51 |
+
]
|
52 |
+
}, 'class_type': 'CogVideoImageEncode', '_meta': {'title': 'CogVideo ImageEncode'
|
53 |
+
}
|
54 |
+
}, '73': {'inputs': {'url': 'https: //huggingface.co/spaces/gosign-de/comfyui-api/resolve/main/images/iStock_000014226797_Small.jpg', 'cache': True}, 'class_type': 'LoadImageByUrl //Browser', '_meta': {'title': 'Load Image By URL'}}, '75': {'inputs': {'images': ['73', 0]}, 'class_type': 'PreviewImage', '_meta': {'title': 'Preview Image'}}}, {'client_id': 'bb7de022-33ad-4c01-b272-bf2b1eb05e4e'}, ['75', '44']], 'outputs': {'44': {'gifs': [{'filename': 'CogVideoX-I2V_00003.mp4', 'subfolder': '', 'type': 'output', 'format': 'video/h264-mp4', 'frame_rate': 12.0}]}, '75': {'images': [{'filename': 'ComfyUI_temp_favdf_00001_.png', 'subfolder': '', 'type': 'temp'}]}}, 'status': {'status_str': 'success', 'completed': True, 'messages': [['execution_start', {'prompt_id': '2d7683a1-f132-4f75-bb02-ced4975a745b', 'timestamp': 1729158615147}], ['execution_cached', {'nodes': ['1', '20', '30', '31', '37', '58', '73', '75'], 'prompt_id': '2d7683a1-f132-4f75-bb02-ced4975a745b', 'timestamp': 1729158615162}], ['execution_success', {'prompt_id': '2d7683a1-f132-4f75-bb02-ced4975a745b', 'timestamp': 1729158723213}]]}, 'meta': {'44': {'node_id': '44', 'display_node': '44', 'parent_node': None, 'real_node_id': '44'}, '75': {'node_id': '75', 'display_node': '75', 'parent_node': None, 'real_node_id': '75'}}}
|
static/0e65b3a0-cc05-4f5c-8b20-d5854b2fd6d7.jpeg
DELETED
Binary file (708 kB)
|
|
static/127a05b0-0395-45a5-a0c0-ad8ffad070b5.jpeg
DELETED
Binary file (708 kB)
|
|
static/71a7077f-cd9c-4d9c-8da8-4975f8853bc5.jpeg
DELETED
Binary file (708 kB)
|
|
static/eefb530f-7841-4050-9b04-c51444e1a6eb.jpeg
DELETED
Binary file (708 kB)
|
|
workflows/cogvideox_image_to_video_workflow_api.json
CHANGED
@@ -118,7 +118,7 @@
|
|
118 |
"height": 480,
|
119 |
"width": 720,
|
120 |
"num_frames": 49,
|
121 |
-
"steps":
|
122 |
"cfg": 6,
|
123 |
"seed": 65334758276105,
|
124 |
"scheduler": "DPM++",
|
|
|
118 |
"height": 480,
|
119 |
"width": 720,
|
120 |
"num_frames": 49,
|
121 |
+
"steps": 2,
|
122 |
"cfg": 6,
|
123 |
"seed": 65334758276105,
|
124 |
"scheduler": "DPM++",
|