Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- .github/actions/uv/action.yml +16 -0
- .github/dependabot.yml +19 -0
- .github/hadolint.yml +6 -0
- .github/mergify.yml +18 -0
- .github/workflows/code_analysis.yml +51 -0
- .github/workflows/docker.yml +71 -0
- .github/workflows/github.yaml +76 -0
- .github/workflows/huggingface.yml +30 -0
- .gitignore +1 -1
- src/vocalizr/__init__.py +1 -1
- src/vocalizr/__main__.py +1 -1
- src/vocalizr/gui.py +19 -12
- src/vocalizr/model.py +54 -36
.github/actions/uv/action.yml
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: 'Setup UV'
|
2 |
+
description: 'Reusable set of steps to Setup UV'
|
3 |
+
runs:
|
4 |
+
using: "composite"
|
5 |
+
steps:
|
6 |
+
- name: Install uv
|
7 |
+
uses: astral-sh/setup-uv@v6
|
8 |
+
id: uv
|
9 |
+
with:
|
10 |
+
enable-cache: true
|
11 |
+
cache-dependency-glob: "uv.lock"
|
12 |
+
activate-environment: true
|
13 |
+
- name: Install the project
|
14 |
+
shell: bash
|
15 |
+
if: steps.uv.outputs.cache-hit != 'true'
|
16 |
+
run: uv sync --frozen
|
.github/dependabot.yml
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# To get started with Dependabot version updates, you'll need to specify which
|
2 |
+
# package ecosystems to update and where the package manifests are located.
|
3 |
+
# Please see the documentation for all configuration options:
|
4 |
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
5 |
+
|
6 |
+
# version: 2
|
7 |
+
# updates:
|
8 |
+
# - package-ecosystem: "docker"
|
9 |
+
# directory: "/"
|
10 |
+
# schedule:
|
11 |
+
# interval: "weekly"
|
12 |
+
# - package-ecosystem: "github-actions"
|
13 |
+
# directory: "/"
|
14 |
+
# schedule:
|
15 |
+
# interval: "weekly"
|
16 |
+
# - package-ecosystem: "uv"
|
17 |
+
# directory: "/"
|
18 |
+
# schedule:
|
19 |
+
# interval: "weekly"
|
.github/hadolint.yml
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
ignored:
|
2 |
+
- DL3008
|
3 |
+
|
4 |
+
trustedRegistries:
|
5 |
+
- docker.io
|
6 |
+
- ghcr.io
|
.github/mergify.yml
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
pull_request_rules:
|
2 |
+
- name: Automatically merge Renovate PRs
|
3 |
+
conditions:
|
4 |
+
- author = renovate[bot]
|
5 |
+
- check-success = spelling
|
6 |
+
- check-success = ruff
|
7 |
+
- check-success = pyright
|
8 |
+
- check-success = pylint
|
9 |
+
actions:
|
10 |
+
queue:
|
11 |
+
label:
|
12 |
+
add:
|
13 |
+
- dependencies
|
14 |
+
queue_rules:
|
15 |
+
- queue_branch_merge_method: fast-forward
|
16 |
+
allow_queue_branch_edit: true
|
17 |
+
update_method: merge
|
18 |
+
name: default
|
.github/workflows/code_analysis.yml
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Code Analysis
|
2 |
+
on:
|
3 |
+
push:
|
4 |
+
pull_request:
|
5 |
+
permissions:
|
6 |
+
contents: read
|
7 |
+
jobs:
|
8 |
+
spelling:
|
9 |
+
runs-on: ubuntu-latest
|
10 |
+
steps:
|
11 |
+
- name: Checkout Actions Repository
|
12 |
+
uses: actions/checkout@v4
|
13 |
+
- name: Setup UV
|
14 |
+
uses: ./.github/actions/uv
|
15 |
+
- name: Run typos
|
16 |
+
run: uv run typos . --config ./pyproject.toml
|
17 |
+
ruff:
|
18 |
+
runs-on: ubuntu-latest
|
19 |
+
steps:
|
20 |
+
- name: Checkout Actions Repository
|
21 |
+
uses: actions/checkout@v4
|
22 |
+
- name: Setup UV
|
23 |
+
uses: ./.github/actions/uv
|
24 |
+
- name: Run Ruff
|
25 |
+
run: uv run ruff check .
|
26 |
+
pyright:
|
27 |
+
runs-on: ubuntu-latest
|
28 |
+
steps:
|
29 |
+
- name: Checkout Actions Repository
|
30 |
+
uses: actions/checkout@v4
|
31 |
+
- name: Setup UV
|
32 |
+
uses: ./.github/actions/uv
|
33 |
+
- name: Run Pyright
|
34 |
+
run: uv run pyright
|
35 |
+
pylint:
|
36 |
+
runs-on: ubuntu-latest
|
37 |
+
steps:
|
38 |
+
- name: Checkout Actions Repository
|
39 |
+
uses: actions/checkout@v4
|
40 |
+
- name: Setup UV
|
41 |
+
uses: ./.github/actions/uv
|
42 |
+
- name: Analysing the code with Pylint
|
43 |
+
run: uv run pylint $(git ls-files '*.py') --output-format=github --rcfile pyproject.toml
|
44 |
+
dockerfile:
|
45 |
+
runs-on: ubuntu-latest
|
46 |
+
steps:
|
47 |
+
- name: Checkout Actions Repository
|
48 |
+
uses: actions/checkout@v4
|
49 |
+
- uses: hadolint/[email protected]
|
50 |
+
with:
|
51 |
+
config: ./.github/hadolint.yml
|
.github/workflows/docker.yml
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Docker Images
|
2 |
+
on:
|
3 |
+
workflow_dispatch:
|
4 |
+
pull_request:
|
5 |
+
push:
|
6 |
+
permissions:
|
7 |
+
contents: read
|
8 |
+
packages: write
|
9 |
+
attestations: write
|
10 |
+
id-token: write
|
11 |
+
jobs:
|
12 |
+
check_image:
|
13 |
+
runs-on: ubuntu-latest
|
14 |
+
steps:
|
15 |
+
- name: Checkout repository
|
16 |
+
uses: actions/checkout@v4
|
17 |
+
- name: Log in to the Container registry
|
18 |
+
uses: docker/login-action@v3
|
19 |
+
with:
|
20 |
+
registry: ghcr.io
|
21 |
+
username: ${{ github.actor }}
|
22 |
+
password: ${{ secrets.TOKEN_KEY_GITHUB }}
|
23 |
+
- name: Validate build configuration
|
24 |
+
uses: docker/build-push-action@v6
|
25 |
+
with:
|
26 |
+
call: check
|
27 |
+
build_image:
|
28 |
+
needs: check_image
|
29 |
+
runs-on: ubuntu-latest
|
30 |
+
steps:
|
31 |
+
- name: Checkout repository
|
32 |
+
uses: actions/checkout@v4
|
33 |
+
- name: Log in to the Container registry
|
34 |
+
uses: docker/login-action@v3
|
35 |
+
with:
|
36 |
+
registry: ghcr.io
|
37 |
+
username: ${{ github.actor }}
|
38 |
+
password: ${{ secrets.TOKEN_KEY_GITHUB }}
|
39 |
+
- name: Set up QEMU
|
40 |
+
uses: docker/setup-qemu-action@v3
|
41 |
+
- name: Set up Docker Buildx
|
42 |
+
uses: docker/setup-buildx-action@v3
|
43 |
+
- name: Docker meta
|
44 |
+
id: meta
|
45 |
+
uses: docker/metadata-action@v5
|
46 |
+
with:
|
47 |
+
images: ghcr.io/${{ github.repository }}
|
48 |
+
tags: type=raw,value=latest,enable={{is_default_branch}}
|
49 |
+
- name: Build
|
50 |
+
uses: docker/build-push-action@v6
|
51 |
+
with:
|
52 |
+
cache-from: type=gha
|
53 |
+
cache-to: type=gha,mode=max
|
54 |
+
push: true
|
55 |
+
tags: ${{ steps.meta.outputs.tags }}
|
56 |
+
labels: ${{ steps.meta.outputs.labels }}
|
57 |
+
annotations: ${{ steps.meta.outputs.annotations }}
|
58 |
+
docker_scout:
|
59 |
+
needs:
|
60 |
+
- build_image
|
61 |
+
- check_image
|
62 |
+
runs-on: ubuntu-latest
|
63 |
+
steps:
|
64 |
+
- name: Docker Scout
|
65 |
+
continue-on-error: true
|
66 |
+
uses: docker/scout-action@v1
|
67 |
+
with:
|
68 |
+
command: quickview,cves,recommendations
|
69 |
+
dockerhub-user: mh0386
|
70 |
+
dockerhub-password: ${{ secrets.TOKEN_KEY_DOCKER }}
|
71 |
+
image: ghcr.io/${{ github.repository }}:latest
|
.github/workflows/github.yaml
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: GitHub Release
|
2 |
+
on:
|
3 |
+
workflow_dispatch:
|
4 |
+
push:
|
5 |
+
tags:
|
6 |
+
- "[0-9]+.[0-9]+.[0-9]+"
|
7 |
+
- "[0-9]+.[0-9]+.[0-9]+a[0-9]+"
|
8 |
+
- "[0-9]+.[0-9]+.[0-9]+b[0-9]+"
|
9 |
+
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
|
10 |
+
env:
|
11 |
+
PACKAGE_NAME: vocalizr
|
12 |
+
OWNER: AlphaSphereDotAI
|
13 |
+
permissions:
|
14 |
+
contents: write
|
15 |
+
jobs:
|
16 |
+
details:
|
17 |
+
runs-on: ubuntu-latest
|
18 |
+
outputs:
|
19 |
+
new_version: ${{ steps.release.outputs.new_version }}
|
20 |
+
tag_name: ${{ steps.release.outputs.tag_name }}
|
21 |
+
steps:
|
22 |
+
- name: Checkout Code
|
23 |
+
uses: actions/[email protected]
|
24 |
+
- name: Extract tag and Details
|
25 |
+
id: release
|
26 |
+
run: |
|
27 |
+
if [ "${{ github.ref_type }}" = "tag" ]; then
|
28 |
+
TAG_NAME=${GITHUB_REF#refs/tags/}
|
29 |
+
NEW_VERSION=$(echo $TAG_NAME | awk -F'-' '{print $1}')
|
30 |
+
SUFFIX=$(echo $TAG_NAME | grep -oP '[a-z]+[0-9]+' || echo "")
|
31 |
+
echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
|
32 |
+
echo "suffix=$SUFFIX" >> "$GITHUB_OUTPUT"
|
33 |
+
echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT"
|
34 |
+
echo "Version is $NEW_VERSION"
|
35 |
+
echo "Suffix is $SUFFIX"
|
36 |
+
echo "Tag name is $TAG_NAME"
|
37 |
+
else
|
38 |
+
echo "No tag found"
|
39 |
+
exit 1
|
40 |
+
fi
|
41 |
+
setup_and_build:
|
42 |
+
needs: [details]
|
43 |
+
runs-on: ubuntu-latest
|
44 |
+
steps:
|
45 |
+
- name: Checkout Code
|
46 |
+
uses: actions/[email protected]
|
47 |
+
- name: Setup UV
|
48 |
+
uses: ./.github/actions/uv
|
49 |
+
- name: Update Project Version
|
50 |
+
run: uv version ${{ needs.details.outputs.new_version }}
|
51 |
+
- name: Build source and wheel distribution
|
52 |
+
run: uv build --all-packages
|
53 |
+
- name: Upload artifacts
|
54 |
+
uses: actions/upload-artifact@v4
|
55 |
+
with:
|
56 |
+
name: dist
|
57 |
+
path: dist/
|
58 |
+
github_release:
|
59 |
+
name: Create GitHub Release
|
60 |
+
needs: [setup_and_build, details]
|
61 |
+
runs-on: ubuntu-latest
|
62 |
+
steps:
|
63 |
+
- name: Checkout Code
|
64 |
+
uses: actions/[email protected]
|
65 |
+
with:
|
66 |
+
fetch-depth: 0
|
67 |
+
- name: Download artifacts
|
68 |
+
uses: actions/download-artifact@v4
|
69 |
+
with:
|
70 |
+
name: dist
|
71 |
+
path: dist/
|
72 |
+
- name: Create GitHub Release
|
73 |
+
id: create_release
|
74 |
+
env:
|
75 |
+
GH_TOKEN: ${{ github.token }}
|
76 |
+
run: gh release create ${{ needs.details.outputs.tag_name }} dist/* --title ${{ needs.details.outputs.tag_name }} --generate-notes
|
.github/workflows/huggingface.yml
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Push to HuggingFace
|
2 |
+
on:
|
3 |
+
push:
|
4 |
+
env:
|
5 |
+
HF_HUB_ENABLE_HF_TRANSFER: 1
|
6 |
+
jobs:
|
7 |
+
huggingface:
|
8 |
+
runs-on: ubuntu-latest
|
9 |
+
steps:
|
10 |
+
- name: Checkout
|
11 |
+
uses: actions/checkout@v4
|
12 |
+
- name: Setup UV
|
13 |
+
uses: ./.github/actions/uv
|
14 |
+
- name: Remove Unnecessary Files
|
15 |
+
run: |
|
16 |
+
echo ".github" >> .gitignore
|
17 |
+
echo ".idea" >> .gitignore
|
18 |
+
echo "docker-bake.hcl" >> .gitignore
|
19 |
+
echo "renovate.json" >> .gitignore
|
20 |
+
echo ".deepsource.toml" >> .gitignore
|
21 |
+
- name: Add to git credentials
|
22 |
+
run: git config --global credential.helper store
|
23 |
+
- name: Login to HuggingFace Hub
|
24 |
+
run: uv run huggingface-cli login --add-to-git-credential --token ${{ secrets.HF_TOKEN }}
|
25 |
+
- name: Check if logged in
|
26 |
+
run: uv run huggingface-cli whoami
|
27 |
+
- name: Upload
|
28 |
+
run: uv run huggingface-cli upload AlphaSphereDotAI/Vocalizr . . --repo-type space
|
29 |
+
# - name: Minimize uv cache
|
30 |
+
# run: uv cache prune --ci
|
.gitignore
CHANGED
@@ -4,7 +4,7 @@ tmp/
|
|
4 |
.mypy_cache/
|
5 |
**/__pycache__/
|
6 |
.env
|
7 |
-
|
8 |
.idea
|
9 |
docker-bake.hcl
|
10 |
renovate.json
|
|
|
4 |
.mypy_cache/
|
5 |
**/__pycache__/
|
6 |
.env
|
7 |
+
results/.github
|
8 |
.idea
|
9 |
docker-bake.hcl
|
10 |
renovate.json
|
src/vocalizr/__init__.py
CHANGED
@@ -10,7 +10,7 @@ load_dotenv()
|
|
10 |
|
11 |
BASE_DIR: Path = Path(__file__).parent.parent.parent
|
12 |
DEBUG: bool = getenv(key="DEBUG", default="False").lower() == "true"
|
13 |
-
CHAR_LIMIT: int = int(getenv(key="CHAR_LIMIT", default="
|
14 |
SERVER_NAME: str = getenv(key="GRADIO_SERVER_NAME", default="localhost")
|
15 |
SERVER_PORT: int = int(getenv(key="GRADIO_SERVER_PORT", default="8080"))
|
16 |
PIPELINE: KPipeline = KPipeline(lang_code="a", repo_id="hexgrad/Kokoro-82M")
|
|
|
10 |
|
11 |
BASE_DIR: Path = Path(__file__).parent.parent.parent
|
12 |
DEBUG: bool = getenv(key="DEBUG", default="False").lower() == "true"
|
13 |
+
CHAR_LIMIT: int = int(getenv(key="CHAR_LIMIT", default="-1"))
|
14 |
SERVER_NAME: str = getenv(key="GRADIO_SERVER_NAME", default="localhost")
|
15 |
SERVER_PORT: int = int(getenv(key="GRADIO_SERVER_PORT", default="8080"))
|
16 |
PIPELINE: KPipeline = KPipeline(lang_code="a", repo_id="hexgrad/Kokoro-82M")
|
src/vocalizr/__main__.py
CHANGED
@@ -7,7 +7,7 @@ from vocalizr.gui import app_block
|
|
7 |
def main() -> None:
|
8 |
"""Launch the Gradio voice generation web application."""
|
9 |
app: Blocks = app_block()
|
10 |
-
app.launch(
|
11 |
server_name=SERVER_NAME,
|
12 |
server_port=SERVER_PORT,
|
13 |
debug=DEBUG,
|
|
|
7 |
def main() -> None:
|
8 |
"""Launch the Gradio voice generation web application."""
|
9 |
app: Blocks = app_block()
|
10 |
+
app.queue(api_open=True).launch(
|
11 |
server_name=SERVER_NAME,
|
12 |
server_port=SERVER_PORT,
|
13 |
debug=DEBUG,
|
src/vocalizr/gui.py
CHANGED
@@ -10,7 +10,7 @@ from gradio import (
|
|
10 |
Textbox,
|
11 |
)
|
12 |
|
13 |
-
from vocalizr import
|
14 |
from vocalizr.model import generate_audio_for_text
|
15 |
|
16 |
|
@@ -24,13 +24,7 @@ def app_block() -> Blocks:
|
|
24 |
with Column():
|
25 |
text: Textbox = Textbox(
|
26 |
label="Input Text",
|
27 |
-
info=(
|
28 |
-
f"""
|
29 |
-
Up to ~500 characters per Generate,
|
30 |
-
or {"∞" if CHAR_LIMIT is None else CHAR_LIMIT}
|
31 |
-
characters per Stream
|
32 |
-
"""
|
33 |
-
),
|
34 |
)
|
35 |
with Row():
|
36 |
voice: Dropdown = Dropdown(
|
@@ -47,7 +41,8 @@ def app_block() -> Blocks:
|
|
47 |
interactive=CUDA_AVAILABLE,
|
48 |
)
|
49 |
save_file = Checkbox(
|
50 |
-
label="Save Audio",
|
|
|
51 |
)
|
52 |
speed: Slider = Slider(
|
53 |
minimum=0.5,
|
@@ -60,13 +55,25 @@ def app_block() -> Blocks:
|
|
60 |
out_audio: Audio = Audio(
|
61 |
label="Output Audio",
|
62 |
interactive=False,
|
63 |
-
streaming=
|
64 |
autoplay=True,
|
65 |
)
|
66 |
-
|
67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
fn=generate_audio_for_text,
|
69 |
inputs=[text, voice, speed, save_file],
|
70 |
outputs=[out_audio],
|
71 |
)
|
|
|
|
|
|
|
|
|
72 |
return app
|
|
|
10 |
Textbox,
|
11 |
)
|
12 |
|
13 |
+
from vocalizr import CHOICES, CUDA_AVAILABLE
|
14 |
from vocalizr.model import generate_audio_for_text
|
15 |
|
16 |
|
|
|
24 |
with Column():
|
25 |
text: Textbox = Textbox(
|
26 |
label="Input Text",
|
27 |
+
info=("""Enter your text here"""),
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
)
|
29 |
with Row():
|
30 |
voice: Dropdown = Dropdown(
|
|
|
41 |
interactive=CUDA_AVAILABLE,
|
42 |
)
|
43 |
save_file = Checkbox(
|
44 |
+
label="Save Audio",
|
45 |
+
info="Save audio to local storage",
|
46 |
)
|
47 |
speed: Slider = Slider(
|
48 |
minimum=0.5,
|
|
|
55 |
out_audio: Audio = Audio(
|
56 |
label="Output Audio",
|
57 |
interactive=False,
|
58 |
+
streaming=True,
|
59 |
autoplay=True,
|
60 |
)
|
61 |
+
with Row():
|
62 |
+
stream_btn: Button = Button(
|
63 |
+
value="Generate",
|
64 |
+
variant="primary",
|
65 |
+
)
|
66 |
+
stop_btn: Button = Button(
|
67 |
+
value="Stop",
|
68 |
+
variant="stop",
|
69 |
+
)
|
70 |
+
stream_event = stream_btn.click(
|
71 |
fn=generate_audio_for_text,
|
72 |
inputs=[text, voice, speed, save_file],
|
73 |
outputs=[out_audio],
|
74 |
)
|
75 |
+
stop_btn.click(
|
76 |
+
fn=None,
|
77 |
+
cancels=stream_event,
|
78 |
+
)
|
79 |
return app
|
src/vocalizr/model.py
CHANGED
@@ -1,55 +1,73 @@
|
|
1 |
-
from
|
2 |
-
from os import makedirs
|
3 |
-
|
4 |
from gradio import Error
|
|
|
5 |
from loguru import logger
|
6 |
-
from numpy import
|
|
|
7 |
from soundfile import write
|
8 |
-
from torch import Tensor
|
9 |
|
10 |
-
from vocalizr import
|
11 |
|
12 |
|
13 |
-
|
|
|
14 |
"""Save audio data to a WAV file in the 'results' directory.
|
15 |
-
|
16 |
Creates a timestamped WAV file in the 'results' directory with
|
17 |
the provided audio data at a fixed sample rate of 24,000 Hz.
|
18 |
|
19 |
-
:
|
20 |
-
|
21 |
-
|
|
|
|
|
22 |
"""
|
23 |
-
makedirs(name="results", exist_ok=True)
|
24 |
-
filename: str = (
|
25 |
-
f"{BASE_DIR}/results/{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.wav"
|
26 |
-
)
|
27 |
try:
|
28 |
-
logger.info(f"Saving audio to {
|
29 |
-
write(file=
|
30 |
-
except
|
31 |
-
|
|
|
32 |
|
33 |
|
|
|
|
|
34 |
def generate_audio_for_text(
|
35 |
-
text: str,
|
36 |
-
|
|
|
|
|
|
|
37 |
"""Generate audio for the input text.
|
38 |
|
39 |
-
:
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
"""
|
46 |
-
text = text if CHAR_LIMIT is None else text.strip()[:CHAR_LIMIT]
|
47 |
try:
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any, Generator, Literal
|
|
|
|
|
2 |
from gradio import Error
|
3 |
+
from kokoro import KPipeline
|
4 |
from loguru import logger
|
5 |
+
from numpy import float32
|
6 |
+
from numpy.typing import NDArray
|
7 |
from soundfile import write
|
|
|
8 |
|
9 |
+
from vocalizr import CHAR_LIMIT, PIPELINE, AUDIO_FILE_PATH
|
10 |
|
11 |
|
12 |
+
@logger.catch
|
13 |
+
def save_file_wav(audio: NDArray[float32]) -> None:
|
14 |
"""Save audio data to a WAV file in the 'results' directory.
|
|
|
15 |
Creates a timestamped WAV file in the 'results' directory with
|
16 |
the provided audio data at a fixed sample rate of 24,000 Hz.
|
17 |
|
18 |
+
Args:
|
19 |
+
audio (NDArray[float32]): raw audio data.
|
20 |
+
|
21 |
+
Raises:
|
22 |
+
RuntimeError: If there are problems with saving the audio file locally.
|
23 |
"""
|
|
|
|
|
|
|
|
|
24 |
try:
|
25 |
+
logger.info(f"Saving audio to {AUDIO_FILE_PATH}")
|
26 |
+
write(file=AUDIO_FILE_PATH, data=audio, samplerate=24000)
|
27 |
+
except Exception as e:
|
28 |
+
logger.exception(f"Failed to save audio to {AUDIO_FILE_PATH}: {e}")
|
29 |
+
raise RuntimeError(f"Failed to save audio to {AUDIO_FILE_PATH}: {e}") from e
|
30 |
|
31 |
|
32 |
+
# noinspection PyTypeChecker
|
33 |
+
@logger.catch
|
34 |
def generate_audio_for_text(
|
35 |
+
text: str,
|
36 |
+
voice: str = "af_heart",
|
37 |
+
speed: float = 1,
|
38 |
+
save_file: bool = False,
|
39 |
+
) -> Generator[tuple[Literal[24000], NDArray[float32]], Any, None]:
|
40 |
"""Generate audio for the input text.
|
41 |
|
42 |
+
Args:
|
43 |
+
text (str): Input text to convert to speech
|
44 |
+
voice (str, optional): Voice identifier. Defaults to "af_heart".
|
45 |
+
speed (float, optional): Speech speed. Defaults to 1.
|
46 |
+
save_file (bool, optional): If to save the audio file to disk. Defaults to False.
|
47 |
+
|
48 |
+
Raises:
|
49 |
+
Error: If text (str) is empty
|
50 |
+
Error: If audio (NDArray[float32]) is str
|
51 |
+
Error: If audio (NDArray[float32]) is None
|
52 |
+
|
53 |
+
Yields:
|
54 |
+
Generator[tuple[Literal[24000], NDArray[float32]], Any, None]: Tuple containing the audio sample rate and raw audio data.
|
55 |
"""
|
|
|
56 |
try:
|
57 |
+
text = text if CHAR_LIMIT == -1 else text.strip()[:CHAR_LIMIT]
|
58 |
+
except Exception as e:
|
59 |
+
logger.exception(str(object=e))
|
60 |
+
raise Error(message=str(object=e)) from e
|
61 |
+
generator: Generator[KPipeline.Result, None, None] = PIPELINE(
|
62 |
+
text=text, voice=voice, speed=speed
|
63 |
+
)
|
64 |
+
logger.info(f"Generating audio for '{text}'")
|
65 |
+
for _, _, audio in generator:
|
66 |
+
if audio is None or isinstance(audio, str):
|
67 |
+
logger.exception(f"Unexpected type (audio): {type(audio)}")
|
68 |
+
raise Error(message=f"Unexpected type (audio): {type(audio)}")
|
69 |
+
audio_np: NDArray[float32] = audio.numpy()
|
70 |
+
if save_file:
|
71 |
+
save_file_wav(audio=audio_np)
|
72 |
+
logger.info(f"Yielding audio for '{text}'")
|
73 |
+
yield 24000, audio_np
|