lex-sobieski commited on
Commit
d4cc140
·
1 Parent(s): 47503fe

Add playback controls panel for video labeling

Browse files

- Add seek buttons (-1s, -100ms, +100ms, +1s) for precision scrubbing
- Add play/pause toggle with dynamic label update
- Add speed controls (0.25x, 0.5x, 0.75x, 1x, 2x) with active state highlighting
- Move next video button and incomplete checkbox to same row

Files changed (2) hide show
  1. Resources/localization.py +20 -0
  2. app.py +87 -3
Resources/localization.py CHANGED
@@ -34,6 +34,16 @@ STRINGS = {
34
  "add_entry_button_form": "Add Entry",
35
  "cancel_button": "Cancel",
36
 
 
 
 
 
 
 
 
 
 
 
37
  # Messages
38
  "please_sign_in": "Please sign in to save changes",
39
  "start_less_than_end": "Start time must be less than end time",
@@ -76,6 +86,16 @@ STRINGS = {
76
  "add_entry_button_form": "Додати запис",
77
  "cancel_button": "Скасувати",
78
 
 
 
 
 
 
 
 
 
 
 
79
  # Messages
80
  "please_sign_in": "Будь ласка, увійдіть, щоб зберегти зміни",
81
  "start_less_than_end": "Час початку повинен бути менше часу кінця",
 
34
  "add_entry_button_form": "Add Entry",
35
  "cancel_button": "Cancel",
36
 
37
+ # Playback controls
38
+ "playback_controls_title": "Playback Controls",
39
+ "seek_back_1s": "-1s",
40
+ "seek_back_100ms": "-100ms",
41
+ "play_button": "Play",
42
+ "pause_button": "Pause",
43
+ "seek_forward_100ms": "+100ms",
44
+ "seek_forward_1s": "+1s",
45
+ "speed_label": "Speed:",
46
+
47
  # Messages
48
  "please_sign_in": "Please sign in to save changes",
49
  "start_less_than_end": "Start time must be less than end time",
 
86
  "add_entry_button_form": "Додати запис",
87
  "cancel_button": "Скасувати",
88
 
89
+ # Playback controls
90
+ "playback_controls_title": "Керування відтворенням",
91
+ "seek_back_1s": "-1с",
92
+ "seek_back_100ms": "-100мс",
93
+ "play_button": "Грати",
94
+ "pause_button": "Пауза",
95
+ "seek_forward_100ms": "+100мс",
96
+ "seek_forward_1s": "+1с",
97
+ "speed_label": "Швидкість:",
98
+
99
  # Messages
100
  "please_sign_in": "Будь ласка, увійдіть, щоб зберегти зміни",
101
  "start_less_than_end": "Час початку повинен бути менше часу кінця",
app.py CHANGED
@@ -103,6 +103,15 @@ def cancel_edit():
103
  return gr.update(visible=False)
104
 
105
 
 
 
 
 
 
 
 
 
 
106
  def change_completion_status(completion_status):
107
  if user == "anonymous_user":
108
  return gr.Warning(get_string("please_sign_in"))
@@ -166,9 +175,27 @@ with gr.Blocks(css=css, head=yt_init_js, fill_width=True) as main_page:
166
  with gr.Row():
167
  with gr.Column(scale=2, min_width=600):
168
  video_embed = gr.HTML(value=get_youtube_player_html())
169
- next_video_button = gr.Button(get_string("next_button"), key="next_video")
170
- show_incomplete_only_checkbox = gr.Checkbox(label=get_string("show_incomplete_only_checkbox"),
171
- value=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
  with gr.Column(scale=1, min_width=200):
174
  caption_editor = gr.DataFrame(
@@ -277,4 +304,61 @@ with gr.Blocks(css=css, head=yt_init_js, fill_width=True) as main_page:
277
 
278
  cancel_button.click(fn=cancel_edit, outputs=[editing_panel])
279
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  main_page.launch(share=True)
 
103
  return gr.update(visible=False)
104
 
105
 
106
+ def update_speed_buttons(selected_speed):
107
+ """Return updates for all speed buttons, highlighting the selected one"""
108
+ speeds = [0.25, 0.5, 0.75, 1, 2]
109
+ return [
110
+ gr.update(variant="primary" if speed == selected_speed else "secondary")
111
+ for speed in speeds
112
+ ]
113
+
114
+
115
  def change_completion_status(completion_status):
116
  if user == "anonymous_user":
117
  return gr.Warning(get_string("please_sign_in"))
 
175
  with gr.Row():
176
  with gr.Column(scale=2, min_width=600):
177
  video_embed = gr.HTML(value=get_youtube_player_html())
178
+
179
+ with gr.Group():
180
+ gr.Markdown(f"### {get_string('playback_controls_title')}")
181
+ with gr.Row():
182
+ seek_back_1s_btn = gr.Button(get_string("seek_back_1s"), size="sm", min_width=60)
183
+ seek_back_100ms_btn = gr.Button(get_string("seek_back_100ms"), size="sm", min_width=80)
184
+ play_pause_btn = gr.Button(get_string("play_button"), size="sm", min_width=80, variant="primary")
185
+ seek_forward_100ms_btn = gr.Button(get_string("seek_forward_100ms"), size="sm", min_width=80)
186
+ seek_forward_1s_btn = gr.Button(get_string("seek_forward_1s"), size="sm", min_width=60)
187
+ gr.Markdown(f"**{get_string('speed_label')}**")
188
+ with gr.Row():
189
+ speed_025_btn = gr.Button("0.25x", size="sm", min_width=60)
190
+ speed_05_btn = gr.Button("0.5x", size="sm", min_width=60)
191
+ speed_075_btn = gr.Button("0.75x", size="sm", min_width=60)
192
+ speed_1_btn = gr.Button("1x", size="sm", min_width=60, variant="primary")
193
+ speed_2_btn = gr.Button("2x", size="sm", min_width=60)
194
+
195
+ with gr.Row():
196
+ next_video_button = gr.Button(get_string("next_button"), key="next_video")
197
+ show_incomplete_only_checkbox = gr.Checkbox(label=get_string("show_incomplete_only_checkbox"),
198
+ value=True)
199
 
200
  with gr.Column(scale=1, min_width=200):
201
  caption_editor = gr.DataFrame(
 
304
 
305
  cancel_button.click(fn=cancel_edit, outputs=[editing_panel])
306
 
307
+ # Playback control handlers
308
+ seek_back_1s_btn.click(
309
+ fn=None, inputs=None, outputs=None,
310
+ js="() => { if (window.ytPlayer) { window.ytPlayer.seekTo(Math.max(0, window.ytPlayer.getCurrentTime() - 1), true); } }"
311
+ )
312
+ seek_back_100ms_btn.click(
313
+ fn=None, inputs=None, outputs=None,
314
+ js="() => { if (window.ytPlayer) { window.ytPlayer.seekTo(Math.max(0, window.ytPlayer.getCurrentTime() - 0.1), true); } }"
315
+ )
316
+ seek_forward_100ms_btn.click(
317
+ fn=None, inputs=None, outputs=None,
318
+ js="() => { if (window.ytPlayer) { window.ytPlayer.seekTo(window.ytPlayer.getCurrentTime() + 0.1, true); } }"
319
+ )
320
+ seek_forward_1s_btn.click(
321
+ fn=None, inputs=None, outputs=None,
322
+ js="() => { if (window.ytPlayer) { window.ytPlayer.seekTo(window.ytPlayer.getCurrentTime() + 1, true); } }"
323
+ )
324
+ play_pause_btn.click(
325
+ fn=None, inputs=None, outputs=play_pause_btn,
326
+ js=f"""() => {{
327
+ if (window.ytPlayer) {{
328
+ const state = window.ytPlayer.getPlayerState();
329
+ if (state === 1) {{
330
+ window.ytPlayer.pauseVideo();
331
+ return "{get_string('play_button')}";
332
+ }} else {{
333
+ window.ytPlayer.playVideo();
334
+ return "{get_string('pause_button')}";
335
+ }}
336
+ }}
337
+ return "{get_string('play_button')}";
338
+ }}"""
339
+ )
340
+
341
+ # Speed control handlers
342
+ speed_outputs = [speed_025_btn, speed_05_btn, speed_075_btn, speed_1_btn, speed_2_btn]
343
+ speed_025_btn.click(
344
+ fn=lambda: update_speed_buttons(0.25), outputs=speed_outputs,
345
+ js="() => { if (window.ytPlayer) { window.ytPlayer.setPlaybackRate(0.25); } }"
346
+ )
347
+ speed_05_btn.click(
348
+ fn=lambda: update_speed_buttons(0.5), outputs=speed_outputs,
349
+ js="() => { if (window.ytPlayer) { window.ytPlayer.setPlaybackRate(0.5); } }"
350
+ )
351
+ speed_075_btn.click(
352
+ fn=lambda: update_speed_buttons(0.75), outputs=speed_outputs,
353
+ js="() => { if (window.ytPlayer) { window.ytPlayer.setPlaybackRate(0.75); } }"
354
+ )
355
+ speed_1_btn.click(
356
+ fn=lambda: update_speed_buttons(1), outputs=speed_outputs,
357
+ js="() => { if (window.ytPlayer) { window.ytPlayer.setPlaybackRate(1); } }"
358
+ )
359
+ speed_2_btn.click(
360
+ fn=lambda: update_speed_buttons(2), outputs=speed_outputs,
361
+ js="() => { if (window.ytPlayer) { window.ytPlayer.setPlaybackRate(2); } }"
362
+ )
363
+
364
  main_page.launch(share=True)