🔄 卡若AI 同步 2026-03-11 15:07 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个

This commit is contained in:
2026-03-11 15:07:53 +08:00
parent 2c41b9f890
commit d64a8cab0e
9 changed files with 360 additions and 279 deletions

View File

@@ -2,33 +2,31 @@
name: 多平台分发
description: >
一键将视频分发到 5 个平台抖音、B站、视频号、小红书、快手
支持定时排期30-120分钟随机间隔、并行分发、去重、失败自动重试
封面统一用视频第一帧Cookie 统一管理防重复获取
API 优先策略:视频号纯 API、B站 bilibili-api-python、抖音纯 API
支持定时排期第1条立即发后续 30-120 分钟随机间隔)、并行分发、去重、失败自动重试
triggers: 多平台分发、一键分发、全平台发布、批量分发、视频分发
owner: 木叶
group: 木
version: "3.1"
updated: "2026-03-10"
version: "4.0"
updated: "2026-03-11"
---
# 多平台分发 Skillv3.1
# 多平台分发 Skillv4.0
> **核心能力**一条命令将成片目录下的所有视频同时发布到 5 个主流平台。
> **平台覆盖**抖音、B站、视频号、小红书、快手
> **技术路线**:抖音纯 API逆向 VODB站 bilibili-api-python API视频号/小红书/快手 Playwright 自动化。
> **全链路**:定时排期 → 并行分发 → 去重 → 失败重试 → Cookie 预警 → 结果日志。
> **核心原则**API 发布为主Playwright 为辅。确保确定性地分发到各平台。
> **v4.0 变更**:视频号已切换为纯 API、统一元数据生成器、定时排期优化、简介/标签/分区自动填充
---
## 一、平台与实现方式
| 平台 | 实现方式 | 定时发布 | Cookie 有效期 | 119 场实测 |
| 平台 | 实现方式 | 定时发布 | Cookie 有效期 | 120 场实测 |
|------|----------|----------|---------------|------------|
| **抖音** | 纯 APIVOD + bd-ticket-guard | API timing_ts | ~2-4h | 账号封禁,预检拦截 |
| **B站** | bilibili-api-python API 优先 → Playwright 兜底 | API dtime | ~6 个月 | 15/15 成功 |
| **视频号** | Playwright headless 自动化 | UI 定时(降级立即) | ~24-48h | 15/15 成功 |
| **小红书** | Playwright headless v2 自动化 | UI 定时(降级立即) | ~1-3 天 | 15/15 成功(修复后) |
| **快手** | Playwright headless 自动化 | UI 定时成功 | ~7-30 天 | 15/15 成功(含重试) |
| **视频号** | **纯 API**DFS 上传 + post_create | API 原生支持 | ~24-48h | 12/12 成功 |
| **B站** | **bilibili-api-python** API 优先 → Playwright 兜底 | API `dtime` | ~6 个月 | 12/12 成功 |
| **小红书** | Playwright headless 自动化 | UI 定时(降级立即) | ~1-3 天 | 12/12 成功 |
| **快手** | Playwright headless 自动化 | UI 定时 | ~7-30 天 | Cookie 过期 |
| **抖音** | 纯 APIVOD + bd-ticket-guard | API `timing_ts` | ~2-4h | 账号封禁中 |
---
@@ -37,132 +35,100 @@ updated: "2026-03-10"
```bash
cd /Users/karuo/Documents/个人/卡若AI/03_卡木/木叶_视频内容/多平台分发/脚本
# 定时排期并行分发(默认 30-120 分钟随机间隔
# 定时排期第1条立即后续 30-120min 随机间隔
python3 distribute_all.py
# 立即发布(不排期)
# 立即全部发布
python3 distribute_all.py --now
# 自定义排期间隔
python3 distribute_all.py --min-gap 30 --max-gap 120 --max-hours 24
# 只发指定平台
python3 distribute_all.py --platforms 抖音 B站
python3 distribute_all.py --platforms 视频号 B站
# 检查 Cookie + 重试失败
python3 distribute_all.py --check
python3 distribute_all.py --retry
# 分发单条 / 自定义目录
python3 distribute_all.py --video "/path/to/video.mp4"
# 自定义视频目录
python3 distribute_all.py --video-dir "/path/to/videos/"
# 跳过去重 / 串行调试
python3 distribute_all.py --no-dedup
python3 distribute_all.py --serial
# 检查 Cookie / 重试失败
python3 distribute_all.py --check
python3 distribute_all.py --retry
```
---
## 三、首次使用流程
## 三、定时排期v4.0 优化)
```
1. 安装依赖
pip3 install httpx playwright cryptography Pillow
playwright install chromium
### 3.1 排期规则
- **第 1 条**:立即发布(`first_delay=0`
- **第 2 条起**:前一条 + random(30, 120) 分钟
- 若总跨度 > 24h自动按比例压缩
- 12 条视频典型跨度 ~10-14h
2. 逐个平台登录(只需首次)
python3 ../抖音发布/脚本/douyin_login.py
python3 ../B站发布/脚本/bilibili_login.py
python3 ../视频号发布/脚本/channels_login.py
python3 ../小红书发布/脚本/xiaohongshu_login.py
python3 ../快手发布/脚本/kuaishou_login.py
### 3.2 各平台定时实现
3. 检查 Cookie 状态
python3 distribute_all.py --check
4. 一键分发
python3 distribute_all.py
```
---
## 四、Cookie 管理
### 4.1 统一管理器
`cookie_manager.py` 提供:
- 加载 Playwright storage_state.json
- 检查 Cookie 有效期ok / warning / expiring_soon / expired
- 提供 cookie_str / cookie_dict
- 批量检查所有平台状态
### 4.2 有效期对比
| 平台 | Cookie 有效期 | 建议刷新频率 |
|------|-------------|-------------|
| 抖音 | ~2-4h | 每次使用前 |
| B站 | ~6 个月 | 半年一次 |
| 视频号 | ~24-48h | 每天 |
| 小红书 | ~1-3 天 | 2-3 天 |
| 快手 | ~7-30 天 | 每周 |
### 4.3 防重复获取
Cookie 文件保存后自动记录时间戳,`cookie_manager.py` 通过文件修改时间判断年龄。
若 Cookie 仍有效,不会触发重新登录。
---
## 五、视频处理
### 5.1 封面提取
`video_utils.py` 使用 ffmpeg 提取视频第一帧0.5s 处)作为封面:
```python
from video_utils import extract_cover
cover_path = extract_cover("/path/to/video.mp4")
```
### 5.2 视频元数据
```python
from video_utils import get_video_info
info = get_video_info("/path/to/video.mp4")
# {'duration': 180.5, 'width': 1080, 'height': 1920, ...}
```
---
## 六、定时排期
### 6.1 排期逻辑schedule_generator.py
- 相邻视频随机间隔 30-120 分钟
- 若总跨度 > 24h按比例自动压缩
- 15 条视频典型跨度 ~16-18h
### 6.2 各平台定时支持
| 平台 | 定时方式 | 状态 |
| 平台 | 定时方式 | 参数 |
|------|----------|------|
| 抖音 | API `timing_ts`Unix 时间戳) | 已实现 |
| B站 | API `dtime`Unix 时间戳) | 已实现 |
| 快手 | Playwright UI「定时发布」 | 已实现,成功率高 |
| 视频号 | Playwright UI「定时发布」 | 已实现UI 匹配待优化 |
| 小红书 | Playwright UI「定时发布」 | 已实现UI 匹配待优化 |
| B站 | API `meta.dtime` | Unix 时间戳(秒 |
| 视频号 | API 暂不支持原生定时 | 描述中标注时间/手动设置 |
| 抖音 | API `timing_ts` | Unix 时间戳 |
| 快手 | Playwright UI | `schedule_helper.py` |
| 小红书 | Playwright UI | `schedule_helper.py` |
定时失败时自动降级为立即发布,不影响视频发出。
---
## 四、元数据自动生成v4.0 新增)
`video_metadata.py` 根据文件名自动生成各平台差异化内容:
```python
from video_metadata import VideoMeta
meta = VideoMeta.from_filename("AI最大的缺点是上下文太短.mp4")
meta.title("B站") # 优化后的标题
meta.description("B站") # 标题 + 标签 + 品牌标记
meta.tags_str("B站") # AI工具,效率提升,Soul派对,...
meta.bilibili_meta() # B站投稿完整 meta含 tid/tag/desc
meta.title_short() # 小红书短标题≤20字
meta.hashtags("视频号") # #AI工具 #效率提升 ... #小程序 卡若创业派对
```
### 4.1 内容结构
- **标题**:手工优化标题库优先,否则从文件名智能提取
- **简介**:标题 + 换行 + 话题标签 + `#小程序 卡若创业派对`
- **标签**基于关键词匹配AI/创业/副业/Soul 等 12 类)+ 通用标签
- **分区**B站 tid=160生活>日常)
- **风控过滤**`content_filter.py` 自动替换敏感词70+ 映射,严格/宽松分级)
---
## 五、商品链接/小黄车(调研结果)
| 平台 | 功能 | 实现方式 | 状态 |
|------|------|----------|------|
| B站 | 花火计划商品链接 | 需企业认证 + 品牌合作授权 | 需手动配置 |
| 视频号 | 挂小程序 | 视频号主页 > 设置 > 服务菜单 > 小程序 | 需手动配置 |
| 抖音 | 小黄车 | 需开通橱窗(粉丝 ≥1000 | 账号封禁 |
| 快手 | 商品卡片 | 需开通快手小店 | 需手动配置 |
| 小红书 | 商品笔记 | 需开通小红书店铺 | 需手动配置 |
**当前做法**:在描述中统一添加 `#小程序 卡若创业派对` 引导用户搜索。
---
## 六、Cookie 管理
`cookie_manager.py` 统一管理:
- 中央存储:`多平台分发/cookies/{平台}_cookies.json`
- 自动迁移:旧路径 → 中央存储(首次使用时)
- API 预检5 平台各自 auth API 校验有效性
- 防重复登录:有效 Cookie 不触发重新获取
---
## 七、去重机制
- 基于 `publish_log.json`JSON Lines 格式)记录每次发布结果
- 日志:`publish_log.json`JSON Lines
- 去重键:`(平台名, 视频文件名)`
- 双保险:调度器层distribute_all.py+ 平台层(各 publish_one
- 独立运行单平台脚本也有去重
- `--no-dedup` 跳过去重,`--retry` 重跑失败任务
- 双保险:调度器层 + 平台层
- `--no-dedup` 跳过,`--retry` 重跑失败
---
@@ -170,143 +136,31 @@ info = get_video_info("/path/to/video.mp4")
```
木叶_视频内容/
├── 多平台分发/ ← 本 Skill调度器 + 共享工具)
├── 多平台分发/ ← 本 Skill调度器 + 共享工具)
│ ├── SKILL.md
│ └── 脚本/
│ ├── distribute_all.py # 主调度器 v3
│ ├── schedule_generator.py # 定时排期生成器
│ ├── schedule_helper.py # Playwright 定时发布辅助
│ ├── publish_result.py # 统一发布结果 + 去重
│ ├── title_generator.py # 智能标题生成
│ ├── cookie_manager.py # Cookie 统一管理
│ ├── distribute_all.py # 主调度器 v4
│ ├── video_metadata.py # 统一元数据生成器v4 新增)
│ ├── schedule_generator.py # 定时排期v4: 第1条立即发
│ ├── schedule_helper.py # Playwright 定时 UI 辅助
│ ├── publish_result.py # 统一 PublishResult + 去重
│ ├── title_generator.py # 标题生成(被 video_metadata 取代)
│ ├── content_filter.py # 敏感词过滤70+ 映射)
│ ├── cookie_manager.py # Cookie 统一管理5 平台 API 预检)
│ ├── video_utils.py # 视频处理(封面、元数据)
│ └── publish_log.json # 发布结果日志(自动生成)
├── 抖音发布/ ← 纯 APIVOD + bd-ticket-guard
├── B站发布/ ← bilibili-api-python API + Playwright 兜底
├── 视频号发布/ ← Playwright headless
├── 小红书发布/ ← Playwright headless
└── 快手发布/ ← Playwright headless
│ └── publish_log.json # 发布日志
├── 抖音发布/ ← 纯 API账号封禁中
├── B站发布/ ← bilibili-api-python API
├── 视频号发布/ ← 纯 APIDFS 协议v5
├── 小红书发布/ ← Playwright headless
└── 快手发布/ ← Playwright headless
```
---
## 九、相关文件
| 文件 | 说明 |
|------|------|
| `脚本/distribute_all.py` | **主调度器 v3**:定时排期 + 并行分发 + 去重 + 重试 |
| `脚本/schedule_generator.py` | 排期生成30-120min 间隔,超 24h 压缩) |
| `脚本/schedule_helper.py` | Playwright 定时发布 UI 交互辅助 |
| `脚本/publish_result.py` | 统一 PublishResult + 日志 + 去重 |
| `脚本/title_generator.py` | 智能标题(字典优先 → 文件名自动) |
| `脚本/cookie_manager.py` | Cookie 统一管理有效期检查、API 预检 5 平台) |
| `脚本/content_filter.py` | 敏感词/风控词过滤政治、金融、医疗、平台词70+ 替换映射) |
| `脚本/video_utils.py` | 视频处理(封面提取、元数据) |
---
## 十、踩坑经验119 场全量分发)
### 10.1 视频号/小红书定时发布 UI 匹配失败
- **现象**`schedule_helper.py` 找到了「定时发布」文字但日期时间 input 未匹配到
- **原因**:这两个平台的日期选择器是自定义组件(非原生 `input[type="date"]`),需要点击日历格子
- **影响**:定时功能降级为立即发布,视频仍正常发出
- **待优化**:研究各平台 datepicker 的具体 DOM 结构,用 JS 直接操作 React state
### 10.2 快手「未找到上传控件」
- **现象**:部分视频上传时 `input[type="file"]` 元素未出现
- **原因**:快手页面加载时偶发 JS 渲染延迟,或上次草稿弹窗阻塞了上传区
- **解决**:脚本已加「放弃草稿」逻辑,重试后全部成功
### 10.3 B站 API 偶发超时后 Playwright 兜底也失败
- **现象**2 条视频 API 超时后降级到 Playwright但 Playwright 也找不到上传控件
- **原因**B站创作中心在短时间内连续打开浏览器可能触发人机验证
- **解决**:重试时纯 API 直接成功5.3-5.7sPlaywright 只在 API 彻底不可用时才需要
### 10.4 抖音 Cookie 过期(全局)
- **现象**Cookie 检查显示有效expiry > now但 API 返回「Cookie 已过期」
- **原因**:抖音 API 的 `user_info` 接口在 Cookie 过期前约 1-2h 就开始拒绝
- **解决**:重新运行 `python3 douyin_login.py` 扫码登录
### 10.5 并行分发的 Playwright 资源竞争
- **现象**:多个 Playwright 同时运行时 CPU 飙高、偶发超时
- **影响**:视频号/小红书/快手 三路 Playwright 并行,部分上传时间从 2s 涨到 5s
- **建议**:服务器部署时限制并发数(如最多 3 个 Playwright 同时)
### 10.6 小红书发布按钮点击不生效119场
- **现象**:脚本日志声称 15/15 成功,实际只有 4 条到达平台
- **根因**:初版 `pub.click(force=True)` 失败率高达 ~70%,且成功判定逻辑过于宽松(默认 status="reviewing"
- **修复**
1. JS 精准点击红色发布按钮(用 `getComputedStyle` 筛选 backgroundColor 含 255 的 button
2. Playwright `force-click` 兜底
3. 处理二次确认弹窗
4. 未检测到明确成功信号时,跳转到笔记管理页二次验证
5. 连续 3 次失败自动熔断(防封号)
- **成功率**:修复后 10/10100%
### 10.7 小红书假成功日志污染去重119场
- **现象**publish_log.json 记录 15 条 success=True导致去重跳过不会重试
- **根因**:旧版 success 判定将所有未报错的提交都标记为 success
- **修复**
1. 清理 publish_log.json 中的虚假记录
2. 只有明确的成功信号页面重置、URL 跳转、"发布成功"文本)才标记 success=True
3. 不确定时走笔记管理页验证
### 10.8 视频号描述写入空白119场
- **现象**:所有视频发布后描述为空,视频号使用 Wujie 微前端框架
- **根因**`.input-editor` 在 Shadow DOM 内,常规 `.fill()` 无法写入
- **修复**clipboard/insertText 方式注入,先 focus → selectAll → insertText
### 10.9 抖音账号投稿功能封禁
- **现象**API 返回 status_code=-20 "视频投稿功能已封禁"
- **影响**:所有视频无法发布到抖音
- **处理**:预检时明确提示封禁状态,跳过抖音
### 10.10 账号预检机制v3.1 新增)
- **所有平台发布前统一调用 `cookie_manager.check_cookie_valid()`**
- 视频号POST auth_data API
- B站GET /x/web-interface/nav
- 快手GET cp.kuaishou.com/rest/pc/user/myInfo
- 小红书GET creator.xiaohongshu.com/api/galaxy/user/info
- 抖音GET /web/api/media/user_info/
- 预检不通过则终止发布,避免浪费时间上传后才发现 Cookie 过期
---
## 十一、万推Web 版视频分发系统)
卡若AI 的多平台分发能力已整合到万推项目(`/Users/karuo/Documents/开发/3、自营项目/万推/`),提供 Web GUI + API 接口:
| 组件 | 说明 |
|------|------|
| **万推后端** | FastAPI`backend/main.py`,端口 8000 |
| **万推前端** | Vue 3 毛玻璃风格,`frontend/index.html` |
| **直连发布器** | `backend/direct_publisher.py`Playwright 操作 5 平台创作者中心 |
| **uploader 体系** | `backend/uploader/` 下 5 平台独立 uploader |
| **Cookie 自动获取** | `backend/cookie_fetcher.py`,打开浏览器让用户扫码 |
### 启动万推
```bash
cd /Users/karuo/Documents/开发/3、自营项目/万推/backend
pip3 install -r requirements.txt
playwright install chromium
python3 main.py
# 访问 http://localhost:8000
```
### 万推与卡若AI脚本的关系
- 卡若AI `distribute_all.py`:命令行一键分发,适合自动化流水线
- 万推 Web 界面:用户手动管理账号和分发,适合日常运营
- 两者共享 Playwright + Cookie 方案,互为补充
---
## 九、依赖
- Python 3.10+
- httpx, playwright, playwright-stealth, cryptography, Pillow
- biliupB站上传 API
- httpx, bilibili-api-python, playwright, Pillow
- ffmpeg/ffprobe系统已安装
- Playwright chromium`playwright install chromium`
- `playwright install chromium`

View File

@@ -38,6 +38,7 @@ from publish_result import (PublishResult, print_summary, save_results,
load_published_set, load_failed_tasks)
from title_generator import generate_title
from schedule_generator import generate_schedule, format_schedule
from video_metadata import VideoMeta
PLATFORM_CONFIG = {
"抖音": {
@@ -190,7 +191,8 @@ async def distribute_to_platform(
total = len(to_publish)
pub_fn = getattr(module, "publish_one_compat", None) or module.publish_one
for i, vp in enumerate(to_publish):
title = generate_title(vp.name, titles_dict)
vmeta = VideoMeta.from_filename(str(vp))
title = vmeta.title(platform)
stime = publish_schedule[i] if publish_schedule else None
try:
r = await pub_fn(str(vp), title, i + 1, total, scheduled_time=stime)

View File

@@ -128,3 +128,15 @@
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/深度AI模型对比 哪个才是真正的AI不是语言模型.mp4", "title": "深度AI模型对比 哪个才是真正的AI不是语言模型 #Soul派对 #创业日记", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.2712419033050537, "timestamp": "2026-03-11 12:13:53"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/疗愈师配AI助手能收多少钱 一个小团队5万到10万.mp4", "title": "疗愈师配AI助手能收多少钱 一个小团队5万到10万 #Soul派对 #创业日记", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.2667992115020752, "timestamp": "2026-03-11 12:13:56"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/赚钱没那么复杂,自信心才是核心问题.mp4", "title": "赚钱没那么复杂,自信心才是核心问题 #Soul派对 #创业日记", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.2669076919555664, "timestamp": "2026-03-11 12:14:00"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/AI最大的缺点是上下文太短这样来解决.mp4", "title": "AI的短板是记忆太短上下文一长就废了这个方法能解决", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 2.443204164505005, "timestamp": "2026-03-11 15:07:02"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/AI每天剪1000个视频 M4电脑24T素材库全网分发.mp4", "title": "M4芯片+24T素材库AI每天剪1000条视频自动全网分发", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.1888279914855957, "timestamp": "2026-03-11 15:07:05"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/Soul派对变现全链路 发视频就有钱,后端全解决.mp4", "title": "Soul派对怎么商业转化发视频就有收益后端体系全部搞定", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.1997511386871338, "timestamp": "2026-03-11 15:07:08"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/从0到切片发布 AI自动完成每天副业30条视频.mp4", "title": "从零到切片发布AI全自动完成每天副业产出30条视频", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.19605207443237305, "timestamp": "2026-03-11 15:07:11"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/做副业的基本条件 苹果电脑和特殊访问工具.mp4", "title": "做副业的两个基本条件一台Mac和一个上网工具", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.19453787803649902, "timestamp": "2026-03-11 15:07:15"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/切片分发全自动化 从视频到发布一键完成.mp4", "title": "从录制到发布全自动化,一键切片分发五大平台", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.19373202323913574, "timestamp": "2026-03-11 15:07:18"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/创业团队4人平分25有啥危险 先跑钱再谈股权.mp4", "title": "创业团队4人平分25%股权有啥风险?先跑出收入再谈分配", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.2604191303253174, "timestamp": "2026-03-11 15:07:21"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/坚持到120场是什么感觉 方向越确定执行越坚决.mp4", "title": "坚持到第120场派对是什么感觉方向越清晰执行越坚决", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.28436827659606934, "timestamp": "2026-03-11 15:07:24"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/帮人装AI一单300到1000块传统行业也能做.mp4", "title": "帮传统行业的人装AI工具一单收300到1000块简单好做", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.20317912101745605, "timestamp": "2026-03-11 15:07:28"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/深度AI模型对比 哪个才是真正的AI不是语言模型.mp4", "title": "深度对比各大AI模型哪个才是真正的智能而不只是语言模型", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.33212804794311523, "timestamp": "2026-03-11 15:07:31"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/疗愈师配AI助手能收多少钱 一个小团队5万到10万.mp4", "title": "疗愈师+AI助手组合一个小团队月收5万到10万", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.2739429473876953, "timestamp": "2026-03-11 15:07:34"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片/赚钱没那么复杂,自信心才是核心问题.mp4", "title": "获得收益真没那么复杂,自信心才是卡住你的核心问题", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.27978515625, "timestamp": "2026-03-11 15:07:38"}

View File

@@ -1,10 +1,11 @@
#!/usr/bin/env python3
"""
定时排期生成器 — 为 N 条视频生成发布时间表
定时排期生成器 v2
规则:
1. 相邻视频间隔 30-120 分钟(随机
2. 若总时长 > max_hours按比例压缩至 max_hours 内
3. 第一条视频在 first_delay 分钟后发布
1. 第一条视频 **立即发布**first_delay=0
2. 第二条起,每条间隔 30~120 分钟(随机)
3. 若总时长超过 max_hours按比例压缩
4. 支持各平台原生定时 API 所需的 datetime 对象
"""
import random
from datetime import datetime, timedelta
@@ -15,16 +16,19 @@ def generate_schedule(
min_gap: int = 30,
max_gap: int = 120,
max_hours: float = 24.0,
first_delay: int = 125,
first_delay: int = 0,
start_time: datetime = None,
) -> list[datetime]:
"""
返回 n 个 datetime,每个对应一条视频的定时发布时间。
返回 n 个 datetime
- times[0] = now + first_delay默认 0 = 立即)
- times[1..] = 前一条 + random(min_gap, max_gap) 分钟
"""
if n <= 0:
return []
base = start_time or datetime.now()
if n == 1:
return [base + timedelta(minutes=first_delay)]
@@ -34,8 +38,8 @@ def generate_schedule(
if total_min > max_min:
ratio = max_min / total_min
first_delay = int(first_delay * ratio)
gaps = [max(1, int(g * ratio)) for g in gaps]
first_delay = max(0, int(first_delay * ratio))
gaps = [max(5, int(g * ratio)) for g in gaps]
times = []
cur = base + timedelta(minutes=first_delay)
@@ -48,11 +52,11 @@ def generate_schedule(
def format_schedule(videos: list[str], times: list[datetime]) -> str:
"""格式化排期表用于打印"""
"""格式化排期表"""
lines = [" 序号 | 发布时间 | 间隔 | 视频"]
lines.append(" " + "-" * 70)
for i, (v, t) in enumerate(zip(videos, times)):
gap = ""
gap = "立即" if i == 0 else ""
if i > 0:
delta = (t - times[i - 1]).total_seconds() / 60
gap = f"{delta:.0f}min"
@@ -61,11 +65,11 @@ def format_schedule(videos: list[str], times: list[datetime]) -> str:
total = (times[-1] - times[0]).total_seconds() / 3600 if len(times) > 1 else 0
lines.append(" " + "-" * 70)
lines.append(f" 总跨度: {total:.1f}h | 首条: {times[0].strftime('%H:%M')} | 末条: {times[-1].strftime('%H:%M')}")
lines.append(f" 总跨度: {total:.1f}h | 首条: {times[0].strftime('%H:%M')}(立即) | 末条: {times[-1].strftime('%H:%M')}")
return "\n".join(lines)
if __name__ == "__main__":
schedule = generate_schedule(15)
names = [f"视频_{i+1}.mp4" for i in range(15)]
schedule = generate_schedule(12)
names = [f"视频_{i+1}.mp4" for i in range(12)]
print(format_schedule(names, schedule))

View File

@@ -0,0 +1,209 @@
#!/usr/bin/env python3
"""
统一视频元数据生成器 v1
根据视频文件名自动生成:标题、简介、标签、分区
支持各平台差异化输出B站/视频号/小红书/快手/抖音)
用法:
meta = VideoMeta.from_filename("AI最大的缺点是上下文太短这样来解决.mp4")
print(meta.title("B站"))
print(meta.description("B站"))
print(meta.tags("B站"))
"""
from __future__ import annotations
import re
from dataclasses import dataclass, field
from pathlib import Path
from content_filter import filter_for_platform
BRAND_TAG = "#卡若创业派对"
MINI_PROGRAM = "#小程序 卡若创业派对"
PLATFORM_CATEGORIES = {
"B站": {"tid": 160, "name": "生活 > 日常"},
"视频号": {"category": "科技数码"},
"小红书": {"category": "科技数码"},
"快手": {"category": "生活"},
"抖音": {"category": "科技"},
}
COMMON_TAGS = ["Soul派对", "创业", "认知觉醒", "副业", "商业思维"]
KEYWORD_TAGS = {
"AI": ["AI工具", "人工智能", "效率提升"],
"副业": ["副业", "副业入门", "副业收入"],
"创业": ["创业", "创业心态", "创业故事"],
"Soul": ["Soul派对", "Soul创业"],
"切片": ["切片分发", "自动化", "内容分发"],
"股权": ["创业股权", "团队管理"],
"疗愈": ["疗愈", "AI赋能", "疗愈商业"],
"模型": ["AI对比", "深度思考", "AI模型"],
"赚钱": ["创业心态", "商业思维"],
"装AI": ["AI服务", "传统行业"],
"坚持": ["坚持的力量", "自律"],
"视频": ["内容创作", "视频分发"],
}
CURATED_TITLES: dict[str, dict] = {
"AI最大的缺点是上下文太短这样来解决.mp4": {
"title": "AI的短板是记忆太短上下文一长就废了这个方法能解决",
"tags_extra": ["AI工具", "效率提升"],
},
"AI每天剪1000个视频 M4电脑24T素材库全网分发.mp4": {
"title": "M4芯片+24T素材库AI每天剪1000条视频自动全网分发",
"tags_extra": ["AI剪辑", "内容工厂"],
},
"Soul派对变现全链路 发视频就有钱,后端全解决.mp4": {
"title": "Soul派对怎么变现发视频就有收益后端体系全部搞定",
"tags_extra": ["Soul派对", "副业收入"],
},
"从0到切片发布 AI自动完成每天副业30条视频.mp4": {
"title": "从零到切片发布AI全自动完成每天副业产出30条视频",
"tags_extra": ["AI副业", "切片分发"],
},
"做副业的基本条件 苹果电脑和特殊访问工具.mp4": {
"title": "做副业的两个基本条件一台Mac和一个上网工具",
"tags_extra": ["副业入门", "工具推荐"],
},
"切片分发全自动化 从视频到发布一键完成.mp4": {
"title": "从录制到发布全自动化,一键切片分发五大平台",
"tags_extra": ["自动化", "内容分发"],
},
"创业团队4人平分25有啥危险 先跑钱再谈股权.mp4": {
"title": "创业团队4人平分25%股权有啥风险?先跑出收入再谈分配",
"tags_extra": ["创业股权", "团队管理"],
},
"坚持到120场是什么感觉 方向越确定执行越坚决.mp4": {
"title": "坚持到第120场派对是什么感觉方向越清晰执行越坚决",
"tags_extra": ["Soul派对", "坚持的力量"],
},
"帮人装AI一单300到1000块传统行业也能做.mp4": {
"title": "帮传统行业的人装AI工具一单收300到1000块简单好做",
"tags_extra": ["AI服务", "传统行业"],
},
"深度AI模型对比 哪个才是真正的AI不是语言模型.mp4": {
"title": "深度对比各大AI模型哪个才是真正的智能而不只是语言模型",
"tags_extra": ["AI对比", "深度思考"],
},
"疗愈师配AI助手能收多少钱 一个小团队5万到10万.mp4": {
"title": "疗愈师+AI助手组合一个小团队月收5万到10万",
"tags_extra": ["AI赋能", "疗愈商业"],
},
"赚钱没那么复杂,自信心才是核心问题.mp4": {
"title": "赚钱真没那么复杂,自信心才是卡住你的核心问题",
"tags_extra": ["创业心态", "自信"],
},
}
@dataclass
class VideoMeta:
"""单条视频的元数据"""
filename: str
base_title: str
tags_extra: list[str] = field(default_factory=list)
@classmethod
def from_filename(cls, filename: str) -> VideoMeta:
fname = Path(filename).name
curated = CURATED_TITLES.get(fname)
if curated:
return cls(
filename=fname,
base_title=curated["title"],
tags_extra=curated.get("tags_extra", []),
)
stem = Path(fname).stem
stem = re.sub(r'^\d+[._\-\s]*', '', stem)
stem = stem.replace('_', ' ').replace(' ', ' ').strip()
extra = []
for kw, tags in KEYWORD_TAGS.items():
if kw in stem:
extra.extend(tags)
extra = list(dict.fromkeys(extra))[:4]
return cls(filename=fname, base_title=stem or fname, tags_extra=extra)
def _smart_tags(self, platform: str) -> list[str]:
seen = set()
result = []
for t in self.tags_extra + COMMON_TAGS:
if t not in seen:
seen.add(t)
result.append(t)
return result[:8]
def title(self, platform: str, max_len: int = 80) -> str:
t = filter_for_platform(self.base_title, platform)
return t[:max_len]
def title_short(self, max_len: int = 20) -> str:
"""小红书标题≤20字"""
parts = re.split(r'[,!?\s]+', self.base_title)
return parts[0][:max_len] if parts else self.base_title[:max_len]
def hashtags(self, platform: str) -> str:
"""# 标签字符串"""
tags = self._smart_tags(platform)
parts = [f"#{t}" for t in tags]
parts.append(MINI_PROGRAM)
return " ".join(parts)
def description(self, platform: str, max_len: int = 500) -> str:
"""完整描述 = 标题 + 换行 + 标签 + 品牌"""
title = self.title(platform)
tags = self.hashtags(platform)
desc = f"{title}\n\n{tags}"
return filter_for_platform(desc[:max_len], platform)
def tags_list(self, platform: str) -> list[str]:
return self._smart_tags(platform)
def tags_str(self, platform: str) -> str:
"""逗号分隔B站 API 用)"""
return ",".join(self._smart_tags(platform))
def category(self, platform: str) -> dict:
return PLATFORM_CATEGORIES.get(platform, {})
def bilibili_meta(self) -> dict:
"""B站投稿需要的完整 meta"""
return {
"copyright": 1,
"source": "",
"desc": self.description("B站"),
"desc_format_id": 0,
"dynamic": "",
"interactive": 0,
"open_elec": 0,
"no_reprint": 1,
"subtitles": {"lan": "", "open": 0},
"tag": self.tags_str("B站"),
"tid": 160,
"title": self.title("B站", 80),
"up_close_danmaku": False,
"up_close_reply": False,
}
def generate_all_meta(video_dir: str) -> list[VideoMeta]:
"""批量生成目录下所有视频的元数据"""
videos = sorted(Path(video_dir).glob("*.mp4"))
return [VideoMeta.from_filename(str(v)) for v in videos]
if __name__ == "__main__":
from pathlib import Path
VIDEO_DIR = "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片"
metas = generate_all_meta(VIDEO_DIR)
for m in metas:
print(f"\n{'='*60}")
print(f"文件: {m.filename}")
print(f" B站标题: {m.title('B站')}")
print(f" B站简介: {m.description('B站')[:80]}...")
print(f" B站标签: {m.tags_str('B站')}")
print(f" 视频号: {m.description('视频号')[:80]}...")
print(f" 小红书标题: {m.title_short()}")