commit e6b36c2b48ead07e3fd94ef61e21ec333078a5b5 Author: ddidderr Date: Sat Sep 5 11:40:26 2026 +0200 feat: add simple audio transcription command Convert audio and video audio tracks to mono 16 kHz WAV with FFmpeg, then submit them to OpenRouter's MAI-Transcribe-2 endpoint. Read the API key from the environment and print the transcript to stdout for easy redirection. Store request payloads in temporary files to avoid shell argument size limits, and document dependencies and usage. Test Plan: - bash -n transcribe and shellcheck transcribe passed. - All eight documented formats passed local FFmpeg conversion and mocked API request/response checks, including WAV rate and channel validation. - git diff --cached --check passed. - No live API request was made. diff --git a/README.md b/README.md new file mode 100644 index 0000000..84f6d04 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# Transcribe + +Requires Bash, FFmpeg, curl, jq, and standard coreutils. + +```sh +export OPENROUTER_API_KEY='sk-or-v1-...' +./transcribe recording.m4a +./transcribe video.mp4 > transcript.txt +``` + +Accepts m4a, mp4, aac, flac, opus, mp3, ogg, wav, and other formats your +FFmpeg supports. Uses the first audio track, converts it to mono 16 kHz WAV, +and sends it to OpenRouter's `microsoft/mai-transcribe-2` model. Temporary +files are removed on exit. The transcript goes to stdout; errors to stderr. + +One file per invocation; the whole recording is sent in one request, so +the service's audio length and request size limits apply. + +[OpenRouter model and API reference](https://openrouter.ai/microsoft/mai-transcribe-2) diff --git a/transcribe b/transcribe new file mode 100755 index 0000000..4f0d68d --- /dev/null +++ b/transcribe @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# == 1 && ($1 == -h || $1 == --help) ]]; then + echo "Usage: ${0##*/} AUDIO_FILE" + echo 'Transcribe audio using OPENROUTER_API_KEY; print the text to stdout.' + exit 0 +fi +if [[ $# != 1 ]]; then + echo "Usage: ${0##*/} AUDIO_FILE" >&2 + exit 1 +fi +if [[ ! -f $1 || ! -r $1 ]]; then + echo "Cannot read audio file: $1" >&2 + exit 1 +fi +: "${OPENROUTER_API_KEY:?Set OPENROUTER_API_KEY first}" +for tool in ffmpeg curl jq base64 mktemp; do + command -v "$tool" >/dev/null || { + echo "Missing dependency: $tool" >&2 + exit 1 + } +done + +tmp=$(mktemp -d) +trap 'rm -rf -- "$tmp"' EXIT +trap 'exit 130' INT +trap 'exit 143' TERM + +# Normalize any FFmpeg-readable audio (or the first audio track in a video). +ffmpeg -nostdin -hide_banner -loglevel error -i "$(realpath -- "$1")" \ + -map 0:a:0 -ac 1 -ar 16000 -c:a pcm_s16le "$tmp/audio.wav" + +# Keep large audio payloads out of shell arguments (which have a size limit). +base64 <"$tmp/audio.wav" | tr -d '\n\r' >"$tmp/audio.b64" +jq -n --rawfile data "$tmp/audio.b64" \ + '{model: "microsoft/mai-transcribe-2", input_audio: {data: $data, format: "wav"}}' \ + >"$tmp/request.json" + +curl --silent --show-error --fail-with-body \ + https://openrouter.ai/api/v1/audio/transcriptions \ + -H "Authorization: Bearer $OPENROUTER_API_KEY" \ + -H 'Content-Type: application/json' \ + --data-binary @"$tmp/request.json" -o "$tmp/response.json" || { + cat "$tmp/response.json" >&2 + exit 1 +} +jq -r 'if (.text | type) == "string" then .text + else error("Transcription failed: " + (.error.message // tojson)) end' \ + "$tmp/response.json"