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?

1 Like

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.