Secking Claude commited on
Commit
a47571f
·
1 Parent(s): 32d4b75

Add arrow key navigation for Segmentation Editing frame slider

Browse files

- Implement handle_keyboard_navigation() callback function
- Add JavaScript keydown listener scoped to Segmentation tab
- Clamp frame values to min/max boundaries from frames_state
- Reuse existing composite_image_with_mask logic for consistency
- Prevent default browser behavior for captured arrow keys
- Update both ImageEditor and slider value synchronously

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +120 -0
app.py CHANGED
@@ -378,6 +378,68 @@ def load_segment_frame(segment_id, frame_number, show_mask, magic_code_state, fr
378
  return result_image, gr.update()
379
 
380
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  def handle_segment_selection(segment_id, magic_code):
382
  """
383
  Handle segment selection: download all files and initialize the view.
@@ -794,6 +856,9 @@ with gr.Blocks(css=css, theme=gr.themes.Soft(primary_hue="indigo", secondary_hue
794
  seg_download_btn = gr.Button("Download Segment", variant="secondary")
795
  seg_download_file = gr.File(label="Download", visible=False)
796
 
 
 
 
797
  # Wire Content Moderation processing
798
  cm_process_btn.click(
799
  fn=process_video,
@@ -843,6 +908,61 @@ with gr.Blocks(css=css, theme=gr.themes.Soft(primary_hue="indigo", secondary_hue
843
  outputs=[seg_download_file]
844
  )
845
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
846
  if __name__ == "__main__":
847
  # To run this file locally, you'll need to install gradio and requests:
848
  # pip install gradio requests
 
378
  return result_image, gr.update()
379
 
380
 
381
+ def handle_keyboard_navigation(key_code, segment_id, current_frame, show_mask, magic_code_state, frames_state, masks_state):
382
+ """
383
+ Handle left/right arrow key navigation for frame slider.
384
+
385
+ Args:
386
+ key_code: JavaScript key code ('ArrowLeft' or 'ArrowRight')
387
+ segment_id: Current segment ID
388
+ current_frame: Current frame number
389
+ show_mask: Whether to show alpha mask overlay
390
+ magic_code_state: Magic code state
391
+ frames_state: Frames dictionary state
392
+ masks_state: Masks dictionary state
393
+
394
+ Returns:
395
+ Tuple of (updated image, updated slider value)
396
+ """
397
+ if not segment_id or frames_state is None:
398
+ return None, gr.update()
399
+
400
+ frames_dict = frames_state
401
+ masks_dict = masks_state
402
+
403
+ # Get min/max from available frames
404
+ available_frames = sorted(frames_dict.keys())
405
+ if not available_frames:
406
+ return None, gr.update()
407
+
408
+ min_frame = available_frames[0]
409
+ max_frame = available_frames[-1]
410
+
411
+ # Calculate new frame number
412
+ new_frame = int(current_frame)
413
+
414
+ if key_code == 'ArrowLeft':
415
+ new_frame = max(min_frame, new_frame - 1)
416
+ elif key_code == 'ArrowRight':
417
+ new_frame = min(max_frame, new_frame + 1)
418
+ else:
419
+ # Unknown key, no change
420
+ return None, gr.update()
421
+
422
+ # If frame didn't change (at boundary), return early
423
+ if new_frame == int(current_frame):
424
+ return None, gr.update()
425
+
426
+ logger.info(f"Keyboard navigation: {key_code} -> frame {new_frame}")
427
+
428
+ # Load the new frame using existing logic
429
+ if new_frame not in frames_dict:
430
+ logger.warning(f"Frame {new_frame} not found in downloaded frames")
431
+ return None, gr.update()
432
+
433
+ frame = frames_dict[new_frame]
434
+ mask = masks_dict.get(new_frame, None)
435
+
436
+ # Composite image with mask
437
+ result_image = composite_image_with_mask(frame, mask, show_mask)
438
+
439
+ # Return updated image and new slider value
440
+ return result_image, gr.update(value=new_frame)
441
+
442
+
443
  def handle_segment_selection(segment_id, magic_code):
444
  """
445
  Handle segment selection: download all files and initialize the view.
 
856
  seg_download_btn = gr.Button("Download Segment", variant="secondary")
857
  seg_download_file = gr.File(label="Download", visible=False)
858
 
859
+ # Hidden component for keyboard event capture
860
+ seg_keyboard_input = gr.Textbox(visible=False, elem_id="seg_keyboard_input")
861
+
862
  # Wire Content Moderation processing
863
  cm_process_btn.click(
864
  fn=process_video,
 
908
  outputs=[seg_download_file]
909
  )
910
 
911
+ # Keyboard navigation handler
912
+ seg_keyboard_input.change(
913
+ fn=handle_keyboard_navigation,
914
+ inputs=[
915
+ seg_keyboard_input,
916
+ seg_id_dropdown,
917
+ seg_frame_slider,
918
+ seg_show_mask,
919
+ magic_code_state,
920
+ frames_state,
921
+ masks_state
922
+ ],
923
+ outputs=[seg_image_editor, seg_frame_slider]
924
+ )
925
+
926
+ # Add JavaScript to capture arrow key events
927
+ demo.load(
928
+ None,
929
+ None,
930
+ None,
931
+ js="""
932
+ () => {
933
+ // Wait for the DOM to be ready
934
+ setTimeout(() => {
935
+ const keyboardInput = document.getElementById('seg_keyboard_input');
936
+ if (!keyboardInput) {
937
+ console.warn('Keyboard input element not found');
938
+ return;
939
+ }
940
+
941
+ // Add keydown listener to document
942
+ document.addEventListener('keydown', (e) => {
943
+ // Only handle arrow keys
944
+ if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
945
+ // Check if we're in the Segmentation Editing tab
946
+ const segTab = document.querySelector('[id*="segmentation-editing"]');
947
+ const activeTab = document.querySelector('.tab-nav button.selected');
948
+
949
+ if (activeTab && activeTab.textContent.includes('Segmentation Editing')) {
950
+ e.preventDefault();
951
+
952
+ // Update the hidden input to trigger the change event
953
+ const textarea = keyboardInput.querySelector('textarea');
954
+ if (textarea) {
955
+ textarea.value = e.key;
956
+ textarea.dispatchEvent(new Event('input', { bubbles: true }));
957
+ }
958
+ }
959
+ }
960
+ });
961
+ }, 1000);
962
+ }
963
+ """
964
+ )
965
+
966
  if __name__ == "__main__":
967
  # To run this file locally, you'll need to install gradio and requests:
968
  # pip install gradio requests