Skill: ide-rea/ai-notes-for-video (version 1.0.2)

--- name: baidu-netdisk-ai-video-notes description: 百度网盘AI视频笔记是一款基于大模型的视频内容理解工具，可以对视频(本地视频，网络视频)语音识别生成文稿笔记、大纲笔记、图文笔记三种笔记格式。当用户需要从视频生成笔记、总结视频内容、或将视频转化为文字记录时使用，经典场景（教育，会议视频总结笔记，短视频文案建议，关键信息提取） metadata: { "openclaw": { "emoji": "📺", "requires": { "bins": ["python3"], "env":["BAIDU_API_KEY"]},"primaryEnv":"BAIDU_API_KEY" } } --- # 百度网盘AI视频笔记 ## 功能特色 - 支持mp4、mov、flv、mpeg、avi、mkv、wmv等主流视频格式 - 异步生成机制、进度反馈、多格式输出 ## 应用场景 - 视频教学内容理解与笔记 - 短视频平台内容分析与文案建议 - 会议视频记录与关键信息提取 - 高峰时段直播视频回顾与笔记 ## 执行流程 1. **任务创建**: 提交视频URL(本地视频或网络视频) → 生成任务ID 2. **轮询任务状态**: 每3-5秒查询一次任务状态，直到任务完成 3. **获取解析结果**: 当 status = 10002 时，输出解析结果 ## 状态码说明 | Code | Status | Action | |-------|--------|--------| | 10000 | 进行中 | 继续轮询 | | 10002 | 完成 | 输出解析结果 | | Other | 失败 | 提示异常信息 | ## 笔记类型 | Type | Description | |------|-------------| | 1 | 文稿笔记 | | 2 | 大纲笔记 | | 3 | 图文笔记 | ## 使用示例 ### 创建任务 **Endpoint**: `POST /v2/tools/ai_note/task_create` **Parameters**: - `video_url` (required): Public video URL **Example**: ```bash python3 scripts/ai_notes_task_create.py --video_url='https://example.com/video.mp4' ``` **Response**: ```json { "task_id": "uuid-string" } ``` ### 查询任务状态 **Endpoint**: `GET /v2/tools/ai_note/query` **Parameters**: - `task_id` (required): Task ID from create endpoint **Example**: ```bash python3 scripts/ai_notes_task_query.py --task_id="task-id-here" ``` **Response** (Completed): ```json [ { "状态": "完成", "文稿笔记": ["note1", "note2"] }, { "状态": "完成", "大纲笔记": ["note1", "note2"] }, { "状态": "完成", "图文笔记": ["note1", "note2"] } ] ``` ## 循环拉取任务状态 ### Option 1: Manual Polling 1. Create task → store `task_id` 2. Query every 3-5 seconds: ```bash python3 scripts/ai_notes_task_query.py --task_id="task-id-here" ``` 3. Show progress updates: - Status 10000: Processing... - Status 10002: Completed 4. Stop after 30-60 seconds (video length dependent) ### Option 2: Auto Polling (Recommended) Use the polling script for automatic status updates: ```bash python3 scripts/ai_notes_poll.py --task_id="task-id-here" [--max_attempts=30] [--interval=5] ``` **Examples**: ```bash # Default: 20 attempts, 3-second intervals python3 scripts/ai_notes_poll.py --task_id="task-id-here" # Custom: 30 attempts, 5-second intervals python3 scripts/ai_notes_poll.py --task_id="task-id-here" --max_attempts=30 --interval=5 ``` **Output**: - Shows real-time progress: `[1/20] Processing... 25%` - Auto-stops when complete - Returns formatted notes with type labels ## 异常处理 - 文件下载失败: "File not found or invalid URL" - Processing error: "Failed to parse video" - Timeout: "Video too long, try again later"

path: _meta.json
content: ownerId: kn7akgt520t01vgs2tzx7yk6m180kt26
slug: ai-notes-for-video
version: 1.0.1
publishedAt: 1775033098724

path: scripts/ai_notes_task_create.py
content: #!/usr/bin/env python3
"""
AI Video Notes - Create Task
Submit a video URL for AI note generation.
"""

import os
import sys
import json
import requests
import argparse
from typing import Dict, Any, Tuple
from urllib.parse import urlparse

BASE_URL = "https://qianfan.baidubce.com/v2"


# ── 工具函数 ──────────────────────────────────────────────────────────────────
def _normalize_video_url(api_key, value: str) -> str:
    """
    转换本地路径为网络
    """
    if not value:
        return value
    if not value.startswith(("http://", "https://")):
        if not os.path.exists(value):
            raise FileNotFoundError(f"视频文件不存在: {value}")
        value = upload_video_to_bos(api_key, value)
    return value


def upload_video_to_bos(api_key: str, file_path: str) -> str:
    """
    上传视频文件到 BOS，并返回下载路径

    Args:
        api_key: BCE v3 认证 token
        file_path: 本地视频文件路径

    Returns:
        BOS 下载路径
    """
    url, headers = resolve_sandbox_url(api_key, "https://appbuilder.baidu.com/v2/tools/bos/upload")
    headers = {
        "Authorization": f"Bearer {api_key}",
        "X-Appbuilder-From": "openclaw",
    }
    with open(file_path, "rb") as f:
        files = {"file": (os.path.basename(file_path), f)}
        response = requests.post(url, headers=headers, files=files)

    response.raise_for_status()
    result = response.json()

    if result.get("code") is not None and result.get("code") != "0":
        raise RuntimeError(result.get("detail", result.get("message", "Upload failed")))

    # 返回下载路径
    return result.get("data", {}).get("downloadUrl")


def resolve_sandbox_url(api_key: str, original_url: str) -> Tuple[str, Dict[str, str]]:
    """若当前在沙盒环境中，将目标 URL 替换为代理 URL，并返回需要附加的 headers。"""
    session_id = os.environ.get("DUMATE_SESSION_ID")
    scheduler_url = os.environ.get("DUMATE_SCHEDULER_URL")

    headers = {
        "Content-Type": "application/json",
    }
    if not session_id or not scheduler_url:
        if not api_key:
            raise ValueError("未设置 API Key，请通过环境变量 BAIDU_API_KEY 设置或使用")
        headers.update({
            "Authorization": f"Bearer {api_key}",
            "X-Appbuilder-From": "openclaw",
        })
        return original_url, headers

    parsed = urlparse(original_url)
    proxy_url = f"{scheduler_url}/api/qianfanproxy{parsed.path}"
    if parsed.query:
        proxy_url += f"?{parsed.query}"

    headers.update({
        "Host": parsed.netloc,
        "X-Dumate-Session-Id": session_id,
        "X-Appbuilder-From": "desktop",
    })
    return proxy_url, headers


def create_note_task(api_key: str, video_url: str) -> Dict[str, Any]:
    """Create an AI note generation task.

    Args:
        api_key: Baidu API key
        video_url: Public video URL

    Returns:
        Task data with task_id

    Raises:
        RuntimeError: If API returns error
    """
    url, headers = resolve_sandbox_url(api_key, f"{BASE_URL}/tools/ai_note/task_create")
    data = {"url": video_url}

    try:
        response = requests.post(url, headers=headers, json=data, timeout=30)
        response.raise_for_status()
        result = response.json()

        if "code" in result:
            raise RuntimeError(result.get("detail", "API error"))
        if "errno" in result and result["errno"] != 0:
            raise RuntimeError(result.get("errmsg", "Unknown error"))

        return result["data"]

    except requests.exceptions.Timeout:
        raise RuntimeError("Request timeout. Video URL may be inaccessible.")
    except requests.exceptions.RequestException as e:
        raise RuntimeError(f"Network error: {str(e)}")


def main():
    parser = argparse.ArgumentParser(description="创建AI视频笔记任务")
    parser.add_argument("--video_url", required=True, help="本地视频路径或视频链接")
    parser.add_argument("--api_key", default=os.environ.get("BAIDU_API_KEY"), help="API Key")

    args = parser.parse_args()
    video_url = args.video_url
    [REDACTED_SECRET]
    try:
        video_url = _normalize_video_url(api_key, video_url)
        task_data = create_note_task(api_key, video_url)
        print(json.dumps({
            "status": "success",
            "message": "Task created successfully",
            "task_id": task_data.get("task_id"),
            "next_step": f"Query task status: python ai_notes_task_query.py {task_data.get('task_id')}"
        }, indent=2))

    except RuntimeError as e:
        print(json.dumps({
            "status": "error",
            "error": str(e)
        }, indent=2))
        sys.exit(1)
    except Exception as e:
        print(json.dumps({
            "status": "error",
            "error": f"Unexpected error: {str(e)}"
        }, indent=2))
        sys.exit(1)


if __name__ == "__main__":
    main()


path: scripts/ai_notes_task_query.py
content: import os
import sys
import requests
import json
import argparse
from typing import Dict, Any, Tuple
from urllib.parse import urlparse

BASE_URL = "https://qianfan.baidubce.com/v2"


def resolve_sandbox_url(api_key: str, original_url: str) -> Tuple[str, Dict[str, str]]:
    """若当前在沙盒环境中，将目标 URL 替换为代理 URL，并返回需要附加的 headers。"""
    session_id = os.environ.get("DUMATE_SESSION_ID")
    scheduler_url = os.environ.get("DUMATE_SCHEDULER_URL")

    headers = {
        "Content-Type": "application/json",
    }
    if not session_id or not scheduler_url:
        if not api_key:
            raise ValueError("未设置 API Key，请通过环境变量 BAIDU_API_KEY 设置或使用")
        headers.update({
            "Authorization": f"Bearer {api_key}",
            "X-Appbuilder-From": "openclaw",
        })
        return original_url, headers

    parsed = urlparse(original_url)
    proxy_url = f"{scheduler_url}/api/qianfanproxy{parsed.path}"
    if parsed.query:
        proxy_url += f"?{parsed.query}"

    headers.update({
        "Host": parsed.netloc,
        "X-Dumate-Session-Id": session_id,
        "X-Appbuilder-From": "desktop",
    })
    return proxy_url, headers


def ai_notes_task_query(api_key: str, task_id: str):
    """
    Query the status of an AI note generation task.
    """
    url, headers = resolve_sandbox_url(api_key, f"{BASE_URL}/tools/ai_note/query")
    params = {
        "task_id": task_id,
    }
    response = requests.get(url, headers=headers, params=params)
    response.raise_for_status()
    result = response.json()
    if "code" in result:
        raise RuntimeError(result["detail"])
    if "errno" in result and result["errno"] == 10000:
        print("task status code: %s, message: %s" % (result["errno"], result["show_msg"]))
        return {}
    if "errno" in result and result["errno"] != 0:
        raise RuntimeError(result["show_msg"])
    results = []
    detail_list = result.get("data", {}).get("list", [])
    for item in detail_list:
        if item.get("detail", {}).get("status") == 10002 and item.get("tpl_no") == "1":
            results.append({
                "状态": "完成",
                "文稿笔记": item.get("detail", {}).get("contents", [])
            })
        if item.get("detail", {}).get("status") == 10002 and item.get("tpl_no") == "2":
            results.append({
                "状态": "完成",
                "大纲笔记": item.get("detail", {}).get("contents", [])
            })
        if item.get("detail", {}).get("status") == 10002 and item.get("tpl_no") == "3":
            results.append({
                "状态": "完成",
                "图文笔记": item.get("detail", {}).get("contents", [])
            })
    return results


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="查询AI视频笔记生成任务状态")
    parser.add_argument("--task_id", required=True, help="任务ID")
    parser.add_argument("--api_key", default=os.environ.get("BAIDU_API_KEY"), help="API Key")
    args = parser.parse_args()
    task_id = args.task_id
    [REDACTED_SECRET]
    try:
        results = ai_notes_task_query(api_key, task_id)
        print(json.dumps(results, ensure_ascii=False, indent=2))
    except Exception as e:
        print(f"Error: {str(e)}")
        sys.exit(1)


path: scripts/ai_notes_poll.py
content: #!/usr/bin/env python3
"""
AI Video Notes - Poll Task
Automatically poll task until completion with progress updates.
"""

import os
import sys
import json
import time
import requests
import argparse
from typing import Dict, Any, Tuple
from urllib.parse import urlparse

STATUS_CODES = {
    10000: "processing",
    10002: "completed",
}

BASE_URL = "https://qianfan.baidubce.com/v2"


def resolve_sandbox_url(api_key: str, original_url: str) -> Tuple[str, Dict[str, str]]:
    """若当前在沙盒环境中，将目标 URL 替换为代理 URL，并返回需要附加的 headers。"""
    session_id = os.environ.get("DUMATE_SESSION_ID")
    scheduler_url = os.environ.get("DUMATE_SCHEDULER_URL")

    headers = {
        "Content-Type": "application/json",
    }
    if not session_id or not scheduler_url:
        if not api_key:
            raise ValueError("未设置 API Key，请通过环境变量 BAIDU_API_KEY 设置或使用")
        headers.update({
            "Authorization": f"Bearer {api_key}",
            "X-Appbuilder-From": "openclaw",
        })
        return original_url, headers

    parsed = urlparse(original_url)
    proxy_url = f"{scheduler_url}/api/qianfanproxy{parsed.path}"
    if parsed.query:
        proxy_url += f"?{parsed.query}"

    headers.update({
        "Host": parsed.netloc,
        "X-Dumate-Session-Id": session_id,
        "X-Appbuilder-From": "desktop",
    })
    return proxy_url, headers


def query_task(api_key: str, task_id: str) -> Dict[str, Any]:
    """Query task status."""
    url = f"{BASE_URL}/tools/ai_note/query"
    url, headers = resolve_sandbox_url(api_key, url)
    params = {"task_id": task_id}

    response = requests.get(url, headers=headers, params=params, timeout=30)
    response.raise_for_status()
    result = response.json()
    return result


def poll_task(api_key: str, task_id: str, max_attempts: int = 20, interval: int = 3):
    """Poll task until completion or timeout.

    Args:
        api_key: Baidu API key
        task_id: Task ID
        max_attempts: Maximum poll attempts (default 20)
        interval: Seconds between polls (default 3)

    Returns:
        Final task data
    """
    data = None
    for attempt in range(max_attempts):
        try:
            data = query_task(api_key, task_id)
            if "code" in data:
                raise RuntimeError(result["detail"])
            if "errno" in data and data["errno"] == 10000:
                errno = data["errno"]
                error_msg = data["show_msg"]
                print(f"[{attempt + 1}/{max_attempts}] Processing...(task status code: {errno}, message: {error_msg})")
                time.sleep(interval)
                continue
            if "errno" in data and data["errno"] != 0:
                raise RuntimeError(data.get("show_msg", "Unknown error"))
            data = data.get("data", {})
            ai_notes_list = data.get("list", [])
            status_code = 0
            for note in ai_notes_list:
                status_code = note.get("detail", {}).get("status", 0)
                if status_code != 10002:
                    break

            if status_code == 10002:
                for note in data.get("list", []):
                    tpl_no = note.get("tpl_no")
                    if tpl_no == "1":
                        print("\n" + "=" * 50)
                        print("✓ 文稿笔记笔记生成成功，如下")
                        print("=" * 50)
                        for content in note.get("detail", {}).get("contents", []):
                            print(content)

                    if tpl_no == "2":
                        print("\n" + "=" * 50)
                        print("✓ 大纲笔记笔记生成成功，如下")
                        print("=" * 50)
                        for content in note.get("detail", {}).get("contents", []):
                            print(content)
                    if tpl_no == "3":
                        print("\n" + "=" * 50)
                        print("✓ 图文笔记笔记生成成功，如下")
                        print("=" * 50)
                        for content in note.get("detail", {}).get("contents", []):
                            print(content)
                return data

            elif status_code == 10000:
                print(f"[{attempt + 1}/{max_attempts}] Processing...")
                time.sleep(interval)
                continue
            else:
                print(f"\n✗ Task failed, with status code: {status_code}")
                return data

        except RuntimeError as e:
            print(f"\n✗ Error: {str(e)}")
            return data
        except Exception as e:
            if attempt == max_attempts - 1:
                print(f"\n✗ Unexpected error: {str(e)}")
                return data
            time.sleep(interval)

    print(f"\n✗ Timeout after {max_attempts * interval} seconds")
    print("Task may still be running. Try querying manually:")
    print(f"  python scripts/ai_notes_task_query.py {task_id}")
    return data


def main():
    parser = argparse.ArgumentParser(description="轮询AI视频笔记生成任务状态直到完成")
    parser.add_argument("--task_id", required=True, help="任务ID")
    parser.add_argument("--api_key", default=os.environ.get("BAIDU_API_KEY"), help="API Key")
    parser.add_argument("--max_attempts", type=int, help="最大轮询次数")
    parser.add_argument("--interval", type=int, help="每次轮询间隔（秒）")
    args = parser.parse_args()
    task_id = args.task_id
    [REDACTED_SECRET]
    max_attempts = args.max_attempts
    interval = args.interval
    max_attempts = max_attempts if max_attempts else 20
    interval = interval if interval else 3

    print(f"Polling task: {task_id}")
    print(f"Max attempts: {max_attempts}, Interval: {interval}s")
    print("-" * 50)

    poll_task(api_key, task_id, max_attempts, interval)


if __name__ == "__main__":
    main()


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