audacity-mcp-server 0.1.17__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.
- audacity_mcp_server-0.1.17/.github/workflows/ci.yml +30 -0
- audacity_mcp_server-0.1.17/.github/workflows/publish.yml +58 -0
- audacity_mcp_server-0.1.17/.gitignore +45 -0
- audacity_mcp_server-0.1.17/CHANGELOG.md +221 -0
- audacity_mcp_server-0.1.17/CONTRIBUTING.md +72 -0
- audacity_mcp_server-0.1.17/LICENSE +190 -0
- audacity_mcp_server-0.1.17/PKG-INFO +562 -0
- audacity_mcp_server-0.1.17/README.md +536 -0
- audacity_mcp_server-0.1.17/audacity_mcp/__init__.py +0 -0
- audacity_mcp_server-0.1.17/audacity_mcp/audacity_client.py +347 -0
- audacity_mcp_server-0.1.17/audacity_mcp/main.py +20 -0
- audacity_mcp_server-0.1.17/audacity_mcp/setup_transcription.py +136 -0
- audacity_mcp_server-0.1.17/audacity_mcp/tool_registry.py +12 -0
- audacity_mcp_server-0.1.17/audacity_mcp/tools/__init__.py +0 -0
- audacity_mcp_server-0.1.17/audacity_mcp/tools/analysis_tools.py +86 -0
- audacity_mcp_server-0.1.17/audacity_mcp/tools/cleanup_tools.py +1450 -0
- audacity_mcp_server-0.1.17/audacity_mcp/tools/edit_tools.py +74 -0
- audacity_mcp_server-0.1.17/audacity_mcp/tools/effects_tools.py +550 -0
- audacity_mcp_server-0.1.17/audacity_mcp/tools/generate_tools.py +156 -0
- audacity_mcp_server-0.1.17/audacity_mcp/tools/label_tools.py +137 -0
- audacity_mcp_server-0.1.17/audacity_mcp/tools/project_tools.py +205 -0
- audacity_mcp_server-0.1.17/audacity_mcp/tools/selection_tools.py +92 -0
- audacity_mcp_server-0.1.17/audacity_mcp/tools/track_tools.py +134 -0
- audacity_mcp_server-0.1.17/audacity_mcp/tools/transcription_tools.py +679 -0
- audacity_mcp_server-0.1.17/audacity_mcp/tools/transport_tools.py +57 -0
- audacity_mcp_server-0.1.17/audacity_mcp_shared/__init__.py +3 -0
- audacity_mcp_server-0.1.17/audacity_mcp_shared/constants.py +101 -0
- audacity_mcp_server-0.1.17/audacity_mcp_shared/error_codes.py +33 -0
- audacity_mcp_server-0.1.17/audacity_mcp_shared/pipe_protocol.py +69 -0
- audacity_mcp_server-0.1.17/docs/INSTALLATION.md +368 -0
- audacity_mcp_server-0.1.17/docs/TOOLS.md +932 -0
- audacity_mcp_server-0.1.17/docs/images/mod-script-pipe-enable.png +0 -0
- audacity_mcp_server-0.1.17/install.bat +340 -0
- audacity_mcp_server-0.1.17/install.sh +331 -0
- audacity_mcp_server-0.1.17/pyproject.toml +52 -0
- audacity_mcp_server-0.1.17/setup_gpu.bat +32 -0
- audacity_mcp_server-0.1.17/setup_gpu.sh +31 -0
- audacity_mcp_server-0.1.17/tests/__init__.py +0 -0
- audacity_mcp_server-0.1.17/tests/conftest.py +10 -0
- audacity_mcp_server-0.1.17/tests/test_client.py +80 -0
- audacity_mcp_server-0.1.17/tests/test_label_tools.py +104 -0
- audacity_mcp_server-0.1.17/tests/test_pipe_paths.py +100 -0
- audacity_mcp_server-0.1.17/tests/test_pipe_protocol.py +146 -0
- audacity_mcp_server-0.1.17/tests/test_tools.py +82 -0
- audacity_mcp_server-0.1.17/tests/test_transcription.py +468 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
"on":
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
16
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{ matrix.python-version }}
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: pip install -e ".[dev]"
|
|
28
|
+
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: pytest tests/ -x -q
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Fires automatically on every version tag push (git tag vX.Y.Z && git push
|
|
4
|
+
# --tags), or manually via workflow_dispatch — no manual `twine upload` step,
|
|
5
|
+
# and no PyPI token stored anywhere in this repo. Uses PyPI's Trusted
|
|
6
|
+
# Publisher (OIDC) mechanism instead: PyPI is configured (once, on pypi.org)
|
|
7
|
+
# to trust this exact workflow in this exact repo, so GitHub can prove its
|
|
8
|
+
# identity to PyPI without a shared secret to store, rotate, or leak.
|
|
9
|
+
"on":
|
|
10
|
+
push:
|
|
11
|
+
tags:
|
|
12
|
+
- "v*"
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
# Gate: publishing requires the tagged commit itself to pass tests, not
|
|
18
|
+
# just whatever main's CI run said earlier — a tag can point anywhere.
|
|
19
|
+
runs-on: ${{ matrix.os }}
|
|
20
|
+
strategy:
|
|
21
|
+
fail-fast: false
|
|
22
|
+
matrix:
|
|
23
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
24
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: ${{ matrix.python-version }}
|
|
30
|
+
- run: pip install -e ".[dev]"
|
|
31
|
+
- run: pytest tests/ -x -q
|
|
32
|
+
|
|
33
|
+
build:
|
|
34
|
+
needs: test
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- uses: actions/setup-python@v5
|
|
39
|
+
with:
|
|
40
|
+
python-version: "3.12"
|
|
41
|
+
- run: python -m pip install --upgrade build
|
|
42
|
+
- run: python -m build
|
|
43
|
+
- uses: actions/upload-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: dist
|
|
46
|
+
path: dist/
|
|
47
|
+
|
|
48
|
+
publish:
|
|
49
|
+
needs: build
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
permissions:
|
|
52
|
+
id-token: write # required for Trusted Publishing — do not remove
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/download-artifact@v4
|
|
55
|
+
with:
|
|
56
|
+
name: dist
|
|
57
|
+
path: dist/
|
|
58
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
*.egg-info/
|
|
7
|
+
*.egg
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
.eggs/
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
venv/
|
|
14
|
+
.venv/
|
|
15
|
+
env/
|
|
16
|
+
.env/
|
|
17
|
+
|
|
18
|
+
# IDE
|
|
19
|
+
.vscode/
|
|
20
|
+
.idea/
|
|
21
|
+
*.swp
|
|
22
|
+
*.swo
|
|
23
|
+
*~
|
|
24
|
+
|
|
25
|
+
# Testing
|
|
26
|
+
.pytest_cache/
|
|
27
|
+
.coverage
|
|
28
|
+
htmlcov/
|
|
29
|
+
.tox/
|
|
30
|
+
|
|
31
|
+
# OS
|
|
32
|
+
.DS_Store
|
|
33
|
+
Thumbs.db
|
|
34
|
+
desktop.ini
|
|
35
|
+
|
|
36
|
+
# Whisper models (large, downloaded at runtime)
|
|
37
|
+
*.bin
|
|
38
|
+
*.pt
|
|
39
|
+
|
|
40
|
+
# Claude Code
|
|
41
|
+
.claude/
|
|
42
|
+
CLAUDE.md
|
|
43
|
+
|
|
44
|
+
# Local MCP config (contains user-specific paths)
|
|
45
|
+
.mcp.json
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to AudacityMCP will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.17] - 2026-08-01
|
|
6
|
+
|
|
7
|
+
### PyPI Package Renamed to `audacity-mcp-server`
|
|
8
|
+
|
|
9
|
+
Setting up the new tokenless PyPI publish workflow (see 0.1.16 below) surfaced that the `audacity-mcp` project on PyPI belongs to a different PyPI account than the one publishing this repo going forward, so Trusted Publishing couldn't be linked to it. Rather than depend on access to that other account, the package is published under a new name this account owns outright.
|
|
10
|
+
|
|
11
|
+
- PyPI package renamed: `audacity-mcp` → **`audacity-mcp-server`** (`pip install audacity-mcp-server`).
|
|
12
|
+
- The installed CLI command is unaffected — it's still `audacity-mcp` (and `audacity-mcp-setup-gpu`), since `[project.scripts]` in `pyproject.toml` is independent of the package's PyPI name. **No existing Claude Desktop config needs to change.**
|
|
13
|
+
- Updated `README.md` and `docs/INSTALLATION.md` pip-install references accordingly.
|
|
14
|
+
- The old `audacity-mcp` PyPI project is not affiliated with this repo and will not receive further updates from here.
|
|
15
|
+
|
|
16
|
+
## [0.1.16] - 2026-08-01
|
|
17
|
+
|
|
18
|
+
### install.bat/install.sh: Claude Desktop Never Getting Configured on Windows, and a Redundant Reinstall
|
|
19
|
+
|
|
20
|
+
A user reported the installer didn't seem to actually connect AudacityMCP to Claude Desktop.
|
|
21
|
+
|
|
22
|
+
- **Root cause**: the Microsoft Store / MSIX build of Claude Desktop redirects its `%APPDATA%` writes into an isolated per-package folder (`%LOCALAPPDATA%\Packages\Claude_<id>\LocalCache\Roaming\Claude\`) instead of the standard `%APPDATA%\Claude\` path. `install.bat` only ever wrote to the standard path, which that build never reads — so the config step silently did nothing useful for anyone on the Store build.
|
|
23
|
+
- **Fix**: config writing is now a shared subroutine, called once for the standard path and once for every `Claude_*` folder found under `%LOCALAPPDATA%\Packages\`, so both install types get configured.
|
|
24
|
+
|
|
25
|
+
**The installer was also always re-fetching the package it was sitting right next to.** Both scripts ran `pip install audacity-mcp` unconditionally — fetching fresh from PyPI even when run from inside a just-cloned/downloaded copy of the repo, which is the only way you'd have `install.bat`/`install.sh` in the first place. That's a pointless redundant download, and it also meant the installer could install an older *published* version instead of whatever fixes were sitting in the local copy the user just got.
|
|
26
|
+
|
|
27
|
+
- Both scripts now always install from the local folder the script itself lives in (`pip install "<script's own folder>"`), never from PyPI or GitHub.
|
|
28
|
+
- If the script is ever separated from the rest of the repo (moved or downloaded standalone, no `pyproject.toml` next to it), it now refuses to run with a clear error instead of silently trying to fetch the code from somewhere else.
|
|
29
|
+
- Fixed a related display bug in `install.bat`'s Python check: `%PYVER%` was expanding at parse-time (before the `for /f` that sets it, inside the same `if (...)` block) so the detected version always printed blank — `Found Python - already installed`. Switched to delayed expansion (`!PYVER!`).
|
|
30
|
+
|
|
31
|
+
**Other hardening in this pass, since the whole install flow was under review:**
|
|
32
|
+
|
|
33
|
+
- Added `--dry-run` (`-n` on macOS/Linux) to both scripts — prints every action (package install, Audacity config edit, Claude Desktop config edit) without changing anything.
|
|
34
|
+
- Both scripts now explain what the Claude Desktop config step does and ask for an explicit y/n confirmation *before* touching that file at all, not just before overwriting it — matching the existing confirmation already in place for Audacity's config.
|
|
35
|
+
- Stripped a handful of non-ASCII characters (`──`, `—`) from `install.bat` that could cause cmd.exe to misparse the file under some codepages; both scripts are now pure ASCII.
|
|
36
|
+
- Restructured (not removed) the Python-missing detection/auto-install flow in both scripts to be more reliable about skipping the winget/brew/apt/dnf/pacman install offer when Python is already present.
|
|
37
|
+
- Updated `README.md` and `docs/INSTALLATION.md` to match: Quick Start / Option A now walks through "download or clone the repo, then run the installer from inside it" instead of a standalone single-file download or `curl | bash` one-liner (which no longer works now that the script requires the repo around it).
|
|
38
|
+
|
|
39
|
+
## [0.1.15] - 2026-07-29
|
|
40
|
+
|
|
41
|
+
### Transcription: Wrong-Language Retries, and Model Re-Downloads
|
|
42
|
+
|
|
43
|
+
Two separate reports while using transcription on real files.
|
|
44
|
+
|
|
45
|
+
**Retrying a bad language auto-detect required switching tools entirely.** A user's English audio got auto-detected and transcribed as Japanese. Fixing it meant removing the label track and starting over — but `transcribe_to_labels` and `transcribe_to_file` hardcoded `task="transcribe"` and never exposed the parameter at all, so there was no way to retry the *same* tool with `task="translate"` (forces English output) or an explicit `language`. The only way to recover was switching to `transcribe_audio` and manually reconstructing the labels from its output — exactly the tangle it took another Claude session an extra half-dozen tool calls to work around.
|
|
46
|
+
|
|
47
|
+
- Added `task` to `transcribe_to_labels` and `transcribe_to_file`, matching `transcribe_audio`/`transcribe_selection`.
|
|
48
|
+
- Added docstring guidance across all four transcription tools: auto-detect can occasionally misidentify the language (background music, noise, short/ambiguous clips) — if you already know the language, pass it explicitly instead of trusting auto-detect, and retry with the *same* tool rather than switching.
|
|
49
|
+
|
|
50
|
+
**A model that was already downloaded appeared to re-download on first use.** `_get_cache_dir()` hardcoded `~/.cache/huggingface/hub`, ignoring `HF_HOME`/`HUGGINGFACE_HUB_CACHE`. The manual pre-download command in the setup docs uses huggingface_hub's own default resolution, which *does* honor those variables — so anyone who has ever redirected their HF cache (common for moving model storage to a bigger/different drive) would have the pre-downloaded model in one place and the running server hardcoded to look in another, re-downloading every time. Not reproduced on this dev machine (no mismatch here), but the hardcoded-path bug is real and independently verifiable in the source regardless.
|
|
51
|
+
|
|
52
|
+
- `_get_cache_dir()` now checks `HUGGINGFACE_HUB_CACHE`, then `HF_HOME`, before falling back to the hardcoded default — the fallback still exists for its original purpose (some MCP subprocesses on Windows can't resolve huggingface_hub's own default cache path).
|
|
53
|
+
- Added `tests/test_transcription.py::TestGetCacheDir` and extended `TestTranscribeToLabels`/`TestTranscribeToFile` for the `task` parameter.
|
|
54
|
+
|
|
55
|
+
## [0.1.14] - 2026-07-29
|
|
56
|
+
|
|
57
|
+
### Long Transcriptions Getting Silently Truncated
|
|
58
|
+
|
|
59
|
+
Reported by a user: a 48-minute file only got ~23 minutes labeled, a 4.5-hour file only got ~45 minutes labeled — both cut off partway through, no error shown.
|
|
60
|
+
|
|
61
|
+
- **Root cause**: `_cleanup_stale_jobs()` killed any transcription job running longer than `_STALE_JOB_TIMEOUT` (10 minutes) measured from **job start**, regardless of whether it was still actively progressing. The label-adding loop does a `SelectTime`+`AddLabel`+`SetLabel` round trip *per segment* — a multi-hour transcript can have thousands of segments, so that loop alone can legitimately run past 10 minutes even when working correctly. The next time `check_transcription_status` got polled after the 10-minute mark, it silently cancelled the still-running task mid-loop, leaving only whatever labels had been added so far.
|
|
62
|
+
- **Fix**: staleness is now measured from **time since last progress**, not time since start. Every step transition and every single label added now updates a `last_progress_at` timestamp; a job only gets killed after 10 minutes with *no* forward progress (i.e. actually stuck), not just for running long on a big file. Also threaded progress reporting through the transcription step itself (`_run_transcription` now accepts an `on_progress` callback, called per-segment), so a slow CPU transcription of a very long file can't hit the same wall before labeling even starts.
|
|
63
|
+
- Added `tests/test_transcription.py::TestStaleJobCleanup` and `TestRunTranscriptionProgress`.
|
|
64
|
+
|
|
65
|
+
## [0.1.13] - 2026-07-28
|
|
66
|
+
|
|
67
|
+
### Labels Overwriting Each Other
|
|
68
|
+
|
|
69
|
+
Reported by a user: transcribing with `add_labels=True` (or adding several labels via `label_add`/`label_add_at` in a row, e.g. asking Claude to mark episode boundaries in a long track) left every label blank except the first, which ended up with the *last* label's text.
|
|
70
|
+
|
|
71
|
+
- **Root cause**: `label_add`, `label_add_at`, and the transcription auto-labeling loop all called `SetLabel(Label=0, ...)` unconditionally after `AddLabel`. `AddLabel` doesn't report back the index of the label it just created, and `Label=0` doesn't mean "the label just added" — it means "the very first label in the project," full stop. So every `SetLabel` call kept retargeting that same first label, overwriting it each time, while every other newly-created label stayed blank.
|
|
72
|
+
- **Fix**: added `count_existing_labels()` (`audacity_mcp/tools/label_tools.py`) — queries `GetInfo Type=Labels`, parses the JSON, and counts labels already in the project. Since `AddLabel` appends, the new label's index is exactly that count (or `count + i` for the i-th label added in a batch, in `label_add`/`label_add_at`/the transcription loop). Confirmed via a related [Audacity GitHub issue](https://github.com/audacity/audacity/issues/1577) that label indices are assigned in creation order, 0-based — the same assumption this fix relies on.
|
|
73
|
+
- **Known caveat, not addressed here**: that same GitHub issue shows `SetLabel` can fail past label index ~100 in some Audacity versions. Not something to work around speculatively without knowing which versions are affected, but worth knowing if a transcript has 100+ segments.
|
|
74
|
+
- **Not independently tested against a live Audacity instance** — verified via unit tests with mocked `GetInfo`/`SetLabel` responses and the corroborating GitHub issue, since no running Audacity was available to test against directly. Please verify against real Audacity before considering this fully closed.
|
|
75
|
+
- Added `tests/test_label_tools.py`.
|
|
76
|
+
|
|
77
|
+
## [0.1.12] - 2026-07-28
|
|
78
|
+
|
|
79
|
+
### GPU Detection False Negative
|
|
80
|
+
|
|
81
|
+
Reported by a user who ran `audacity-mcp-setup-gpu` (which confirmed GPU transcription worked) and restarted Claude Desktop, but the actual server logs still showed `Loading whisper model 'small' on CPU...` with no "GPU failed" message in between — meaning `_cuda_is_available()` returned `False` before a GPU attempt was even made.
|
|
82
|
+
|
|
83
|
+
- **Fixed `_cuda_is_available()`** (`audacity_mcp/tools/transcription_tools.py`): it trusted `torch.cuda.is_available()` exclusively when torch was importable, returning immediately on `False` without ever running the actual check that matters. faster-whisper runs on CTranslate2, not torch — a coincidentally-installed CPU-only (or mismatched-CUDA) torch build could silently mask a perfectly working `nvidia-cublas-cu12`/`nvidia-cudnn-cu12` install. Now torch is only trusted for a *positive* answer; `False` or an import failure falls through to the real `cublas64_12.dll`/`libcublas.so` load check.
|
|
84
|
+
- Most likely underlying cause for this specific report is still an environment mismatch (packages installed where `setup_gpu` ran vs. the Python Claude Desktop's config actually launches) — the torch issue is a separate, independently real bug found investigating the same report.
|
|
85
|
+
- Added `tests/test_transcription.py::TestCudaIsAvailable` covering: torch-False-falls-through, torch-True-short-circuits, no-torch-no-cublas, and the macOS no-CUDA path.
|
|
86
|
+
|
|
87
|
+
## [0.1.11] - 2026-07-28
|
|
88
|
+
|
|
89
|
+
### Double-Clickable GPU Setup
|
|
90
|
+
|
|
91
|
+
- **Added `setup_gpu.bat` (Windows) and `setup_gpu.sh` (macOS/Linux)** at the repo root, alongside `install.bat`/`install.sh`. Same one-click pattern: no terminal, no typed commands — just download and double-click (or run). They wrap `python -m audacity_mcp.setup_transcription` (not the bare `audacity-mcp-setup-gpu` command) so they only depend on `python` being on PATH, not pip's Scripts directory too. `setup_gpu.bat` runs via `cmd.exe`, not PowerShell, so it isn't affected by PowerShell's execution-policy restriction that blocks `.ps1` scripts by default on Windows.
|
|
92
|
+
- `install.bat`/`install.sh` now mention `setup_gpu.bat`/`setup_gpu.sh` in their "Next steps" so new users discover GPU setup without needing to read the docs.
|
|
93
|
+
- README + `docs/INSTALLATION.md` link to both scripts as an alternative to the `audacity-mcp-setup-gpu` command.
|
|
94
|
+
|
|
95
|
+
## [0.1.10] - 2026-07-28
|
|
96
|
+
|
|
97
|
+
### GPU Transcription Setup & Clarity
|
|
98
|
+
|
|
99
|
+
Prompted by a user report of transcription "seeming to time out / run on CPU" with no clear way to check why:
|
|
100
|
+
|
|
101
|
+
- **New `audacity-mcp-setup-gpu` command**: one-step GPU setup for transcription. Detects an NVIDIA GPU via `nvidia-smi`, installs `nvidia-cublas-cu12`/`nvidia-cudnn-cu12` into the exact Python environment `audacity-mcp` itself runs from (installing into the wrong environment was a real, common failure mode with the old manual instructions), and then actually loads a model on the GPU to confirm it works — instead of finding out later that it silently fell back to CPU. Cleanly reports "no NVIDIA GPU detected" and exits successfully (CPU is fine) rather than erroring when there's no GPU to find.
|
|
102
|
+
- **Fixed a real bug in `_setup_cuda_path()`** (`audacity_mcp/tools/transcription_tools.py`): it read `nvidia.cublas.__file__`/`nvidia.cudnn.__file__` to locate the DLL directories, but current `nvidia-cublas-cu12`/`nvidia-cudnn-cu12` releases are PEP 420 namespace packages with no `__init__.py`, so `__file__` is `None` — the function's own guard against that silently skipped every time, meaning it never actually added anything to PATH. Switched to `__path__`, which is always populated. (In practice this wasn't the root cause of the CPU-fallback report — testing showed `ctranslate2` finds the DLLs on its own regardless — but it was still dead, broken code worth fixing since it was found in the course of building the setup script.)
|
|
103
|
+
- **Documented in README + `docs/INSTALLATION.md`**: GPU acceleration is NVIDIA-only — AMD/Intel graphics and macOS aren't supported by faster-whisper's CTranslate2 backend at all, no driver update fixes that. GeForce vs. Quadro/RTX-workstation/older-GTX doesn't matter; any reasonably current NVIDIA GPU works. Added manual verification steps (Task Manager GPU usage, `nvidia-smi`, direct `WhisperModel` repro) for anyone who wants to check without the new script.
|
|
104
|
+
- Added `tests/test_transcription.py::TestSetupCudaPath` regression test for the namespace-package fix.
|
|
105
|
+
|
|
106
|
+
## [0.1.9] - 2026-07-25
|
|
107
|
+
|
|
108
|
+
### Snap / Flatpak Audacity Support (Linux)
|
|
109
|
+
|
|
110
|
+
Reported in [#7](https://github.com/xDarkzx/Audacity-MCP/issues/7): Snap-packaged Audacity on Ubuntu sandboxes `/tmp`, so the hardcoded pipe path never matched anything and the installer failed silently. Investigated the same class of issue across every OS/packaging combination we ship for (Windows installer/portable, macOS DMG/Homebrew/portable, Linux native/Snap/Flatpak/AppImage) and fixed the two real bugs found:
|
|
111
|
+
|
|
112
|
+
- **Pipe path auto-detection**: `PipePaths.resolve()` now re-detects the pipe directory on every connection instead of hardcoding `/tmp` at import time. It checks an `AUDACITY_PIPE_DIR` override first, then the plain `/tmp` path, then falls back to walking `/proc/*/comm` for a process named `audacity` and using `/proc/<pid>/root/tmp` — but only if both FIFOs actually exist there for the current uid, so a confined Audacity with mod-script-pipe disabled doesn't get mistaken for a working pipe. This isn't Snap-specific — it also fixes Flatpak, which sandboxes `/tmp` the same way via bubblewrap.
|
|
113
|
+
- **Installer config-path detection (`install.sh`)**: now falls back to Snap's (`~/snap/audacity/current/.config/audacity/audacity.cfg`) and Flatpak's (`~/.var/app/org.audacityteam.Audacity/config/audacity/audacity.cfg`) config locations when the standard `$XDG_CONFIG_HOME` path is missing, instead of stopping at step 3 with "config not found."
|
|
114
|
+
- **Documented, not fixed**: Audacity's portable mode (a `Portable Settings` folder next to the executable, on any OS) relocates `audacity.cfg` outside every path above. No user has hit this yet, and auto-detecting it requires locating Audacity's install directory, which the installer doesn't do — added a troubleshooting note (README + `docs/INSTALLATION.md`) with the manual workaround instead of building speculative detection for an unreported case.
|
|
115
|
+
- Added `tests/test_pipe_paths.py` covering the detection logic (override precedence, matching process found, FIFOs missing, non-`audacity` process skipped, non-Linux no-op).
|
|
116
|
+
|
|
117
|
+
## [0.1.7] - 2026-04-10
|
|
118
|
+
|
|
119
|
+
### macOS / Linux Compatibility
|
|
120
|
+
|
|
121
|
+
- **Fixed macOS import crash**: `ctypes.wintypes.HANDLE` type annotation was evaluated at class definition time on all platforms, causing `NameError` on macOS/Linux. Fixed with `from __future__ import annotations` for lazy evaluation.
|
|
122
|
+
- **Fixed cross-platform CUDA detection**: `_cuda_is_available()` hardcoded Windows DLL (`cublas64_12.dll`). Now uses `torch.cuda.is_available()` with platform-specific fallbacks (returns `False` on macOS, checks `libcublas.so` on Linux).
|
|
123
|
+
- **Fixed macOS pipe paths**: Merged community PR — pipes now use `os.getuid()` for correct user-specific paths instead of hardcoded UID 0.
|
|
124
|
+
- **Added macOS/Linux system directory protection**: `_safe_path()` now blocks `/System`, `/Library`, `/usr`, `/bin`, `/sbin`, `/etc`, `/var` on Unix systems. Previously only blocked Windows system directories.
|
|
125
|
+
- **Fixed path comparison**: Replaced `.lower()` with `os.path.normcase()` for correct case handling on all platforms.
|
|
126
|
+
- **Updated docstrings**: Export path examples changed from Windows-only (`C:\Users\Name\Music`) to platform-neutral (`~/Music`) format.
|
|
127
|
+
|
|
128
|
+
### Memory Leaks Fixed
|
|
129
|
+
|
|
130
|
+
- **Whisper model GPU/CPU memory leak**: When switching model sizes (e.g., `large-v3` → `small`), the old model was replaced but never explicitly freed. CUDA/CTranslate2 held references preventing garbage collection. Now explicitly `del`s the old model and calls `gc.collect()` before loading a new one.
|
|
131
|
+
- **Job dict memory growth**: Completed job cleanup (`_cleanup_stale_jobs`) only ran when creating new jobs. If 100+ jobs completed without new ones starting, all remained in memory. Now also runs on every `check_pipeline_status` / `check_transcription_status` call.
|
|
132
|
+
|
|
133
|
+
### Race Conditions Fixed
|
|
134
|
+
|
|
135
|
+
- **`transcription_set_model` bypassed job lock**: Created jobs and wrote to `_jobs` dict without acquiring `_job_lock`, risking corruption if called simultaneously with `_start_transcription`. Now properly acquires the lock.
|
|
136
|
+
- **Pipeline/transcription interleaving**: A running transcription didn't block starting a pipeline (and vice versa). Both send commands to the same Audacity pipe — interleaved commands could corrupt Audacity state. Now cross-check each other before starting.
|
|
137
|
+
- **Stale background tasks kept running**: `_cleanup_stale_jobs()` marked timed-out jobs as errored but the `asyncio.Task` continued executing. Now stores task references and calls `task.cancel()` on timeout.
|
|
138
|
+
|
|
139
|
+
### Pipe Reliability
|
|
140
|
+
|
|
141
|
+
- **Handle leak on partial pipe open**: If the first pipe opened but the second failed, the first handle was leaked. Now calls `_close_pipes()` in all error paths.
|
|
142
|
+
- **No shutdown cleanup**: Pipe handles (especially Windows kernel handles) were never released on server exit. Added `atexit.register(client.close)`.
|
|
143
|
+
- **POSIX pipes could hang forever**: `readline()` blocked with no timeout. If Audacity crashed mid-response, the server thread hung permanently (even `asyncio` cancellation can't interrupt OS-level blocking reads). Now uses `select.select()` with configurable timeout.
|
|
144
|
+
- **Backslash escaping in pipe protocol**: `_quote_value()` escaped `"` but not `\`. A path like `C:\new\test` could have `\n` and `\t` misinterpreted. Now escapes backslashes before quotes.
|
|
145
|
+
|
|
146
|
+
### Other Fixes
|
|
147
|
+
|
|
148
|
+
- **Temp file race on Windows**: Transcription used `NamedTemporaryFile` which on Windows creates then immediately closes a file — another process could grab the same path. Now uses UUID-based paths (matching the pattern already used in cleanup pipelines).
|
|
149
|
+
|
|
150
|
+
## [0.1.4] - 2026-03-16
|
|
151
|
+
|
|
152
|
+
### Easy Setup
|
|
153
|
+
|
|
154
|
+
- **One-click installer**: Added `install.bat` (Windows) and `install.sh` (macOS/Linux) that automatically install from PyPI and configure Claude Desktop — no git clone, no manual JSON editing.
|
|
155
|
+
- **`pip install audacity-mcp`** is now the primary install method (was previously git clone + `pip install -e .`).
|
|
156
|
+
- **README rewritten** to lead with one-click install and `pip install` from PyPI. Manual git clone steps moved to a collapsible section.
|
|
157
|
+
- **Installation guide updated**: Three clear options — one-click (easiest), pip install (recommended), from source (developers).
|
|
158
|
+
|
|
159
|
+
### Documentation
|
|
160
|
+
|
|
161
|
+
- Fixed tool counts in README: updated from 96 to 131 tools across all categories.
|
|
162
|
+
- Fixed test count in project structure: updated from 40 to 60 tests.
|
|
163
|
+
- Updated all references from `pip install -e .` to `pip install audacity-mcp`.
|
|
164
|
+
|
|
165
|
+
## [0.1.3] - 2026-03-15
|
|
166
|
+
|
|
167
|
+
### Added
|
|
168
|
+
|
|
169
|
+
- Added 32 new tools (99 → 131 total) across effects, editing, tracks, selection, transcription, and labels
|
|
170
|
+
- Fixed pipeline settings
|
|
171
|
+
- Live-tested on production audio
|
|
172
|
+
|
|
173
|
+
## [0.1.1] - 2026-03-15
|
|
174
|
+
|
|
175
|
+
### Security
|
|
176
|
+
|
|
177
|
+
- **Path traversal protection**: All file paths are now canonicalized with `os.path.realpath()` before use, preventing `../` traversal attacks. System directories (Windows, Program Files) are blocked.
|
|
178
|
+
- **Command injection hardening**: Fixed `_quote_value()` in pipe protocol to properly escape embedded double quotes, preventing malformed commands from reaching Audacity.
|
|
179
|
+
- **File overwrite protection**: Export tools (audio, labels, sample data, transcription) now refuse to overwrite existing files, preventing accidental data loss from AI-hallucinated paths.
|
|
180
|
+
|
|
181
|
+
### Bug Fixes
|
|
182
|
+
|
|
183
|
+
- **Memory leak**: Pipeline and transcription job stores (`_jobs` dicts) now cap at 50 completed entries and evict oldest automatically. Previously grew unbounded for the lifetime of the server process.
|
|
184
|
+
- **Stale job timeout**: Added 10-minute timeout to cleanup pipelines (was only in transcription). Stuck pipelines no longer block all future pipeline runs forever.
|
|
185
|
+
- **Race condition**: Pipeline and transcription job creation now uses `asyncio.Lock` to prevent near-simultaneous MCP calls from bypassing the concurrent-run check and starting two pipelines at once.
|
|
186
|
+
- **Temp file collision**: Analysis WAV files now use unique filenames (`uuid` suffix) instead of a fixed path, preventing data corruption if multiple server instances run simultaneously.
|
|
187
|
+
- **Removed `wma` from allowed export formats** — Audacity doesn't natively support WMA export; including it caused confusing errors.
|
|
188
|
+
- **`select_zero_crossing` called wrong command**: Was calling `SnapToOff` (disables snapping) instead of `ZeroCross` (find zero crossings). Users thought they were snapping to zero crossings but were actually turning snapping off.
|
|
189
|
+
- **`auto_analyze_audio` track info never parsed**: `GetInfo` returns JSON in the message field but code expected it in `data` dict. Track count and metadata were always empty. Now properly parses the JSON response.
|
|
190
|
+
- **Transcription export missing `SelAllTracks`**: `Export2` requires both track and time selection. Transcription only called `SelectAll` (time) but not `SelAllTracks`, which could cause incomplete exports on multi-track projects.
|
|
191
|
+
- **`parse_response` overwrote error messages**: When Audacity returned an error message followed by `BatchCommand finished: Failed!`, the batch line overwrote the actual error text. Error details are now preserved.
|
|
192
|
+
- **`effect_amplify` accepted ratio=0**: A ratio of 0 silences audio entirely. Now rejects values <= 0.
|
|
193
|
+
- **`check_pipeline_status` deleted other jobs**: Querying one completed job triggered cleanup that could delete other users' job results. Job eviction now only happens during `_create_job`.
|
|
194
|
+
|
|
195
|
+
### Validation
|
|
196
|
+
|
|
197
|
+
- **Effect parameter validation**: Added range checks to `reverb` (7 params), `phaser` (6 params), `wahwah` (5 params), `distortion`, and `equalization`. Previously these accepted any value, potentially crashing Audacity.
|
|
198
|
+
- **Generator duration caps**: `generate_tone`, `generate_noise`, and `generate_chirp` now enforce a 1-hour maximum duration to prevent runaway generation.
|
|
199
|
+
- **Analysis parameter validation**: Added bounds checking to `analyze_find_clipping` (duty cycle 1-1000) and `analyze_sample_data_export` (limit 1-1,000,000).
|
|
200
|
+
|
|
201
|
+
### Reliability
|
|
202
|
+
|
|
203
|
+
- **Narrowed exception handlers**: CUDA setup in transcription now catches only `ImportError`, `AttributeError`, `OSError` instead of bare `Exception`, so real bugs surface instead of being silently swallowed.
|
|
204
|
+
- **Thread-safe Whisper model loading**: Added `threading.Lock` around model initialization with double-checked locking pattern. Prevents concurrent transcription jobs from loading the model simultaneously and wasting memory.
|
|
205
|
+
|
|
206
|
+
### Tests
|
|
207
|
+
|
|
208
|
+
- Added 19 new tests (41 → 60 total): pipe protocol edge cases (negative floats, Unicode, Windows paths, embedded quotes, empty strings, large numbers), path safety validation, parse_response edge cases.
|
|
209
|
+
|
|
210
|
+
## [0.1.0] - 2025-12-01
|
|
211
|
+
|
|
212
|
+
### Added
|
|
213
|
+
|
|
214
|
+
- Initial release with 99 MCP tools across 11 modules
|
|
215
|
+
- Named pipe bridge to Audacity via mod-script-pipe
|
|
216
|
+
- 9 automated audio pipelines (analyze, cleanup, podcast, audiobook, interview, vocal, live, music mastering, lo-fi)
|
|
217
|
+
- Background job system with start/poll pattern for long-running operations
|
|
218
|
+
- Transcription support via faster-whisper (local, offline)
|
|
219
|
+
- Cross-platform pipe protocol (Windows Win32 API + Unix named pipes)
|
|
220
|
+
- Injection detection on pipe commands
|
|
221
|
+
- 41 passing tests
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Contributing to AudacityMCP
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in contributing! Here's how to get started.
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/xDarkzx/Audacity-MCP.git
|
|
9
|
+
cd AudacityMCP
|
|
10
|
+
pip install -e ".[dev]"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Running Tests
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pytest tests/ -x -q
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
All tests must pass before submitting a PR.
|
|
20
|
+
|
|
21
|
+
## Adding a New Tool
|
|
22
|
+
|
|
23
|
+
1. Choose the appropriate module in `audacity_mcp/tools/` (or create a new one)
|
|
24
|
+
2. Add your tool inside the `register(mcp)` function using the `@mcp.tool()` decorator
|
|
25
|
+
3. Validate all inputs — use `AudacityMCPError` with appropriate error codes
|
|
26
|
+
4. Use `client.execute()` for fast commands or `client.execute_long()` for effects that process audio
|
|
27
|
+
5. Add tests in `tests/`
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
@mcp.tool()
|
|
33
|
+
async def my_tool(param: float = 1.0) -> dict:
|
|
34
|
+
"""Short description of what this tool does.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
param: What this parameter controls. Default: 1.0
|
|
38
|
+
"""
|
|
39
|
+
if param < 0:
|
|
40
|
+
raise AudacityMCPError(ErrorCode.VALUE_OUT_OF_RANGE, "param must be >= 0")
|
|
41
|
+
return await client.execute_long("AudacityCommand", Param=param)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Code Style
|
|
45
|
+
|
|
46
|
+
- Python 3.10+
|
|
47
|
+
- Type hints on public functions
|
|
48
|
+
- No unnecessary comments or docstrings on obvious code
|
|
49
|
+
- Keep it simple — don't over-engineer
|
|
50
|
+
- No `exec`/`eval` — every operation maps to a static handler
|
|
51
|
+
|
|
52
|
+
## Security
|
|
53
|
+
|
|
54
|
+
- Validate all parameters before sending to Audacity
|
|
55
|
+
- Block injection characters (`\n`, `\r`, `\x00`) in string inputs
|
|
56
|
+
- Validate file paths are absolute
|
|
57
|
+
- Range-check all numeric inputs
|
|
58
|
+
|
|
59
|
+
## Pull Requests
|
|
60
|
+
|
|
61
|
+
- Keep PRs focused on a single change
|
|
62
|
+
- Include tests for new tools
|
|
63
|
+
- Make sure all existing tests still pass
|
|
64
|
+
- Describe what your change does and why
|
|
65
|
+
|
|
66
|
+
## Reporting Issues
|
|
67
|
+
|
|
68
|
+
Open an issue on GitHub with:
|
|
69
|
+
- What you expected to happen
|
|
70
|
+
- What actually happened
|
|
71
|
+
- Steps to reproduce
|
|
72
|
+
- Your OS and Audacity version
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to the Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by the Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding any notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
Copyright 2026 Daniel Hodgetts
|
|
179
|
+
|
|
180
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
181
|
+
you may not use this file except in compliance with the License.
|
|
182
|
+
You may obtain a copy of the License at
|
|
183
|
+
|
|
184
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
185
|
+
|
|
186
|
+
Unless required by applicable law or agreed to in writing, software
|
|
187
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
188
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
189
|
+
See the License for the specific language governing permissions and
|
|
190
|
+
limitations under the License.
|