diff --git a/01_卡资(金)/金仓_存储备份/群晖NAS管理/参考资料/NAS千问小模型API配置.md b/01_卡资(金)/金仓_存储备份/群晖NAS管理/参考资料/NAS千问小模型API配置.md index 18a70fae..089b6130 100644 --- a/01_卡资(金)/金仓_存储备份/群晖NAS管理/参考资料/NAS千问小模型API配置.md +++ b/01_卡资(金)/金仓_存储备份/群晖NAS管理/参考资料/NAS千问小模型API配置.md @@ -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/` diff --git a/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py index d7f05143..9da8a783 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py +++ b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py @@ -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) diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index 06226dd2..c0bfcb4c 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -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 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 64e2512d..9aa4f2a7 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -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) |