{"record":{"id":"2719427bd17aa472","repo":"usememos/memos","slug":"webm-has-no-opus-audio-track","errorCode":null,"errorMessage":"webm has no Opus audio track","messagePattern":"webm has no Opus audio track","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/ai/audio/webm.go","lineNumber":51,"sourceCode":"//\n// The output is mono or stereo at 48 kHz (Opus's native decode rate),\n// regardless of the original encoder's hint. Pre-skip samples declared in\n// the OpusHead are discarded to avoid the encoder's startup padding.\n//\n// The function reads the entire WebM document into memory; callers should\n// enforce their own size limits before invoking it.\nfunc WebMOpusToWAV(input []byte) ([]byte, error) {\n\tvar doc struct {\n\t\tHeader  webm.EBMLHeader `ebml:\"EBML\"`\n\t\tSegment webm.Segment    `ebml:\"Segment\"`\n\t}\n\tif err := ebml.Unmarshal(bytes.NewReader(input), &doc); err != nil && !errors.Is(err, io.EOF) {\n\t\treturn nil, errors.Wrap(err, \"parse webm\")\n\t}\n\n\ttrack := findOpusTrack(doc.Segment.Tracks.TrackEntry)\n\tif track == nil {\n\t\treturn nil, errors.New(\"webm has no Opus audio track\")\n\t}\n\tif len(track.CodecPrivate) < opusHeadMinLength {\n\t\treturn nil, errors.Errorf(\"invalid OpusHead: expected at least %d bytes, got %d\", opusHeadMinLength, len(track.CodecPrivate))\n\t}\n\n\tchannels := int(track.Audio.Channels)\n\tif channels < 1 || channels > 2 {\n\t\treturn nil, errors.Errorf(\"unsupported Opus channel count: %d\", channels)\n\t}\n\tpreSkip := int(binary.LittleEndian.Uint16(track.CodecPrivate[10:12]))\n\n\tdecoder := opus.NewDecoder()\n\tif err := decoder.Init(opusOutputSampleRate, channels); err != nil {\n\t\treturn nil, errors.Wrap(err, \"init opus decoder\")\n\t}\n\n\tpcm := make([]int16, 0, 1<<16)\n\tframe := make([]int16, maxOpusPacketSamples*channels)","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/ai/audio/webm.go#L33-L69","documentation":"Returned by WebMOpusToWAV (internal/ai/audio) when the parsed WebM container's Segment.Tracks.TrackEntry contains no track with an Opus codec. The converter only supports the Opus audio tracks that browser MediaRecorder produces; a WebM with another codec (or video-only) cannot be converted to WAV for STT.","triggerScenarios":"Feeding WebMOpusToWAV audio recorded with a MediaRecorder mimeType whose audio codec is not Opus (e.g., 'audio/webm;codecs=vorbis' where supported, or a video/webm recording with no audio track); uploading an arbitrary .webm file to the voice/STT path; a malformed file whose tracks parse but expose no Opus entry.","commonSituations":"Cross-browser differences in MediaRecorder codec support; users uploading non-recorded .webm files to the memo voice input; screen-capture webm (video-only, or VP8/VP9 video + no Opus audio); truncated recordings where the Tracks element lacks the audio entry.","solutions":["Record with an explicitly Opus mime type: MediaRecorder.isTypeSupported('audio/webm;codecs=opus') and use it.","If the input is an uploaded file, validate/convert it server-side (ffmpeg -i in.webm -vn -c:a libopus out.webm) before this path.","For video webm, strip to the audio track first; this converter never reads video tracks.","Reject or transcode unsupported inputs upstream with a clear user-facing message."],"exampleFix":"// before\nconst rec = new MediaRecorder(stream); // browser-default codec, may not be Opus\n\n// after\nconst mime = ['audio/webm;codecs=opus', 'audio/webm'].find(m => MediaRecorder.isTypeSupported(m));\nconst rec = new MediaRecorder(stream, mime ? { mimeType: mime } : undefined);","handlingStrategy":"validation","validationCode":"// TypeScript — negotiate an Opus mime before recording\nconst mime = [\"audio/webm;codecs=opus\", \"audio/webm\"].find(m => MediaRecorder.isTypeSupported(m));\nif (!mime) throw new Error(\"This browser cannot record Opus WebM audio\");\nconst rec = new MediaRecorder(stream, { mimeType: mime });","typeGuard":null,"tryCatchPattern":"// Go\nwav, err := audio.WebMOpusToWAV(blob)\nif err != nil {\n  if strings.Contains(err.Error(), \"no Opus audio track\") {\n    return status.Errorf(codes.InvalidArgument, \"Audio must be WebM with an Opus track; re-record or convert the file\")\n  }\n  return err\n}","preventionTips":["Always set an explicit Opus mimeType on MediaRecorder when supported.","Prefer audio/webm over video/webm recordings for voice input.","If accepting uploads, convert with ffmpeg to Opus WebM (or WAV) before the STT path."],"tags":["ai","audio","webm","opus","mediarecorder","go"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}