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++",
|