Classes/AudioHelper.psm1

using module ".\OpenAiAudio.psm1"
using module ".\Dialog.psm1"
using module "..\Private\OutHelper.psm1"

class AudioHelper {
    [string]$Model = $null
    [string]$UserVoice = "fable"
    [string]$AssistantVoice = "onyx"
    [string]$Response_Format = $null
    [decimal]$Speed = $null
    [string]$AuthToken
    [bool]$ShowProgress
    [int]$TurnDelay = 600
    [bool]$ConcatUsingFfmpeg = $true
    [string]$FfmpegExecutablePath = $null
    [int]$FfmpegQuality = 3

    DialogToAudioFile([Dialog]$dialog, [string]$fileName = "") {
        $audioApi = [OpenAiAudio]::new($this.AuthToken)
        # $audioApi._debug = $true
        if(!$fileName) {
            $ext = if($this.Response_Format) { $this.Response_Format } else { "mp3" }
            $fileName = "dialog-$(Get-Date -Format "yyyyMMdd_HHmmss").$ext"
        }

        if(!$this.FfmpegExecutablePath) {
            $this.FfmpegExecutablePath = "ffmpeg"
        }

        $tmpFiles = @()
        $audioRequest = [OpenAiAudioSpeechRequest]::new()
        if($this.Model) { $audioRequest.Model = $this.Model }
        if($this.Speed) { $audioRequest.Speed = $this.Speed }  
        if($this.Response_Format) { $audioRequest.Response_Format = $this.Response_Format }

        $messages = $dialog.Messages | Where-Object { $_.Role -ne "system" }
        $step = 0; $stepsTotal = $messages.Count + ([int]$this.ConcatUsingFfmpeg * 2)
        function Update-Progress($stepNo, $description) {
            if($this.ShowProgress) {
                Write-Progress -Activity "AudioHelper" -Status "$description $($stepNo)/$stepsTotal" -PercentComplete (($stepNo/$stepsTotal*100))
            }
        }

        foreach($dm in $messages) {
            Update-Progress (++$step) "Getting audio snippet"

            if($dm.Role -eq "system") { continue }
            $audioRequest.Voice = if($dm.Role -eq "user") { $this.UserVoice } else { $this.AssistantVoice }
            $audioRequest.Input = $dm.Content

            $tmpFile = "$fileName-$($dm.Role)-$($tmpFiles.Count).$($fileName.Split(".")[-1])"
            Write-Debug "AudioHelper: Getting audio snippet: $tmpFile"
            $tmpFiles += $tmpFile
            $audioApi.SpeechToFile($audioRequest, $tmpFile)       
        }

        if($this.ConcatUsingFfmpeg) {
            # build ffmpeg command that will stich all the audio files (tmpFiles) together
            # ffmpeg -i "file1.mp3" -i "file2.opus" -i "file3.aac"
            # -filter_complex "[0:0][1:0][2:0]concat=n=3:v=0:a=1[out]"
            # -map "[out]" "output.mp3"
            Update-Progress (++$step) "Concatenating audio files"
            $ffmpegInputArgs = ""
            $ffmpegFilterArgs = ""
            $ffmpegConcat = ""
            foreach($tmpFile in $tmpFiles) {
                $ffmpegInputArgs += "-i `"$tmpFile`" "
                $index = $tmpFiles.IndexOf($tmpFile)
                $ffmpegFilterArgs += "[$($index):0]adelay=$($this.TurnDelay)[a$($index)];"
                $ffmpegConcat += "[a$($index)]"
            }
            $ffmpegFilterArgs += $ffmpegConcat + "concat=n=$($tmpFiles.Count):v=0:a=1[out]"
            $ffmpegCmd = "$($this.FfmpegExecutablePath) $ffmpegInputArgs -filter_complex `"$($ffmpegFilterArgs)`" -map `"[out]`" -q:a $($this.FfmpegQuality) `"$fileName`""
            Write-Debug "AudioHelper: Executing ffmpeg command: $ffmpegCmd"

            Invoke-Expression -Command $ffmpegCmd
            if($LASTEXITCODE -ne 0) {
                throw "ffmpeg failed with exit code: $LASTEXITCODE. Command: $ffmpegCmd"
            }

            Update-Progress (++$step) "Cleaning up"
            Remove-Item $tmpFiles
        }

        Write-Progress -Activity "AudioHelper" -Completed
    }
}