Example: Websocket example, asynchronous client with iterator

This example shows:

  1. How to open a websocket connection using an async context manager.

  2. Generate a take.

  3. How to iterate over the resulting status and audio messages using iter_request().

  4. How to make a request with and without chunks enabled. (Add argument --chunks.)

Example output
$ python3 -m examples.websocket_example_async_iter
Found Daisys Speak API version=1 minor=0
[0.002] Take status was changed to: WAITING.
[0.024] Take status was changed to: STARTED.
[0.761] New part being received.
[0.761] Received audio chunk of size 245760.
[1.197] Take status was changed to: PROGRESS_50.
[1.200] New part being received.
[1.200] Received audio chunk of size 108544.
[2.595] Take status was changed to: READY.
Deleting take t01jqrj756qrvrqaw59zgyxpcrw: True
Example output (chunks enabled)
$ python3 -m examples.websocket_example_async_iter --chunks
Found Daisys Speak API version=1 minor=0
[0.002] Take status was changed to: WAITING.
[0.023] Take status was changed to: STARTED.
[0.318] New part being received.
[0.318] Received audio chunk of size 4096.
[0.331] Received audio chunk of size 4096.
[0.344] Received audio chunk of size 4096.
[0.358] Received audio chunk of size 4096.
[0.371] Received audio chunk of size 4096.
[0.384] Received audio chunk of size 4096.
[0.397] Received audio chunk of size 4096.
[0.411] Received audio chunk of size 4096.
[0.424] Received audio chunk of size 4096.
[0.437] Received audio chunk of size 4096.
[0.450] Received audio chunk of size 4096.
[0.463] Received audio chunk of size 4096.
[0.472] Received audio chunk of size 4096.
[0.482] Received audio chunk of size 4096.
[0.492] Received audio chunk of size 4096.
[0.503] Received audio chunk of size 4096.
[0.513] Received audio chunk of size 4096.
[0.523] Received audio chunk of size 4096.
[0.533] Received audio chunk of size 4096.
[0.543] Received audio chunk of size 4096.
[0.553] Received audio chunk of size 4096.
[0.564] Received audio chunk of size 4096.
[0.575] Received audio chunk of size 4096.
[0.584] Received audio chunk of size 4096.
[0.595] Received audio chunk of size 4096.
[0.605] Received audio chunk of size 4096.
[0.615] Received audio chunk of size 4096.
[0.626] Received audio chunk of size 4096.
[0.636] Received audio chunk of size 4096.
[0.646] Received audio chunk of size 1024.
[1.002] Take status was changed to: PROGRESS_50.
[1.005] New part being received.
[1.005] Received audio chunk of size 4096.
[1.015] Received audio chunk of size 4096.
[1.024] Received audio chunk of size 4096.
[1.033] Received audio chunk of size 4096.
[1.043] Received audio chunk of size 4096.
[1.053] Received audio chunk of size 4096.
[1.064] Received audio chunk of size 4096.
[1.074] Received audio chunk of size 4096.
[1.084] Received audio chunk of size 4096.
[1.095] Received audio chunk of size 4096.
[1.105] Received audio chunk of size 4096.
[1.115] Received audio chunk of size 4096.
[1.125] Received audio chunk of size 4096.
[1.135] Received audio chunk of size 2048.
[2.641] Take status was changed to: READY.
Deleting take t01jqrk04k7fhrdgs764bv6h7p1: True
examples/websocket_example_async_iter.py
 1import sys, os, asyncio, time
 2from typing import Optional
 3from daisys import DaisysAPI
 4from daisys.v1.speak import (DaisysWebsocketGenerateError, HTTPStatusError, Status, TakeResponse,
 5                             StreamOptions, StreamMode)
 6
 7# Override DAISYS_EMAIL and DAISYS_PASSWORD with your details!
 8EMAIL = os.environ.get('DAISYS_EMAIL', 'user@example.com')
 9PASSWORD = os.environ.get('DAISYS_PASSWORD', 'pw')
10
11# Please see tokens_example.py for how to use an access token instead of a password.
12
13async def main(chunks):
14    async with DaisysAPI('speak', email=EMAIL, password=PASSWORD) as speak:
15        print('Found Daisys Speak API', await speak.version())
16
17        # A buffer to receive parts; we initialize with a single empty bytes()
18        # because we will use it to accumulate chunks of the current wav file
19        # there.  In total we will end with a list of wav files, one for each
20        # part.  Parts are bits of speech, usually full sentences, that end with
21        # silence.
22        audio_wavs = [bytes()]
23
24        # Assume at least one voice is available
25        voice = (await speak.get_voices())[0]
26
27        async with speak.websocket(voice_id=voice.voice_id) as ws:
28            # Time the latency from when we submit the request until each part
29            # is received.
30            t0 = time.time()
31
32            # Submit a request to generate a take over the websocket connection.
33            generate_request_id = await ws.generate_take(
34                voice_id=voice.voice_id,
35                text='Hello from Daisys websockets! How may I help you?',
36
37                # Optional
38                stream_options=StreamOptions(mode=StreamMode.CHUNKS) if chunks else None,
39            )
40
41            # The use of an interator simplifies streaming, here we show how to
42            # get both status and audio chunks from the same iterator.
43            async for take_id, take, header, audio in ws.iter_request(generate_request_id):
44                now = time.time() - t0
45                if take is not None:
46                    print(f'[{now:.03f}] Take status was changed to: {take.status.name}.')
47                if header is not None:
48                    print(f'[{now:.03f}] New part being received.')
49                if audio is not None:
50                    print(f'[{now:.03f}] Received audio chunk of size {len(audio)}.')
51
52        # Delete the take
53        if take_id:
54            print(f'Deleting take {take_id}:', await speak.delete_take(take_id))
55
56if __name__=='__main__':
57    try:
58        asyncio.run(main('--chunks' in sys.argv[1:]))
59    except HTTPStatusError as e:
60        try:
61            print(f'HTTP error status {e.response.status_code}: {e.response.json()["detail"]}, {e.request.url}')
62        except:
63            print(f'HTTP error status {e.response.status_code}: {e.response.text}, {e.request.url}')