dagloop5 commited on
Commit
d2ce847
·
verified ·
1 Parent(s): 6c70c31

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +58 -1
app.py CHANGED
@@ -867,6 +867,36 @@ def _fit_keyframe(image_path, current_canvas):
867
  return gr.update(value=image_path), gr.update(value=label)
868
 
869
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
870
  load_models()
871
 
872
  INTRO = """# PlagueKind · MiniMax-H3
@@ -885,6 +915,16 @@ CSS = """
885
  .main.fillable {max-width: 1250px !important}
886
  .dark .gradio-container { color: var(--body-text-color); }
887
  .status p {font-size: 0.8rem; opacity: 0.65; text-align: center;}
 
 
 
 
 
 
 
 
 
 
888
  """
889
 
890
  with gr.Blocks(title="PlagueKind · MiniMax-H3") as demo:
@@ -979,7 +1019,12 @@ with gr.Blocks(title="PlagueKind · MiniMax-H3") as demo:
979
  )
980
 
981
  with gr.Column():
982
- video = gr.Video(label="Video + soundtrack")
 
 
 
 
 
983
  report = gr.Markdown()
984
  with gr.Accordion("Distilled / Turbo LoRAs", open=False):
985
  lora_1_strength = gr.Slider(
@@ -1060,6 +1105,18 @@ with gr.Blocks(title="PlagueKind · MiniMax-H3") as demo:
1060
  first_frame.upload(_fit_keyframe, [first_frame, canvas], [first_frame, canvas])
1061
  last_frame.upload(_fit_keyframe, [last_frame, canvas], [last_frame, canvas])
1062
 
 
 
 
 
 
 
 
 
 
 
 
 
1063
  controls = [
1064
  prompt,
1065
  canvas,
 
867
  return gr.update(value=image_path), gr.update(value=label)
868
 
869
 
870
+ # Client-side only (`fn=None`, no server round-trip): reads the real `<video>` element's current playback
871
+ # position, not a property of the file — so "grab this frame" means whatever's on screen when the button is
872
+ # pressed, paused or scrubbed to, not automatically the clip's last frame.
873
+ _FRAME_GRAB_JS = """
874
+ function() {
875
+ const video = document.querySelector('#h3-generated-video video');
876
+ return video ? video.currentTime : 0;
877
+ }
878
+ """
879
+
880
+
881
+ def _extract_frame(video_path, timestamp):
882
+ """The frame at `timestamp` seconds into `video_path`, as a numpy RGB array — Gradio converts it to a PIL
883
+ image for whichever `gr.Image` this is wired to. Runs on CPU; no GPU time, no interaction with `_generate`."""
884
+ if not video_path:
885
+ return None
886
+ import cv2
887
+
888
+ cap = cv2.VideoCapture(video_path)
889
+ if not cap.isOpened():
890
+ return None
891
+ fps = cap.get(cv2.CAP_PROP_FPS) or FPS
892
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
893
+ target_frame = min(int(float(timestamp) * fps), max(0, total_frames - 1))
894
+ cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame)
895
+ ok, frame = cap.read()
896
+ cap.release()
897
+ return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) if ok else None
898
+
899
+
900
  load_models()
901
 
902
  INTRO = """# PlagueKind · MiniMax-H3
 
915
  .main.fillable {max-width: 1250px !important}
916
  .dark .gradio-container { color: var(--body-text-color); }
917
  .status p {font-size: 0.8rem; opacity: 0.65; text-align: center;}
918
+ .h3-hidden-timestamp {
919
+ opacity: 0;
920
+ height: 0px;
921
+ width: 0px;
922
+ margin: 0px;
923
+ padding: 0px;
924
+ overflow: hidden;
925
+ position: absolute;
926
+ pointer-events: none;
927
+ }
928
  """
929
 
930
  with gr.Blocks(title="PlagueKind · MiniMax-H3") as demo:
 
1019
  )
1020
 
1021
  with gr.Column():
1022
+ video = gr.Video(label="Video + soundtrack", elem_id="h3-generated-video")
1023
+ with gr.Row():
1024
+ grab_first_btn = gr.Button("📸 Use current frame as First frame", size="sm", variant="secondary")
1025
+ grab_last_btn = gr.Button("📸 Use current frame as Last frame", size="sm", variant="secondary")
1026
+ first_frame_timestamp = gr.Number(value=0, visible=True, elem_classes="h3-hidden-timestamp")
1027
+ last_frame_timestamp = gr.Number(value=0, visible=True, elem_classes="h3-hidden-timestamp")
1028
  report = gr.Markdown()
1029
  with gr.Accordion("Distilled / Turbo LoRAs", open=False):
1030
  lora_1_strength = gr.Slider(
 
1105
  first_frame.upload(_fit_keyframe, [first_frame, canvas], [first_frame, canvas])
1106
  last_frame.upload(_fit_keyframe, [last_frame, canvas], [last_frame, canvas])
1107
 
1108
+ # Grabbing the currently-displayed frame: the button's own click runs only the JS above (`fn=None`, no
1109
+ # server round-trip) to read the real `<video>` element's playback position into a hidden number box; that
1110
+ # box's `.change()` is what actually decodes and writes the frame, server-side.
1111
+ grab_first_btn.click(fn=None, inputs=None, outputs=[first_frame_timestamp], js=_FRAME_GRAB_JS)
1112
+ first_frame_timestamp.change(
1113
+ _extract_frame, [video, first_frame_timestamp], first_frame, show_progress="hidden"
1114
+ )
1115
+ grab_last_btn.click(fn=None, inputs=None, outputs=[last_frame_timestamp], js=_FRAME_GRAB_JS)
1116
+ last_frame_timestamp.change(
1117
+ _extract_frame, [video, last_frame_timestamp], last_frame, show_progress="hidden"
1118
+ )
1119
+
1120
  controls = [
1121
  prompt,
1122
  canvas,