Spaces:
Running
Running
fixed multiple trimming logic
Browse files
components/dashboard_page.py
CHANGED
@@ -322,22 +322,14 @@ class DashboardPage:
|
|
322 |
|
323 |
if not user_id:
|
324 |
log.warning("load_all_items_fn: user_id not found in session. Dashboard will display default state until login completes and data is refreshed.")
|
325 |
-
# Prepare default/empty values for all outputs of show_current_item_fn
|
326 |
-
# (tts_id, filename, sentence, ann_text, audio_placeholder,
|
327 |
-
# trim_start_sec_ui, trim_end_sec_ui,
|
328 |
-
# applied_trims_list_state_val, trims_display_val, audio_update_obj)
|
329 |
empty_item_display_tuple = ("", "", "", "", None, None, None, [], self._convert_trims_to_df_data([]), gr.update(value=None, autoplay=False))
|
330 |
-
|
331 |
-
# load_all_items_fn returns: [items_to_load, initial_idx] + list(initial_ui_values_tuple) + [progress_str]
|
332 |
-
# Total 13 values.
|
333 |
-
return [[], 0] + list(empty_item_display_tuple) + ["Progress: Waiting for login..."]
|
334 |
|
335 |
if user_id:
|
336 |
with get_db() as db:
|
337 |
try:
|
338 |
repo = AnnotatorWorkloadRepo(db)
|
339 |
-
|
340 |
-
raw_items = repo.get_tts_data_with_annotations_for_user_id(user_id)
|
341 |
|
342 |
items_to_load = [
|
343 |
{
|
@@ -351,24 +343,25 @@ class DashboardPage:
|
|
351 |
log.info(f"Loaded {len(items_to_load)} items for user {user_name} (ID: {user_id})")
|
352 |
|
353 |
# --- Resume Logic: Find first unannotated or last item ---
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
|
|
366 |
else: # No items assigned
|
367 |
initial_idx = 0
|
368 |
-
log.info("No items assigned to user.")
|
369 |
|
370 |
except Exception as e:
|
371 |
-
log.error(f"Failed to load items or determine resume index for user {user_name}: {e}")
|
372 |
gr.Error(f"Could not load your assigned data: {e}")
|
373 |
|
374 |
initial_ui_values_tuple = show_current_item_fn(items_to_load, initial_idx, sess)
|
@@ -665,62 +658,62 @@ class DashboardPage:
|
|
665 |
return None, gr.update(value=None, autoplay=False)
|
666 |
|
667 |
sr, wav_orig = original_audio_data
|
|
|
668 |
|
669 |
if not trims_list_sec: # No trims to apply
|
670 |
log.info("apply_multiple_trims_fn: No trims in list, returning original audio.")
|
671 |
-
return (sr,
|
|
|
|
|
672 |
|
673 |
-
|
674 |
-
for trim_info in trims_list_sec:
|
675 |
start_s = trim_info.get('start_sec')
|
676 |
end_s = trim_info.get('end_sec')
|
677 |
-
if start_s is not None and end_s is not None and end_s > start_s and start_s >= 0:
|
678 |
-
start_sample = int(sr * start_s)
|
679 |
-
end_sample = int(sr * end_s)
|
680 |
-
start_sample = max(0, min(start_sample, len(wav_orig)))
|
681 |
-
end_sample = max(start_sample, min(end_sample, len(wav_orig)))
|
682 |
-
if start_sample < end_sample:
|
683 |
-
delete_intervals_samples.append((start_sample, end_sample))
|
684 |
-
else:
|
685 |
-
log.warning(f"apply_multiple_trims_fn: Invalid trim skipped: {trim_info}")
|
686 |
|
687 |
-
|
688 |
-
|
689 |
-
|
|
|
690 |
|
691 |
-
|
|
|
|
|
692 |
|
693 |
-
|
694 |
-
|
695 |
-
|
696 |
-
for
|
697 |
-
|
698 |
-
|
699 |
-
else:
|
700 |
-
merged_delete_intervals.append((current_start, current_end))
|
701 |
-
current_start, current_end = next_start, next_end
|
702 |
-
merged_delete_intervals.append((current_start, current_end))
|
703 |
-
|
704 |
-
log.info(f"apply_multiple_trims_fn: Original wav shape: {wav_orig.shape}, Merged delete intervals (samples): {merged_delete_intervals}")
|
705 |
-
|
706 |
-
kept_parts_wav = []
|
707 |
-
current_pos_samples = 0
|
708 |
-
for del_start, del_end in merged_delete_intervals:
|
709 |
-
if del_start > current_pos_samples:
|
710 |
-
kept_parts_wav.append(wav_orig[current_pos_samples:del_start])
|
711 |
-
current_pos_samples = del_end
|
712 |
-
|
713 |
-
if current_pos_samples < len(wav_orig):
|
714 |
-
kept_parts_wav.append(wav_orig[current_pos_samples:])
|
715 |
-
|
716 |
-
if not kept_parts_wav:
|
717 |
-
final_wav = np.array([], dtype=wav_orig.dtype)
|
718 |
-
log.info("apply_multiple_trims_fn: All audio trimmed, resulting in empty audio.")
|
719 |
-
else:
|
720 |
-
final_wav = np.concatenate(kept_parts_wav)
|
721 |
-
log.info(f"apply_multiple_trims_fn: Final wav shape after trimming: {final_wav.shape}")
|
722 |
|
723 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
724 |
|
725 |
def _convert_trims_to_df_data(self, trims_list_sec):
|
726 |
if not trims_list_sec:
|
|
|
322 |
|
323 |
if not user_id:
|
324 |
log.warning("load_all_items_fn: user_id not found in session. Dashboard will display default state until login completes and data is refreshed.")
|
|
|
|
|
|
|
|
|
325 |
empty_item_display_tuple = ("", "", "", "", None, None, None, [], self._convert_trims_to_df_data([]), gr.update(value=None, autoplay=False))
|
326 |
+
return [[], 0] + list(empty_item_display_tuple) + ["Progress: loading data..."]
|
|
|
|
|
|
|
327 |
|
328 |
if user_id:
|
329 |
with get_db() as db:
|
330 |
try:
|
331 |
repo = AnnotatorWorkloadRepo(db)
|
332 |
+
raw_items = repo.get_tts_data_with_annotations_for_user_id(user_id, annotator_name_for_log=user_name)
|
|
|
333 |
|
334 |
items_to_load = [
|
335 |
{
|
|
|
343 |
log.info(f"Loaded {len(items_to_load)} items for user {user_name} (ID: {user_id})")
|
344 |
|
345 |
# --- Resume Logic: Find first unannotated or last item ---
|
346 |
+
if items_to_load:
|
347 |
+
first_unannotated_idx = -1
|
348 |
+
for i, item_data in enumerate(items_to_load):
|
349 |
+
if not item_data["annotated"]:
|
350 |
+
first_unannotated_idx = i
|
351 |
+
break
|
352 |
+
|
353 |
+
if first_unannotated_idx != -1:
|
354 |
+
initial_idx = first_unannotated_idx
|
355 |
+
log.info(f"Resuming at first unannotated item, index: {initial_idx} (ID: {items_to_load[initial_idx]['id']})")
|
356 |
+
else: # All items are annotated
|
357 |
+
initial_idx = len(items_to_load) - 1
|
358 |
+
log.info(f"All items annotated, starting at last item, index: {initial_idx} (ID: {items_to_load[initial_idx]['id']})")
|
359 |
else: # No items assigned
|
360 |
initial_idx = 0
|
361 |
+
log.info("No items assigned to user, starting at index 0.")
|
362 |
|
363 |
except Exception as e:
|
364 |
+
log.error(f"Failed to load items or determine resume index for user {user_name}: {e}")
|
365 |
gr.Error(f"Could not load your assigned data: {e}")
|
366 |
|
367 |
initial_ui_values_tuple = show_current_item_fn(items_to_load, initial_idx, sess)
|
|
|
658 |
return None, gr.update(value=None, autoplay=False)
|
659 |
|
660 |
sr, wav_orig = original_audio_data
|
661 |
+
current_wav = wav_orig.copy() # Start with a copy of the original waveform
|
662 |
|
663 |
if not trims_list_sec: # No trims to apply
|
664 |
log.info("apply_multiple_trims_fn: No trims in list, returning original audio.")
|
665 |
+
return (sr, current_wav), gr.update(value=(sr, current_wav), autoplay=False)
|
666 |
+
|
667 |
+
log.info(f"Applying {len(trims_list_sec)} trims sequentially. Initial shape: {current_wav.shape}, Initial duration: {len(current_wav)/sr:.3f}s")
|
668 |
|
669 |
+
for i, trim_info in enumerate(trims_list_sec):
|
|
|
670 |
start_s = trim_info.get('start_sec')
|
671 |
end_s = trim_info.get('end_sec')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
672 |
|
673 |
+
# Validate trim times for the current audio segment
|
674 |
+
if not (start_s is not None and end_s is not None and end_s > start_s and start_s >= 0):
|
675 |
+
log.warning(f"Trim {i+1}/{len(trims_list_sec)}: Invalid trim definition skipped: {trim_info}")
|
676 |
+
continue
|
677 |
|
678 |
+
if len(current_wav) == 0:
|
679 |
+
log.warning(f"Trim {i+1}/{len(trims_list_sec)}: Audio is already empty, skipping remaining trims.")
|
680 |
+
break # No more audio to trim
|
681 |
|
682 |
+
current_duration_s = len(current_wav) / sr
|
683 |
+
log.info(f"Trim {i+1}: Processing trim {trim_info} on audio of current duration {current_duration_s:.3f}s.")
|
684 |
+
|
685 |
+
# Convert seconds to sample indices for the current waveform
|
686 |
+
start_sample = int(sr * start_s)
|
687 |
+
end_sample = int(sr * end_s)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
688 |
|
689 |
+
current_len_samples = len(current_wav)
|
690 |
+
|
691 |
+
# Clamp sample indices to the bounds of the current waveform
|
692 |
+
# Ensure start_sample is not past the end of the current audio
|
693 |
+
start_sample = max(0, min(start_sample, current_len_samples))
|
694 |
+
# Ensure end_sample is not past the end, and not before start_sample
|
695 |
+
end_sample = max(start_sample, min(end_sample, current_len_samples))
|
696 |
+
|
697 |
+
if start_sample < end_sample: # If there's a segment to remove
|
698 |
+
log.info(f"Trim {i+1}: Applying samples {start_sample}-{end_sample} to current audio (length {current_len_samples} samples). Shape before: {current_wav.shape}")
|
699 |
+
|
700 |
+
part1 = current_wav[:start_sample]
|
701 |
+
part2 = current_wav[end_sample:]
|
702 |
+
|
703 |
+
if len(part1) == 0 and len(part2) == 0: # The entire current audio segment was trimmed
|
704 |
+
current_wav = np.array([], dtype=wav_orig.dtype)
|
705 |
+
elif len(part1) == 0: # Trimmed from the beginning of the current segment
|
706 |
+
current_wav = part2
|
707 |
+
elif len(part2) == 0: # Trimmed to the end of the current segment
|
708 |
+
current_wav = part1
|
709 |
+
else: # Trimmed from the middle of the current segment
|
710 |
+
current_wav = np.concatenate((part1, part2))
|
711 |
+
log.info(f"Trim {i+1}: Shape after: {current_wav.shape}, New duration: {len(current_wav)/sr:.3f}s")
|
712 |
+
else:
|
713 |
+
log.info(f"Trim {i+1}: No effective change for trim {trim_info} on current audio (start_sample >= end_sample after clamping or trim times out of bounds for current audio).")
|
714 |
+
|
715 |
+
log.info(f"Finished sequential trimming. Final shape: {current_wav.shape}, Final duration: {len(current_wav)/sr:.3f}s")
|
716 |
+
return (sr, current_wav), gr.update(value=(sr, current_wav), autoplay=False)
|
717 |
|
718 |
def _convert_trims_to_df_data(self, trims_list_sec):
|
719 |
if not trims_list_sec:
|
scripts/import_annotations_from_json.py
CHANGED
@@ -35,7 +35,7 @@ def import_annotations(db: SQLAlchemySession, data: dict): # Changed SessionLoca
|
|
35 |
tts_data_cache = {}
|
36 |
annotator_cache = {}
|
37 |
|
38 |
-
annotation_ids_for_trim_deletion_in_batch = [] #
|
39 |
|
40 |
# Create a mapping from JSON ID to sample data for efficient lookup
|
41 |
samples_by_id = {s.get("id"): s for s in samples if s.get("id") is not None}
|
@@ -200,6 +200,11 @@ def import_annotations(db: SQLAlchemySession, data: dict): # Changed SessionLoca
|
|
200 |
).first()
|
201 |
|
202 |
if annotation_obj:
|
|
|
|
|
|
|
|
|
|
|
203 |
annotation_obj.annotated_sentence = final_annotated_sentence
|
204 |
annotation_obj.annotated_at = final_annotated_at
|
205 |
updated_count +=1
|
@@ -221,8 +226,8 @@ def import_annotations(db: SQLAlchemySession, data: dict): # Changed SessionLoca
|
|
221 |
continue
|
222 |
|
223 |
if annotation_obj.id:
|
224 |
-
if annotation_obj.id not in annotation_ids_for_trim_deletion_in_batch:
|
225 |
-
|
226 |
|
227 |
json_audio_trims = json_ann.get("audio_trims", [])
|
228 |
if json_audio_trims:
|
@@ -260,10 +265,11 @@ def import_annotations(db: SQLAlchemySession, data: dict): # Changed SessionLoca
|
|
260 |
samples_processed_in_batch += 1
|
261 |
|
262 |
if samples_processed_in_batch >= BATCH_SIZE or (sample_idx == len(samples) - 1):
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
|
|
267 |
|
268 |
try:
|
269 |
db.commit()
|
@@ -271,7 +277,6 @@ def import_annotations(db: SQLAlchemySession, data: dict): # Changed SessionLoca
|
|
271 |
except Exception as e_commit:
|
272 |
db.rollback()
|
273 |
log.error(f"Failed to commit batch after sample index {sample_idx} (TTSData JSON ID {current_sample_json_id}): {e_commit}. Rolling back this batch.")
|
274 |
-
annotation_ids_for_trim_deletion_in_batch.clear()
|
275 |
finally:
|
276 |
samples_processed_in_batch = 0 # Reset for next batch or end
|
277 |
|
|
|
35 |
tts_data_cache = {}
|
36 |
annotator_cache = {}
|
37 |
|
38 |
+
# annotation_ids_for_trim_deletion_in_batch = [] # Removed
|
39 |
|
40 |
# Create a mapping from JSON ID to sample data for efficient lookup
|
41 |
samples_by_id = {s.get("id"): s for s in samples if s.get("id") is not None}
|
|
|
200 |
).first()
|
201 |
|
202 |
if annotation_obj:
|
203 |
+
# If annotation exists, delete its old trims first
|
204 |
+
if annotation_obj.id:
|
205 |
+
# log.debug(f"Deleting existing trims for Annotation ID {annotation_obj.id} before updating.")
|
206 |
+
db.query(AudioTrim).filter(AudioTrim.annotation_id == annotation_obj.id).delete(synchronize_session=False)
|
207 |
+
|
208 |
annotation_obj.annotated_sentence = final_annotated_sentence
|
209 |
annotation_obj.annotated_at = final_annotated_at
|
210 |
updated_count +=1
|
|
|
226 |
continue
|
227 |
|
228 |
if annotation_obj.id:
|
229 |
+
# Removed: if annotation_obj.id not in annotation_ids_for_trim_deletion_in_batch:
|
230 |
+
# Removed: annotation_ids_for_trim_deletion_in_batch.append(annotation_obj.id)
|
231 |
|
232 |
json_audio_trims = json_ann.get("audio_trims", [])
|
233 |
if json_audio_trims:
|
|
|
265 |
samples_processed_in_batch += 1
|
266 |
|
267 |
if samples_processed_in_batch >= BATCH_SIZE or (sample_idx == len(samples) - 1):
|
268 |
+
# Removed the block for batch deleting trims that used annotation_ids_for_trim_deletion_in_batch
|
269 |
+
# if annotation_ids_for_trim_deletion_in_batch:
|
270 |
+
# log.info(f"Batch deleting trims for {len(annotation_ids_for_trim_deletion_in_batch)} annotations in current batch.")
|
271 |
+
# db.query(AudioTrim).filter(AudioTrim.annotation_id.in_(annotation_ids_for_trim_deletion_in_batch)).delete(synchronize_session=False)
|
272 |
+
# annotation_ids_for_trim_deletion_in_batch.clear()
|
273 |
|
274 |
try:
|
275 |
db.commit()
|
|
|
277 |
except Exception as e_commit:
|
278 |
db.rollback()
|
279 |
log.error(f"Failed to commit batch after sample index {sample_idx} (TTSData JSON ID {current_sample_json_id}): {e_commit}. Rolling back this batch.")
|
|
|
280 |
finally:
|
281 |
samples_processed_in_batch = 0 # Reset for next batch or end
|
282 |
|