如何使用 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的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Perl 在 Windows 中创建 unicode 文件名
如何审核我的 Windows 应用程序以进行正确的 Unicode 处理?