1

I'm testing a Python package on Windows 10: playsound

It seems have problem with some characters of pathnames, like "c:\sauté" and wide characters. So It can't find the files.

Error 275 for command: open "C:\sauté.wav" alias playsound_0.4091468603477375 Cannot find the specified file. Make sure the path and filename are correct.

I tried to use the unicode version mciSendStringW(). It turned out mciSendStringW won't recognize the encoded command at all. I have no idea what else I can do now.

def winCommand(*command):
    buf = c_buffer(255)
    command = ' '.join(command).encode(getfilesystemencoding())
    errorCode = int(windll.winmm.mciSendStringA(command, buf, 254, 0))
    if errorCode:
        errorBuffer = c_buffer(255)
        windll.winmm.mciGetErrorStringA(errorCode, errorBuffer, 254)
        exceptionMessage = ('\n    Error ' + str(errorCode) + ' for command:'
                            '\n        ' + command.decode() +
                            '\n    ' + errorBuffer.value.decode())
        raise PlaysoundException(exceptionMessage)
    return buf.value

prj site: https://pypi.org/project/playsound/ (Including installation and quickstart guide)

src code: https://raw.githubusercontent.com/TaylorSMarks/playsound/master/playsound.py

Microsoft mciSendString function: https://learn.microsoft.com/en-us/previous-versions/dd757161(v=vs.85)

minion
  • 561
  • 4
  • 17

3 Answers3

0

When using the wide function, mciSendStringW, you should not encode the string. Therefore, your line should read simply command = ' '.join(command). At least that is the case on my Windows10 machine with Python 3.6.

To double check, you can run the code below. The second error code will be 296, which is just a complaint about it being the wrong file type because we create an empty file for testing.

from ctypes import c_buffer, windll
from sys import getfilesystemencoding

if __name__ == '__main__':
    buf = c_buffer(255)
    filesystemencoding = getfilesystemencoding()
    filename = r'.\sauté.wav'
    # create the file if it doesn't exist
    file = open(filename, 'w+')
    file.close()

    # ASCII
    command = 'open ' + filename
    byte_string_command = command.encode(filesystemencoding)

    errorCode = int(windll.winmm.mciSendStringA(byte_string_command, buf, 254, 0))
    # errorCode should be 275: Cannot find the file
    errorBuffer = c_buffer(255)
    windll.winmm.mciGetErrorStringA(errorCode, errorBuffer, 254)
    print("{}: {}".format(errorCode, errorBuffer.value.decode()))

    # Unicode
    errorCode = int(windll.winmm.mciSendStringW(command, buf, 254, 0))
    # errorCode should be 296: The specified file cannot be played
    errorBuffer = c_buffer(255)
    windll.winmm.mciGetErrorStringA(errorCode, errorBuffer, 254)
    print("{}: {}".format(errorCode, errorBuffer.value.decode()))
FiddleStix
  • 3,016
  • 20
  • 21
  • No sound although it returns "0" (successful). Mine is Python3.7 and Win10. I also tried utf-8, utf-16-le, nothing worked. – minion Nov 03 '19 at 12:03
0

No sound although it returns "0" (successful). Mine is Python3.7 and Win10. I also tried utf-8, utf-16-le, nothing worked.

You need to add wait flag. Having this flag on, allows you indeed to wait until the called function completed. The reason you can actually play your file. In the case you removed it, it will initiate the play and immediately after close it.

The Whole code(ASCII):

from ctypes import c_buffer, windll
from sys import getfilesystemencoding

if __name__ == '__main__':
    buf = c_buffer(255)
    filesystemencoding = getfilesystemencoding()
    filename = r'.\file_example.mp3'


    # ASCII
    command = 'open ' + filename + ' alias test2'
    waitcommand = 'play test2 wait'
    byte_string_command = command.encode(filesystemencoding)
    waiting = waitcommand.encode(filesystemencoding)
    errorCode = int(windll.winmm.mciSendStringA(byte_string_command, buf, 254, 0))
    # errorCode should be 275: Cannot find the file
    errorBuffer = c_buffer(255)
    windll.winmm.mciGetErrorStringA(errorCode, errorBuffer, 254)
    print("{}: {}".format(errorCode, errorBuffer.value.decode()))

    errorCode = int(windll.winmm.mciSendStringA(waiting, buf, 254, 0))
    # errorCode should be 275: Cannot find the file
    errorBuffer = c_buffer(255)
    windll.winmm.mciGetErrorStringA(errorCode, errorBuffer, 254)
    print("{}: {}".format(errorCode, errorBuffer.value.decode()))

UNICODE:

from ctypes import c_buffer, windll
from sys import getfilesystemencoding

if __name__ == '__main__':
    buf = c_buffer(255)
    filesystemencoding = getfilesystemencoding()
    filename = r'.\file_example.mp3'


    # ASCII
    command = r'open ' + filename + r' alias test2'
    waitcommand = r'play test2 wait'
    byte_string_command = command.encode(filesystemencoding)
    waiting = waitcommand.encode(filesystemencoding)

    # Unicode
    errorCode = int(windll.winmm.mciSendStringW(command, buf, 254, 0))
    # errorCode should be 296: The specified file cannot be played
    errorBuffer = c_buffer(255)
    windll.winmm.mciGetErrorStringA(errorCode, errorBuffer, 254)
    print("{}: {}".format(errorCode, errorBuffer.value.decode()))

    errorCode = int(windll.winmm.mciSendStringW(waitcommand, buf, 254, 0))
    # errorCode should be 275: Cannot find the file
    errorBuffer = c_buffer(255)
    windll.winmm.mciGetErrorStringA(errorCode, errorBuffer, 254)
    print("{}: {}".format(errorCode, errorBuffer.value.decode()))

If code works, it will return 0: The specified command was carried out.

Note:

  1. sys.getfilesystemencoding()

Return the name of the encoding used to convert between Unicode filenames and bytes filenames. For best compatibility, str should be used for filenames in all cases, although representing filenames as bytes is also supported. Functions accepting or returning filenames should support either str or bytes and internally convert to the system’s preferred representation.

This encoding is always ASCII-compatible.

[os.fsencode()][2] and [os.fsdecode()][3] should be used to ensure that the
correct encoding and errors mode are used.

In the UTF-8 mode, the encoding is utf-8 on any platform.

On macOS, the encoding is 'utf-8'.

On Unix, the encoding is the locale encoding.

On Windows, the encoding may be 'utf-8' or 'mbcs', depending on user
configuration.

Changed in version 3.6: Windows is no longer guaranteed to return
'mbcs'. See PEP 529 and [_enablelegacywindowsfsencoding()][4] for more
information.

Changed in version 3.7: Return ‘utf-8’ in the UTF-8 mode.
  1. Using an Alias

When you open a device, you can use the "alias" flag to specify a device identifier for the device. This flag lets you assign a short device identifier for compound devices with lengthy filenames, and it lets you open multiple instances of the same file or device.

  1. Avoid using wait

If you want play no wait, you need to handle the MCI_NOTIFY, set the callback window handle, and handle the MM_MCINOTIFY when the play has finish.

hwndCallback: Handle to a callback window if the "notify" flag was specified in the command string.

Strive Sun
  • 5,988
  • 1
  • 9
  • 26
0

I'm testing playsound (Python 3.8, Windows 10) as well and have run into the same problem which could be solved using the answers in this thread. Many thanks to all contributors!!!

The trick is in using mciSendStringW instead of mciSendStringA, and the 'wait' flag in the 'play' command.

Here's the modified code:

def _playsoundWin(sound, block = True):

from ctypes import c_buffer, windll
from random import random
from time   import sleep

def winCommand(*command):
    buf = c_buffer(255)
    command = ' '.join(command)
    # errorCode = int(windll.winmm.mciSendStringA(command, buf, 254, 0)) # original line
    errorCode = int(windll.winmm.mciSendStringW(command, buf, 254, 0))
    if errorCode:
        errorBuffer = c_buffer(255)
        # windll.winmm.mciGetErrorStringA(errorCode, errorBuffer, 254) # original line
        windll.winmm.mciGetErrorStringW(errorCode, errorBuffer, 254)
        exceptionMessage = ('\n    Error ' + str(errorCode) + ' for command:'
                            '\n        ' + command +
                            '\n    ' + errorBuffer.value)
        raise PlaysoundException(exceptionMessage)
    return buf.value

alias = 'playsound_' + str(random())
winCommand('open "' + sound + '" alias', alias)
# winCommand('set', alias, 'time format milliseconds') # is not needed
# durationInMS = winCommand('status', alias, 'length') # returns bytes!
# durationInMS = durationInMS.decode() # needed for the original command
# winCommand('play', alias, 'from 0 to', durationInMS)
winCommand('play', alias, 'wait') # 'wait' does the trick

if block:
    pass
    # sleep(float(durationInMS) / 1000.0) # don't know it's purpose