batchalign 0.7.22.post2__tar.gz → 0.7.22.post4__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.
Files changed (131) hide show
  1. {batchalign-0.7.22.post2/batchalign.egg-info → batchalign-0.7.22.post4}/PKG-INFO +3 -2
  2. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/cli/cli.py +51 -30
  3. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/asr/utils.py +3 -0
  4. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/avqi/engine.py +53 -37
  5. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/opensmile/engine.py +26 -71
  6. batchalign-0.7.22.post4/batchalign/version +3 -0
  7. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4/batchalign.egg-info}/PKG-INFO +3 -2
  8. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign.egg-info/requires.txt +2 -1
  9. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/setup.py +3 -2
  10. batchalign-0.7.22.post2/batchalign/version +0 -3
  11. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/LICENSE +0 -0
  12. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/MANIFEST.in +0 -0
  13. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/README.md +0 -0
  14. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/__init__.py +0 -0
  15. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/__main__.py +0 -0
  16. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/cli/__init__.py +0 -0
  17. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/cli/dispatch.py +0 -0
  18. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/constants.py +0 -0
  19. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/document.py +0 -0
  20. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/errors.py +0 -0
  21. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/formats/__init__.py +0 -0
  22. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/formats/base.py +0 -0
  23. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/formats/chat/__init__.py +0 -0
  24. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/formats/chat/file.py +0 -0
  25. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/formats/chat/generator.py +0 -0
  26. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/formats/chat/lexer.py +0 -0
  27. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/formats/chat/parser.py +0 -0
  28. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/formats/chat/utils.py +0 -0
  29. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/formats/textgrid/__init__.py +0 -0
  30. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/formats/textgrid/file.py +0 -0
  31. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/formats/textgrid/generator.py +0 -0
  32. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/formats/textgrid/parser.py +0 -0
  33. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/__init__.py +0 -0
  34. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/resolve.py +0 -0
  35. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/speaker/__init__.py +0 -0
  36. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/speaker/config.yaml +0 -0
  37. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/speaker/infer.py +0 -0
  38. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/speaker/utils.py +0 -0
  39. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/training/__init__.py +0 -0
  40. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/training/run.py +0 -0
  41. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/training/utils.py +0 -0
  42. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/utils.py +0 -0
  43. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/utterance/__init__.py +0 -0
  44. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/utterance/cantonese_infer.py +0 -0
  45. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/utterance/dataset.py +0 -0
  46. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/utterance/execute.py +0 -0
  47. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/utterance/infer.py +0 -0
  48. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/utterance/prep.py +0 -0
  49. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/utterance/train.py +0 -0
  50. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/wave2vec/__init__.py +0 -0
  51. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/wave2vec/infer_fa.py +0 -0
  52. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/whisper/__init__.py +0 -0
  53. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/whisper/infer_asr.py +0 -0
  54. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/models/whisper/infer_fa.py +0 -0
  55. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/__init__.py +0 -0
  56. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/analysis/__init__.py +0 -0
  57. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/analysis/eval.py +0 -0
  58. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/asr/__init__.py +0 -0
  59. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/asr/num2chinese.py +0 -0
  60. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/asr/oai_whisper.py +0 -0
  61. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/asr/rev.py +0 -0
  62. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/asr/whisper.py +0 -0
  63. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/asr/whisperx.py +0 -0
  64. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/avqi/__init__.py +0 -0
  65. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/base.py +0 -0
  66. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/cleanup/__init__.py +0 -0
  67. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/cleanup/cleanup.py +0 -0
  68. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/cleanup/disfluencies.py +0 -0
  69. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/cleanup/parse_support.py +0 -0
  70. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/cleanup/retrace.py +0 -0
  71. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/cleanup/support/filled_pauses.eng +0 -0
  72. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/cleanup/support/replacements.eng +0 -0
  73. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/cleanup/support/test.test +0 -0
  74. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/diarization/__init__.py +0 -0
  75. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/diarization/pyannote.py +0 -0
  76. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/dispatch.py +0 -0
  77. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/fa/__init__.py +0 -0
  78. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/fa/wave2vec_fa.py +0 -0
  79. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/fa/whisper_fa.py +0 -0
  80. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/morphosyntax/__init__.py +0 -0
  81. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/morphosyntax/coref.py +0 -0
  82. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/morphosyntax/en/irr.py +0 -0
  83. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/morphosyntax/fr/apm.py +0 -0
  84. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/morphosyntax/fr/apmn.py +0 -0
  85. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/morphosyntax/fr/case.py +0 -0
  86. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/morphosyntax/ja/verbforms.py +0 -0
  87. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/morphosyntax/ud.py +0 -0
  88. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/opensmile/__init__.py +0 -0
  89. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/pipeline.py +0 -0
  90. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/speaker/__init__.py +0 -0
  91. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/speaker/nemo_speaker.py +0 -0
  92. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/translate/__init__.py +0 -0
  93. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/translate/gtrans.py +0 -0
  94. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/translate/seamless.py +0 -0
  95. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/translate/utils.py +0 -0
  96. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/utr/__init__.py +0 -0
  97. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/utr/rev_utr.py +0 -0
  98. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/utr/utils.py +0 -0
  99. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/utr/whisper_utr.py +0 -0
  100. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/utterance/__init__.py +0 -0
  101. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/pipelines/utterance/ud_utterance.py +0 -0
  102. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/__init__.py +0 -0
  103. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/conftest.py +0 -0
  104. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/formats/chat/test_chat_file.py +0 -0
  105. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/formats/chat/test_chat_generator.py +0 -0
  106. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/formats/chat/test_chat_lexer.py +0 -0
  107. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/formats/chat/test_chat_parser.py +0 -0
  108. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/formats/chat/test_chat_utils.py +0 -0
  109. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/formats/textgrid/test_textgrid.py +0 -0
  110. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/pipelines/analysis/test_eval.py +0 -0
  111. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/pipelines/asr/test_asr_pipeline.py +0 -0
  112. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/pipelines/asr/test_asr_utils.py +0 -0
  113. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/pipelines/cleanup/test_disfluency.py +0 -0
  114. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/pipelines/cleanup/test_parse_support.py +0 -0
  115. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/pipelines/fa/test_fa_pipeline.py +0 -0
  116. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/pipelines/fixures.py +0 -0
  117. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/pipelines/test_pipeline.py +0 -0
  118. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/pipelines/test_pipeline_models.py +0 -0
  119. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/tests/test_document.py +0 -0
  120. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/utils/__init__.py +0 -0
  121. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/utils/abbrev.py +0 -0
  122. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/utils/compounds.py +0 -0
  123. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/utils/config.py +0 -0
  124. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/utils/dp.py +0 -0
  125. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/utils/names.py +0 -0
  126. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign/utils/utils.py +0 -0
  127. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign.egg-info/SOURCES.txt +0 -0
  128. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign.egg-info/dependency_links.txt +0 -0
  129. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign.egg-info/entry_points.txt +0 -0
  130. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/batchalign.egg-info/top_level.txt +0 -0
  131. {batchalign-0.7.22.post2 → batchalign-0.7.22.post4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batchalign
3
- Version: 0.7.22.post2
3
+ Version: 0.7.22.post4
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
@@ -40,9 +40,10 @@ Requires-Dist: googletrans
40
40
  Requires-Dist: openai-whisper
41
41
  Requires-Dist: llvmlite>=0.44.0
42
42
  Requires-Dist: praat-parselmouth==0.4.6
43
- Requires-Dist: opensmile>=2.3.0
43
+ Requires-Dist: opensmile>=2.5.0
44
44
  Requires-Dist: pyannote.audio
45
45
  Requires-Dist: onnxruntime
46
+ Requires-Dist: certifi>=2025.10.5
46
47
  Provides-Extra: dev
47
48
  Requires-Dist: pytest; extra == "dev"
48
49
  Provides-Extra: train
@@ -361,46 +361,67 @@ def benchmark(ctx, in_dir, out_dir, lang, num_speakers, whisper, **kwargs):
361
361
  #################### AVQI ################################
362
362
 
363
363
  @batchalign.command()
364
- @click.argument("cs_file", type=click.Path(exists=True, file_okay=True))
365
- @click.argument("sv_file", type=click.Path(exists=True, file_okay=True))
364
+ @click.argument("input_dir", type=click.Path(exists=True, file_okay=False))
365
+ @click.argument("output_dir", type=click.Path(exists=True, file_okay=False))
366
366
  @click.option("--lang",
367
367
  help="sample language in three-letter ISO 3166-1 alpha-3 code",
368
- show_default=True,
369
- default="eng",
370
- type=str)
368
+ show_default=True, default="eng", type=str)
371
369
  @click.pass_context
372
- def avqi(ctx, cs_file, sv_file, lang, **kwargs):
373
- """Calculate Acoustic Voice Quality Index (AVQI) from continuous speech and sustained vowel audio files."""
374
-
375
- # Import AVQI engine
370
+ def avqi(ctx, input_dir, output_dir, lang, **kwargs):
371
+ """Calculate AVQI from paired .cs and .sv audio files in input directory."""
372
+
376
373
  from batchalign.pipelines.avqi import AVQIEngine
374
+ from pathlib import Path
375
+ import os
376
+
377
+ # Get all .cs files
378
+ cs_files = []
379
+ for root, dirs, files in os.walk(input_dir):
380
+ for f in files:
381
+ if '.cs.' in f and any(f.endswith(ext) for ext in ['.mp3', '.wav', '.mp4']):
382
+ cs_files.append(os.path.join(root, f))
377
383
 
378
- # Get output file path (same directory as cs_file, with .avqi.txt extension)
379
- cs_path = Path(cs_file)
380
- output_file = cs_path.with_suffix('.avqi.txt')
384
+ if not cs_files:
385
+ C.print("[bold red]No .cs audio files found in input directory[/bold red]")
386
+ return
381
387
 
382
- # Create AVQI engine
383
- avqi_engine = AVQIEngine()
388
+ C.print(f"\nMode: [blue]avqi[/blue]; got [bold cyan]{len(cs_files)}[/bold cyan] file pair{'s' if len(cs_files) > 1 else ''} to process from {input_dir}:\n")
384
389
 
385
- try:
386
- # Calculate AVQI
387
- C.print(f"\n[blue]Calculating AVQI[/blue] for:")
388
- C.print(f" Continuous Speech: [cyan]{cs_file}[/cyan]")
389
- C.print(f" Sustained Vowel: [cyan]{sv_file}[/cyan]")
390
- C.print(f" Language: [cyan]{lang}[/cyan]")
391
- C.print(f" Output: [cyan]{output_file}[/cyan]\n")
390
+ engine = AVQIEngine()
391
+
392
+ for cs_file in cs_files:
393
+ cs_path = Path(cs_file)
394
+ C.print(f"Processing: [cyan]{cs_path.name}[/cyan]")
392
395
 
393
- results = avqi_engine.analyze(cs_file, sv_file, str(output_file), lang)
396
+ doc = Document.new(media_path=cs_file, lang=lang)
397
+ results = engine.analyze(doc)
394
398
 
395
- C.print(f"[bold green]✓ AVQI calculation completed![/bold green]")
396
- C.print(f"[bold]AVQI Score: {results['avqi']:.3f}[/bold]")
397
- C.print(f"Results saved to: [cyan]{output_file}[/cyan]\n")
399
+ # Create output path
400
+ rel_path = os.path.relpath(cs_file, input_dir)
401
+ output_path = Path(os.path.join(output_dir, rel_path))
402
+ os.makedirs(output_path.parent, exist_ok=True)
398
403
 
399
- except Exception as e:
400
- C.print(f"[bold red]ERROR[/bold red]: {str(e)}")
401
- if ctx.obj["verbose"] > 0:
402
- import traceback
403
- C.print(traceback.format_exc())
404
+ if results.get('success', False):
405
+ output_txt = output_path.with_suffix('.avqi.txt')
406
+ with open(output_txt, 'w') as f:
407
+ f.write(f"AVQI: {results['avqi']:.3f}\n")
408
+ f.write(f"CPPS: {results['cpps']:.3f}\n")
409
+ f.write(f"HNR: {results['hnr']:.3f}\n")
410
+ f.write(f"Shimmer Local: {results['shimmer_local']:.3f}\n")
411
+ f.write(f"Shimmer Local dB: {results['shimmer_local_db']:.3f}\n")
412
+ f.write(f"LTAS Slope: {results['slope']:.3f}\n")
413
+ f.write(f"LTAS Tilt: {results['tilt']:.3f}\n")
414
+ f.write(f"CS File: {results['cs_file']}\n")
415
+ f.write(f"SV File: {results['sv_file']}\n")
416
+ f.write(f"Language: {lang}\n")
417
+ C.print(f" [bold green]✓[/bold green] AVQI: {results['avqi']:.3f} → {output_txt.name}")
418
+ else:
419
+ error_file = output_path.with_suffix('.error.txt')
420
+ with open(error_file, 'w') as f:
421
+ f.write(f"AVQI calculation failed: {results.get('error', 'Unknown error')}\n")
422
+ C.print(f" [bold red]✗[/bold red] Failed: {results.get('error', 'Unknown error')}")
423
+
424
+ C.print(f"\nAll done. Results saved to {output_dir}!\n")
404
425
 
405
426
  #################### OPENSMILE ################################
406
427
 
@@ -267,6 +267,9 @@ def process_generation(output, lang="eng", utterance_engine=None):
267
267
  continue
268
268
  if word not in ENDING_PUNCT+MOR_PUNCT+["؟", "۔", "،", "؛"]:
269
269
  word_replaced = word
270
+
271
+ for i in ENDING_PUNCT+MOR_PUNCT+["؟", "۔", "،", "؛"]:
272
+ word_replaced = word_replaced.replace(i, "")
270
273
 
271
274
  if start == None or end == None:
272
275
  words.append(Form(text=word_replaced, time=None))
@@ -13,7 +13,7 @@ from pathlib import Path
13
13
  import logging
14
14
 
15
15
  from batchalign.pipelines.base import BatchalignEngine
16
- from batchalign.document import Task
16
+ from batchalign.document import Task, Document
17
17
 
18
18
 
19
19
  L = logging.getLogger('batchalign')
@@ -21,15 +21,15 @@ L = logging.getLogger('batchalign')
21
21
 
22
22
  class AVQIEngine(BatchalignEngine):
23
23
  """Engine for calculating Acoustic Voice Quality Index (AVQI)."""
24
-
24
+
25
25
  def __init__(self):
26
26
  super().__init__()
27
27
  self._tasks = [Task.FEATURE_EXTRACT]
28
-
28
+
29
29
  @property
30
30
  def tasks(self):
31
31
  return self._tasks
32
-
32
+
33
33
  def extract_voiced_segments(self, sound):
34
34
  """Extract voiced segments from audio."""
35
35
  original = call(sound, "Copy", "original")
@@ -198,34 +198,57 @@ class AVQIEngine(BatchalignEngine):
198
198
  "slope": slope,
199
199
  "tilt": tilt,
200
200
  }
201
-
202
- def analyze(self, cs_file: str, sv_file: str, output_file: str, lang: str = 'eng', **kwargs) -> Dict:
201
+
202
+ def analyze(self, doc: Document, **kwargs) -> Dict:
203
203
  """
204
204
  Analyze audio files and calculate AVQI.
205
205
 
206
+ Expects the document to have a .cs audio file, and looks for matching .sv file.
207
+
206
208
  Parameters
207
209
  ----------
208
- cs_file : str
209
- Path to continuous speech audio file
210
- sv_file : str
211
- Path to sustained vowel audio file
212
- output_file : str
213
- Path to output file
214
- lang : str
215
- Language code (default: 'eng')
210
+ doc : Document
211
+ Document with .cs media attached
212
+ **kwargs : dict
213
+ Additional arguments
216
214
 
217
215
  Returns
218
216
  -------
219
217
  Dict
220
218
  Dictionary containing AVQI score and features
221
219
  """
222
- L.info(f"Calculating AVQI for CS: {cs_file}, SV: {sv_file}")
223
220
 
221
+ if not doc.media or not doc.media.url:
222
+ return {
223
+ 'error': 'Document has no media attached',
224
+ 'success': False
225
+ }
226
+
227
+ cs_file = doc.media.url
228
+ cs_path = Path(cs_file)
229
+
230
+ # Check if this is a .cs file
231
+ if not '.cs.' in cs_path.name:
232
+ return {
233
+ 'error': f'Expected .cs audio file, got {cs_path.name}',
234
+ 'success': False
235
+ }
236
+
237
+ # Find matching .sv file by replacing .cs. with .sv.
238
+ sv_name = cs_path.name.replace('.cs.', '.sv.')
239
+ sv_file = cs_path.parent / sv_name
240
+
241
+ if not sv_file.exists():
242
+ return {
243
+ 'error': f'Matching .sv file not found: {sv_name}',
244
+ 'success': False
245
+ }
246
+
247
+ L.info(f"Calculating AVQI for CS: {cs_path.name}, SV: {sv_name}")
248
+
224
249
  try:
225
- # Calculate AVQI using the proper algorithm
226
- avqi_score, features = self.calculate_avqi_features(cs_file, sv_file)
227
-
228
- # Prepare results
250
+ avqi_score, features = self.calculate_avqi_features(str(cs_file), str(sv_file))
251
+
229
252
  results = {
230
253
  'avqi': avqi_score,
231
254
  'cpps': features['cpps'],
@@ -233,26 +256,17 @@ class AVQIEngine(BatchalignEngine):
233
256
  'shimmer_local': features['shimmer_local'],
234
257
  'shimmer_local_db': features['shimmer_local_db'],
235
258
  'slope': features['slope'],
236
- 'tilt': features['tilt']
259
+ 'tilt': features['tilt'],
260
+ 'cs_file': cs_path.name,
261
+ 'sv_file': sv_name,
262
+ 'success': True
237
263
  }
238
-
239
- # Write results to file
240
- with open(output_file, 'w') as f:
241
- f.write(f"AVQI: {avqi_score:.3f}\n")
242
- f.write(f"CPPS: {features['cpps']:.3f}\n")
243
- f.write(f"HNR: {features['hnr']:.3f}\n")
244
- f.write(f"Shimmer Local: {features['shimmer_local']:.3f}\n")
245
- f.write(f"Shimmer Local dB: {features['shimmer_local_db']:.3f}\n")
246
- f.write(f"LTAS Slope: {features['slope']:.3f}\n")
247
- f.write(f"LTAS Tilt: {features['tilt']:.3f}\n")
248
- f.write(f"Language: {lang}\n")
249
-
250
- L.info(f"AVQI results written to: {output_file}")
264
+
265
+ L.info(f"AVQI Score: {avqi_score:.3f}")
251
266
  return results
252
-
267
+
253
268
  except Exception as e:
254
269
  L.error(f"Error calculating AVQI: {e}")
255
- # Return default values on error
256
270
  return {
257
271
  'avqi': 0.0,
258
272
  'cpps': 0.0,
@@ -260,5 +274,7 @@ class AVQIEngine(BatchalignEngine):
260
274
  'shimmer_local': 0.0,
261
275
  'shimmer_local_db': 0.0,
262
276
  'slope': 0.0,
263
- 'tilt': 0.0
264
- }
277
+ 'tilt': 0.0,
278
+ 'error': str(e),
279
+ 'success': False
280
+ }
@@ -1,14 +1,14 @@
1
1
  """
2
- OpenSMILE Engine for Batchalign2 - M1 Mac Compatible Version
2
+ OpenSMILE Engine for Batchalign2
3
3
  Audio feature extraction using the openSMILE toolkit
4
4
  """
5
5
 
6
6
  import opensmile
7
+ from opensmile import FeatureSet, FeatureLevel
7
8
  import pandas as pd
8
9
  from pathlib import Path
9
10
  import logging
10
- from typing import Dict, Optional
11
- import platform
11
+ from typing import Dict
12
12
 
13
13
  from batchalign.pipelines.base import BatchalignEngine
14
14
  from batchalign.document import Task, TaskType, Document
@@ -18,6 +18,14 @@ L = logging.getLogger('batchalign')
18
18
  class OpenSMILEEngine(BatchalignEngine):
19
19
  """Engine for extracting openSMILE audio features."""
20
20
 
21
+ # Map string names to FeatureSet enums
22
+ FEATURE_SET_MAP = {
23
+ 'eGeMAPSv02': FeatureSet.eGeMAPSv02,
24
+ 'eGeMAPSv01b': FeatureSet.eGeMAPSv01b,
25
+ 'GeMAPSv01b': FeatureSet.GeMAPSv01b,
26
+ 'ComParE_2016': FeatureSet.ComParE_2016,
27
+ }
28
+
21
29
  def __init__(self, feature_set: str = 'eGeMAPSv02',
22
30
  feature_level: str = 'functionals'):
23
31
  super().__init__()
@@ -26,20 +34,15 @@ class OpenSMILEEngine(BatchalignEngine):
26
34
  self.feature_set = feature_set
27
35
  self.feature_level = feature_level
28
36
 
29
- self.is_m1_mac = (platform.system() == 'Darwin' and
30
- platform.processor() == 'arm')
31
-
32
37
  try:
33
- if self.is_m1_mac:
34
- L.info("M1 Mac detected - using default openSMILE configuration")
35
- self.smile = opensmile.Smile()
36
- self._requested_feature_set = feature_set
37
- else:
38
- self.smile = opensmile.Smile(
39
- feature_set=feature_set,
40
- feature_level=feature_level,
41
- )
42
- L.debug(f"OpenSMILE initialized (M1 compatibility mode: {self.is_m1_mac})")
38
+ feature_set_enum = self.FEATURE_SET_MAP.get(feature_set, FeatureSet.eGeMAPSv02)
39
+ feature_level_enum = FeatureLevel.Functionals if feature_level == 'functionals' else FeatureLevel.LowLevelDescriptors
40
+
41
+ self.smile = opensmile.Smile(
42
+ feature_set=feature_set_enum,
43
+ feature_level=feature_level_enum,
44
+ )
45
+ L.debug(f"OpenSMILE initialized with {feature_set}")
43
46
  except Exception as e:
44
47
  L.error(f"Failed to initialize openSMILE: {e}")
45
48
  raise
@@ -48,13 +51,12 @@ class OpenSMILEEngine(BatchalignEngine):
48
51
  def tasks(self):
49
52
  return self._tasks
50
53
 
51
- def analyze(self, doc: Document, feature_set: str = None, **kwargs) -> Dict:
54
+ def analyze(self, doc: Document, **kwargs) -> Dict:
52
55
  """
53
56
  Extract openSMILE features from Document.
54
57
 
55
58
  Args:
56
59
  doc: Document with media attached
57
- feature_set: Feature set to use (ignored on M1 Mac)
58
60
  **kwargs: Additional arguments
59
61
 
60
62
  Returns:
@@ -69,32 +71,9 @@ class OpenSMILEEngine(BatchalignEngine):
69
71
 
70
72
  actual_audio_path = doc.media.url
71
73
 
72
- if feature_set and feature_set != self.feature_set:
73
- if self.is_m1_mac:
74
- L.warning(f"Feature set switching not supported on M1 Mac - using default features instead of {feature_set}")
75
- else:
76
- L.info(f"Switching feature set from {self.feature_set} to {feature_set}")
77
- try:
78
- self.feature_set = feature_set
79
- self.smile = opensmile.Smile(
80
- feature_set=feature_set,
81
- feature_level=self.feature_level,
82
- )
83
- except Exception as e:
84
- L.error(f"Failed to switch to feature set {feature_set}: {e}")
85
- return {
86
- 'feature_set': self.feature_set,
87
- 'num_features': 0,
88
- 'error': f"Feature set switch failed: {str(e)}",
89
- 'success': False
90
- }
91
-
92
74
  try:
93
75
  L.info(f"Extracting features from: {Path(actual_audio_path).name}")
94
- if self.is_m1_mac:
95
- L.info("Using M1-compatible default feature set (eGeMAPSv02 equivalent)")
96
- else:
97
- L.info(f"Using {self.feature_set} feature set")
76
+ L.info(f"Using {self.feature_set} feature set")
98
77
 
99
78
  features_df = self.smile.process_file(actual_audio_path)
100
79
 
@@ -110,26 +89,17 @@ class OpenSMILEEngine(BatchalignEngine):
110
89
  if duration_segments > 0:
111
90
  first_row_features = features_df.iloc[0].to_dict()
112
91
 
113
- actual_feature_set = self.feature_set
114
- if self.is_m1_mac:
115
- actual_feature_set = "M1-default (eGeMAPSv02-like)"
116
-
117
92
  results = {
118
- 'feature_set': actual_feature_set,
93
+ 'feature_set': self.feature_set,
119
94
  'feature_level': self.feature_level,
120
95
  'num_features': num_features,
121
96
  'duration_segments': duration_segments,
122
97
  'audio_file': str(actual_audio_path),
123
98
  'features_sample': first_row_features,
124
99
  'success': True,
125
- 'm1_compatibility_mode': self.is_m1_mac,
126
100
  'features_df': results_df,
127
101
  }
128
102
 
129
- if self.is_m1_mac and hasattr(self, '_requested_feature_set'):
130
- results['requested_feature_set'] = self._requested_feature_set
131
- results['warning'] = f"M1 Mac compatibility: used default features instead of {self._requested_feature_set}"
132
-
133
103
  L.info(f"Successfully extracted {num_features} features from {duration_segments} segments")
134
104
  return results
135
105
 
@@ -142,30 +112,15 @@ class OpenSMILEEngine(BatchalignEngine):
142
112
  'duration_segments': 0,
143
113
  'audio_file': str(actual_audio_path),
144
114
  'error': str(e),
145
- 'success': False,
146
- 'm1_compatibility_mode': self.is_m1_mac
115
+ 'success': False
147
116
  }
148
117
 
149
118
  def get_available_feature_sets(self) -> list:
150
- """Return list of available feature sets (limited on M1 Mac)."""
151
- if self.is_m1_mac:
152
- return ['M1-default (eGeMAPSv02-like)']
153
- return [
154
- 'eGeMAPSv02',
155
- 'eGeMAPSv01b',
156
- 'GeMAPSv01b',
157
- 'ComParE_2016'
158
- ]
119
+ """Return list of available feature sets."""
120
+ return list(self.FEATURE_SET_MAP.keys())
159
121
 
160
122
  def get_feature_set_info(self, feature_set: str) -> dict:
161
123
  """Get information about a specific feature set."""
162
- if self.is_m1_mac:
163
- return {
164
- 'description': 'M1 Mac compatible default feature set (similar to eGeMAPSv02)',
165
- 'num_features': 'Variable',
166
- 'recommended_for': 'General audio analysis on Apple Silicon'
167
- }
168
-
169
124
  info = {
170
125
  'eGeMAPSv02': {
171
126
  'description': 'Extended Geneva Minimalistic Acoustic Parameter Set v02',
@@ -188,4 +143,4 @@ class OpenSMILEEngine(BatchalignEngine):
188
143
  'recommended_for': 'Comprehensive analysis (large feature space)'
189
144
  }
190
145
  }
191
- return info.get(feature_set, {'description': 'Unknown feature set', 'num_features': 'Unknown'})
146
+ return info.get(feature_set, {'description': 'Unknown feature set', 'num_features': 'Unknown'})
@@ -0,0 +1,3 @@
1
+ 0.7.22-post.4
2
+ October 17th, 2025
3
+ Bump certs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batchalign
3
- Version: 0.7.22.post2
3
+ Version: 0.7.22.post4
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
@@ -40,9 +40,10 @@ Requires-Dist: googletrans
40
40
  Requires-Dist: openai-whisper
41
41
  Requires-Dist: llvmlite>=0.44.0
42
42
  Requires-Dist: praat-parselmouth==0.4.6
43
- Requires-Dist: opensmile>=2.3.0
43
+ Requires-Dist: opensmile>=2.5.0
44
44
  Requires-Dist: pyannote.audio
45
45
  Requires-Dist: onnxruntime
46
+ Requires-Dist: certifi>=2025.10.5
46
47
  Provides-Extra: dev
47
48
  Requires-Dist: pytest; extra == "dev"
48
49
  Provides-Extra: train
@@ -30,9 +30,10 @@ googletrans
30
30
  openai-whisper
31
31
  llvmlite>=0.44.0
32
32
  praat-parselmouth==0.4.6
33
- opensmile>=2.3.0
33
+ opensmile>=2.5.0
34
34
  pyannote.audio
35
35
  onnxruntime
36
+ certifi>=2025.10.5
36
37
 
37
38
  [dev]
38
39
  pytest
@@ -62,9 +62,10 @@ setup(
62
62
  "openai-whisper",
63
63
  "llvmlite>=0.44.0",
64
64
  "praat-parselmouth==0.4.6",
65
- "opensmile>=2.3.0",
65
+ "opensmile>=2.5.0",
66
66
  "pyannote.audio",
67
- "onnxruntime"
67
+ "onnxruntime",
68
+ "certifi>=2025.10.5"
68
69
  ],
69
70
  extras_require={
70
71
  'dev': [
@@ -1,3 +0,0 @@
1
- 0.7.22-post.2
2
- October 15th, 2025
3
- Fix abbreviations