Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[run]
branch = true
source_pkgs =
styletts2
everyvoice.model.e2e.StyleTTS2_lightning.styletts2
omit =
*tmp*
*/tests/*
*/__main__.py

[report]
precision = 2
exclude_lines =
pragma: no cover
if 0:
if __name__ == .__main__.:
77 changes: 77 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Run Tests
on:
- push
- pull_request
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
env:
# Fix a bug in the ffmpeg installation via awalsh128/cache-apt-pkgs-action
LD_LIBRARY_PATH: /usr/lib/x86_64-linux-gnu/lapack:/usr/lib/x86_64-linux-gnu/blas
steps:
- uses: actions/checkout@v6

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current best practice recommendations are to add

   with:
     persist-credentials: false

every time we use actions/checkout, except for the rare cases where true is required (e.g., a workflow to needs to push to gh-pages), but I see that I have not started making that change anywhere in EV, nor even in Studio. It seems I only did it in g2p so far, so this comment is out-of-scope for this PR, but I wanted to mention it as something that I'll eventually spread everywhere, and is best practice to use when you create a new workflow.


- uses: actions/setup-python@v6
with:
python-version: "3.10"

- uses: awalsh128/cache-apt-pkgs-action@681749ae568c81c2037cb9185e38b709b261bd2f # v1.6.1
with:
packages: sox ffmpeg

- run: sox --version
- run: ffmpeg -version

- name: Fetch everyvoice
run: |
git clone https://github.com/EveryVoiceTTS/EveryVoice
cd EveryVoice
git checkout ${{ github.ref_name }} || git checkout ${{ github.head_ref }} || true
git submodule update --init
cd everyvoice/model/e2e/StyleTTS2_lightning
git fetch origin ${{ github.ref }}
git checkout FETCH_HEAD

- name: Install uv
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
version: "0.11.21"

- name: Install the project
run: |
cd EveryVoice/everyvoice/model/e2e/StyleTTS2_lightning/
uv sync --locked --all-extras --dev
uv pip install --editable ../../../..

- name: uv pip freeze
run: |
cd EveryVoice/everyvoice/model/e2e/StyleTTS2_lightning/
uv pip freeze
- name: uv pip list
run: |
cd EveryVoice/everyvoice/model/e2e/StyleTTS2_lightning/
uv pip list

- name: Run unit tests
run: |
cd EveryVoice/everyvoice/model/e2e/StyleTTS2_lightning/
uv run coverage run -m pytest
uv run coverage xml
env:
PYTHONPATH: ${{ github.workspace }}/EveryVoice

- name: Plain text coverage report
run: |
cd EveryVoice/everyvoice/model/e2e/StyleTTS2_lightning/
uv run coverage report

- uses: codecov/codecov-action@v5
with:
fail_ci_if_error: false # optional (default = false)
token: ${{ secrets.CODECOV_TOKEN }}
119 changes: 92 additions & 27 deletions styletts2/cli/synthesize.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

from __future__ import annotations

import sys
from pathlib import Path

import typer
from everyvoice import logger
from everyvoice.base_cli import command, default_typer_args
from everyvoice.base_cli.interfaces import typer_file_option
from everyvoice.model.feature_prediction.FastSpeech2_lightning.fs2.type_definitions import (
SynthesizeOutputFormats,
)
Expand Down Expand Up @@ -138,30 +140,54 @@ def synthesize(
file_okay=True,
dir_okay=False,
),
reference: Path = typer.Option(
...,
reference: Path | None = typer_file_option(
None,
"--reference",
"-r",
help="Reference audio file used to extract speaker style.",
exists=True,
help="Reference audio file used to extract speaker style. Required"
" unless every row of --filelist provides its own 'reference' or"
" 'reference_path' column.",
),
text: list[str] = typer.Option(
...,
[],
"--text",
"-t",
help="Text string(s) to synthesize. Repeat the flag for multiple utterances.",
help="Text string(s) to synthesize. Repeat the flag for multiple utterances."
" Use --filelist instead if you want to synthesize a lot of sentences or"
" have different speaker/language/reference per sentence.",
),
filelist: Path | None = typer_file_option(
None,
"--filelist",
"-f",
help="The path to a file containing a list of utterances (a.k.a filelist)."
" Expected columns: 'basename', 'characters' (or 'phones'), 'speaker',"
" 'language', and optionally 'reference' (or 'reference_path'). Any column"
" that is absent falls back to the corresponding --speaker/--language/"
"--reference CLI option. Use --text if you want to just synthesize one sample.",
),
output_dir: Path = typer.Option(
Path("synthesis_output"),
"--output-dir",
"-o",
help="Directory where synthesized files will be written.",
help="Directory where synthesized files will be written."
" By default, filenames include the basename, speaker, language, and"
" other metadata, e.g. 'LJ050-0269--LJ--eng--ckpt=100000--pred.wav'."
" Use --simple-filenames to write just 'LJ050-0269.wav' instead.",
),
output_type: list[SynthesizeOutputFormats] = typer.Option(
[SynthesizeOutputFormats.wav],
"--output-type",
help="Output format(s) to produce.",
),
simple_filenames: bool = typer.Option(
False,
"--simple-filenames",
help="Write output filenames as just the basename and extension"
" (e.g. 'LJ050-0269.wav') instead of the default."
" Only use this if your basenames are unique across speakers and"
" languages, otherwise outputs can overwrite each other.",
),
accelerator: str = typer.Option(
"auto",
"--accelerator",
Expand Down Expand Up @@ -204,21 +230,42 @@ def synthesize(
):
"""Synthesize audio from text using a trained StyleTTS2 model.

Example:
Examples:

**everyvoice synthesize text-to-wav logs_and_checkpoints/.../stage-2-last.ckpt \\
--reference path/to/reference.wav \\
--text "Hello world" --text "How are you?"**

Or, for batch synthesis from a filelist:
Comment thread
roedoejet marked this conversation as resolved.

**everyvoice synthesize text-to-wav logs_and_checkpoints/.../stage-2-last.ckpt \\

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This \\ at the end yields something that is not cut-and-pastable as is. But if you use \\ \\ instead, the output has a single \ and then multi-line cut and paste works.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option that works even better is

    ```
    everyvoice synthesize text-to-wav logs_and_checkpoints/.../stage-2-last.ckpt \\
        --reference path/to/reference.wav  \\
        --text "Hello world" --text "How are you?"
    ```

this gives you something displayed as a code block, preserving indentation (the current solution does not), which is even better.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose, though, cut and pasting in this case is a silly concept, maybe never mind. But I might still consider using the code block option for our examples.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, I don't think copy/paste is that important here. the code block option formats things a bit weirdly in my terminal, I think I'll leave this as is if that's alright

--reference path/to/reference.wav --filelist my_filelist.psv --simple-filenames**
"""
# Do argument error checking before doing expensive imports
if text and filelist:
print(
"Got arguments for both --text and --filelist."
" You can only synthesize using one of these options",
file=sys.stderr,
Comment thread
roedoejet marked this conversation as resolved.
)
sys.exit(1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, another change that's going to be out of scope for this PR: this should be

        raise typer.BadParameter(
            "Got arguments for both --text and --filelist."
            " You can only synthesize using one of these options"
        )

instead of print and exit.

But I see that we have 39 instances of raise typer.BadParameter(...) and 43 instances of sys.exit(1) in the repo, so I'll make this a separate issue to do systematically: EveryVoiceTTS/EveryVoice#855

if not text and not filelist:
print("You must define either --text or --filelist", file=sys.stderr)
sys.exit(1)
if text and reference is None:
print(
"Missing --reference option, which is required when using --text.",
file=sys.stderr,
)
sys.exit(1)

import lightning as L
import torch
from everyvoice.model.feature_prediction.FastSpeech2_lightning.fs2.utils import (
truncate_basename,
)
from everyvoice.utils import slugify

from .utils_heavy import (
StyleTTS2SynthesisDataModule,
build_filelist_entries,
build_text_entries,
get_styletts2_synthesis_output_callbacks,
)

Expand All @@ -238,23 +285,41 @@ def synthesize(
state = torch.load(model_path, map_location="cpu", weights_only=True)
global_step = int(state.get("global_step", 0))

entries = [
{
"raw_text": t,
"basename": truncate_basename(slugify(t)),
"speaker": speaker,
"language": language,
"reference_path": str(reference),
"diffusion_steps": diffusion_steps,
"embedding_scale": embedding_scale,
"acoustic_blend": acoustic_blend,
"prosody_blend": prosody_blend,
}
for t in text
]
try:
if text:
entries = build_text_entries(
text,
str(reference),
speaker,
language,
diffusion_steps,
embedding_scale,
acoustic_blend,
prosody_blend,
)
else:
assert filelist is not None
filelist_loader = module.config["ev_config"].training.filelist_loader
entries = build_filelist_entries(
filelist_loader(filelist),
str(reference) if reference else None,
speaker,
language,
diffusion_steps,
embedding_scale,
acoustic_blend,
prosody_blend,
)
except ValueError as e:
logger.error(str(e))
sys.exit(1)

callbacks = get_styletts2_synthesis_output_callbacks(
output_type, output_dir, global_step, module.sr
output_type,
output_dir,
global_step,
module.sr,
simple_filenames=simple_filenames,
)
if not callbacks:
logger.warning("No output format requested; nothing to do.")
Expand Down
Loading
Loading