Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:40:57 +08:00
commit abc7aa590b
7 changed files with 501 additions and 0 deletions

105
skills/plan-video/SKILL.md Normal file
View File

@@ -0,0 +1,105 @@
---
name: plan-video
description: 视频处理任务规划工具。从用户输入中提取视频 URLs生成唯一 VideoId创建结构化的 todolist.md 追踪待生成文件。支持小红书、抖音、TikTok、B站、YouTube、快手等 11+ 平台。
---
# Plan Video - 视频任务规划
## 概述
为批量视频处理创建结构化任务清单。提取 URLs → 生成 VideoIds → 创建 todolist.md 追踪文件。
## 使用时机
- 用户提供视频 URLs 需要批量处理
- 在下载/处理前需要规划任务结构
## 工作流程
### 1. 提取视频 URLs
从用户输入(消息/文件)中提取所有视频链接。
```python
from scripts.extract_video_id import extract_urls
urls = extract_urls(user_input_text)
```
**支持平台**小红书、抖音、TikTok、B站、YouTube、快手等
### 2. 提取 VideoId
为每个 URL 提取唯一标识符。
```python
from scripts.extract_video_id import extract_video_id
video_id = extract_video_id(url)
# 'http://xhslink.com/o/6VbNVltFQRX' → '6VbNVltFQRX'
```
### 3. 生成 todolist.md
在输出目录创建任务清单,列出所有待生成文件。
**格式**
```markdown
# Video Processing Tasks
## {VideoId} ({原始短链接URL})
- [ ] orgin/{VideoId}/{VideoId}.json # 元数据,通过 skill `parse-video` 获得
- [ ] orgin/{VideoId}/{VideoId}.mp4 # 视频,通过 skill `parse-video` 获得
- [ ] orgin/{VideoId}/{VideoId}_cover.jpg # 封面,通过 skill `rip-video` 获得
- [ ] orgin/{VideoId}/{VideoId}.mp3 # 音频,通过 skill `rip-video` 获得
- [ ] orgin/{VideoId}/{VideoId}.srt # 字幕,通过 skill `rip-video` 获得
```
**示例**
```markdown
## 6VbNVltFQRX (http://xhslink.com/o/6VbNVltFQRX)
- [ ] orgin/6VbNVltFQRX/6VbNVltFQRX.json
- [ ] orgin/6VbNVltFQRX/6VbNVltFQRX.mp4
- [ ] orgin/6VbNVltFQRX/6VbNVltFQRX_cover.jpg
- [ ] orgin/6VbNVltFQRX/6VbNVltFQRX.mp3
- [ ] orgin/6VbNVltFQRX/6VbNVltFQRX.srt
```
### 4. 输出报告
```markdown
============================================================
视频处理计划已创建!
============================================================
找到视频: {total} 个
输出目录: {outputDir}/orgin/
任务列表: {outputDir}/todolist.md
可以继续使用 parse-video 和 rip-video 进行下载和处理。
============================================================
```
## 错误处理
- **未找到 URLs**:提示用户未检测到有效视频链接
- **VideoId 提取失败**:跳过该 URL在报告中说明
- **输出目录不存在**:自动创建目录结构
- **todolist.md 已存在**:询问用户是否覆盖或追加
## 资源文件
### scripts/extract_video_id.py
VideoId 提取工具。
**函数**
- `extract_video_id(url: str) -> str`:提取单个 URL 的 VideoId
- `extract_urls(text: str) -> list[str]`:从文本提取所有视频 URLs
## 集成说明
此 skill 为后续处理准备基础:
1. **parse-video**:使用 todolist.md 中的 VideoIds 和 URLs 解析元数据、下载视频
2. **rip-video**:使用 todolist.md 中的 MP4 路径提取音频和字幕
todolist.md 是整个视频处理流程的中心追踪文档。

View File

@@ -0,0 +1,140 @@
#!/usr/bin/env python3
"""
Extract VideoId from various video platform URLs.
Supports: 小红书, 抖音, TikTok, B站, YouTube, 快手等
"""
import re
from urllib.parse import urlparse, parse_qs
def extract_video_id(url: str) -> str:
"""
Extract VideoId from video platform URL.
Args:
url: Video URL from supported platforms
Returns:
VideoId string
Raises:
ValueError: If URL format is not recognized
"""
url = url.strip()
# 小红书短链接: http://xhslink.com/o/6VbNVltFQRX
if 'xhslink.com' in url or 'xiaohongshu.com' in url:
match = re.search(r'/o/([A-Za-z0-9]+)', url)
if match:
return match.group(1)
# 抖音: https://v.douyin.com/xxx/ or https://www.douyin.com/video/xxx
if 'douyin.com' in url:
# 短链接
match = re.search(r'v\.douyin\.com/([A-Za-z0-9]+)', url)
if match:
return match.group(1)
# 长链接
match = re.search(r'/video/(\d+)', url)
if match:
return match.group(1)
# TikTok: https://www.tiktok.com/@user/video/1234567890
if 'tiktok.com' in url:
match = re.search(r'/video/(\d+)', url)
if match:
return match.group(1)
# 短链接 https://vm.tiktok.com/xxx/
match = re.search(r'vm\.tiktok\.com/([A-Za-z0-9]+)', url)
if match:
return match.group(1)
# B站: https://www.bilibili.com/video/BVxxx or https://b23.tv/xxx
if 'bilibili.com' in url or 'b23.tv' in url:
# BV号
match = re.search(r'/(BV[A-Za-z0-9]+)', url)
if match:
return match.group(1)
# av号
match = re.search(r'/av(\d+)', url)
if match:
return f"av{match.group(1)}"
# 短链接
match = re.search(r'b23\.tv/([A-Za-z0-9]+)', url)
if match:
return match.group(1)
# YouTube: https://www.youtube.com/watch?v=xxx or https://youtu.be/xxx
if 'youtube.com' in url or 'youtu.be' in url:
# youtu.be 短链接
match = re.search(r'youtu\.be/([A-Za-z0-9_-]+)', url)
if match:
return match.group(1)
# 标准链接
parsed = urlparse(url)
if parsed.query:
params = parse_qs(parsed.query)
if 'v' in params:
return params['v'][0]
# 快手: https://www.kuaishou.com/short-video/xxx
if 'kuaishou.com' in url:
match = re.search(r'/short-video/(\d+)', url)
if match:
return match.group(1)
match = re.search(r'\.com/([A-Za-z0-9]+)', url)
if match:
return match.group(1)
# 如果都不匹配,尝试提取 URL 最后的路径部分
parsed = urlparse(url)
path_parts = [p for p in parsed.path.split('/') if p]
if path_parts:
# 返回最后一个非空路径部分
return path_parts[-1]
raise ValueError(f"无法从 URL 提取 VideoId: {url}")
def extract_urls(text: str) -> list[str]:
"""
Extract video URLs from text.
Args:
text: Text containing video URLs
Returns:
List of unique video URLs
"""
pattern = r'https?://[^\s]+(?:douyin|tiktok|youtube|youtu\.be|xiaohongshu|xhslink|bilibili|b23\.tv|kuaishou)[^\s]*'
urls = re.findall(pattern, text)
return list(dict.fromkeys(urls)) # 保持顺序去重
if __name__ == '__main__':
# 测试用例
test_urls = [
'http://xhslink.com/o/6VbNVltFQRX',
'http://xhslink.com/o/16X9vM9CTBo',
'https://v.douyin.com/eFyQRjc/',
'https://www.douyin.com/video/7123456789',
'https://www.tiktok.com/@user/video/7123456789',
'https://vm.tiktok.com/ZMeAbCdEf/',
'https://www.bilibili.com/video/BV1xx411c7mD',
'https://b23.tv/av12345678',
'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
'https://youtu.be/dQw4w9WgXcQ',
'https://www.kuaishou.com/short-video/1234567890',
]
print("VideoId 提取测试:\n")
for url in test_urls:
try:
video_id = extract_video_id(url)
print(f"{url}")
print(f"{video_id}\n")
except ValueError as e:
print(f"{url}")
print(f"{e}\n")