Navid Arabi commited on
Commit
faa5405
·
2 Parent(s): c1ac104 8ddd8e7

Merge branch 'main' of hf.co:spaces/navidved/tts_labeling

Browse files
components/dashboard_page.py CHANGED
@@ -188,7 +188,7 @@ class DashboardPage:
188
  log.error(f"Error fetching progress for user {user_id}: {e}")
189
  return "Annotation Progress: Error" # Added label
190
 
191
- def download_voice_fn(folder_link, filename_to_load, autoplay_on_load=False): # Autoplay here is for the btn_load_voice click
192
  if not filename_to_load:
193
  return None, None, gr.update(value=None, autoplay=False)
194
  try:
@@ -324,22 +324,14 @@ class DashboardPage:
324
 
325
  if not user_id:
326
  log.warning("load_all_items_fn: user_id not found in session. Dashboard will display default state until login completes and data is refreshed.")
327
- # Prepare default/empty values for all outputs of show_current_item_fn
328
- # (tts_id, filename, sentence, ann_text, audio_placeholder,
329
- # trim_start_sec_ui, trim_end_sec_ui,
330
- # applied_trims_list_state_val, trims_display_val, audio_update_obj)
331
  empty_item_display_tuple = ("", "", "", "", None, None, None, [], self._convert_trims_to_df_data([]), gr.update(value=None, autoplay=False))
332
-
333
- # load_all_items_fn returns: [items_to_load, initial_idx] + list(initial_ui_values_tuple) + [progress_str]
334
- # Total 13 values.
335
- return [[], 0] + list(empty_item_display_tuple) + ["Progress: Waiting for login..."]
336
 
337
  if user_id:
338
  with get_db() as db:
339
  try:
340
  repo = AnnotatorWorkloadRepo(db)
341
- # Get all assigned items
342
- raw_items = repo.get_tts_data_with_annotations_for_user_id(user_id)
343
 
344
  items_to_load = [
345
  {
@@ -353,24 +345,25 @@ class DashboardPage:
353
  log.info(f"Loaded {len(items_to_load)} items for user {user_name} (ID: {user_id})")
354
 
355
  # --- Resume Logic: Find first unannotated or last item ---
356
- first_unannotated_idx = -1
357
- for i, item_data in enumerate(items_to_load):
358
- if not item_data["annotated"]:
359
- first_unannotated_idx = i
360
- break
361
-
362
- if first_unannotated_idx != -1:
363
- initial_idx = first_unannotated_idx
364
- log.info(f"Resuming at first unannotated item, index: {initial_idx} (ID: {items_to_load[initial_idx]['id']})")
365
- elif items_to_load: # All annotated, start at the last one or first if only one
366
- initial_idx = len(items_to_load) - 1
367
- log.info(f"All items annotated, starting at last item, index: {initial_idx} (ID: {items_to_load[initial_idx]['id']})")
 
368
  else: # No items assigned
369
  initial_idx = 0
370
- log.info("No items assigned to user.")
371
 
372
  except Exception as e:
373
- log.error(f"Failed to load items or determine resume index for user {user_name}: {e}") # Removed exc_info=True
374
  gr.Error(f"Could not load your assigned data: {e}")
375
 
376
  initial_ui_values_tuple = show_current_item_fn(items_to_load, initial_idx, sess)
@@ -664,65 +657,67 @@ class DashboardPage:
664
  def _apply_multiple_trims_fn(self, original_audio_data, trims_list_sec):
665
  if not original_audio_data:
666
  log.warning("apply_multiple_trims_fn: No original audio data.")
667
- return None, gr.update(value=None, autoplay=False)
668
 
669
  sr, wav_orig = original_audio_data
 
670
 
671
  if not trims_list_sec: # No trims to apply
672
- log.info("apply_multiple_trims_fn: No trims in list, returning original audio.")
673
- return (sr, wav_orig.copy()), gr.update(value=(sr, wav_orig.copy()), autoplay=False)
 
 
 
674
 
675
- delete_intervals_samples = []
676
- for trim_info in trims_list_sec:
677
  start_s = trim_info.get('start_sec')
678
  end_s = trim_info.get('end_sec')
679
- if start_s is not None and end_s is not None and end_s > start_s and start_s >= 0:
680
- start_sample = int(sr * start_s)
681
- end_sample = int(sr * end_s)
682
- start_sample = max(0, min(start_sample, len(wav_orig)))
683
- end_sample = max(start_sample, min(end_sample, len(wav_orig)))
684
- if start_sample < end_sample:
685
- delete_intervals_samples.append((start_sample, end_sample))
686
- else:
687
- log.warning(f"apply_multiple_trims_fn: Invalid trim skipped: {trim_info}")
688
 
689
- if not delete_intervals_samples:
690
- log.info("apply_multiple_trims_fn: No valid trims to apply, returning original audio.")
691
- return (sr, wav_orig.copy()), gr.update(value=(sr, wav_orig.copy()), autoplay=False)
 
692
 
693
- delete_intervals_samples.sort(key=lambda x: x[0])
 
 
694
 
695
- merged_delete_intervals = []
696
- if delete_intervals_samples:
697
- current_start, current_end = delete_intervals_samples[0]
698
- for next_start, next_end in delete_intervals_samples[1:]:
699
- if next_start < current_end:
700
- current_end = max(current_end, next_end)
701
- else:
702
- merged_delete_intervals.append((current_start, current_end))
703
- current_start, current_end = next_start, next_end
704
- merged_delete_intervals.append((current_start, current_end))
705
-
706
- log.info(f"apply_multiple_trims_fn: Original wav shape: {wav_orig.shape}, Merged delete intervals (samples): {merged_delete_intervals}")
707
-
708
- kept_parts_wav = []
709
- current_pos_samples = 0
710
- for del_start, del_end in merged_delete_intervals:
711
- if del_start > current_pos_samples:
712
- kept_parts_wav.append(wav_orig[current_pos_samples:del_start])
713
- current_pos_samples = del_end
714
-
715
- if current_pos_samples < len(wav_orig):
716
- kept_parts_wav.append(wav_orig[current_pos_samples:])
717
-
718
- if not kept_parts_wav:
719
- final_wav = np.array([], dtype=wav_orig.dtype)
720
- log.info("apply_multiple_trims_fn: All audio trimmed, resulting in empty audio.")
721
- else:
722
- final_wav = np.concatenate(kept_parts_wav)
723
- log.info(f"apply_multiple_trims_fn: Final wav shape after trimming: {final_wav.shape}")
724
 
725
- return (sr, final_wav), gr.update(value=(sr, final_wav), autoplay=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
726
 
727
  def _convert_trims_to_df_data(self, trims_list_sec):
728
  if not trims_list_sec:
 
188
  log.error(f"Error fetching progress for user {user_id}: {e}")
189
  return "Annotation Progress: Error" # Added label
190
 
191
+ def download_voice_fn(folder_link, filename_to_load, autoplay_on_load=True): # Autoplay here is for the btn_load_voice click
192
  if not filename_to_load:
193
  return None, None, gr.update(value=None, autoplay=False)
194
  try:
 
324
 
325
  if not user_id:
326
  log.warning("load_all_items_fn: user_id not found in session. Dashboard will display default state until login completes and data is refreshed.")
 
 
 
 
327
  empty_item_display_tuple = ("", "", "", "", None, None, None, [], self._convert_trims_to_df_data([]), gr.update(value=None, autoplay=False))
328
+ return [[], 0] + list(empty_item_display_tuple) + ["Progress: loading data..."]
 
 
 
329
 
330
  if user_id:
331
  with get_db() as db:
332
  try:
333
  repo = AnnotatorWorkloadRepo(db)
334
+ raw_items = repo.get_tts_data_with_annotations_for_user_id(user_id, annotator_name_for_log=user_name)
 
335
 
336
  items_to_load = [
337
  {
 
345
  log.info(f"Loaded {len(items_to_load)} items for user {user_name} (ID: {user_id})")
346
 
347
  # --- Resume Logic: Find first unannotated or last item ---
348
+ if items_to_load:
349
+ first_unannotated_idx = -1
350
+ for i, item_data in enumerate(items_to_load):
351
+ if not item_data["annotated"]:
352
+ first_unannotated_idx = i
353
+ break
354
+
355
+ if first_unannotated_idx != -1:
356
+ initial_idx = first_unannotated_idx
357
+ log.info(f"Resuming at first unannotated item, index: {initial_idx} (ID: {items_to_load[initial_idx]['id']})")
358
+ else: # All items are annotated
359
+ initial_idx = len(items_to_load) - 1
360
+ log.info(f"All items annotated, starting at last item, index: {initial_idx} (ID: {items_to_load[initial_idx]['id']})")
361
  else: # No items assigned
362
  initial_idx = 0
363
+ log.info("No items assigned to user, starting at index 0.")
364
 
365
  except Exception as e:
366
+ log.error(f"Failed to load items or determine resume index for user {user_name}: {e}")
367
  gr.Error(f"Could not load your assigned data: {e}")
368
 
369
  initial_ui_values_tuple = show_current_item_fn(items_to_load, initial_idx, sess)
 
657
  def _apply_multiple_trims_fn(self, original_audio_data, trims_list_sec):
658
  if not original_audio_data:
659
  log.warning("apply_multiple_trims_fn: No original audio data.")
660
+ return None, gr.update(value=None, autoplay=False) # Keep False if no audio
661
 
662
  sr, wav_orig = original_audio_data
663
+ current_wav = wav_orig.copy() # Start with a copy of the original waveform
664
 
665
  if not trims_list_sec: # No trims to apply
666
+ log.info("apply_multiple_trims_fn: No trims in list, returning original audio with autoplay.")
667
+ # Autoplay is True here as this function is called after audio load
668
+ return (sr, current_wav), gr.update(value=(sr, current_wav), autoplay=True)
669
+
670
+ log.info(f"Applying {len(trims_list_sec)} trims sequentially. Initial shape: {current_wav.shape}, Initial duration: {len(current_wav)/sr:.3f}s")
671
 
672
+ for i, trim_info in enumerate(trims_list_sec):
 
673
  start_s = trim_info.get('start_sec')
674
  end_s = trim_info.get('end_sec')
 
 
 
 
 
 
 
 
 
675
 
676
+ # Validate trim times for the current audio segment
677
+ if not (start_s is not None and end_s is not None and end_s > start_s and start_s >= 0):
678
+ log.warning(f"Trim {i+1}/{len(trims_list_sec)}: Invalid trim definition skipped: {trim_info}")
679
+ continue
680
 
681
+ if len(current_wav) == 0:
682
+ log.warning(f"Trim {i+1}/{len(trims_list_sec)}: Audio is already empty, skipping remaining trims.")
683
+ break # No more audio to trim
684
 
685
+ current_duration_s = len(current_wav) / sr
686
+ log.info(f"Trim {i+1}: Processing trim {trim_info} on audio of current duration {current_duration_s:.3f}s.")
687
+
688
+ # Convert seconds to sample indices for the current waveform
689
+ start_sample = int(sr * start_s)
690
+ end_sample = int(sr * end_s)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
691
 
692
+ current_len_samples = len(current_wav)
693
+
694
+ # Clamp sample indices to the bounds of the current waveform
695
+ # Ensure start_sample is not past the end of the current audio
696
+ start_sample = max(0, min(start_sample, current_len_samples))
697
+ # Ensure end_sample is not past the end, and not before start_sample
698
+ end_sample = max(start_sample, min(end_sample, current_len_samples))
699
+
700
+ if start_sample < end_sample: # If there's a segment to remove
701
+ 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}")
702
+
703
+ part1 = current_wav[:start_sample]
704
+ part2 = current_wav[end_sample:]
705
+
706
+ if len(part1) == 0 and len(part2) == 0: # The entire current audio segment was trimmed
707
+ current_wav = np.array([], dtype=wav_orig.dtype)
708
+ elif len(part1) == 0: # Trimmed from the beginning of the current segment
709
+ current_wav = part2
710
+ elif len(part2) == 0: # Trimmed to the end of the current segment
711
+ current_wav = part1
712
+ else: # Trimmed from the middle of the current segment
713
+ current_wav = np.concatenate((part1, part2))
714
+ log.info(f"Trim {i+1}: Shape after: {current_wav.shape}, New duration: {len(current_wav)/sr:.3f}s")
715
+ else:
716
+ 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).")
717
+
718
+ log.info(f"Finished sequential trimming. Final shape: {current_wav.shape}, Final duration: {len(current_wav)/sr:.3f}s")
719
+ # Autoplay is True here as this function is called after audio load and processing
720
+ return (sr, current_wav), gr.update(value=(sr, current_wav), autoplay=True)
721
 
722
  def _convert_trims_to_df_data(self, trims_list_sec):
723
  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 = [] # For batch deletion of trims
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
- annotation_ids_for_trim_deletion_in_batch.append(annotation_obj.id)
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
- if annotation_ids_for_trim_deletion_in_batch:
264
- log.info(f"Batch deleting trims for {len(annotation_ids_for_trim_deletion_in_batch)} annotations in current batch.")
265
- db.query(AudioTrim).filter(AudioTrim.annotation_id.in_(annotation_ids_for_trim_deletion_in_batch)).delete(synchronize_session=False)
266
- annotation_ids_for_trim_deletion_in_batch.clear()
 
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