From 129950f57f6344fda075241001bf4dec907616fc Mon Sep 17 00:00:00 2001 From: Eric Joanis Date: Tue, 12 May 2026 16:12:10 -0400 Subject: [PATCH 1/7] docs: better pointer to where to get ffmpeg --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 495eeaec..152fe8d2 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ This is the Text-to-Speech (TTS) toolkit used by the Small Teams "Speech Generat - On Ubuntu, `sudo apt-get install ffmpeg` should work. - Other Linux distros should have an equivalent package. - With Conda, `conda install ffmpeg` is reliable. - - Or, use the official bundles from https://www.ffmpeg.org/download.html + - Or, use the applicable link under "Get packages & executables files" at https://www.ffmpeg.org/download.html - Install `torch` and `torchaudio` version 2.1.0 for your platform and CUDA version: follow the instructions at https://pytorch.org/get-started/locally/ but specify `torch==2.1.0 torchaudio==2.1.0` in the install command and remove `torchvision`. From 5619e112c539f7856e03839fc04008d71f6fa27d Mon Sep 17 00:00:00 2001 From: Eric Joanis Date: Tue, 19 May 2026 14:01:21 -0400 Subject: [PATCH 2/7] fix(deps): move coverage to test deps; remove chardet, no longer needed --- pyproject.toml | 3 +-- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fee6c922..c3483587 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,8 +102,6 @@ torch = [ ] dev = [ "black~=24.3", - "coverage", - "chardet<6", # requests<=2.32.5 not compat with chardet>=6, remove this on next requests release "diff-cover", "flake8>=4.0.1", "gitlint-core>=0.19.0", @@ -118,6 +116,7 @@ dev = [ "everyvoice[test]", ] test = [ + "coverage", "jsonschema>=4.17.3", "pep440>=0.1.2", "playwright>=1.52.0", diff --git a/uv.lock b/uv.lock index 0461a92d..63a8b326 100644 --- a/uv.lock +++ b/uv.lock @@ -754,7 +754,6 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "black" }, - { name = "chardet" }, { name = "coverage" }, { name = "diff-cover" }, { name = "flake8" }, @@ -783,6 +782,7 @@ docs = [ { name = "mkdocstrings", extra = ["python"] }, ] test = [ + { name = "coverage" }, { name = "jsonschema" }, { name = "pep440" }, { name = "playwright" }, @@ -798,9 +798,9 @@ torch = [ requires-dist = [ { name = "anytree", specifier = ">=2.12.1" }, { name = "black", marker = "extra == 'dev'", specifier = "~=24.3" }, - { name = "chardet", marker = "extra == 'dev'", specifier = "<6" }, { name = "clipdetect", specifier = ">=0.1.4" }, { name = "coverage", marker = "extra == 'dev'" }, + { name = "coverage", marker = "extra == 'test'" }, { name = "deepdiff", specifier = ">=6.5.0" }, { name = "diff-cover", marker = "extra == 'dev'" }, { name = "einops", specifier = "==0.5.0" }, From 01b1b5670bac864d6cfcea12a25a3bf73cdf1cb7 Mon Sep 17 00:00:00 2001 From: Eric Joanis Date: Tue, 19 May 2026 14:02:57 -0400 Subject: [PATCH 3/7] fix(ci): install the least dependencies in CI to test things In particular, test the CLI speed before installing dev dependencies to catch accidental dependencies at run-time on dev deps like I did with pytest. --- .github/workflows/test.yml | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ffabbfb8..61e04079 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,9 +41,21 @@ jobs: # Install a specific version of uv. version: "0.11.8" - name: Install the project - run: uv sync --locked --all-extras --dev + run: uv sync --locked - run: uv pip freeze - run: uv pip list + - name: Make sure the CLI stays fast + run: | + source .venv/bin/activate + ./profile-help-ci.sh "${{ github.event.pull_request.head.sha }}" + - name: Report help speed in PR + if: github.event_name == 'pull_request' + uses: mshick/add-pr-comment@7c1a3a3e5a072270dc21c0639df39ca11e860b2b # v3.5.0 + with: + preformatted: true + message-path: import-message.txt + - name: Install test dependencies + run: uv sync --locked --extra test - name: Run tests run: | cd everyvoice @@ -58,14 +70,6 @@ jobs: with: fail_ci_if_error: false # optional (default = false) token: ${{ secrets.CODECOV_TOKEN }} - - name: Make sure the CLI stays fast - run: ./profile-help-ci.sh "${{ github.event.pull_request.head.sha }}" - - name: Report help speed in PR - if: github.event_name == 'pull_request' - uses: mshick/add-pr-comment@7c1a3a3e5a072270dc21c0639df39ca11e860b2b # v3.5.0 - with: - preformatted: true - message-path: import-message.txt test-on-windows: runs-on: windows-latest @@ -101,7 +105,7 @@ jobs: # Install a specific version of uv. version: "0.11.8" - name: Install the project - run: uv sync --locked --all-extras --dev + run: uv sync --locked --extra test - run: uv pip freeze - run: uv pip list - name: Run tests @@ -146,7 +150,8 @@ jobs: steps: - uses: actions/checkout@v6 with: - submodules: recursive + sparse-checkout: pyproject.toml + submodules: false - run: pip install licensecheck --no-warn-conflicts - name: Run license check overall run: | From 1002542ef9372aaef843207cb93ff5a0de69dae7 Mon Sep 17 00:00:00 2001 From: Eric Joanis Date: Tue, 12 May 2026 15:38:17 -0400 Subject: [PATCH 4/7] fix: remove "test" from the CLI so pytest is not a prod dep --- everyvoice/cli.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/everyvoice/cli.py b/everyvoice/cli.py index 6ea46e52..1c54e453 100644 --- a/everyvoice/cli.py +++ b/everyvoice/cli.py @@ -64,7 +64,6 @@ synthesize as synthesize_hfg, ) from everyvoice.model.vocoder.HiFiGAN_iSTFT_lightning.hfgl.cli import train as train_hfg -from everyvoice.run_tests import SUITE_NAMES, run_tests from everyvoice.utils import spinner from everyvoice.wizard import ( PREPROCESSING_CONFIG_FILENAME_PREFIX, @@ -641,24 +640,6 @@ def inspect_checkpoint(model_path: Path): ) -TestSuites = Enum("TestSuites", {name: name for name in SUITE_NAMES}) # type: ignore - - -@app.command(hidden=True) -def test(suite: TestSuites = typer.Argument("dev")): # pragma: no cover - """Run a test suite""" - try: - import everyvoice.tests # noqa: F401 - - run_tests(suite.value) - except ModuleNotFoundError: - print( - "ERROR: hidden command 'everyvoice test' only works when you install EveryVoice from source, with dev dependencies.", - file=sys.stderr, - ) - sys.exit(1) - - # Deferred full initialization to optimize the CLI, but still exposed for unit testing. SCHEMAS_TO_OUTPUT: dict[str, Any] = {} # dict[str, type[BaseModel]] From b409b2a60f76be8d7e470090fd39b5594d1c7fe9 Mon Sep 17 00:00:00 2001 From: Eric Joanis Date: Tue, 12 May 2026 15:40:49 -0400 Subject: [PATCH 5/7] feat: have update-schemas give you more detailed messages up-to-date schemas are not an error when out-of-date schemas exist, tell the user where they are tell the user what gets generated more detailed advice at the end. --- everyvoice/cli.py | 43 +++++++++++++++++++++++++++--------- everyvoice/tests/test_cli.py | 33 +++++++++++++++++---------- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/everyvoice/cli.py b/everyvoice/cli.py index 1c54e453..1870bdfe 100644 --- a/everyvoice/cli.py +++ b/everyvoice/cli.py @@ -640,10 +640,6 @@ def inspect_checkpoint(model_path: Path): ) -# Deferred full initialization to optimize the CLI, but still exposed for unit testing. -SCHEMAS_TO_OUTPUT: dict[str, Any] = {} # dict[str, type[BaseModel]] - - AllowedDemoOutputFormats = Enum( # type: ignore "AllowedDemoOutputFormats", [("all", "all")] + [(i.name, i.value) for i in SynthesizeOutputFormats], @@ -816,6 +812,10 @@ def demo( ) +# Deferred full initialization to optimize the CLI, but still exposed for unit testing. +SCHEMAS_TO_OUTPUT: dict[str, Any] = {} # dict[str, type[BaseModel]] + + @app.command(hidden=True) def update_schemas( out_dir: Annotated[ @@ -852,16 +852,37 @@ def update_schemas( } ) + all_good = True for filename, schema in SCHEMAS_TO_OUTPUT.items(): + schema_contents = json.dumps(schema.model_json_schema(), indent=2) + "\n" if (schema_dir_path / filename).exists(): - raise FileExistsError( - f"Sorry a schema already exists for version {filename}.\n" - "If it's already been published to the schema store, please bump the EveryVoice minor version number and generate the schemas again.\n" - "If the current minor version is still in development, just delete the schema files and try again." + with open(schema_dir_path / filename) as f: + existing_contents = f.read() + if existing_contents == schema_contents: + print(f"Schema '{filename}' already up to date.") + else: + all_good = False + print( + f"Out of date schema '{filename}' exists in '{schema_dir_path}'." + ) + else: + with open( + schema_dir_path / filename, "w", encoding="utf8", newline="\n" + ) as f: + f.write(schema_contents) + print(f"Schema '{filename}' created.") + + if not all_good: + sys.exit( + dedent( + """ + ERROR: out-of-date schemas exist. + If the current schemas were already published to the schema store, please + bump the EveryVoice minor version number and run update-schemas again. + If the current minor version is still in development, delete the out-of-date + schemas and try again.""" ) - with open(schema_dir_path / filename, "w", encoding="utf8", newline="\n") as f: - json.dump(schema.model_json_schema(), f, indent=2) - f.write("\n") + ) @app.command() diff --git a/everyvoice/tests/test_cli.py b/everyvoice/tests/test_cli.py index c1ae1474..3e377750 100755 --- a/everyvoice/tests/test_cli.py +++ b/everyvoice/tests/test_cli.py @@ -269,16 +269,17 @@ def test_command_help_messages(self): result = self.runner.invoke(app, [command, "-h"]) assert result.exit_code == 0 - def test_update_schema(self): + def test_update_schemas(self): dummy_contact = ContactInformation( contact_name="Test Runner", contact_email="info@everyvoice.ca" ) - with tempfile.TemporaryDirectory() as tmpdir: + with tempfile.TemporaryDirectory() as tmpdir_s: + tmpdir = Path(tmpdir_s) # Validate that schema generation works correctly. - _ = self.runner.invoke(app, ["update-schemas", "-o", tmpdir]) + _ = self.runner.invoke(app, ["update-schemas", "-o", tmpdir_s]) for filename, obj in SCHEMAS_TO_OUTPUT.items(): with self.subTest(filename=filename, type=obj): - with open(Path(tmpdir) / filename, encoding="utf8") as f: + with open(tmpdir / filename, encoding="utf8") as f: schema = json.load(f) # serialize the model to json and then validate against the schema # Some objects will require a contact key @@ -297,10 +298,8 @@ def test_update_schema(self): # i.e., that we didn't change the models but forget to update the schemas. for filename in SCHEMAS_TO_OUTPUT: with self.subTest(filename=filename): - with open(Path(tmpdir) / filename, encoding="utf8") as f: - new_schema = f.read().replace( - "\\\\", "/" - ) # force paths to posix + with open(tmpdir / filename, encoding="utf8") as f: + new_schema = f.read() try: with open(EV_DIR / ".schema" / filename, encoding="utf8") as f: saved_schema = f.read() @@ -314,11 +313,21 @@ def test_update_schema(self): 'Schemas are out of date, please run "everyvoice update-schemas".', ) - # Next, but only if everything above passed, we make sure we can't overwrite - # existing schemas by accident. + # Make sure we can't overwrite existing but out-of-date schemas by accident. + with open(tmpdir / next(iter(SCHEMAS_TO_OUTPUT)), "w") as f: + # Make one of the schemas forcefully out of date + f.write("asdf") + result = self.runner.invoke(app, ["update-schemas", "-o", tmpdir_s]) + assert result.exit_code != 0 + assert "ERROR" in result.output + assert "Out of date" in result.output + + # If everything above passed, running update-schemas should say schemas are up to date result = self.runner.invoke(app, ["update-schemas"]) - assert result.exit_code != 0 - assert "FileExistsError" in str(result) + assert result.exit_code == 0 + assert "already up to date" in result.output + assert "Out of date" not in result.output + assert "ERROR" not in result.output def test_evaluate(self): result = self.runner.invoke( From aa9a57c81aa44eed7d6425d6f47d182eb467f969 Mon Sep 17 00:00:00 2001 From: Eric Joanis Date: Wed, 13 May 2026 17:11:56 -0400 Subject: [PATCH 6/7] build(deps): we no longer need to lock protobuf --- pyproject.toml | 1 - uv.lock | 19 +++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c3483587..29b88c28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,6 @@ dependencies = [ "nltk==3.9.3", "packaging>=22.0", "pandas~=2.0", - "protobuf~=4.25", # https://github.com/EveryVoiceTTS/EveryVoice/issues/387 "pydantic[email]>=2.4.2,<2.8.0", "pympi-ling", "pyworld-prebuilt==0.3.4.4", diff --git a/uv.lock b/uv.lock index 63a8b326..1cbe5998 100644 --- a/uv.lock +++ b/uv.lock @@ -730,7 +730,6 @@ dependencies = [ { name = "nltk" }, { name = "packaging" }, { name = "pandas" }, - { name = "protobuf" }, { name = "pydantic", extra = ["email"] }, { name = "pympi-ling" }, { name = "pyworld-prebuilt" }, @@ -838,7 +837,6 @@ requires-dist = [ { name = "playwright", marker = "extra == 'dev'", specifier = ">=1.52.0" }, { name = "playwright", marker = "extra == 'test'", specifier = ">=1.52.0" }, { name = "prek", marker = "extra == 'dev'", specifier = ">=0.3" }, - { name = "protobuf", specifier = "~=4.25" }, { name = "pydantic", extras = ["email"], specifier = ">=2.4.2,<2.8.0" }, { name = "pympi-ling" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=7" }, @@ -2883,16 +2881,17 @@ wheels = [ [[package]] name = "protobuf" -version = "4.25.9" +version = "7.34.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d9/8e/d08c41a8c004e1d437ef467e7c4f9c3295cd784eba48ed5d1d01f94b1dad/protobuf-4.25.9.tar.gz", hash = "sha256:b0dc7e7c68de8b1ce831dacb12fb407e838edbb8b6cc0dc3a2a6b4cbf6de9cff", size = 381040, upload-time = "2026-03-25T23:09:36.423Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/6b/a0e95cad1ad7cc3f2c6821fcab91671bd5b78bd42afb357bb4765f29bc41/protobuf-7.34.1.tar.gz", hash = "sha256:9ce42245e704cc5027be797c1db1eb93184d44d1cdd71811fb2d9b25ad541280", size = 454708, upload-time = "2026-03-20T17:34:47.036Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/e9/59435bd04bdd46cb38c42a336b22f9843e8e586ff83c35a5423f8b14704e/protobuf-4.25.9-cp310-abi3-win32.whl", hash = "sha256:bde396f568b0b46fc8fbfe9f02facf25b6755b2578a3b8ac61e74b9d69499e03", size = 392879, upload-time = "2026-03-25T23:09:21.32Z" }, - { url = "https://files.pythonhosted.org/packages/f3/16/42a5c7f1001783d2b5bfcecde10127f09010f78982c86ae409122ce3ece6/protobuf-4.25.9-cp310-abi3-win_amd64.whl", hash = "sha256:3683c05154252206f7cb2d371626514b3708199d9bcf683b503dabf3a2e38e06", size = 413900, upload-time = "2026-03-25T23:09:23.589Z" }, - { url = "https://files.pythonhosted.org/packages/56/5b/0074a0a9eb01f3d1c4648ca5e81b22090c811b210b61df9018ac6d6c5cda/protobuf-4.25.9-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:9560813560e6ee72c11ca8873878bdb7ee003c96a57ebb013245fe84e2540904", size = 394826, upload-time = "2026-03-25T23:09:25.194Z" }, - { url = "https://files.pythonhosted.org/packages/54/aa/b2dba856f64c36b2a06c67be1472de98cca07a2322d0f0cbf03279a40e5b/protobuf-4.25.9-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:999146ef02e7fa6a692477badd1528bcd7268df211852a3df2d834ba2b480791", size = 294191, upload-time = "2026-03-25T23:09:26.613Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5c/53f18822017b8bda6bd8bb4e02048e911fdc79a3dafdc83ab994fe922a84/protobuf-4.25.9-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:438c636de8fb706a0de94a12a268ef1ae8f5ba5ae655a7671fcda5968ba3c9be", size = 295178, upload-time = "2026-03-25T23:09:27.839Z" }, - { url = "https://files.pythonhosted.org/packages/16/28/d5065b212685875d3924bcdb3201cbf467cb4d58a18aa19a8dfd99ea80a9/protobuf-4.25.9-py3-none-any.whl", hash = "sha256:d49b615e7c935194ac161f0965699ac84df6112c378e05ec53da65d2e4cbb6d4", size = 156822, upload-time = "2026-03-25T23:09:34.957Z" }, + { url = "https://files.pythonhosted.org/packages/ec/11/3325d41e6ee15bf1125654301211247b042563bcc898784351252549a8ad/protobuf-7.34.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8b2cc79c4d8f62b293ad9b11ec3aebce9af481fa73e64556969f7345ebf9fc7", size = 429247, upload-time = "2026-03-20T17:34:37.024Z" }, + { url = "https://files.pythonhosted.org/packages/eb/9d/aa69df2724ff63efa6f72307b483ce0827f4347cc6d6df24b59e26659fef/protobuf-7.34.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:5185e0e948d07abe94bb76ec9b8416b604cfe5da6f871d67aad30cbf24c3110b", size = 325753, upload-time = "2026-03-20T17:34:38.751Z" }, + { url = "https://files.pythonhosted.org/packages/92/e8/d174c91fd48e50101943f042b09af9029064810b734e4160bbe282fa1caa/protobuf-7.34.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:403b093a6e28a960372b44e5eb081775c9b056e816a8029c61231743d63f881a", size = 340198, upload-time = "2026-03-20T17:34:39.871Z" }, + { url = "https://files.pythonhosted.org/packages/53/1b/3b431694a4dc6d37b9f653f0c64b0a0d9ec074ee810710c0c3da21d67ba7/protobuf-7.34.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ff40ce8cd688f7265326b38d5a1bed9bfdf5e6723d49961432f83e21d5713e4", size = 324267, upload-time = "2026-03-20T17:34:41.1Z" }, + { url = "https://files.pythonhosted.org/packages/85/29/64de04a0ac142fb685fd09999bc3d337943fb386f3a0ec57f92fd8203f97/protobuf-7.34.1-cp310-abi3-win32.whl", hash = "sha256:34b84ce27680df7cca9f231043ada0daa55d0c44a2ddfaa58ec1d0d89d8bf60a", size = 426628, upload-time = "2026-03-20T17:34:42.536Z" }, + { url = "https://files.pythonhosted.org/packages/4d/87/cb5e585192a22b8bd457df5a2c16a75ea0db9674c3a0a39fc9347d84e075/protobuf-7.34.1-cp310-abi3-win_amd64.whl", hash = "sha256:e97b55646e6ce5cbb0954a8c28cd39a5869b59090dfaa7df4598a7fba869468c", size = 437901, upload-time = "2026-03-20T17:34:44.112Z" }, + { url = "https://files.pythonhosted.org/packages/88/95/608f665226bca68b736b79e457fded9a2a38c4f4379a4a7614303d9db3bc/protobuf-7.34.1-py3-none-any.whl", hash = "sha256:bb3812cd53aefea2b028ef42bd780f5b96407247f20c6ef7c679807e9d188f11", size = 170715, upload-time = "2026-03-20T17:34:45.384Z" }, ] [[package]] From 1a63ce04ba2e80e679249f126c70a132d7d14702 Mon Sep 17 00:00:00 2001 From: Eric Joanis Date: Wed, 13 May 2026 17:12:40 -0400 Subject: [PATCH 7/7] feat: let train commands without args imply --help --- everyvoice/cli.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/everyvoice/cli.py b/everyvoice/cli.py index 1870bdfe..f71c1fe3 100644 --- a/everyvoice/cli.py +++ b/everyvoice/cli.py @@ -529,6 +529,7 @@ def new_project( train_group.command( name="text-to-spec", + no_args_is_help=True, short_help="Train your Text-to-Spec (FastSpeech2) model", help=f"""Train your text-to-spec model. For example: @@ -538,6 +539,7 @@ def new_project( train_group.command( name="spec-to-wav", + no_args_is_help=True, short_help="Train your Spec-to-Wav (HiFiGAN) model", help=f"""Train your spec-to-wav model. For example: @@ -547,6 +549,7 @@ def new_project( train_group.command( name="text-to-wav", + no_args_is_help=True, short_help="Train an end-to-end (StyleTTS2) model", help=f"""Train an end-to-end text-to-speech model. For example: