Maryna Kosse commited on
Commit
8b0a345
·
unverified ·
2 Parent(s): 3c6f1cc 725c75e

Merge pull request #1 from lexsobieski/feature/editing

Browse files
Pages/Functions/video_player_functions.py CHANGED
@@ -1,19 +1,26 @@
1
- import re
2
  from .db_connection import videos_ref
3
 
4
 
5
- def youtube_link_to_id(link):
6
- video_id = re.findall("=(.*?)&", link)
7
- if len(video_id) == 0:
8
- video_id = re.findall("=(.*)", link)
9
- return video_id[0]
 
 
10
 
 
 
 
 
 
 
 
11
 
12
  def get_video_embed_by_id(video_id):
13
- return f"""
14
- <div class="container">
15
- <iframe src="https://www.youtube.com/embed/{video_id}" frameborder="0" allowfullscreen class="video"></iframe>
16
- </div>"""
17
 
18
 
19
  def get_video_link_by_pointer(pointer):
 
1
+ import urllib
2
  from .db_connection import videos_ref
3
 
4
 
5
+ def get_youtube_player_html():
6
+ """Returns the static HTML container for YouTube player (API loaded in page head)"""
7
+ return """
8
+ <div style="margin:0 auto; width: fit-content;">
9
+ <div id="yt-container" style="width: 640px; height: 360px;"></div>
10
+ </div>
11
+ """
12
 
13
+ def youtube_link_to_id(link):
14
+ try:
15
+ from urllib.parse import urlparse, parse_qs
16
+ parsed = urlparse(link)
17
+ return parse_qs(parsed.query)['v'][0]
18
+ except (KeyError, IndexError):
19
+ raise ValueError(f"Invalid YouTube URL: {link}")
20
 
21
  def get_video_embed_by_id(video_id):
22
+ """Returns just the video ID - actual loading happens via JavaScript in main_page"""
23
+ return video_id
 
 
24
 
25
 
26
  def get_video_link_by_pointer(pointer):
Pages/Resources/css.py CHANGED
@@ -1,14 +1,4 @@
1
  css = """
2
- .container {
3
- position: relative;
4
- width: 100%;
5
- height: 0;
6
- padding-bottom: 56.25%;
7
  }
8
- .video {
9
- position: absolute;
10
- top: 0;
11
- left: 0;
12
- width: 100%;
13
- height: 100%;
14
- }"""
 
1
  css = """
2
+ #yt-container {
 
 
 
 
3
  }
4
+ """
 
 
 
 
 
 
Pages/Resources/js.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ yt_init_js = """
2
+ <script src="https://www.youtube.com/iframe_api"></script>
3
+ <script>
4
+ window.onYouTubeIframeAPIReady = function() {
5
+ window.ytPlayer = new YT.Player('yt-container', {
6
+ height: '360',
7
+ width: '640',
8
+ playerVars: {
9
+ origin: window.location.origin,
10
+ playsinline: 1
11
+ },
12
+ events: {
13
+ 'onReady': function(event) {
14
+ window.ytPlayerReady = true;
15
+ }
16
+ }
17
+ });
18
+ };
19
+ </script>
20
+ """
Pages/main_page.py CHANGED
@@ -1,7 +1,9 @@
1
  import gradio as gr
2
- from .Functions.video_player_functions import youtube_link_to_id, get_video_embed_by_id, get_video_link_by_pointer
 
3
  from .Functions.caption_editor_functions import get_captions_by_video_id, save_dataframe
4
  from .Resources.css import css
 
5
 
6
  next_video_pointer = 0
7
  user = ""
@@ -16,6 +18,84 @@ def save(df, video_id):
16
  return save_dataframe(df, video_id, user)
17
 
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  def get_next_components():
20
  global next_video_pointer
21
  next_video_link = get_video_link_by_pointer(next_video_pointer)
@@ -24,35 +104,128 @@ def get_next_components():
24
  next_video_link = get_video_link_by_pointer(0)
25
  next_video_pointer = 1
26
 
27
- next_video_id = youtube_link_to_id(next_video_link)
28
-
29
- next_video = get_video_embed_by_id(next_video_id)
30
- next_captions = get_captions_by_video_id(next_video_id)
 
 
 
 
31
 
32
- return next_video, next_captions, next_video_id
33
 
 
34
 
35
- (start_video, start_captions, start_video_id) = get_next_components()
36
-
37
- with gr.Blocks(css=css) as main_page:
38
  gr.Markdown("## Caption Editor")
39
  current_video_id = gr.Textbox(value=start_video_id, visible=False, interactive=False)
 
 
40
  with gr.Row():
41
- with gr.Column():
42
- caption_editor = gr.DataFrame(interactive=True,
 
 
 
 
 
 
43
  value=start_captions,
44
  datatype=["number", "str", "number"],
45
- row_count=(start_captions.shape[0], "fixed"),
46
- col_count=(3, "fixed"), column_widths=["20%", "60%", "20%"])
47
- save_button = gr.Button(value="Save")
48
- save_result = gr.Markdown()
49
- with gr.Column():
50
- video_embed = gr.HTML(value=start_video)
51
- next_video_button = gr.Button("Next")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
- next_video_button.click(fn=get_next_components,
54
- outputs=[video_embed, caption_editor, current_video_id])
55
- save_button.click(fn=save,
56
- inputs=[caption_editor, current_video_id],
57
- outputs=save_result)
58
- main_page.load(get_username)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
+ import pandas as pd
3
+ from .Functions.video_player_functions import youtube_link_to_id, get_video_embed_by_id, get_video_link_by_pointer, get_youtube_player_html
4
  from .Functions.caption_editor_functions import get_captions_by_video_id, save_dataframe
5
  from .Resources.css import css
6
+ from .Resources.js import yt_init_js
7
 
8
  next_video_pointer = 0
9
  user = ""
 
18
  return save_dataframe(df, video_id, user)
19
 
20
 
21
+ def on_row_select(df, evt: gr.SelectData):
22
+ """Handle row selection in DataFrame"""
23
+ if evt.index is not None and len(evt.index) > 0:
24
+ row_idx = evt.index[0]
25
+ if row_idx < len(df):
26
+ row = df.iloc[row_idx]
27
+ return (
28
+ gr.update(visible=True), # editing_panel
29
+ gr.update(value=float(row['Start'])), # start_time
30
+ gr.update(value=str(row['Text'])), # text_input
31
+ gr.update(value=float(row['End'])), # end_time
32
+ gr.update(value=row_idx), # selected_row_idx
33
+ gr.update(value="Update Entry") # save_entry_button
34
+ )
35
+ return gr.update(visible=False), "", "", "", -1, "Save Entry"
36
+
37
+
38
+ def show_add_entry_form():
39
+ """Show editing panel for adding new entry"""
40
+ return (
41
+ gr.update(visible=True), # editing_panel
42
+ gr.update(value=0.0), # start_time
43
+ gr.update(value=""), # text_input
44
+ gr.update(value=0.0), # end_time
45
+ gr.update(value=-1), # selected_row_idx (-1 means new entry)
46
+ gr.update(value="Add Entry") # save_entry_button
47
+ )
48
+
49
+
50
+ def save_entry(df, start_time, text, end_time, selected_row_idx, video_id):
51
+ """Save or update a caption entry"""
52
+ try:
53
+ start_time = float(start_time)
54
+ end_time = float(end_time)
55
+
56
+ if start_time >= end_time:
57
+ return df, gr.update(visible=True), gr.Warning("Start time must be less than end time")
58
+
59
+ if not text.strip():
60
+ return df, gr.update(visible=True), gr.Warning("Text cannot be empty")
61
+
62
+ df_copy = df.copy()
63
+
64
+ if selected_row_idx == -1: # Adding new entry
65
+ new_row = pd.DataFrame({
66
+ 'Start': [start_time],
67
+ 'Text': [text.strip()],
68
+ 'End': [end_time]
69
+ })
70
+ df_copy = pd.concat([df_copy, new_row], ignore_index=True)
71
+ # Sort by start time
72
+ df_copy = df_copy.sort_values('Start').reset_index(drop=True)
73
+ else: # Updating existing entry
74
+ if 0 <= selected_row_idx < len(df_copy):
75
+ df_copy.iloc[selected_row_idx] = [start_time, text.strip(), end_time]
76
+ # Sort by start time
77
+ df_copy = df_copy.sort_values('Start').reset_index(drop=True)
78
+
79
+ # Save to file
80
+ save_result = save_dataframe(df_copy, video_id, user)
81
+
82
+ return (
83
+ df_copy,
84
+ gr.update(visible=False), # Hide panel on success
85
+ gr.Info(f"{save_result}")
86
+ )
87
+
88
+ except ValueError as e:
89
+ return df, gr.update(visible=True), gr.Warning(f"Invalid time format: {str(e)}")
90
+ except Exception as e:
91
+ return df, gr.update(visible=True), gr.Error(f"Error: {str(e)}")
92
+
93
+
94
+ def cancel_edit():
95
+ """Cancel editing and hide the form"""
96
+ return gr.update(visible=False)
97
+
98
+
99
  def get_next_components():
100
  global next_video_pointer
101
  next_video_link = get_video_link_by_pointer(next_video_pointer)
 
104
  next_video_link = get_video_link_by_pointer(0)
105
  next_video_pointer = 1
106
 
107
+ try:
108
+ next_video_id = youtube_link_to_id(next_video_link)
109
+ next_captions = get_captions_by_video_id(next_video_id)
110
+ return next_captions, next_video_id
111
+ except (ValueError, Exception) as e:
112
+ empty_captions = pd.DataFrame(columns=["Start", "Text", "End"])
113
+ return empty_captions, "error"
114
+
115
 
 
116
 
117
+ (start_captions, start_video_id) = get_next_components()
118
 
119
+ with gr.Blocks(css=css, head=yt_init_js) as main_page:
 
 
120
  gr.Markdown("## Caption Editor")
121
  current_video_id = gr.Textbox(value=start_video_id, visible=False, interactive=False)
122
+ selected_row_idx = gr.Number(value=-1, visible=False)
123
+
124
  with gr.Row():
125
+ with gr.Column(scale=2, min_width=600):
126
+ # Video player and "next video button
127
+ video_embed = gr.HTML(value=get_youtube_player_html())
128
+ next_video_button = gr.Button("Next")
129
+ with gr.Column(scale=1, min_width=200):
130
+ # Read-only DataFrame with add button
131
+ caption_editor = gr.DataFrame(interactive=False,
132
+ elem_id="tbl",
133
  value=start_captions,
134
  datatype=["number", "str", "number"],
135
+ col_count=(3, "fixed"),
136
+ column_widths=["20%", "60%", "20%"],
137
+ headers=["Start", "Text", "End"],
138
+ wrap=True)
139
+ add_entry_button = gr.Button("Add Entry", variant="secondary")
140
+
141
+ with gr.Row():
142
+ # Editing panel (initially hidden) - spans full width
143
+ with gr.Group(visible=False) as editing_panel:
144
+ gr.Markdown("### Edit Caption Entry")
145
+ with gr.Row(equal_height=False):
146
+ with gr.Column():
147
+ start_time_input = gr.Textbox(label="Start Time (seconds)", value="0.000", interactive=False)
148
+ insert_start_time_button = gr.Button("Insert Current Time")
149
+ with gr.Column():
150
+ text_input = gr.Textbox(label="Caption Text", placeholder="Enter caption text...")
151
+
152
+ with gr.Column():
153
+ end_time_input = gr.Textbox(label="End Time (seconds)", value="0.000", interactive=False)
154
+ insert_end_time_button = gr.Button("Insert Current Time")
155
+
156
+ with gr.Row(equal_height=False):
157
+ save_entry_button = gr.Button("Save Entry", variant="primary")
158
+ cancel_button = gr.Button("Cancel", variant="secondary")
159
+
160
+ save_result = gr.Markdown()
161
+
162
+ # Event handlers
163
+ next_video_button.click(
164
+ fn=get_next_components,
165
+ outputs=[caption_editor, current_video_id]
166
+ )
167
+
168
+ # Load video when current_video_id changes
169
+ current_video_id.change(
170
+ fn=None,
171
+ inputs=current_video_id,
172
+ outputs=None,
173
+ js="""(videoId) => {
174
+ if (window.ytPlayer && window.ytPlayer.cueVideoById) {
175
+ console.log('[Video Load] Calling cueVideoById with:', videoId);
176
+ window.ytPlayer.cueVideoById(videoId);
177
+ } else {
178
+ console.error('[Video Load] Player not ready yet');
179
+ }
180
+ }"""
181
+ )
182
+
183
+ # Handle row selection in DataFrame
184
+ caption_editor.select(
185
+ fn=on_row_select,
186
+ inputs=[caption_editor],
187
+ outputs=[editing_panel, start_time_input, text_input, end_time_input, selected_row_idx, save_entry_button]
188
+ )
189
+
190
+ # Handle add entry button
191
+ add_entry_button.click(
192
+ fn=show_add_entry_form,
193
+ outputs=[editing_panel, start_time_input, text_input, end_time_input, selected_row_idx, save_entry_button]
194
+ )
195
+
196
+ # Handle save entry
197
+ save_entry_button.click(
198
+ fn=save_entry,
199
+ inputs=[caption_editor, start_time_input, text_input, end_time_input, selected_row_idx, current_video_id],
200
+ outputs=[caption_editor, editing_panel, save_result]
201
+ )
202
+
203
+ insert_start_time_button.click(fn=None, inputs=None, outputs=start_time_input,
204
+ js="() => window.ytPlayer ? +window.ytPlayer.getCurrentTime().toFixed(3) : 0")
205
+
206
+ insert_end_time_button.click(fn=None, inputs=None, outputs=end_time_input,
207
+ js="() => window.ytPlayer ? +window.ytPlayer.getCurrentTime().toFixed(3) : 0")
208
 
209
+
210
+ # Handle cancel
211
+ cancel_button.click(
212
+ fn=cancel_edit,
213
+ outputs=[editing_panel]
214
+ )
215
+
216
+ # Load initial video on page load
217
+ main_page.load(
218
+ fn=None,
219
+ inputs=current_video_id,
220
+ outputs=None,
221
+ js="""(videoId) => {
222
+ const checkPlayer = setInterval(() => {
223
+ if (window.ytPlayer && window.ytPlayer.cueVideoById) {
224
+ clearInterval(checkPlayer);
225
+ window.ytPlayer.cueVideoById(videoId);
226
+ }
227
+ }, 100);
228
+ }"""
229
+ )
230
+
231
+ main_page.load(get_username) # Disabled when auth is disabled