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