Skip to content

Commit

Permalink
Merge pull request #8 from DavideGalilei/dev
Browse files Browse the repository at this point in the history
🕶 Update 1.2.0 - TTS
  • Loading branch information
DavideGalilei authored May 31, 2021
2 parents b635c6d + 285e14a commit bab9d83
Show file tree
Hide file tree
Showing 12 changed files with 269 additions and 12 deletions.
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,32 @@ print(f"Translation: {translation.text}\nDetected language: {language}")
Output:
```
Translation: Hello how are you? I'm fine, haha.
Detected language: it
Detected language: en
```

### Text to Speech
[Async Example:](/examples/async/tts.py)
```python
import asyncio, aiofiles
from gpytranslate import Translator

async def main():
translator = Translator()
async with aiofiles.open("test.mp3", "wb") as file:
await translator.tts("Hello world!", file=file)

if __name__ == "__main__":
asyncio.run(main())
```

[Sync Example:](/examples/sync/tts.py)
```python
from gpytranslate import SyncTranslator

translator = SyncTranslator()

with open("test.mp3", "wb") as file:
translator.tts("Hello world!", file=file)
```
----
## Development
Expand All @@ -65,4 +90,4 @@ Want to contribute? Pull requests are accepted!
## License
Licensed under the GNU GPLv3.

Click [here](/LICENSE) for futher information.
Click [here](/LICENSE) for further information.
34 changes: 34 additions & 0 deletions examples/async/tts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
gpytranslate - A Python3 library for translating text using Google Translate API.
Copyright (C) 2020-2021 Davide Galilei
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

import asyncio

import aiofiles

from gpytranslate import Translator


async def main():
translator = Translator()

async with aiofiles.open("test.mp3", "wb") as file:
await translator.tts("Hello world!", file=file, targetlang="en")


if __name__ == "__main__":
asyncio.run(main())
6 changes: 6 additions & 0 deletions examples/sync/tts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from gpytranslate import SyncTranslator

translator = SyncTranslator()

with open("test.mp3", "wb") as file:
translator.tts("Hello world!", file=file, targetlang="en")
4 changes: 2 additions & 2 deletions gpytranslate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
from .sync import SyncTranslator
from .types import TranslatedObject

__version__ = "1.1.0"
__all__ = ["Translator", "SyncTranslator", "TranslatedObject"]
__version__ = "1.2.0"
__all__ = ["Translator", "SyncTranslator", "TranslatedObject", "__version__"]
51 changes: 48 additions & 3 deletions gpytranslate/gpytranslate.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,30 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

import io
from collections.abc import Mapping
from typing import Union, Dict, List, Any

import httpx
from aiofiles.threadpool import AsyncBufferedIOBase

from .types import TranslatedObject, BaseTranslator
from .types import TranslatedObject, BaseTranslator, BASE_HEADERS


class Translator(BaseTranslator):
def __init__(
self,
proxies: Dict[str, str] = None,
url: str = "https://translate.googleapis.com/translate_a/single",
tts_url: str = "https://translate.google.com/translate_tts",
headers: dict = ...,
**options
):
self.url = url
self.tts_url = tts_url
self.proxies = proxies
self.options = options
self.headers = BASE_HEADERS if headers is Ellipsis else headers
self.client: httpx.AsyncClient = httpx.AsyncClient(proxies=proxies, **options)

async def translate(
Expand Down Expand Up @@ -99,7 +104,7 @@ async def translate(
"dj": dj,
**extra,
}.items()
if v
if v is not None
}
async with self.client as c:
c: httpx.AsyncClient
Expand All @@ -108,6 +113,7 @@ async def translate(
await c.post(
self.url,
params={**params, "q": text},
headers=self.headers,
)
).json()
if isinstance(text, str)
Expand All @@ -117,6 +123,7 @@ async def translate(
await c.post(
self.url,
params={**params, "q": v},
headers=self.headers,
)
).json()
for k, v in text.items()
Expand All @@ -127,6 +134,7 @@ async def translate(
await c.post(
self.url,
params={**params, "q": elem},
headers=self.headers,
)
).json()
for elem in text
Expand All @@ -146,4 +154,41 @@ async def detect(self, text: Union[str, list, dict]):
else:
raise ValueError("Language detection works only with str, list and dict")

async def tts(
self,
text: Union[str, List[str], Dict[Any, str], Mapping],
file: Union[AsyncBufferedIOBase, io.BytesIO],
targetlang: str = "en",
client: str = "at",
idx: int = 0,
prev: str = "input",
chunk_size: int = 1024,
textlen: int = None,
**extra
) -> AsyncBufferedIOBase:
params = self.parse_tts(
client=client,
targetlang=targetlang,
idx=idx,
prev=prev,
text=text,
textlen=textlen,
extra=extra,
)
async with self.client.stream(
"GET",
url=self.tts_url,
params=params,
headers=self.headers,
) as response:
response: httpx.Response
if isinstance(file, io.BytesIO):
async for chunk in response.aiter_bytes(chunk_size=chunk_size):
file.write(chunk)
else:
file: AsyncBufferedIOBase
async for chunk in response.aiter_bytes(chunk_size=chunk_size):
await file.write(chunk)
return file

__call__ = translate # Backwards compatibility
45 changes: 42 additions & 3 deletions gpytranslate/sync/sync_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,27 @@
"""

from collections.abc import Mapping
from typing import Union, Dict, List, Any
from typing import Union, Dict, List, Any, BinaryIO

import httpx

from ..types import TranslatedObject, BaseTranslator
from ..types import TranslatedObject, BaseTranslator, BASE_HEADERS


class SyncTranslator(BaseTranslator):
def __init__(
self,
proxies: Dict[str, str] = None,
url: str = "https://translate.googleapis.com/translate_a/single",
tts_url: str = "https://translate.google.com/translate_tts",
headers: dict = ...,
**options
):
self.url = url
self.tts_url = tts_url
self.proxies = proxies
self.options = options
self.headers = BASE_HEADERS if headers is Ellipsis else headers
self.client: httpx.Client = httpx.Client(proxies=proxies, **options)

def translate(
Expand Down Expand Up @@ -97,14 +101,15 @@ def translate(
"dj": dj,
**extra,
}.items()
if v
if v is not None
}
with self.client as c:
raw: Union[Mapping, List] = (
(
c.post(
self.url,
params={**params, "q": text},
headers=self.headers,
)
).json()
if isinstance(text, str)
Expand All @@ -113,6 +118,7 @@ def translate(
k: c.post(
self.url,
params={**params, "q": v},
headers=self.headers,
).json()
for k, v in text.items()
}
Expand All @@ -121,6 +127,7 @@ def translate(
c.post(
self.url,
params={**params, "q": elem},
headers=self.headers,
).json()
for elem in text
]
Expand All @@ -139,4 +146,36 @@ def detect(self, text: Union[str, list, dict]):
else:
raise ValueError("Language detection works only with str, list and dict")

def tts(
self,
text: Union[str, List[str], Dict[Any, str], Mapping],
file: BinaryIO,
targetlang: str = "en",
client: str = "at",
idx: int = 0,
prev: str = "input",
chunk_size: int = 1024,
textlen: int = None,
**extra
) -> BinaryIO:
params = self.parse_tts(
client=client,
targetlang=targetlang,
idx=idx,
prev=prev,
text=text,
textlen=textlen,
extra=extra,
)
with httpx.stream(
"GET",
url=self.tts_url,
params=params,
headers=self.headers,
) as response:
response: httpx.Response
for chunk in response.iter_bytes(chunk_size=chunk_size):
file.write(chunk)
return file

__call__ = translate
4 changes: 4 additions & 0 deletions gpytranslate/types/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
from .translated_object import TranslatedObject
from .base_translator import BaseTranslator

BASE_HEADERS: dict = {
"User-Agent": "GoogleTranslate/6.6.1.RC09.302039986 (Linux; U; Android 9; Redmi Note 8)",
}
26 changes: 26 additions & 0 deletions gpytranslate/types/base_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,29 @@ def check(
return {k: self.parse(v) for k, v in raw.items()}
else:
return [self.parse(elem) for elem in raw]

@staticmethod
def parse_tts(
client: str,
targetlang: str,
idx: int,
prev: str,
text: str,
textlen: int,
extra: dict,
) -> Dict[str, Union[str, int]]:
return {
k: v
for k, v in {
"client": client,
"tl": targetlang,
"ie": "utf-8",
"oe": "utf-8",
"idx": idx,
"prev": prev,
"textlen": len(text) if textlen is None else textlen,
"q": text,
**extra,
}.items()
if v is not None
}
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
httpx
aiofiles
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import setuptools

with open("README.md", "r", encoding="utf8") as readme, open(
"requirements.txt", "r", encoding="utf8"
"requirements.txt", "r", encoding="utf8"
) as requirements:
long_description = readme.read()
requires = requirements.read().splitlines(keepends=False)

setuptools.setup(
name="gpytranslate",
version="1.1.0",
version="1.2.0",
author="Davide Galilei",
author_email="davidegalilei2018@gmail.com",
description="A Python3 library for translating text using Google Translate API.",
Expand Down
37 changes: 37 additions & 0 deletions tests/async/test_tts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
gpytranslate - A Python3 library for translating text using Google Translate API.
Copyright (C) 2020-2021 Davide Galilei
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

import os.path

import aiofiles.os
import pytest

from gpytranslate import Translator


@pytest.mark.asyncio
async def test_tts():
translator = Translator()
filename = "test.mp3"

async with aiofiles.open(filename, "wb") as file:
await translator.tts("Hello world!", file=file, targetlang="en")

assert os.path.isfile(filename), "File doesn't exist"

await aiofiles.os.remove(filename)
Loading

0 comments on commit bab9d83

Please sign in to comment.