{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "926fea16",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Populating the interactive namespace from numpy and matplotlib\n"
     ]
    }
   ],
   "source": [
    "%pylab inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "da147adb",
   "metadata": {},
   "outputs": [],
   "source": [
    "import nest_asyncio\n",
    "nest_asyncio.apply()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "71cec813",
   "metadata": {},
   "outputs": [],
   "source": [
    "import io\n",
    "import time\n",
    "import funcy\n",
    "import json\n",
    "import numpy as np\n",
    "import urllib\n",
    "import websocket\n",
    "import threading\n",
    "from urllib.parse import urlencode\n",
    "from Bio import pairwise2\n",
    "\n",
    "from suno_utils.audio import Audio\n",
    "from suno_utils.web.mfa import align_text\n",
    "from suno_utils.utils.text import normalize_whitespace\n",
    "\n",
    "with open(\"/home/georg/.secrets/secrets.json\") as f:\n",
    "    secrets = json.load(f)\n",
    "DEEPGRAM_API_KEY = secrets[\"deepgram_api_secret\"]\n",
    "\n",
    "AUDIO_FILEPATH = \"/home/georg/notebooks/tasks/s2t/sample_data/audio_16k/russia.wav\"\n",
    "\n",
    "with open(\"/home/georg/notebooks/tasks/s2t/sample_data/transcripts.json\") as f:\n",
    "    transcript_data = json.load(f)[\"a85b9df3-46a4-4461-88ab-c4a52b9fdd96\"]\n",
    "TRANSCRIPT = transcript_data[\"true\"]\n",
    "TRANSCRIPT_NORM = transcript_data[\"true_norm\"]\n",
    "# TRANSCRIPT_TSS = align_text(\n",
    "#     [AUDIO_FILEPATH], \n",
    "#     [transcript_norm], \n",
    "#     \"/home/georg/anaconda3/etc/profile.d/conda.sh\", \n",
    "#     \"mfa\",\n",
    "# )[0]\n",
    "TRANSCRIPT_TSS = [\n",
    "    ('president', (1.57, 2.02)),\n",
    "    ('joe', (2.02, 2.21)),\n",
    "    ('biden', (2.21, 2.79)),\n",
    "    ('backed', (2.82, 3.14)),\n",
    "    ('by', (3.14, 3.25)),\n",
    "    ('the', (3.25, 3.33)),\n",
    "    ('full', (3.33, 3.61)),\n",
    "    ('symbolic', (3.61, 4.13)),\n",
    "    ('power', (4.13, 4.49)),\n",
    "    ('of', (4.49, 4.61)),\n",
    "    ('the', (4.61, 4.66)),\n",
    "    ('western', (4.66, 5.07)),\n",
    "    ('alliance', (5.07, 5.74)),\n",
    "    ('is', (5.81, 5.95)),\n",
    "    ('locked', (5.95, 6.25)),\n",
    "    ('in', (6.25, 6.31)),\n",
    "    ('a', (6.31, 6.36)),\n",
    "    ('showdown', (6.36, 6.93)),\n",
    "    ('with', (6.93, 7.11)),\n",
    "    ('russian', (7.11, 7.46)),\n",
    "    ('president', (7.46, 7.94)),\n",
    "    ('vladimir', (7.94, 8.36)),\n",
    "    ('putin', (8.36, 8.83)),\n",
    "    ('who', (9.14, 9.21)),\n",
    "    ('is', (9.21, 9.3)),\n",
    "    ('using', (9.3, 9.66)),\n",
    "    ('ukraine', (9.66, 10.29)),\n",
    "    ('as', (10.38, 10.49)),\n",
    "    ('a', (10.49, 10.65)),\n",
    "    ('hostage', (10.65, 11.32)),\n",
    "    ('to', (11.32, 11.43)),\n",
    "    ('try', (11.43, 11.67)),\n",
    "    ('to', (11.67, 11.75)),\n",
    "    ('force', (11.75, 12.11)),\n",
    "    ('the', (12.11, 12.18)),\n",
    "    ('u', (12.18, 12.39)),\n",
    "    ('s', (12.39, 12.54)),\n",
    "    ('to', (12.54, 12.66)),\n",
    "    ('renegotiate', (12.66, 13.64)),\n",
    "    ('the', (13.71, 13.8)),\n",
    "    ('settled', (13.8, 14.2)),\n",
    "    ('outcome', (14.2, 14.77)),\n",
    "    ('of', (14.8, 14.91)),\n",
    "    ('the', (14.91, 14.98)),\n",
    "    ('cold', (14.98, 15.27)),\n",
    "    ('war', (15.31, 15.62)),\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c57e4a7f",
   "metadata": {},
   "source": [
    "## configured client"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "4417988b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from deepgram import Deepgram\n",
    "import asyncio\n",
    "import aiohttp\n",
    "import base64\n",
    "import json\n",
    "import websockets"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fe44b854",
   "metadata": {},
   "outputs": [],
   "source": [
    "# https://developers.deepgram.com/api-reference/#transcription-streaming\n",
    "\n",
    "deepgram = Deepgram(DEEPGRAM_API_KEY)\n",
    "\n",
    "deepgramLive = await deepgram.transcription.live({\n",
    "    \"punctuate\": True, \n",
    "    \"model\": \"general\", \n",
    "    \"language\": \"en\", \n",
    "    \"tier\": \"enhanced\",\n",
    "    \"interim_results\": True,\n",
    "    \"endpointing\": True,\n",
    "#     \"diarize\": True,\n",
    "#     \"encoding\": \"linear16\",\n",
    "#     \"sample_rate\": 16_000,\n",
    "})\n",
    "\n",
    "# Listen for the connection to close\n",
    "deepgramLive.registerHandler(deepgramLive.event.CLOSE, lambda _: print('Connection closed.'))\n",
    "    \n",
    "# Listen for any transcripts received from Deepgram and write them to the console\n",
    "deepgramLive.registerHandler(deepgramLive.event.TRANSCRIPT_RECEIVED, print)\n",
    "\n",
    "URL = 'http://stream.live.vc.bbcmedia.co.uk/bbc_radio_fourlw_online_nonuk'\n",
    "\n",
    "# Listen for the connection to open and send streaming audio from the URL to Deepgram\n",
    "async with aiohttp.ClientSession() as session:\n",
    "    async with session.get(URL) as audio:\n",
    "        while True:\n",
    "            data = await audio.content.readany()\n",
    "            deepgramLive.send(data)\n",
    "\n",
    "            # If no data is being sent from the live stream, then break out of the loop.\n",
    "            if not data:\n",
    "                break\n",
    "\n",
    "# Indicate that we've finished sending data by sending the customary zero-byte message \n",
    "# to the Deepgram streaming endpoint, and wait until we get back the final summary metadata object\n",
    "await deepgramLive.finish()\n",
    "\n",
    "# asyncio.run(main())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29a10f43",
   "metadata": {},
   "source": [
    "# custom client"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "4e92ddce",
   "metadata": {},
   "outputs": [],
   "source": [
    "import asyncio\n",
    "import websockets"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "7a6c6000",
   "metadata": {},
   "outputs": [],
   "source": [
    "options = {\n",
    "    \"punctuate\": True, \n",
    "    \"model\": \"general\", \n",
    "    \"language\": \"en\", \n",
    "    \"tier\": \"enhanced\",\n",
    "    \"interim_results\": True,\n",
    "    \"endpointing\": True,\n",
    "#     \"diarize\": True,\n",
    "    \"encoding\": \"linear16\",\n",
    "    \"sample_rate\": 16_000,\n",
    "}\n",
    "\n",
    "CHUNK_LEN = 1024\n",
    "\n",
    "CHUNK_DURATION_S = 0.03125 * CHUNK_LEN / 1_000"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "e76f151f",
   "metadata": {},
   "outputs": [],
   "source": [
    "option_str = urllib.parse.urlencode([(k, str(v).lower()) for k, v in options.items()])\n",
    "if len(option_str) > 0:\n",
    "    option_str = \"?\" + option_str\n",
    "WEBSOCKET_URL = \"wss://api.deepgram.com/v1/listen\" + option_str\n",
    "\n",
    "def connect(key):\n",
    "    return websockets.connect(\n",
    "        WEBSOCKET_URL,\n",
    "        extra_headers = {\n",
    "            \"Authorization\": \"Token {}\".format(key),\n",
    "            \"interim_results\": True,\n",
    "        }\n",
    "    )\n",
    "\n",
    "async def run(key, send_queue, receive_queue, send_meta_queue):\n",
    "    async with connect(key) as ws:\n",
    "\n",
    "        async def sender(ws): \n",
    "            while True:\n",
    "                audio_bytes_chunk = await send_queue.get()\n",
    "                if audio_bytes_chunk is None:\n",
    "                    break\n",
    "                t0 = time.time()\n",
    "                await ws.send(audio_bytes_chunk)\n",
    "                # slow down to send in realtime\n",
    "                td = time.time() - t0\n",
    "                if td < CHUNK_DURATION_S:\n",
    "                    await asyncio.sleep(CHUNK_DURATION_S - td)\n",
    "                await send_meta_queue.put((time.time(), len(audio_bytes_chunk)))\n",
    "            ## Close the connection cleanly.\n",
    "            await ws.send(json.dumps({\"type\": \"CloseStream\"}))\n",
    "\n",
    "        async def receiver(ws):\n",
    "            async for msg in ws:\n",
    "                t0 = time.time()\n",
    "                m = json.loads(msg)\n",
    "                await receive_queue.put((t0, m))\n",
    "\n",
    "        await asyncio.wait([\n",
    "            asyncio.ensure_future(sender(ws)),\n",
    "            asyncio.ensure_future(receiver(ws))\n",
    "        ])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "4b65990e",
   "metadata": {},
   "outputs": [],
   "source": [
    "send_queue  = asyncio.Queue()\n",
    "receive_queue = asyncio.Queue()\n",
    "send_meta_queue = asyncio.Queue()\n",
    "\n",
    "audio = Audio.from_file(AUDIO_FILEPATH, sample_rate=16_000, byte_width=2)\n",
    "for audio_bytes_chunk in funcy.chunks(CHUNK_LEN, audio.bytes):\n",
    "    send_queue.put_nowait(audio_bytes_chunk)\n",
    "send_queue.put_nowait(None)\n",
    "\n",
    "## Run the event loop\n",
    "asyncio.get_event_loop().run_until_complete(run(DEEPGRAM_API_KEY, send_queue, receive_queue, send_meta_queue))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "02661fa1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "506 messages sent\n",
      "19 messages received\n"
     ]
    }
   ],
   "source": [
    "send_meta = []\n",
    "received_items = []\n",
    "while True:\n",
    "    try:\n",
    "        m = receive_queue.get_nowait()\n",
    "        received_items.append(m)\n",
    "    except asyncio.QueueEmpty:\n",
    "        break\n",
    "while True:\n",
    "    try:\n",
    "        m = send_meta_queue.get_nowait()\n",
    "        send_meta.append(m)\n",
    "    except asyncio.QueueEmpty:\n",
    "        break\n",
    "print(len(send_meta), \"messages sent\")\n",
    "print(len(received_items), \"messages received\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "4ffc587e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "5 final transcripts\n",
      "13 prelim transcripts\n",
      "1 other\n"
     ]
    }
   ],
   "source": [
    "print(len([m for _, m in received_items if m.get(\"is_final\") == True]), \"final transcripts\")\n",
    "print(len([m for _, m in received_items if m.get(\"is_final\") == False]), \"prelim transcripts\")\n",
    "print(len([m for _, m in received_items if \"is_final\" not in m]), \"other\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "83e393e0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'President Joe Biden backed by the full symbolic power of the West an alliance is locked in a showdown with Russian president of Vladimir Putin who is using Ukraine as a hostage to try to force the US to renegotiate the settled outcome of the Cold War.'"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# get final transcript\n",
    "final_transcript = normalize_whitespace(\" \".join([\n",
    "    m['channel']['alternatives'][0]['transcript'] \n",
    "    for _, m in received_items \n",
    "    if m.get(\"is_final\") == True\n",
    "]))\n",
    "final_transcript"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 126,
   "id": "b508ad77",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['',\n",
       " 'President',\n",
       " 'President Joe Biden backed',\n",
       " 'President Joe Biden backed by the full symbolic',\n",
       " \"an alliance because he's\",\n",
       " 'an alliance is locked in a showdown',\n",
       " 'an alliance is locked in a showdown with Russian president of',\n",
       " 'an alliance is locked in a showdown with Russian president of Vladimir Putin',\n",
       " \"who's using new\",\n",
       " \"who's using Ukraine as a hostile\",\n",
       " \"who's using Ukraine as a hostage to try to force\",\n",
       " \"who's using Ukraine as a hostage to try to force the US to read a\",\n",
       " 'settled outcome of']"
      ]
     },
     "execution_count": 126,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# print prelim results\n",
    "[\n",
    "    m[\"channel\"][\"alternatives\"][0][\"transcript\"]\n",
    "    for _, m in received_items \n",
    "    if m.get(\"is_final\") == False\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "a133c3a7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(False, ''),\n",
       " (False, 'President'),\n",
       " (False, 'President Joe Biden backed'),\n",
       " (False, 'President Joe Biden backed by the full symbolic'),\n",
       " (True, 'President Joe Biden backed by the full symbolic power of the West'),\n",
       " (False, \"an alliance because he's\"),\n",
       " (False, 'an alliance is locked in a showdown'),\n",
       " (False, 'an alliance is locked in a showdown with Russian president of'),\n",
       " (False,\n",
       "  'an alliance is locked in a showdown with Russian president of Vladimir Putin'),\n",
       " (True,\n",
       "  'an alliance is locked in a showdown with Russian president of Vladimir Putin'),\n",
       " (False, \"who's using new\"),\n",
       " (False, \"who's using Ukraine as a hostage\"),\n",
       " (False, \"who's using Ukraine as a hostage to try to force\"),\n",
       " (False, \"who's using Ukraine as a hostage to try to force the US to read\"),\n",
       " (True,\n",
       "  'who is using Ukraine as a hostage to try to force the US to renegotiate the'),\n",
       " (False, 'settled outcome of'),\n",
       " (True, 'settled outcome of the Cold War.'),\n",
       " (True, '')]"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[\n",
    "    (m.get(\"is_final\"), m[\"channel\"][\"alternatives\"][0].get(\"transcript\"))\n",
    "    for _, m in received_items \n",
    "    if \"channel\" in m\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "b2f9e406",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'President Joe Biden backed'"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[\n",
    "    m[\"channel\"][\"alternatives\"][0]\n",
    "    for _, m in received_items \n",
    "    if m.get(\"is_final\") == False\n",
    "][2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 136,
   "id": "3bb68bcf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "President Joe Biden, backed by the full symbolic power of the Wester**n A*lliance, is locked in a showdown with Russian P*resident ***Vladimir Putin, who is using Ukraine as a hostage to try to force the U.S. to renegotiate the settled outcome of the Cold War.\n",
      "President Joe Biden* backed by the full symbolic power of the West** an *alliance* is locked in a showdown with Russian *president of Vladimir Putin* who is using Ukraine as a hostage to try to force the U*S* to renegotiate the settled outcome of the Cold War.\n",
      "--\n"
     ]
    }
   ],
   "source": [
    "out = pairwise2.align.globalxx(\n",
    "    list(TRANSCRIPT), \n",
    "    list(final_transcript), \n",
    "    gap_char=[\"*\"],\n",
    ")\n",
    "print(\"\".join(out[0].seqA))  # true\n",
    "print(\"\".join(out[0].seqB))  # asr\n",
    "\n",
    "print(\"--\")\n",
    "\n",
    "# out = pairwise2.align.globalxx(\n",
    "#     list(TRANSCRIPT_NORM), \n",
    "#     list(\" \".join([e[\"value\"] for e in partial_results[-1][-1][\"elements\"]])), \n",
    "#     gap_char=[\"*\"],\n",
    "# )\n",
    "# print(\"\".join(out[0].seqA))  # true\n",
    "# print(\"\".join(out[0].seqB))  # asr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 210,
   "id": "9aa4c31f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# TIL:\n",
    "#\n",
    "# final:\n",
    "#   delay ~5s\n",
    "#   capitalized and punctuated\n",
    "#   space is implicit and punctuation is part of token\n",
    "#\n",
    "# partial: \n",
    "#   delay ~500ms\n",
    "#   everything is modifiable, nothing guaranteed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 211,
   "id": "d5f57b87",
   "metadata": {},
   "outputs": [],
   "source": [
    "# # get latest send time of each word\n",
    "# word_send_times = []\n",
    "# for word, (ts_s, ts_e) in TRANSCRIPT_TSS:\n",
    "#     # check when it was sent\n",
    "#     n_chunk_sent = int(np.ceil(ts_e / CHUNK_DURATION))\n",
    "#     ts = SEND_INFO[n_chunk_sent][2]\n",
    "#     word_send_times.append((word, ts))\n",
    "# # get earliest time that each word arrives\n",
    "# word_arrival_times = []\n",
    "# prev_text = \"\"\n",
    "# for ts, info in partial_results:\n",
    "#     text = \" \".join([e[\"value\"] for e in info[\"elements\"]])\n",
    "#     for word in text[len(prev_text):].strip().split():\n",
    "#         word_arrival_times.append((word, ts))\n",
    "#     prev_text = text\n",
    "# # figure out which words map and we keep\n",
    "# out = pairwise2.align.globalxx(\n",
    "#     [e[0] for e in word_send_times], \n",
    "#     [e[0] for e in word_arrival_times], \n",
    "#     gap_char=[\"*\"],\n",
    "# )\n",
    "# keep_a = []\n",
    "# keep_b = []\n",
    "# idx_a = 0\n",
    "# idx_b = 0\n",
    "# for a, b in zip(out[0].seqA, out[0].seqB):\n",
    "#     if a == b and a != \"*\":\n",
    "#         keep_a.append(idx_a)\n",
    "#         keep_b.append(idx_b)\n",
    "#     if a != \"*\":\n",
    "#         idx_a += 1\n",
    "#     if b != \"*\":\n",
    "#         idx_b += 1\n",
    "# reliable_word_send_times = [word_send_times[n] for n in keep_a]\n",
    "# reliable_word_arrival_times = [word_arrival_times[n] for n in keep_b]\n",
    "# # calculate delay\n",
    "# delay_times_s = []\n",
    "# for (word_send, ts_send), (word_rec, ts_rec) in zip(reliable_word_send_times, reliable_word_arrival_times):\n",
    "#     delay_times_s.append(int(round((ts_rec - ts_send) * 100)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 137,
   "id": "02638835",
   "metadata": {},
   "outputs": [],
   "source": [
    "# plt.plot(delay_times_s, \".-\")\n",
    "# plt.ylabel(\"delay (ms)\")\n",
    "# plt.xlabel(\"index (arb units)\");"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "69ee336d",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ae437714",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "178b76f7",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0c9193d5",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2eec0f9f",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3ee14250",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39f59ace",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "fd084092",
   "metadata": {},
   "source": [
    "# custom client2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "id": "f6614675",
   "metadata": {},
   "outputs": [],
   "source": [
    "import asyncio\n",
    "import websockets"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "id": "aeef22f3",
   "metadata": {},
   "outputs": [],
   "source": [
    "options = {\n",
    "    \"punctuate\": True, \n",
    "    \"model\": \"general\", \n",
    "    \"language\": \"en\", \n",
    "    \"tier\": \"enhanced\",\n",
    "    \"interim_results\": True,\n",
    "    \"endpointing\": True,\n",
    "#     \"diarize\": True,\n",
    "    \"encoding\": \"linear16\",\n",
    "    \"sample_rate\": 16_000,\n",
    "}\n",
    "\n",
    "CHUNK_LEN = 1024\n",
    "\n",
    "CHUNK_DURATION_S = 0.03125 * CHUNK_LEN / 1_000\n",
    "\n",
    "global SEND_INFO\n",
    "global RECEIVE_INFO\n",
    "SEND_INFO = []\n",
    "RECEIVE_INFO = []"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "id": "d0bef238",
   "metadata": {},
   "outputs": [],
   "source": [
    "option_str = urllib.parse.urlencode([(k, str(v).lower()) for k, v in options.items()])\n",
    "if len(option_str) > 0:\n",
    "    option_str = \"?\" + option_str\n",
    "WEBSOCKET_URL = \"wss://api.deepgram.com/v1/listen\" + option_str\n",
    "\n",
    "def connect(key):\n",
    "    return websockets.connect(\n",
    "        WEBSOCKET_URL,\n",
    "        extra_headers = {\n",
    "            \"Authorization\": \"Token {}\".format(key),\n",
    "            \"interim_results\": True,\n",
    "        }\n",
    "    )\n",
    "\n",
    "async def run(key, audio_bytes_chunks):\n",
    "    async with connect(key) as ws:\n",
    "\n",
    "        async def sender(ws): \n",
    "            for audio_bytes_chunk in audio_bytes_chunks:\n",
    "                if audio_bytes_chunk is None:\n",
    "                    break\n",
    "                t0 = time.time()\n",
    "                await ws.send(audio_bytes_chunk)\n",
    "                # slow down to send in realtime\n",
    "                td = time.time() - t0\n",
    "                if td < CHUNK_DURATION_S:\n",
    "                    time.sleep(CHUNK_DURATION_S - td)\n",
    "                SEND_INFO.append((time.time(), len(audio_bytes_chunk)))\n",
    "            ## Close the connection cleanly.\n",
    "            await ws.send(json.dumps({\"type\": \"CloseStream\"}))\n",
    "\n",
    "        async def receiver(ws):\n",
    "            async for msg in ws:\n",
    "                print(\"receiving\")\n",
    "                t0 = time.time()\n",
    "                m = json.loads(msg)\n",
    "                RECEIVE_INFO.append((t0, m))\n",
    "\n",
    "        await asyncio.wait([\n",
    "            asyncio.ensure_future(sender(ws)),\n",
    "            asyncio.ensure_future(receiver(ws))\n",
    "        ])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "id": "a9aaaf3d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n",
      "receiving\n"
     ]
    }
   ],
   "source": [
    "send_queue  = asyncio.Queue()\n",
    "receive_queue = asyncio.Queue()\n",
    "send_meta_queue = asyncio.Queue()\n",
    "\n",
    "audio = Audio.from_file(AUDIO_FILEPATH, sample_rate=16_000, byte_width=2)\n",
    "audio_bytes_chunks = []\n",
    "for audio_bytes_chunk in funcy.chunks(CHUNK_LEN, audio.bytes):\n",
    "    audio_bytes_chunks.append(audio_bytes_chunk)\n",
    "audio_bytes_chunks.append(None)\n",
    "\n",
    "## Run the event loop\n",
    "asyncio.get_event_loop().run_until_complete(run(DEEPGRAM_API_KEY, audio_bytes_chunks))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "id": "8170f057",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "506 messages sent\n",
      "19 messages received\n"
     ]
    }
   ],
   "source": [
    "print(len(SEND_INFO), \"messages sent\")\n",
    "print(len(RECEIVE_INFO), \"messages received\")\n",
    "# 506 messages sent\n",
    "# 19 messages received"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "64699924",
   "metadata": {},
   "outputs": [],
   "source": [
    "# if 'alternatives' in msg:\n",
    "#     transcript = msg['alternatives'][0]['transcript']\n",
    "# elif 'channel' in msg and msg['is_final']:\n",
    "#     transcript = msg['channel']['alternatives'][0]['transcript']\n",
    "# else:\n",
    "#     pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "id": "1823bcc5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'President Joe Biden backed by the full symbolic power of the West an alliance is locked in a showdown with Russian president of Vladimir Putin who is using Ukraine as a hostage to try to force the US to renegotiate the settled outcome of the Cold War. '"
      ]
     },
     "execution_count": 109,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# get final transcript\n",
    "\" \".join([\n",
    "    m['channel']['alternatives'][0]['transcript'] \n",
    "    for _, m in RECEIVE_INFO \n",
    "    if m.get(\"is_final\") == True\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3fbb2ba0",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5f7a2491",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0da5a7e5",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f6d49abc",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "8c70f241",
   "metadata": {},
   "source": [
    "## Playground"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79b2cf31",
   "metadata": {},
   "source": [
    "## configured client"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "d74bdb88",
   "metadata": {},
   "outputs": [],
   "source": [
    "# config = MediaConfig(\"audio/x-raw\", \"interleaved\", 16000, \"S16LE\", 1)\n",
    "config = MediaConfig(\"audio/wav\")\n",
    "\n",
    "streamclient = RevAiStreamingClient(\n",
    "    REV_AI_ACCESS_TOKEN,\n",
    "    config,\n",
    "#     version=\"v2\",\n",
    ")\n",
    "\n",
    "with io.open(AUDIO_FILEPATH, \"rb\") as stream:\n",
    "    MEDIA_GENERATOR = [stream.read()]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "0e72e109",
   "metadata": {},
   "outputs": [],
   "source": [
    "response_generator = streamclient.start(\n",
    "    MEDIA_GENERATOR,\n",
    "    metadata=None,\n",
    "    custom_vocabulary_id=None,\n",
    "    filter_profanity=None,\n",
    "    remove_disfluencies=None,\n",
    "    delete_after_seconds=None,\n",
    "    detailed_partials=None,\n",
    "    start_ts=None,\n",
    "    transcriber=None,\n",
    "    language=None,\n",
    "    skip_postprocessing=None,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "97f9fa50",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Connected, Job ID : DdziJHtwp0DUjcXj\n",
      "Connection Closed. Code : 1000; Reason : End of input. Closing\n"
     ]
    }
   ],
   "source": [
    "t0 = time.time()\n",
    "out = []\n",
    "for response in response_generator:\n",
    "    out.append((time.time() - t0, response))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "29bddbb9",
   "metadata": {},
   "outputs": [],
   "source": [
    "streamclient.end()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "603fc04d",
   "metadata": {},
   "source": [
    "## custom streaming client"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 148,
   "id": "bd35a5ad",
   "metadata": {},
   "outputs": [],
   "source": [
    "def on_error(error):\n",
    "    raise error\n",
    "\n",
    "def on_close(code, reason):\n",
    "    print(f\"Connection Closed. Code: {code}; Reason: {reason}\")\n",
    "\n",
    "def on_connected(job_id):\n",
    "    print(f\"Connected, Job ID: {job_id}\")\n",
    "    \n",
    "media_config = MediaConfig(\"audio/x-raw\", \"interleaved\", 16_000, \"S16LE\", 1)\n",
    "\n",
    "audio = Audio.from_file(AUDIO_FILEPATH, sample_rate=16_000, byte_width=2)\n",
    "# audio_2 = Audio.from_file(AUDIO_FILEPATH, sample_rate=16_000, byte_width=2)\n",
    "# audio_1 = Audio.from_array(audio_2.array * 0, audio_2.sample_rate)\n",
    "# audio = Audio.concatenate([audio_1, audio_2])\n",
    "\n",
    "audio_bytes = audio.bytes\n",
    "# media_config = MediaConfig(\"audio/wav\")\n",
    "# with open(AUDIO_FILEPATH, \"rb\") as f:\n",
    "#     audio_bytes = f.read()\n",
    "    \n",
    "API_VERSION = \"v1\"\n",
    "BASE_URL = f\"wss://api.rev.ai/speechtotext/{API_VERSION}/stream\"\n",
    "API_URL = BASE_URL + '?' + urlencode({\n",
    "    \"access_token\": REV_AI_ACCESS_TOKEN,\n",
    "    \"content_type\": media_config.get_content_type_string(),\n",
    "    \"user_agent\": f\"RevAi-PythonSDK/{rev_ai.__version__}\",\n",
    "    \"transcriber\": \"machine_v2\",\n",
    "})\n",
    "\n",
    "CHUNK_LEN = 1024\n",
    "CHUNK_DURATION = CHUNK_LEN / len(audio.bytes) * audio.duration_s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "372c020c",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 207,
   "id": "b76c6787",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Connected, Job ID: Iw61qhbYz9WpOHJI\n",
      "Connection Closed. Code: 1000; Reason: End of input. Closing\n",
      "done.\n"
     ]
    }
   ],
   "source": [
    "client = websocket.WebSocket(enable_multithread=True)\n",
    "\n",
    "global SEND_INFO\n",
    "global RECEIVE_INFO\n",
    "SEND_INFO = []\n",
    "RECEIVE_INFO = []\n",
    "\n",
    "try:\n",
    "    client.connect(API_URL)\n",
    "except Exception as e:\n",
    "    on_error(e)\n",
    "\n",
    "def _send_data(generator):\n",
    "    global SEND_INFO\n",
    "    for n, chunk in enumerate(generator):\n",
    "        t0 = time.time()\n",
    "        client.send_binary(chunk)\n",
    "        t1 = time.time()\n",
    "        time.sleep(0.02)\n",
    "        SEND_INFO.append((n, t0, t1, len(chunk)))\n",
    "    client.send(\"EOS\")    \n",
    "\n",
    "# thread 1\n",
    "audio_bytes_chunks = list(funcy.chunks(CHUNK_LEN, audio_bytes))\n",
    "request_thread = threading.Thread(\n",
    "    target=_send_data,\n",
    "    args=[audio_bytes_chunks],\n",
    ")\n",
    "request_thread.start()\n",
    "\n",
    "# thread 2\n",
    "out = []\n",
    "while True:\n",
    "    with client.readlock:\n",
    "        opcode, data = client.recv_data()\n",
    "    if opcode == websocket.ABNF.OPCODE_TEXT:\n",
    "        data = data.decode(\"utf-8\")\n",
    "        data_dict = json.loads(data)\n",
    "        if data_dict[\"type\"] == \"connected\":\n",
    "            on_connected(data_dict[\"id\"])\n",
    "        else:\n",
    "            t0 = time.time()\n",
    "            RECEIVE_INFO.append(t0)                                \n",
    "            out.append(data_dict)\n",
    "    elif opcode == websocket.ABNF.OPCODE_CLOSE:\n",
    "        if data and len(data) >= 2:\n",
    "            code = 256 * data[0] + data[1]\n",
    "            reason = data[2:].decode(\"utf-8\")\n",
    "            on_close(code, reason)\n",
    "        break\n",
    "    else:\n",
    "        continue\n",
    "        \n",
    "assert(len(RECEIVE_INFO) == len(out))\n",
    "print(\"done.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 208,
   "id": "662d2561",
   "metadata": {},
   "outputs": [],
   "source": [
    "# client.abort()  # needed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 209,
   "id": "1ce08dda",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "42 partial results\n",
      "1 final results\n"
     ]
    }
   ],
   "source": [
    "partial_results = []\n",
    "final_results = []\n",
    "for rec_ts, res_dict in zip(RECEIVE_INFO, out):\n",
    "    if res_dict[\"type\"] == \"partial\":\n",
    "        partial_results.append((rec_ts, res_dict))\n",
    "    elif res_dict[\"type\"] == \"final\":\n",
    "        final_results.append((rec_ts, res_dict))\n",
    "    else:\n",
    "        raise ValueError()\n",
    "print(len(partial_results), \"partial results\")\n",
    "print(len(final_results), \"final results\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 229,
   "id": "bd13e247",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "president joe biden backed by the full symbolic power of the western alliance is locked in a showdown with russian president **vladimir putin who is using ukraine as a hostage to try to force the u s to renegotiate the settled outcome of the co*ld war*\n",
      "president*joe*biden*backed*by*the*full*symbolic*power*of*the*western*alliance*is*locked*in*a*showdown*with*russian*president*ofvladimir*putin*who*is*using*ukraine*as*a*hostage*to*try*to*force*the*u*s*to*renegotiate*the*settled*outcome*of*the*c*ul*****t\n"
     ]
    }
   ],
   "source": [
    "print(\"\".join(out[0].seqA))\n",
    "print(\"\".join(out[0].seqB))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 230,
   "id": "bb8c2fa1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "President Joe Biden, backed by the full symbolic power of the Western Alliance, is locked in a showdown with Russian P*resident ***Vladimir Putin, who is using Ukraine as a hostage to try to force the U.S.** to renegotiate the settled outcome of the Co**ld* W*ar.\n",
      "President Joe Biden* backed by the full symbolic power of the Western Alliance* is locked in a showdown with Russian *president of Vladimir Putin, who is using Ukraine as a hostage to try to force the ****us to renegotiate the settled outcome of the **cul*t *war.\n",
      "--\n",
      "president joe biden backed by the full symbolic power of the western alliance is locked in a showdown with russian president ***vladimir putin who is using ukraine as a hostage to try to force the u s to renegotiate the settled outcome of the co*ld war*\n",
      "president joe biden backed by the full symbolic power of the western alliance is locked in a showdown with russian president of vladimir putin who is using ukraine as a hostage to try to force the u*s to renegotiate the settled outcome of the c*ul*****t\n"
     ]
    }
   ],
   "source": [
    "out = pairwise2.align.globalxx(\n",
    "    list(TRANSCRIPT), \n",
    "    list(\"\".join([e[\"value\"] for e in final_results[-1][-1][\"elements\"]])), \n",
    "    gap_char=[\"*\"],\n",
    ")\n",
    "print(\"\".join(out[0].seqA))  # true\n",
    "print(\"\".join(out[0].seqB))  # asr\n",
    "\n",
    "print(\"--\")\n",
    "\n",
    "out = pairwise2.align.globalxx(\n",
    "    list(TRANSCRIPT_NORM), \n",
    "    list(\" \".join([e[\"value\"] for e in partial_results[-1][-1][\"elements\"]])), \n",
    "    gap_char=[\"*\"],\n",
    ")\n",
    "print(\"\".join(out[0].seqA))  # true\n",
    "print(\"\".join(out[0].seqB))  # asr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 210,
   "id": "38346b99",
   "metadata": {},
   "outputs": [],
   "source": [
    "# TIL:\n",
    "#\n",
    "# final:\n",
    "#   delay ~20s\n",
    "#   capitalized and punctuated (punctuation is separate token)\n",
    "#   space is separate token without timestamp\n",
    "#\n",
    "# partial: \n",
    "#   delay after finished word is immediate (~40-80ms)\n",
    "#   timestamps are modifiable but words are not\n",
    "#   words aren't added 1by1 necessarily\n",
    "#   silence/spaces aren't returned"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 211,
   "id": "d0d69780",
   "metadata": {},
   "outputs": [],
   "source": [
    "# get latest send time of each word\n",
    "word_send_times = []\n",
    "for word, (ts_s, ts_e) in TRANSCRIPT_TSS:\n",
    "    # check when it was sent\n",
    "    n_chunk_sent = int(np.ceil(ts_e / CHUNK_DURATION))\n",
    "    ts = SEND_INFO[n_chunk_sent][2]\n",
    "    word_send_times.append((word, ts))\n",
    "# get earliest time that each word arrives\n",
    "word_arrival_times = []\n",
    "prev_text = \"\"\n",
    "for ts, info in partial_results:\n",
    "    text = \" \".join([e[\"value\"] for e in info[\"elements\"]])\n",
    "    for word in text[len(prev_text):].strip().split():\n",
    "        word_arrival_times.append((word, ts))\n",
    "    prev_text = text\n",
    "# figure out which words map and we keep\n",
    "out = pairwise2.align.globalxx(\n",
    "    [e[0] for e in word_send_times], \n",
    "    [e[0] for e in word_arrival_times], \n",
    "    gap_char=[\"*\"],\n",
    ")\n",
    "keep_a = []\n",
    "keep_b = []\n",
    "idx_a = 0\n",
    "idx_b = 0\n",
    "for a, b in zip(out[0].seqA, out[0].seqB):\n",
    "    if a == b and a != \"*\":\n",
    "        keep_a.append(idx_a)\n",
    "        keep_b.append(idx_b)\n",
    "    if a != \"*\":\n",
    "        idx_a += 1\n",
    "    if b != \"*\":\n",
    "        idx_b += 1\n",
    "reliable_word_send_times = [word_send_times[n] for n in keep_a]\n",
    "reliable_word_arrival_times = [word_arrival_times[n] for n in keep_b]\n",
    "# calculate delay\n",
    "delay_times_s = []\n",
    "for (word_send, ts_send), (word_rec, ts_rec) in zip(reliable_word_send_times, reliable_word_arrival_times):\n",
    "    delay_times_s.append(int(round((ts_rec - ts_send) * 100)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 212,
   "id": "3994ec3b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(delay_times_s, \".-\")\n",
    "plt.ylabel(\"delay (ms)\")\n",
    "plt.xlabel(\"index (arb units)\");"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "187cd8ee",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b3264ebc",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44101b21",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "49786575",
   "metadata": {},
   "source": [
    "## Playground"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c05c7f81",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7faa9682",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b103e7f4",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8c9b6d3d",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0643081a",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
