runapi-gemini-tts 0.1.0__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.
- runapi_gemini_tts-0.1.0/PKG-INFO +37 -0
- runapi_gemini_tts-0.1.0/README.md +20 -0
- runapi_gemini_tts-0.1.0/pyproject.toml +34 -0
- runapi_gemini_tts-0.1.0/src/runapi/gemini_tts/__init__.py +24 -0
- runapi_gemini_tts-0.1.0/src/runapi/gemini_tts/client.py +19 -0
- runapi_gemini_tts-0.1.0/src/runapi/gemini_tts/contract_gen.py +37 -0
- runapi_gemini_tts-0.1.0/src/runapi/gemini_tts/py.typed +1 -0
- runapi_gemini_tts-0.1.0/src/runapi/gemini_tts/resources/__init__.py +5 -0
- runapi_gemini_tts-0.1.0/src/runapi/gemini_tts/resources/text_to_speech.py +30 -0
- runapi_gemini_tts-0.1.0/src/runapi/gemini_tts/types.py +18 -0
- runapi_gemini_tts-0.1.0/tests/test_client.py +91 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: runapi-gemini-tts
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: RunAPI Gemini TTS SDK for multi-speaker speech generation in JavaScript, Python, Ruby, Go, Java, and PHP
|
|
5
|
+
Project-URL: Homepage, https://runapi.ai/models/gemini-tts
|
|
6
|
+
Project-URL: Documentation, https://runapi.ai/docs#sdk-gemini-tts
|
|
7
|
+
Project-URL: Source, https://github.com/runapi-ai/gemini-tts-sdk
|
|
8
|
+
Project-URL: Issues, https://github.com/runapi-ai/gemini-tts-sdk/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/runapi-ai/gemini-tts-sdk/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Release Notes, https://github.com/runapi-ai/gemini-tts-sdk/releases/tag/python%2Fv0.1.0
|
|
11
|
+
Author-email: RunAPI <contact@runapi.ai>
|
|
12
|
+
License-Expression: Apache-2.0
|
|
13
|
+
Keywords: api,audio,gemini,gemini-tts,golang,google,gradle,java,maven,python,ruby,runapi,runapi-ai,sdk,speech,text-to-speech,typescript
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Requires-Dist: runapi-core>=0.2.0
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# Gemini TTS Python SDK for RunAPI
|
|
19
|
+
|
|
20
|
+
Install `runapi-gemini-tts` for keyword-based multi-speaker speech requests and normalized task responses.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install runapi-gemini-tts
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from runapi.gemini_tts import GeminiTtsClient
|
|
28
|
+
|
|
29
|
+
client = GeminiTtsClient()
|
|
30
|
+
result = client.text_to_speech.run(
|
|
31
|
+
model="gemini-2.5-pro-tts",
|
|
32
|
+
speakers=[{"speaker_id": "Speaker 1", "voice_name": "Fenrir", "accent": "British (RP)", "style": "Deadpan", "pace": "Natural"}],
|
|
33
|
+
dialogue_turns=[{"speaker_id": "Speaker 1", "text": "Welcome."}],
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
See the [model page](https://runapi.ai/models/gemini-tts) and [API reference](https://runapi.ai/docs#gemini-tts). Licensed under Apache-2.0.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Gemini TTS Python SDK for RunAPI
|
|
2
|
+
|
|
3
|
+
Install `runapi-gemini-tts` for keyword-based multi-speaker speech requests and normalized task responses.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install runapi-gemini-tts
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from runapi.gemini_tts import GeminiTtsClient
|
|
11
|
+
|
|
12
|
+
client = GeminiTtsClient()
|
|
13
|
+
result = client.text_to_speech.run(
|
|
14
|
+
model="gemini-2.5-pro-tts",
|
|
15
|
+
speakers=[{"speaker_id": "Speaker 1", "voice_name": "Fenrir", "accent": "British (RP)", "style": "Deadpan", "pace": "Natural"}],
|
|
16
|
+
dialogue_turns=[{"speaker_id": "Speaker 1", "text": "Welcome."}],
|
|
17
|
+
)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
See the [model page](https://runapi.ai/models/gemini-tts) and [API reference](https://runapi.ai/docs#gemini-tts). Licensed under Apache-2.0.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "runapi-gemini-tts"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "RunAPI Gemini TTS SDK for multi-speaker speech generation in JavaScript, Python, Ruby, Go, Java, and PHP"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
authors = [{ name = "RunAPI", email = "contact@runapi.ai" }]
|
|
13
|
+
keywords = ["runapi", "runapi-ai", "gemini-tts", "gemini", "google", "text-to-speech", "speech", "audio", "api", "sdk", "typescript", "python", "ruby", "golang", "java", "maven", "gradle"]
|
|
14
|
+
dependencies = ["runapi-core>=0.2.0"]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://runapi.ai/models/gemini-tts"
|
|
18
|
+
Documentation = "https://runapi.ai/docs#sdk-gemini-tts"
|
|
19
|
+
Source = "https://github.com/runapi-ai/gemini-tts-sdk"
|
|
20
|
+
Issues = "https://github.com/runapi-ai/gemini-tts-sdk/issues"
|
|
21
|
+
Changelog = "https://github.com/runapi-ai/gemini-tts-sdk/blob/main/CHANGELOG.md"
|
|
22
|
+
"Release Notes" = "https://github.com/runapi-ai/gemini-tts-sdk/releases/tag/python%2Fv0.1.0"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["src/runapi"]
|
|
26
|
+
|
|
27
|
+
[tool.uv]
|
|
28
|
+
package = true
|
|
29
|
+
|
|
30
|
+
[dependency-groups]
|
|
31
|
+
dev = ["pytest>=8"]
|
|
32
|
+
|
|
33
|
+
[tool.runapi]
|
|
34
|
+
slug = "gemini-tts"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Gemini TTS client for RunAPI."""
|
|
2
|
+
|
|
3
|
+
from runapi.core import (
|
|
4
|
+
AuthenticationError,
|
|
5
|
+
InsufficientCreditsError,
|
|
6
|
+
NotFoundError,
|
|
7
|
+
RateLimitError,
|
|
8
|
+
TaskFailedError,
|
|
9
|
+
TaskTimeoutError,
|
|
10
|
+
ValidationError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from .client import GeminiTtsClient
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"GeminiTtsClient",
|
|
17
|
+
"AuthenticationError",
|
|
18
|
+
"RateLimitError",
|
|
19
|
+
"InsufficientCreditsError",
|
|
20
|
+
"NotFoundError",
|
|
21
|
+
"ValidationError",
|
|
22
|
+
"TaskFailedError",
|
|
23
|
+
"TaskTimeoutError",
|
|
24
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Gemini TTS client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from runapi.core import ClientOptions, HttpClient, resolve_api_key
|
|
8
|
+
|
|
9
|
+
from .resources.text_to_speech import TextToSpeech
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GeminiTtsClient:
|
|
13
|
+
"""Client for multi-speaker Gemini TTS generation."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, api_key: Optional[str] = None, **options: Any) -> None:
|
|
16
|
+
resolved_api_key = resolve_api_key(api_key)
|
|
17
|
+
client_options = ClientOptions(api_key=resolved_api_key, **options)
|
|
18
|
+
http = client_options.http_client or HttpClient(client_options)
|
|
19
|
+
self.text_to_speech = TextToSpeech(http)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
CONTRACT = {
|
|
2
|
+
"text-to-speech": {
|
|
3
|
+
"models": ["gemini-2.5-pro-tts", "gemini-3.1-flash-tts"],
|
|
4
|
+
"fields_by_model": {
|
|
5
|
+
"gemini-2.5-pro-tts": {
|
|
6
|
+
"dialogue_turns": {
|
|
7
|
+
"required": True
|
|
8
|
+
},
|
|
9
|
+
"model": {
|
|
10
|
+
"required": True
|
|
11
|
+
},
|
|
12
|
+
"speakers": {
|
|
13
|
+
"required": True
|
|
14
|
+
},
|
|
15
|
+
"temperature": {
|
|
16
|
+
"min": 0,
|
|
17
|
+
"max": 2
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"gemini-3.1-flash-tts": {
|
|
21
|
+
"dialogue_turns": {
|
|
22
|
+
"required": True
|
|
23
|
+
},
|
|
24
|
+
"model": {
|
|
25
|
+
"required": True
|
|
26
|
+
},
|
|
27
|
+
"speakers": {
|
|
28
|
+
"required": True
|
|
29
|
+
},
|
|
30
|
+
"temperature": {
|
|
31
|
+
"min": 0,
|
|
32
|
+
"max": 2
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Gemini TTS text-to-speech resource."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from runapi.core import Resource, RequestOptions
|
|
8
|
+
|
|
9
|
+
from ..contract_gen import CONTRACT
|
|
10
|
+
from ..types import AudioTaskResponse, CompletedAudioTaskResponse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TextToSpeech(Resource):
|
|
14
|
+
"""Generate multi-speaker speech from ordered dialogue turns."""
|
|
15
|
+
|
|
16
|
+
ENDPOINT = "/api/v1/gemini_tts/text_to_speech"
|
|
17
|
+
RESPONSE_CLASS = AudioTaskResponse
|
|
18
|
+
COMPLETED_RESPONSE_CLASS = CompletedAudioTaskResponse
|
|
19
|
+
|
|
20
|
+
def run(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
|
|
21
|
+
task = self.create(options=options, **params)
|
|
22
|
+
return self._poll_until_complete(lambda: self.get(task.id, options=options))
|
|
23
|
+
|
|
24
|
+
def create(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
|
|
25
|
+
compacted = self._compact_params(params)
|
|
26
|
+
self._validate_contract(CONTRACT["text-to-speech"], compacted)
|
|
27
|
+
return self._request("post", self.ENDPOINT, body=compacted, options=options)
|
|
28
|
+
|
|
29
|
+
def get(self, id: str, options: Optional[RequestOptions] = None) -> Any:
|
|
30
|
+
return self._request("get", f"{self.ENDPOINT}/{id}", options=options)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Gemini TTS response models."""
|
|
2
|
+
|
|
3
|
+
from runapi.core import BaseModel, TaskResponse, optional, required
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Audio(BaseModel):
|
|
7
|
+
url = required(str)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AudioTaskResponse(TaskResponse):
|
|
11
|
+
id = required(str)
|
|
12
|
+
status = optional(str, enum=lambda: TaskResponse.Status.ALL)
|
|
13
|
+
audios = optional([lambda: Audio])
|
|
14
|
+
error = optional(str)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CompletedAudioTaskResponse(AudioTaskResponse):
|
|
18
|
+
audios = required([lambda: Audio])
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from runapi.core import config
|
|
4
|
+
from runapi.core.errors import AuthenticationError, ValidationError
|
|
5
|
+
from runapi.gemini_tts import GeminiTtsClient
|
|
6
|
+
from runapi.gemini_tts.resources.text_to_speech import TextToSpeech
|
|
7
|
+
from runapi.gemini_tts.types import CompletedAudioTaskResponse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FakeHttp:
|
|
11
|
+
def __init__(self, *responses):
|
|
12
|
+
self._responses = list(responses)
|
|
13
|
+
self.calls = []
|
|
14
|
+
|
|
15
|
+
def request(self, method, path, body=None, options=None):
|
|
16
|
+
self.calls.append((method, path, body))
|
|
17
|
+
return self._responses.pop(0) if self._responses else {"id": "task_1", "status": "processing"}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture(autouse=True)
|
|
21
|
+
def reset_config(monkeypatch):
|
|
22
|
+
monkeypatch.delenv("RUNAPI_API_KEY", raising=False)
|
|
23
|
+
monkeypatch.setattr(config, "api_key", None)
|
|
24
|
+
yield
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def valid_params():
|
|
28
|
+
return {
|
|
29
|
+
"model": "gemini-2.5-pro-tts",
|
|
30
|
+
"temperature": 0.8,
|
|
31
|
+
"speakers": [{
|
|
32
|
+
"speaker_id": "Speaker 1",
|
|
33
|
+
"voice_name": "Fenrir",
|
|
34
|
+
"accent": "British (RP)",
|
|
35
|
+
"style": "Deadpan",
|
|
36
|
+
"pace": "Natural",
|
|
37
|
+
}],
|
|
38
|
+
"dialogue_turns": [{"speaker_id": "Speaker 1", "text": "Welcome."}],
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_raises_without_api_key():
|
|
43
|
+
with pytest.raises(AuthenticationError, match="API key is required"):
|
|
44
|
+
GeminiTtsClient()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_exposes_text_to_speech_resource():
|
|
48
|
+
client = GeminiTtsClient(api_key="k", http_client=FakeHttp())
|
|
49
|
+
assert isinstance(client.text_to_speech, TextToSpeech)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_create_posts_flat_nested_body():
|
|
53
|
+
fake = FakeHttp({"id": "task_1", "status": "processing"})
|
|
54
|
+
client = GeminiTtsClient(api_key="k", http_client=fake)
|
|
55
|
+
result = client.text_to_speech.create(**valid_params())
|
|
56
|
+
|
|
57
|
+
assert fake.calls == [("post", "/api/v1/gemini_tts/text_to_speech", valid_params())]
|
|
58
|
+
assert result.id == "task_1"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_get_decodes_audio_results():
|
|
62
|
+
fake = FakeHttp({
|
|
63
|
+
"id": "task_1",
|
|
64
|
+
"status": "completed",
|
|
65
|
+
"audios": [{"url": "https://tempfile.runapi.ai/dialogue.mp3"}],
|
|
66
|
+
})
|
|
67
|
+
client = GeminiTtsClient(api_key="k", http_client=fake)
|
|
68
|
+
result = client.text_to_speech.get("task_1")
|
|
69
|
+
|
|
70
|
+
assert fake.calls == [("get", "/api/v1/gemini_tts/text_to_speech/task_1", None)]
|
|
71
|
+
assert result.audios[0].url == "https://tempfile.runapi.ai/dialogue.mp3"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_run_returns_completed_response():
|
|
75
|
+
fake = FakeHttp(
|
|
76
|
+
{"id": "task_1", "status": "processing"},
|
|
77
|
+
{"id": "task_1", "status": "completed", "audios": [{"url": "https://tempfile.runapi.ai/dialogue.mp3"}]},
|
|
78
|
+
)
|
|
79
|
+
client = GeminiTtsClient(api_key="k", http_client=fake)
|
|
80
|
+
result = client.text_to_speech.run(**valid_params())
|
|
81
|
+
|
|
82
|
+
assert isinstance(result, CompletedAudioTaskResponse)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_contract_requires_nested_collections():
|
|
86
|
+
client = GeminiTtsClient(api_key="k", http_client=FakeHttp())
|
|
87
|
+
params = valid_params()
|
|
88
|
+
params.pop("speakers")
|
|
89
|
+
|
|
90
|
+
with pytest.raises(ValidationError, match="speakers is required"):
|
|
91
|
+
client.text_to_speech.create(**params)
|