🔄 卡若AI 同步 2026-03-11 00:31 | 更新:金仓、卡木、运营中枢工作台 | 排除 >20MB: 11 个
This commit is contained in:
@@ -60,6 +60,8 @@ export OLLAMA_BASE_URL="http://192.168.1.201:11434"
|
||||
|
||||
外网需确保 **frp 服务端(42.194.245.239)已开放 11401 端口**;若无法访问,请在宝塔/安全组中放行 `11401/TCP`。
|
||||
|
||||
**OpenClaw(如阿猫 Mac)使用千问**:已配置主模型为 `nas-qwen/qwen2.5:3b`,备选 `qwen2.5:1.5b` → `v0/v0-1.5-lg`。若该终端 **DNS 解析超时**(访问 `open.quwanzhi.com` 报 Resolving timed out),可改为 **IP 直连**:在 OpenClaw 的 nas-qwen 里将 `baseUrl` 设为 `http://42.194.245.239:11401/v1`(frp 同机 IP),改完后重启网关即可连通千问。
|
||||
|
||||
---
|
||||
|
||||
## 二、常用 API 端点
|
||||
@@ -87,9 +89,9 @@ curl -s http://192.168.1.201:11434/api/tags | jq .
|
||||
### 2. 文本生成(curl)
|
||||
|
||||
```bash
|
||||
# 外网示例(qwen2.5:1.5b)
|
||||
# 外网示例(推荐 qwen2.5:3b,也可用 qwen2.5:1.5b)
|
||||
curl -s http://open.quwanzhi.com:11401/api/generate -d '{
|
||||
"model": "qwen2.5:1.5b",
|
||||
"model": "qwen2.5:3b",
|
||||
"prompt": "用一句话介绍厦门",
|
||||
"stream": false
|
||||
}' | jq .
|
||||
@@ -99,7 +101,7 @@ curl -s http://open.quwanzhi.com:11401/api/generate -d '{
|
||||
|
||||
```bash
|
||||
curl -s http://open.quwanzhi.com:11401/v1/chat/completions -d '{
|
||||
"model": "qwen2.5:1.5b",
|
||||
"model": "qwen2.5:3b",
|
||||
"messages": [{"role": "user", "content": "你好,请简短回复"}],
|
||||
"stream": false
|
||||
}' | jq .
|
||||
@@ -130,7 +132,7 @@ import requests
|
||||
# 外网
|
||||
OLLAMA_BASE = os.environ.get("OLLAMA_BASE_URL", "http://open.quwanzhi.com:11401")
|
||||
|
||||
def chat(text: str, model: str = "qwen2.5:1.5b") -> str:
|
||||
def chat(text: str, model: str = "qwen2.5:3b") -> str:
|
||||
r = requests.post(
|
||||
f"{OLLAMA_BASE}/api/generate",
|
||||
json={"model": model, "prompt": text, "stream": False},
|
||||
@@ -153,7 +155,7 @@ client = OpenAI(
|
||||
api_key="ollama", # Ollama 不校验,可随意
|
||||
)
|
||||
r = client.chat.completions.create(
|
||||
model="qwen2.5:1.5b",
|
||||
model="qwen2.5:3b",
|
||||
messages=[{"role": "user", "content": "你好"}],
|
||||
)
|
||||
print(r.choices[0].message.content)
|
||||
@@ -165,7 +167,7 @@ print(r.choices[0].message.content)
|
||||
|
||||
- **一键部署(本机执行)**:
|
||||
`bash 群晖NAS管理/scripts/ollama/deploy_ollama_nas.sh`
|
||||
会完成:创建目录、上传 compose、启动容器、拉取 qwen2.5:1.5b、配置 frp 并重启 frpc。
|
||||
会完成:创建目录、上传 compose、启动容器、拉取 qwen2.5:1.5b(及推荐 qwen2.5:3b)、配置 frp 并重启 frpc。
|
||||
|
||||
- **NAS 上手动操作**:
|
||||
- 编排目录:`/volume1/docker/ollama/`
|
||||
|
||||
@@ -963,56 +963,71 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
|
||||
print(f" ✓ 封面烧录", flush=True)
|
||||
|
||||
# 5.2 烧录字幕
|
||||
# 策略:先尝试单次 FFmpeg 通道(一次 pass 完成所有字幕叠加);
|
||||
# 若失败(filter 太长/输入太多)则自动分批(batch_size=40)兜底。
|
||||
# 策略:concat 图片序列 + 单次 overlay(最快正确方案)
|
||||
# 原理:
|
||||
# - -loop 1 + enable=between:每帧都要判断所有overlay节点,极慢(163条/270s需30min+)
|
||||
# - -ss/-t 对PNG:PNG只有1帧,seek到>0时返回空流,字幕消失
|
||||
# - concat 图片序列(每条字幕是精确时长的帧段)+单次overlay:一次pass,速度快几十倍
|
||||
if sub_images:
|
||||
print(f" [3/5] 字幕烧录中({len(sub_images)} 条,随语音时间轴显示)…", flush=True)
|
||||
print(f" [3/5] 字幕烧录中({len(sub_images)} 条,concat+overlay 单次 pass)…", flush=True)
|
||||
|
||||
# 字幕烧录:使用 -ss/-t 有限时长输入(非 -loop 1),FFmpeg 只在字幕有效段处理图像帧,速度大幅提升。
|
||||
# 原理:-loop 1 会生成无限帧流(每帧都要合成),-ss start -t duration 生成有限帧,FFmpeg 自动优化。
|
||||
# 每批 25 条 overlay(约 2-4 次 pass)。
|
||||
batch_size = 25
|
||||
total_batches = (len(sub_images) + batch_size - 1) // batch_size
|
||||
for batch_idx in range(0, len(sub_images), batch_size):
|
||||
batch = sub_images[batch_idx:batch_idx + batch_size]
|
||||
inputs = ['-i', current_video]
|
||||
for img in batch:
|
||||
sub_start = max(img['start'], cover_duration)
|
||||
sub_end = img['end']
|
||||
sub_dur = max(0.05, sub_end - sub_start) # 有限时长,至少 50ms
|
||||
# -ss 偏移,-t 时长,-i 图片 → 有限帧数的图像输入
|
||||
inputs.extend(['-ss', f'{sub_start:.3f}', '-t', f'{sub_dur:.3f}', '-i', img['path']])
|
||||
fc_parts = []
|
||||
last = '0:v'
|
||||
for i, img in enumerate(batch):
|
||||
idx = i + 1
|
||||
out_n = f'vsub{i}'
|
||||
sub_start = max(img['start'], cover_duration)
|
||||
if sub_start < img['end']:
|
||||
# itsoffset 告诉 FFmpeg 从主视频哪个时刻开始叠加该输入
|
||||
fc_parts.append(
|
||||
f"[{last}][{idx}:v]overlay={overlay_pos}:enable='between(t,{sub_start:.3f},{img['end']:.3f})'[{out_n}]"
|
||||
)
|
||||
else:
|
||||
fc_parts.append(f"[{last}]copy[{out_n}]")
|
||||
last = out_n
|
||||
fc = ';'.join(fc_parts)
|
||||
batch_out = os.path.join(temp_dir, f'sub_batch_{batch_idx}.mp4')
|
||||
cmd = [
|
||||
'ffmpeg', '-y', *inputs,
|
||||
'-filter_complex', fc,
|
||||
'-map', f'[{last}]', '-map', '0:a',
|
||||
'-c:v', 'libx264', '-preset', 'fast', '-crf', '22',
|
||||
'-c:a', 'copy', '-shortest', batch_out
|
||||
]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
print(f" ⚠ 字幕批次 {batch_idx//batch_size+1} 失败: {(r.stderr or '')[-300:]}", file=sys.stderr)
|
||||
if r.returncode == 0 and os.path.exists(batch_out):
|
||||
current_video = batch_out
|
||||
if total_batches > 1:
|
||||
print(f" 字幕批次 {batch_idx//batch_size+1}/{total_batches} 完成", flush=True)
|
||||
print(f" ✓ 字幕烧录完成({total_batches}批,{len(sub_images)} 条)", flush=True)
|
||||
# 创建透明空白帧(RGBA 498x1080,所有像素透明)
|
||||
blank_path = os.path.join(temp_dir, 'sub_blank.png')
|
||||
if not os.path.exists(blank_path):
|
||||
blank = Image.new('RGBA', (out_w, out_h), (0, 0, 0, 0))
|
||||
blank.save(blank_path, 'PNG')
|
||||
|
||||
# 构建 concat 文件:把所有字幕帧描述为"时间段→图片"的序列
|
||||
# concat demuxer 格式:
|
||||
# file 'path'
|
||||
# duration X.XXX
|
||||
# 最后一行不写 duration(用于循环/截断防报错)
|
||||
concat_lines = []
|
||||
prev_end = cover_duration # 字幕从封面结束后开始
|
||||
|
||||
for img in sub_images:
|
||||
sub_start = max(img['start'], cover_duration)
|
||||
sub_end = img['end']
|
||||
if sub_start >= sub_end:
|
||||
continue
|
||||
# 空白段(上一条字幕结束 → 本条开始)
|
||||
gap = sub_start - prev_end
|
||||
if gap > 0.04:
|
||||
concat_lines.append(f"file '{blank_path}'")
|
||||
concat_lines.append(f"duration {gap:.3f}")
|
||||
# 字幕段
|
||||
concat_lines.append(f"file '{img['path']}'")
|
||||
concat_lines.append(f"duration {sub_end - sub_start:.3f}")
|
||||
prev_end = sub_end
|
||||
|
||||
# 末尾空白(最后一条字幕结束 → 视频结束)
|
||||
tail = duration - prev_end
|
||||
if tail > 0.04:
|
||||
concat_lines.append(f"file '{blank_path}'")
|
||||
concat_lines.append(f"duration {tail:.3f}")
|
||||
concat_lines.append(f"file '{blank_path}'") # concat demuxer 必须的结束帧
|
||||
|
||||
concat_file = os.path.join(temp_dir, 'sub_concat.txt')
|
||||
with open(concat_file, 'w') as f:
|
||||
f.write('\n'.join(concat_lines))
|
||||
|
||||
# 单次 FFmpeg overlay:-f concat 读图片序列 → overlay 到主视频
|
||||
sub_out = os.path.join(temp_dir, 'with_subs.mp4')
|
||||
cmd = [
|
||||
'ffmpeg', '-y',
|
||||
'-i', current_video,
|
||||
'-f', 'concat', '-safe', '0', '-i', concat_file,
|
||||
'-filter_complex', f'[0:v][1:v]overlay={overlay_pos}[v]',
|
||||
'-map', '[v]', '-map', '0:a',
|
||||
'-c:v', 'libx264', '-preset', 'fast', '-crf', '22',
|
||||
'-c:a', 'copy', sub_out
|
||||
]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if r.returncode == 0 and os.path.exists(sub_out):
|
||||
current_video = sub_out
|
||||
print(f" ✓ 字幕烧录完成(concat 单次 pass,{len(sub_images)} 条)", flush=True)
|
||||
else:
|
||||
print(f" ⚠ 字幕烧录失败: {(r.stderr or '')[-400:]}", file=sys.stderr)
|
||||
else:
|
||||
if do_burn_subs and os.path.exists(transcript_path):
|
||||
print(f" ⚠ 未烧录字幕:解析后无有效字幕(请用 MLX Whisper 重新生成 transcript.srt)", flush=True)
|
||||
|
||||
@@ -276,3 +276,4 @@
|
||||
| 2026-03-10 20:26:50 | 🔄 卡若AI 同步 2026-03-10 20:26 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
| 2026-03-10 20:45:03 | 🔄 卡若AI 同步 2026-03-10 20:45 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
| 2026-03-10 20:55:38 | 🔄 卡若AI 同步 2026-03-10 20:55 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
| 2026-03-10 23:34:57 | 🔄 卡若AI 同步 2026-03-10 23:34 | 更新:金仓、卡木、运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
|
||||
@@ -279,3 +279,4 @@
|
||||
| 2026-03-10 20:26:50 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-10 20:26 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-10 20:45:03 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-10 20:45 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-10 20:55:38 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-10 20:55 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-10 23:34:57 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-10 23:34 | 更新:金仓、卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
|
||||
Reference in New Issue
Block a user