diff --git a/README.md b/README.md index 1743230..d14857c 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ | Skill | Description | License | |-------|-------------|---------| +| `mimo-v2-5-omni` | MiMo V2.5 multimodal understanding. Supports image, video, and audio analysis including OCR, video content analysis, and audio transcription. | MIT | | `mimo-v2-5-tts` | MiMo V2.5 TTS voice synthesis. Supports preset voices, voice design, voice cloning, emotion styles, dialects, and singing. Supports natural language control, Director Mode, and audio tag control. | MIT | ## Installation diff --git a/README_zh.md b/README_zh.md index 75a032b..750e3e7 100644 --- a/README_zh.md +++ b/README_zh.md @@ -11,6 +11,7 @@ | Skill | 描述 | 许可证 | |-------|------|--------| +| `mimo-v2-5-omni` | MiMo V2.5 多模态理解。支持图片、视频、音频分析,包括 OCR、视频内容分析、音频转录。 | MIT | | `mimo-v2-5-tts` | MiMo V2.5 TTS 语音合成。支持预置音色、音色设计、音色克隆、情绪风格、方言、唱歌。支持自然语言控制、导演模式、音频标签控制。 | MIT | ## 安装 diff --git a/skills/mimo-v2-5-omni/SKILL.md b/skills/mimo-v2-5-omni/SKILL.md new file mode 100644 index 0000000..38b86ca --- /dev/null +++ b/skills/mimo-v2-5-omni/SKILL.md @@ -0,0 +1,88 @@ +--- +name: mimo-v2-5-omni +description: "MiMo V2.5 多模态理解。使用小米 MiMo V2.5 模型进行图片、视频、音频的理解与分析。当需要识别图片内容、分析截图、OCR 文字提取、视频内容分析、音频转录、或用户发送了图片/视频/音频文件时激活此 skill。" +license: MIT +metadata: + version: 0.1.0 +--- + +# MiMo Omni + +使用小米 MiMo V2.5 模型进行多模态理解。支持图片、视频、音频三种媒体类型,支持本地文件和 URL 输入。 + +脚本目录:`$SKILLS_PATH/mimo-v2-5-omni/scripts/` + +> **`$SKILLS_PATH` 说明:** skills 目录路径,因部署环境而异。 + +## 能力概览 + +| 类型 | 支持格式 | 说明 | +|------|---------|------| +| **图片理解** | jpg, png, gif, webp, bmp | 图片描述、OCR、分类、对比 | +| **视频理解** | mp4, webm, avi, mov | 视频内容分析、画面描述、关键信息提取 | +| **音频理解** | mp3, wav, flac, ogg, m4a | 语音转录、音频内容分析 | + +## 环境依赖 + +| 环境变量 | 说明 | 必需 | +|---------|------|------| +| `MIMO_API_KEY` | MiMo API 密钥(MiMo 开放平台获取) | 是 | + +| 依赖 | 说明 | 必需 | +|------|------|------| +| `python3` | 运行脚本 | 是 | +| `openai` | `pip install openai` | 是 | + +## 用法 + +### 图片理解 + +```bash +python3 $SKILLS_PATH/mimo-v2-5-omni/scripts/mimo_omni.py image /path/to/photo.jpg --prompt "描述这张图片的内容" +``` + +### 视频理解 + +```bash +python3 $SKILLS_PATH/mimo-v2-5-omni/scripts/mimo_omni.py video /path/to/video.mp4 --prompt "总结这个视频的内容" +``` + +### 音频理解 + +```bash +python3 $SKILLS_PATH/mimo-v2-5-omni/scripts/mimo_omni.py audio /path/to/audio.mp3 --prompt "转录这段音频" +``` + +### URL 输入 + +```bash +python3 $SKILLS_PATH/mimo-v2-5-omni/scripts/mimo_omni.py image "https://example.com/photo.jpg" --prompt "这是什么?" +``` + +### 视频参数 + +```bash +python3 $SKILLS_PATH/mimo-v2-5-omni/scripts/mimo_omni.py video /path/to/video.mp4 \ + --prompt "描述视频内容" \ + --fps 5 \ + --resolution high +``` + +## 使用场景 + +| 场景 | 类型 | 提示词示例 | +|------|------|-----------| +| 图片描述 | image | "描述这张图片的内容" | +| OCR 文字提取 | image | "提取图片中的所有文字,保持原始格式" | +| 截图分析 | image | "分析这张截图,说明界面上有什么" | +| 视频内容总结 | video | "总结这个视频的主要内容" | +| 视频数据提取 | video | "提取视频中出现的所有数据和文字" | +| 语音转录 | audio | "完整转录这段音频中说的每一句话" | +| 音频分析 | audio | "分析这段音频的内容和情绪" | + +## 注意事项 + +- 大文件(视频/音频)处理时间较长,超时设为 300 秒 +- URL 输入需确保链接可公开访问 +- 视频默认抽帧率 2fps,可通过 `--fps` 调整 +- 音频建议先用 ffmpeg 转为 wav 格式以获得最佳效果 diff --git a/skills/mimo-v2-5-omni/scripts/mimo_omni.py b/skills/mimo-v2-5-omni/scripts/mimo_omni.py new file mode 100644 index 0000000..85c9865 --- /dev/null +++ b/skills/mimo-v2-5-omni/scripts/mimo_omni.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""MiMo Omni — 多模态理解(图片/视频/音频)。 + +模型: mimo-v2.5 +支持: 图片理解、视频理解、音频理解 + +Requires: + pip install openai + export MIMO_API_KEY=... +""" + +import argparse +import base64 +import mimetypes +import os +import sys +from pathlib import Path + +from openai import OpenAI + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="MiMo V2.5 多模态理解") + parser.add_argument( + "type", + choices=["image", "video", "audio"], + help="媒体类型: image / video / audio", + ) + parser.add_argument( + "input", + help="文件路径或 URL", + ) + parser.add_argument( + "--prompt", + required=True, + help="文本提示", + ) + parser.add_argument( + "--fps", + type=int, + default=2, + help="视频抽帧率 (仅 video 类型,默认 2)", + ) + parser.add_argument( + "--resolution", + default="default", + help="视频分辨率 (仅 video 类型,默认 default)", + ) + parser.add_argument( + "--max-tokens", + type=int, + default=4096, + help="最大输出 token 数 (默认 4096)", + ) + return parser.parse_args() + + +def build_client() -> OpenAI: + api_key = os.environ.get("MIMO_API_KEY") + if not api_key: + print("MIMO_API_KEY is not set", file=sys.stderr) + sys.exit(1) + return OpenAI(api_key=api_key, base_url="https://api.xiaomimimo.com/v1") + + +def load_media(media_type: str, file_input: str) -> dict: + """根据类型和输入方式构建 content block。""" + is_url = file_input.startswith("http://") or file_input.startswith("https://") + + if media_type == "image": + if is_url: + return {"type": "image_url", "image_url": {"url": file_input}} + path = _resolve_path(file_input) + mime = _guess_mime(path, "image/png") + b64 = _read_base64(path) + return {"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}} + + if media_type == "video": + if is_url: + return { + "type": "video_url", + "video_url": {"url": file_input}, + "fps": 2, + "media_resolution": "default", + } + path = _resolve_path(file_input) + mime = _guess_mime(path, "video/mp4") + b64 = _read_base64(path) + return { + "type": "video_url", + "video_url": {"url": f"data:{mime};base64,{b64}"}, + "fps": 2, + "media_resolution": "default", + } + + if media_type == "audio": + if is_url: + return {"type": "input_audio", "input_audio": {"data": file_input}} + path = _resolve_path(file_input) + mime = _guess_mime(path, "audio/wav") + b64 = _read_base64(path) + return {"type": "input_audio", "input_audio": {"data": f"data:{mime};base64,{b64}"}} + + print(f"Unknown media type: {media_type}", file=sys.stderr) + sys.exit(1) + + +def _resolve_path(file_input: str) -> Path: + path = Path(file_input).resolve() + if not path.is_file(): + print(f"File not found: {path}", file=sys.stderr) + sys.exit(1) + return path + + +def _guess_mime(path: Path, default: str) -> str: + mime, _ = mimetypes.guess_type(str(path)) + return mime or default + + +def _read_base64(path: Path) -> str: + with open(path, "rb") as f: + return base64.b64encode(f.read()).decode() + + +def main() -> None: + args = parse_args() + client = build_client() + + media_block = load_media(args.type, args.input) + + completion = client.chat.completions.create( + model="mimo-v2.5", + max_tokens=args.max_tokens, + messages=[ + { + "role": "user", + "content": [ + media_block, + {"type": "text", "text": args.prompt}, + ], + } + ], + ) + + message = completion.choices[0].message + if message.content: + print(message.content) + else: + print("No content in response", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main()