yuanflow-cli 0.1.10 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/package.json +1 -1
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/SKILL.md +91 -0
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/_preview-html-report-styles/index.html +214 -0
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/references/report-shapes.md +139 -0
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/references/theme-system.md +74 -0
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/templates/warm-beige/01-insight-brief.html +98 -0
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/templates/warm-beige/02-data-dashboard.html +86 -0
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/templates/warm-beige/03-deep-report.html +87 -0
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/templates/warm-beige/04-comparison.html +63 -0
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/templates/warm-beige/05-card-collection.html +54 -0
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/templates/warm-beige/06-process-steps.html +57 -0
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/templates/warm-beige/07-timeline.html +55 -0
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/templates/warm-beige/08-action-board.html +58 -0
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/templates/warm-beige/09-knowledge-map.html +72 -0
- package/skills/yuanflow-skill/HTML/346/212/245/345/221/212/347/224/237/346/210/220/templates/warm-beige/report-warm-beige.css +258 -0
- package/skills/yuanflow-skill/README.md +102 -0
- package/skills/yuanflow-skill/SKILL.md +25 -1
- package/skills/yuanflow-skill//346/234/254/345/234/260/351/237/263/350/247/206/351/242/221/350/275/254/346/226/207/345/255/227/SKILL.md +148 -0
- package/skills/yuanflow-skill//346/234/254/345/234/260/351/237/263/350/247/206/351/242/221/350/275/254/346/226/207/345/255/227/scripts/common/__init__.py +1 -0
- package/skills/yuanflow-skill//346/234/254/345/234/260/351/237/263/350/247/206/351/242/221/350/275/254/346/226/207/345/255/227/scripts/common/media.py +46 -0
- package/skills/yuanflow-skill//346/234/254/345/234/260/351/237/263/350/247/206/351/242/221/350/275/254/346/226/207/345/255/227/scripts/common/sensevoice.py +82 -0
- package/skills/yuanflow-skill//346/234/254/345/234/260/351/237/263/350/247/206/351/242/221/350/275/254/346/226/207/345/255/227/scripts/common/utils.py +62 -0
- package/skills/yuanflow-skill//346/234/254/345/234/260/351/237/263/350/247/206/351/242/221/350/275/254/346/226/207/345/255/227/scripts/requirements-transcribe.txt +2 -0
- package/skills/yuanflow-skill//346/234/254/345/234/260/351/237/263/350/247/206/351/242/221/350/275/254/346/226/207/345/255/227/scripts/transcribe_media.py +126 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #e9e5dc;
|
|
3
|
+
--page: #fbfaf6;
|
|
4
|
+
--ink: #1b1814;
|
|
5
|
+
--muted: #6b6459;
|
|
6
|
+
--primary: #1d1b18;
|
|
7
|
+
--primary-2: #9b6a3a;
|
|
8
|
+
--line: #d8cfc1;
|
|
9
|
+
--soft: #f3efe6;
|
|
10
|
+
--soft-2: #eee6d7;
|
|
11
|
+
--success: #12805c;
|
|
12
|
+
--warning: #b56b00;
|
|
13
|
+
--danger: #d92d20;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
* { box-sizing: border-box; }
|
|
17
|
+
|
|
18
|
+
body {
|
|
19
|
+
margin: 0;
|
|
20
|
+
background: var(--bg);
|
|
21
|
+
color: var(--ink);
|
|
22
|
+
font-family: "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", Arial, sans-serif;
|
|
23
|
+
line-height: 1.68;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.report-page {
|
|
27
|
+
width: min(1120px, calc(100% - 40px));
|
|
28
|
+
margin: 28px auto;
|
|
29
|
+
padding: 40px 42px 26px;
|
|
30
|
+
background: var(--page);
|
|
31
|
+
border: 1px solid var(--line);
|
|
32
|
+
box-shadow: 0 18px 48px rgba(47, 40, 31, 0.08);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.report-topline {
|
|
36
|
+
display: flex;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
gap: 24px;
|
|
39
|
+
color: var(--muted);
|
|
40
|
+
font-size: 13px;
|
|
41
|
+
border-bottom: 1px solid var(--line);
|
|
42
|
+
padding-bottom: 14px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.report-header {
|
|
46
|
+
padding: 24px 0 22px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.eyebrow {
|
|
50
|
+
color: var(--primary-2);
|
|
51
|
+
font-size: 14px;
|
|
52
|
+
font-weight: 700;
|
|
53
|
+
margin: 0 0 8px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
h1 {
|
|
57
|
+
margin: 0;
|
|
58
|
+
color: var(--primary);
|
|
59
|
+
font-size: 32px;
|
|
60
|
+
line-height: 1.22;
|
|
61
|
+
letter-spacing: 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.subtitle {
|
|
65
|
+
margin: 10px 0 0;
|
|
66
|
+
color: var(--muted);
|
|
67
|
+
font-size: 15px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.tags {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-wrap: wrap;
|
|
73
|
+
gap: 8px;
|
|
74
|
+
margin-top: 16px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.tag {
|
|
78
|
+
padding: 6px 10px;
|
|
79
|
+
border: 1px solid var(--line);
|
|
80
|
+
background: var(--soft);
|
|
81
|
+
color: var(--primary);
|
|
82
|
+
font-size: 12px;
|
|
83
|
+
font-weight: 700;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.summary {
|
|
87
|
+
display: grid;
|
|
88
|
+
gap: 12px;
|
|
89
|
+
padding: 18px;
|
|
90
|
+
background: linear-gradient(180deg, #f6f1e8, #fbfaf6);
|
|
91
|
+
border: 1px solid var(--line);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.summary-strong {
|
|
95
|
+
margin: 0;
|
|
96
|
+
color: var(--primary);
|
|
97
|
+
font-size: 20px;
|
|
98
|
+
font-weight: 800;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.section {
|
|
102
|
+
margin-top: 22px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.section-title {
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
gap: 10px;
|
|
109
|
+
margin: 0 0 12px;
|
|
110
|
+
color: var(--primary);
|
|
111
|
+
font-size: 18px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.section-title::before {
|
|
115
|
+
content: "";
|
|
116
|
+
width: 4px;
|
|
117
|
+
height: 18px;
|
|
118
|
+
background: var(--primary);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
|
|
122
|
+
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
|
|
123
|
+
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
|
|
124
|
+
|
|
125
|
+
.card {
|
|
126
|
+
border: 1px solid var(--line);
|
|
127
|
+
background: #fffdf8;
|
|
128
|
+
padding: 15px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.card.soft { background: var(--soft); }
|
|
132
|
+
|
|
133
|
+
.metric-value {
|
|
134
|
+
color: var(--primary);
|
|
135
|
+
font-size: 28px;
|
|
136
|
+
line-height: 1;
|
|
137
|
+
font-weight: 900;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.metric-label, .muted {
|
|
141
|
+
color: var(--muted);
|
|
142
|
+
font-size: 13px;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.number {
|
|
146
|
+
display: inline-grid;
|
|
147
|
+
place-items: center;
|
|
148
|
+
width: 28px;
|
|
149
|
+
height: 28px;
|
|
150
|
+
background: var(--primary);
|
|
151
|
+
color: #fff;
|
|
152
|
+
font-size: 13px;
|
|
153
|
+
font-weight: 800;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.list {
|
|
157
|
+
margin: 0;
|
|
158
|
+
padding-left: 18px;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.list li { margin: 6px 0; }
|
|
162
|
+
|
|
163
|
+
.table {
|
|
164
|
+
width: 100%;
|
|
165
|
+
border-collapse: collapse;
|
|
166
|
+
overflow: hidden;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.table th, .table td {
|
|
170
|
+
border: 1px solid var(--line);
|
|
171
|
+
padding: 12px;
|
|
172
|
+
text-align: left;
|
|
173
|
+
vertical-align: top;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.table th {
|
|
177
|
+
background: var(--soft);
|
|
178
|
+
color: var(--primary);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.bar-chart {
|
|
182
|
+
display: grid;
|
|
183
|
+
grid-template-columns: repeat(7, 1fr);
|
|
184
|
+
align-items: end;
|
|
185
|
+
gap: 12px;
|
|
186
|
+
height: 170px;
|
|
187
|
+
padding: 16px;
|
|
188
|
+
border: 1px solid var(--line);
|
|
189
|
+
background: repeating-linear-gradient(0deg, #fffdf8, #fffdf8 33px, #e8dfd1 34px);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.bar {
|
|
193
|
+
background: linear-gradient(180deg, #b8894f, var(--primary));
|
|
194
|
+
min-height: 24px;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.timeline {
|
|
198
|
+
display: grid;
|
|
199
|
+
gap: 14px;
|
|
200
|
+
border-left: 3px solid var(--primary);
|
|
201
|
+
padding-left: 18px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.timeline-item {
|
|
205
|
+
position: relative;
|
|
206
|
+
border: 1px solid var(--line);
|
|
207
|
+
padding: 14px;
|
|
208
|
+
background: #fffdf8;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.timeline-item::before {
|
|
212
|
+
content: "";
|
|
213
|
+
position: absolute;
|
|
214
|
+
left: -27px;
|
|
215
|
+
top: 20px;
|
|
216
|
+
width: 12px;
|
|
217
|
+
height: 12px;
|
|
218
|
+
background: var(--primary);
|
|
219
|
+
border: 3px solid #fff;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.flow-row {
|
|
223
|
+
display: grid;
|
|
224
|
+
grid-template-columns: repeat(5, 1fr);
|
|
225
|
+
gap: 8px;
|
|
226
|
+
align-items: stretch;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.flow-step {
|
|
230
|
+
position: relative;
|
|
231
|
+
padding: 12px;
|
|
232
|
+
background: var(--soft);
|
|
233
|
+
border: 1px solid var(--line);
|
|
234
|
+
color: var(--primary);
|
|
235
|
+
font-weight: 800;
|
|
236
|
+
text-align: center;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.priority-high { border-top: 6px solid var(--danger); }
|
|
240
|
+
.priority-medium { border-top: 6px solid #b8894f; }
|
|
241
|
+
.priority-low { border-top: 6px solid #12b76a; }
|
|
242
|
+
|
|
243
|
+
.footer {
|
|
244
|
+
display: flex;
|
|
245
|
+
justify-content: space-between;
|
|
246
|
+
gap: 20px;
|
|
247
|
+
margin-top: 30px;
|
|
248
|
+
padding-top: 14px;
|
|
249
|
+
border-top: 1px solid var(--line);
|
|
250
|
+
color: var(--muted);
|
|
251
|
+
font-size: 12px;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@media (max-width: 820px) {
|
|
255
|
+
.report-page { width: calc(100% - 20px); padding: 28px 20px; }
|
|
256
|
+
.grid-2, .grid-3, .grid-4, .flow-row { grid-template-columns: 1fr; }
|
|
257
|
+
.report-topline, .footer { flex-direction: column; }
|
|
258
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# yuanflow-skill
|
|
2
|
+
|
|
3
|
+
YuanFlow Skill 是 `yuanflow-cli` 的 Agent Skill 仓库,用于把社媒平台 API 调用能力安装到支持 Skill 的本地 AI Agent 中。
|
|
4
|
+
|
|
5
|
+
## 包含内容
|
|
6
|
+
|
|
7
|
+
- `SKILL.md`:总入口,负责分流和索引。
|
|
8
|
+
- `yuanflow-cli/`:API CLI 子 Skill,说明 `yuanflow-cli` 的安装、配置、命令发现、schema 查询、dry-run 和 agent-json 调用方式。
|
|
9
|
+
- `自媒体知识库/`:自媒体知识库渐进查询 Skill,走 `yuanflow-cli knowledge`。
|
|
10
|
+
- `OSS文件中转工具/`:OSS 临时上传、签名链接、对象复制 Skill,走 `yuanflow-cli oss`。
|
|
11
|
+
- `生图技能/`:图片生成与编辑 Skill,优先走 YuanFlow 内置 `yuanflow_image_request`。
|
|
12
|
+
- `HTML报告生成/`:单页 HTML 报告生成 Skill,内置 9 种米色留白报告模板。
|
|
13
|
+
- `本地音视频转文字/`:本地 SenseVoice 音视频转文字 Skill,首次明确使用时按需下载模型。
|
|
14
|
+
|
|
15
|
+
## 适用场景
|
|
16
|
+
|
|
17
|
+
- 查询抖音、小红书、Bilibili、微信视频号、微信公众号等平台数据。
|
|
18
|
+
- 查看可用 API 命令、快捷命令和 schema。
|
|
19
|
+
- 让本地 Agent 稳定调用 `yuanflow-cli` 并解析 JSON 输出。
|
|
20
|
+
- 查询自媒体知识库公开方向、方法包和规则摘要。
|
|
21
|
+
- 上传文件到临时 OSS、生成签名链接或复制 OSS 对象。
|
|
22
|
+
- 生成图片、编辑图片,并缓存返回 URL 或 base64 图片。生成图片必填 `prompt`,可选 `size / quality / style / n / response_format`;编辑图片必须通过 multipart 上传本地图片。
|
|
23
|
+
- 把自媒体分析、数据复盘、文案方案、账号监控、知识梳理和执行计划生成可直接打开的单页 HTML 报告。
|
|
24
|
+
- 在用户明确要求本地转写时,把本地音频或视频转成文字;视频会先抽取音频,模型和缓存都保存在 Skill 自己目录下。
|
|
25
|
+
|
|
26
|
+
## 双环境使用方式
|
|
27
|
+
|
|
28
|
+
这套 Skill 同时支持两种环境:
|
|
29
|
+
|
|
30
|
+
- YuanFlow 主程序内:优先使用内置工具 `yuanflow_cli_call`、`yuanflow_gateway_request` 或 `yuanflow_image_request`,token 由主程序认证系统注入,不要求用户安装 npm 或配置 token。
|
|
31
|
+
- 外部 Agent 内:优先使用本地 `yuanflow-cli`;如果本地没有,再通过 npm 安装。
|
|
32
|
+
|
|
33
|
+
外部 Agent 独立使用时,推荐环境变量:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
YUANCHUANG_API_TOKEN=<你的令牌>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
也可以通过 `yuanflow-cli config set-token <你的令牌>` 保存到本地配置。
|
|
40
|
+
|
|
41
|
+
## 配合 npm 包使用
|
|
42
|
+
|
|
43
|
+
安装 `yuanflow-cli`:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install -g yuanflow-cli
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
安装 Skill 到本地 Agent:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
yuanflow-skill install
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
指定安装目标:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
yuanflow-skill install --agent codex,cursor
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
项目级安装:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
yuanflow-skill install --project
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
查看可发现的 Skill:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
yuanflow-skill list-skills
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 目录规范
|
|
74
|
+
|
|
75
|
+
每个子 Skill 使用独立目录,并在目录内提供 `SKILL.md`。
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
.
|
|
79
|
+
├─ SKILL.md
|
|
80
|
+
├─ yuanflow-cli
|
|
81
|
+
│ └─ SKILL.md
|
|
82
|
+
├─ 自媒体知识库
|
|
83
|
+
│ └─ SKILL.md
|
|
84
|
+
├─ 生图技能
|
|
85
|
+
│ └─ SKILL.md
|
|
86
|
+
├─ HTML报告生成
|
|
87
|
+
│ ├─ SKILL.md
|
|
88
|
+
│ ├─ templates/
|
|
89
|
+
│ └─ references/
|
|
90
|
+
├─ 本地音视频转文字
|
|
91
|
+
│ ├─ SKILL.md
|
|
92
|
+
│ └─ scripts/
|
|
93
|
+
└─ OSS文件中转工具
|
|
94
|
+
└─ SKILL.md
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 维护说明
|
|
98
|
+
|
|
99
|
+
- 根目录 `SKILL.md` 只维护分流逻辑。
|
|
100
|
+
- 子 Skill 的具体说明和工作流放在各自目录中。
|
|
101
|
+
- 新增子 Skill 时,在根目录 `SKILL.md` 中补充分流规则。
|
|
102
|
+
- 保持 Skill 名称、目录名和 npm 命令名一致,统一使用小写 `yuanflow-*`。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yuanflow-skill
|
|
3
|
-
description: Use when the user asks about social-media API workflows, platform data queries, command discovery, or choosing among yuanflow-cli sub-skills for atomic access to Douyin, Xiaohongshu, Bilibili, WeChat, TikTok, YouTube, Weibo, Zhihu and related platforms.
|
|
3
|
+
description: Use when the user asks about social-media API workflows, platform data queries, command discovery, image generation, OSS transfer, knowledge-base workflows, HTML report generation, or choosing among yuanflow-cli sub-skills for atomic access to Douyin, Xiaohongshu, Bilibili, WeChat, TikTok, YouTube, Weibo, Zhihu and related platforms.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# YuanFlow Skill
|
|
@@ -21,6 +21,8 @@ description: Use when the user asks about social-media API workflows, platform d
|
|
|
21
21
|
- `自媒体知识库/`
|
|
22
22
|
- `OSS文件中转工具/`
|
|
23
23
|
- `生图技能/`
|
|
24
|
+
- `HTML报告生成/`
|
|
25
|
+
- `本地音视频转文字/`
|
|
24
26
|
|
|
25
27
|
## 环境判断
|
|
26
28
|
|
|
@@ -152,6 +154,28 @@ description: Use when the user asks about social-media API workflows, platform d
|
|
|
152
154
|
|
|
153
155
|
- `生图技能`
|
|
154
156
|
|
|
157
|
+
### 10. 走 `HTML报告生成`
|
|
158
|
+
|
|
159
|
+
遇到下面这些需求,优先进入这个子 Skill:
|
|
160
|
+
|
|
161
|
+
- 生成 HTML 报告、单页报告、可视化长文报告。
|
|
162
|
+
- 把自媒体分析、数据复盘、文案方案、账号监控、知识梳理、执行计划整理成可直接打开的 HTML 文件。
|
|
163
|
+
- 用户要求套用报告模板、生成报告页面、做一份可以离线查看的 HTML 总结页。
|
|
164
|
+
|
|
165
|
+
子 Skill 名称:
|
|
166
|
+
|
|
167
|
+
- `HTML报告生成`
|
|
168
|
+
|
|
169
|
+
### 11. 走 `本地音视频转文字`
|
|
170
|
+
|
|
171
|
+
只有当用户明确要求使用本地音视频转文字、本地转写、本地 ASR、离线转写或本地模型把音频/视频转成文字时,才进入这个子 Skill。
|
|
172
|
+
|
|
173
|
+
不要因为用户普通提到音频、视频、口播、文案或总结就自动使用。
|
|
174
|
+
|
|
175
|
+
子 Skill 名称:
|
|
176
|
+
|
|
177
|
+
- `本地音视频转文字`
|
|
178
|
+
|
|
155
179
|
## 多需求时怎么处理
|
|
156
180
|
|
|
157
181
|
如果用户一次提了多段流程,不要强行塞进一个子 Skill,按阶段拆开:
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 本地音视频转文字
|
|
3
|
+
description: 仅当用户明确要求使用本地音视频转文字、本地转写、本地 ASR、离线/本地模型把音频或视频转成文字时使用;不要因为用户普通提到音频、视频、文案或总结就自动使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 本地音视频转文字
|
|
7
|
+
|
|
8
|
+
本 Skill 用本地 SenseVoice 模型把音频或视频转成文字。它只在用户明确提出“使用本地音视频转文字”“本地转写”“用本地模型转文字”“离线转写”等需求时使用。
|
|
9
|
+
|
|
10
|
+
## 触发边界
|
|
11
|
+
|
|
12
|
+
只有满足下面条件之一时才使用:
|
|
13
|
+
|
|
14
|
+
- 用户明确要求使用“本地音视频转文字”这个技能。
|
|
15
|
+
- 用户明确要求本地转写、本地 ASR、离线转写、本地模型转文字。
|
|
16
|
+
- 用户提供音频或视频文件,并明确说要用本地能力把它转成文字。
|
|
17
|
+
|
|
18
|
+
不要在下面情况自动使用:
|
|
19
|
+
|
|
20
|
+
- 用户只是让你总结一段已有文字。
|
|
21
|
+
- 用户只是提到音频、视频、文案、脚本,但没有要求本地转写。
|
|
22
|
+
- 用户要求在线识别、云端转写或其它指定服务。
|
|
23
|
+
|
|
24
|
+
## 固定目录
|
|
25
|
+
|
|
26
|
+
本 Skill 的脚本目录为当前 Skill 目录下的 `scripts/`。
|
|
27
|
+
|
|
28
|
+
默认目录:
|
|
29
|
+
|
|
30
|
+
- 模型保存目录:`scripts/models`
|
|
31
|
+
- 任务缓存目录:`scripts/cache`
|
|
32
|
+
- 抽取音频目录:`scripts/cache/audio`
|
|
33
|
+
- 转写文本目录:`scripts/cache/transcripts`
|
|
34
|
+
|
|
35
|
+
在 YuanFlow 程序内置环境中,`skill_read` 返回的 `config.managed_skill_dir` 是当前 Skill 的真实目录。执行脚本时优先以这个目录为基准:
|
|
36
|
+
|
|
37
|
+
```powershell
|
|
38
|
+
cd "<config.managed_skill_dir>\scripts"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
不要把模型下载到用户桌面、项目根目录或系统临时目录。不要把模型文件打包进 Skill 或 npm 包。
|
|
42
|
+
|
|
43
|
+
## 首次使用模型下载规则
|
|
44
|
+
|
|
45
|
+
开始转写前先检查模型目录是否已经存在:
|
|
46
|
+
|
|
47
|
+
- `scripts/models/SenseVoiceSmall`
|
|
48
|
+
- `scripts/models/fsmn-vad`
|
|
49
|
+
|
|
50
|
+
如果这两个目录都存在且不为空,直接执行后续任务。
|
|
51
|
+
|
|
52
|
+
如果首次使用没有模型,运行脚本时允许自动下载:
|
|
53
|
+
|
|
54
|
+
- SenseVoice:`iic/SenseVoiceSmall`
|
|
55
|
+
- VAD:`iic/speech_fsmn_vad_zh-cn-16k-common-pytorch`
|
|
56
|
+
|
|
57
|
+
下载由 `modelscope.snapshot_download()` 完成,保存到 `scripts/models`。下载完成后继续转写。
|
|
58
|
+
|
|
59
|
+
## 执行流程
|
|
60
|
+
|
|
61
|
+
1. 确认用户明确要求本地转写。
|
|
62
|
+
2. 读取本 Skill 完整说明。
|
|
63
|
+
3. 定位 `config.managed_skill_dir` 或当前 Skill 目录。
|
|
64
|
+
4. 检查 Python 虚拟环境和依赖;不要全局安装依赖。
|
|
65
|
+
5. 如果用户提供的是视频,先转为音频,再把音频转成文字。
|
|
66
|
+
6. 如果用户提供的是音频,直接转成文字。
|
|
67
|
+
7. 把输出的 `.txt` 文件路径和核心转写结果报告给用户。
|
|
68
|
+
|
|
69
|
+
## 环境准备
|
|
70
|
+
|
|
71
|
+
在 `scripts/` 目录下创建虚拟环境并安装依赖:
|
|
72
|
+
|
|
73
|
+
```powershell
|
|
74
|
+
python -m venv .venv
|
|
75
|
+
.\.venv\Scripts\python.exe -m pip install -r requirements-transcribe.txt
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
视频转音频需要本机可用 `ffmpeg`。如果用户给的是视频且系统找不到 `ffmpeg`,先明确报告缺少 ffmpeg,不要伪造转写结果。
|
|
79
|
+
|
|
80
|
+
## 推荐调用方式
|
|
81
|
+
|
|
82
|
+
统一入口脚本:
|
|
83
|
+
|
|
84
|
+
```powershell
|
|
85
|
+
cd "<Skill目录>\scripts"
|
|
86
|
+
.\.venv\Scripts\python.exe .\transcribe_media.py "C:\path\to\input.mp3"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
视频示例:
|
|
90
|
+
|
|
91
|
+
```powershell
|
|
92
|
+
cd "<Skill目录>\scripts"
|
|
93
|
+
.\.venv\Scripts\python.exe .\transcribe_media.py "C:\path\to\input.mp4"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
常用参数:
|
|
97
|
+
|
|
98
|
+
| 参数 | 说明 |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| `input_path` | 音频文件、视频文件或目录。 |
|
|
101
|
+
| `--cache-root` | 缓存目录,默认 `scripts/cache`。 |
|
|
102
|
+
| `--models-root` | 模型目录,默认 `scripts/models`。 |
|
|
103
|
+
| `--recursive` | 输入为目录时递归扫描。 |
|
|
104
|
+
| `--device` | `auto`、`cpu`、`cuda:0` 等,默认 `auto`。 |
|
|
105
|
+
| `--language` | `zh`、`en`、`yue`、`ja`、`ko`、`auto`,默认 `auto`。 |
|
|
106
|
+
| `--batch-size-s` | 动态 batch 秒数,默认 `60`。设备性能差时调小。 |
|
|
107
|
+
| `--overwrite` | 允许覆盖已存在的音频缓存和文本结果。 |
|
|
108
|
+
|
|
109
|
+
## 低性能设备处理
|
|
110
|
+
|
|
111
|
+
如果用户设备性能较差,或执行期间出现 CPU、内存不足、进程被系统杀掉、模型加载失败等情况,不要一次性硬跑完整任务。
|
|
112
|
+
|
|
113
|
+
处理方式:
|
|
114
|
+
|
|
115
|
+
- 优先把 `--batch-size-s` 调小,例如 `20` 或 `10`。
|
|
116
|
+
- 明确告诉用户当前设备资源不足,正在改用更小批次。
|
|
117
|
+
- 如果仍然失败,建议先用 ffmpeg 把长音频切分成多个片段后逐段转写。
|
|
118
|
+
- 每段完成后保留对应 `.txt` 文件,并在最终结果中说明哪些片段已完成、哪些片段失败、失败原因是什么。
|
|
119
|
+
|
|
120
|
+
不要只回复“失败”或“网络错误”。要报告:
|
|
121
|
+
|
|
122
|
+
- 模型目录是否已存在。
|
|
123
|
+
- 是否发生了首次模型下载。
|
|
124
|
+
- 输入文件类型是音频还是视频。
|
|
125
|
+
- 视频是否已抽取音频。
|
|
126
|
+
- 输出文本文件路径。
|
|
127
|
+
- 如果分段执行,列出每段结果文件。
|
|
128
|
+
|
|
129
|
+
## 清理规则
|
|
130
|
+
|
|
131
|
+
只有用户明确要求删除缓存或模型文件时,才可以删除:
|
|
132
|
+
|
|
133
|
+
- 缓存目录:`scripts/cache`
|
|
134
|
+
- 模型目录:`scripts/models`
|
|
135
|
+
|
|
136
|
+
删除前必须确认目标路径位于当前 Skill 的 `scripts/` 目录下,不能删除其它项目目录、用户桌面目录或系统目录。
|
|
137
|
+
|
|
138
|
+
## 输出要求
|
|
139
|
+
|
|
140
|
+
成功后回复必须包含:
|
|
141
|
+
|
|
142
|
+
- 转写是否成功。
|
|
143
|
+
- 是否首次下载模型,或复用了已有模型。
|
|
144
|
+
- 如果输入是视频,说明已先抽取音频。
|
|
145
|
+
- `.txt` 结果文件的绝对路径。
|
|
146
|
+
- 转写文本的核心内容,长文本可以先给摘要并提示文件里有完整文本。
|
|
147
|
+
|
|
148
|
+
不要编造没有生成的文件路径。不要暴露任何 token 或认证信息。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .utils import ensure_parent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def extract_audio(
|
|
11
|
+
video_path: Path,
|
|
12
|
+
output_path: Path,
|
|
13
|
+
*,
|
|
14
|
+
ffmpeg_bin: str = "ffmpeg",
|
|
15
|
+
sample_rate: int = 16000,
|
|
16
|
+
channels: int = 1,
|
|
17
|
+
overwrite: bool = False,
|
|
18
|
+
) -> Path:
|
|
19
|
+
if not shutil.which(ffmpeg_bin):
|
|
20
|
+
raise FileNotFoundError(f"未找到 ffmpeg:{ffmpeg_bin}")
|
|
21
|
+
if output_path.exists() and not overwrite:
|
|
22
|
+
return output_path
|
|
23
|
+
|
|
24
|
+
ensure_parent(output_path)
|
|
25
|
+
command = [
|
|
26
|
+
ffmpeg_bin,
|
|
27
|
+
"-y" if overwrite else "-n",
|
|
28
|
+
"-i",
|
|
29
|
+
str(video_path),
|
|
30
|
+
"-vn",
|
|
31
|
+
"-ac",
|
|
32
|
+
str(channels),
|
|
33
|
+
"-ar",
|
|
34
|
+
str(sample_rate),
|
|
35
|
+
str(output_path),
|
|
36
|
+
]
|
|
37
|
+
result = subprocess.run(
|
|
38
|
+
command,
|
|
39
|
+
capture_output=True,
|
|
40
|
+
text=True,
|
|
41
|
+
encoding="utf-8",
|
|
42
|
+
errors="replace",
|
|
43
|
+
)
|
|
44
|
+
if result.returncode != 0:
|
|
45
|
+
raise RuntimeError(result.stderr.strip() or "ffmpeg 抽音频失败")
|
|
46
|
+
return output_path
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .utils import ensure_dir
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
SENSEVOICE_REPO = "iic/SenseVoiceSmall"
|
|
10
|
+
VAD_REPO = "iic/speech_fsmn_vad_zh-cn-16k-common-pytorch"
|
|
11
|
+
TOKEN_RE = re.compile(r"<\|[^>]+\|>")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def import_snapshot_download():
|
|
15
|
+
try:
|
|
16
|
+
from modelscope import snapshot_download # type: ignore
|
|
17
|
+
except ImportError:
|
|
18
|
+
from modelscope.hub.snapshot_download import snapshot_download # type: ignore
|
|
19
|
+
return snapshot_download
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def model_dir_ready(model_dir: Path) -> bool:
|
|
23
|
+
return model_dir.exists() and any(model_dir.iterdir())
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def ensure_model_dir(model_dir: Path, model_id: str) -> tuple[Path, bool]:
|
|
27
|
+
if model_dir_ready(model_dir):
|
|
28
|
+
return model_dir, False
|
|
29
|
+
snapshot_download = import_snapshot_download()
|
|
30
|
+
ensure_dir(model_dir)
|
|
31
|
+
try:
|
|
32
|
+
snapshot_download(
|
|
33
|
+
model_id=model_id,
|
|
34
|
+
local_dir=str(model_dir),
|
|
35
|
+
local_dir_use_symlinks=False,
|
|
36
|
+
)
|
|
37
|
+
except TypeError:
|
|
38
|
+
snapshot_download(
|
|
39
|
+
model_id=model_id,
|
|
40
|
+
local_dir=str(model_dir),
|
|
41
|
+
)
|
|
42
|
+
return model_dir, True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def ensure_models(models_root: Path) -> tuple[Path, Path, bool]:
|
|
46
|
+
sensevoice_dir, downloaded_sensevoice = ensure_model_dir(
|
|
47
|
+
models_root / "SenseVoiceSmall",
|
|
48
|
+
SENSEVOICE_REPO,
|
|
49
|
+
)
|
|
50
|
+
vad_dir, downloaded_vad = ensure_model_dir(models_root / "fsmn-vad", VAD_REPO)
|
|
51
|
+
return sensevoice_dir, vad_dir, downloaded_sensevoice or downloaded_vad
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def resolve_device(device: str = "auto") -> str:
|
|
55
|
+
if device != "auto":
|
|
56
|
+
return device
|
|
57
|
+
try:
|
|
58
|
+
import torch # type: ignore
|
|
59
|
+
|
|
60
|
+
return "cuda:0" if torch.cuda.is_available() else "cpu"
|
|
61
|
+
except Exception:
|
|
62
|
+
return "cpu"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def clean_transcript(text: str) -> str:
|
|
66
|
+
cleaned = TOKEN_RE.sub(" ", text)
|
|
67
|
+
cleaned = re.sub(r"\s+", " ", cleaned)
|
|
68
|
+
return cleaned.strip()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def build_model(models_root: Path, device: str = "auto"):
|
|
72
|
+
from funasr import AutoModel # type: ignore
|
|
73
|
+
|
|
74
|
+
sensevoice_dir, vad_dir, downloaded = ensure_models(models_root)
|
|
75
|
+
model = AutoModel(
|
|
76
|
+
model=str(sensevoice_dir),
|
|
77
|
+
trust_remote_code=True,
|
|
78
|
+
vad_model=str(vad_dir),
|
|
79
|
+
vad_kwargs={"max_single_segment_time": 30000},
|
|
80
|
+
device=resolve_device(device),
|
|
81
|
+
)
|
|
82
|
+
return model, downloaded
|