JavaScript Delete Object if it Collides or Overlaps with another object that has CSS Class X

Imagine we have a page that has anywhere from 1 to 100 floating blocks. Now what if I want the block(s) to disappear if it runs into another type of block or a boarder? How would we do that?

First lets get a list of all elements with X and Y class. We could swap one of these classes out for an ID if we wanted to.

const elements1 = document.querySelectorAll('.boxes')
const elements2 = document.querySelectorAll('.borders')

The variable elements1 and 2 are both arrays of all the elements that have class .boxes and boarders.

Now lets create a function to detect a collision.

function detectCollision(class1, class2) {
  for (let i = 0; i < class1.length; i++) {
    const e1Rectangle = class1[i].getBoundingClientRect()
    for (let i2 = 0; i2 < e2.length; i2++) {
      const e2Rectangle = class2[i2].getBoundingClientRect()
      if (
        e1Rectangle.left < e2Rectangle.right &&
        e1Rectangle.right > e2Rectangle.left &&
        e1Rectangle.top < e2Rectangle.bottom &&
        e1Rectangle.bottom > e2Rectangle.top
      ) {
        destroyElement(class1[i])
        destroyElement(class1[i2])
      }
    }
  }
}

This function takes two arrays as inputs, loops over each array object and compares to see if any are overlapping. If they are overlapping, run the destroyElement() function for those two specific elements.

Create the destroyElement function with

 function destroyElement(element) {
  elementId.parentNode.removeChild(elementId)
}

Here is our full code

const elements1 = document.querySelectorAll('.boxes')
const elements2 = document.querySelectorAll('.borders')

function detectCollision (class1, class2) {
  for (let i = 0; i < class1.length; i++) {
    const e1Rectangle = class1[i].getBoundingClientRect()
    for (let i2 = 0; i2 < e2.length; i2++) {
      const e2Rectangle = class2[i2].getBoundingClientRect()
      if (
        e1Rectangle.left < e2Rectangle.right &&
        e1Rectangle.right > e2Rectangle.left &&
        e1Rectangle.top < e2Rectangle.bottom &&
        e1Rectangle.bottom > e2Rectangle.top
      ) {
        destroyElement(class1[i])
        destroyElement(class1[i2])
      }
    }
  }
}

// Element is element[arraynumber].etc
function destroyElement(element) {
  elementId.parentNode.removeChild(elementId)
}

// Every second, let's run our collision function to check for collisions
setInterval(function () {
   detectCollision(elements1, elements2)
  }, 1000)

Ansible Error – An unhandled exception occurred while templating

}}'. Error was a <class 'ansible.errors.AnsibleError'>, original message: An unhandled exception occurred while templating '{{ ntfy_visitor_request_limit_exempt_hosts_container_networks_inspect_commands_string | split('###') }}'. Error was a <class 'ansible.errors.AnsibleError'>, original message: template error while templating string: no filter named 'split'. String: {{ ntfy_visitor_request_limit_exempt_hosts_container_networks_inspect_commands_string | split('###') }

To solve the issue, update Ansible. If you are already on the “latest” version of ansible available to for your distro, uninstall, and then install it again following the directions on Ansible’s website

For Ubuntu we can simply do that with

sudo apt remove ansible
sudo apt-add-repository ppa:ansible/ansible
sudo apt install ansible

Notes on Buttons with JavaScript

Here are some very basic notes on using buttons to change elements on a web page

We have a very simple page with three buttons that change the background color when the button is clicked.

Clicking a button changes the text and the background color.

Create a button in our html file.

    <button type="button" class="button green">Green</button>

We have two classes assigned to this button, button which is used for styling, and the green, which JavaScript will use to know which button is clicked.

In our JavaScript, we will set up an event listener for when the button is clicked.

document.querySelector('.green').addEventListener('click', function () {
  document.querySelector('body').style.backgroundColor = 'Green'
  console.log('Green')
})

We use the document.querySelector to interact with HTML objects. So we setup the event listener to listen to the button that is in Class green, and we wait for the click event. Once that happens, we run the code in the function(){ }.

Line 2 is what changes our background color. We query the body tag, and set the backgroundColor to Green. Notice that the background color name is slightly different between JavaScript and HTML.
HTML and CSS it has a – in it: background-color
JavaScript is it Camel Case: backgroundColor

Here is the full code for the above screenshots. There are three files. index.html, style.css, and index.js. Should be able to copy them to the same folder and run the index.html file.

Index.html code

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="style.css" />
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>JavaScript DOM Practice</title>
  </head>
  <br />
  <section class="mainwindow">
    <div class="output">Click a button to change the background color</div>
    <br />
    <br />
    <br />
    <button type="button" class="button green">Green</button>
    <button type="button" class="button blue">Blue</button>
    <button type="button" class="button yellow">Yellow</button>
  </section>

  <body>
    <script src="index.js"></script>
  </body>
</html>

JavaScript index.js

'use strict'

document.querySelector('.green').addEventListener('click', function () {
  ChangeBackgrounColor('Green')
  LogBackgroundColor('Green')
})

document.querySelector('.blue').addEventListener('click', function () {
  ChangeBackgrounColor('Blue')
  LogBackgroundColor('Blue')
})

document.querySelector('.yellow').addEventListener('click', function () {
  ChangeBackgrounColor('Yellow')
  LogBackgroundColor('Yellow')
})

function ChangeBackgrounColor (color) {
  document.querySelector('body').style.backgroundColor = color
}

function LogBackgroundColor (color) {
  document.querySelector('.output').innerHTML = `Background is ${color}`
  console.log(`Background color is ${color}`)
}

CSS File style.css

.mainwindow {
  padding: 1rem;
}
body {
  background-color: white;
}
button {
  border: none;
  font-size: 24px;
  cursor: pointer;
  padding: 1rem 1.5rem;
  margin: 1rem;
}
.output {
  font-size: 2rem;
}

Javascript Ternary Operator

The ternary operator is a conditional operator that takes three (ternary) options. We can almost think of it as a concise if then statement.

The basic syntax is as follows.

Condition ? Value-If-True : Value-If-False

For an example

const isOver21 = age >= 21 ? "Is over 21" : "Is Under 21"

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#conditional_ternary_operator

Making RDP Faster (60FPS)

https://learn.microsoft.com/en-us/troubleshoot/windows-server/remote/frame-rate-limited-to-30-fps

On RDP Server, Open up Registry Editor as administrator

Go to

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations

Add a new DWORD

Set the name to DWMFRAMEINTERVAL

Set the Value to 15, and base Decimal.

Apply and reboot.

You may also want to look at enabling the following options in Group Policy Editor

Computer Configuration > Policies > Administrative Template > Windows Components > Remote Desktop Session Host > Connections > Select RDP transport Protocols > Use both TCP and UDP

Computer Configuration > Policies > Administrative Template > Windows Components > Remote Desktop Session Host > Connections > Remote Session Environment > Use hardware graphics adapters for all Rmote Desktop Services sessions

https://github.com/maxprehl/TurboRemoteFX
https://www.reddit.com/r/sysadmin/comments/fv7d12/pushing_remote_fx_to_its_limits/

Running Node App as systemd Service

In this post we will be using systemd to run a node application. This is helpful as it will automatically start the app when the server starts so we don’t have to manually. These steps can easily be modified to run a bash script, or any other application.

  • Create systemd file
  • Customize systemd file
  • Enable systemd file

We’ll be creating a service for the Simple Whisper Web Interface as an example. Chang things as needed.

Create systemd file

This is super simple. We create a .service file in /lib/systemd/system. When we enable the service, it will create a symlink to this file.

sudo vim /lib/systemd/system/whisperweb.service

Customize systemd file

Change the settings as appropriate. It would be a good idea to run any service as a limited user that only has the rights needed to get the job done. Do note that you will need to have any prerequisites installed and available for that user to use. I.e. libraries installed with npm etc.

[Unit]
Description=Simple Whisper Web Interface Service File
After=network.target

[Service]
Type=simple
User=whisperuser
ExecStart=/usr/bin/node mainssl.js
WorkingDirectory=/home/whisperuser/
Restart=on-failure

[Install]
WantedBy=multi-user.target

Enable systemd file

Enabling the service will create a symlink that will then run this service file on system boot.

sudo systemctl enable whisperweb.service

And now we can start the service.

sudo systemctl start whisperweb.service

We can verify that the service is running by running

sudo systemctl status whisperweb.service

The following article has some great explanations on what different options in the unit file mean and do.

https://nodesource.com/blog/running-your-node-js-app-with-systemd-part-1/

Voxer Bot Attempts

The following are various notes and findings on trying to create a “Bot” for Voxer. Voxer doesn’t really have any options for web hooks, and the SDK is still in beta. We’ll be exploring sending messages to a channel, how to use Curl or Fetch to send messages. Unfortunately, signing in does not appear to be easy to automate.

Looking at Voxer

Voxer is primarily used via the modile apps, but there is also a web version at web.voxer.com

Using Firefox, we can play with the Web Tools to get a better idea of what is going on.

All the Javascript is easily readable. api.js is of interest. This can helps us understand how messages are sent.

The Web interface seems to be somewhat buggy, could be it just doesn’t like going through burp, but had better luck monitoring the channel for new messages from a phone.

Proxying the traffic through Burp is tricky. The log in does not appear to work while the proxy is active, but you can log in, and then activate the proxy to capture and replay sending messages.

If you get “Voxer is open in another tab. Please click ‘OK’ then close this tab.” clear all voxer cookies and log in again. Seems to happen quite often.

Interestingly, if you send a message, then send that message POST request to Repeater, change the text and resubmit it, it “Updates” the text of the message. So if you know the message_id, or maybe the create_time, you can change the text in messages.

About Sending Messages

When you send a message, there are 2 POST requests sent. The first one sends the message and the second one consumes the message. It looks like you really only need the first post message request to actually send messages. Think the consume message post is for the browser to trigger a refresh on the messages in the message list.

There are two variables that change message to message

message_id and create_time

Looking into the api.js file we see that create_time is done with the following code

now = new Date().getTime() / 1000,

And here is the code used to generate the message_id. First part is the time, the next part is random.

window.generate_message_id = function (type) {
        var message_id = new Date().getTime() + "_" + Math.floor((Math.random() * 10000000000) + 1000);

        if (type && type === "image") {
            message_id += "_v1.jpg";
        }

        return message_id;
    };

Structure of the POST request

The following entries are slightly abbreviated

Cookies

There are a bunch of tracking cookies, the session cookie is the one we are interested in. The Rv_session_key is what will allow us to actually send messages. As a side note, it appears that every time we send a message, there is analytics that is also sent, saying who the message was sent to, and when.

session={"gcp1-prod":{"user_id":"MyVoxerUser_ID","Rv_session_key":"db0bc8a069148140151a38bab2098a01"}}

Body

{"message_id":"1681191108600_7318671093","create_time":1681191108,"model":"User_Agent","content_type":"text","from":"MyVoxerUser_ID","subject":"Walkie","body":"This is the text of the message","thread_id":"This.is.the.thread_id"}

Using JavaScript / fetch

After some trial an error, the following solution finally worked. You can copy the following code, and run with “node file.js”

// Change variables as needed
let body = "Hello World!"
// let body = process.argv.slice(2) // You can use this if you want to pass in the message as an argumant. i.e. node voxer.js "Voxer Message"
const threadID = 'ThreadID'
const fromID = 'UserID'
let cookie = `session={"gcp1-prod":{"user_id":"${fromID}","Rv_session_key":"RVSESSIONID"}}`
let time = new Date().getTime() / 1000
let messageID = new Date().getTime() + '_' + Math.floor(Math.random() * 10000000000 + 1000)

// Send message.
function sendMessage () {
  fetch(`https://gcp1-prod-nr60.voxer.com/2/cs/post_message?now=${time}`, {
    credentials: 'include',
    credentials: 'same-origin',
    headers: {
      'User-Agent': 'UserAgent',
      Accept: '*/*',
      'Accept-Language': 'en-US,en;q=0.5',
      'Content-Type': 'text/plain',
      'Sec-Fetch-Dest': 'empty',
      'Sec-Fetch-Mode': 'cors',
      'Sec-Fetch-Site': 'same-site',
      'sec-ch-ua-platform': '"Windows"',
      'sec-ch-ua': '"Opera";v="97", "Chromium";v="97", "Not=A?Brand";v="24"',
      'sec-ch-ua-mobile': '?0',
      Cookie: cookie
    },
    referrer: 'https://web.voxer.com/',
    body: `{\"message_id\":\"${messageID}\",\"create_time\":${time},\"model\":\"\",\"content_type\":\"text\",\"from\":\"${fromID}\",\"subject\":\"CHANGING\",\"body\":\"${body}\\n\",\"thread_id\":\"${threadID}\"}\r\n`,
    method: 'POST',
    mode: 'cors'
  })}

 sendMessage()

You can find the thread_id, user_id, session cookie by toggling the developer console, logging into Voxer, and send a message.

An HTTPS version of Simple Whisper Web Interface

This version is the same as we previously posted, but adds a security certificate, and lots of extra new stuff! You will need a valid certificate, this is fairly easy to setup using Let’s Encrypt, or you could do a self signed certificate.

Setting up Prerequisite

Installing whisper-ctranslate2

pip install -U whisper-ctranslate2

Install NodeJS

sudo apt install nodejs

or

sudo dnf install nodejs

Install Node Dependencies

npm install formidable
npm install https
npm install fs

Setting up Simple Whisper Web Interface

First we need a web directory to use.

Next lets make an uploads folder

mkdir uploads

Code for main.js. Change WEBSITE.COM to your website name. Node will need access to the certificates. You can run this web app as root (If you do that, then root needs node, and prerequisites), or copy the certs to the users directory, and change the path. If you do the later, look at using a script/cronjob to copy the updated certificates to the users directory.

const http = require('http')
const https = require('https')
const formidable = require('formidable')
const fs = require('fs')

const execSync = require('child_process').execSync

let newpath = ''
let modelSize = 'medium.en'
const { exec } = require('node:child_process')
const validModels = [
  'medium.en',
  'tiny',
  'tiny.en',
  'base',
  'base.en',
  'small',
  'small.en',
  'medium',
  'medium.en',
  'large-v1',
  'large-v2'
]

var options = {
  key: fs.readFileSync('/path/to/privkey.pem'),
  cert: fs.readFileSync('/path/to/fullchain.pem')
}
fs.readFile('./index.html', function (err, html) {
  if (err) throw err

  https
    .createServer(options, function (req, res) {
      if (req.url == '/fileupload') {
        res.write(html)
        res.write(`<div class="loading results"></div>`)
        var form = new formidable.IncomingForm()
        form.parse(req, function (err, fields, files) {
          console.log('Fields ' + fields.modeltouse)
          console.log('File ' + files.filetoupload)
          var oldpath = files.filetoupload.filepath
          newpath = './uploads/' + files.filetoupload.originalFilename
          modelSize = validModels.includes(fields.modeltouse)
            ? fields.modeltouse
            : 'medium.en'
          console.log('modelSize::' + modelSize)
          fs.rename(oldpath, newpath, function (err) {
            if (err) {
              console.log('No file selected!') // throw err
              res.write(`<div class="results">No file selected</div>`)
            } else {
              console.log(newpath)
              if (fields.timestamps) {
                      console.log("true check")
                var output = execSync(
                  `./whisper.sh "${newpath}" ${modelSize} "true"`,
                  { encoding: 'utf-8' }
                )
              } else {
                var output = execSync(
                  `./whisper.sh "${newpath}" ${modelSize} false`,
                  { encoding: 'utf-8' }
                )
              }
        res.write(`<script>document.querySelector('.loading').classList.add('hidden')</script>`)
              res.write(
                `<div class="results"><h2>Results:</h2> <p>${output}</p></div>`
              )
              res.end()
            }
          })
        })
      } else {
        res.writeHead(200, { 'Content-Type': 'text/html' })
        res.write(html)
        return res.end()
      }
    })
    .listen(8443)
})

Add the following to index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Voice Transcribing Using Whisper</title>
    <link type="text/css" rel="stylesheet" href="style.css" />
  </head>
  <style>
    body {
      background-color: #b9dbe7;
      align-items: center;
    }

    .box {
      border-radius: 25px;
      padding: 25px;
      width: 80%;
      background-color: azure;
      margin: auto;
      border-bottom: 25px;
      margin-bottom: 25px;
                    font-family: 'advent_prothin', Arial, sans-serif;
    }

    .button {
      border-radius: 25px;
      margin: auto;
      width: 50%;
      height: 50px;
      display: flex;
      justify-content: center;
      border-style: solid;

      background-color: #e8d2ba;
    }

    h1 {
      text-align: center;
      padding: 0%;
      margin: 0%;
    }
    @font-face {
    font-family: 'advent_prothin';
    src: url('Typewriter.otf');
    font-weight: normal;
    font-style: normal;

}


p {
      font-size: larger;
      font-family: 'MyWebFont', Arial, sans-serif;
    }
    .headings {
      font-size: large;
      font-weight: bold;
    }
    input {
      font-size: medium;
    }
    .checkboxes {
      transform: scale(1.5);
    }
    select {
      font-size: medium;
    }
    .results {
      white-space: pre-wrap;
      border-radius: 25px;
      padding: 25px;
      width: 80%;
      align-self: center;
      background-color: azure;
      margin: auto;
      font-family: Typewriter;
    }
    .hidden {
              display: none;
    }
    .note {
      font-style: italic;
      font-size: small;
      font-weight: normal;
    }
        .loading {
      border: 16px solid azure;
      border-radius: 50%;
      border-top: 16px solid rgb(207, 255, 255);
      width: 64px;
      height: 64px;
      animation: spin 1s linear infinite;
    }
    /* Safari */

    @keyframes spin {
      0% {
        transform: rotate(0deg);
      }
      100% {
        transform: rotate(360deg);
      }
    }
  </style>
  <body>
    <script>
    </script>
    <div class="box">
      <h1>Simple Whisper Web Interface</h1>
      <br />
      <p>
        Welcome to the very Simple Whisper Web Interface!<br /><br />
        This is a very basic, easy to use, web interface for OpenAI's Whisper
        tool. It has not been extensively tested, so you may encounter bugs or
        other problems.
        <br /><br />
        Instructions for use. <br />1. Select audio file <br />2. Select the
        Model you want to use <br />
        3. Click Transcribe! <br />4. Copy your transcription
      </p>
      <br />
      <br />
      <div class="headings">
        <form action="fileupload" method="post" enctype="multipart/form-data">
          Audio File: <input type="file" name="filetoupload" /><br />

          <br />
          Model:
          <select name="modeltouse" id="modeltouse">
            <option value="medium.en">medium.en</option>
            <option value="tiny">tiny</option>
            <option value="tiny.en">tiny.en</option>
            <option value="base">base</option>
            <option value="base.en">base.en</option>
            <option value="small">small</option>
            <option value="small.en">small.en</option>
            <option value="medium">medium</option>
            <option value="medium.en">medium.en</option>
            <option value="large-v1">large-v1</option>
            <option value="large-v2">large-v2</option>
          </select>
          <p class="note">
            Large-v2 and medium.en seem to produce the most accurate results.
          </p>
          Timestamps?
          <input class="checkboxes" type="checkbox" id="timestamps" name="timestamps" />
          <br />
          <br />
          <br />
          <input class="button" type="submit" value="Transcribe!" />
        </form>
      </div>
    </div>
  </body>
</html>

Run the server with

node main.js

If we want to start it in the background, run

node ./main.js &

Next steps are to setup a SystemD file so we can auto start on system boot and also make the installation quicker/easier.

A Very Basic Simple Whisper Web Interface

Created a little web interface to use Whisper, technically using whisper-ctranslate2 which is built on faster-whisper.

This is not currently ready to be run on the public web. It doesn’t have any sort of TLS for encrypting communications from client to server and all the files are stored on server. Only use in a trusted environment.

Setting up Prerequisite

Installing whisper-ctranslate2

pip install -U whisper-ctranslate2

Install NodeJS

sudo apt install nodejs

or

sudo dnf install nodejs

Install Node Dependencies

npm install formidable
npm install http
npm install fs

Setting up Simple Whisper Web Interface

First we need a web directory to use.

Next lets make an uploads folder

mkdir uploads

Now let’s create a main.js file. Node is going to be our webserver. Copy the following contents.

var http = require('http')
var formidable = require('formidable')
var fs = require('fs')

const execSync = require('child_process').execSync

let newpath = ''
let modelSize = 'medium.en'
const { exec } = require('node:child_process')
const validModels = [
  'medium.en',
  'tiny',
  'tiny.en',
  'base',
  'base.en',
  'small',
  'small.en',
  'medium',
  'medium.en',
  'large-v1',
  'large-v2'
]
fs.readFile('./index.html', function (err, html) {
  if (err) throw err

  http
    .createServer(function (req, res) {
      if (req.url == '/fileupload') {
        res.write(html)
        var form = new formidable.IncomingForm()
        form.parse(req, function (err, fields, files) {
          console.log('Fields ' + fields.modeltousema)
          console.log('File ' + files.filetoupload)
          var oldpath = files.filetoupload.filepath
          newpath = './uploads/' + files.filetoupload.originalFilename
          modelSize = validModels.includes(fields.modeltouse)
            ? fields.modeltouse
            : 'medium.en'
          console.log('modelSize::' + modelSize)
          fs.rename(oldpath, newpath, function (err) {
            if (err) {
              console.log('No file selected!') // throw err
              res.write(`<div class="results">No file selected</div>`)
            } else {
              console.log(newpath)
              const output = execSync(
                `whisper-ctranslate2 ${newpath} --model ${modelSize}`,
                { encoding: 'utf-8' }
              )

              res.write(
                `<div class="results"><h2>Results:</h2> <p>${output}</p></div>`
              )
              res.end()
            }
          })
        })
      } else {
        res.writeHead(200, { 'Content-Type': 'text/html' })
        res.write(html)
        return res.end()
      }
    })
    .listen(8080)
})

Now create an index.html file and paste the following in

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Voice Transcribing Using Whisper</title>
    <link type="text/css" rel="stylesheet" href="style.css" />
  </head>
  <style>
    body {
      background-color: #b9dbe7;
      align-items: center;
    }

    .box {
      border-radius: 25px;
      padding: 25px;
      width: 80%;
      background-color: azure;
      margin: auto;
      border-bottom: 25px;
      margin-bottom: 25px;
    }

    .button {
      border-radius: 25px;
      margin: auto;
      width: 50%;
      height: 50px;
      display: flex;
      justify-content: center;
      border-style: solid;

      background-color: #e8d2ba;
    }

    h1 {
      text-align: center;
      padding: 0%;
      margin: 0%;
    }

    p {
      font-size: larger;
    }
    .headings {
      font-size: large;
      font-weight: bold;
    }
    input {
      font-size: medium;
    }
    select {
      font-size: medium;
    }
    .results {
      white-space: pre-wrap;
      border-radius: 25px;
      padding: 25px;
      width: 80%;
      align-self: center;
      background-color: azure;
      margin: auto;
    }
    .note {
      font-style: italic;
      font-size: small;
      font-weight: normal;
    }
  </style>
  <body>
    <script></script>
    <div class="box">
      <h1>Simple Whisper Web Interface</h1>
      <br />
      <p>
        Welcome to the very Simple Whisper Web Interface!<br /><br />
        This is a very basic, easy to use, web interface for OpenAI's Whisper
        tool. It has not been extensively tested, so you may encounter bugs or
        other problems.
        <br /><br />
        Instructions for use. <br />1. Select audio file <br />2. Select the
        Model you want to use <br />
        3. Click Transcribe! <br />4. Copy your transcription
      </p>
      <br />
      <br />
      <div class="headings">
        <form action="fileupload" method="post" enctype="multipart/form-data">
          Audio File: <input type="file" name="filetoupload" /><br />

          <br />
          Model:
          <select name="modeltouse" id="modeltouse">
            <option value="medium.en">medium.en</option>
            <option value="tiny">tiny</option>
            <option value="tiny.en">tiny.en</option>
            <option value="base">base</option>
            <option value="base.en">base.en</option>
            <option value="small">small</option>
            <option value="small.en">small.en</option>
            <option value="medium">medium</option>
            <option value="medium.en">medium.en</option>
            <option value="large-v1">large-v1</option>
            <option value="large-v2">large-v2</option>
          </select>
          <p class="note">
            Large-v2 and medium.en seem to produce the most accurate results.
          </p>
          <br />
          <br />
          <br />
          <input class="button" type="submit" value="Transcribe!" />
        </form>
      </div>
    </div>
  </body>
</html>

Now we should be set to go.

Fire the web server up with

node ./main.js

If we want to start it in the background, run

node ./main.js &

Known Limitations or Bugs

If you hit Transcribe with no file selected, the server crashes.

We are calling whisper-ctranslate2 directly, if it is not in the path, then it won’t work.

We are currently using the medium.en model, if the model is not downloaded, then the first transcription may take awhile while it downloads. Would like to add a menu for selecting which model to use. We fixed this by adding a drop down that let’s you select a model.

Would be nice to have an option for getting rid of the timestamps.