Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| from Functions.video_player_functions import youtube_link_to_id, get_video_link_by_pointer, get_youtube_player_html, change_video_completion_status | |
| from Functions.caption_editor_functions import request_captions_by_video_id, save_captions_to_db | |
| from Resources.css import css | |
| from Resources.js import yt_init_js | |
| from Resources.localization import get_string | |
| next_video_pointer = 0 | |
| user = "anonymous_user" | |
| n_videos = 21 | |
| placeholder_link = "https://www.youtube.com/watch?v=wTQjwG2-ePA" | |
| def get_username(profile: gr.OAuthProfile): | |
| global user | |
| if profile is None: | |
| user = "anonymous_user" | |
| else: | |
| user = profile.username | |
| return profile | |
| def on_row_select(df, evt: gr.SelectData): | |
| """Handle row selection in DataFrame""" | |
| if evt.index is not None and len(evt.index) > 0: | |
| row_idx = evt.index[0] | |
| if row_idx < len(df): | |
| row = df.iloc[row_idx] | |
| return ( | |
| gr.update(visible=True), # editing_panel | |
| gr.update(value=float(row['Start'])), # start_time | |
| gr.update(value=str(row['Text'])), # text_input | |
| gr.update(value=float(row['End'])), # end_time | |
| gr.update(value=row_idx), # selected_row_idx | |
| gr.update(value=get_string("update_entry_button")) # save_entry_button | |
| ) | |
| return gr.update(visible=False), "", "", "", -1, get_string("save_entry_button") | |
| def show_add_entry_form(): | |
| """Show editing panel for adding new entry""" | |
| return ( | |
| gr.update(visible=True), # editing_panel | |
| gr.update(value=0.0), # start_time | |
| gr.update(value=""), # text_input | |
| gr.update(value=0.0), # end_time | |
| gr.update(value=-1), # selected_row_idx (-1 means new entry) | |
| gr.update(value=get_string("add_entry_button_form")) # save_entry_button | |
| ) | |
| def save_entry(df, start_time, text, end_time, selected_row_idx, video_id): | |
| """Save or update a caption entry""" | |
| if user == "anonymous_user": | |
| return df, gr.update(visible=True), gr.Warning(get_string("please_sign_in")) | |
| if next_video_pointer == -1: | |
| return df, gr.update(visible=True), gr.Warning(get_string("all_videos_transcribed")) | |
| try: | |
| start_time = float(start_time) | |
| end_time = float(end_time) | |
| if start_time >= end_time: | |
| return df, gr.update(visible=True), gr.Warning(get_string("start_less_than_end")) | |
| if not text.strip(): | |
| return df, gr.update(visible=True), gr.Warning(get_string("text_cannot_be_empty")) | |
| df_copy = df.copy() | |
| if selected_row_idx == -1: # Adding new entry | |
| new_row = pd.DataFrame({ | |
| 'Start': [start_time], | |
| 'Text': [text.strip()], | |
| 'End': [end_time] | |
| }) | |
| df_copy = pd.concat([df_copy, new_row], ignore_index=True) | |
| # Sort by start time | |
| df_copy = df_copy.sort_values('Start').reset_index(drop=True) | |
| else: # Updating existing entry | |
| if 0 <= selected_row_idx < len(df_copy): | |
| df_copy.iloc[selected_row_idx] = [start_time, text.strip(), end_time] | |
| # Sort by start time | |
| df_copy = df_copy.sort_values('Start').reset_index(drop=True) | |
| # Update in database | |
| save_result = save_captions_to_db(df_copy, video_id, user) | |
| return ( | |
| df_copy, | |
| gr.update(visible=False), # Hide panel on success | |
| gr.Info(f"{save_result}") | |
| ) | |
| except ValueError as e: | |
| return df, gr.update(visible=True), gr.Warning(f"{get_string('invalid_time_format')} {str(e)}") | |
| except Exception as e: | |
| return df, gr.update(visible=True), gr.Error(f"{get_string('error')} {str(e)}") | |
| def cancel_edit(): | |
| """Cancel editing and hide the form""" | |
| return gr.update(visible=False) | |
| def change_completion_status(completion_status): | |
| global next_video_pointer | |
| change_video_completion_status(completion_status, (next_video_pointer + n_videos - 1) % n_videos) | |
| def get_next_components(): | |
| global next_video_pointer | |
| if next_video_pointer != -1: | |
| next_video_link = get_video_link_by_pointer(next_video_pointer) | |
| next_video_pointer = (next_video_pointer + 1) % n_videos | |
| for i in range(n_videos + 1): | |
| if next_video_link is not None: | |
| break | |
| next_video_link = get_video_link_by_pointer(next_video_pointer) | |
| next_video_pointer = (next_video_pointer + 1) % n_videos | |
| if next_video_link is None: | |
| next_video_link = placeholder_link | |
| next_video_pointer = -1 | |
| try: | |
| next_video_id = youtube_link_to_id(next_video_link) | |
| next_captions = request_captions_by_video_id(next_video_id) | |
| return next_captions, next_video_id | |
| except (ValueError, Exception) as e: | |
| empty_captions = pd.DataFrame(columns=["Start", "Text", "End"]) | |
| return empty_captions, "error" | |
| (start_captions, start_video_id) = get_next_components() | |
| with gr.Blocks(css=css, head=yt_init_js, fill_width=True) as main_page: | |
| user_state = gr.State("anonymous_user") | |
| pointer_state = gr.State(0) | |
| current_video_id = gr.Textbox(value="", visible=False, interactive=False) | |
| selected_row_idx = gr.Number(value=-1, visible=False) | |
| main_page.load(get_username, outputs=user_state) | |
| with gr.Row(variant="panel"): | |
| with gr.Column(scale=4): | |
| gr.Markdown(f"## {get_string('app_title')}") | |
| with gr.Column(scale=4): | |
| def check_login(logged_in_user): | |
| if logged_in_user == "anonymous_user": | |
| gr.Markdown(get_string("please_log_in"), rtl=True) | |
| else: | |
| gr.Markdown(f"{get_string('logged_in_as')} {logged_in_user.username}", rtl=True) | |
| with gr.Column(scale=1, min_width=50): | |
| gr.LoginButton(value=get_string("log_in_button"), logout_value=get_string("log_in_button")) | |
| with gr.Row(): | |
| with gr.Column(scale=2, min_width=600): | |
| video_embed = gr.HTML(value=get_youtube_player_html()) | |
| next_video_button = gr.Button(get_string("next_button"), key="next_video") | |
| with gr.Column(scale=1, min_width=200): | |
| caption_editor = gr.DataFrame( | |
| interactive=False, | |
| elem_id="tbl", | |
| datatype=["number", "str", "number"], | |
| col_count=(3, "fixed"), | |
| column_widths=["20%", "60%", "20%"], | |
| headers=[get_string("header_start"), get_string("header_text"), get_string("header_end")], | |
| wrap=True | |
| ) | |
| add_entry_button = gr.Button(get_string("add_entry_button"), variant="secondary") | |
| editing_complete_checkbox = gr.Checkbox(label=get_string("editing_complete_checkbox")) | |
| with gr.Row(): | |
| with gr.Group(visible=False) as editing_panel: | |
| gr.Markdown(f"### {get_string('edit_caption_title')}") | |
| with gr.Row(equal_height=False): | |
| with gr.Column(): | |
| start_time_input = gr.Textbox(label=get_string("start_time_label"), value="0.000", interactive=False) | |
| insert_start_time_button = gr.Button(get_string("insert_current_time")) | |
| with gr.Column(): | |
| text_input = gr.Textbox(label=get_string("caption_text_label"), placeholder=get_string("caption_text_placeholder")) | |
| with gr.Column(): | |
| end_time_input = gr.Textbox(label=get_string("end_time_label"), value="0.000", interactive=False) | |
| insert_end_time_button = gr.Button(get_string("insert_current_time")) | |
| with gr.Row(equal_height=False): | |
| save_entry_button = gr.Button(get_string("save_entry_button"), variant="primary") | |
| cancel_button = gr.Button(get_string("cancel_button"), variant="secondary") | |
| save_result = gr.Markdown() | |
| main_page.load( | |
| fn=get_next_components, | |
| outputs=[caption_editor, current_video_id], | |
| js="""() => { | |
| const checkPlayer = setInterval(() => { | |
| if (window.ytPlayer && window.ytPlayer.cueVideoById) { | |
| clearInterval(checkPlayer); | |
| // cue video dynamically once captions arrive | |
| } | |
| }, 100); | |
| }""" | |
| ) | |
| next_video_button.click( | |
| fn=get_next_components, | |
| outputs=[caption_editor, current_video_id] | |
| ) | |
| next_video_button.click( | |
| fn=lambda: False, | |
| outputs=editing_complete_checkbox | |
| ) | |
| current_video_id.change( | |
| fn=None, | |
| inputs=current_video_id, | |
| outputs=None, | |
| js="""(videoId) => { | |
| if (window.ytPlayer && window.ytPlayer.cueVideoById) { | |
| console.log('[Video Load] cueVideoById', videoId); | |
| window.ytPlayer.cueVideoById(videoId); | |
| } | |
| }""" | |
| ) | |
| caption_editor.select( | |
| fn=on_row_select, | |
| inputs=[caption_editor], | |
| outputs=[editing_panel, start_time_input, text_input, end_time_input, selected_row_idx, save_entry_button] | |
| ) | |
| editing_complete_checkbox.input( | |
| fn=change_completion_status, | |
| inputs=editing_complete_checkbox | |
| ) | |
| add_entry_button.click( | |
| fn=show_add_entry_form, | |
| outputs=[editing_panel, start_time_input, text_input, end_time_input, selected_row_idx, save_entry_button] | |
| ) | |
| save_entry_button.click( | |
| fn=save_entry, | |
| inputs=[caption_editor, start_time_input, text_input, end_time_input, | |
| selected_row_idx, current_video_id], | |
| outputs=[caption_editor, editing_panel, save_result] | |
| ) | |
| insert_start_time_button.click( | |
| fn=None, inputs=None, outputs=start_time_input, | |
| js="() => window.ytPlayer ? +window.ytPlayer.getCurrentTime().toFixed(3) : 0" | |
| ) | |
| insert_end_time_button.click( | |
| fn=None, inputs=None, outputs=end_time_input, | |
| js="() => window.ytPlayer ? +window.ytPlayer.getCurrentTime().toFixed(3) : 0" | |
| ) | |
| cancel_button.click(fn=cancel_edit, outputs=[editing_panel]) | |
| main_page.launch(share=True) |