🔄 卡若AI 同步 2026-03-11 00:31 | 更新:金仓、卡木、运营中枢工作台 | 排除 >20MB: 11 个

This commit is contained in:
2026-03-11 00:31:08 +08:00
parent 5779dc5539
commit ed61639a29
4 changed files with 73 additions and 54 deletions

View File

@@ -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/`

View File

@@ -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 对PNGPNG只有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 1FFmpeg 只在字幕有效段处理图像帧,速度大幅提升。
# 原理:-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:
# 创建透明空白帧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']
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')
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', *inputs,
'-filter_complex', fc,
'-map', f'[{last}]', '-map', '0:a',
'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', '-shortest', batch_out
'-c:a', 'copy', sub_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)
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)

View File

@@ -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 个 |

View File

@@ -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) |