maryna7679's picture
Merge
0c936fa
raw
history blame
8.96 kB
import gradio as gr
import pandas as pd
from .Functions.video_player_functions import youtube_link_to_id, get_video_embed_by_id, get_video_link_by_pointer, get_youtube_player_html
from .Functions.caption_editor_functions import get_captions_by_video_id, save_dataframe
from .Resources.css import css
from .Resources.js import yt_init_js
next_video_pointer = 0
user = ""
def get_username(profile: gr.OAuthProfile):
global user
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="Update Entry") # save_entry_button
)
return gr.update(visible=False), "", "", "", -1, "Save Entry"
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="Add Entry") # save_entry_button
)
def save_entry(df, start_time, text, end_time, selected_row_idx, video_id, df_full):
"""Save or update a caption entry"""
try:
start_time = float(start_time)
end_time = float(end_time)
if start_time >= end_time:
return df, gr.update(visible=True), gr.Warning("Start time must be less than end time")
if not text.strip():
return df, gr.update(visible=True), gr.Warning("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_dataframe(df_copy, df_full, 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"Invalid time format: {str(e)}")
except Exception as e:
return df, gr.update(visible=True), gr.Error(f"Error: {str(e)}")
def cancel_edit():
"""Cancel editing and hide the form"""
return gr.update(visible=False)
def get_next_components():
global next_video_pointer
next_video_link = get_video_link_by_pointer(next_video_pointer)
next_video_pointer += 1
if next_video_link is None:
next_video_link = get_video_link_by_pointer(0)
next_video_pointer = 1
try:
next_video_id = youtube_link_to_id(next_video_link)
next_captions, next_captions_full = get_captions_by_video_id(next_video_id)
return next_captions, next_captions_full, next_video_id
except (ValueError, Exception) as e:
empty_captions = pd.DataFrame(columns=["Start", "Text", "End"])
return empty_captions, "error"
(start_captions, start_captions_full, start_video_id) = get_next_components()
with gr.Blocks(css=css, head=yt_init_js) as main_page:
gr.Markdown("## Caption Editor")
gr.LoginButton()
current_video_id = gr.Textbox(value=start_video_id, visible=False, interactive=False)
current_captions_full = gr.DataFrame(value=start_captions_full, visible=False, interactive=False)
selected_row_idx = gr.Number(value=-1, visible=False)
with gr.Row():
with gr.Column(scale=2, min_width=600):
# Video player and "next video button
video_embed = gr.HTML(value=get_youtube_player_html())
next_video_button = gr.Button("Next")
with gr.Column(scale=1, min_width=200):
# Read-only DataFrame with add button
caption_editor = gr.DataFrame(interactive=False,
elem_id="tbl",
value=start_captions,
datatype=["number", "str", "number"],
col_count=(3, "fixed"),
column_widths=["20%", "60%", "20%"],
headers=["Start", "Text", "End"],
wrap=True)
add_entry_button = gr.Button("Add Entry", variant="secondary")
with gr.Row():
# Editing panel (initially hidden) - spans full width
with gr.Group(visible=False) as editing_panel:
gr.Markdown("### Edit Caption Entry")
with gr.Row(equal_height=False):
with gr.Column():
start_time_input = gr.Textbox(label="Start Time (seconds)", value="0.000", interactive=False)
insert_start_time_button = gr.Button("Insert Current Time")
with gr.Column():
text_input = gr.Textbox(label="Caption Text", placeholder="Enter caption text...")
with gr.Column():
end_time_input = gr.Textbox(label="End Time (seconds)", value="0.000", interactive=False)
insert_end_time_button = gr.Button("Insert Current Time")
with gr.Row(equal_height=False):
save_entry_button = gr.Button("Save Entry", variant="primary")
cancel_button = gr.Button("Cancel", variant="secondary")
save_result = gr.Markdown()
# Event handlers
next_video_button.click(
fn=get_next_components,
outputs=[caption_editor, current_captions_full, current_video_id]
)
# Load video when current_video_id changes
current_video_id.change(
fn=None,
inputs=current_video_id,
outputs=None,
js="""(videoId) => {
if (window.ytPlayer && window.ytPlayer.cueVideoById) {
console.log('[Video Load] Calling cueVideoById with:', videoId);
window.ytPlayer.cueVideoById(videoId);
} else {
console.error('[Video Load] Player not ready yet');
}
}"""
)
# Handle row selection in DataFrame
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]
)
# Handle add entry button
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]
)
# Handle save entry
save_entry_button.click(
fn=save_entry,
inputs=[caption_editor, start_time_input, text_input, end_time_input, selected_row_idx, current_video_id, current_captions_full],
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")
# Handle cancel
cancel_button.click(
fn=cancel_edit,
outputs=[editing_panel]
)
# Load initial video on page load
main_page.load(
fn=None,
inputs=current_video_id,
outputs=None,
js="""(videoId) => {
const checkPlayer = setInterval(() => {
if (window.ytPlayer && window.ytPlayer.cueVideoById) {
clearInterval(checkPlayer);
window.ytPlayer.cueVideoById(videoId);
}
}, 100);
}"""
)
main_page.load(get_username) # Disabled when auth is disabled