From 2314fcc9a880e4eac06b23e68a91c6bf4baf39ca Mon Sep 17 00:00:00 2001 From: karuo Date: Sun, 22 Feb 2026 07:20:19 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20=E5=8D=A1=E8=8B=A5AI=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=202026-02-22=2007:20=20|=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=9A=E6=80=BB=E7=B4=A2=E5=BC=95=E4=B8=8E=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=E3=80=81=E9=87=91=E4=BB=93=E3=80=81=E5=8D=A1=E6=9C=A8=E3=80=81?= =?UTF-8?q?=E8=BF=90=E8=90=A5=E4=B8=AD=E6=9E=A2=E5=B7=A5=E4=BD=9C=E5=8F=B0?= =?UTF-8?q?=20|=20=E6=8E=92=E9=99=A4=20>20MB:=209=20=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../服务器管理/references/存客宝_宝塔管理SKILL.md | 6 + .../存客宝_站点无法访问_ERR_CONNECTION_CLOSED修复.md | 55 ++++++ .../scripts/存客宝_ERR_CONNECTION_CLOSED修复_宝塔终端执行.sh | 30 +++ .../服务器管理/scripts/腾讯云_TAT_修复502_Node项目.py | 126 +++++++++++++ .../服务器管理/scripts/腾讯云_TAT_存客宝站点修复.py | 93 +++++++++ .../服务器管理/scripts/腾讯云_存客宝安全组放行443.py | 124 ++++++++++++ .../视频切片/脚本/identify_highlights.py | 129 +++++++++++++ .../视频切片/脚本/soul_slice_pipeline.py | 176 ++++++++++++++++++ 运营中枢/工作台/gitea_push_log.md | 1 + 运营中枢/工作台/代码管理.md | 1 + 11 files changed, 742 insertions(+) create mode 100644 01_卡资(金)/金仓_存储备份/服务器管理/references/存客宝_站点无法访问_ERR_CONNECTION_CLOSED修复.md create mode 100644 01_卡资(金)/金仓_存储备份/服务器管理/scripts/存客宝_ERR_CONNECTION_CLOSED修复_宝塔终端执行.sh create mode 100644 01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_修复502_Node项目.py create mode 100644 01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_存客宝站点修复.py create mode 100644 01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_存客宝安全组放行443.py create mode 100644 03_卡木(木)/木叶_视频内容/视频切片/脚本/identify_highlights.py create mode 100644 03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_slice_pipeline.py diff --git a/.gitignore b/.gitignore index 68b030e5..7ed5b578 100644 --- a/.gitignore +++ b/.gitignore @@ -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/.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_卡木(木)/木叶_视频内容/视频切片/脚本/存客宝体系_成片TEMP_MPY_wvf_snd.mp4 # === 自动排除结束 === diff --git a/01_卡资(金)/金仓_存储备份/服务器管理/references/存客宝_宝塔管理SKILL.md b/01_卡资(金)/金仓_存储备份/服务器管理/references/存客宝_宝塔管理SKILL.md index 18db7d3b..2d713bb6 100644 --- a/01_卡资(金)/金仓_存储备份/服务器管理/references/存客宝_宝塔管理SKILL.md +++ b/01_卡资(金)/金仓_存储备份/服务器管理/references/存客宝_宝塔管理SKILL.md @@ -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 改为存客宝) diff --git a/01_卡资(金)/金仓_存储备份/服务器管理/references/存客宝_站点无法访问_ERR_CONNECTION_CLOSED修复.md b/01_卡资(金)/金仓_存储备份/服务器管理/references/存客宝_站点无法访问_ERR_CONNECTION_CLOSED修复.md new file mode 100644 index 00000000..eeddd1da --- /dev/null +++ b/01_卡资(金)/金仓_存储备份/服务器管理/references/存客宝_站点无法访问_ERR_CONNECTION_CLOSED修复.md @@ -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 均正常才能访问 diff --git a/01_卡资(金)/金仓_存储备份/服务器管理/scripts/存客宝_ERR_CONNECTION_CLOSED修复_宝塔终端执行.sh b/01_卡资(金)/金仓_存储备份/服务器管理/scripts/存客宝_ERR_CONNECTION_CLOSED修复_宝塔终端执行.sh new file mode 100644 index 00000000..d2405206 --- /dev/null +++ b/01_卡资(金)/金仓_存储备份/服务器管理/scripts/存客宝_ERR_CONNECTION_CLOSED修复_宝塔终端执行.sh @@ -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 入站规则。" diff --git a/01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_修复502_Node项目.py b/01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_修复502_Node项目.py new file mode 100644 index 00000000..d6665df0 --- /dev/null +++ b/01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_修复502_Node项目.py @@ -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()) diff --git a/01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_存客宝站点修复.py b/01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_存客宝站点修复.py new file mode 100644 index 00000000..46bd22d6 --- /dev/null +++ b/01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_存客宝站点修复.py @@ -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()) diff --git a/01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_存客宝安全组放行443.py b/01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_存客宝安全组放行443.py new file mode 100644 index 00000000..647f35b8 --- /dev/null +++ b/01_卡资(金)/金仓_存储备份/服务器管理/scripts/腾讯云_存客宝安全组放行443.py @@ -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()) diff --git a/03_卡木(木)/木叶_视频内容/视频切片/脚本/identify_highlights.py b/03_卡木(木)/木叶_视频内容/视频切片/脚本/identify_highlights.py new file mode 100644 index 00000000..9daef1d7 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/视频切片/脚本/identify_highlights.py @@ -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() diff --git a/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_slice_pipeline.py b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_slice_pipeline.py new file mode 100644 index 00000000..5449a7f9 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_slice_pipeline.py @@ -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() diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index e1d10e1b..1a268801 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -56,3 +56,4 @@ | 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:39:21 | 🔄 卡若AI 同步 2026-02-22 06:39 | 更新:总索引与入口、水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 8 个 | +| 2026-02-22 06:47:15 | 🔄 卡若AI 同步 2026-02-22 06:47 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 8 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index fdec0897..e4a58170 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -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 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: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) |