Skill: ayalili/multimodal-parser (version 1.0.1)

--- name: multimodal-parser slug: multimodal-parser description: Unified multi-modal content parser for images, PDF, DOCX, audio, auto OCR/transcription, output structured text for LLM processing --- # 📄 多模态内容解析器 ## 核心亮点 1. 🔄 **统一接口**：一套API支持图片/PDF/Word/音频4大类格式解析，不需要对接多个服务 2. 🚀 **开箱即用**：内置OCR、音频转文字、文档解析能力，零配置即可使用 3. 📝 **多格式输出**：支持纯文本/Markdown/结构化JSON三种输出格式，适配不同LLM处理需求 4. 💡 **友好错误提示**：依赖缺失时自动给出安装命令，新手也能快速上手 ## 🎯 适用场景 - 多模态Agent的内容解析层 - 文档问答、知识库构建场景的文件预处理 - 图片OCR识别、语音转文字需求 - 批量文档解析与结构化处理 ## 📝 参数说明 | 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | file_path | string | 是 | - | 要解析的文件路径 | | file_type | string | 否 | auto | 文件类型：image/pdf/docx/audio/auto | | output_format | string | 否 | text | 输出格式：text/markdown/structured | | options.ocr_lang | string | 否 | chi_sim+eng | OCR识别语言 | | options.audio_model | string | 否 | base | Whisper模型大小（base/small/medium/large） | | options.pdf_page_range | tuple | 否 | undefined | PDF解析页码范围，如[1, 10]表示解析第1-10页 | ## 💡 开箱即用示例 ### 图片OCR识别 ```typescript const result = await skills.multimodalParser({ file_path: "./resume.jpg", file_type: "image", output_format: "markdown" }); ``` ### PDF解析（指定页码范围） ```typescript const result = await skills.multimodalParser({ file_path: "./document.pdf", output_format: "structured", options: { pdf_page_range: [1, 50] // 只解析前50页 } }); ``` ### 音频转文字 ```typescript const result = await skills.multimodalParser({ file_path: "./meeting.mp3", options: { audio_model: "small" // 用small模型，速度更快 } }); ``` ## 🔧 依赖安装 根据需要解析的文件类型安装对应依赖： ```bash # 全量安装所有依赖（推荐） ## macOS brew install tesseract tesseract-lang poppler pandoc pip install openai-whisper ffmpeg ## Ubuntu/Debian apt install tesseract-ocr tesseract-ocr-chi-sim poppler-utils pandoc ffmpeg pip install openai-whisper ``` ## 技术实现说明 - 基于成熟的开源工具链（Tesseract/Poppler/Whisper/Pandoc） - 自动文件类型检测，无需手动指定格式 - 模块化设计，可轻松扩展支持更多格式 - 输出格式标准化，直接可被LLM处理

path: index.ts
content: import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";

// 类型定义
const MultimodalParserParams = z.object({
  file_path: z.string(),
  file_type: z.enum(["image", "pdf", "docx", "audio", "auto"]).optional().default("auto"),
  output_format: z.enum(["text", "markdown", "structured"]).optional().default("text"),
  options: z.object({
    ocr_lang: z.string().optional().default("chi_sim+eng"),
    audio_model: z.string().optional().default("base"),
    extract_images: z.boolean().optional().default(false),
    pdf_page_range: z.tuple([z.number(), z.number()]).optional(),
  }).optional().default({})
});

type MultimodalParserParams = z.infer<typeof MultimodalParserParams>;

/**
 * 检查依赖是否存在
 */
async function checkDependency(cmd: string): Promise<boolean> {
  try {
    const res = await new Deno.Command(cmd, { 
      args: ["--version"],
      stderr: "null",
      stdout: "null"
    }).output();
    return res.success;
  } catch {
    return false;
  }
}

/**
 * 检测文件类型
 */
function detectFileType(filePath: string): string | null {
  const ext = filePath.split(".").pop()?.toLowerCase();
  const typeMap: Record<string, string> = {
    "jpg": "image", "jpeg": "image", "png": "image", "webp": "image", "bmp": "image",
    "pdf": "pdf",
    "docx": "docx", "doc": "docx",
    "mp3": "audio", "wav": "audio", "m4a": "audio", "flac": "audio", "ogg": "audio"
  };
  return ext ? typeMap[ext] || null : null;
}

/**
 * 图片解析（OCR）
 */
async function parseImage(filePath: string, options: any): Promise<any> {
  // 检查依赖
  if (!await checkDependency("tesseract")) {
    throw new Error("OCR依赖未安装，请执行：\n- macOS: brew install tesseract tesseract-lang\n- Ubuntu: apt install tesseract-ocr tesseract-ocr-chi-sim");
  }

  const args = [filePath, "stdout", "-l", options.ocr_lang || "chi_sim+eng"];
  const cmd = new Deno.Command("tesseract", { args });
  const { stdout, stderr, success } = await cmd.output();
  
  if (!success) {
    throw new Error(`OCR失败：${new TextDecoder().decode(stderr)}`);
  }
  
  const text = new TextDecoder().decode(stdout).trim();
  return {
    text,
    metadata: { 
      file_type: "image", 
      ocr_lang: options.ocr_lang,
      file_size: (await Deno.stat(filePath)).size
    }
  };
}

/**
 * PDF解析
 */
async function parsePdf(filePath: string, options: any): Promise<any> {
  if (!await checkDependency("pdftotext")) {
    throw new Error("PDF解析依赖未安装，请执行：\n- macOS: brew install poppler\n- Ubuntu: apt install poppler-utils");
  }

  const args = ["-layout"];
  if (options.pdf_page_range) {
    args.push("-f", options.pdf_page_range[0].toString(), "-l", options.pdf_page_range[1].toString());
  }
  args.push(filePath, "-");

  const cmd = new Deno.Command("pdftotext", { args });
  const { stdout, stderr, success } = await cmd.output();
  
  if (!success) {
    throw new Error(`PDF解析失败：${new TextDecoder().decode(stderr)}`);
  }
  
  const text = new TextDecoder().decode(stdout).trim();
  return {
    text,
    metadata: { 
      file_type: "pdf",
      page_range: options.pdf_page_range,
      file_size: (await Deno.stat(filePath)).size
    }
  };
}

/**
 * Docx解析
 */
async function parseDocx(filePath: string, options: any): Promise<any> {
  if (!await checkDependency("pandoc")) {
    throw new Error("DOCX解析依赖未安装，请执行：\n- macOS: brew install pandoc\n- Ubuntu: apt install pandoc");
  }

  const cmd = new Deno.Command("pandoc", { args: [filePath, "-t", "markdown"] });
  const { stdout, stderr, success } = await cmd.output();
  
  if (!success) {
    throw new Error(`DOCX解析失败：${new TextDecoder().decode(stderr)}`);
  }
  
  const text = new TextDecoder().decode(stdout).trim();
  return {
    text,
    metadata: { 
      file_type: "docx",
      file_size: (await Deno.stat(filePath)).size
    }
  };
}

/**
 * 音频解析（转文字）
 */
async function parseAudio(filePath: string, options: any): Promise<any> {
  if (!await checkDependency("whisper")) {
    throw new Error("音频转文字依赖未安装，请执行：pip install openai-whisper ffmpeg");
  }

  const args = [
    filePath, 
    "--model", options.audio_model || "base", 
    "--output_format", "txt",
    "--output_dir", "-"
  ];

  const cmd = new Deno.Command("whisper", { args });
  const { stdout, stderr, success } = await cmd.output();
  
  if (!success) {
    throw new Error(`音频转文字失败：${new TextDecoder().decode(stderr)}`);
  }
  
  const text = new TextDecoder().decode(stdout).trim();
  return {
    text,
    metadata: { 
      file_type: "audio", 
      model: options.audio_model,
      file_size: (await Deno.stat(filePath)).size
    }
  };
}

/**
 * 转换为Markdown格式
 */
function convertToMarkdown(result: any, fileType: string): string {
  const header = `# ${fileType.toUpperCase()} 解析结果\n\n`;
  const metadata = result.metadata ? `## 元数据\n\`\`\`json\n${JSON.stringify(result.metadata, null, 2)}\n\`\`\`\n\n` : "";
  const content = `## 内容\n\n${result.text}`;
  return header + metadata + content;
}

/**
 * OpenClaw Skill: 多模态内容解析器
 * 统一解析图片、PDF、Word、音频等多格式文件，输出结构化文本
 */
export default async function multimodalParser(params: MultimodalParserParams) {
  const validatedParams = MultimodalParserParams.parse(params);
  const { file_path, file_type, output_format, options } = validatedParams;

  // 检查文件是否存在
  try {
    await Deno.stat(file_path);
  } catch {
    return { success: false, error: `文件不存在：${file_path}` };
  }

  // 自动检测文件类型
  const detectedType = file_type === "auto" 
    ? detectFileType(file_path) 
    : file_type;

  if (!detectedType) {
    return { success: false, error: "不支持的文件类型，支持：jpg/png/pdf/docx/mp3/wav/m4a" };
  }

  try {
    let result: any;

    switch (detectedType) {
      case "image":
        result = await parseImage(file_path, options);
        break;
      case "pdf":
        result = await parsePdf(file_path, options);
        break;
      case "docx":
        result = await parseDocx(file_path, options);
        break;
      case "audio":
        result = await parseAudio(file_path, options);
        break;
      default:
        return { success: false, error: "不支持的文件类型" };
    }

    // 格式化输出
    let output: string | object;
    switch (output_format) {
      case "markdown":
        output = convertToMarkdown(result, detectedType);
        break;
      case "structured":
        output = result;
        break;
      default:
        output = result.text;
    }

    return {
      success: true,
      file_type: detectedType,
      output_format,
      content: output,
      metadata: result.metadata || {}
    };

  } catch (error) {
    return {
      success: false,
      error: `解析失败：${(error as Error).message}`
    };
  }
}


path: README.md
content: # 📄 多模态内容解析器
## 核心亮点
1. 🔄 **统一接口**：一套API支持图片/PDF/Word/音频4大类格式解析，不需要对接多个服务
2. 🚀 **开箱即用**：内置OCR、音频转文字、文档解析能力，零配置即可使用
3. 📝 **多格式输出**：支持纯文本/Markdown/结构化JSON三种输出格式，适配不同LLM处理需求
4. 💡 **友好错误提示**：依赖缺失时自动给出安装命令，新手也能快速上手

## 🎯 适用场景
- 多模态Agent的内容解析层
- 文档问答、知识库构建场景的文件预处理
- 图片OCR识别、语音转文字需求
- 批量文档解析与结构化处理

## 📝 参数说明
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| file_path | string | 是 | - | 要解析的文件路径 |
| file_type | string | 否 | auto | 文件类型：image/pdf/docx/audio/auto |
| output_format | string | 否 | text | 输出格式：text/markdown/structured |
| options.ocr_lang | string | 否 | chi_sim+eng | OCR识别语言 |
| options.audio_model | string | 否 | base | Whisper模型大小（base/small/medium/large） |
| options.pdf_page_range | tuple | 否 | undefined | PDF解析页码范围，如[1, 10]表示解析第1-10页 |

## 💡 开箱即用示例
### 图片OCR识别
```typescript
const result = await skills.multimodalParser({
  file_path: "./resume.jpg",
  file_type: "image",
  output_format: "markdown"
});
```

### PDF解析（指定页码范围）
```typescript
const result = await skills.multimodalParser({
  file_path: "./document.pdf",
  output_format: "structured",
  options: {
    pdf_page_range: [1, 50] // 只解析前50页
  }
});
```

### 音频转文字
```typescript
const result = await skills.multimodalParser({
  file_path: "./meeting.mp3",
  options: { 
    audio_model: "small" // 用small模型，速度更快
  }
});
```

## 🔧 依赖安装
根据需要解析的文件类型安装对应依赖：
```bash
# 全量安装所有依赖（推荐）
## macOS
brew install tesseract tesseract-lang poppler pandoc
pip install openai-whisper ffmpeg

## Ubuntu/Debian
apt install tesseract-ocr tesseract-ocr-chi-sim poppler-utils pandoc ffmpeg
pip install openai-whisper
```

## 技术实现说明
- 基于成熟的开源工具链（Tesseract/Poppler/Whisper/Pandoc）
- 自动文件类型检测，无需手动指定格式
- 模块化设计，可轻松扩展支持更多格式
- 输出格式标准化，直接可被LLM处理


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)