Transcript of customer's leg after queuing - Stasis

I was successful in transcribing with the vosk model, using the basic context as below:

[from-internal]

exten => 4,1,Answer()
same => n,Stasis(v)
same => n,Hangup()

Vosk code:

#!/usr/bin/python3

import anyio
import asyncari
import logging
import aioudp
import os
import vosk
import array
import json
ast_host = os.getenv("AST_HOST", '127.0.0.1')
ast_port = int(os.getenv("AST_ARI_PORT", 8088))
ast_url = os.getenv("AST_URL", 'http://%s:%d/'%(ast_host,ast_port))
ast_username = os.getenv("AST_USER", 'user-here')
ast_password = os.getenv("AST_PASS", 'password-here')
ast_app = os.getenv("AST_APP", 'v')


model = vosk.Model('vosk-model-pt-fb-v0.1.1-20220516_2113/')
#model = vosk.Model(lang='en-us')
channels = {}

class Channel:

    async def rtp_handler(self, connection):
        async for message in connection:
            data = array.array('h', message[12:])
            data.byteswap()
            if self.rec.AcceptWaveform(data.tobytes()):
                res = self.rec.Result()
            else:
                res = self.rec.PartialResult()
           print(res)

    async def init(self, client, channel):
        self.port = 45000 + len(channels)
        self.rec = vosk.KaldiRecognizer(model, 16000)
        self.udp = aioudp.serve("127.0.0.1", self.port, self.rtp_handler)
        await self.udp.__aenter__()

        bridge = await client.bridges.create(type='mixing')
        media_id = client.generate_id()

        # Alterando para _apps
        await client.channels.externalMedia(channelId=media_id, app=client._apps, external_host='127.0.0.1:' + str(self.port), format='slin16')

        await bridge.addChannel(channel=[media_id, channel.id])

async def statis_handler(objs, ev, client):
    channel = objs['channel']
    channel.answer()
    if 'UnicastRTP' in channel.name:
         return

    local_channel = Channel()
    await local_channel.init(client, channel)
    channels[channel.id] = local_channel

async def main():
     async with asyncari.connect(ast_url, ast_app, ast_username, ast_password) as client:
         async with client.on_channel_event('StasisStart') as listener:
             async for objs, event in listener:
                  await statis_handler(objs, event, client)

if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG)
    anyio.run(main)

I would like some guidance as to whether it is possible to transcribe the customer’s leg after going through the queue application?

Hi. Not sure what the problem is but you can use spoofing channel to select only customer. Something like

        # First create snooping channel and put it into a snooping bridge
        await client.channels.snoopChannelWithId(app=client._app, appArgs='snooping', channelId=channel.id, snoopId = channel.id + "_snoop", spy="in")
        snoop_bridge = await client.bridges.create(type='mixing')
        await client.channels.externalMedia(channelId=channel.id + "_media", app=client._app, external_host='localhost:' + str(self.port), format='slin16')
        await snoop_bridge.addChannel(channel=[channel.id + "_media", channel.id + "_snoop"])

        # Dial out and create second bridge
        outgoing_channel = await client.channels.originate(endpoint="PJSIP/1002", app = client._app, appArgs = "dialed")

        await client.channels.snoopChannelWithId(app=client._app, appArgs='snooping', channelId=outgoing_channel.id, snoopId = outgoing_channel.id + "_snoop", spy="in")
        snoop_bridge = await client.bridges.create(type='mixing')
        await client.channels.externalMedia(channelId=outgoing_channel.id + "_media", app=client._app, external_host='localhost:' + str(self.port + 1), format='slin16')
        await snoop_bridge.addChannel(channel=[outgoing_channel.id + "_media", outgoing_channel.id + "_snoop"])

        # Bridge calls
        main_bridge = await client.bridges.create(type='mixing')
        await snoop_bridge.addChannel(channel=[channel.id, outgoing_channel.id])
aiohttp
anyio==3.7.0
asks
asyncari==0.10.11
asyncswagger11==0.12.1
h11==0.12.0
aioudp
vosk>=0.3.45
google-cloud-speech
httpx==0.20.0
websocket-client==1.8.0
#!/usr/bin/python3

import anyio
import asyncari
import logging
import aioudp
import os
import vosk
import array
import sys
import aiohttp

ast_host = os.getenv("AST_HOST", '127.0.0.1')
ast_port = int(os.getenv("AST_ARI_PORT", 8088))
ast_url = os.getenv("AST_URL", 'http://%s:%d/'%(ast_host,ast_port))
ast_username = os.getenv("AST_USER", 'asterisk')
ast_password = os.getenv("AST_PASS", 'asterisk')
ast_app = os.getenv("AST_APP", 'v')

model = vosk.Model('vosk-model-pt-fb-v0.1.1-20220516_2113/')
channels = {}

class Channel:
    async def rtp_handler(self, connection):
        async for message in connection:
            data = array.array('h', message[12:])
            data.byteswap()
            if self.rec.AcceptWaveform(data.tobytes()):
                res = self.rec.Result()
            else:
                res = self.rec.PartialResult()
            print(res)

    async def init(self, client, channel):
        self.port = 45000 + len(channels)
        self.rec = vosk.KaldiRecognizer(model, 16000)
        self.udp = aioudp.serve("127.0.0.1", self.port, self.rtp_handler)
        await self.udp.__aenter__()

        bridge = await client.bridges.create(type='mixing')
        media_id = client.generate_id()
        logging.info("O canal do Unicast é: " + str(media_id))

        # Cria um canal de mídia externa
        await client.channels.externalMedia(
            channelId=media_id,
            app=ast_app,
            external_host='127.0.0.1:' + str(self.port),
            format='slin16'
        )

        # Adiciona o canal espionado e o canal de mídia externa à ponte
        await bridge.addChannel(channel=[media_id, channel.id])


async def create_channel_in_stasis(client, endpoint):
    """Cria um novo canal no contexto Stasis."""
    channel = await client.channels.originate(
        endpoint=endpoint,
        app=ast_app,
        appArgs=['stasis-start']
    )
    return channel

async def main(channel_id):
    async with asyncari.connect(ast_url, ast_app, ast_username, ast_password) as client:
        # Cria um canal espionado (snoop) do canal existente
        snoop_channel = await client.channels.snoopChannel(
            channelId=channel_id,
            app=ast_app,
            spy='in',  # Espiona entrada e saída de áudio
            whisper='none'  # Não interfere no áudio original
        )

        # Inicializa o canal local para transcrição
        local_channel = Channel()
        await local_channel.init(client, snoop_channel)
        channels[snoop_channel.id] = local_channel

        # Mantém o script em execução para evitar que o aplicativo Stasis seja desativado
        await anyio.sleep_forever()

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Uso: {} <channel_id>".format(sys.argv[0]))
        sys.exit(1)

    logging.basicConfig(level=logging.INFO)
    anyio.run(main, sys.argv[1])

After the call enters the queue, search for the channel to be transcribed using:
core show channels or curl -u asterisk:asterisk http://localhost:8088/ari/channels

Then I ran the script in Python:
python3 script.py

Improvements can be made using flask or fastapi to receive the channel via post as soon as the call is answered in the queue using
exten => 300,n(qcall),Queue(300,${QOPTIONS},${QAANNOUNCE},${QMAXWAIT},${QAGI},${QGOSUB}channel,s,1,${QRULE},${QPOSITION},)

[channel]; Send channel to flask
exten => s,1,Progress()
exten => s,n,Noop(${MCORGCHAN})
exten => s,n,System(/usr/bin/python3 /var/lib/asterisk/agi-bin/flask-canal.py ${MCORGCHAN} &)
exten => s,n,Noop(${MEDIA_ID}); MEDIA_ID must be sent via global variable in the flask as soon as transcription starts
exten => s,n,Return()

[queueMemberCall-hangup]
include => queueMemberCall-hangup-custom
exten => s,1,Noop(${ARG1})
exten => s,n,UserEvent(QueueMemberCallHangup,QAGENT:${ARG1},UNIQ:${ARG2},Queue:${ARG3},DIALSTATUS:${DIALSTATUS})
exten => s,n,System(/usr/bin/python3 /var/lib/asterisk/agi-bin/unicast-del.py ${MEDIA_ID} &); Remove UnicastChannel
exten => s,n,Return()

Flask code:

#!/usr/bin/python3

from flask import Flask, request, jsonify
import anyio
import asyncari
import logging
import aioudp
import os
import vosk
import array
import sys
import aiohttp

app = Flask(__name__)

ast_host = os.getenv("AST_HOST", '127.0.0.1')
ast_port = int(os.getenv("AST_ARI_PORT", 8088))
ast_url = os.getenv("AST_URL", 'http://%s:%d/'%(ast_host,ast_port))
ast_username = os.getenv("AST_USER", 'asterisk')
ast_password = os.getenv("AST_PASS", 'asterisk')
ast_app = os.getenv("AST_APP", 'v')

model = vosk.Model('vosk-model-pt-fb-v0.1.1-20220516_2113/')
channels = {}

class Channel:
    async def rtp_handler(self, connection):
        async for message in connection:
            data = array.array('h', message[12:])
            data.byteswap()
            if self.rec.AcceptWaveform(data.tobytes()):
                res = self.rec.Result()
            else:
                res = self.rec.PartialResult()
            print(res)

    async def init(self, client, channel):
        self.port = 45000 + len(channels)
        self.rec = vosk.KaldiRecognizer(model, 16000)
        self.udp = aioudp.serve("127.0.0.1", self.port, self.rtp_handler)
        await self.udp.__aenter__()

        bridge = await client.bridges.create(type='mixing')
        media_id = client.generate_id()

        # Define uma variável global no Asterisk usando o endpoint /asterisk/variable
        async with aiohttp.ClientSession() as session:
            params = {
                'variable': 'MEDIA_ID',
                'value': media_id
            }
            async with session.post(
                f"{ast_url}ari/asterisk/variable",
                params=params,
                auth=aiohttp.BasicAuth(ast_username, ast_password)
            ) as response:
                if response.status != 204:
                    print(f"Failed to set global variable: {await response.text()}")

        # Cria um canal de mídia externa
        await client.channels.externalMedia(
            channelId=media_id,
            app=ast_app,
            external_host='127.0.0.1:' + str(self.port),
            format='slin16'
        )

        # Adiciona o canal espionado e o canal de mídia externa à ponte
        await bridge.addChannel(channel=[media_id, channel.id])

async def create_channel_in_stasis(client, endpoint):
    """Cria um novo canal no contexto Stasis."""
    channel = await client.channels.originate(
        endpoint=endpoint,
        app=ast_app,
        appArgs=['stasis-start']
    )
    return channel

async def main(channel_id):
    async with asyncari.connect(ast_url, ast_app, ast_username, ast_password) as client:
        # Cria um canal espionado (snoop) do canal existente
        snoop_channel = await client.channels.snoopChannel(
            channelId=channel_id,
            app=ast_app,
            spy='in',  # Espiona entrada e saída de áudio
            whisper='none'  # Não interfere no áudio original
        )

        # Inicializa o canal local para transcrição
        local_channel = Channel()
        await local_channel.init(client, snoop_channel)
        channels[snoop_channel.id] = local_channel

        # Mantém o script em execução para evitar que o aplicativo Stasis seja desativado
        await anyio.sleep_forever()

@app.route('/start_transcription', methods=['POST'])
def start_transcription():
    data = request.json
    channel_id = data.get('channel_id')
    if not channel_id:
        return jsonify({"error": "channel_id is required"}), 400

    logging.basicConfig(level=logging.DEBUG)
    anyio.run(main, channel_id)
    return jsonify({"status": "Transcription started", "channel_id": channel_id}), 200

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000)

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.