演示如何使用 powershell 编写调用 AI 查询单词释义、调用 TTS 生成发音文件并进行播放的例子
可以定义 shell 环境, code $PROFILE 加入快捷调用函数,比如 wd word (脚本见最后)
单词查询脚本
<#
.DESCRIPTION
查询单词
.EXAMPLE
.\SearchWork.ps1 -word "example"
.NOTES
- 单词语音合成使用 edge-tts 生成并播放,需提前安装 python 应用 edge-tts , 安装方法: pipx (pip) install edge-tts
- edge-tts 可能在 ~/.local/bin/ 下, 需要将该路径添加到系统 PATH 中, 以便 powershell 脚本调用
- 语音合成以可以使用本地 TTS 引擎, Piper TTS, 有空再研究
- Windows 下使用 PresentationCore.dll 中的 MediaPlayer 类播放音频。
- linux 下使用 mpg123 播放音频, 需要提前安装 mpg123, 安装方法: sudo apt install mpg123
- 单词释义使用 code365scripts.openai 模块调用 AI 接口,配置了一个 ty 的 profile, 见: https://blog.xsoft.ltd/2025/10/13/1456/
- 单词释义也可以调用接口 https://api.dictionaryapi.dev/api/v2/entries/en/$($this.word) 获取,以后根据需要再研究
- 需要安装 glow 用于渲染 markdown, 安装方法: winget install glow / https://github.com/charmbracelet/glow
#>
param (
[string]$word = "example"
)
$ErrorActionPreference = "Stop"
Import-Module code365scripts.openai -Force
# 后台工作的辅助类
class ThreadRunner : System.IDisposable {
ThreadRunner([runspace]$runspace, [powershell]$ps, [System.IAsyncResult] $asyncResult) {
$this._runspace = $runspace
$this._ps = $ps
$this._asyncResult = $asyncResult
}
[runspace] $_runspace
[powershell] $_ps
[System.IAsyncResult] $_asyncResult
[void] wait() {
$this._ps.EndInvoke($this._asyncResult)
$this.Dispose()
}
static [ThreadRunner]run([scriptblock]$scriptBlock, [object[]]$arguments) {
$runspace = [runspacefactory]::CreateRunspace()
$runspace.ApartmentState = "STA"
$runspace.Open()
$ps = [powershell]::Create()
$ps.Runspace = $runspace
$ps.AddScript($scriptBlock)
if ($arguments) {
foreach ( $arg in $arguments ) {
$ps.AddArgument($arg)
}
}
$asyncResult = $ps.BeginInvoke()
return [ThreadRunner]::new($runspace, $ps, $asyncResult)
}
[void] Dispose() {
$this._ps.Dispose()
$this._runspace.Close()
$this._runspace.Dispose()
}
}
# 单词发音辅助类
class Pronunciation : System.IDisposable {
Pronunciation() {
$name = [System.IO.Path]::GetRandomFileName() + ".mp3"
$tmpDir = [System.IO.Path]::GetTempPath()
$this.voiceFile = Join-Path -Path $tmpDir -ChildPath $name
}
# 存放发音文件路径
[string] $voiceFile
static [bool] isWindows() {
return $global:IsWindows
}
# 播放音频, 仅在 windows 下可用
static [void] playVoice([string] $file) {
if ( -not (Test-Path -Path $file) ) {
throw "发音文件不存在: $($file)"
}
Add-Type -AssemblyName PresentationCore
$player = New-Object System.Windows.Media.MediaPlayer
$player.Open($file)
$player.Volume = 1 # 确保音量最大
try {
# 等待加载完成
while ($player.NaturalDuration.HasTimeSpan -eq $false) {
Start-Sleep -Milliseconds 100
}
# 播放
$player.Play()
# 等待播放结束(根据时长)
$duration = $player.NaturalDuration.TimeSpan.TotalSeconds
Start-Sleep -Seconds ([Math]::Ceiling($duration))
}
finally {
$player.Stop()
$player.Close()
}
}
[ThreadRunner] play() {
# 因为后台无法直接调用 playVoice 方法,所以这里包装入 scriptblock 传递过去
$palyVoiceScript = {
param ($file)
[Pronunciation]::playVoice($file)
}
if(-not [Pronunciation]::isWindows()){
$palyVoiceScript = {
param ($file)
& mpg123 $file
}
}
$script = {
param ($file, [scriptblock]$playVoice)
if (-not (Test-Path -Path $file)) {
throw "发音文件生成失败: $($file)"
}
# 播放5次
for ($idx = 0; $idx -lt 5; $idx++) {
& $playVoice $file
Start-Sleep -Seconds 0.5
}
}
$runner = [ThreadRunner]::run($script, @($this.voiceFile, $palyVoiceScript))
return $runner
}
# 获取 edge-tts 可执行文件路径
[string] getEdgeTtsPath() {
if ([Pronunciation]::isWindows()) {
# Windows 下直接使用 edge-tts 命令
return 'edge-tts'
}
# ubuntu 下无法定位 edge-tts , 这里指定一下完整的路径
$localBinPath = Join-Path -Path $env:HOME -ChildPath '.local' | join-path -childpath 'bin' | join-path -childpath 'edge-tts'
if (Test-Path -Path $localBinPath) {
return $localBinPath
}
throw "edge-tts 未安装, $localBinPath。"
}
# 生成发音文件
[ThreadRunner] pronounce([string] $word) {
[string]$appPath = $this.getEdgeTtsPath();
$script = {
param ($word, $file, $appPath)
# 生成发音文件, ai 推荐这几位的发音 en-US-JennyNeural | en-US-EmmaNeural | en-US-AriaNeural
& $appPath --text $word --voice "en-US-JennyNeural" --write-media $file
if (-not (Test-Path -Path $file)) {
throw "发音文件生成失败: $($file)"
}
}
$runner = [ThreadRunner]::run($script, @($word, $this.voiceFile, $appPath))
return $runner
}
[void] Dispose() {
if (Test-Path -Path $this.voiceFile) {
Remove-Item -Path $this.voiceFile -Force
}
}
}
# 判断当前是否是 windows 系统
if ($IsWindows) {
# 设置代码页为 UTF-8
chcp 65001 | Out-Null
}
else {
# 三方组件中使用了变量 USERPROFILE , 在非 windows 系统下该值为空, 需要设置为 HOME
$env:USERPROFILE = $env:HOME
}
# 设置 PowerShell 输出编码为 UTF-8
$OutputEncoding = [System.Text.UTF8Encoding]::new()
$pronunciation = [Pronunciation]::new()
try {
# 生成发音
$runner = $pronunciation.pronounce($word)
# 查询释义
$markdown = New-ChatCompletions -context @{word = $word } -system '你是一个专业、权威的智能词典,擅长解释词语、术语、缩略语、成语和俚语。你能够:\n0. 给出清晰、简洁的定义及音标。\n1. 该单词背诵的技巧,可以是任意类型的技巧 n2. 指出词性、语法类别(如名词、动词、形容词等)。\n3. 提供一个或多个相关例句。\n4. 提供常见用法、词源、近义词或反义词(如有)。\n5. 支持中英文词语,自动识别语言。\n请保持结构清晰,并尽可能使用 Markdown 格式输出,便于阅读。尽量使用中文进行回复。' -prompt '请给出{{word}}的解释' -profile 'ty'
# 生成语音文件
$runner.wait()
# 渲染 markdown 输出, & 新开进各程执行 glow
$markdown | & glow
# 播放发音
$runner = $pronunciation.play()
# 等待发音完成
$runner.wait()
}
finally {
$pronunciation.Dispose()
}
调用脚本
'vim $PROFILE' 或
code $PROFILE.CurrentUser*code $PROFILE.AllUsers*function wd {
Write-Host "[Debug] WordDict args: $args" -ForegroundColor Green
$path = '/opt/bin/search-word.ps1'
if($IsWindows){
$path= 'C:\Users\icoms\bin\WordDict.ps1'
}
if (Test-Path $path) {
& $path @args
} else {
Write-Error "WordDict.ps1 not exist"
}
}