-
Notifications
You must be signed in to change notification settings - Fork 1
feat: allow simple filenames for synthesis #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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__.: |
| 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 | ||
|
|
||
| - 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 }} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
| ) | ||
|
|
@@ -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", | ||
|
|
@@ -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: | ||
|
roedoejet marked this conversation as resolved.
|
||
|
|
||
| **everyvoice synthesize text-to-wav logs_and_checkpoints/.../stage-2-last.ckpt \\ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another option that works even better is this gives you something displayed as a code block, preserving indentation (the current solution does not), which is even better.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
|
roedoejet marked this conversation as resolved.
|
||
| ) | ||
| sys.exit(1) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 instead of print and exit. But I see that we have 39 instances of |
||
| 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, | ||
| ) | ||
|
|
||
|
|
@@ -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.") | ||
|
|
||
There was a problem hiding this comment.
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
every time we use
actions/checkout, except for the rare cases wheretrueis required (e.g., a workflow to needs to push togh-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.