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

This commit is contained in:
2026-02-22 07:20:19 +08:00
parent 4089a94390
commit 2314fcc9a8
11 changed files with 742 additions and 0 deletions

1
.gitignore vendored
View File

@@ -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
# === 自动排除结束 === # === 自动排除结束 ===

View File

@@ -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 改为存客宝)

View File

@@ -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 均正常才能访问

View File

@@ -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 入站规则。"

View 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())

View 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())

View 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())

View 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 分析并返回 JSONREST 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秒Hook15字以内让用户停下来
2. 结尾CTA20字以内引导关注/进群
判断标准:金句观点(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()

View 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()

View File

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

View File

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