Spaces:
Running
Running
updating interface for trim, delete, jump
Browse files- components/dashboard_page.py +404 -108
- components/login_page.py +1 -1
components/dashboard_page.py
CHANGED
@@ -1,9 +1,14 @@
|
|
1 |
import gradio as gr
|
2 |
import numpy as np
|
|
|
|
|
|
|
3 |
from components.header import Header
|
4 |
from utils.logger import Logger
|
5 |
-
from utils.gdrive_downloader import PublicFolderAudioLoader
|
6 |
from config import conf
|
|
|
|
|
7 |
|
8 |
log = Logger()
|
9 |
LOADER = PublicFolderAudioLoader(conf.GDRIVE_API_KEY)
|
@@ -21,145 +26,436 @@ class DashboardPage:
|
|
21 |
with gr.Row():
|
22 |
self.tts_id = gr.Textbox(label="ID", interactive=False)
|
23 |
self.filename = gr.Textbox(label="Filename", interactive=False)
|
24 |
-
self.sentence = gr.Textbox(
|
25 |
-
label="Sentence", interactive=False, max_lines=5, rtl=True
|
26 |
-
)
|
27 |
-
self.ann_sentence = gr.Textbox(
|
28 |
-
label="Annotated Sentence",
|
29 |
-
interactive=True,
|
30 |
-
max_lines=5,
|
31 |
-
rtl=True,
|
32 |
-
)
|
33 |
with gr.Row():
|
34 |
-
self.
|
35 |
-
label="
|
36 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
self.validated = gr.Checkbox(
|
38 |
-
label="Validated", interactive=
|
39 |
)
|
40 |
with gr.Row():
|
41 |
-
self.btn_prev = gr.Button("⬅️ Previous")
|
42 |
-
self.btn_next = gr.Button("Next ➡️")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
|
44 |
# ستون راست
|
45 |
with gr.Column(scale=2):
|
46 |
-
self.btn_load_voice = gr.Button("Load Audio")
|
47 |
-
self.audio = gr.Audio(
|
|
|
|
|
48 |
|
49 |
# stateها
|
50 |
self.items_state = gr.State([])
|
51 |
self.idx_state = gr.State(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
|
53 |
# ---------------- wiring ---------------- #
|
54 |
def register_callbacks(
|
55 |
self, login_page, session_state: gr.State, root_blocks: gr.Blocks
|
56 |
):
|
57 |
-
|
58 |
self.header.register_callbacks(login_page, self, session_state)
|
59 |
|
60 |
-
#
|
61 |
-
def
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
|
75 |
-
def
|
76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
|
78 |
-
def
|
79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
|
81 |
-
def
|
82 |
items = sess.get("dashboard_items", [])
|
83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
|
85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
root_blocks.load(
|
87 |
-
fn=
|
|
|
|
|
|
|
88 |
inputs=[session_state],
|
89 |
-
outputs=[
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
|
|
100 |
)
|
101 |
|
102 |
-
#
|
103 |
-
for
|
104 |
-
(
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
self.
|
115 |
-
self.
|
116 |
-
self.ann_sentence,
|
117 |
-
self.ann_at,
|
118 |
-
self.validated,
|
119 |
-
self.audio,
|
120 |
],
|
|
|
121 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
)
|
123 |
|
124 |
-
#
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
gr.update(interactive=True),
|
136 |
-
gr.update(interactive=True),
|
137 |
-
]
|
138 |
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
import numpy as np
|
3 |
+
import datetime
|
4 |
+
from sqlalchemy import orm
|
5 |
+
|
6 |
from components.header import Header
|
7 |
from utils.logger import Logger
|
8 |
+
from utils.gdrive_downloader import PublicFolderAudioLoader # Assuming LOADER uses this
|
9 |
from config import conf
|
10 |
+
from utils.database import get_db # For DB operations
|
11 |
+
from data.models import Annotation, AudioTrim, TTSData # Import your models
|
12 |
|
13 |
log = Logger()
|
14 |
LOADER = PublicFolderAudioLoader(conf.GDRIVE_API_KEY)
|
|
|
26 |
with gr.Row():
|
27 |
self.tts_id = gr.Textbox(label="ID", interactive=False)
|
28 |
self.filename = gr.Textbox(label="Filename", interactive=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
with gr.Row():
|
30 |
+
self.sentence = gr.Textbox(
|
31 |
+
label="Sentence", interactive=False, max_lines=5, rtl=True
|
32 |
)
|
33 |
+
self.btn_copy = gr.Button("📋 Copy", interactive=True)
|
34 |
+
with gr.Row():
|
35 |
+
self.ann_sentence = gr.Textbox(
|
36 |
+
label="Annotated Sentence",
|
37 |
+
interactive=True,
|
38 |
+
max_lines=5,
|
39 |
+
rtl=True,
|
40 |
+
)
|
41 |
+
self.btn_paste = gr.Button("📥 Paste", interactive=True)
|
42 |
+
with gr.Row():
|
43 |
self.validated = gr.Checkbox(
|
44 |
+
label="Validated", interactive=True
|
45 |
)
|
46 |
with gr.Row():
|
47 |
+
self.btn_prev = gr.Button("⬅️ Previous", interactive=True)
|
48 |
+
self.btn_next = gr.Button("Next ➡️", interactive=True)
|
49 |
+
self.btn_delete = gr.Button("🗑️ Delete", interactive=True)
|
50 |
+
with gr.Row():
|
51 |
+
self.jump_data_id_input = gr.Number(
|
52 |
+
label="Jump to Data ID", value=0, precision=0, interactive=True
|
53 |
+
)
|
54 |
+
self.btn_jump = gr.Button("Go", interactive=True)
|
55 |
+
with gr.Row():
|
56 |
+
self.trim_start_sec = gr.Number(
|
57 |
+
label="Trim Start (s)", value=0.0, precision=3, interactive=True
|
58 |
+
)
|
59 |
+
self.trim_end_sec = gr.Number(
|
60 |
+
label="Trim End (s)", value=0.0, precision=3, interactive=True
|
61 |
+
)
|
62 |
+
self.btn_trim = gr.Button("✂️ Trim", interactive=True)
|
63 |
+
self.btn_undo_trim = gr.Button("↩️ Undo Trim", interactive=True)
|
64 |
|
65 |
# ستون راست
|
66 |
with gr.Column(scale=2):
|
67 |
+
self.btn_load_voice = gr.Button("Load Audio", interactive=True)
|
68 |
+
self.audio = gr.Audio(
|
69 |
+
label="🔊 Audio", interactive=False, autoplay=True
|
70 |
+
)
|
71 |
|
72 |
# stateها
|
73 |
self.items_state = gr.State([])
|
74 |
self.idx_state = gr.State(0)
|
75 |
+
self.clipboard_state = gr.State("")
|
76 |
+
self.original_audio_state = gr.State(None)
|
77 |
+
self.current_trim_params = gr.State(None)
|
78 |
+
|
79 |
+
# List of all interactive UI elements for enabling/disabling
|
80 |
+
self.interactive_ui_elements = [
|
81 |
+
self.btn_prev, self.btn_next, self.btn_delete, self.btn_jump,
|
82 |
+
self.jump_data_id_input, self.trim_start_sec, self.trim_end_sec,
|
83 |
+
self.btn_trim, self.btn_undo_trim, self.btn_load_voice,
|
84 |
+
self.ann_sentence, self.validated, self.btn_copy, self.btn_paste
|
85 |
+
]
|
86 |
|
87 |
# ---------------- wiring ---------------- #
|
88 |
def register_callbacks(
|
89 |
self, login_page, session_state: gr.State, root_blocks: gr.Blocks
|
90 |
):
|
|
|
91 |
self.header.register_callbacks(login_page, self, session_state)
|
92 |
|
93 |
+
# Helper function to update UI interactive state
|
94 |
+
def update_ui_interactive_state(is_interactive: bool):
|
95 |
+
updates = []
|
96 |
+
for elem in self.interactive_ui_elements:
|
97 |
+
if elem == self.btn_load_voice and not is_interactive:
|
98 |
+
updates.append(gr.update(value="⏳ Loading...", interactive=False))
|
99 |
+
elif elem == self.btn_load_voice and is_interactive:
|
100 |
+
updates.append(gr.update(value="Load Audio", interactive=True))
|
101 |
+
else:
|
102 |
+
updates.append(gr.update(interactive=is_interactive))
|
103 |
+
return updates
|
104 |
+
|
105 |
+
# ---- All Helper Functions ----
|
106 |
+
def apply_loaded_trim_fn(audio_data_as_loaded, trim_params_from_state, original_audio_for_state):
|
107 |
+
"""
|
108 |
+
Applies trim if trim_params_from_state are available to the audio_data_as_loaded.
|
109 |
+
This is used after loading an item and its original audio.
|
110 |
+
original_audio_for_state is preserved as the true original.
|
111 |
+
"""
|
112 |
+
if audio_data_as_loaded and trim_params_from_state:
|
113 |
+
sr, wav = audio_data_as_loaded
|
114 |
+
start = trim_params_from_state.get("start")
|
115 |
+
end = trim_params_from_state.get("end")
|
116 |
+
operation = trim_params_from_state.get("operation")
|
117 |
+
|
118 |
+
if operation == "delete" and start is not None and end is not None and end > start and start >= 0:
|
119 |
+
start_sample = int(sr * start / 1000.0)
|
120 |
+
end_sample = int(sr * end / 1000.0)
|
121 |
+
|
122 |
+
audio_duration_samples = len(wav)
|
123 |
+
start_sample = max(0, min(start_sample, audio_duration_samples))
|
124 |
+
end_sample = max(start_sample, min(end_sample, audio_duration_samples))
|
125 |
+
|
126 |
+
if start_sample == 0 and end_sample == audio_duration_samples:
|
127 |
+
log.info(f"Applying saved trim: delete entire audio from {start}ms to {end}ms. Resulting in empty audio.")
|
128 |
+
return (sr, np.array([], dtype=wav.dtype)), original_audio_for_state
|
129 |
+
|
130 |
+
part1 = wav[:start_sample]
|
131 |
+
part2 = wav[end_sample:]
|
132 |
+
deleted_segment_wav = np.concatenate((part1, part2))
|
133 |
+
|
134 |
+
log.info(f"Applied saved trim (delete operation): {start}ms to {end}ms. Original shape: {wav.shape}, New shape: {deleted_segment_wav.shape}")
|
135 |
+
return (sr, deleted_segment_wav), original_audio_for_state
|
136 |
+
else:
|
137 |
+
if operation != "delete":
|
138 |
+
log.warning("Saved trim parameters do not specify a 'delete' operation. Using original audio.")
|
139 |
+
else:
|
140 |
+
log.warning("Invalid saved trim parameters for delete operation. Using original audio.")
|
141 |
+
return audio_data_as_loaded, original_audio_for_state
|
142 |
+
return audio_data_as_loaded, original_audio_for_state
|
143 |
|
144 |
+
def download_voice_fn(folder_link, filename_to_load):
|
145 |
+
if not filename_to_load:
|
146 |
+
return None, None
|
147 |
+
try:
|
148 |
+
log.info(f"Downloading voice: {filename_to_load}")
|
149 |
+
sr, wav = LOADER.load_audio(folder_link, filename_to_load)
|
150 |
+
return (sr, wav), (sr, wav.copy())
|
151 |
+
except Exception as e:
|
152 |
+
log.error(f"GDrive download failed for {filename_to_load}: {e}")
|
153 |
+
gr.Error(f"Failed to load audio: {filename_to_load}. Error: {e}")
|
154 |
+
return None, None
|
155 |
+
|
156 |
+
def save_annotation_db_fn(current_tts_id, session, ann_text_to_save, is_validated_ui, active_trim_params):
|
157 |
+
annotator_id = session.get("user_id")
|
158 |
+
if not current_tts_id or not annotator_id:
|
159 |
+
gr.Error("Cannot save: Missing TTS ID or User ID.")
|
160 |
+
return False
|
161 |
+
validated_to_save = bool(is_validated_ui)
|
162 |
+
with get_db() as db:
|
163 |
+
try:
|
164 |
+
annotation_obj = db.query(Annotation).filter_by(
|
165 |
+
tts_data_id=current_tts_id, annotator_id=annotator_id
|
166 |
+
).first()
|
167 |
+
if not annotation_obj:
|
168 |
+
annotation_obj = Annotation(
|
169 |
+
tts_data_id=current_tts_id, annotator_id=annotator_id
|
170 |
+
)
|
171 |
+
db.add(annotation_obj)
|
172 |
+
annotation_obj.annotated_sentence = ann_text_to_save
|
173 |
+
annotation_obj.validated = validated_to_save
|
174 |
+
annotation_obj.annotated_at = datetime.datetime.utcnow()
|
175 |
+
if active_trim_params and active_trim_params.get("operation") == "delete" and active_trim_params.get("start") is not None:
|
176 |
+
start_to_save = active_trim_params["start"]
|
177 |
+
end_to_save = active_trim_params["end"]
|
178 |
+
if not annotation_obj.audio_trim:
|
179 |
+
db.flush()
|
180 |
+
if annotation_obj.id is None:
|
181 |
+
gr.Error("Failed to get annotation ID for saving trim.")
|
182 |
+
db.rollback()
|
183 |
+
return False
|
184 |
+
new_trim = AudioTrim(
|
185 |
+
annotation_id=annotation_obj.id,
|
186 |
+
original_tts_data_id=current_tts_id,
|
187 |
+
start=start_to_save,
|
188 |
+
end=end_to_save,
|
189 |
+
)
|
190 |
+
annotation_obj.audio_trim = new_trim
|
191 |
+
else:
|
192 |
+
annotation_obj.audio_trim.start = start_to_save
|
193 |
+
annotation_obj.audio_trim.end = end_to_save
|
194 |
+
elif annotation_obj.audio_trim:
|
195 |
+
db.delete(annotation_obj.audio_trim)
|
196 |
+
annotation_obj.audio_trim = None
|
197 |
+
db.commit()
|
198 |
+
gr.Info(f"Annotation for ID {current_tts_id} saved.")
|
199 |
+
return validated_to_save
|
200 |
+
except Exception as e:
|
201 |
+
db.rollback()
|
202 |
+
log.error(f"Failed to save annotation for {current_tts_id}: {e}")
|
203 |
+
gr.Error(f"Save failed: {e}")
|
204 |
+
return False
|
205 |
|
206 |
+
def show_current_item_fn(items, idx, session):
|
207 |
+
if not items or idx >= len(items):
|
208 |
+
return "", "", "", "", False, None, 0.0, 0.0, None
|
209 |
+
current_item = items[idx]
|
210 |
+
tts_data_id = current_item.get("id")
|
211 |
+
annotator_id = session.get("user_id")
|
212 |
+
ann_text, is_validated, trim_params_for_ui = "", False, None
|
213 |
+
start_sec_ui, end_sec_ui = 0.0, 0.0
|
214 |
+
if tts_data_id and annotator_id:
|
215 |
+
with get_db() as db:
|
216 |
+
try:
|
217 |
+
existing_annotation = db.query(Annotation).filter_by(
|
218 |
+
tts_data_id=tts_data_id, annotator_id=annotator_id
|
219 |
+
).options(orm.joinedload(Annotation.audio_trim)).first() # Eager load audio_trim
|
220 |
+
if existing_annotation:
|
221 |
+
ann_text = existing_annotation.annotated_sentence or ""
|
222 |
+
is_validated = existing_annotation.validated
|
223 |
+
if existing_annotation.audio_trim:
|
224 |
+
trim_params_for_ui = {
|
225 |
+
"start": existing_annotation.audio_trim.start,
|
226 |
+
"end": existing_annotation.audio_trim.end,
|
227 |
+
"operation": "delete"
|
228 |
+
}
|
229 |
+
start_sec_ui = existing_annotation.audio_trim.start / 1000.0
|
230 |
+
end_sec_ui = existing_annotation.audio_trim.end / 1000.0
|
231 |
+
except Exception as e:
|
232 |
+
log.error(f"Database error in show_current_item_fn for TTS ID {tts_data_id}: {e}")
|
233 |
+
gr.Error(f"Error loading annotation details: {e}")
|
234 |
+
return (
|
235 |
+
current_item.get("id", ""), current_item.get("filename", ""),
|
236 |
+
current_item.get("sentence", ""), ann_text, is_validated, None,
|
237 |
+
start_sec_ui, end_sec_ui, trim_params_for_ui
|
238 |
+
)
|
239 |
+
|
240 |
+
def navigate_idx_fn(items, current_idx, direction):
|
241 |
+
if not items: return 0
|
242 |
+
new_idx = min(current_idx + 1, len(items) - 1) if direction == "next" else max(current_idx - 1, 0)
|
243 |
+
return new_idx
|
244 |
|
245 |
+
def load_all_items_fn(sess):
|
246 |
items = sess.get("dashboard_items", [])
|
247 |
+
initial_ui_values = show_current_item_fn(items, 0, sess)
|
248 |
+
return items, 0, *initial_ui_values
|
249 |
+
|
250 |
+
def jump_by_data_id_fn(items, target_data_id_str, current_idx):
|
251 |
+
if not target_data_id_str: return current_idx
|
252 |
+
try:
|
253 |
+
target_id = int(target_data_id_str)
|
254 |
+
for i, item_dict in enumerate(items):
|
255 |
+
if item_dict.get("id") == target_id: return i
|
256 |
+
gr.Warning(f"Data ID {target_id} not found.")
|
257 |
+
except ValueError:
|
258 |
+
gr.Warning(f"Invalid Data ID format: {target_data_id_str}")
|
259 |
+
return current_idx
|
260 |
|
261 |
+
def perform_trim_fn(original_audio_data, start_sec, end_sec, current_audio_for_fallback):
|
262 |
+
log.info(f"perform_trim_fn called with start_sec: {start_sec}, end_sec: {end_sec}")
|
263 |
+
if original_audio_data is None:
|
264 |
+
gr.Warning("No original audio loaded. Cannot perform new trim.")
|
265 |
+
return current_audio_for_fallback, None
|
266 |
+
if start_sec is None or end_sec is None or start_sec < 0 or end_sec <= start_sec:
|
267 |
+
gr.Warning("Invalid trim times. Start must be >= 0 and End > Start.")
|
268 |
+
return original_audio_data, None
|
269 |
+
try:
|
270 |
+
sr, wav = original_audio_data
|
271 |
+
start_sample, end_sample = int(sr * start_sec), int(sr * end_sec)
|
272 |
+
audio_duration_samples = len(wav)
|
273 |
+
start_sample = max(0, min(start_sample, audio_duration_samples))
|
274 |
+
end_sample = max(start_sample, min(end_sample, audio_duration_samples))
|
275 |
+
trimmed_wav = np.concatenate((wav[:start_sample], wav[end_sample:]))
|
276 |
+
active_trim_params = {"start": start_sec * 1000.0, "end": end_sec * 1000.0, "operation": "delete"}
|
277 |
+
log.info(f"Audio segment deleted. New shape: {trimmed_wav.shape}")
|
278 |
+
if trimmed_wav.size == 0: gr.Warning("Trim resulted in empty audio.")
|
279 |
+
return (sr, trimmed_wav), active_trim_params
|
280 |
+
except Exception as e:
|
281 |
+
log.error(f"Error during audio trimming: {e}")
|
282 |
+
gr.Error(f"Failed to trim audio: {e}")
|
283 |
+
return original_audio_data, None
|
284 |
+
|
285 |
+
def delete_db_and_ui_fn(items, current_idx, session):
|
286 |
+
item_info = items[current_idx]
|
287 |
+
tts_data_id_to_delete = item_info.get("id")
|
288 |
+
annotator_id_for_delete = session.get("user_id")
|
289 |
+
if tts_data_id_to_delete and annotator_id_for_delete:
|
290 |
+
with get_db() as db:
|
291 |
+
try:
|
292 |
+
annotation_obj = db.query(Annotation).filter_by(
|
293 |
+
tts_data_id=tts_data_id_to_delete, annotator_id=annotator_id_for_delete
|
294 |
+
).first()
|
295 |
+
if annotation_obj:
|
296 |
+
db.delete(annotation_obj) # Cascade should handle AudioTrim
|
297 |
+
db.commit()
|
298 |
+
gr.Info(f"Annotation for ID {tts_data_id_to_delete} deleted.")
|
299 |
+
else:
|
300 |
+
gr.Warning(f"No annotation found to delete for ID {tts_data_id_to_delete}.")
|
301 |
+
except Exception as e:
|
302 |
+
db.rollback()
|
303 |
+
log.error(f"Error deleting annotation {tts_data_id_to_delete}: {e}")
|
304 |
+
gr.Error(f"Failed to delete annotation: {e}")
|
305 |
+
else:
|
306 |
+
gr.Error("Cannot delete: Missing TTS ID or User ID.")
|
307 |
+
refreshed_ui_values = show_current_item_fn(items, current_idx, session)
|
308 |
+
return items, current_idx, *refreshed_ui_values
|
309 |
+
|
310 |
+
# ---- Callback Implementations ----
|
311 |
+
outputs_for_show_current = [
|
312 |
+
self.tts_id, self.filename, self.sentence, self.ann_sentence,
|
313 |
+
self.validated, self.audio, self.trim_start_sec,
|
314 |
+
self.trim_end_sec, self.current_trim_params,
|
315 |
+
]
|
316 |
+
|
317 |
+
# Initial Load
|
318 |
root_blocks.load(
|
319 |
+
fn=lambda: update_ui_interactive_state(False),
|
320 |
+
outputs=self.interactive_ui_elements
|
321 |
+
).then(
|
322 |
+
fn=load_all_items_fn,
|
323 |
inputs=[session_state],
|
324 |
+
outputs=[self.items_state, self.idx_state] + outputs_for_show_current,
|
325 |
+
).then(
|
326 |
+
fn=download_voice_fn,
|
327 |
+
inputs=[gr.State(GDRIVE_FOLDER), self.filename],
|
328 |
+
outputs=[self.audio, self.original_audio_state],
|
329 |
+
).then(
|
330 |
+
fn=apply_loaded_trim_fn,
|
331 |
+
inputs=[self.audio, self.current_trim_params, self.original_audio_state],
|
332 |
+
outputs=[self.audio, self.original_audio_state]
|
333 |
+
).then(
|
334 |
+
fn=lambda: update_ui_interactive_state(True),
|
335 |
+
outputs=self.interactive_ui_elements
|
336 |
)
|
337 |
|
338 |
+
# Navigation (Prev/Next)
|
339 |
+
for btn_widget, direction_str in [
|
340 |
+
(self.btn_prev, "prev"), (self.btn_next, "next"),
|
341 |
+
]:
|
342 |
+
event_chain = btn_widget.click(
|
343 |
+
fn=lambda: update_ui_interactive_state(False),
|
344 |
+
outputs=self.interactive_ui_elements
|
345 |
+
)
|
346 |
+
if direction_str == "next":
|
347 |
+
event_chain = event_chain.then(
|
348 |
+
fn=save_annotation_db_fn,
|
349 |
+
inputs=[
|
350 |
+
self.tts_id, session_state, self.ann_sentence,
|
351 |
+
self.validated, self.current_trim_params,
|
|
|
|
|
|
|
|
|
352 |
],
|
353 |
+
outputs=[self.validated]
|
354 |
)
|
355 |
+
event_chain.then(
|
356 |
+
fn=navigate_idx_fn,
|
357 |
+
inputs=[self.items_state, self.idx_state, gr.State(direction_str)],
|
358 |
+
outputs=self.idx_state,
|
359 |
+
).then(
|
360 |
+
fn=show_current_item_fn,
|
361 |
+
inputs=[self.items_state, self.idx_state, session_state],
|
362 |
+
outputs=outputs_for_show_current,
|
363 |
+
).then(
|
364 |
+
fn=download_voice_fn,
|
365 |
+
inputs=[gr.State(GDRIVE_FOLDER), self.filename],
|
366 |
+
outputs=[self.audio, self.original_audio_state],
|
367 |
+
).then(
|
368 |
+
fn=apply_loaded_trim_fn,
|
369 |
+
inputs=[self.audio, self.current_trim_params, self.original_audio_state],
|
370 |
+
outputs=[self.audio, self.original_audio_state]
|
371 |
+
).then(
|
372 |
+
fn=lambda: update_ui_interactive_state(True),
|
373 |
+
outputs=self.interactive_ui_elements
|
374 |
)
|
375 |
|
376 |
+
# Manual Load Audio Button
|
377 |
+
self.btn_load_voice.click(
|
378 |
+
fn=lambda: update_ui_interactive_state(False),
|
379 |
+
outputs=self.interactive_ui_elements
|
380 |
+
).then(
|
381 |
+
fn=download_voice_fn,
|
382 |
+
inputs=[gr.State(GDRIVE_FOLDER), self.filename],
|
383 |
+
outputs=[self.audio, self.original_audio_state],
|
384 |
+
).then(
|
385 |
+
fn=apply_loaded_trim_fn,
|
386 |
+
inputs=[self.audio, self.current_trim_params, self.original_audio_state],
|
387 |
+
outputs=[self.audio, self.original_audio_state]
|
388 |
+
).then(
|
389 |
+
fn=lambda: update_ui_interactive_state(True),
|
390 |
+
outputs=self.interactive_ui_elements
|
391 |
+
)
|
392 |
|
393 |
+
# Copy/Paste (Quick operations, no UI disable needed)
|
394 |
+
self.btn_copy.click(fn=lambda x: x, inputs=self.sentence, outputs=self.clipboard_state)
|
395 |
+
self.btn_paste.click(fn=lambda x: x, inputs=self.clipboard_state, outputs=self.ann_sentence)
|
|
|
|
|
|
|
396 |
|
397 |
+
# Jump to Data ID
|
398 |
+
self.btn_jump.click(
|
399 |
+
fn=lambda: update_ui_interactive_state(False),
|
400 |
+
outputs=self.interactive_ui_elements
|
401 |
+
).then(
|
402 |
+
fn=jump_by_data_id_fn,
|
403 |
+
inputs=[self.items_state, self.jump_data_id_input, self.idx_state],
|
404 |
+
outputs=self.idx_state,
|
405 |
+
).then(
|
406 |
+
fn=show_current_item_fn,
|
407 |
+
inputs=[self.items_state, self.idx_state, session_state],
|
408 |
+
outputs=outputs_for_show_current,
|
409 |
+
).then(
|
410 |
+
fn=download_voice_fn,
|
411 |
+
inputs=[gr.State(GDRIVE_FOLDER), self.filename],
|
412 |
+
outputs=[self.audio, self.original_audio_state],
|
413 |
+
).then(
|
414 |
+
fn=apply_loaded_trim_fn,
|
415 |
+
inputs=[self.audio, self.current_trim_params, self.original_audio_state],
|
416 |
+
outputs=[self.audio, self.original_audio_state]
|
417 |
+
).then(
|
418 |
+
fn=lambda: update_ui_interactive_state(True),
|
419 |
+
outputs=self.interactive_ui_elements
|
420 |
+
)
|
421 |
+
|
422 |
+
# Trim Audio
|
423 |
+
self.btn_trim.click(
|
424 |
+
fn=lambda: update_ui_interactive_state(False),
|
425 |
+
outputs=self.interactive_ui_elements
|
426 |
+
).then(
|
427 |
+
fn=perform_trim_fn,
|
428 |
+
inputs=[self.original_audio_state, self.trim_start_sec, self.trim_end_sec, self.audio],
|
429 |
+
outputs=[self.audio, self.current_trim_params],
|
430 |
+
).then(
|
431 |
+
fn=lambda: update_ui_interactive_state(True),
|
432 |
+
outputs=self.interactive_ui_elements
|
433 |
)
|
434 |
+
|
435 |
+
# Undo Trim
|
436 |
+
self.btn_undo_trim.click(
|
437 |
+
fn=lambda: update_ui_interactive_state(False),
|
438 |
+
outputs=self.interactive_ui_elements
|
439 |
+
).then(
|
440 |
+
fn=lambda orig_audio: (orig_audio, None, 0.0, 0.0) if orig_audio else (None, None, 0.0, 0.0),
|
441 |
+
inputs=[self.original_audio_state],
|
442 |
+
outputs=[self.audio, self.current_trim_params, self.trim_start_sec, self.trim_end_sec],
|
443 |
+
).then(
|
444 |
+
fn=lambda: update_ui_interactive_state(True),
|
445 |
+
outputs=self.interactive_ui_elements
|
446 |
+
)
|
447 |
+
|
448 |
+
# Delete Annotation
|
449 |
+
self.btn_delete.click(
|
450 |
+
fn=lambda: update_ui_interactive_state(False),
|
451 |
+
outputs=self.interactive_ui_elements
|
452 |
+
).then(
|
453 |
+
fn=delete_db_and_ui_fn,
|
454 |
+
inputs=[self.items_state, self.idx_state, session_state],
|
455 |
+
outputs=[self.items_state, self.idx_state] + outputs_for_show_current,
|
456 |
+
).then(
|
457 |
+
fn=lambda: update_ui_interactive_state(True),
|
458 |
+
outputs=self.interactive_ui_elements
|
459 |
+
)
|
460 |
+
|
461 |
+
return self.container
|
components/login_page.py
CHANGED
@@ -48,7 +48,7 @@ class LoginPage:
|
|
48 |
dashboard_page.filename,
|
49 |
dashboard_page.sentence,
|
50 |
dashboard_page.ann_sentence,
|
51 |
-
dashboard_page.ann_at,
|
52 |
dashboard_page.validated,
|
53 |
],
|
54 |
)
|
|
|
48 |
dashboard_page.filename,
|
49 |
dashboard_page.sentence,
|
50 |
dashboard_page.ann_sentence,
|
51 |
+
# dashboard_page.ann_at,
|
52 |
dashboard_page.validated,
|
53 |
],
|
54 |
)
|