{"record":{"id":"9ed7c80d33db9970","repo":"heygen-com/hyperframes","slug":"unsupported-file-type-ext","errorCode":null,"errorMessage":"Unsupported file type: ${ext}","messagePattern":"Unsupported file type: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/whisper/transcribe.ts","lineNumber":435,"sourceCode":"  });\n\n  // 3. Prepare audio\n  let wavPath: string;\n  const ext = extname(inputPath).toLowerCase();\n\n  if (isAudioFile(inputPath)) {\n    options?.onProgress?.(\"Preparing audio...\");\n    wavPath = prepareAudio(inputPath);\n  } else if (isVideoFile(inputPath)) {\n    if (!hasFFmpeg()) {\n      throw new Error(\n        `ffmpeg is required to extract audio from video. Install: ${getFFmpegInstallHint()}`,\n      );\n    }\n    options?.onProgress?.(\"Extracting audio from video...\");\n    wavPath = extractAudio(inputPath);\n  } else {\n    throw new Error(`Unsupported file type: ${ext}`);\n  }\n\n  // 4. Detect language and ensure correct model\n  let effectiveModel = model;\n  let effectiveModelPath = modelPath;\n  let detectedLanguage = options?.language ?? null;\n\n  // Only auto-detect language when using a multilingual model.\n  // .en models always report \"en\" regardless of actual language, so detection\n  // would be a no-op. If the user chose .en, they want English.\n  if (!detectedLanguage && !effectiveModel.endsWith(\".en\")) {\n    options?.onProgress?.(\"Detecting language...\");\n    detectedLanguage = detectLanguage(whisper.executablePath, effectiveModelPath, wavPath);\n  }\n\n  if (detectedLanguage && detectedLanguage !== \"en\" && effectiveModel.endsWith(\".en\")) {\n    const multilingualModel = effectiveModel.replace(/\\.en$/, \"\");\n    options?.onProgress?.(","sourceCodeStart":417,"sourceCodeEnd":453,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/whisper/transcribe.ts#L417-L453","documentation":"Thrown by transcribe() when the input file's extension matches neither AUDIO_EXTENSIONS (.mp3/.wav/.m4a/.aac/.ogg/.flac) nor VIDEO_EXTENSIONS (.mp4/.webm/.mov/.mkv/.avi). The lowercased extension is interpolated into the message so you can see exactly what was rejected. This is a hard gate: only those two extension sets are accepted, decided by extname() alone (not by file content / MIME sniffing).","triggerScenarios":"Passing a file whose extname() is outside both sets — e.g. .opus, .wma, .aiff, .ts, .m2ts, .3gp, .gif, .bmp, a dotfile with no extension, or a path where the extension is upper-case-mangled after a manual toLowerCase bug. A path that is actually a directory also lands here because extname returns ''.","commonSituations":"User drags a .mov exported from a phone renamed to .MOV (covered by toLowerCase) but more often an unusual container like .opus audio or .m2ts video; pointing transcribe at a still image or a text file by mistake; a pipeline that auto-generates a filename with no extension.","solutions":["Check the file extension against the accepted sets and convert the source to a supported format first (e.g. ffmpeg -i in.opus out.wav, or transcode video to .mp4).","If the file genuinely is supported audio/video with a non-listed extension, rename or transcode it to one of the accepted extensions before calling transcribe().","Confirm the path points at a file, not a directory — extname on a directory yields '' and falls through to this branch.","Request/confirm whether your container should be added to AUDIO_EXTENSIONS or VIDEO_EXTENSIONS upstream if it is a common format HyperFrames should support."],"exampleFix":"// before\nawait transcribe(\"./recording.opus\", \"./out\"); // throws Unsupported file type: .opus\n\n// after — transcode to a supported extension first\nimport { execFileSync } from \"node:child_process\";\nexecFileSync(\"ffmpeg\", [\"-y\", \"-i\", \"./recording.opus\", \"-ar\", \"16000\", \"-ac\", \"1\", \"./recording.wav\"]);\nawait transcribe(\"./recording.wav\", \"./out\");","handlingStrategy":"validation","validationCode":"import { extname } from \"node:path\";\nconst AUDIO = new Set([\".mp3\",\".wav\",\".m4a\",\".aac\",\".ogg\",\".flac\"]);\nconst VIDEO = new Set([\".mp4\",\".webm\",\".mov\",\".mkv\",\".avi\"]);\nfunction isTranscribable(p: string): boolean {\n  const ext = extname(p).toLowerCase();\n  return AUDIO.has(ext) || VIDEO.has(ext);\n}\nif (!isTranscribable(inputPath)) throw new Error(`unsupported extension ${extname(inputPath)}`);","typeGuard":"function isTranscribableFile(p: string): boolean {\n  const ext = extname(p).toLowerCase();\n  return AUDIO.has(ext) || VIDEO.has(ext);\n}","tryCatchPattern":"try { await transcribe(p, out); }\ncatch (err) { if (/Unsupported file type/.test(String(err))) { /* convert source */ } else throw err; }","preventionTips":["Validate the extension against the accepted sets before calling transcribe().","Transcode unusual containers (opus, m2ts) to .wav or .mp4 first.","Ensure the path is a file, not a directory."],"tags":["file-type","validation","transcription","audio","video"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}