sample-indexer 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.
Files changed (42) hide show
  1. sample_indexer-0.1.0/LICENSE +21 -0
  2. sample_indexer-0.1.0/PKG-INFO +233 -0
  3. sample_indexer-0.1.0/README.md +214 -0
  4. sample_indexer-0.1.0/pyproject.toml +33 -0
  5. sample_indexer-0.1.0/sample_indexer/__init__.py +6 -0
  6. sample_indexer-0.1.0/sample_indexer/__main__.py +6 -0
  7. sample_indexer-0.1.0/sample_indexer/audio.py +423 -0
  8. sample_indexer-0.1.0/sample_indexer/audio_dsp.py +160 -0
  9. sample_indexer-0.1.0/sample_indexer/cli.py +343 -0
  10. sample_indexer-0.1.0/sample_indexer/config.py +43 -0
  11. sample_indexer-0.1.0/sample_indexer/db.py +132 -0
  12. sample_indexer-0.1.0/sample_indexer/hashing.py +18 -0
  13. sample_indexer-0.1.0/sample_indexer/labels.py +443 -0
  14. sample_indexer-0.1.0/sample_indexer/loops.py +574 -0
  15. sample_indexer-0.1.0/sample_indexer/loops_dsp.py +503 -0
  16. sample_indexer-0.1.0/sample_indexer/lowend.py +660 -0
  17. sample_indexer-0.1.0/sample_indexer/lowend_dsp.py +459 -0
  18. sample_indexer-0.1.0/sample_indexer/metadata.py +663 -0
  19. sample_indexer-0.1.0/sample_indexer/midi.py +133 -0
  20. sample_indexer-0.1.0/sample_indexer/midi_parser.py +284 -0
  21. sample_indexer-0.1.0/sample_indexer/migrations.py +323 -0
  22. sample_indexer-0.1.0/sample_indexer/reports.py +411 -0
  23. sample_indexer-0.1.0/sample_indexer/scanner.py +440 -0
  24. sample_indexer-0.1.0/sample_indexer.egg-info/PKG-INFO +233 -0
  25. sample_indexer-0.1.0/sample_indexer.egg-info/SOURCES.txt +40 -0
  26. sample_indexer-0.1.0/sample_indexer.egg-info/dependency_links.txt +1 -0
  27. sample_indexer-0.1.0/sample_indexer.egg-info/entry_points.txt +2 -0
  28. sample_indexer-0.1.0/sample_indexer.egg-info/requires.txt +1 -0
  29. sample_indexer-0.1.0/sample_indexer.egg-info/top_level.txt +1 -0
  30. sample_indexer-0.1.0/setup.cfg +4 -0
  31. sample_indexer-0.1.0/tests/test_audio.py +275 -0
  32. sample_indexer-0.1.0/tests/test_audio_dsp.py +148 -0
  33. sample_indexer-0.1.0/tests/test_labels.py +203 -0
  34. sample_indexer-0.1.0/tests/test_loops.py +281 -0
  35. sample_indexer-0.1.0/tests/test_loops_dsp.py +261 -0
  36. sample_indexer-0.1.0/tests/test_lowend.py +452 -0
  37. sample_indexer-0.1.0/tests/test_lowend_dsp.py +165 -0
  38. sample_indexer-0.1.0/tests/test_metadata.py +639 -0
  39. sample_indexer-0.1.0/tests/test_midi.py +81 -0
  40. sample_indexer-0.1.0/tests/test_midi_parser.py +188 -0
  41. sample_indexer-0.1.0/tests/test_query_technical.py +146 -0
  42. sample_indexer-0.1.0/tests/test_scanner.py +393 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adrián Cancio and contributors
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.
@@ -0,0 +1,233 @@
1
+ Metadata-Version: 2.4
2
+ Name: sample-indexer
3
+ Version: 0.1.0
4
+ Summary: SQLite index of a sample and MIDI library with incremental reconciliation
5
+ Author: Adrián Cancio
6
+ License-Expression: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: End Users/Desktop
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
14
+ Requires-Python: >=3.12
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: numpy
18
+ Dynamic: license-file
19
+
20
+ # sample-indexer
21
+
22
+ A SQLite index and technical analysis engine for sample and MIDI libraries. Built on Python standard library, system `ffprobe`/`ffmpeg` binaries, and `numpy` for digital signal processing (DSP).
23
+
24
+ The sample library is strictly **read-only**: files are opened `O_RDONLY` and never modified, moved, or deleted.
25
+
26
+ ---
27
+
28
+ ## Requirements
29
+
30
+ - **Python**: `>= 3.12`
31
+ - **System tools**: `ffmpeg` and `ffprobe` must be available in `PATH`.
32
+ - Fedora: `sudo dnf install ffmpeg-free` (or RPM Fusion `ffmpeg`)
33
+ - Ubuntu / Debian: `sudo apt install ffmpeg`
34
+ - Arch Linux: `sudo pacman -S ffmpeg`
35
+ - **Python dependencies**: `numpy` (installed automatically via `pip`).
36
+
37
+ ---
38
+
39
+ ## Installation
40
+
41
+ Install directly from the source repository:
42
+
43
+ ```bash
44
+ pip install .
45
+ ```
46
+
47
+ Or for development (editable mode):
48
+
49
+ ```bash
50
+ pip install -e .
51
+ ```
52
+
53
+ Once installed, the CLI tool is available as `sample-indexer` (or via `python3 -m sample_indexer`).
54
+
55
+ ---
56
+
57
+ ## Quick start
58
+
59
+ ```bash
60
+ # Initialize or migrate database
61
+ sample-indexer init
62
+
63
+ # Scan library: compute SHA-256 identity and track paths
64
+ sample-indexer scan --root /path/to/samples
65
+
66
+ # Probe audio containers with ffprobe and extract name/folder labels
67
+ sample-indexer metadata --workers 4
68
+
69
+ # Run full decode & low-end DSP (sub-bass, pitch f0, decay) on kick/bass candidates
70
+ sample-indexer lowend --workers 2
71
+
72
+ # Run full decode & tempo/meter/key DSP on loop candidates
73
+ sample-indexer loops --workers 2
74
+
75
+ # General streaming timbre, true peak, and integrated loudness (LUFS)
76
+ sample-indexer audio --workers 2
77
+
78
+ # Parse Standard MIDI Files (SMF/RMID) into musical note metrics
79
+ sample-indexer midi
80
+
81
+ # Check coverage, health, and library statistics
82
+ sample-indexer stats
83
+ sample-indexer coverage
84
+ sample-indexer midi-coverage
85
+
86
+ # Query samples by technical criteria
87
+ sample-indexer query --folder "Samples/Kicks" --ext wav --limit 20
88
+
89
+ # Create a safe, online SQLite backup
90
+ sample-indexer backup index.backup.sqlite
91
+ ```
92
+
93
+ Global option `--db PATH` (placed before the subcommand) or the environment variable `SAMPLE_INDEXER_DB` selects the database location (default: `data/index.sqlite`).
94
+ `SAMPLE_INDEXER_ROOT` or `--root PATH` configures the library root.
95
+
96
+ Exit codes:
97
+ - `0`: success
98
+ - `1`: differences detected / file or probe errors
99
+ - `2`: usage error or database error
100
+ - `130`: interrupted (SIGINT / Ctrl-C)
101
+
102
+ ---
103
+
104
+ ## Analysis pipeline
105
+
106
+ | Stage | Subcommand | Reads | Writes |
107
+ |---|---|---|---|
108
+ | **Identity** | `scan` | File content (SHA-256), `stat` | `resource` (sha256, size, ext), `path` (rel_path, mtime) |
109
+ | **Probe** | `metadata` | Header via single `ffprobe` per audio file | duration, sample rate, channels, bit depth, codec |
110
+ | **Labels** | `metadata` | File path and name only | kind, category, bpm_name, key_name, label evidence |
111
+ | **Low-End** | `lowend` | Full decode via `ffmpeg -xerror` | `valid`/`valid_error`, peak, RMS, crest factor, bands, sub peak Hz, f0 pitch Hz, decay ms, stereo correlation |
112
+ | **Loops** | `loops` | Full decode via `ffmpeg -xerror` | `bpm_est`, `beats`, `bars_est`, `key_est`, confidence metrics, chroma analysis |
113
+ | **Audio** | `audio` | Complete streaming PCM | Integrated LUFS, true peak dBTP, spectral centroid, rolloff, flatness, structured descriptors |
114
+ | **MIDI** | `midi` | SMF / RMID events (stdlib parser) | Note counts, pitch bounds, note density, tempo, bars |
115
+
116
+ ### Identity and reconciliation (`scan`)
117
+ - Identity is determined by the content SHA-256 (`resource`), while file paths are recorded as attributes (`path`).
118
+ - Paths with unchanged `(rel_path, size, mtime_ns)` are not re-hashed.
119
+ - Relocated or renamed files are recognized by matching their SHA-256 with vanished records.
120
+ - Deleted or missing files are marked with `missing=1`; database records are never hard-deleted.
121
+
122
+ ### Container probing and heuristics (`metadata`)
123
+ - Fast, non-destructive header inspection using `ffprobe` captures duration, sample rate, bit depth, and codec.
124
+ - Heuristic classification extracts provisional `kind` (`one_shot`, `loop`, `midi`, `unknown`), `category`, and filename BPM/key hints.
125
+ - Audio validity is left `NULL` until a stage actually performs a conclusive full decode.
126
+
127
+ ### Low-End & Kick analysis (`lowend`)
128
+ - Focuses on kick drums, 808s, and bass candidates.
129
+ - Measures fundamental pitch ($f_0$) via time-domain autocorrelation, sub-bass peak frequency via high-resolution zero-padded FFT, decay time ($T_{60}$ equivalent), and low-frequency stereo correlation ($< 150$ Hz).
130
+
131
+ ### Loop & Tempo analysis (`loops`)
132
+ - Targets loop candidates (folder conventions or loop labels).
133
+ - Measures tempo via onset spectral flux autocorrelation across candidate BPM ranges.
134
+ - Estimates musical key using 12-bin chroma profiling correlated against Krumhansl-Schmuckler key profiles.
135
+
136
+ ### Comprehensive timbre & loudness (`audio`)
137
+ - Streams full audio signals through chunked spectral analysis.
138
+ - Computes integrated loudness (LUFS) and true peak (dBTP) using EBU R128 metering standards.
139
+ - Evaluates spectral centroid, 85% spectral rolloff, and spectral flatness.
140
+
141
+ ### MIDI parsing (`midi`)
142
+ - Pure Python Standard MIDI File (SMF format 0/1/2) and RMID parser without external dependencies.
143
+ - Calculates total notes, pitch range (`pitch_min`, `pitch_max`), note density (notes/beat), bar counts, and tempo events.
144
+
145
+ ---
146
+
147
+ ## Querying the index
148
+
149
+ ### CLI examples
150
+
151
+ ```bash
152
+ # 1. Kick one-shots with sub frequency peak in 45-65 Hz range
153
+ sample-indexer query --folder "Samples/Drums - One Shots/Kicks" --sub-peak-min 45 --sub-peak-max 65 --limit 10
154
+
155
+ # 2. Fast loops (>= 140 BPM) with measured tempo confidence >= 0.20
156
+ sample-indexer query --bpm-min 140 --bpm-confidence-min 0.20 --limit 10
157
+
158
+ # 3. Stereo loops with high mono compatibility in the low end (stereo_corr_low >= 0.85)
159
+ sample-indexer query --stereo-corr-low-min 0.85 --limit 10
160
+
161
+ # 4. Short percussion/hi-hat one-shots (duration <= 0.15s, sr=44100)
162
+ sample-indexer query --duration-max 0.15 --sr 44100 --limit 10
163
+
164
+ # 5. Long atmospheric textures (duration >= 10.0s)
165
+ sample-indexer query --duration-min 10.0 --limit 10
166
+
167
+ # 6. Dense MIDI files (density >= 6.0 notes/beat)
168
+ sample-indexer query --ext mid --midi-density-min 6.0 --limit 10
169
+
170
+ # 7. Long MIDI arrangements with 100+ note events
171
+ sample-indexer query --ext mid --midi-notes-min 100 --limit 10
172
+
173
+ # 8. Samples matching musical key 'F min' (from filename hint or measured chroma)
174
+ sample-indexer query --key "F min" --limit 10
175
+
176
+ # 9. Basslines with fundamental pitch f0 between 40 and 90 Hz
177
+ sample-indexer query --f0-min 40 --f0-max 90 --limit 10
178
+
179
+ # 10. Locate known corrupted or unparseable files
180
+ sample-indexer query --only-errors
181
+
182
+ # 11. Output structured JSON including technical audio descriptors
183
+ sample-indexer query --folder "Samples/Loops/Bass Loops" --bpm-min 130 --bpm-max 140 --json --limit 5
184
+ ```
185
+
186
+ ### Python API
187
+
188
+ `sample-indexer` can be imported directly into Python scripts:
189
+
190
+ ```python
191
+ import sqlite3
192
+ from sample_indexer import query
193
+
194
+ conn = sqlite3.connect("data/index.sqlite")
195
+ conn.row_factory = sqlite3.Row
196
+
197
+ # Query kicks tuned between 48 Hz and 55 Hz
198
+ rows = query(
199
+ conn,
200
+ folder="Samples/Drums - One Shots/Kicks",
201
+ sub_peak_min=48.0,
202
+ sub_peak_max=55.0,
203
+ limit=25,
204
+ )
205
+
206
+ for row in rows:
207
+ print(f"{row['rel_path']} - {row['sub_peak_hz']:.1f} Hz")
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Safety and execution semantics
213
+
214
+ - **Resumable execution**: Each stage tracks progress in `resource_stage`. Interrupted runs (Ctrl-C / `--max-seconds`) commit finished checkpoints and exit cleanly.
215
+ - **Error handling**: Corrupted files or failed decodes record the failure status in the index and count as processed to prevent retry storms. Use `--retry-errors` to retry them or `--force` to reprocess.
216
+ - **Database backups**: Schema migrations automatically create timestamped SQLite backups before applying changes. Manual backups can be created at any time using `sample-indexer backup <dest>`.
217
+ - **Subprocess security**: All external calls to `ffmpeg` and `ffprobe` use explicit argument lists (`file:<path>`) without shell interpolation.
218
+
219
+ ---
220
+
221
+ ## Running tests
222
+
223
+ The test suite runs against temporary file trees and scratch SQLite databases:
224
+
225
+ ```bash
226
+ python3 -m unittest discover
227
+ ```
228
+
229
+ ---
230
+
231
+ ## License
232
+
233
+ This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,214 @@
1
+ # sample-indexer
2
+
3
+ A SQLite index and technical analysis engine for sample and MIDI libraries. Built on Python standard library, system `ffprobe`/`ffmpeg` binaries, and `numpy` for digital signal processing (DSP).
4
+
5
+ The sample library is strictly **read-only**: files are opened `O_RDONLY` and never modified, moved, or deleted.
6
+
7
+ ---
8
+
9
+ ## Requirements
10
+
11
+ - **Python**: `>= 3.12`
12
+ - **System tools**: `ffmpeg` and `ffprobe` must be available in `PATH`.
13
+ - Fedora: `sudo dnf install ffmpeg-free` (or RPM Fusion `ffmpeg`)
14
+ - Ubuntu / Debian: `sudo apt install ffmpeg`
15
+ - Arch Linux: `sudo pacman -S ffmpeg`
16
+ - **Python dependencies**: `numpy` (installed automatically via `pip`).
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ Install directly from the source repository:
23
+
24
+ ```bash
25
+ pip install .
26
+ ```
27
+
28
+ Or for development (editable mode):
29
+
30
+ ```bash
31
+ pip install -e .
32
+ ```
33
+
34
+ Once installed, the CLI tool is available as `sample-indexer` (or via `python3 -m sample_indexer`).
35
+
36
+ ---
37
+
38
+ ## Quick start
39
+
40
+ ```bash
41
+ # Initialize or migrate database
42
+ sample-indexer init
43
+
44
+ # Scan library: compute SHA-256 identity and track paths
45
+ sample-indexer scan --root /path/to/samples
46
+
47
+ # Probe audio containers with ffprobe and extract name/folder labels
48
+ sample-indexer metadata --workers 4
49
+
50
+ # Run full decode & low-end DSP (sub-bass, pitch f0, decay) on kick/bass candidates
51
+ sample-indexer lowend --workers 2
52
+
53
+ # Run full decode & tempo/meter/key DSP on loop candidates
54
+ sample-indexer loops --workers 2
55
+
56
+ # General streaming timbre, true peak, and integrated loudness (LUFS)
57
+ sample-indexer audio --workers 2
58
+
59
+ # Parse Standard MIDI Files (SMF/RMID) into musical note metrics
60
+ sample-indexer midi
61
+
62
+ # Check coverage, health, and library statistics
63
+ sample-indexer stats
64
+ sample-indexer coverage
65
+ sample-indexer midi-coverage
66
+
67
+ # Query samples by technical criteria
68
+ sample-indexer query --folder "Samples/Kicks" --ext wav --limit 20
69
+
70
+ # Create a safe, online SQLite backup
71
+ sample-indexer backup index.backup.sqlite
72
+ ```
73
+
74
+ Global option `--db PATH` (placed before the subcommand) or the environment variable `SAMPLE_INDEXER_DB` selects the database location (default: `data/index.sqlite`).
75
+ `SAMPLE_INDEXER_ROOT` or `--root PATH` configures the library root.
76
+
77
+ Exit codes:
78
+ - `0`: success
79
+ - `1`: differences detected / file or probe errors
80
+ - `2`: usage error or database error
81
+ - `130`: interrupted (SIGINT / Ctrl-C)
82
+
83
+ ---
84
+
85
+ ## Analysis pipeline
86
+
87
+ | Stage | Subcommand | Reads | Writes |
88
+ |---|---|---|---|
89
+ | **Identity** | `scan` | File content (SHA-256), `stat` | `resource` (sha256, size, ext), `path` (rel_path, mtime) |
90
+ | **Probe** | `metadata` | Header via single `ffprobe` per audio file | duration, sample rate, channels, bit depth, codec |
91
+ | **Labels** | `metadata` | File path and name only | kind, category, bpm_name, key_name, label evidence |
92
+ | **Low-End** | `lowend` | Full decode via `ffmpeg -xerror` | `valid`/`valid_error`, peak, RMS, crest factor, bands, sub peak Hz, f0 pitch Hz, decay ms, stereo correlation |
93
+ | **Loops** | `loops` | Full decode via `ffmpeg -xerror` | `bpm_est`, `beats`, `bars_est`, `key_est`, confidence metrics, chroma analysis |
94
+ | **Audio** | `audio` | Complete streaming PCM | Integrated LUFS, true peak dBTP, spectral centroid, rolloff, flatness, structured descriptors |
95
+ | **MIDI** | `midi` | SMF / RMID events (stdlib parser) | Note counts, pitch bounds, note density, tempo, bars |
96
+
97
+ ### Identity and reconciliation (`scan`)
98
+ - Identity is determined by the content SHA-256 (`resource`), while file paths are recorded as attributes (`path`).
99
+ - Paths with unchanged `(rel_path, size, mtime_ns)` are not re-hashed.
100
+ - Relocated or renamed files are recognized by matching their SHA-256 with vanished records.
101
+ - Deleted or missing files are marked with `missing=1`; database records are never hard-deleted.
102
+
103
+ ### Container probing and heuristics (`metadata`)
104
+ - Fast, non-destructive header inspection using `ffprobe` captures duration, sample rate, bit depth, and codec.
105
+ - Heuristic classification extracts provisional `kind` (`one_shot`, `loop`, `midi`, `unknown`), `category`, and filename BPM/key hints.
106
+ - Audio validity is left `NULL` until a stage actually performs a conclusive full decode.
107
+
108
+ ### Low-End & Kick analysis (`lowend`)
109
+ - Focuses on kick drums, 808s, and bass candidates.
110
+ - Measures fundamental pitch ($f_0$) via time-domain autocorrelation, sub-bass peak frequency via high-resolution zero-padded FFT, decay time ($T_{60}$ equivalent), and low-frequency stereo correlation ($< 150$ Hz).
111
+
112
+ ### Loop & Tempo analysis (`loops`)
113
+ - Targets loop candidates (folder conventions or loop labels).
114
+ - Measures tempo via onset spectral flux autocorrelation across candidate BPM ranges.
115
+ - Estimates musical key using 12-bin chroma profiling correlated against Krumhansl-Schmuckler key profiles.
116
+
117
+ ### Comprehensive timbre & loudness (`audio`)
118
+ - Streams full audio signals through chunked spectral analysis.
119
+ - Computes integrated loudness (LUFS) and true peak (dBTP) using EBU R128 metering standards.
120
+ - Evaluates spectral centroid, 85% spectral rolloff, and spectral flatness.
121
+
122
+ ### MIDI parsing (`midi`)
123
+ - Pure Python Standard MIDI File (SMF format 0/1/2) and RMID parser without external dependencies.
124
+ - Calculates total notes, pitch range (`pitch_min`, `pitch_max`), note density (notes/beat), bar counts, and tempo events.
125
+
126
+ ---
127
+
128
+ ## Querying the index
129
+
130
+ ### CLI examples
131
+
132
+ ```bash
133
+ # 1. Kick one-shots with sub frequency peak in 45-65 Hz range
134
+ sample-indexer query --folder "Samples/Drums - One Shots/Kicks" --sub-peak-min 45 --sub-peak-max 65 --limit 10
135
+
136
+ # 2. Fast loops (>= 140 BPM) with measured tempo confidence >= 0.20
137
+ sample-indexer query --bpm-min 140 --bpm-confidence-min 0.20 --limit 10
138
+
139
+ # 3. Stereo loops with high mono compatibility in the low end (stereo_corr_low >= 0.85)
140
+ sample-indexer query --stereo-corr-low-min 0.85 --limit 10
141
+
142
+ # 4. Short percussion/hi-hat one-shots (duration <= 0.15s, sr=44100)
143
+ sample-indexer query --duration-max 0.15 --sr 44100 --limit 10
144
+
145
+ # 5. Long atmospheric textures (duration >= 10.0s)
146
+ sample-indexer query --duration-min 10.0 --limit 10
147
+
148
+ # 6. Dense MIDI files (density >= 6.0 notes/beat)
149
+ sample-indexer query --ext mid --midi-density-min 6.0 --limit 10
150
+
151
+ # 7. Long MIDI arrangements with 100+ note events
152
+ sample-indexer query --ext mid --midi-notes-min 100 --limit 10
153
+
154
+ # 8. Samples matching musical key 'F min' (from filename hint or measured chroma)
155
+ sample-indexer query --key "F min" --limit 10
156
+
157
+ # 9. Basslines with fundamental pitch f0 between 40 and 90 Hz
158
+ sample-indexer query --f0-min 40 --f0-max 90 --limit 10
159
+
160
+ # 10. Locate known corrupted or unparseable files
161
+ sample-indexer query --only-errors
162
+
163
+ # 11. Output structured JSON including technical audio descriptors
164
+ sample-indexer query --folder "Samples/Loops/Bass Loops" --bpm-min 130 --bpm-max 140 --json --limit 5
165
+ ```
166
+
167
+ ### Python API
168
+
169
+ `sample-indexer` can be imported directly into Python scripts:
170
+
171
+ ```python
172
+ import sqlite3
173
+ from sample_indexer import query
174
+
175
+ conn = sqlite3.connect("data/index.sqlite")
176
+ conn.row_factory = sqlite3.Row
177
+
178
+ # Query kicks tuned between 48 Hz and 55 Hz
179
+ rows = query(
180
+ conn,
181
+ folder="Samples/Drums - One Shots/Kicks",
182
+ sub_peak_min=48.0,
183
+ sub_peak_max=55.0,
184
+ limit=25,
185
+ )
186
+
187
+ for row in rows:
188
+ print(f"{row['rel_path']} - {row['sub_peak_hz']:.1f} Hz")
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Safety and execution semantics
194
+
195
+ - **Resumable execution**: Each stage tracks progress in `resource_stage`. Interrupted runs (Ctrl-C / `--max-seconds`) commit finished checkpoints and exit cleanly.
196
+ - **Error handling**: Corrupted files or failed decodes record the failure status in the index and count as processed to prevent retry storms. Use `--retry-errors` to retry them or `--force` to reprocess.
197
+ - **Database backups**: Schema migrations automatically create timestamped SQLite backups before applying changes. Manual backups can be created at any time using `sample-indexer backup <dest>`.
198
+ - **Subprocess security**: All external calls to `ffmpeg` and `ffprobe` use explicit argument lists (`file:<path>`) without shell interpolation.
199
+
200
+ ---
201
+
202
+ ## Running tests
203
+
204
+ The test suite runs against temporary file trees and scratch SQLite databases:
205
+
206
+ ```bash
207
+ python3 -m unittest discover
208
+ ```
209
+
210
+ ---
211
+
212
+ ## License
213
+
214
+ This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sample-indexer"
7
+ version = "0.1.0"
8
+ description = "SQLite index of a sample and MIDI library with incremental reconciliation"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.12"
12
+ dependencies = [
13
+ "numpy",
14
+ ]
15
+ authors = [
16
+ { name = "Adrián Cancio" },
17
+ ]
18
+ classifiers = [
19
+ "Development Status :: 4 - Beta",
20
+ "Intended Audience :: End Users/Desktop",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Topic :: Multimedia :: Sound/Audio :: Analysis",
26
+ ]
27
+
28
+ [project.scripts]
29
+ sample-indexer = "sample_indexer.cli:main"
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["."]
33
+ include = ["sample_indexer*"]
@@ -0,0 +1,6 @@
1
+ """sample-indexer: SQLite index of a sample/MIDI library with incremental reconciliation."""
2
+
3
+ from .reports import query
4
+
5
+ __version__ = "0.1.0"
6
+ __all__ = ["__version__", "query"]
@@ -0,0 +1,6 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())