Skip to content

Commit 16702a8

Browse files
maxitoonclaude
andcommitted
Fix corrupted chunk audio: use sox raw reader + trim instead of dd
The dd byte extraction produced corrupted PCM because the WAV header size was assumed to be 44 bytes (can vary with extra metadata chunks). This caused Whisper to hear noise and hallucinate "(machine whirring)". New approach: tell sox to read the recording as raw PCM (-t raw) which ignores the unfinalised WAV header entirely, then use sox trim to extract time ranges. Sox handles all the byte math correctly. Also fix hallucination filter to catch (parenthesized) patterns too. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fa00cc4 commit 16702a8

1 file changed

Lines changed: 21 additions & 24 deletions

File tree

whisper-transcribe-with-download.sh

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,15 @@ original_live_transcription() {
190190
trap cleanup SIGINT SIGTERM
191191

192192
# Incremental chunk-based live transcription
193-
# rec writes 16kHz mono 16-bit PCM = 32000 bytes/sec
194-
# WAV header is not finalized until rec stops, so we calculate
195-
# duration from file size and extract raw PCM bytes directly.
193+
# rec writes 16kHz mono 16-bit PCM = 32000 bytes/sec.
194+
# The WAV header is NOT finalized until rec stops, so we:
195+
# 1. Estimate duration from file size
196+
# 2. Tell sox to read the file as raw PCM (-t raw), bypassing the broken header
197+
# 3. Use sox trim to extract time ranges directly
196198
local BYTES_PER_SEC=32000
197-
local WAV_HEADER_SIZE=44
198199
local MIN_CHUNK_SECS=10
199200
local OVERLAP_SECS=2
200-
local last_byte_offset=$WAV_HEADER_SIZE
201+
local last_secs=0
201202
local chunk_count=0
202203
local running_transcript="/tmp/running_transcript_${TIMESTAMP}.txt"
203204
touch "$running_transcript"
@@ -206,29 +207,26 @@ original_live_transcription() {
206207
if [ -f "$recording_file" ]; then
207208
local current_size
208209
current_size=$(stat -f%z "$recording_file" 2>/dev/null || echo "0")
209-
local new_bytes=$((current_size - last_byte_offset))
210-
local new_secs=$((new_bytes / BYTES_PER_SEC))
210+
local current_secs=$((current_size / BYTES_PER_SEC))
211+
local new_secs=$((current_secs - last_secs))
211212

212213
if [ "$new_secs" -ge "$MIN_CHUNK_SECS" ]; then
213214
chunk_count=$((chunk_count + 1))
214215

215-
# Include overlap from previous chunk for context (except first chunk)
216-
local overlap_bytes=0
217-
if [ "$chunk_count" -gt 1 ]; then
218-
overlap_bytes=$((OVERLAP_SECS * BYTES_PER_SEC))
219-
fi
220-
local extract_offset=$((last_byte_offset - overlap_bytes))
221-
local chunk_bytes=$((new_secs * BYTES_PER_SEC + overlap_bytes))
222-
223-
local raw_file="/tmp/chunk_raw_${TIMESTAMP}_${chunk_count}.pcm"
224216
local chunk_file="/tmp/chunk_${TIMESTAMP}_${chunk_count}.wav"
225217

226-
# Extract raw PCM bytes (bypass incomplete WAV header)
227-
dd if="$recording_file" of="$raw_file" bs=1 skip="$extract_offset" count="$chunk_bytes" 2>/dev/null
218+
# Include overlap from previous chunk for context (except first chunk)
219+
local trim_start=$last_secs
220+
if [ "$chunk_count" -gt 1 ] && [ "$trim_start" -ge "$OVERLAP_SECS" ]; then
221+
trim_start=$((trim_start - OVERLAP_SECS))
222+
fi
223+
local trim_duration=$((current_secs - trim_start))
228224

229-
# Convert raw PCM to valid WAV for whisper-cli
230-
sox -t raw -r 16000 -c 1 -b 16 -e signed-integer -L "$raw_file" "$chunk_file" 2>/dev/null
231-
rm -f "$raw_file"
225+
# Read recording as raw PCM (bypasses unfinalised WAV header)
226+
# and extract only the time range we need
227+
sox -t raw -r 16000 -c 1 -b 16 -e signed-integer -L \
228+
"$recording_file" "$chunk_file" \
229+
trim "$trim_start" "$trim_duration" 2>/dev/null
232230

233231
if [ -f "$chunk_file" ] && [ -s "$chunk_file" ]; then
234232
local chunk_transcript="/tmp/chunk_transcript_${TIMESTAMP}_${chunk_count}"
@@ -241,7 +239,7 @@ original_live_transcription() {
241239
new_text=$(cat "${chunk_transcript}.txt" | sed '/^$/d')
242240
# Filter out Whisper hallucinations on silence/noise
243241
local filtered_text
244-
filtered_text=$(echo "$new_text" | grep -viE '^\[.*\]$|^\*.*\*$|^[[:space:]]*$' || true)
242+
filtered_text=$(echo "$new_text" | grep -viE '^\s*[\[\(\*].*[\]\)\*]\s*$|^[[:space:]]*$' || true)
245243
if [ -n "$filtered_text" ]; then
246244
echo "$filtered_text" >> "$running_transcript"
247245
print_color "$GREEN" "$filtered_text" >&2
@@ -251,8 +249,7 @@ original_live_transcription() {
251249
rm -f "$chunk_file" "${chunk_transcript}.txt" 2>/dev/null
252250
fi
253251

254-
# Advance position (without overlap — overlap is re-read next time)
255-
last_byte_offset=$((last_byte_offset + new_secs * BYTES_PER_SEC))
252+
last_secs=$current_secs
256253
fi
257254
fi
258255
sleep 2

0 commit comments

Comments
 (0)