Lesson 2’s browser-based recognition is the simplest starting point, but it has two real limits: Chrome’s implementation sends audio to a server you don’t control, and Firefox does not support it by default. When either of those matters, privacy on a site handling sensitive input, or reliability across every visitor’s browser, the real alternative is running speech-to-text yourself. OpenAI’s Whisper is a real, MIT-licensed, open-weight model that runs fully offline, and faster-whisper is the actively maintained, CTranslate2-based reimplementation that makes running it locally actually fast enough to use. This lesson sets it up as a small local service and calls it from WordPress over plain HTTP, the same pattern Course 13 used for provider-hosted transcription, just pointed at a process on your own machine instead of a third-party API.
Course 13’s Audio Transcription
lesson for the wp_remote_post() multipart pattern this lesson reuses, and a machine
that can run Python, this is not something you install as a WordPress plugin alone.
Step 1: Why reach for this instead of the browser API
None of that is free. Running your own model means managing a server process, choosing a model size that fits your hardware, and accepting that a large model on modest hardware is genuinely slower than a cloud round trip. Reach for this when one of the reasons above actually applies to your project, not by default.
Step 2: Install faster-whisper
# Context: on the machine that will run transcription, separate from your WordPress host
python3 -m venv whisper-env
source whisper-env/bin/activate
pip install faster-whisper fastapi uvicorn python-multipart
Step 3: Wrap it in a minimal local HTTP service
faster-whisper is a Python library, not a server, so a thin wrapper is what makes it callable over HTTP the way this track’s other AI integrations expect:
# File: whisper-server/main.py
from fastapi import FastAPI, UploadFile
from faster_whisper import WhisperModel
import tempfile
app = FastAPI()
model = WhisperModel( "small", device="cpu", compute_type="int8" )
@app.post( "/transcribe" )
async def transcribe( file: UploadFile ):
with tempfile.NamedTemporaryFile( suffix=".wav" ) as tmp:
tmp.write( await file.read() )
tmp.flush()
segments, info = model.transcribe( tmp.name )
text = " ".join( segment.text.strip() for segment in segments )
return { "text": text.strip(), "language": info.language }
# Context: starting the local transcription service on port 5001
uvicorn main:app --host 127.0.0.1 --port 5001
"small" is a reasonable middle ground for CPU-only hardware, "base" is faster and
less accurate, "medium" or "large-v3" are more accurate and meaningfully slower
without a GPU. compute_type="int8" trades a small amount of accuracy for real speed
on CPU, this is exactly the kind of tuning faster-whisper exists to make practical.
Step 4: Call it from WordPress over plain HTTP
This reuses the exact multipart pattern Course 13 established, just pointed at
127.0.0.1 instead of a third-party provider:
// File: my-plugin/class-local-transcription.php
function my_plugin_transcribe_locally( string $file_path ): string|WP_Error {
$file_data = file_get_contents( $file_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
if ( false === $file_data ) {
return new WP_Error( 'unreadable_file', 'Could not read the audio file.' );
}
$boundary = wp_generate_password( 24, false );
$body = "--{$boundary}\r\n";
$body .= 'Content-Disposition: form-data; name="file"; filename="' . basename( $file_path ) . "\"\r\n";
$body .= "Content-Type: audio/wav\r\n\r\n";
$body .= $file_data . "\r\n";
$body .= "--{$boundary}--\r\n";
$response = wp_remote_post(
'http://127.0.0.1:5001/transcribe',
array(
'timeout' => 60,
'headers' => array( 'Content-Type' => 'multipart/form-data; boundary=' . $boundary ),
'body' => $body,
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( empty( $data['text'] ) ) {
return new WP_Error( 'transcription_failed', 'Local transcription returned no text.' );
}
return $data['text'];
}
This assumes the whisper-server process runs on the same machine as WordPress, or somewhere reachable at that address. On typical managed WordPress hosting, you cannot run a Python process alongside it at all, this pattern fits a VPS, a dedicated server, or a separate machine on the same private network, reachable by its actual address rather than localhost. Confirm your hosting model can actually run a second long-lived process before committing to this approach.
Step 5: Test it against a real short clip
wp eval '
$result = my_plugin_transcribe_locally( "/path/to/a/short-test-clip.wav" );
if ( is_wp_error( $result ) ) {
echo "Error: " . $result->get_error_message() . "\n";
} else {
echo "Transcript: " . $result . "\n";
}
'
Compare the output against what was actually said. With the "small" model on
typical CPU hardware, expect a short delay, a few seconds for a short clip, and
occasional small errors on uncommon names or heavy accents, real, measurable
tradeoffs against the cloud-backed accuracy of a large hosted model, not a
one-for-one replacement.
If accuracy on a specific test clip is disappointing, the first thing to try is a
larger model ("medium" or "large-v3"), not a WordPress-side change. That tradeoff
against speed and hardware requirements is faster-whisper’s, not this plugin code’s,
to manage.
Recap
faster-whisper, the actively maintained, MIT-licensed, CTranslate2-based
reimplementation of OpenAI’s real Whisper model, runs fully offline and gives you a
transcription pipeline that never sends audio anywhere you don’t control. Since it’s a
library, not a server, a small FastAPI wrapper makes it callable over plain HTTP, and
wp_remote_post() calls it from WordPress using the same multipart pattern this track
already established for third-party transcription APIs. The real cost is
infrastructure you have to run and manage yourself, and a genuine speed and accuracy
tradeoff tied to model size, reach for this when Lesson 2’s browser API’s privacy or
support gaps actually matter for your project.
Resources & further reading
- OpenAI Whisper repository, GitHub
- faster-whisper repository, GitHub
- wp_remote_post() function reference, developer.wordpress.org
- Audio Transcription, Course 13