ghostai1 commited on
Commit
aa4b9e2
·
verified ·
1 Parent(s): 4266e41

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +2 -355
app.py CHANGED
@@ -2,7 +2,7 @@
2
  # FILE: app.py
3
  # Description: Image-to-Video generation server with Gradio UI and FastAPI for Hugging Face Spaces
4
  # Version: 1.2.8
5
- # Timestamp: 2025-07-01 20:03 CDT
6
  # Author: Grok 3, built by xAI (based on GhostAI's ghostpack_gradio_f1.py)
7
  # NOTE: Optimized for Hugging Face Spaces with H200 GPU, 25 min/day render time
8
  # Loads models from Hugging Face Hub to avoid HDD costs
@@ -1024,357 +1024,4 @@ def process(img, prm, npr, sd, sec, win, stp, cfg, gsc, rsc, kee, tea, crf, disa
1024
  yield None, None, "", "", gr.update(interactive=False), gr.update(interactive=True)
1025
  stream = AsyncStream()
1026
  jid = str(uuid.uuid4())
1027
- async_run(worker, img, prm, npr, sd, sec, win, stp, cfg, gsc, rsc, kee, tea, crf, disable_prompt_mods, link_steps_window, stream, jid)
1028
- out, log = None, ""
1029
- try:
1030
- while True:
1031
- flag, data = stream.output_queue.next()
1032
- if job_status.get(jid, {}).get("status") == "complete":
1033
- break
1034
- if flag == "file":
1035
- out = data
1036
- yield out, gr.update(), gr.update(), log, gr.update(interactive=False), gr.update(interactive=True)
1037
- if flag == "progress":
1038
- pv, desc, html = data
1039
- log = desc
1040
- yield gr.update(), gr.update(visible=True, value=pv), desc, html, gr.update(interactive=False), gr.update(interactive=True)
1041
- if flag == "complete":
1042
- yield data, gr.update(visible=False), "Generation complete", "", gr.update(interactive=True), gr.update(interactive=False)
1043
- break
1044
- if flag == "end":
1045
- yield out, gr.update(visible=False), f"Error: {data}", "", gr.update(interactive=True), gr.update(interactive=False)
1046
- break
1047
- except Exception as e:
1048
- logger.error(f"Process loop failed: {e}")
1049
- yield out, gr.update(visible=False), f"Error: {str(e)}", "", gr.update(interactive=True), gr.update(interactive=False)
1050
- job_status[jid]["status"] = "error"
1051
- finally:
1052
- clear_queue(stream.input_queue)
1053
- clear_queue(stream.output_queue)
1054
- torch.cuda.empty_cache()
1055
-
1056
- def end_process():
1057
- global stream
1058
- if stream:
1059
- stream.input_queue.push("end")
1060
- logger.info("Gradio: Render stop requested")
1061
- print(f"{red('Gradio: Render stop requested')}")
1062
-
1063
- # Gradio UI (same as original)
1064
- quick_prompts = [
1065
- ["Smooth animation: A character waves for 3 seconds, then stands still for 2 seconds, static camera, silent."],
1066
- ["Smooth animation: A character moves for 5 seconds, static camera, silent."]
1067
- ]
1068
- css = make_progress_bar_css() + """
1069
- .orange-button{background:#ff6200;color:#fff;border-color:#ff6200;}
1070
- .load-button{background:#4CAF50;color:#fff;border-color:#4CAF50;margin-left:10px;}
1071
- .big-setting-button{background:#0066cc;color:#fff;border:none;padding:14px 24px;font-size:18px;width:100%;border-radius:6px;margin:8px 0;}
1072
- .styled-dropdown{width:250px;padding:5px;border-radius:4px;}
1073
- .viewer-column{width:100%;max-width:900px;margin:0 auto;}
1074
- .media-preview img,.media-preview video{max-width:100%;height:380px;object-fit:contain;border:1px solid #444;border-radius:6px;}
1075
- .media-container{display:flex;gap:20px;align-items:flex-start;}
1076
- .control-box{min-width:220px;}
1077
- .control-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
1078
- .image-gallery{display:grid!important;grid-template-columns:repeat(auto-fit,minmax(300px,1fr))!important;gap:10px;padding:10px!important;overflow-y:auto!important;max-height:360px!important;}
1079
- .image-gallery .gallery-item{padding:10px;height:360px!important;width:300px!important;}
1080
- .image-gallery img{object-fit:contain;height:360px!important;width:300px!important;}
1081
- .video-gallery{display:grid!important;grid-template-columns:repeat(auto-fit,minmax(300px,1fr))!important;gap:10px;padding:10px!important;overflow-y:auto!important;max-height:360px!important;}
1082
- .video-gallery .gallery-item{padding:10px;height:360px!important;width:300px!important;}
1083
- .video-gallery video{object-fit:contain;height:360px!important;width:300px!important;}
1084
- .stop-button {background-color: #ff4d4d !important; color: white !important;}
1085
- """
1086
-
1087
- blk = gr.Blocks(css=css, title="GhostPack F1 Pro").queue()
1088
- with blk:
1089
- gr.Markdown("# 👻 GhostPack F1 Pro")
1090
- with gr.Tabs():
1091
- with gr.TabItem("👻 Generate"):
1092
- with gr.Row():
1093
- with gr.Column():
1094
- img_in = gr.Image(sources="upload", type="numpy", label="Image", height=320)
1095
- generate_button = gr.Button("Generate Video", elem_id="generate_button")
1096
- stop_button = gr.Button("Stop Generation", elem_id="stop_button", elem_classes="stop-button")
1097
- prm = gr.Textbox(
1098
- label="Prompt",
1099
- value="Smooth animation: A female stands with subtle, sensual micro-movements, breathing gently, slight head tilt, static camera, silent",
1100
- elem_id="prompt_input",
1101
- )
1102
- npr = gr.Textbox(
1103
- label="Negative Prompt",
1104
- value="low quality, blurry, speaking, talking, moaning, vocalizing, lip movement, mouth animation, sound, dialogue, speech, whispering, shouting, lip sync, facial animation, expressive face, verbal expression, animated mouth",
1105
- elem_id="negative_prompt_input",
1106
- )
1107
- save_msg = gr.Markdown("")
1108
- disable_prompt_mods = gr.Checkbox(label="Disable Prompt Modifications", value=False)
1109
- link_steps_window = gr.Checkbox(label="Link Steps and Latent Window", value=True)
1110
- btn_save = gr.Button("Save Prompt")
1111
- btn1, btn2, btn3 = (
1112
- gr.Button("Load Most Recent"),
1113
- gr.Button("Load 2nd Recent"),
1114
- gr.Button("Load 3rd Recent"),
1115
- )
1116
- ds = gr.Dataset(samples=quick_prompts, label="Quick List", components=[prm])
1117
- ds.click(lambda x: x[0], [ds], [prm])
1118
- btn_save.click(save_prompt_fn, [prm, npr], [save_msg])
1119
- btn1.click(lambda: load_prompt_fn(0), [], [prm])
1120
- btn2.click(lambda: load_prompt_fn(1), [], [prm])
1121
- btn3.click(lambda: load_prompt_fn(2), [], [prm])
1122
- camera_action_input = gr.Dropdown(
1123
- choices=[
1124
- "Static Camera", "Slight Orbit Left", "Slight Orbit Right",
1125
- "Slight Orbit Up", "Slight Orbit Down", "Top-Down View",
1126
- "Slight Zoom In", "Slight Zoom Out",
1127
- ],
1128
- label="Camera Action",
1129
- value="Static Camera",
1130
- elem_id="camera_action_input",
1131
- info="Select a camera movement to append to the prompt.",
1132
- )
1133
- camera_action_input.change(
1134
- fn=lambda prompt, camera_action: update_prompt(prompt, camera_action),
1135
- inputs=[prm, camera_action_input],
1136
- outputs=prm,
1137
- )
1138
- with gr.Column():
1139
- pv = gr.Image(label="Next Latents", height=200, visible=False)
1140
- vid = gr.Video(label="Finished", autoplay=True, height=500, loop=True, show_share_button=False)
1141
- log_md = gr.Markdown("")
1142
- bar = gr.HTML("")
1143
- with gr.Column():
1144
- se = gr.Number(label="Seed", value=31337, precision=0, elem_id="seed_input")
1145
- sec = gr.Slider(label="Video Length (s)", minimum=1, maximum=10, value=8.0, step=0.1, elem_id="video_length_input")
1146
- win = gr.Slider(label="Latent Window", minimum=1, maximum=10, value=3, step=1, elem_id="latent_window_input")
1147
- stp = gr.Slider(label="Steps", minimum=1, maximum=100, value=12, step=1, elem_id="steps_input")
1148
- cfg = gr.Slider(label="CFG", minimum=1, maximum=32, value=1.7, step=0.01, elem_id="cfg_input")
1149
- gsc = gr.Slider(label="Distilled CFG", minimum=1, maximum=32, value=4.0, step=0.01, elem_id="distilled_cfg_input")
1150
- rsc = gr.Slider(label="CFG Re-Scale", minimum=0, maximum=1, value=0.5, step=0.01, elem_id="cfg_rescale_input")
1151
- kee = gr.Slider(label="GPU Keep (GB)", minimum=6, maximum=free_mem, value=6.5, step=0.1, elem_id="gpu_keep_input")
1152
- crf = gr.Slider(label="MP4 CRF", minimum=0, maximum=100, value=20, step=1, elem_id="mp4_crf_input")
1153
- tea = gr.Checkbox(label="Use TeaCache", value=True, elem_id="use_teacache_input")
1154
- generate_button.click(
1155
- fn=process,
1156
- inputs=[img_in, prm, npr, se, sec, win, stp, cfg, gsc, rsc, kee, tea, crf, disable_prompt_mods, link_steps_window],
1157
- outputs=[vid, pv, log_md, bar, generate_button, stop_button],
1158
- )
1159
- stop_button.click(fn=end_process)
1160
- gr.Button("Update Progress").click(fn=lambda: get_progress(), outputs=[log_md, bar])
1161
-
1162
- with gr.TabItem("🖼️ Image Gallery"):
1163
- with gr.Row(elem_classes="media-container"):
1164
- with gr.Column(scale=3):
1165
- image_preview = gr.Image(
1166
- label="Viewer", value=(list_images()[0] if list_images() else None),
1167
- interactive=False, elem_classes="media-preview",
1168
- )
1169
- with gr.Column(elem_classes="control-box"):
1170
- image_dropdown = gr.Dropdown(
1171
- choices=[os.path.basename(i) for i in list_images()],
1172
- value=(os.path.basename(list_images()[0]) if list_images() else None),
1173
- label="Select", elem_classes="styled-dropdown",
1174
- )
1175
- with gr.Row(elem_classes="control-grid"):
1176
- load_btn = gr.Button("Load", elem_classes="load-button")
1177
- next_btn = gr.Button("Next", elem_classes="load-button")
1178
- with gr.Row(elem_classes="control-grid"):
1179
- refresh_btn = gr.Button("Refresh")
1180
- delete_btn = gr.Button("Delete", elem_classes="orange-button")
1181
- image_gallery = gr.Gallery(
1182
- value=list_images(), label="Thumbnails", columns=6, height=360,
1183
- allow_preview=False, type="filepath", elem_classes="image-gallery",
1184
- )
1185
- load_btn.click(load_image, [image_dropdown], [image_preview, image_dropdown])
1186
- next_btn.click(next_image_and_load, [image_dropdown], [image_preview, image_dropdown])
1187
- refresh_btn.click(
1188
- lambda: (
1189
- gr.update(choices=[os.path.basename(i) for i in list_images()], value=os.path.basename(list_images()[0]) if list_images() else None),
1190
- gr.update(value=list_images()[0] if list_images() else None),
1191
- gr.update(value=list_images()),
1192
- ),
1193
- [], [image_dropdown, image_preview, image_gallery],
1194
- )
1195
- delete_btn.click(
1196
- lambda sel: (
1197
- os.remove(os.path.join(VIDEO_IMG_DIR, sel)) if sel and os.path.exists(os.path.join(VIDEO_IMG_DIR, sel)) else None
1198
- ) or load_image(""),
1199
- [image_dropdown], [image_preview, image_dropdown],
1200
- )
1201
- image_gallery.select(gallery_image_select, [], [image_preview, image_dropdown])
1202
-
1203
- with gr.TabItem("🎬 Video Gallery"):
1204
- with gr.Row(elem_classes="media-container"):
1205
- with gr.Column(scale=3):
1206
- video_preview = gr.Video(
1207
- label="Viewer", value=(list_videos()[0] if list_videos() else None),
1208
- autoplay=True, loop=True, interactive=False, elem_classes="media-preview",
1209
- )
1210
- with gr.Column(elem_classes="control-box"):
1211
- video_dropdown = gr.Dropdown(
1212
- choices=[os.path.basename(v) for v in list_videos()],
1213
- value=(os.path.basename(list_videos()[0]) if list_videos() else None),
1214
- label="Select", elem_classes="styled-dropdown",
1215
- )
1216
- with gr.Row(elem_classes="control-grid"):
1217
- load_vbtn = gr.Button("Load", elem_classes="load-button")
1218
- next_vbtn = gr.Button("Next", elem_classes="load-button")
1219
- with gr.Row(elem_classes="control-grid"):
1220
- refresh_v = gr.Button("Refresh")
1221
- delete_v = gr.Button("Delete", elem_classes="orange-button")
1222
- video_gallery = gr.Gallery(
1223
- value=list_videos(), label="Thumbnails", columns=6, height=360,
1224
- allow_preview=False, type="filepath", elem_classes="video-gallery",
1225
- )
1226
- load_vbtn.click(load_video, [video_dropdown], [video_preview, video_dropdown])
1227
- next_vbtn.click(next_video_and_load, [video_dropdown], [video_preview, video_dropdown])
1228
- refresh_v.click(
1229
- lambda: (
1230
- gr.update(choices=[os.path.basename(v) for v in list_videos()], value=os.path.basename(list_videos()[0]) if list_videos() else None),
1231
- gr.update(value=list_videos()[0] if list_videos() else None),
1232
- gr.update(value=list_videos()),
1233
- ),
1234
- [], [video_dropdown, video_preview, video_gallery],
1235
- )
1236
- delete_v.click(
1237
- lambda sel: (
1238
- os.remove(os.path.join(VIDEO_OUTPUT_DIR, sel)) if sel and os.path.exists(os.path.join(VIDEO_OUTPUT_DIR, sel)) else None
1239
- ) or load_video(""),
1240
- [video_dropdown], [video_preview, video_dropdown],
1241
- )
1242
- video_gallery.select(gallery_video_select, [], [video_preview, video_dropdown])
1243
-
1244
- with gr.TabItem("👻 About"):
1245
- gr.Markdown("## GhostPack F1 Pro")
1246
- with gr.Row():
1247
- with gr.Column():
1248
- gr.Markdown("**🛠️ Description**\nImage-to-Video toolkit powered by HunyuanVideo & FramePack-F1")
1249
- with gr.Column():
1250
- gr.Markdown(f"**📦 Version**\n{VERSION}")
1251
- with gr.Column():
1252
- gr.Markdown("**✍️ Author**\nGhostAI")
1253
- with gr.Column():
1254
- gr.Markdown("**🔗 Repo**\nhttps://huggingface.co/spaces/ghostai1/GhostPack")
1255
-
1256
- with gr.TabItem("⚙️ Settings"):
1257
- ct = gr.Button("Clear Temp", elem_classes="big-setting-button")
1258
- ctmsg = gr.Markdown("")
1259
- co = gr.Button("Clear Old", elem_classes="big-setting-button")
1260
- comsg = gr.Markdown("")
1261
- ci = gr.Button("Clear Images", elem_classes="big-setting-button")
1262
- cimg = gr.Markdown("")
1263
- cv = gr.Button("Clear Videos", elem_classes="big-setting-button")
1264
- cvid = gr.Markdown("")
1265
- ct.click(clear_temp_videos, [], ctmsg)
1266
- co.click(clear_old_files, [], comsg)
1267
- ci.click(clear_images, [], cimg)
1268
- cv.click(clear_videos, [], cvid)
1269
-
1270
- with gr.TabItem("🛠️ Install"):
1271
- xs = gr.Textbox(value=status_xformers(), interactive=False, label="xformers")
1272
- bx = gr.Button("Install xformers", elem_classes="big-setting-button")
1273
- ss = gr.Textbox(value=status_sage(), interactive=False, label="sage-attn")
1274
- bs = gr.Button("Install sage-attn", elem_classes="big-setting-button")
1275
- fs = gr.Textbox(value=status_flash(), interactive=False, label="flash-attn")
1276
- bf = gr.Button("Install flash-attn", elem_classes="big-setting-button")
1277
- cs = gr.Textbox(value=status_colorama(), interactive=False, label="colorama")
1278
- bc = gr.Button("Install colorama", elem_classes="big-setting-button")
1279
- bx.click(install_xformers, [], xs)
1280
- bs.click(install_sage_attn, [], ss)
1281
- bf.click(install_flash_attn, [], fs)
1282
- bc.click(install_colorama, [], cs)
1283
-
1284
- with gr.TabItem("📜 Logs"):
1285
- logs = gr.Textbox(lines=20, interactive=False, label="Install Logs")
1286
- rl = gr.Button("Refresh", elem_classes="big-setting-button")
1287
- cl = gr.Button("Clear", elem_classes="big-setting-button")
1288
- rl.click(refresh_logs, [], logs)
1289
- cl.click(clear_logs, [], logs)
1290
-
1291
- gr.HTML(
1292
- """
1293
- <script>
1294
- document.querySelectorAll('.video-gallery video').forEach(v => {
1295
- v.addEventListener('loadedmetadata', () => {
1296
- if (v.duration > 2) v.currentTime = 2;
1297
- });
1298
- });
1299
- </script>
1300
- """
1301
- )
1302
-
1303
- def update_prompt(prompt, camera_action):
1304
- camera_actions = [
1305
- "static camera", "slight camera orbit left", "slight camera orbit right",
1306
- "slight camera orbit up", "slight camera orbit down", "top-down view",
1307
- "slight camera zoom in", "slight camera zoom out",
1308
- ]
1309
- for action in camera_actions:
1310
- prompt = re.sub(rf",\s*{re.escape(action)}\b", "", prompt, flags=re.IGNORECASE).strip()
1311
- if camera_action and camera_action != "None":
1312
- camera_phrase = f", {camera_action.lower()}"
1313
- if len(prompt.split()) + len(camera_phrase.split()) <= 50:
1314
- return prompt + camera_phrase
1315
- else:
1316
- logger.warning(f"Prompt exceeds 50 words after adding camera action: {prompt}")
1317
- print(f"{yellow(f'API: Warning: Prompt exceeds 50 words with camera action')}")
1318
- return prompt
1319
-
1320
- def get_progress():
1321
- return f"Status: {job_status.get('latest', {'status': 'idle'})['status']}\nProgress: {job_status.get('latest', {'progress': 0.0})['progress']:.1f}%\nLast Render Time: {job_status.get('latest', {'render_time': 0})['render_time']:.1f}s"
1322
-
1323
- # Check for port conflicts
1324
- if is_port_in_use(args.port):
1325
- logger.error(f"Port {args.port} is already in use")
1326
- print(f"{red(f'Error: Port {args.port} is already in use. Please stop other instances or change ports.')}")
1327
- sys.exit(1)
1328
-
1329
- # Run FastAPI and optional Gradio
1330
- def run_api():
1331
- try:
1332
- logger.info(f"Starting FastAPI on {args.server}:{args.port}")
1333
- print(f"{green(f'Starting FastAPI on {args.server}:{args.port}')}")
1334
- uvicorn.run(app, host=args.server, port=args.port)
1335
- except Exception as e:
1336
- logger.error(f"Failed to start FastAPI: {e}", exc_info=True)
1337
- print(f"{red(f'Error: Failed to start FastAPI: {e}')}")
1338
- sys.exit(1)
1339
-
1340
- if __name__ == "__main__":
1341
- try:
1342
- logger.info(f"Starting GhostPack F1 Pro Server version {VERSION}")
1343
- print(f"Starting GhostPack F1 Pro Server version {VERSION}")
1344
- api_thread = Thread(target=run_api)
1345
- api_thread.daemon = True
1346
- api_thread.start()
1347
- time.sleep(5)
1348
- try:
1349
- response = requests.get(f"http://{args.server}:{args.port}/health", timeout=10)
1350
- if response.status_code != 200:
1351
- raise RuntimeError("FastAPI health check failed")
1352
- logger.info("FastAPI health check passed")
1353
- print(f"{green('FastAPI health check passed')}")
1354
- except Exception as e:
1355
- logger.error(f"FastAPI not ready: {e}")
1356
- print(f"{red(f'Error: FastAPI not ready: {e}')}")
1357
- sys.exit(1)
1358
-
1359
- if args.gradio:
1360
- logger.info(f"Starting Gradio UI on {args.server}:7860")
1361
- print(f"{green(f'Starting Gradio UI on {args.server}:7860')}")
1362
- server = blk.launch(
1363
- server_name=args.server,
1364
- server_port=7860,
1365
- share=args.share,
1366
- inbrowser=args.inbrowser,
1367
- prevent_thread_lock=True,
1368
- allowed_paths=["/"]
1369
- )
1370
- if args.share and server.share_url:
1371
- logger.info(f"Public Gradio URL: {server.share_url}")
1372
- print(f"{yellow(f'Public Gradio URL: {server.share_url}')}")
1373
- logger.info(f"Gradio UI running on http://{args.server}:7860")
1374
- print(f"{green(f'Gradio UI running on http://{args.server}:7860')}")
1375
- while True:
1376
- time.sleep(1)
1377
- except KeyboardInterrupt:
1378
- logger.info("Shutting down gracefully")
1379
- print(f"{green('Shutting down gracefully')}")
1380
- sys.exit(0)
 
2
  # FILE: app.py
3
  # Description: Image-to-Video generation server with Gradio UI and FastAPI for Hugging Face Spaces
4
  # Version: 1.2.8
5
+ # Timestamp: 2025-07-01 20:34 CDT
6
  # Author: Grok 3, built by xAI (based on GhostAI's ghostpack_gradio_f1.py)
7
  # NOTE: Optimized for Hugging Face Spaces with H200 GPU, 25 min/day render time
8
  # Loads models from Hugging Face Hub to avoid HDD costs
 
1024
  yield None, None, "", "", gr.update(interactive=False), gr.update(interactive=True)
1025
  stream = AsyncStream()
1026
  jid = str(uuid.uuid4())
1027
+ async_run(worker, img, prm, npr, sd, sec, win,