如何使用 unicode 版本的 Windows API:mciSendString()、Python

Posted

技术标签:

【中文标题】如何使用 unicode 版本的 Windows API:mciSendString()、Python【英文标题】:How to use unicode version Windows API: mciSendString(), Python 【发布时间】:2019-11-01 12:36:59 【问题描述】:

我正在 Windows 10 上测试 Python 包:playsound

路径名的某些字符似乎有问题,例如“c:\sauté”和宽字符。所以它找不到文件。

命令的错误 275: 打开“C:\sauté.wav”别名 playsound_0.4091468603477375 找不到指定的文件。确保路径和文件名正确。

我尝试使用 unicode 版本mciSendStringW()。原来 mciSendStringW 根本无法识别编码命令。我不知道我现在还能做什么。

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 站点:https://pypi.org/project/playsound/(包括安装和快速入门指南)

源代码:https://raw.githubusercontent.com/TaylorSMarks/playsound/master/playsound.py

微软 mciSendString 函数: https://docs.microsoft.com/en-us/previous-versions/dd757161(v=vs.85)

【问题讨论】:

“好像有问题”。究竟是什么问题? 找不到路径名(文件)。 你必须传递一个 UTF16 编码的字符串。 【参考方案1】:

当使用宽函数 mciSendStringW 时,您不应该对字符串进行编码。因此,您的行应该简单地读作command = ' '.join(command)。至少在我的装有 Python 3.6 的 Windows10 机器上是这样。

要仔细检查,您可以运行以下代码。第二个错误代码将是 296,这只是抱怨它是错误的文件类型,因为我们创建了一个空文件进行测试。

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()))

【讨论】:

没有声音,虽然它返回“0”(成功)。我的是 Python3.7 和 Win10。我也试过utf-8、utf-16-le,没用。【参考方案2】:

虽然返回“0”(成功),但没有声音。我的是 Python3.7 和 Win10。我也试过 utf-8、utf-16-le,没用。

您需要添加wait 标志。有了这个标志,你确实可以 等到调用的函数完成。你实际上可以的原因 播放您的文件。如果您将其删除,它将启动播放 并在关闭后立即。

整个代码(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()))

如果代码有效,它将返回0: The specified command was carried out

注意:

    sys.getfilesystemencoding()

返回用于在 Unicode 之间转换的编码名称 文件名和字节文件名。为了获得最佳兼容性, str 应该是 在所有情况下都用于文件名,尽管将文件名表示为 也支持字节。接受或返回文件名的函数 应该支持 str 或 bytes 并在内部转换为 系统的首选表示。

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.
    Using an Alias

当你打开一个设备时,你可以使用“alias”标志来指定一个 设备的设备标识符。此标志可让您分配一个短 具有长文件名的复合设备的设备标识符,以及 它允许您打开同一文件或设备的多个实例。

    Avoid using wait

如果你想玩没有等待,你需要处理MCI_NOTIFY,设置 回调窗口句柄,并在处理MM_MCINOTIFY时 游戏结束。

hwndCallback:如果有“通知”标志,则处理回调窗口 在命令字符串中指定。

【讨论】:

@minion 嗨,这个答案对你有用吗?【参考方案3】:

我也在测试 playsound(Python 3.8,Windows 10)并且遇到了同样的问题,可以使用这个线程中的答案来解决。非常感谢所有贡献者!!!

诀窍在于使用 mciSendStringW 代替 mciSendStringA,并在“播放”命令中使用“等待”标志。

这是修改后的代码:

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 

【讨论】:

以上是关于如何使用 unicode 版本的 Windows API:mciSendString()、Python的主要内容,如果未能解决你的问题,请参考以下文章

Windows控制台对Unicode有什么限制?

如何使用 Perl 在 Windows 中创建 unicode 文件名

如何在Windows命令行中使用unicode字符?

如何审核我的 Windows 应用程序以进行正确的 Unicode 处理?

如何将使用 tchar.h 处理 unicode 的 Windows C++ 移植到 iOS 应用程序

非Unicode编码的软件如何在Windows系统上运行