src / media / ffmpegPath.ts

/**
 * @file Resolve ffmpeg/ffprobe paths — LM Studio runs without the user's shell PATH,
 * so we search common installation directories explicitly.
 */

import { stat } from "fs/promises";

const FFMPEG_SEARCH_PATHS = [
  "/opt/homebrew/bin/ffmpeg",
  "/usr/local/bin/ffmpeg",
  "/usr/bin/ffmpeg",
  "/opt/local/bin/ffmpeg",
];

const FFPROBE_SEARCH_PATHS = [
  "/opt/homebrew/bin/ffprobe",
  "/usr/local/bin/ffprobe",
  "/usr/bin/ffprobe",
  "/opt/local/bin/ffprobe",
];

let cachedFfmpegPath: string | null = null;
let cachedFfprobePath: string | null = null;

async function findExecutable(candidates: string[]): Promise<string | null> {
  for (const p of candidates) {
    try {
      const s = await stat(p);
      if (s.isFile()) return p;
    } catch {
      // not found, try next
    }
  }
  return null;
}

export async function getFfmpegPath(): Promise<string> {
  if (cachedFfmpegPath) return cachedFfmpegPath;
  const found = await findExecutable(FFMPEG_SEARCH_PATHS);
  if (!found) {
    throw new Error(
      "ffmpeg not found. Install it with: brew install ffmpeg\n" +
      `Searched: ${FFMPEG_SEARCH_PATHS.join(", ")}`,
    );
  }
  cachedFfmpegPath = found;
  return found;
}

export async function getFfprobePath(): Promise<string> {
  if (cachedFfprobePath) return cachedFfprobePath;
  const found = await findExecutable(FFPROBE_SEARCH_PATHS);
  if (!found) {
    throw new Error(
      "ffprobe not found. Install it with: brew install ffmpeg\n" +
      `Searched: ${FFPROBE_SEARCH_PATHS.join(", ")}`,
    );
  }
  cachedFfprobePath = found;
  return found;
}