taters 0.1.0__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.
- taters-0.1.0/LICENSE +21 -0
- taters-0.1.0/PKG-INFO +161 -0
- taters-0.1.0/README.MD +131 -0
- taters-0.1.0/pyproject.toml +72 -0
- taters-0.1.0/setup.cfg +4 -0
- taters-0.1.0/src/taters/Taters.py +115 -0
- taters-0.1.0/src/taters/__init__.py +0 -0
- taters-0.1.0/src/taters/audio/__init__.py +0 -0
- taters-0.1.0/src/taters/audio/convert_to_wav.py +112 -0
- taters-0.1.0/src/taters/audio/diarize_with_thirdparty.py +5 -0
- taters-0.1.0/src/taters/audio/diarizer/__init__.py +0 -0
- taters-0.1.0/src/taters/audio/diarizer/whisper-diarization/__init__.py +0 -0
- taters-0.1.0/src/taters/audio/diarizer/whisper-diarization/diarization/__init__.py +3 -0
- taters-0.1.0/src/taters/audio/diarizer/whisper-diarization/diarization/msdd/msdd.py +100 -0
- taters-0.1.0/src/taters/audio/diarizer/whisper-diarization/diarize.py +247 -0
- taters-0.1.0/src/taters/audio/diarizer/whisper-diarization/diarize_custom.py +374 -0
- taters-0.1.0/src/taters/audio/diarizer/whisper-diarization/diarize_parallel.py +269 -0
- taters-0.1.0/src/taters/audio/diarizer/whisper-diarization/helpers.py +552 -0
- taters-0.1.0/src/taters/audio/diarizer/whisper_diar_wrapper.py +293 -0
- taters-0.1.0/src/taters/audio/extract_wav_from_video.py +177 -0
- taters-0.1.0/src/taters/audio/extract_whisper_embeddings.py +243 -0
- taters-0.1.0/src/taters/audio/extract_whisper_embeddings_subproc.py +574 -0
- taters-0.1.0/src/taters/audio/split_wav_by_speaker.py +182 -0
- taters-0.1.0/src/taters/helpers/__init__.py +0 -0
- taters-0.1.0/src/taters/helpers/feature_gather.py +349 -0
- taters-0.1.0/src/taters/helpers/find_files.py +222 -0
- taters-0.1.0/src/taters/helpers/text_gather.py +520 -0
- taters-0.1.0/src/taters/pipelines/__init__.py +0 -0
- taters-0.1.0/src/taters/pipelines/run_pipeline.py +384 -0
- taters-0.1.0/src/taters/text/analyze_with_archetypes.py +238 -0
- taters-0.1.0/src/taters/text/analyze_with_dictionaries.py +246 -0
- taters-0.1.0/src/taters/text/dictionary_analyzers/__init__.py +0 -0
- taters-0.1.0/src/taters/text/dictionary_analyzers/multi_archetype_analyzer.py +125 -0
- taters-0.1.0/src/taters/text/dictionary_analyzers/multi_dict_analyzer.py +254 -0
- taters-0.1.0/src/taters/text/extract_sentence_embeddings.py +329 -0
- taters-0.1.0/src/taters/video/__init__.py +0 -0
- taters-0.1.0/src/taters/video/extract_features.py +0 -0
- taters-0.1.0/src/taters/video/features_basic.py +0 -0
- taters-0.1.0/src/taters/video/features_motion.py +0 -0
- taters-0.1.0/src/taters/video/features_ocr.py +0 -0
- taters-0.1.0/src/taters/video/features_shots.py +0 -0
- taters-0.1.0/src/taters/video/read_video.py +0 -0
- taters-0.1.0/src/taters/video/windowing.py +0 -0
- taters-0.1.0/src/taters.egg-info/PKG-INFO +161 -0
- taters-0.1.0/src/taters.egg-info/SOURCES.txt +48 -0
- taters-0.1.0/src/taters.egg-info/dependency_links.txt +1 -0
- taters-0.1.0/src/taters.egg-info/entry_points.txt +2 -0
- taters-0.1.0/src/taters.egg-info/not-zip-safe +1 -0
- taters-0.1.0/src/taters.egg-info/requires.txt +14 -0
- taters-0.1.0/src/taters.egg-info/top_level.txt +1 -0
taters-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ryan L. Boyd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
taters-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: taters
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Analyze, process, and extract from many types of input data. Highly modular/customizable.
|
|
5
|
+
Author-email: "Ryan L. Boyd" <ryan@ryanboyd.io>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ryanboyd/taters
|
|
8
|
+
Project-URL: Issues, https://github.com/ryanboyd/taters/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: faster-whisper>=1.1.0
|
|
18
|
+
Requires-Dist: transformers>=4.38.0
|
|
19
|
+
Requires-Dist: librosa>=0.10.1
|
|
20
|
+
Requires-Dist: pydub>=0.25.1
|
|
21
|
+
Requires-Dist: contentcoder
|
|
22
|
+
Requires-Dist: archetyper
|
|
23
|
+
Requires-Dist: nltk
|
|
24
|
+
Requires-Dist: sentence-transformers
|
|
25
|
+
Provides-Extra: diarization
|
|
26
|
+
Requires-Dist: nemo-toolkit[asr]>=2.dev; extra == "diarization"
|
|
27
|
+
Provides-Extra: cuda
|
|
28
|
+
Requires-Dist: nvidia-cudnn-cu12; extra == "cuda"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# TATERS — Takes All Things, Extracts Relevant Stuff
|
|
32
|
+
|
|
33
|
+
Taters is a broad-scope toolkit for researchers that can be used to extract features from multiple types of data (video, audio, text) into clean, analysis-ready artifacts and features. Think of it as a small, dependable kitchen crew for your data: you bring potatoes (files), it handles the peeling, chopping, and plating.
|
|
34
|
+
|
|
35
|
+
**Status:** active WIP. It works today, but expect some rough edges and breaking changes as the project grows.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## What Taters is (and isn't)
|
|
40
|
+
|
|
41
|
+
Taters is **a library and a CLI** for end-to-end A/V + text processing with predictable outputs. It's **not** a monolithic "black box" pipeline — each step is a clear, reusable function you can run on its own or string together with YAML presets.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## What you can do with it (high level)
|
|
46
|
+
|
|
47
|
+
Note: everything below is currently implemented, but is highly subject to change as the project evolves.
|
|
48
|
+
|
|
49
|
+
* **Pull audio from video**: extract one or more WAV streams from containers.
|
|
50
|
+
* **Diarize + transcribe**: wrap a proven third-party stack to produce per-recording CSV/SRT/TXT.
|
|
51
|
+
* **Per-speaker WAVs**: build one WAV per speaker from a transcript CSV.
|
|
52
|
+
* **Embeddings**
|
|
53
|
+
|
|
54
|
+
* **Whisper encoder embeddings** (segment-level from a transcript or general audio without one).
|
|
55
|
+
* **Sentence embeddings** (mean per row) for any text dataset.
|
|
56
|
+
* **Text gatherer**: stream CSVs or folders of `.txt` into a single “analysis-ready” CSV, with optional grouping.
|
|
57
|
+
* **Feature extraction**
|
|
58
|
+
|
|
59
|
+
* **Dictionary coding** across any number of ContentCoder dictionaries → one wide CSV with stable column order.
|
|
60
|
+
* **Archetype scoring** with sentence-transformers → tidy, fixed columns.
|
|
61
|
+
* **Predictable outputs**: if you don't specify a path, Taters writes to `./features/<kind>/<filename>.csv`, where `<filename>` reflects how the text was gathered (e.g., grouped vs. concatenated).
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## How you'll use it
|
|
66
|
+
|
|
67
|
+
### Python (quick sketch)
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from taters import Taters
|
|
71
|
+
t = Taters()
|
|
72
|
+
|
|
73
|
+
# 1) Audio from video
|
|
74
|
+
wavs = t.audio.extract_wavs_from_video(input_path="input.mp4")
|
|
75
|
+
|
|
76
|
+
# 2) Diarize (CSV/SRT/TXT)
|
|
77
|
+
diar = t.audio.diarize_with_thirdparty(audio_path=wavs[0], device="cuda")
|
|
78
|
+
|
|
79
|
+
# 3) Features (defaults write under ./features/<kind>/)
|
|
80
|
+
t.audio.extract_whisper_embeddings(source_wav=wavs[0], transcript_csv=diar["csv"])
|
|
81
|
+
t.text.analyze_with_dictionaries(csv_path=diar["csv"], dict_paths=["dicts/LIWC-22.dicx"])
|
|
82
|
+
t.text.analyze_with_archetypes(csv_path=diar["csv"], archetype_csvs=["archetypes/Resilience.csv"])
|
|
83
|
+
t.text.extract_sentence_embeddings(csv_path=diar["csv"], text_cols=["text"], id_cols=["speaker"], group_by=["speaker"])
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### CLI (quick sketch)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Diarize
|
|
90
|
+
python -m taters.audio.diarize_with_thirdparty \
|
|
91
|
+
--audio_path audio/session.wav --device cuda
|
|
92
|
+
|
|
93
|
+
# Whisper embeddings (general audio; non-silent spans + mean pool)
|
|
94
|
+
python -m taters.audio.extract_whisper_embeddings \
|
|
95
|
+
--source_wav audio/session.wav --strategy nonsilent --aggregate mean
|
|
96
|
+
|
|
97
|
+
# Gather text from CSV (auto names the output if --out omitted)
|
|
98
|
+
python -m taters.helpers.text_gather \
|
|
99
|
+
--csv transcripts/session.csv --text-col text --group-by speaker --delimiter ,
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Pipelines (do it all at once)
|
|
103
|
+
|
|
104
|
+
Presets live in YAML (e.g., `taters/pipelines/presets/`). Point at a folder, choose a preset, and Taters will run the steps in order—using each step's output as the next step's input. You can override variables (like models, device, overwrite behavior) on the command line.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Install (tidy version)
|
|
109
|
+
|
|
110
|
+
Use a fresh virtual environment. Seriously, a fresh virtual environment is strongly recommended.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
python -m venv venv-taters
|
|
114
|
+
source venv-taters/bin/activate
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Quick path (when available)
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pip install "taters[diarization,cuda]"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Then install the three git extras used by the diarization wrapper:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
pip install git+https://github.com/MahmoudAshraf97/demucs.git
|
|
127
|
+
pip install git+https://github.com/oliverguhr/deepmultilingualpunctuation.git
|
|
128
|
+
pip install git+https://github.com/MahmoudAshraf97/ctc-forced-aligner.git
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Install PyTorch built for **CUDA 12.4** (the stack ChopShop targets):
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
pip install --force-reinstall --no-cache-dir \
|
|
135
|
+
torch==2.6.0 torchvision==0.21.0 torchaudio==2.6.0 \
|
|
136
|
+
--index-url https://download.pytorch.org/whl/cu124
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
And ensure **FFmpeg** is on your `PATH` (Ubuntu: `sudo apt-get install ffmpeg`, macOS: `brew install ffmpeg`).
|
|
140
|
+
|
|
141
|
+
> Tip: If you hit CUDA/cuDNN loader errors, it usually means your runtime and wheel builds don't match. Keep CUDA **12.4**, `cu124` wheels, and cuDNN 9 aligned.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Roadmap (short)
|
|
146
|
+
|
|
147
|
+
* More feature families
|
|
148
|
+
* More obviously composable pipelines (per-item + global phases, manifests, post-run aggregation).
|
|
149
|
+
* Rich gatherers/aggregators to unify outputs across large runs.
|
|
150
|
+
* Clear docs, examples, and ready-to-run presets.
|
|
151
|
+
|
|
152
|
+
If you try Taters on a real project, feedback on your flow and pain points is incredibly helpful.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## License & credits
|
|
157
|
+
|
|
158
|
+
MIT license. Built on top of excellent open-source projects (Faster-Whisper, sentence-transformers, ContentCoder, and an incredible [community diarization stack](https://github.com/MahmoudAshraf97/whisper-diarization).
|
|
159
|
+
|
|
160
|
+
*(Taters grew out of the earlier "ChopShop" prototype; many ideas and defaults carry over.)*
|
|
161
|
+
|
taters-0.1.0/README.MD
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# TATERS — Takes All Things, Extracts Relevant Stuff
|
|
2
|
+
|
|
3
|
+
Taters is a broad-scope toolkit for researchers that can be used to extract features from multiple types of data (video, audio, text) into clean, analysis-ready artifacts and features. Think of it as a small, dependable kitchen crew for your data: you bring potatoes (files), it handles the peeling, chopping, and plating.
|
|
4
|
+
|
|
5
|
+
**Status:** active WIP. It works today, but expect some rough edges and breaking changes as the project grows.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What Taters is (and isn't)
|
|
10
|
+
|
|
11
|
+
Taters is **a library and a CLI** for end-to-end A/V + text processing with predictable outputs. It's **not** a monolithic "black box" pipeline — each step is a clear, reusable function you can run on its own or string together with YAML presets.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What you can do with it (high level)
|
|
16
|
+
|
|
17
|
+
Note: everything below is currently implemented, but is highly subject to change as the project evolves.
|
|
18
|
+
|
|
19
|
+
* **Pull audio from video**: extract one or more WAV streams from containers.
|
|
20
|
+
* **Diarize + transcribe**: wrap a proven third-party stack to produce per-recording CSV/SRT/TXT.
|
|
21
|
+
* **Per-speaker WAVs**: build one WAV per speaker from a transcript CSV.
|
|
22
|
+
* **Embeddings**
|
|
23
|
+
|
|
24
|
+
* **Whisper encoder embeddings** (segment-level from a transcript or general audio without one).
|
|
25
|
+
* **Sentence embeddings** (mean per row) for any text dataset.
|
|
26
|
+
* **Text gatherer**: stream CSVs or folders of `.txt` into a single “analysis-ready” CSV, with optional grouping.
|
|
27
|
+
* **Feature extraction**
|
|
28
|
+
|
|
29
|
+
* **Dictionary coding** across any number of ContentCoder dictionaries → one wide CSV with stable column order.
|
|
30
|
+
* **Archetype scoring** with sentence-transformers → tidy, fixed columns.
|
|
31
|
+
* **Predictable outputs**: if you don't specify a path, Taters writes to `./features/<kind>/<filename>.csv`, where `<filename>` reflects how the text was gathered (e.g., grouped vs. concatenated).
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## How you'll use it
|
|
36
|
+
|
|
37
|
+
### Python (quick sketch)
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from taters import Taters
|
|
41
|
+
t = Taters()
|
|
42
|
+
|
|
43
|
+
# 1) Audio from video
|
|
44
|
+
wavs = t.audio.extract_wavs_from_video(input_path="input.mp4")
|
|
45
|
+
|
|
46
|
+
# 2) Diarize (CSV/SRT/TXT)
|
|
47
|
+
diar = t.audio.diarize_with_thirdparty(audio_path=wavs[0], device="cuda")
|
|
48
|
+
|
|
49
|
+
# 3) Features (defaults write under ./features/<kind>/)
|
|
50
|
+
t.audio.extract_whisper_embeddings(source_wav=wavs[0], transcript_csv=diar["csv"])
|
|
51
|
+
t.text.analyze_with_dictionaries(csv_path=diar["csv"], dict_paths=["dicts/LIWC-22.dicx"])
|
|
52
|
+
t.text.analyze_with_archetypes(csv_path=diar["csv"], archetype_csvs=["archetypes/Resilience.csv"])
|
|
53
|
+
t.text.extract_sentence_embeddings(csv_path=diar["csv"], text_cols=["text"], id_cols=["speaker"], group_by=["speaker"])
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### CLI (quick sketch)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Diarize
|
|
60
|
+
python -m taters.audio.diarize_with_thirdparty \
|
|
61
|
+
--audio_path audio/session.wav --device cuda
|
|
62
|
+
|
|
63
|
+
# Whisper embeddings (general audio; non-silent spans + mean pool)
|
|
64
|
+
python -m taters.audio.extract_whisper_embeddings \
|
|
65
|
+
--source_wav audio/session.wav --strategy nonsilent --aggregate mean
|
|
66
|
+
|
|
67
|
+
# Gather text from CSV (auto names the output if --out omitted)
|
|
68
|
+
python -m taters.helpers.text_gather \
|
|
69
|
+
--csv transcripts/session.csv --text-col text --group-by speaker --delimiter ,
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Pipelines (do it all at once)
|
|
73
|
+
|
|
74
|
+
Presets live in YAML (e.g., `taters/pipelines/presets/`). Point at a folder, choose a preset, and Taters will run the steps in order—using each step's output as the next step's input. You can override variables (like models, device, overwrite behavior) on the command line.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Install (tidy version)
|
|
79
|
+
|
|
80
|
+
Use a fresh virtual environment. Seriously, a fresh virtual environment is strongly recommended.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
python -m venv venv-taters
|
|
84
|
+
source venv-taters/bin/activate
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Quick path (when available)
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pip install "taters[diarization,cuda]"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Then install the three git extras used by the diarization wrapper:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install git+https://github.com/MahmoudAshraf97/demucs.git
|
|
97
|
+
pip install git+https://github.com/oliverguhr/deepmultilingualpunctuation.git
|
|
98
|
+
pip install git+https://github.com/MahmoudAshraf97/ctc-forced-aligner.git
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Install PyTorch built for **CUDA 12.4** (the stack ChopShop targets):
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip install --force-reinstall --no-cache-dir \
|
|
105
|
+
torch==2.6.0 torchvision==0.21.0 torchaudio==2.6.0 \
|
|
106
|
+
--index-url https://download.pytorch.org/whl/cu124
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
And ensure **FFmpeg** is on your `PATH` (Ubuntu: `sudo apt-get install ffmpeg`, macOS: `brew install ffmpeg`).
|
|
110
|
+
|
|
111
|
+
> Tip: If you hit CUDA/cuDNN loader errors, it usually means your runtime and wheel builds don't match. Keep CUDA **12.4**, `cu124` wheels, and cuDNN 9 aligned.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Roadmap (short)
|
|
116
|
+
|
|
117
|
+
* More feature families
|
|
118
|
+
* More obviously composable pipelines (per-item + global phases, manifests, post-run aggregation).
|
|
119
|
+
* Rich gatherers/aggregators to unify outputs across large runs.
|
|
120
|
+
* Clear docs, examples, and ready-to-run presets.
|
|
121
|
+
|
|
122
|
+
If you try Taters on a real project, feedback on your flow and pain points is incredibly helpful.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## License & credits
|
|
127
|
+
|
|
128
|
+
MIT license. Built on top of excellent open-source projects (Faster-Whisper, sentence-transformers, ContentCoder, and an incredible [community diarization stack](https://github.com/MahmoudAshraf97/whisper-diarization).
|
|
129
|
+
|
|
130
|
+
*(Taters grew out of the earlier "ChopShop" prototype; many ideas and defaults carry over.)*
|
|
131
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "taters"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [{ name = "Ryan L. Boyd", email = "ryan@ryanboyd.io" }]
|
|
9
|
+
description = "Analyze, process, and extract from many types of input data. Highly modular/customizable."
|
|
10
|
+
|
|
11
|
+
# Make sure the filename matches your repo exactly (case-sensitive on Linux)
|
|
12
|
+
readme = { file = "README.MD", content-type = "text/markdown" }
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
|
|
16
|
+
dependencies = [
|
|
17
|
+
# Core, minimal runtime for splitting, per-speaker builds, and embeddings
|
|
18
|
+
"faster-whisper>=1.1.0",
|
|
19
|
+
"transformers>=4.38.0", # for WhisperFeatureExtractor
|
|
20
|
+
"librosa>=0.10.1",
|
|
21
|
+
"pydub>=0.25.1",
|
|
22
|
+
"contentcoder",
|
|
23
|
+
"archetyper",
|
|
24
|
+
"nltk",
|
|
25
|
+
"sentence-transformers",
|
|
26
|
+
# NOTE TO SELF:
|
|
27
|
+
# Do NOT list torch/torchaudio here;
|
|
28
|
+
# users must install the right CUDA wheel themselves
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
classifiers = [
|
|
32
|
+
"Programming Language :: Python :: 3",
|
|
33
|
+
"License :: OSI Approved :: MIT License",
|
|
34
|
+
"Operating System :: OS Independent",
|
|
35
|
+
"Topic :: Multimedia :: Sound/Audio :: Analysis",
|
|
36
|
+
"Intended Audience :: Science/Research",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/ryanboyd/taters"
|
|
41
|
+
Issues = "https://github.com/ryanboyd/taters/issues"
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
# Full diarization pipeline (large downloads)
|
|
45
|
+
diarization = [
|
|
46
|
+
"nemo-toolkit[asr]>=2.dev",
|
|
47
|
+
]
|
|
48
|
+
# User-space cuDNN libs for CUDA 12 (handy on many systems)
|
|
49
|
+
cuda = [
|
|
50
|
+
"nvidia-cudnn-cu12",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.setuptools]
|
|
54
|
+
# Map top-level packages to the 'src' directory
|
|
55
|
+
package-dir = {"" = "src"}
|
|
56
|
+
include-package-data = true
|
|
57
|
+
zip-safe = false
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# find packages
|
|
61
|
+
packages = { find = { where = ["src"], include = ["taters*"] } }
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
[tool.setuptools.package-data]
|
|
65
|
+
# Because I vendored the diarizer repo under taters/audio/diarizer/whisper-diarization/
|
|
66
|
+
"taters.diarizer" = ["whisper-diarization/**"]
|
|
67
|
+
"taters.pipeline.presets" = ["*.yaml"]
|
|
68
|
+
|
|
69
|
+
[project.scripts]
|
|
70
|
+
# Entry points must target a callable, not __main__
|
|
71
|
+
# Ensure extract_whisper_embeddings.py defines a `main()` (or `cli()`) function.
|
|
72
|
+
taters-embeddings = "taters.extract_whisper_embeddings:main"
|
taters-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# taters/Taters.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
import inspect
|
|
6
|
+
|
|
7
|
+
def _abs(p: str | Path) -> str:
|
|
8
|
+
return str(Path(p).resolve())
|
|
9
|
+
|
|
10
|
+
def _forward(func, kwargs: dict[str, Any]):
|
|
11
|
+
sig = inspect.signature(func)
|
|
12
|
+
try:
|
|
13
|
+
# validate names & requireds; don't execute defaults here
|
|
14
|
+
sig.bind_partial(**kwargs)
|
|
15
|
+
except TypeError as e:
|
|
16
|
+
allowed = ", ".join([str(p) for p in sig.parameters.values()])
|
|
17
|
+
raise TypeError(f"{func.__module__}.{func.__name__}: {e}\nAllowed params: {allowed}")
|
|
18
|
+
return func(**kwargs)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Taters:
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.audio = _AudioAPI(self)
|
|
24
|
+
self.text = _TextAPI(self)
|
|
25
|
+
self.helpers = _HelpersAPI(self)
|
|
26
|
+
|
|
27
|
+
# back-compat pass-throughs
|
|
28
|
+
|
|
29
|
+
# audio
|
|
30
|
+
def convert_to_wav(self, **kwargs): return self.audio.convert_to_wav(**kwargs)
|
|
31
|
+
def extract_wavs_from_video(self, **kwargs): return self.audio.extract_wavs_from_video(**kwargs)
|
|
32
|
+
def split_wav_by_speaker(self, **kwargs): return self.audio.split_wav_by_speaker(**kwargs)
|
|
33
|
+
def extract_whisper_embeddings(self, **kwargs): return self.audio.extract_whisper_embeddings(**kwargs)
|
|
34
|
+
def diarize_with_thirdparty(self, **kwargs): return self.audio.diarize_with_thirdparty(**kwargs)
|
|
35
|
+
|
|
36
|
+
#text
|
|
37
|
+
def analyze_with_dictionaries(self, **kwargs): return self.text.analyze_with_dictionaries(**kwargs)
|
|
38
|
+
def analyze_with_archetypes(self, **kwargs): return self.text.analyze_with_archetypes(**kwargs)
|
|
39
|
+
def extract_sentence_embeddings(self, **kwargs): return self.text.extract_sentence_embeddings(**kwargs)
|
|
40
|
+
|
|
41
|
+
# helpers
|
|
42
|
+
def txt_folder_to_analysis_ready_csv(self, **kwargs): return self.helpers.txt_folder_to_analysis_ready_csv(**kwargs)
|
|
43
|
+
def csv_to_analysis_ready_csv(self, **kwargs): return self.helpers.csv_to_analysis_ready_csv(**kwargs)
|
|
44
|
+
def find_files(self, **kwargs): return self.helpers.find_files(**kwargs)
|
|
45
|
+
def feature_gather(self, **kwargs): return self.helpers.feature_gather(**kwargs)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def txt_folder_to_analysis_ready_csv(self, **kwargs):
|
|
49
|
+
from .helpers.text_gather import txt_folder_to_analysis_ready_csv
|
|
50
|
+
return _forward(txt_folder_to_analysis_ready_csv, kwargs)
|
|
51
|
+
|
|
52
|
+
def csv_to_analysis_ready_csv(self, **kwargs):
|
|
53
|
+
from .helpers.text_gather import csv_to_analysis_ready_csv
|
|
54
|
+
return _forward(csv_to_analysis_ready_csv, kwargs)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class _AudioAPI:
|
|
58
|
+
def __init__(self, parent: Taters): self._cs = parent
|
|
59
|
+
|
|
60
|
+
def convert_to_wav(self, **kwargs):
|
|
61
|
+
from .audio.convert_to_wav import convert_audio_to_wav
|
|
62
|
+
return _forward(convert_audio_to_wav, kwargs)
|
|
63
|
+
|
|
64
|
+
def extract_wavs_from_video(self, **kwargs):
|
|
65
|
+
from .audio.extract_wav_from_video import split_audio_streams_to_wav
|
|
66
|
+
return _forward(split_audio_streams_to_wav, kwargs)
|
|
67
|
+
|
|
68
|
+
def split_wav_by_speaker(self, **kwargs):
|
|
69
|
+
from .audio.split_wav_by_speaker import make_speaker_wavs_from_csv
|
|
70
|
+
return _forward(make_speaker_wavs_from_csv, kwargs)
|
|
71
|
+
|
|
72
|
+
def extract_whisper_embeddings(self, **kwargs):
|
|
73
|
+
from .audio.extract_whisper_embeddings import extract_whisper_embeddings
|
|
74
|
+
return _forward(extract_whisper_embeddings, kwargs)
|
|
75
|
+
|
|
76
|
+
def diarize_with_thirdparty(self, **kwargs):
|
|
77
|
+
from .audio.diarizer.whisper_diar_wrapper import run_whisper_diarization_repo
|
|
78
|
+
return _forward(run_whisper_diarization_repo, kwargs)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class _TextAPI:
|
|
82
|
+
def __init__(self, parent: Taters): self._cs = parent
|
|
83
|
+
|
|
84
|
+
def analyze_with_dictionaries(self, **kwargs):
|
|
85
|
+
from .text.analyze_with_dictionaries import analyze_with_dictionaries
|
|
86
|
+
return _forward(analyze_with_dictionaries, kwargs)
|
|
87
|
+
|
|
88
|
+
def analyze_with_archetypes(self, **kwargs):
|
|
89
|
+
from .text.analyze_with_archetypes import analyze_with_archetypes
|
|
90
|
+
return _forward(analyze_with_archetypes, kwargs)
|
|
91
|
+
|
|
92
|
+
def extract_sentence_embeddings(self, **kwargs):
|
|
93
|
+
from .text.extract_sentence_embeddings import analyze_with_sentence_embeddings
|
|
94
|
+
return _forward(analyze_with_sentence_embeddings, kwargs)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class _HelpersAPI:
|
|
98
|
+
def __init__(self, parent: Taters): self._cs = parent
|
|
99
|
+
|
|
100
|
+
def txt_folder_to_analysis_ready_csv(self, **kwargs):
|
|
101
|
+
from .helpers.text_gather import txt_folder_to_analysis_ready_csv
|
|
102
|
+
return _forward(txt_folder_to_analysis_ready_csv, kwargs)
|
|
103
|
+
|
|
104
|
+
def csv_to_analysis_ready_csv(self, **kwargs):
|
|
105
|
+
from .helpers.text_gather import csv_to_analysis_ready_csv
|
|
106
|
+
return _forward(csv_to_analysis_ready_csv, kwargs)
|
|
107
|
+
|
|
108
|
+
def find_files(self, **kwargs):
|
|
109
|
+
from .helpers.find_files import find_files
|
|
110
|
+
return _forward(find_files, kwargs)
|
|
111
|
+
|
|
112
|
+
def feature_gather(self, **kwargs):
|
|
113
|
+
from .helpers import feature_gather
|
|
114
|
+
return _forward(feature_gather, kwargs)
|
|
115
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# taters/audio/convert_to_wav.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, Union
|
|
7
|
+
|
|
8
|
+
class FFmpegNotFoundError(RuntimeError):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
def _check_ffmpeg():
|
|
12
|
+
if shutil.which("ffmpeg") is None or shutil.which("ffprobe") is None:
|
|
13
|
+
raise FFmpegNotFoundError("ffmpeg and/or ffprobe not found on PATH.")
|
|
14
|
+
|
|
15
|
+
def convert_audio_to_wav(
|
|
16
|
+
input_path: Union[str, Path],
|
|
17
|
+
*,
|
|
18
|
+
output_path: Optional[Union[str, Path]] = None,
|
|
19
|
+
output_dir: Optional[Union[str, Path]] = None,
|
|
20
|
+
sample_rate: int = 16000, # common for ASR
|
|
21
|
+
bit_depth: int = 16, # 16/24/32 signed PCM
|
|
22
|
+
channels: int = 1, # 1=mono, 2=stereo
|
|
23
|
+
overwrite_existing: bool = False, # if the file already exists, let's not overwrite by default
|
|
24
|
+
) -> Path:
|
|
25
|
+
"""
|
|
26
|
+
Convert any audio (or A/V container) to a PCM WAV file using ffmpeg.
|
|
27
|
+
|
|
28
|
+
If output_path is None and output_dir is None, writes <input_stem>.wav next to input.
|
|
29
|
+
If output_dir is given (and output_path is None), writes <output_dir>/<input_stem>.wav.
|
|
30
|
+
If output_path is given, it takes precedence.
|
|
31
|
+
|
|
32
|
+
Returns the Path to the created WAV.
|
|
33
|
+
"""
|
|
34
|
+
_check_ffmpeg()
|
|
35
|
+
|
|
36
|
+
in_path = Path(input_path).resolve()
|
|
37
|
+
if not in_path.exists():
|
|
38
|
+
raise FileNotFoundError(f"Input file not found: {in_path}")
|
|
39
|
+
|
|
40
|
+
if output_path and output_dir:
|
|
41
|
+
raise ValueError("Provide at most one of output_path or output_dir.")
|
|
42
|
+
|
|
43
|
+
if output_path:
|
|
44
|
+
out_path = Path(output_path).resolve()
|
|
45
|
+
else:
|
|
46
|
+
base = in_path.stem + ".wav"
|
|
47
|
+
out_dir = Path(output_dir).resolve() if output_dir else Path.cwd() / "audio"
|
|
48
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
out_path = out_dir / base
|
|
50
|
+
|
|
51
|
+
if not overwrite_existing and Path(out_path).is_file():
|
|
52
|
+
print("WAV file already exists; returning existing file.")
|
|
53
|
+
return out_path
|
|
54
|
+
|
|
55
|
+
pcm_map = {16: "pcm_s16le", 24: "pcm_s24le", 32: "pcm_s32le"}
|
|
56
|
+
if bit_depth not in pcm_map:
|
|
57
|
+
raise ValueError("bit_depth must be one of {16, 24, 32}.")
|
|
58
|
+
if channels not in (1, 2):
|
|
59
|
+
raise ValueError("channels must be 1 (mono) or 2 (stereo).")
|
|
60
|
+
|
|
61
|
+
cmd = [
|
|
62
|
+
"ffmpeg",
|
|
63
|
+
"-nostdin",
|
|
64
|
+
"-hide_banner", "-loglevel", "error",
|
|
65
|
+
"-y" if overwrite_existing else "-n",
|
|
66
|
+
"-i", str(in_path),
|
|
67
|
+
"-vn", # ignore video
|
|
68
|
+
"-acodec", pcm_map[bit_depth],
|
|
69
|
+
"-ar", str(sample_rate),
|
|
70
|
+
"-ac", str(channels),
|
|
71
|
+
str(out_path),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
result = subprocess.run(cmd, capture_output=True, text=True, stdin=subprocess.DEVNULL)
|
|
75
|
+
if result.returncode != 0:
|
|
76
|
+
if not overwrite_existing and out_path.exists():
|
|
77
|
+
raise FileExistsError(f"Target exists (use overwrite=True): {out_path}")
|
|
78
|
+
raise RuntimeError(f"ffmpeg failed: {result.stderr.strip()}")
|
|
79
|
+
|
|
80
|
+
return out_path
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# --- CLI --------------------------------------------------------------------
|
|
84
|
+
def _build_arg_parser():
|
|
85
|
+
import argparse
|
|
86
|
+
p = argparse.ArgumentParser(description="Convert any audio (or A/V) file to PCM WAV via ffmpeg.")
|
|
87
|
+
p.add_argument("input", help="Input file (audio or video container)")
|
|
88
|
+
p.add_argument("--out", dest="output_path", default=None,
|
|
89
|
+
help="Exact output .wav path (overrides --out-dir)")
|
|
90
|
+
p.add_argument("--out-dir", dest="output_dir", default=None,
|
|
91
|
+
help="Directory for output (filename will be <input_stem>.wav)")
|
|
92
|
+
p.add_argument("--sr", dest="sample_rate", type=int, default=16000, help="Sample rate (Hz)")
|
|
93
|
+
p.add_argument("--bit-depth", type=int, choices=[16, 24, 32], default=16, help="PCM bit depth")
|
|
94
|
+
p.add_argument("--channels", type=int, choices=[1, 2], default=1, help="1=mono, 2=stereo")
|
|
95
|
+
p.add_argument("--overwrite_existing", type=bool, default=False, help="Overwrite existing output")
|
|
96
|
+
return p
|
|
97
|
+
|
|
98
|
+
def main():
|
|
99
|
+
args = _build_arg_parser().parse_args()
|
|
100
|
+
out = convert_audio_to_wav(
|
|
101
|
+
args.input,
|
|
102
|
+
output_path=args.output_path,
|
|
103
|
+
output_dir=args.output_dir,
|
|
104
|
+
sample_rate=args.sample_rate,
|
|
105
|
+
bit_depth=args.bit_depth,
|
|
106
|
+
channels=args.channels,
|
|
107
|
+
overwrite_existing=args.overwrite_existing,
|
|
108
|
+
)
|
|
109
|
+
print(str(out))
|
|
110
|
+
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
main()
|
|
File without changes
|
|
File without changes
|