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.
@@ -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,5 @@
1
+ """Gemini TTS resources."""
2
+
3
+ from .text_to_speech import TextToSpeech
4
+
5
+ __all__ = ["TextToSpeech"]
@@ -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)