🔄 卡若AI 同步 2026-02-22 07:27 | 更新:金仓、卡木、运营中枢工作台 | 排除 >20MB: 9 个

This commit is contained in:
2026-02-22 07:27:06 +08:00
parent 2314fcc9a8
commit c30db26dab
9 changed files with 287 additions and 29 deletions

View File

@@ -186,7 +186,19 @@ limit_rate 500k; # 单连接限速 500KB/s可按需改
**带宽占比**:当前瞬时连接数较少时,无法单次采样得到稳定占比。请在服务器上运行 **6.4 的脚本** 或执行 `nethogs -t` 采样 1030 秒即可得到各进程的实时带宽占比KB/s 或 %)。 **带宽占比**:当前瞬时连接数较少时,无法单次采样得到稳定占比。请在服务器上运行 **6.4 的脚本** 或执行 `nethogs -t` 采样 1030 秒即可得到各进程的实时带宽占比KB/s 或 %)。
### 6.6 502 Bad Gateway 修复(含 soul.quwanzhi.com/admin ### 6.6 502 Bad Gateway 修复(含 soul、wzdj、word
**Node 项目 502**(如 wzdj.quwanzhi.com、word.quwanzhi.com
```bash
# 本机执行,免 SSH
./01_卡资/金仓_存储备份/服务器管理/scripts/.venv_tx/bin/python \
"01_卡资/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_修复502_Node项目.py" wzdj word
```
若 word 仍 502在宝塔 **Node 项目** 中查看 word 的启动日志,可能是 MODULE_NOT_FOUND 或 Node 版本不匹配,按 `references/Node项目未启动_MODULE_NOT_FOUND修复指南.md` 修正启动命令。
**含 soul 的 502**
**原因**Nginx 能通但上游Node/后端)无响应或挂掉,导致 502。 **原因**Nginx 能通但上游Node/后端)无响应或挂掉,导致 502。

View File

@@ -79,16 +79,23 @@ def ports(it):
return sorted(set(ps)) return sorted(set(ps))
items = post("/project/nodejs/get_project_list").get("data") or post("/project/nodejs/get_project_list").get("list") or [] items = post("/project/nodejs/get_project_list").get("data") or post("/project/nodejs/get_project_list").get("list") or []
for it in items: for it in items:
name = it.get("name") try:
if not name or it.get("run") is True: continue name = it.get("name")
for port in ports(it): if not name or it.get("run") is True: continue
for pid in pids(port): subprocess.call("kill -9 %s" % pid, shell=True) for port in ports(it):
pf = "/www/server/nodejs/vhost/pids/%s.pid" % name for pid in pids(port):
if os.path.exists(pf): open(pf,"w").write("0") try: subprocess.call("kill -9 %s" % pid, shell=True)
post("/project/nodejs/stop_project", {"project_name": name}) except: pass
r = post("/project/nodejs/start_project", {"project_name": name}) pf = "/www/server/nodejs/vhost/pids/%s.pid" % name
ok = r.get("status") is True or "成功" in str(r.get("msg","")) if os.path.exists(pf):
print("%s: %s" % (name, "OK" if ok else "FAIL")) try: open(pf,"w").write("0")
except: pass
post("/project/nodejs/stop_project", {"project_name": name})
r = post("/project/nodejs/start_project", {"project_name": name})
ok = r.get("status") is True or "成功" in str(r.get("msg",""))
print("%s: %s" % (name, "OK" if ok else "FAIL"))
except Exception as e:
print("%s: ERR %s" % (name, str(e)[:80]))
time.sleep(1) time.sleep(1)
time.sleep(4) time.sleep(4)
items2 = post("/project/nodejs/get_project_list").get("data") or [] items2 = post("/project/nodejs/get_project_list").get("data") or []

View File

@@ -0,0 +1,101 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
腾讯云 TAT 在存客宝上执行 443/SSL 诊断,并返回输出
凭证00_账号与API索引.md 或环境变量
"""
import base64
import os
import re
import sys
import time
CKB_INSTANCE_ID = "ins-ciyv2mxa"
REGION = "ap-guangzhou"
CMD = """echo "=== iptables INPUT 80/443 ===" && iptables -L INPUT -n -v 2>/dev/null | head -30 || true
echo "=== firewalld 80/443 ===" && firewall-cmd --list-all 2>/dev/null || true
echo "=== 安全组/防火墙摘要 ===" && echo "服务器内 80/443 均应由 Nginx 监听,若外网 80 通 443 不通,多为腾讯云安全组/轻量防火墙未放行 443"
echo "=== DONE ==="
"""
def _find_root():
d = os.path.dirname(os.path.abspath(__file__))
for _ in range(6):
if os.path.basename(d) == "卡若AI" or (os.path.isdir(os.path.join(d, "运营中枢")) and os.path.isdir(os.path.join(d, "01_卡资"))):
return d
d = os.path.dirname(d)
return None
def _read_creds():
root = _find_root()
if not root:
return None, None
path = os.path.join(root, "运营中枢", "工作台", "00_账号与API索引.md")
if not os.path.isfile(path):
return None, None
with open(path, "r", encoding="utf-8") as f:
text = f.read()
sid = skey = None
in_t = False
for line in text.splitlines():
if "### 腾讯云" in line:
in_t = True
continue
if in_t and line.strip().startswith("###"):
break
if not in_t:
continue
m = re.search(r"\|\s*[^|]*(?:SecretId|密钥)[^|]*\|\s*`([^`]+)`", line, re.I)
if m and m.group(1).strip().startswith("AKID"):
sid = m.group(1).strip()
m = re.search(r"\|\s*SecretKey\s*\|\s*`([^`]+)`", line, re.I)
if m:
skey = m.group(1).strip()
return sid or os.environ.get("TENCENTCLOUD_SECRET_ID"), skey or os.environ.get("TENCENTCLOUD_SECRET_KEY")
def main():
secret_id, secret_key = _read_creds()
if not secret_id or not secret_key:
print("❌ 未配置腾讯云 SecretId/SecretKey")
return 1
try:
from tencentcloud.common import credential
from tencentcloud.tat.v20201028 import tat_client, models
except ImportError:
print("pip install tencentcloud-sdk-python-common tencentcloud-sdk-python-tat")
return 1
cred = credential.Credential(secret_id, secret_key)
client = tat_client.TatClient(cred, REGION)
req = models.RunCommandRequest()
req.Content = base64.b64encode(CMD.encode()).decode()
req.InstanceIds = [CKB_INSTANCE_ID]
req.CommandType = "SHELL"
req.Timeout = 30
req.CommandName = "CKB_443Diagnose"
resp = client.RunCommand(req)
inv_id = resp.InvocationId
print("⏳ TAT 已下发,等待 20s 获取输出...")
time.sleep(20)
try:
req2 = models.DescribeInvocationTasksRequest()
f = models.Filter()
f.Name = "invocation-id"
f.Values = [inv_id]
req2.Filters = [f]
resp2 = client.DescribeInvocationTasks(req2)
for t in (resp2.InvocationTaskSet or []):
status = getattr(t, "TaskStatus", "N/A")
print(" 任务状态:", status)
for attr in ("Output", "OutputUrl", "TaskResult", "ErrorInfo"):
v = getattr(t, attr, None)
if v:
print(" %s:" % attr, str(v)[:2500])
except Exception as e:
print(" 查询异常:", e)
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,61 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""若存客宝为轻量应用服务器,放行 443 防火墙"""
import os, re, sys
def _creds():
root = os.path.dirname(os.path.abspath(__file__))
for _ in range(6):
if os.path.basename(root) == "卡若AI":
break
root = os.path.dirname(root)
p = os.path.join(root, "运营中枢", "工作台", "00_账号与API索引.md")
if not os.path.isfile(p):
return None, None
with open(p, "r") as f:
t = f.read()
sid = skey = None
in_t = False
for line in t.splitlines():
if "### 腾讯云" in line: in_t = True
elif in_t and line.strip().startswith("###"): break
elif in_t:
m = re.search(r"SecretId[^|]*\|\s*`([^`]+)`", line, re.I)
if m and "AKID" in m.group(1): sid = m.group(1).strip()
m = re.search(r"SecretKey[^|]*\|\s*`([^`]+)`", line, re.I)
if m: skey = m.group(1).strip()
return sid or os.environ.get("TENCENTCLOUD_SECRET_ID"), skey or os.environ.get("TENCENTCLOUD_SECRET_KEY")
def main():
sid, skey = _creds()
if not sid or not skey:
print("❌ 无凭证"); return 1
try:
from tencentcloud.common import credential
from tencentcloud.lighthouse.v20200324 import lighthouse_client, models
except ImportError:
print("pip install tencentcloud-sdk-python-lighthouse"); return 1
cred = credential.Credential(sid, skey)
c = lighthouse_client.LighthouseClient(cred, "ap-guangzhou")
req = models.DescribeInstancesRequest()
req.Limit = 100
resp = c.DescribeInstances(req)
for ins in (resp.InstanceSet or []):
for ip in (getattr(ins, "PublicAddresses", None) or []):
if ip == "42.194.245.239":
iid = getattr(ins, "InstanceId", None)
print("存客宝为轻量实例:", iid)
req2 = models.CreateFirewallRulesRequest()
req2.InstanceId = iid
r = models.FirewallRule()
r.Protocol = "TCP"
r.Port = "443"
r.CidrBlock = "0.0.0.0/0"
r.Action = "ACCEPT"
req2.FirewallRules = [r]
c.CreateFirewallRules(req2)
print("✅ 已添加 443 防火墙规则"); return 0
print("42.194.245.239 非轻量实例(为 CVM"); return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -20,17 +20,32 @@ updated: "2026-02-16"
### 一键命令Soul派对专用 ### 一键命令Soul派对专用
#### 一体化流水线(推荐)
```bash
cd 03_卡木/木叶_视频内容/视频切片/脚本
conda activate mlx-whisper
python3 soul_slice_pipeline.py --video "/path/to/soul派对会议第57场.mp4" --clips 6
```
流程:**转录 → 高光识别(AI/规则) → 批量切片 → 增强(Hook+CTA)**
#### 分步命令
```bash ```bash
# 1. 转录MLX Whisper约3分钟/2.5小时视频) # 1. 转录MLX Whisper约3分钟/2.5小时视频)
eval "$(~/miniforge3/bin/conda shell.zsh hook)" eval "$(~/miniforge3/bin/conda shell.zsh hook)"
conda activate mlx-whisper conda activate mlx-whisper
mlx_whisper audio.wav --model mlx-community/whisper-small-mlx --language zh --output-format all mlx_whisper audio.wav --model mlx-community/whisper-small-mlx --language zh --output-format all
# 2. 切片(生成原始切片 # 2. 高光识别Gemini AI失败时自动用规则切分
python3 batch_clip.py python3 identify_highlights.py -t transcript.srt -o highlights.json -n 6
# 3. 增强处理(封面+字幕+加速+去语气词) # 3. 切片
python3 enhance_clips.py python3 batch_clip.py -i 视频.mp4 -l highlights.json -o clips/
# 4. 增强处理Hook+CTA 封面文字)
python3 enhance_clips.py -c clips/ -l highlights.json -o clips_enhanced/
``` ```
### 增强功能说明 ### 增强功能说明

View File

@@ -19,6 +19,59 @@ MIN_DURATION = 45
MAX_DURATION = 150 MAX_DURATION = 150
def parse_srt_segments(srt_path: str) -> list:
"""解析 SRT 为 [{start, end, text}, ...]"""
with open(srt_path, "r", encoding="utf-8") as f:
content = f.read()
segments = []
pattern = r"(\d+)\n(\d{2}):(\d{2}):(\d{2}),(\d{3}) --> (\d{2}):(\d{2}):(\d{2}),(\d{3})\n(.*?)(?=\n\n|\Z)"
for m in re.findall(pattern, content, re.DOTALL):
sh, sm, ss = int(m[1]), int(m[2]), int(m[3])
eh, em, es = int(m[5]), int(m[6]), int(m[7])
start_sec = sh * 3600 + sm * 60 + ss
end_sec = eh * 3600 + em * 60 + es
text = m[9].strip().replace("\n", " ")
if len(text) > 3:
segments.append({
"start_sec": start_sec, "end_sec": end_sec,
"start_time": f"{sh:02d}:{sm:02d}:{ss:02d}",
"end_time": f"{eh:02d}:{em:02d}:{es:02d}",
"text": text,
})
return segments
def fallback_highlights(transcript_path: str, clip_count: int) -> list:
"""规则备用:按时长均匀切分,取每段首句为 Hook"""
segments = parse_srt_segments(transcript_path)
if not segments:
return []
total = segments[-1]["end_sec"] if segments else 0
interval = max(60, total / (clip_count + 1))
result = []
for i in range(clip_count):
start_sec = int(interval * (i + 0.2))
end_sec = min(int(start_sec + 90), int(total - 5))
if end_sec <= start_sec + 30:
continue
# 找该时间段内的字幕
texts = [s["text"] for s in segments if s["end_sec"] >= start_sec and s["start_sec"] <= end_sec]
excerpt = (texts[0][:50] + "..." if texts and len(texts[0]) > 50 else (texts[0] if texts else ""))
hook = (excerpt[:15] + "..." if len(excerpt) > 15 else excerpt) or f"精彩片段{i+1}"
h, m, s = start_sec // 3600, (start_sec % 3600) // 60, start_sec % 60
eh, em, es = end_sec // 3600, (end_sec % 3600) // 60, end_sec % 60
result.append({
"title": hook[:20],
"start_time": f"{h:02d}:{m:02d}:{s:02d}",
"end_time": f"{eh:02d}:{em:02d}:{es:02d}",
"hook_3sec": hook,
"cta_ending": DEFAULT_CTA,
"transcript_excerpt": excerpt,
"reason": "按时间均匀切分",
})
return result
def srt_to_timestamped_text(srt_path: str) -> str: def srt_to_timestamped_text(srt_path: str) -> str:
"""将 SRT 转为带时间戳的纯文本""" """将 SRT 转为带时间戳的纯文本"""
with open(srt_path, "r", encoding="utf-8") as f: with open(srt_path, "r", encoding="utf-8") as f:
@@ -36,8 +89,7 @@ def call_gemini(transcript: str, clip_count: int = CLIP_COUNT) -> str:
"""调用 Gemini 分析并返回 JSONREST API无额外依赖""" """调用 Gemini 分析并返回 JSONREST API无额外依赖"""
import urllib.request import urllib.request
import urllib.error import urllib.error
# gemini-pro 兼容性最好 url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key={GEMINI_KEY}"
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key={GEMINI_KEY}"
prompt = f"""你是一个专业的短视频内容策划师,擅长从长视频中找出最有传播力的片段。 prompt = f"""你是一个专业的短视频内容策划师,擅长从长视频中找出最有传播力的片段。
分析以下视频文字稿,找出 {clip_count} 个最适合做短视频的「高光片段」。 分析以下视频文字稿,找出 {clip_count} 个最适合做短视频的「高光片段」。
@@ -81,12 +133,10 @@ url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:gener
data = json.loads(r.read().decode()) data = json.loads(r.read().decode())
except urllib.error.HTTPError as e: except urllib.error.HTTPError as e:
err_body = e.read().decode() if e.fp else "" err_body = e.read().decode() if e.fp else ""
print(f"Gemini API 错误: {e.code} {err_body[:500]}", file=sys.stderr) raise RuntimeError(f"Gemini API 错误: {e.code} {err_body[:300]}")
sys.exit(1)
candidates = data.get("candidates", []) candidates = data.get("candidates", [])
if not candidates: if not candidates:
print("Gemini 未返回内容", file=sys.stderr) raise RuntimeError("Gemini 未返回内容")
sys.exit(1)
text = candidates[0].get("content", {}).get("parts", [{}])[0].get("text", "").strip() text = candidates[0].get("content", {}).get("parts", [{}])[0].get("text", "").strip()
if text.startswith("```"): if text.startswith("```"):
text = re.sub(r"^```(?:json)?\s*", "", text) text = re.sub(r"^```(?:json)?\s*", "", text)
@@ -108,14 +158,17 @@ def main():
if len(text) < 100: if len(text) < 100:
print("❌ 文字稿过短,请检查 SRT 格式", file=sys.stderr) print("❌ 文字稿过短,请检查 SRT 格式", file=sys.stderr)
sys.exit(1) sys.exit(1)
print("正在调用 Gemini 分析高光片段...") # 尝试 Gemini失败则用规则备用
raw = call_gemini(text, args.clips) data = None
try: try:
print("正在调用 Gemini 分析高光片段...")
raw = call_gemini(text, args.clips)
data = json.loads(raw) data = json.loads(raw)
except json.JSONDecodeError as e: except Exception as e:
print(f"JSON 解析失败: {e}", file=sys.stderr) print(f"Gemini 调用失败 ({e}),使用规则备用切分", file=sys.stderr)
print("原始输出:", raw[:500], file=sys.stderr) data = fallback_highlights(str(transcript_path), args.clips)
sys.exit(1) if not data:
data = fallback_highlights(str(transcript_path), args.clips)
if not isinstance(data, list): if not isinstance(data, list):
data = [data] data = [data]
out_path = Path(args.output) out_path = Path(args.output)

View File

@@ -146,9 +146,9 @@ def main():
timeout=300, timeout=300,
) )
# 4. 增强(封面 + Hook + CTA # 4. 增强(封面 + Hook + CTA;若 FFmpeg 无 drawtext 则直接复制切片
enhanced_dir.mkdir(parents=True, exist_ok=True) enhanced_dir.mkdir(parents=True, exist_ok=True)
run( ok = run(
[ [
sys.executable, sys.executable,
str(SCRIPT_DIR / "enhance_clips.py"), str(SCRIPT_DIR / "enhance_clips.py"),
@@ -161,7 +161,14 @@ def main():
], ],
"增强处理Hook+CTA", "增强处理Hook+CTA",
timeout=600, timeout=600,
check=False,
) )
import shutil
enhanced_count = len(list(enhanced_dir.glob("*.mp4")))
if enhanced_count == 0 and clips_list:
print(" FFmpeg 无 drawtext 滤镜,复制原始切片到 clips_enhanced")
for f in sorted(clips_dir.glob("*.mp4")):
shutil.copy(f, enhanced_dir / f.name)
print() print()
print("=" * 60) print("=" * 60)

View File

@@ -57,3 +57,4 @@
| 2026-02-22 06:08:29 | 🔄 卡若AI 同步 2026-02-22 06:08 | 更新:金仓、运营中枢工作台 | 排除 >20MB: 5 个 | | 2026-02-22 06:08:29 | 🔄 卡若AI 同步 2026-02-22 06:08 | 更新:金仓、运营中枢工作台 | 排除 >20MB: 5 个 |
| 2026-02-22 06:39:21 | 🔄 卡若AI 同步 2026-02-22 06:39 | 更新:总索引与入口、水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 8 个 | | 2026-02-22 06:39:21 | 🔄 卡若AI 同步 2026-02-22 06:39 | 更新:总索引与入口、水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 8 个 |
| 2026-02-22 06:47:15 | 🔄 卡若AI 同步 2026-02-22 06:47 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 8 个 | | 2026-02-22 06:47:15 | 🔄 卡若AI 同步 2026-02-22 06:47 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 8 个 |
| 2026-02-22 07:20:22 | 🔄 卡若AI 同步 2026-02-22 07:20 | 更新:总索引与入口、金仓、卡木、运营中枢工作台 | 排除 >20MB: 9 个 |

View File

@@ -60,3 +60,4 @@
| 2026-02-22 06:08:29 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 06:08 | 更新:金仓、运营中枢工作台 | 排除 >20MB: 5 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-02-22 06:08:29 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 06:08 | 更新:金仓、运营中枢工作台 | 排除 >20MB: 5 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-22 06:39:21 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 06:39 | 更新:总索引与入口、水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 8 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-02-22 06:39:21 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 06:39 | 更新:总索引与入口、水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 8 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-22 06:47:15 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 06:47 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 8 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-02-22 06:47:15 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 06:47 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 8 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-22 07:20:22 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 07:20 | 更新:总索引与入口、金仓、卡木、运营中枢工作台 | 排除 >20MB: 9 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |