pywinrt / python-winsdk

Python package with bindings for Windows SDK
https://python-winsdk.readthedocs.io
MIT License
77 stars 8 forks source link

DataReader.read_bytes not available in this version of Windows #26

Closed Diceycle closed 1 year ago

Diceycle commented 1 year ago

I'm completely new to WinRT and stumbled over its SpeechSynthesis capabilities. Inspired by this snippet https://github.com/ankitects/anki/issues/996#issuecomment-739452183 I want to read the synthesised voice into a .wav file, however the code runs really slow.

import asyncio

import winsdk.windows.media.speechsynthesis as speechsynthesis
import winsdk.windows.storage.streams as streams

async def writeTestFile():
    synthesizer = speechsynthesis.SpeechSynthesizer()

    stream = await synthesizer.synthesize_text_to_stream_async("Reading Byte by Byte is really slow")
    inputStream = stream.get_input_stream_at(0)
    dataReader = streams.DataReader(inputStream)
    dataReader.load_async(stream.size)
    f = open("test.wav", "wb")
    for x in range(stream.size):
        f.write(bytes([dataReader.read_byte()]))
    f.close()

asyncio.run(writeTestFile())

Understandably the overhead of reading the stream byte by byte is killing the performance. I was able to speed it up significantly by reading longer chunks

for x in range(stream.size // 8):
    f.write(dataReader.read_uint64().to_bytes(8, "big"))

however ideally I would be able to read an arbitrary amount of bytes or the entire stream in one operation. Attempting to use dataReader.read_bytes(8) is met with AttributeError: method 1 args is not available in this version of Windows

My Windows version should be an up to date Windows 10(10.0.19045.2728) so I'm not sure what causes the error. Any advice as to how to read from an InputStream efficiently or alternative suggestions would be greatly appreciated.

dlech commented 1 year ago

This will be fixed (and the API changed) in the next release.

dlech commented 1 year ago

Did you try just calling stream.read_async()?

Diceycle commented 1 year ago

Hey thanks for the quick response! I did try read_async but did not know what to to with the Buffer Object afterwards. The Buffer itself isn't any easier to read from and I wasn't able to open a file to read the buffer into.

buffer = streams.Buffer(stream.size)
await stream.read_async(buffer, stream.size, streams.InputStreamOptions(0))
file = await storage.DownloadsFolder.create_file_async("test.wav")
storage.FileIO.write_buffer_async(file, buffer)

The code above fails in line 3 with OSError: [WinError -2147286953] Invalid parameter error Attempting to create a file with storage.ApplicationData.current.local_folder.create_file_async() like in this example also did not work because AttributeError: type object '_winsdk_Windows_Storage.ApplicationData' has no attribute 'current'

dlech commented 1 year ago

AttributeError: type object '_winsdk_Windows_Storage.ApplicationData' has no attribute 'current'

This is another thing that will be fixed (API change) in the next release. Currently static properties are implemented as functions, so get_current() instead of current.

dlech commented 1 year ago

OSError: [WinError -2147286953] Invalid parameter error

Failing because the file already exists?

dlech commented 1 year ago

You can use the buffer object as python bytes-like object, so

with open('test.wav', 'wb') as f:
    f.write(buffer)

should work instead of using Windows APIs.

Diceycle commented 1 year ago

OSError: [WinError -2147286953] Invalid parameter error

Failing because the file already exists?

Not as far as I can tell. No matter what String I put as the parameter, its the same error.

You can use the buffer object as python bytes-like object, so

with open('test.wav', 'wb') as f:
    f.write(buffer)

should work instead of using Windows APIs.

That worked like a charm. I tried accessing __bytes__ directly before but I guess Python doesn't like that.

Thanks a lot for your quick help.

dlech commented 1 year ago

I tried accessing __bytes__ directly before but I guess Python doesn't like that.

Yeah, that is not the same as the buffer protocol. But bytes(buffer) works if you really need a bytes object - however, it is usually much more efficient to pass the buffer directly to other functions.