🔄 卡若AI 同步 2026-02-22 07:20 | 更新:总索引与入口、金仓、卡木、运营中枢工作台 | 排除 >20MB: 9 个
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -91,4 +91,5 @@ _大文件外置/财务管理_data/chat.snapshot_收集.db
|
|||||||
03_卡木(木)/木叶_视频内容/Remotion程序化视频/10秒视频/node_modules/.cache/webpack/remotion-production-4.0.427/a233e9cccba253c3b0157f54cad843b8/0.pack
|
03_卡木(木)/木叶_视频内容/Remotion程序化视频/10秒视频/node_modules/.cache/webpack/remotion-production-4.0.427/a233e9cccba253c3b0157f54cad843b8/0.pack
|
||||||
03_卡木(木)/木叶_视频内容/Remotion程序化视频/10秒视频/node_modules/.remotion/chrome-headless-shell/mac-x64/chrome-headless-shell-mac-x64/chrome-headless-shell
|
03_卡木(木)/木叶_视频内容/Remotion程序化视频/10秒视频/node_modules/.remotion/chrome-headless-shell/mac-x64/chrome-headless-shell-mac-x64/chrome-headless-shell
|
||||||
03_卡木(木)/木叶_视频内容/Remotion程序化视频/10秒视频/node_modules/@rspack/binding-darwin-x64/rspack.darwin-x64.node
|
03_卡木(木)/木叶_视频内容/Remotion程序化视频/10秒视频/node_modules/@rspack/binding-darwin-x64/rspack.darwin-x64.node
|
||||||
|
03_卡木(木)/木叶_视频内容/视频切片/脚本/存客宝体系_成片TEMP_MPY_wvf_snd.mp4
|
||||||
# === 自动排除结束 ===
|
# === 自动排除结束 ===
|
||||||
|
|||||||
@@ -25,6 +25,12 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 站点无法访问(ERR_CONNECTION_CLOSED)
|
||||||
|
|
||||||
|
若 kr-kf.quwanzhi.com、lytiao.com 等无法打开:先查 **443 端口**。常见为腾讯云安全组未放行 443,或 Nginx 未监听 443。详见 `references/存客宝_站点无法访问_ERR_CONNECTION_CLOSED修复.md`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 快速操作
|
## 快速操作
|
||||||
|
|
||||||
- **Node 项目**:若有 Node 项目,可参考 `references/宝塔Node项目管理_SKILL.md` 编写存客宝版批量修复脚本(PANEL、API_KEY 改为存客宝)
|
- **Node 项目**:若有 Node 项目,可参考 `references/宝塔Node项目管理_SKILL.md` 编写存客宝版批量修复脚本(PANEL、API_KEY 改为存客宝)
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# 存客宝 kr-kf.quwanzhi.com、lytiao.com 无法访问 修复指南
|
||||||
|
|
||||||
|
> 现象:ERR_CONNECTION_CLOSED,面板显示 运行中
|
||||||
|
> 诊断结果:**443 端口 Connection refused**(80 正常)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、根因
|
||||||
|
|
||||||
|
- 80 端口可达
|
||||||
|
- **443 端口被拒绝** → 访问 https:// 会失败
|
||||||
|
- 域名已正确解析到 42.194.245.239
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、处理步骤(按顺序)
|
||||||
|
|
||||||
|
### 1. 腾讯云安全组放行 443
|
||||||
|
|
||||||
|
1. 打开 [腾讯云控制台](https://console.cloud.tencent.com/cvm/instance) → 找到存客宝实例 (42.194.245.239)
|
||||||
|
2. 点击实例 → **安全组** → **编辑规则** → **入站规则**
|
||||||
|
3. 确认有 **443/TCP** 入站,来源 `0.0.0.0/0`
|
||||||
|
4. 若无,点击 **添加规则**:协议端口 443,来源 0.0.0.0/0,策略 允许
|
||||||
|
|
||||||
|
### 2. 宝塔面板终端执行(Nginx 重载)
|
||||||
|
|
||||||
|
在 https://42.194.245.239:9988 → 终端 执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nginx -t && nginx -s reload
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 检查 SSL 证书
|
||||||
|
|
||||||
|
宝塔 → **网站** → 找到 kr-kf.quwanzhi.com、www.lytiao.com → **设置** → **SSL**
|
||||||
|
|
||||||
|
- 若未部署证书,部署 Let's Encrypt 或自有证书
|
||||||
|
- 若已过期,续签或重新部署
|
||||||
|
|
||||||
|
### 4. 确认 Nginx 监听 443
|
||||||
|
|
||||||
|
终端执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ss -tlnp | grep 443
|
||||||
|
```
|
||||||
|
|
||||||
|
若无输出,说明 Nginx 未监听 443,需在对应站点启用 SSL 并保存配置。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、快速验证
|
||||||
|
|
||||||
|
- **http://kr-kf.quwanzhi.com**(80)若可访问,说明应用正常,问题在 443/SSL
|
||||||
|
- **https://kr-kf.quwanzhi.com** 需 443 和 SSL 均正常才能访问
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 存客宝 kr-kf.quwanzhi.com、lytiao.com 无法访问(ERR_CONNECTION_CLOSED)修复
|
||||||
|
# 在存客宝宝塔面板【终端】复制整段粘贴执行
|
||||||
|
|
||||||
|
echo "========== 存客宝 站点无法访问 修复 =========="
|
||||||
|
|
||||||
|
echo "[1] 端口监听"
|
||||||
|
ss -tlnp | grep -E ':80 |:443 ' || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[2] Nginx 配置测试"
|
||||||
|
nginx -t 2>&1
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[3] 重启 Nginx"
|
||||||
|
nginx -s reload 2>&1 || systemctl restart nginx 2>&1
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[4] 宝塔防火墙 80/443(若启用)"
|
||||||
|
bt 14 2>/dev/null | grep -E "80|443" | head -5 || echo " (bt 14 未输出)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[5] 腾讯云安全组"
|
||||||
|
echo " 请到 腾讯云控制台 → 云服务器 → 存客宝实例 → 安全组 → 入站规则"
|
||||||
|
echo " 确认 80、443 已放行(0.0.0.0/0 或 来源 0.0.0.0/0)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========== 完成 =========="
|
||||||
|
echo "若 443 仍未监听,检查各站点 SSL 证书是否已部署;"
|
||||||
|
echo "若腾讯云安全组未放行 443,需在控制台添加 443 入站规则。"
|
||||||
126
01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_修复502_Node项目.py
Normal file
126
01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_修复502_Node项目.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
腾讯云 TAT:在 kr宝塔 上重启 Nginx + 指定 Node 项目(修复 502,免 SSH)
|
||||||
|
适用:wzdj、word 等 Node 项目 502。凭证:00_账号与API索引.md
|
||||||
|
"""
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
KR_INSTANCE_ID = "ins-aw0tnqjo"
|
||||||
|
REGION = "ap-guangzhou"
|
||||||
|
|
||||||
|
# 默认重启的项目名(502 常见)
|
||||||
|
RESTART_NAMES = ["wzdj", "word", "soul", "zhiji", "dlm"]
|
||||||
|
|
||||||
|
def _read_creds():
|
||||||
|
d = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
for _ in range(6):
|
||||||
|
root = d
|
||||||
|
if os.path.isfile(os.path.join(root, "运营中枢", "工作台", "00_账号与API索引.md")):
|
||||||
|
path = os.path.join(root, "运营中枢", "工作台", "00_账号与API索引.md")
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
text = f.read()
|
||||||
|
sid = skey = None
|
||||||
|
in_tx = False
|
||||||
|
for line in text.splitlines():
|
||||||
|
if "### 腾讯云" in line:
|
||||||
|
in_tx = True
|
||||||
|
continue
|
||||||
|
if in_tx and line.strip().startswith("###"):
|
||||||
|
break
|
||||||
|
if not in_tx:
|
||||||
|
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 None, skey or None
|
||||||
|
d = os.path.dirname(d)
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def build_shell(names):
|
||||||
|
want_csv = ",".join(n.lower() for n in names)
|
||||||
|
names_str = " ".join(names)
|
||||||
|
return f'''#!/bin/bash
|
||||||
|
set -e
|
||||||
|
echo "=== 1. 重载 Nginx ==="
|
||||||
|
nginx -t && nginx -s reload
|
||||||
|
echo "=== 2. 重启 Node 项目: {names_str} ==="
|
||||||
|
python3 -c "
|
||||||
|
import hashlib, json, urllib.request, urllib.parse, ssl, time
|
||||||
|
ssl._create_default_https_context = ssl._create_unverified_context
|
||||||
|
P, K = 'https://127.0.0.1:9988', 'qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT'
|
||||||
|
def sign():
|
||||||
|
t = int(time.time())
|
||||||
|
s = str(t) + hashlib.md5(K.encode()).hexdigest()
|
||||||
|
return {{'request_time': t, 'request_token': hashlib.md5(s.encode()).hexdigest()}}
|
||||||
|
def post(path, d=None):
|
||||||
|
pl = sign()
|
||||||
|
if d: pl.update(d)
|
||||||
|
r = urllib.request.Request(P+path, data=urllib.parse.urlencode(pl).encode())
|
||||||
|
with urllib.request.urlopen(r, timeout=25) as resp:
|
||||||
|
return json.loads(resp.read().decode())
|
||||||
|
items = post('/project/nodejs/get_project_list').get('data') or post('/project/nodejs/get_project_list').get('list') or []
|
||||||
|
want = set('{want_csv}'.split(','))
|
||||||
|
for it in items:
|
||||||
|
nm = (it.get('name') or '').lower()
|
||||||
|
if nm in want:
|
||||||
|
post('/project/nodejs/restart_project', {{'project_name': it.get('name') or it.get('project_name')}})
|
||||||
|
print(' 已重启:', nm)
|
||||||
|
time.sleep(2)
|
||||||
|
"
|
||||||
|
echo "=== 完成 ==="
|
||||||
|
'''
|
||||||
|
|
||||||
|
def main():
|
||||||
|
names = (sys.argv[1:] or RESTART_NAMES)[:10]
|
||||||
|
sid = os.environ.get("TENCENTCLOUD_SECRET_ID")
|
||||||
|
skey = os.environ.get("TENCENTCLOUD_SECRET_KEY")
|
||||||
|
if not sid or not skey:
|
||||||
|
sid, skey = _read_creds()
|
||||||
|
if not sid or not skey:
|
||||||
|
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-tat")
|
||||||
|
return 1
|
||||||
|
shell = build_shell(names)
|
||||||
|
cred = credential.Credential(sid, skey)
|
||||||
|
client = tat_client.TatClient(cred, REGION)
|
||||||
|
req = models.RunCommandRequest()
|
||||||
|
req.Content = base64.b64encode(shell.encode()).decode()
|
||||||
|
req.InstanceIds = [KR_INSTANCE_ID]
|
||||||
|
req.CommandType = "SHELL"
|
||||||
|
req.Timeout = 90
|
||||||
|
req.CommandName = "Fix502_NodeRestart"
|
||||||
|
resp = client.RunCommand(req)
|
||||||
|
print("✅ TAT 已下发 InvocationId:", resp.InvocationId)
|
||||||
|
print(" 重启项目:", ", ".join(names))
|
||||||
|
print(" 等待 50s...")
|
||||||
|
time.sleep(50)
|
||||||
|
try:
|
||||||
|
req2 = models.DescribeInvocationTasksRequest()
|
||||||
|
f = models.Filter()
|
||||||
|
f.Name = "invocation-id"
|
||||||
|
f.Values = [resp.InvocationId]
|
||||||
|
req2.Filters = [f]
|
||||||
|
r2 = client.DescribeInvocationTasks(req2)
|
||||||
|
for t in (r2.InvocationTaskSet or []):
|
||||||
|
print(" 状态:", getattr(t, "TaskStatus", ""))
|
||||||
|
if hasattr(t, "Output") and t.Output:
|
||||||
|
print(" 输出:", (t.Output or "")[:600])
|
||||||
|
except Exception as e:
|
||||||
|
print(" 查询:", e)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
93
01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_存客宝站点修复.py
Normal file
93
01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_存客宝站点修复.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
腾讯云 TAT 在存客宝 CVM 上执行 Nginx 重启与站点诊断(修复 kr-kf.quwanzhi.com、lytiao.com 无法访问)
|
||||||
|
凭证:00_账号与API索引.md 或环境变量
|
||||||
|
"""
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
CKB_INSTANCE_ID = "ins-ciyv2mxa"
|
||||||
|
REGION = "ap-guangzhou"
|
||||||
|
|
||||||
|
def _find_karuo_ai_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_karuo_ai_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()
|
||||||
|
secret_id = secret_key = None
|
||||||
|
in_tencent = False
|
||||||
|
for line in text.splitlines():
|
||||||
|
if "### 腾讯云" in line:
|
||||||
|
in_tencent = True
|
||||||
|
continue
|
||||||
|
if in_tencent and line.strip().startswith("###"):
|
||||||
|
break
|
||||||
|
if not in_tencent:
|
||||||
|
continue
|
||||||
|
m = re.search(r"\|\s*[^|]*(?:SecretId|密钥)[^|]*\|\s*`([^`]+)`", line, re.I)
|
||||||
|
if m:
|
||||||
|
val = m.group(1).strip()
|
||||||
|
if val.startswith("AKID"):
|
||||||
|
secret_id = val
|
||||||
|
m = re.search(r"\|\s*SecretKey\s*\|\s*`([^`]+)`", line, re.I)
|
||||||
|
if m:
|
||||||
|
secret_key = m.group(1).strip()
|
||||||
|
return secret_id or None, secret_key or None
|
||||||
|
|
||||||
|
# 在存客宝上执行:Nginx 配置检查、重载、端口监听检查
|
||||||
|
CMD = """echo "=== 端口监听 ===" && ss -tlnp | grep -E ':80 |:443 ' || true
|
||||||
|
echo "=== Nginx 测试 ===" && nginx -t 2>&1
|
||||||
|
echo "=== Nginx 重载 ===" && nginx -s reload 2>&1
|
||||||
|
echo "=== kr-kf lytiao 配置存在 ===" && grep -l -E 'kr-kf|lytiao' /www/server/panel/vhost/nginx/*.conf 2>/dev/null | head -5
|
||||||
|
echo "=== 完成 ==="
|
||||||
|
"""
|
||||||
|
|
||||||
|
def main():
|
||||||
|
secret_id = os.environ.get("TENCENTCLOUD_SECRET_ID")
|
||||||
|
secret_key = os.environ.get("TENCENTCLOUD_SECRET_KEY")
|
||||||
|
if not secret_id or not secret_key:
|
||||||
|
sid, skey = _read_creds()
|
||||||
|
secret_id = secret_id or sid
|
||||||
|
secret_key = secret_key or skey
|
||||||
|
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_NginxReload"
|
||||||
|
resp = client.RunCommand(req)
|
||||||
|
inv_id = resp.InvocationId
|
||||||
|
print("✅ 存客宝 Nginx 重载指令已下发 InvocationId:", inv_id)
|
||||||
|
print(" 预计 10s 内生效,请刷新 kr-kf.quwanzhi.com 与 lytiao.com 测试")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
124
01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_存客宝安全组放行443.py
Normal file
124
01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_存客宝安全组放行443.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
腾讯云 API 为存客宝 42.194.245.239 安全组放行 443(修复 kr-kf、lytiao 无法访问)
|
||||||
|
凭证:00_账号与API索引.md 或环境变量
|
||||||
|
依赖:pip install tencentcloud-sdk-python-common tencentcloud-sdk-python-cvm tencentcloud-sdk-python-vpc
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
CKB_IP = "42.194.245.239"
|
||||||
|
REGIONS = ["ap-guangzhou", "ap-beijing", "ap-shanghai"]
|
||||||
|
|
||||||
|
def _find_karuo_ai_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_karuo_ai_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.cvm.v20170312 import cvm_client, models as cvm_models
|
||||||
|
from tencentcloud.vpc.v20170312 import vpc_client, models as vpc_models
|
||||||
|
except ImportError:
|
||||||
|
print("请安装: pip install tencentcloud-sdk-python-common tencentcloud-sdk-python-cvm tencentcloud-sdk-python-vpc")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
cred = credential.Credential(secret_id, secret_key)
|
||||||
|
sg_ids = []
|
||||||
|
region = None
|
||||||
|
for r in REGIONS:
|
||||||
|
try:
|
||||||
|
c = cvm_client.CvmClient(cred, r)
|
||||||
|
req = cvm_models.DescribeInstancesRequest()
|
||||||
|
req.Limit = 100
|
||||||
|
resp = c.DescribeInstances(req)
|
||||||
|
for ins in (getattr(resp, "InstanceSet", None) or []):
|
||||||
|
if CKB_IP in list(getattr(ins, "PublicIpAddresses", None) or []):
|
||||||
|
sg_ids = list(getattr(ins, "SecurityGroupIds", None) or [])
|
||||||
|
region = r
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if sg_ids:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not sg_ids or not region:
|
||||||
|
print("❌ 存客宝 %s 未在腾讯云 CVM 中找到" % CKB_IP)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print("=" * 56)
|
||||||
|
print(" 存客宝安全组放行 443")
|
||||||
|
print("=" * 56)
|
||||||
|
print(" 实例 IP: %s 地域: %s" % (CKB_IP, region))
|
||||||
|
print(" 安全组: %s" % ", ".join(sg_ids))
|
||||||
|
|
||||||
|
vc = vpc_client.VpcClient(cred, region)
|
||||||
|
added = 0
|
||||||
|
for sg_id in sg_ids:
|
||||||
|
try:
|
||||||
|
req = vpc_models.CreateSecurityGroupPoliciesRequest()
|
||||||
|
req.SecurityGroupId = sg_id
|
||||||
|
policy_set = vpc_models.SecurityGroupPolicySet()
|
||||||
|
ing = vpc_models.SecurityGroupPolicy()
|
||||||
|
ing.Protocol = "TCP"
|
||||||
|
ing.Port = "443"
|
||||||
|
ing.CidrBlock = "0.0.0.0/0"
|
||||||
|
ing.Action = "ACCEPT"
|
||||||
|
ing.PolicyDescription = "HTTPS"
|
||||||
|
policy_set.Ingress = [ing]
|
||||||
|
req.SecurityGroupPolicySet = policy_set
|
||||||
|
vc.CreateSecurityGroupPolicies(req)
|
||||||
|
print(" ✅ %s 已添加 443/TCP 入站" % sg_id)
|
||||||
|
added += 1
|
||||||
|
except Exception as e:
|
||||||
|
if "RuleAlreadyExists" in str(e) or "已存在" in str(e):
|
||||||
|
print(" ⏭ %s 443 规则已存在" % sg_id)
|
||||||
|
else:
|
||||||
|
print(" ❌ %s: %s" % (sg_id, e))
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print("=" * 56)
|
||||||
|
if added > 0:
|
||||||
|
print(" 请稍等 10 秒后刷新 kr-kf.quwanzhi.com、lytiao.com 测试")
|
||||||
|
print("=" * 56)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
129
03_卡木(木)/木叶_视频内容/视频切片/脚本/identify_highlights.py
Normal file
129
03_卡木(木)/木叶_视频内容/视频切片/脚本/identify_highlights.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
高光识别 - 用 Gemini 分析视频文字稿,输出高光片段 JSON
|
||||||
|
用于 Soul 派对等长视频切片前的 AI 识别步骤
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Gemini API
|
||||||
|
GEMINI_KEY = os.environ.get("GEMINI_API_KEY") or "AIzaSyCPARryq8o6MKptLoT4STAvCsRB7uZuOK8"
|
||||||
|
DEFAULT_CTA = "关注我,每天学一招私域干货"
|
||||||
|
CLIP_COUNT = 8
|
||||||
|
MIN_DURATION = 45
|
||||||
|
MAX_DURATION = 150
|
||||||
|
|
||||||
|
|
||||||
|
def srt_to_timestamped_text(srt_path: str) -> str:
|
||||||
|
"""将 SRT 转为带时间戳的纯文本"""
|
||||||
|
with open(srt_path, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
lines = []
|
||||||
|
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):
|
||||||
|
start = m[1].replace(",", ".")
|
||||||
|
text = m[3].strip().replace("\n", " ")
|
||||||
|
lines.append(f"[{start}] {text}")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def call_gemini(transcript: str, clip_count: int = CLIP_COUNT) -> str:
|
||||||
|
"""调用 Gemini 分析并返回 JSON(REST API,无额外依赖)"""
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
# gemini-pro 兼容性最好
|
||||||
|
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key={GEMINI_KEY}"
|
||||||
|
prompt = f"""你是一个专业的短视频内容策划师,擅长从长视频中找出最有传播力的片段。
|
||||||
|
|
||||||
|
分析以下视频文字稿,找出 {clip_count} 个最适合做短视频的「高光片段」。
|
||||||
|
为每个片段设计:
|
||||||
|
1. 前3秒Hook:15字以内,让用户停下来
|
||||||
|
2. 结尾CTA:20字以内,引导关注/进群
|
||||||
|
|
||||||
|
判断标准:金句观点(30%)、故事案例(25%)、情绪高点(20%)、实操干货(15%)、悬念钩子(10%)
|
||||||
|
时长要求:每个片段 {MIN_DURATION}-{MAX_DURATION} 秒
|
||||||
|
时间戳必须精确到秒,从文字稿中提取
|
||||||
|
相邻片段至少间隔30秒
|
||||||
|
|
||||||
|
输出严格 JSON 数组,不要其他文字。每个对象必须包含:
|
||||||
|
- title: 简短标题
|
||||||
|
- start_time: "HH:MM:SS"
|
||||||
|
- end_time: "HH:MM:SS"
|
||||||
|
- hook_3sec: 前3秒Hook
|
||||||
|
- cta_ending: 结尾CTA(默认可填 "{DEFAULT_CTA}")
|
||||||
|
- transcript_excerpt: 片段内容前50字
|
||||||
|
- reason: 推荐理由
|
||||||
|
|
||||||
|
视频文字稿:
|
||||||
|
---
|
||||||
|
{transcript[:25000]}
|
||||||
|
---
|
||||||
|
|
||||||
|
只输出JSON数组,不要```json包裹。"""
|
||||||
|
|
||||||
|
body = {
|
||||||
|
"contents": [{"parts": [{"text": prompt}]}],
|
||||||
|
"generationConfig": {"temperature": 0.3, "maxOutputTokens": 8192},
|
||||||
|
}
|
||||||
|
req = urllib.request.Request(
|
||||||
|
url,
|
||||||
|
data=json.dumps(body).encode("utf-8"),
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
method="POST",
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=120) as r:
|
||||||
|
data = json.loads(r.read().decode())
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
err_body = e.read().decode() if e.fp else ""
|
||||||
|
print(f"Gemini API 错误: {e.code} {err_body[:500]}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
candidates = data.get("candidates", [])
|
||||||
|
if not candidates:
|
||||||
|
print("Gemini 未返回内容", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
text = candidates[0].get("content", {}).get("parts", [{}])[0].get("text", "").strip()
|
||||||
|
if text.startswith("```"):
|
||||||
|
text = re.sub(r"^```(?:json)?\s*", "", text)
|
||||||
|
text = re.sub(r"\s*```$", "", text)
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="高光识别 - AI 分析文字稿输出 highlights.json")
|
||||||
|
parser.add_argument("--transcript", "-t", required=True, help="transcript.srt 路径")
|
||||||
|
parser.add_argument("--output", "-o", required=True, help="highlights.json 输出路径")
|
||||||
|
parser.add_argument("--clips", "-n", type=int, default=CLIP_COUNT, help="切片数量")
|
||||||
|
args = parser.parse_args()
|
||||||
|
transcript_path = Path(args.transcript)
|
||||||
|
if not transcript_path.exists():
|
||||||
|
print(f"❌ 文字稿不存在: {transcript_path}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
text = srt_to_timestamped_text(str(transcript_path))
|
||||||
|
if len(text) < 100:
|
||||||
|
print("❌ 文字稿过短,请检查 SRT 格式", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
print("正在调用 Gemini 分析高光片段...")
|
||||||
|
raw = call_gemini(text, args.clips)
|
||||||
|
try:
|
||||||
|
data = json.loads(raw)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"JSON 解析失败: {e}", file=sys.stderr)
|
||||||
|
print("原始输出:", raw[:500], file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if not isinstance(data, list):
|
||||||
|
data = [data]
|
||||||
|
out_path = Path(args.output)
|
||||||
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(out_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||||
|
print(f"✅ 已输出 {len(data)} 个高光片段: {out_path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
176
03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_slice_pipeline.py
Normal file
176
03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_slice_pipeline.py
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Soul 切片一体化流水线
|
||||||
|
视频制作(封面/Hook格式)+ 视频切片
|
||||||
|
|
||||||
|
流程:转录 → 高光识别(AI) → 批量切片 → 增强(封面+Hook+CTA)
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 脚本所在目录
|
||||||
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||||
|
SKILL_DIR = SCRIPT_DIR.parent
|
||||||
|
FONTS_DIR = SKILL_DIR / "fonts"
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd: list, desc: str = "", check: bool = True, timeout: int = 600) -> bool:
|
||||||
|
if desc:
|
||||||
|
print(f" {desc}...", flush=True)
|
||||||
|
try:
|
||||||
|
r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
||||||
|
if check and r.returncode != 0:
|
||||||
|
print(f" ❌ 错误: {r.stderr[:300]}")
|
||||||
|
return False
|
||||||
|
if desc:
|
||||||
|
print(" ✓")
|
||||||
|
return True
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
print(" ⏰ 超时")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Soul 切片一体化流水线")
|
||||||
|
parser.add_argument("--video", "-v", required=True, help="输入视频路径")
|
||||||
|
parser.add_argument("--output", "-o", help="输出目录(默认:视频同目录下 视频名_output)")
|
||||||
|
parser.add_argument("--clips", "-n", type=int, default=8, help="切片数量")
|
||||||
|
parser.add_argument("--skip-transcribe", action="store_true", help="跳过转录(已有 transcript.srt)")
|
||||||
|
parser.add_argument("--skip-highlights", action="store_true", help="跳过高光识别(已有 highlights.json)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
video_path = Path(args.video).resolve()
|
||||||
|
if not video_path.exists():
|
||||||
|
print(f"❌ 视频不存在: {video_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if args.output:
|
||||||
|
base_dir = Path(args.output).resolve()
|
||||||
|
else:
|
||||||
|
base_dir = video_path.parent / (video_path.stem + "_output")
|
||||||
|
base_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
audio_path = base_dir / "audio.wav"
|
||||||
|
transcript_path = base_dir / "transcript.srt"
|
||||||
|
highlights_path = base_dir / "highlights.json"
|
||||||
|
clips_dir = base_dir / "clips"
|
||||||
|
enhanced_dir = base_dir / "clips_enhanced"
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("🎬 Soul 切片流水线:视频制作 + 视频切片")
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"输入视频: {video_path}")
|
||||||
|
print(f"输出目录: {base_dir}")
|
||||||
|
print(f"切片数量: {args.clips}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 1. 提取音频 + 转录
|
||||||
|
if not args.skip_transcribe:
|
||||||
|
if not audio_path.exists():
|
||||||
|
run(
|
||||||
|
["ffmpeg", "-y", "-i", str(video_path), "-vn", "-ar", "16000", "-ac", "1", str(audio_path)],
|
||||||
|
"提取音频",
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
if not transcript_path.exists() and audio_path.exists():
|
||||||
|
print(" MLX Whisper 转录(需 conda mlx-whisper)...")
|
||||||
|
cmd = [
|
||||||
|
"mlx_whisper",
|
||||||
|
str(audio_path),
|
||||||
|
"--model", "mlx-community/whisper-small-mlx",
|
||||||
|
"--language", "zh",
|
||||||
|
"--output-format", "srt",
|
||||||
|
"--output-dir", str(base_dir),
|
||||||
|
"--output-name", "transcript",
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
subprocess.run(cmd, check=True, capture_output=True, text=True, timeout=900)
|
||||||
|
print(" ✓")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" 若未安装 mlx_whisper,请先:")
|
||||||
|
print(" conda activate mlx-whisper")
|
||||||
|
print(" 再运行本脚本")
|
||||||
|
sys.exit(1)
|
||||||
|
if not transcript_path.exists():
|
||||||
|
print(f"❌ 需要 transcript.srt,请先完成转录: {transcript_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 2. 高光识别
|
||||||
|
if not args.skip_highlights:
|
||||||
|
run(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
str(SCRIPT_DIR / "identify_highlights.py"),
|
||||||
|
"--transcript", str(transcript_path),
|
||||||
|
"--output", str(highlights_path),
|
||||||
|
"--clips", str(args.clips),
|
||||||
|
],
|
||||||
|
"高光识别(Gemini)",
|
||||||
|
timeout=60,
|
||||||
|
)
|
||||||
|
if not highlights_path.exists():
|
||||||
|
print(f"❌ 需要 highlights.json: {highlights_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 检查 highlights 格式(支持 {"clips": [...]} 或 [...])
|
||||||
|
with open(highlights_path, "r", encoding="utf-8") as f:
|
||||||
|
hl = json.load(f)
|
||||||
|
if isinstance(hl, dict) and "clips" in hl:
|
||||||
|
clips_list = hl["clips"]
|
||||||
|
else:
|
||||||
|
clips_list = hl if isinstance(hl, list) else []
|
||||||
|
|
||||||
|
if not clips_list:
|
||||||
|
print("❌ highlights.json 为空")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 3. 批量切片
|
||||||
|
clips_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
run(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
str(SCRIPT_DIR / "batch_clip.py"),
|
||||||
|
"--input", str(video_path),
|
||||||
|
"--highlights", str(highlights_path),
|
||||||
|
"--output", str(clips_dir),
|
||||||
|
"--prefix", "soul",
|
||||||
|
],
|
||||||
|
"批量切片",
|
||||||
|
timeout=300,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. 增强(封面 + Hook + CTA)
|
||||||
|
enhanced_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
run(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
str(SCRIPT_DIR / "enhance_clips.py"),
|
||||||
|
"--clips_dir", str(clips_dir),
|
||||||
|
"--highlights", str(highlights_path),
|
||||||
|
"--output_dir", str(enhanced_dir),
|
||||||
|
"--hook_duration", "2.5",
|
||||||
|
"--cta_duration", "4",
|
||||||
|
"--default_cta", "关注我,每天学一招私域干货",
|
||||||
|
],
|
||||||
|
"增强处理(Hook+CTA)",
|
||||||
|
timeout=600,
|
||||||
|
)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("✅ 流水线完成")
|
||||||
|
print("=" * 60)
|
||||||
|
print(f" 切片: {clips_dir}")
|
||||||
|
print(f" 增强: {enhanced_dir}")
|
||||||
|
print(f" 清单: {base_dir / 'clips_manifest.json'}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -56,3 +56,4 @@
|
|||||||
| 2026-02-22 05:59:00 | 🔄 卡若AI 同步 2026-02-22 05:58 | 更新:金仓、水溪整理归档、运营中枢工作台 | 排除 >20MB: 5 个 |
|
| 2026-02-22 05:59:00 | 🔄 卡若AI 同步 2026-02-22 05:58 | 更新:金仓、水溪整理归档、运营中枢工作台 | 排除 >20MB: 5 个 |
|
||||||
| 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 个 |
|
||||||
|
|||||||
@@ -59,3 +59,4 @@
|
|||||||
| 2026-02-22 05:59:00 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 05:58 | 更新:金仓、水溪整理归档、运营中枢工作台 | 排除 >20MB: 5 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
| 2026-02-22 05:59:00 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 05:58 | 更新:金仓、水溪整理归档、运营中枢工作台 | 排除 >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: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) |
|
||||||
|
|||||||
Reference in New Issue
Block a user