batchalign 0.8.0.post1__tar.gz → 0.8.0.post3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of batchalign might be problematic. Click here for more details.

Files changed (144) hide show
  1. {batchalign-0.8.0.post1/batchalign.egg-info → batchalign-0.8.0.post3}/PKG-INFO +1 -1
  2. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/cli/dispatch.py +267 -16
  3. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/utils.py +6 -2
  4. batchalign-0.8.0.post3/batchalign/version +3 -0
  5. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3/batchalign.egg-info}/PKG-INFO +1 -1
  6. batchalign-0.8.0.post1/batchalign/version +0 -3
  7. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/LICENSE +0 -0
  8. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/MANIFEST.in +0 -0
  9. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/README.md +0 -0
  10. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/__init__.py +0 -0
  11. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/__main__.py +0 -0
  12. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/cli/__init__.py +0 -0
  13. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/cli/cli.py +0 -0
  14. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/constants.py +0 -0
  15. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/document.py +0 -0
  16. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/errors.py +0 -0
  17. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/formats/__init__.py +0 -0
  18. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/formats/base.py +0 -0
  19. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/formats/chat/__init__.py +0 -0
  20. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/formats/chat/file.py +0 -0
  21. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/formats/chat/generator.py +0 -0
  22. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/formats/chat/lexer.py +0 -0
  23. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/formats/chat/parser.py +0 -0
  24. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/formats/chat/utils.py +0 -0
  25. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/formats/textgrid/__init__.py +0 -0
  26. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/formats/textgrid/file.py +0 -0
  27. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/formats/textgrid/generator.py +0 -0
  28. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/formats/textgrid/parser.py +0 -0
  29. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/__init__.py +0 -0
  30. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/resolve.py +0 -0
  31. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/speaker/__init__.py +0 -0
  32. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/speaker/config.yaml +0 -0
  33. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/speaker/infer.py +0 -0
  34. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/speaker/utils.py +0 -0
  35. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/training/__init__.py +0 -0
  36. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/training/run.py +0 -0
  37. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/training/utils.py +0 -0
  38. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/utils.py +0 -0
  39. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/utterance/__init__.py +0 -0
  40. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/utterance/cantonese_infer.py +0 -0
  41. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/utterance/dataset.py +0 -0
  42. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/utterance/execute.py +0 -0
  43. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/utterance/infer.py +0 -0
  44. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/utterance/prep.py +0 -0
  45. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/utterance/train.py +0 -0
  46. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/wave2vec/__init__.py +0 -0
  47. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/wave2vec/infer_fa.py +0 -0
  48. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/whisper/__init__.py +0 -0
  49. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/whisper/infer_asr.py +0 -0
  50. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/models/whisper/infer_fa.py +0 -0
  51. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/__init__.py +0 -0
  52. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/analysis/__init__.py +0 -0
  53. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/analysis/eval.py +0 -0
  54. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/__init__.py +0 -0
  55. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2chinese.py +0 -0
  56. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2lang/__init__.py +0 -0
  57. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2lang/deu.py +0 -0
  58. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2lang/ell.py +0 -0
  59. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2lang/eng.py +0 -0
  60. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2lang/eus.py +0 -0
  61. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2lang/fra.py +0 -0
  62. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2lang/hrv.py +0 -0
  63. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2lang/ind.py +0 -0
  64. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2lang/jpn.py +0 -0
  65. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2lang/nld.py +0 -0
  66. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2lang/por.py +0 -0
  67. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2lang/spa.py +0 -0
  68. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/num2lang/tha.py +0 -0
  69. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/oai_whisper.py +0 -0
  70. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/rev.py +0 -0
  71. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/whisper.py +0 -0
  72. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/asr/whisperx.py +0 -0
  73. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/avqi/__init__.py +0 -0
  74. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/avqi/engine.py +0 -0
  75. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/base.py +0 -0
  76. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/cleanup/__init__.py +0 -0
  77. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/cleanup/cleanup.py +0 -0
  78. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/cleanup/disfluencies.py +0 -0
  79. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/cleanup/parse_support.py +0 -0
  80. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/cleanup/retrace.py +0 -0
  81. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/cleanup/support/filled_pauses.eng +0 -0
  82. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/cleanup/support/replacements.eng +0 -0
  83. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/cleanup/support/test.test +0 -0
  84. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/diarization/__init__.py +0 -0
  85. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/diarization/pyannote.py +0 -0
  86. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/dispatch.py +0 -0
  87. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/fa/__init__.py +0 -0
  88. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/fa/wave2vec_fa.py +0 -0
  89. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/fa/whisper_fa.py +0 -0
  90. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/morphosyntax/__init__.py +0 -0
  91. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/morphosyntax/coref.py +0 -0
  92. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/morphosyntax/en/irr.py +0 -0
  93. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/morphosyntax/fr/apm.py +0 -0
  94. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/morphosyntax/fr/apmn.py +0 -0
  95. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/morphosyntax/fr/case.py +0 -0
  96. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/morphosyntax/ja/verbforms.py +0 -0
  97. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/morphosyntax/ud.py +0 -0
  98. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/opensmile/__init__.py +0 -0
  99. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/opensmile/engine.py +0 -0
  100. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/pipeline.py +0 -0
  101. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/speaker/__init__.py +0 -0
  102. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/speaker/nemo_speaker.py +0 -0
  103. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/translate/__init__.py +0 -0
  104. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/translate/gtrans.py +0 -0
  105. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/translate/seamless.py +0 -0
  106. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/translate/utils.py +0 -0
  107. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/utr/__init__.py +0 -0
  108. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/utr/rev_utr.py +0 -0
  109. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/utr/utils.py +0 -0
  110. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/utr/whisper_utr.py +0 -0
  111. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/utterance/__init__.py +0 -0
  112. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/pipelines/utterance/ud_utterance.py +0 -0
  113. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/__init__.py +0 -0
  114. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/conftest.py +0 -0
  115. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/formats/chat/test_chat_file.py +0 -0
  116. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/formats/chat/test_chat_generator.py +0 -0
  117. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/formats/chat/test_chat_lexer.py +0 -0
  118. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/formats/chat/test_chat_parser.py +0 -0
  119. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/formats/chat/test_chat_utils.py +0 -0
  120. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/formats/textgrid/test_textgrid.py +0 -0
  121. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/pipelines/analysis/test_eval.py +0 -0
  122. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/pipelines/asr/test_asr_pipeline.py +0 -0
  123. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/pipelines/asr/test_asr_utils.py +0 -0
  124. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/pipelines/cleanup/test_disfluency.py +0 -0
  125. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/pipelines/cleanup/test_parse_support.py +0 -0
  126. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/pipelines/fa/test_fa_pipeline.py +0 -0
  127. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/pipelines/fixures.py +0 -0
  128. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/pipelines/test_pipeline.py +0 -0
  129. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/pipelines/test_pipeline_models.py +0 -0
  130. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/tests/test_document.py +0 -0
  131. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/utils/__init__.py +0 -0
  132. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/utils/abbrev.py +0 -0
  133. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/utils/compounds.py +0 -0
  134. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/utils/config.py +0 -0
  135. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/utils/dp.py +0 -0
  136. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/utils/names.py +0 -0
  137. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign/utils/utils.py +0 -0
  138. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign.egg-info/SOURCES.txt +0 -0
  139. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign.egg-info/dependency_links.txt +0 -0
  140. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign.egg-info/entry_points.txt +0 -0
  141. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign.egg-info/requires.txt +0 -0
  142. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/batchalign.egg-info/top_level.txt +0 -0
  143. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/setup.cfg +0 -0
  144. {batchalign-0.8.0.post1 → batchalign-0.8.0.post3}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: batchalign
3
- Version: 0.8.0.post1
3
+ Version: 0.8.0.post3
4
4
  Summary: Python Speech Language Sample Analysis
5
5
  Author: Brian MacWhinney, Houjun Liu
6
6
  Author-email: macw@cmu.edu, houjun@cmu.edu
@@ -32,6 +32,7 @@ import time
32
32
  import traceback
33
33
  import logging as L
34
34
  baL = L.getLogger('batchalign')
35
+ import psutil
35
36
 
36
37
  warnings.filterwarnings('ignore', category=UserWarning, message='TypedStorage is deprecated')
37
38
 
@@ -55,6 +56,29 @@ def _worker_task(file_info, command, lang, num_speakers, loader_info, writer_inf
55
56
 
56
57
  file, output = file_info
57
58
  pid = os.getpid()
59
+ rss_start = None
60
+ rss_end = None
61
+ rss_peak = None
62
+
63
+ def _safe_rss():
64
+ try:
65
+ import psutil
66
+ return psutil.Process(pid).memory_info().rss
67
+ except Exception:
68
+ return None
69
+
70
+ def _safe_peak_rss():
71
+ try:
72
+ import resource
73
+ peak = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
74
+ if peak is None:
75
+ return None
76
+ # ru_maxrss is KB on Linux, bytes on macOS; normalize to bytes.
77
+ return int(peak * 1024) if peak < 1024 * 1024 * 1024 else int(peak)
78
+ except Exception:
79
+ return None
80
+
81
+ rss_start = _safe_rss()
58
82
 
59
83
  # Configure logging in this worker process
60
84
  if verbose >= 1:
@@ -70,9 +94,8 @@ def _worker_task(file_info, command, lang, num_speakers, loader_info, writer_inf
70
94
  else:
71
95
  baL.setLevel(logging.DEBUG)
72
96
 
73
- # Only capture output if not in verbose mode
74
- # In verbose mode, let logs stream naturally to the console
75
- should_capture = verbose == 0
97
+ # Always capture output to avoid interleaving with progress rendering.
98
+ should_capture = True
76
99
 
77
100
  if should_capture:
78
101
  # Use a temporary file to capture ALL output at the FD level
@@ -134,8 +157,87 @@ def _worker_task(file_info, command, lang, num_speakers, loader_info, writer_inf
134
157
  doc = pipeline(doc, callback=progress_callback, **kw)
135
158
  CHATFile(doc=doc).write(output, write_wor=kwargs.get("wor", True))
136
159
 
160
+ elif command in ["transcribe", "transcribe_s"]:
161
+ from batchalign.document import CustomLine, CustomLineType
162
+ # For transcribe, the "loader" just passes the file path
163
+ doc = file
164
+
165
+ # Process through pipeline
166
+ doc = pipeline(doc, callback=progress_callback)
167
+
168
+ # Write output with ASR comment
169
+ asr = kwargs.get("asr", "rev")
170
+ with open(Path(__file__).parent.parent / "version", 'r') as df:
171
+ VERSION_NUMBER = df.readline().strip()
172
+ doc.content.insert(0, CustomLine(id="Comment", type=CustomLineType.INDEPENDENT,
173
+ content=f"Batchalign {VERSION_NUMBER}, ASR Engine {asr}. Unchecked output of ASR model."))
174
+ CHATFile(doc=doc).write(output
175
+ .replace(".wav", ".cha")
176
+ .replace(".WAV", ".cha")
177
+ .replace(".mp4", ".cha")
178
+ .replace(".MP4", ".cha")
179
+ .replace(".mp3", ".cha")
180
+ .replace(".MP3", ".cha"),
181
+ write_wor=kwargs.get("wor", False))
182
+
183
+ elif command == "translate":
184
+ cf = CHATFile(path=os.path.abspath(file), special_mor_=True)
185
+ doc = cf.doc
186
+ doc = pipeline(doc, callback=progress_callback)
187
+ CHATFile(doc=doc).write(output)
188
+
189
+ elif command == "utseg":
190
+ doc = CHATFile(path=os.path.abspath(file)).doc
191
+ doc = pipeline(doc, callback=progress_callback)
192
+ CHATFile(doc=doc).write(output)
193
+
194
+ elif command == "coref":
195
+ cf = CHATFile(path=os.path.abspath(file))
196
+ doc = cf.doc
197
+ doc = pipeline(doc, callback=progress_callback)
198
+ CHATFile(doc=doc).write(output)
199
+
200
+ elif command == "benchmark":
201
+ # Find gold transcript
202
+ from pathlib import Path as P
203
+ p = P(file)
204
+ cha = p.with_suffix(".cha")
205
+ if not cha.exists():
206
+ raise FileNotFoundError(f"No gold .cha transcript found for benchmarking. audio: {p.name}, desired cha: {cha.name}, looked in: {str(cha)}")
207
+
208
+ gold_doc = CHATFile(path=str(cha), special_mor_=True).doc
209
+ doc = pipeline(file, callback=progress_callback, gold=gold_doc)
210
+
211
+ # Write benchmark results
212
+ import os
213
+ os.remove(P(output).with_suffix(".cha"))
214
+ with open(P(output).with_suffix(".wer.txt"), 'w') as df:
215
+ df.write(str(doc["wer"]))
216
+ with open(P(output).with_suffix(".diff"), 'w') as df:
217
+ df.write(str(doc["diff"]))
218
+ CHATFile(doc=doc["doc"]).write(str(P(output).with_suffix(".asr.cha")),
219
+ write_wor=kwargs.get("wor", False))
220
+
221
+ elif command == "opensmile":
222
+ from batchalign.document import Document
223
+ doc = Document.new(media_path=file, lang=lang)
224
+ results = pipeline(doc, callback=progress_callback, feature_set=kwargs.get("feature_set", "eGeMAPSv02"))
225
+
226
+ # Write opensmile results
227
+ if results.get('success', False):
228
+ output_csv = Path(output).with_suffix('.opensmile.csv')
229
+ features_df = results.get('features_df')
230
+ if features_df is not None:
231
+ features_df.to_csv(output_csv, header=['value'], index_label='feature')
232
+ else:
233
+ error_file = Path(output).with_suffix('.error.txt')
234
+ with open(error_file, 'w') as f:
235
+ f.write(f"OpenSMILE extraction failed: {results.get('error', 'Unknown error')}\n")
236
+
137
237
  else:
138
238
  loader, writer = loader_info, writer_info
239
+ if loader is None or writer is None:
240
+ raise ValueError(f"Command '{command}' requires loader and writer functions, but they are None. This may indicate an unimplemented command or configuration issue.")
139
241
  doc = loader(os.path.abspath(file))
140
242
  kw = {}
141
243
  if isinstance(doc, tuple) and len(doc) > 1:
@@ -152,7 +254,15 @@ def _worker_task(file_info, command, lang, num_speakers, loader_info, writer_inf
152
254
  else:
153
255
  captured = ""
154
256
 
155
- return file, None, None, captured
257
+ rss_end = _safe_rss()
258
+ rss_peak = _safe_peak_rss()
259
+ mem_info = {
260
+ "pid": pid,
261
+ "rss_start": rss_start,
262
+ "rss_end": rss_end,
263
+ "rss_peak": rss_peak,
264
+ }
265
+ return file, None, None, captured, mem_info
156
266
  except Exception as e:
157
267
  # Flush and read captured output if we were capturing
158
268
  if should_capture:
@@ -162,7 +272,15 @@ def _worker_task(file_info, command, lang, num_speakers, loader_info, writer_inf
162
272
  captured = log_file.read()
163
273
  else:
164
274
  captured = ""
165
- return file, traceback.format_exc(), e, captured
275
+ rss_end = _safe_rss()
276
+ rss_peak = _safe_peak_rss()
277
+ mem_info = {
278
+ "pid": pid,
279
+ "rss_start": rss_start,
280
+ "rss_end": rss_end,
281
+ "rss_peak": rss_peak,
282
+ }
283
+ return file, traceback.format_exc(), e, captured, mem_info
166
284
  finally:
167
285
  # Restore original FDs only if we redirected them
168
286
  if should_capture:
@@ -255,6 +373,7 @@ def _dispatch(command, lang, num_speakers,
255
373
  file_pairs = list(zip(files, outputs))
256
374
  file_pairs.sort(key=lambda fo: os.path.getsize(fo[0]) if os.path.exists(fo[0]) else 0, reverse=True)
257
375
  files, outputs = zip(*file_pairs) if file_pairs else ([], [])
376
+ file_sizes = {f: os.path.getsize(f) if os.path.exists(f) else 0 for f in files}
258
377
 
259
378
  C.print(f"\nMode: [blue]{command}[/blue]; got [bold cyan]{len(files)}[/bold cyan] transcript{'s' if len(files) > 1 else ''} to process from {in_dir}:\n")
260
379
 
@@ -289,8 +408,66 @@ def _dispatch(command, lang, num_speakers,
289
408
  # create the spinner
290
409
  prog = Progress(SpinnerColumn(), *Progress.get_default_columns()[:-1],
291
410
  TimeElapsedColumn(),
292
- TextColumn("[cyan]{task.fields[processor]}[/cyan]"), console=C)
411
+ TextColumn("[magenta]{task.fields[mem]}[/magenta]"),
412
+ TextColumn("[cyan]{task.fields[processor]}[/cyan]"),
413
+ console=C, refresh_per_second=5)
293
414
  errors = []
415
+ mem_records = {}
416
+ mem_samples = []
417
+ last_low_mem_warn = 0.0
418
+
419
+ def _format_bytes(count, precision=2):
420
+ if count is None:
421
+ return "unknown"
422
+ units = ["B", "KB", "MB", "GB", "TB"]
423
+ idx = 0
424
+ size = float(count)
425
+ while size >= 1024 and idx < len(units) - 1:
426
+ size /= 1024
427
+ idx += 1
428
+ if idx == 0:
429
+ return f"{int(size)}{units[idx]}"
430
+ return f"{size:.{precision}f}{units[idx]}"
431
+
432
+ def _mem_label(base, available=None, low_mem=False):
433
+ parts = [base]
434
+ if available is not None:
435
+ parts.append(f"avail {_format_bytes(available, precision=1)}")
436
+ if low_mem:
437
+ parts.append("LOW MEM")
438
+ return " | ".join(parts)
439
+
440
+ def _system_memory():
441
+ try:
442
+ vm = psutil.virtual_memory()
443
+ return vm.total, vm.available
444
+ except Exception:
445
+ return None, None
446
+
447
+ def _memory_reserve(total):
448
+ if total is None:
449
+ return None
450
+ return max(int(total * 0.10), 2 * 1024 * 1024 * 1024)
451
+
452
+ def _estimate_worker_bytes(file_size):
453
+ if not mem_samples:
454
+ return 512 * 1024 * 1024
455
+ ratios = [mem / size for size, mem in mem_samples if size and mem]
456
+ if not ratios:
457
+ return 512 * 1024 * 1024
458
+ ratios.sort()
459
+ median_ratio = ratios[len(ratios) // 2]
460
+ est = int(median_ratio * file_size)
461
+ return max(512 * 1024 * 1024, min(est, 6 * 1024 * 1024 * 1024))
462
+
463
+ def _should_throttle(est_bytes):
464
+ total, available = _system_memory()
465
+ if total is None or available is None:
466
+ return False, total, available
467
+ reserve = _memory_reserve(total)
468
+ if reserve is None:
469
+ return False, total, available
470
+ return (available - est_bytes) < reserve, total, available
294
471
 
295
472
  try:
296
473
  with prog as prog:
@@ -298,8 +475,9 @@ def _dispatch(command, lang, num_speakers,
298
475
  task_totals = {}
299
476
 
300
477
  for f in files:
301
- tasks[f] = prog.add_task(Path(f).name, start=False, total=1, processor="Waiting...")
478
+ tasks[f] = prog.add_task(Path(f).name, start=False, total=1, processor="Waiting...", mem="queued")
302
479
  task_totals[f] = 1
480
+ prog.start_task(tasks[f])
303
481
 
304
482
  def drain_progress_queue():
305
483
  if not progress_queue:
@@ -315,27 +493,76 @@ def _dispatch(command, lang, num_speakers,
315
493
  continue
316
494
  task_total = max(int(total) if total else task_totals.get(file, 1), 1)
317
495
  task_totals[file] = task_total
496
+ total_mem, available_mem = _system_memory()
497
+ reserve = _memory_reserve(total_mem)
498
+ low_mem = False
499
+ if reserve is not None and available_mem is not None:
500
+ low_mem = available_mem < reserve
318
501
  prog.update(tasks[file],
319
502
  total=task_total,
320
503
  completed=min(int(completed), task_total),
321
- processor=render_stage(stage_tasks))
504
+ processor=render_stage(stage_tasks),
505
+ mem=_mem_label("running", available_mem, low_mem))
322
506
 
323
507
  with concurrent.futures.ProcessPoolExecutor(max_workers=num_workers) as executor:
324
508
  worker_func = partial(_worker_task,
325
509
  command=command,
326
510
  lang=lang,
327
511
  num_speakers=num_speakers,
328
- loader_info=None,
329
- writer_info=None,
512
+ loader_info=loader,
513
+ writer_info=writer,
330
514
  progress_queue=progress_queue,
331
515
  verbose=ctx.obj["verbose"],
332
516
  **kwargs)
333
517
 
334
- future_to_file = {executor.submit(worker_func, (f, o)): f for f, o in zip(files, outputs)}
518
+ file_iter = iter(zip(files, outputs))
519
+ future_to_file = {}
520
+
521
+ def submit_one(file_path, output_path):
522
+ future = executor.submit(worker_func, (file_path, output_path))
523
+ future_to_file[future] = file_path
524
+ est_bytes = _estimate_worker_bytes(file_sizes.get(file_path, 0))
525
+ total_mem, available_mem = _system_memory()
526
+ reserve = _memory_reserve(total_mem)
527
+ low_mem = False
528
+ if reserve is not None and available_mem is not None:
529
+ low_mem = available_mem < reserve
530
+ prog.update(
531
+ tasks[file_path],
532
+ processor="Processing...",
533
+ mem=_mem_label(f"est {_format_bytes(est_bytes)}", available_mem, low_mem),
534
+ )
335
535
 
336
- for f in files:
337
- prog.start_task(tasks[f])
338
- prog.update(tasks[f], processor="Processing...")
536
+ def schedule_available():
537
+ nonlocal last_low_mem_warn
538
+ while len(future_to_file) < num_workers:
539
+ try:
540
+ next_file, next_output = next(file_iter)
541
+ except StopIteration:
542
+ break
543
+ est_bytes = _estimate_worker_bytes(file_sizes.get(next_file, 0))
544
+ throttle, total, available = _should_throttle(est_bytes)
545
+ if throttle and future_to_file:
546
+ now = time.time()
547
+ if now - last_low_mem_warn > 10:
548
+ reserve = _memory_reserve(total)
549
+ prog.console.print(
550
+ f"[bold yellow]Low memory[/bold yellow]: "
551
+ f"{_format_bytes(available)} free, "
552
+ f"{_format_bytes(reserve)} reserve. "
553
+ f"Throttling new workers."
554
+ )
555
+ last_low_mem_warn = now
556
+ break
557
+ if throttle and not future_to_file:
558
+ prog.console.print(
559
+ f"[bold yellow]Low memory[/bold yellow]: "
560
+ f"{_format_bytes(available)} free. "
561
+ "Continuing with a single worker."
562
+ )
563
+ submit_one(next_file, next_output)
564
+
565
+ schedule_available()
339
566
 
340
567
  pending = set(future_to_file.keys())
341
568
  while pending:
@@ -348,8 +575,9 @@ def _dispatch(command, lang, num_speakers,
348
575
 
349
576
  for future in done:
350
577
  file = future_to_file[future]
578
+ future_to_file.pop(future, None)
351
579
  try:
352
- res_file, trcbk, e, captured = future.result()
580
+ res_file, trcbk, e, captured, mem_info = future.result()
353
581
  final_total = max(task_totals.get(file, 1), 1)
354
582
  if e:
355
583
  prog.update(tasks[file], total=final_total, completed=final_total, processor="[bold red]FAIL[/bold red]")
@@ -357,12 +585,25 @@ def _dispatch(command, lang, num_speakers,
357
585
  else:
358
586
  prog.update(tasks[file], total=final_total, completed=final_total, processor="[bold green]DONE[/bold green]")
359
587
  if ctx.obj["verbose"] >= 1 and captured.strip():
360
- errors.append((res_file, "Logs only (Success)", None, captured))
588
+ prog.console.print(f"[bold blue]INFO[/bold blue] on file [italic]{Path(file).name}[/italic]:\n{escape(captured.strip())}\n")
589
+ if mem_info:
590
+ mem_records[file] = mem_info
591
+ peak = mem_info.get("rss_peak") or mem_info.get("rss_end")
592
+ if peak:
593
+ mem_samples.append((file_sizes.get(file, 0), peak))
594
+ total_mem, available_mem = _system_memory()
595
+ reserve = _memory_reserve(total_mem)
596
+ low_mem = False
597
+ if reserve is not None and available_mem is not None:
598
+ low_mem = available_mem < reserve
599
+ prog.update(tasks[file], mem=_mem_label(_format_bytes(peak), available_mem, low_mem))
361
600
  except Exception as e:
362
601
  final_total = max(task_totals.get(file, 1), 1)
363
602
  prog.update(tasks[file], total=final_total, completed=final_total, processor="[bold red]FAIL[/bold red]")
364
603
  errors.append((file, traceback.format_exc(), e, ""))
365
604
 
605
+ schedule_available()
606
+ pending = set(future_to_file.keys())
366
607
  drain_progress_queue()
367
608
  finally:
368
609
  if manager:
@@ -386,6 +627,16 @@ def _dispatch(command, lang, num_speakers,
386
627
  else:
387
628
  C.print(f"\nAll done. Results saved to {out_dir}!\n")
388
629
 
630
+ if mem_records and ctx.obj["verbose"] >= 1:
631
+ C.print("\nMemory usage per file (worker RSS peak):")
632
+ for file, info in mem_records.items():
633
+ rel_path = os.path.relpath(str(Path(file).absolute()), in_dir)
634
+ peak = info.get("rss_peak") or info.get("rss_end")
635
+ C.print(f"- {rel_path}: {_format_bytes(peak)}")
636
+ total, available = _system_memory()
637
+ if total is not None and available is not None:
638
+ C.print(f"\nSystem memory available: {_format_bytes(available)} / {_format_bytes(total)}")
639
+
389
640
  if ctx.obj["verbose"] > 1:
390
641
  C.end_capture()
391
642
 
@@ -93,11 +93,15 @@ def retokenize_with_engine(intermediate_output, engine):
93
93
  ----------
94
94
  intermediate_output : List
95
95
  Rev.AI style output.
96
-
96
+
97
97
  engine : UtteranceEngine
98
98
  The utterance Engine to use.
99
99
  """
100
-
100
+
101
+ # Safety check: if engine is None or not callable, fall back to regular retokenize
102
+ if engine is None or not callable(engine):
103
+ return retokenize(intermediate_output)
104
+
101
105
  final_outputs = []
102
106
 
103
107
  for speaker, utterance in intermediate_output:
@@ -0,0 +1,3 @@
1
+ 0.8.0-post.3
2
+ Jan 16th, 2025
3
+ Patch regression?
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: batchalign
3
- Version: 0.8.0.post1
3
+ Version: 0.8.0.post3
4
4
  Summary: Python Speech Language Sample Analysis
5
5
  Author: Brian MacWhinney, Houjun Liu
6
6
  Author-email: macw@cmu.edu, houjun@cmu.edu
@@ -1,3 +0,0 @@
1
- 0.8.0-post.1
2
- Jan 13th, 2025
3
- Speed