summaryrefslogtreecommitdiff
path: root/xiaomi-asr.sh
blob: 2452c455e7cf2764c1ed63c0cbc13f91adedb322 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#!/usr/bin/env bash
# Xiaomi MiMo ASR wrapper for Hermes command STT provider.
#
# Usage (called by Hermes automatically via placeholders):
#   xiaomi-asr.sh <input_audio> <output_txt> [model] [language]
#
# Xiaomi ASR uses chat/completions API with input_audio format,
# NOT the standard /v1/audio/transcriptions endpoint.
# Only wav and mp3 are accepted — other formats are auto-converted
# to 16kHz mono WAV via ffmpeg.

set -euo pipefail

INPUT="${1:?usage: xiaomi-asr.sh <input> <output> [model] [language]}"
OUTPUT="${2:?usage: xiaomi-asr.sh <input> <output> [model] [language]}"
MODEL="${3:-mimo-v2.5-asr}"
LANGUAGE="${4:-zh}"

API_KEY="${XIAOMI_API_KEY:?XIAOMI_API_KEY is not set}"
BASE_URL="${XIAOMI_BASE_URL:-https://api.xiaomimimo.com/v1}"

# Determine audio format from extension
EXT="${INPUT##*.}"
case "$EXT" in
  wav|WAV) FORMAT="wav" ;;
  mp3|MP3) FORMAT="mp3" ;;
  *) FORMAT="wav" ;;  # Will convert to WAV via ffmpeg
esac

# If the file is not already wav/mp3, convert to wav via ffmpeg
AUDIO_INPUT="$INPUT"
if [ "$FORMAT" = "wav" ] && [[ ! "$EXT" =~ ^[Ww][Aa][Vv]$ ]] && [[ ! "$EXT" =~ ^[Mm][Pp]3$ ]]; then
  if command -v ffmpeg &>/dev/null; then
    AUDIO_INPUT=$(mktemp /tmp/hermes-asr-XXXXXX.wav)
    ffmpeg -y -i "$INPUT" -ar 16000 -ac 1 -f wav "$AUDIO_INPUT" 2>/dev/null
    trap 'rm -f "$AUDIO_INPUT"' EXIT
  else
    echo "ffmpeg is required to convert .$EXT to wav/mp3 for Xiaomi ASR" >&2
    exit 1
  fi
fi

# Build JSON payload and call API using Python to avoid arg-length limits
RESPONSE=$(python - "$AUDIO_INPUT" "$MODEL" "$FORMAT" "$API_KEY" "$BASE_URL" << 'PYEOF'
import base64, json, sys, urllib.request, urllib.error, ssl

input_path = sys.argv[1]
model = sys.argv[2]
fmt = sys.argv[3]
api_key = sys.argv[4]
base_url = sys.argv[5]

with open(input_path, "rb") as f:
    audio_b64 = base64.b64encode(f.read()).decode()

payload = json.dumps({
    "model": model,
    "messages": [{
        "role": "user",
        "content": [
            {"type": "input_audio", "input_audio": {"data": audio_b64, "format": fmt}},
        ]
    }]
}).encode()

req = urllib.request.Request(
    f"{base_url}/chat/completions",
    data=payload,
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    },
)
ctx = ssl.create_default_context()
try:
    resp = urllib.request.urlopen(req, context=ctx, timeout=300)
    print(resp.read().decode())
except urllib.error.HTTPError as e:
    body = e.read().decode()
    print(f"HTTP {e.code}: {body}", file=sys.stderr)
    sys.exit(1)
PYEOF
)

# Extract the transcribed text from the response
TRANSCRIPT=$(python -c "
import json, sys
resp = json.loads(sys.stdin.read())
choices = resp.get('choices', [])
if choices:
    msg = choices[0].get('message', {})
    content = msg.get('content', '')
    print(content.strip())
else:
    err = resp.get('error', {})
    print(err.get('message', 'No transcription result'), file=sys.stderr)
    sys.exit(1)
" <<< "$RESPONSE")

if [ -z "$TRANSCRIPT" ]; then
  echo "Xiaomi ASR returned empty transcript" >&2
  exit 1
fi

# Write the transcript to the output file
echo "$TRANSCRIPT" > "$OUTPUT"
echo "$TRANSCRIPT"